Merge pull request #76 from JamesLewisLiu/gitadora-feature-add

GITADORA Plugin Feature Add
This commit is contained in:
Freddie W 2025-12-05 04:40:28 +08:00 committed by GitHub
commit 997d141b3b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 32694 additions and 141 deletions

View File

@ -1,15 +1,18 @@
GITADORA Plugin for Asphyxia-Core GITADORA Plugin for Asphyxia-Core
================================= =================================
![Version: v1.3.0](https://img.shields.io/badge/version-v1.3.0-blue) ![Version: v1.4.0](https://img.shields.io/badge/version-v1.4.0-blue)
This plugin is based on converted from public-exported Asphyxia's Routes. This plugin is based on converted from public-exported Asphyxia's Routes.
Supported Versions Supported Versions
================== ==================
- Tri-Boost Re:EVOLVE
- Matixx - Matixx
- Exchain - EXCHAIN
- NEX+AGE - NEX+AGE
- HIGH-VOLTAGE
- FUZZ-UP
- GALAXY WAVE
When Plugin Doesn't work correctly / Startup Error on Plugin When Plugin Doesn't work correctly / Startup Error on Plugin
------------------------------------------------------------ ------------------------------------------------------------
@ -26,9 +29,42 @@ Known Issues
* Special Premium Encore on Nextage is unimplemented. However, a workaround is available. Try it. * Special Premium Encore on Nextage is unimplemented. However, a workaround is available. Try it.
* Friends and Rivals are unimplemented. * Friends and Rivals are unimplemented.
Shared Data Options
===================
Two experimental options allow operators to share data across versions:
* **Shared Favorite Songs** (`shared_favorite_songs`, default: `false`): When enabled, favorite lists are unified across Guitar Freaks, DrumMania, and supported versions.
* **Shared Song Scores** (`shared_song_scores`, default: `false`): When enabled, the server merges the best results for each chart across every stored version and saves them under a shared version identifier. The merged record uses the following shape (fields marked with `//` describe their meaning):
```
scores: {
"<musicid>": {
update: [<seq>, <new_skill>], // Highest new_skill value seen and its associated seq
diffs: {
"<seq>": {
perc: <number>, // Highest achievement percentage
rank: <number>, // Highest rank reached for the chart
clear: <boolean>, // Whether the chart has been cleared
fc: <boolean>, // Whether a full combo was achieved
ex: <boolean>, // Whether an excellent was achieved
meter: "<string>",// Best meter value as a stringified bigint
prog: <number>, // Highest progression value
}
}
}
}
```
Scores are stored under `version: "shared"` but are automatically applied to the active module when loading a profile, ensuring players benefit from their best combined results regardless of the client version.
Release Notes Release Notes
============= =============
v1.4.0
----------------
* Added support for Tri-Boost Re:EVOLVE, HIGH-VOLTAGE, FUZZ-UP, GALAXY WAVE
* Bugfix for launch core with "--dev/--console"
v1.3.0 v1.3.0
---------------- ----------------
* Added experimental 'Shared Favorite Songs' option. If disabled, players will be able to keep separate lists of favorite songs for each version of Gitadora, as well as between Guitar Freaks and Drummania. Enable this option to have a single unified list of favorite songs for both games, and across all versions. Default is false, to match original arcade behaviour. * Added experimental 'Shared Favorite Songs' option. If disabled, players will be able to keep separate lists of favorite songs for each version of Gitadora, as well as between Guitar Freaks and Drummania. Enable this option to have a single unified list of favorite songs for both games, and across all versions. Default is false, to match original arcade behaviour.

View File

@ -11,6 +11,37 @@ export function getEncoreStageData(info: EamuseInfo): EncoreStageData {
const level: number = U.GetConfig("encore_version") const level: number = U.GetConfig("encore_version")
const ntDummyEncore = U.GetConfig("nextage_dummy_encore") const ntDummyEncore = U.GetConfig("nextage_dummy_encore")
switch (getVersion(info)) { switch (getVersion(info)) {
case 'galaxywave':
return {
level,
musics: [
2866, // Calm days
2893, // 愛はToxic! feat.Lilymone
2885, // Astrum
2897, // DESPERATE ERROR
2884, // Multiverse
2919, // DOGMA
2922, // Stay By My Side
2937, // Prog for your Soul
2963, // Zero Visibility
2939, // Hopeful Daybreak!!!
2956, // Over Time Groove
]
}
case 'fuzzup':
return {
level,
musics: [
2812, // THE LAST OF FIREFACE
2814, // ENCOUNT
2783, // Q転直下
2848, // Bloody Iron Maiden
2860, // Serious Joke
2844, // HyperNebula
2877, // AVEL
2892, // Elliptic Orbits
]
}
case 'highvoltage': case 'highvoltage':
return { return {
level, level,
@ -58,7 +89,7 @@ export function getEncoreStageData(info: EamuseInfo): EncoreStageData {
5060, // EXCELSIOR DIVE (CLASSIC) 5060, // EXCELSIOR DIVE (CLASSIC)
2530, // The ULTIMATES -CHRONICLE- 2530, // The ULTIMATES -CHRONICLE-
2581, // 幸せの代償 2581, // 幸せの代償
5046 // Rock to Infinity (CLASSIC) 5046, // Rock to Infinity (CLASSIC)
] ]
} }
case 'matixx': case 'matixx':
@ -77,7 +108,22 @@ export function getEncoreStageData(info: EamuseInfo): EncoreStageData {
2496, // CAPTURING XANADU 2496, // CAPTURING XANADU
2497, // Physical Decay 2497, // Physical Decay
2499, // Cinnamon 2499, // Cinnamon
2498 // けもののおうじゃ★めうめう 2498, // けもののおうじゃ★めうめう
]
}
case 're':
return {
level,
musics: [
2341, // Anathema
2384, // White Forest
2393, // REFLEXES MANIPULATION
2392, // 主亡き機械人形のまなざし
2406, // Exclamation
2414, // MEDUSA
2422, // BLACK ROSES
2411, // ギタドラシカ
2432, // Durian
] ]
} }
default: default:

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,12 +1,14 @@
import Logger from "../../utils/logger"; import Logger from "../../utils/logger";
import { CommonMusicData } from "../../models/commonmusicdata"; import { CommonMusicData } from "../../models/commonmusicdata";
export enum DATAVersion { export enum DATAVersion {
GALAXYWAVE = "gw",
FUZZUP = "fz",
HIGHVOLTAGE = "hv", HIGHVOLTAGE = "hv",
NEXTAGE = "nt", NEXTAGE = "nt",
EXCHAIN = "ex", EXCHAIN = "ex",
MATTIX = "mt" MATTIX = "mt",
TBRE = "re"
} }
const allowedFormats = ['.json', '.xml', '.b64'] const allowedFormats = ['.json', '.xml', '.b64']
@ -46,7 +48,15 @@ export async function readMDBFile(path: string, processHandler?: processRawDataH
break; break;
case '.b64': case '.b64':
const buff = await IO.ReadFile(path, 'utf-8'); const buff = await IO.ReadFile(path, 'utf-8');
const json = Buffer.from(buff, 'base64').toString('utf-8') const bufferCtor = (globalThis as {
Buffer?: {
from(input: string, encoding: string): { toString(encoding: string): string }
}
}).Buffer
if (!bufferCtor) {
throw new Error('Buffer is not available in the current environment.')
}
const json = bufferCtor.from(buff, 'base64').toString('utf-8')
// Uncomment to save the decoded base64 file as JSON. // Uncomment to save the decoded base64 file as JSON.
// await IO.WriteFile(path.replace(".b64",".json"), json) // await IO.WriteFile(path.replace(".b64",".json"), json)
result = JSON.parse(json) result = JSON.parse(json)
@ -54,6 +64,13 @@ export async function readMDBFile(path: string, processHandler?: processRawDataH
default: default:
throw `Invalid MDB file type: ${fileType}. Only .json, .xml, .b64 are supported.` throw `Invalid MDB file type: ${fileType}. Only .json, .xml, .b64 are supported.`
} }
// Some MDB sources may not provide seq_release_state. Ensure it is present for every song entry.
result.music.forEach((entry) => {
if (entry.seq_release_state == null) {
entry.seq_release_state = K.ITEM('s32', 1)
}
})
let gfCount = result.music.filter((e) => e.cont_gf["@content"][0]).length let gfCount = result.music.filter((e) => e.cont_gf["@content"][0]).length
let dmCount = result.music.filter((e) => e.cont_dm["@content"][0]).length let dmCount = result.music.filter((e) => e.cont_dm["@content"][0]).length
@ -63,6 +80,10 @@ export async function readMDBFile(path: string, processHandler?: processRawDataH
export function gameVerToDataVer(ver: string): DATAVersion { export function gameVerToDataVer(ver: string): DATAVersion {
switch(ver) { switch(ver) {
case 'galaxywave':
return DATAVersion.GALAXYWAVE
case 'fuzzup':
return DATAVersion.FUZZUP
case 'highvoltage': case 'highvoltage':
return DATAVersion.HIGHVOLTAGE return DATAVersion.HIGHVOLTAGE
case 'nextage': case 'nextage':
@ -70,8 +91,9 @@ export function gameVerToDataVer(ver: string): DATAVersion {
case 'exchain': case 'exchain':
return DATAVersion.EXCHAIN return DATAVersion.EXCHAIN
case 'matixx': case 'matixx':
default:
return DATAVersion.MATTIX return DATAVersion.MATTIX
default:
return DATAVersion.TBRE
} }
} }
@ -93,9 +115,18 @@ export function findMDBFile(fileNameWithoutExtension: string, path: string = nul
} }
for (const ext of allowedFormats) { for (const ext of allowedFormats) {
const filePath = path + fileNameWithoutExtension + ext const candidateFileNames = ext === ".xml"
if (IO.Exists(filePath)) { ? [
return filePath `mdb_${fileNameWithoutExtension}${ext}`,
`${fileNameWithoutExtension}${ext}`,
]
: [`${fileNameWithoutExtension}${ext}`]
for (const fileName of candidateFileNames) {
const filePath = path + fileName
if (IO.Exists(filePath)) {
return filePath
}
} }
} }
@ -108,7 +139,7 @@ export async function loadSongsForGameVersion(gameVer: string, processHandler?:
let mdbFile = findMDBFile(ver, mdbFolder) let mdbFile = findMDBFile(ver, mdbFolder)
if (mdbFile == null) { if (mdbFile == null) {
throw `No valid MDB files were found in the data/mdb subfolder. Ensure that this folder contains at least one of the following: ${ver}.json, ${ver}.xml or ${ver}.b64` throw `No valid MDB files were found in the data/mdb subfolder. Ensure that this folder contains at least one of the following: ${ver}.json, mdb_${ver}.xml (${ver}.xml as fallback) or ${ver}.b64`
} }
const music = await readMDBFile(mdbFile, processHandler ?? defaultProcessRawXmlData) const music = await readMDBFile(mdbFile, processHandler ?? defaultProcessRawXmlData)
@ -140,7 +171,8 @@ export async function defaultProcessRawXmlData(path: string): Promise<CommonMusi
cont_dm: K.ITEM('bool', dm == 0 ? 0 : 1), cont_dm: K.ITEM('bool', dm == 0 ? 0 : 1),
is_secret: K.ITEM('bool', m.number("is_secret", 0)), is_secret: K.ITEM('bool', m.number("is_secret", 0)),
is_hot: K.ITEM('bool', type == 2 ? 0 : 1), is_hot: K.ITEM('bool', type == 2 ? 0 : 1),
data_ver: K.ITEM('s32', m.number("data_ver", 115)), data_ver: K.ITEM('s32', m.number("data_ver", 255)),
seq_release_state: K.ITEM('s32', 1),
diff: K.ARRAY('u16', [ diff: K.ARRAY('u16', [
d[0], d[0],
d[1], d[1],

File diff suppressed because it is too large Load Diff

View File

@ -60,7 +60,6 @@ export async function applySharedFavoriteMusicToExtra(refid : string, extra : Ex
let favoriteMusic = await loadFavoriteMusic(refid) let favoriteMusic = await loadFavoriteMusic(refid)
if (favoriteMusic == null) { if (favoriteMusic == null) {
logger.debugInfo(`No shared favourite music available for profile ${refid}. Using game specific favorites. Favorites will be saved as shared favorites at the end of the game session.`);
return return
} }
@ -84,10 +83,17 @@ export async function saveFavoriteMusic(refid: string, data : FavoriteMusic) : P
}, data) }, data)
} }
export async function loadFavoriteMusic(refid : string) : Promise<FavoriteMusic> export async function loadFavoriteMusic(refid : string) : Promise<FavoriteMusic | null>
{ {
return await DB.FindOne<FavoriteMusic>(refid, { const favoriteMusic = await DB.FindOne<FavoriteMusic>(refid, {
collection: 'favoritemusic' collection: 'favoritemusic'
}) })
if (!favoriteMusic) {
logger.debugInfo(`No shared favourite music available for profile ${refid}. Using game specific favorites. Favorites will be saved as shared favorites at the end of the game session.`);
return null
}
return favoriteMusic
} }

View File

@ -3,6 +3,7 @@ import { findMDBFile, readMDBFile, loadSongsForGameVersion } from "../data/mdb";
import { CommonMusicDataField } from "../models/commonmusicdata"; import { CommonMusicDataField } from "../models/commonmusicdata";
import Logger from "../utils/logger" import Logger from "../utils/logger"
import { getPlayableMusicResponse, PlayableMusicResponse } from "../models/Responses/playablemusicresponse"; import { getPlayableMusicResponse, PlayableMusicResponse } from "../models/Responses/playablemusicresponse";
import { isAsphyxiaDebugMode } from "../utils/index";
const logger = new Logger("MusicList") const logger = new Logger("MusicList")
@ -31,5 +32,10 @@ export const playableMusic: EPR = async (info, data, send) => {
let response : PlayableMusicResponse = getPlayableMusicResponse(music) let response : PlayableMusicResponse = getPlayableMusicResponse(music)
await send.object(response) await send.object(response)
if (isAsphyxiaDebugMode()) {
await IO.WriteFile(`apisamples/playableMusicList.json`, JSON.stringify(music, null, 4))
}
}; };

View File

@ -0,0 +1,90 @@
import { PLUGIN_VER } from "../const";
import { Scores } from "../models/scores";
type ScoreDiff = Scores['scores'][string]['diffs'][string];
type ScoreEntry = Scores['scores'][string];
function selectBetterMeter(existing?: string, incoming?: string): string {
if (!incoming) return existing ?? "0";
if (!existing) return incoming;
try {
return BigInt(incoming) > BigInt(existing) ? incoming : existing;
} catch (e) {
return incoming || existing;
}
}
function mergeScoreDiff(existing: ScoreDiff | undefined, incoming: ScoreDiff): ScoreDiff {
if (!existing) return incoming;
return {
perc: Math.max(existing.perc ?? 0, incoming.perc ?? 0),
rank: Math.max(existing.rank ?? 0, incoming.rank ?? 0),
meter: selectBetterMeter(existing.meter, incoming.meter),
prog: Math.max(existing.prog ?? 0, incoming.prog ?? 0),
clear: (existing.clear ?? false) || (incoming.clear ?? false),
fc: (existing.fc ?? false) || (incoming.fc ?? false),
ex: (existing.ex ?? false) || (incoming.ex ?? false),
};
}
function mergeScoreEntry(existing: ScoreEntry | undefined, incoming: ScoreEntry): ScoreEntry {
const mergedDiffs: ScoreEntry['diffs'] = existing ? { ...existing.diffs } : {};
for (const [seq, diff] of Object.entries(incoming.diffs)) {
mergedDiffs[seq] = mergeScoreDiff(mergedDiffs[seq], diff);
}
const mergedUpdate = existing?.update ? [...existing.update] : [0, 0];
if (incoming.update && (mergedUpdate[1] ?? 0) < incoming.update[1]) {
mergedUpdate[0] = incoming.update[0];
mergedUpdate[1] = incoming.update[1];
}
return {
update: mergedUpdate,
diffs: mergedDiffs,
};
}
function mergeScoreCollections(target: Scores['scores'], incoming: Scores['scores']): Scores['scores'] {
const merged = { ...target } as Scores['scores'];
for (const [mid, entry] of Object.entries(incoming)) {
merged[mid] = mergeScoreEntry(merged[mid], entry);
}
return merged;
}
async function persistSharedScores(refid: string, game: 'gf' | 'dm', scores: Scores['scores']) {
await DB.Upsert<Scores>(refid, { collection: 'scores', game, version: 'shared' }, {
collection: 'scores',
version: 'shared',
pluginVer: PLUGIN_VER,
game,
scores,
});
}
/**
* Load and merge scores across all versions for a player/game pair and persist them under version "shared".
*/
export async function getMergedSharedScores(refid: string, game: 'gf' | 'dm'): Promise<Scores['scores']> {
const scoreDocs = await DB.Find<Scores>(refid, { collection: 'scores', game });
const mergedScores = scoreDocs.reduce<Scores['scores']>((acc, doc) => mergeScoreCollections(acc, doc.scores), {} as Scores['scores']);
await persistSharedScores(refid, game, mergedScores);
return mergedScores;
}
/**
* Merge the provided score set into the shared scores document for the player/game pair.
*/
export async function mergeScoresIntoShared(refid: string, game: 'gf' | 'dm', scores: Scores['scores']) {
const existingShared = await DB.FindOne<Scores>(refid, { collection: 'scores', game, version: 'shared' });
const mergedScores = mergeScoreCollections(existingShared?.scores ?? {}, scores);
await persistSharedScores(refid, game, mergedScores);
}

View File

@ -1,5 +1,8 @@
/// <reference lib="es2020.bigint" />
import { getEncoreStageData } from "../data/extrastage"; import { getEncoreStageData } from "../data/extrastage";
import Logger from "../utils/logger"; import Logger from "../utils/logger";
import { getVersion } from "../utils";
const logger = new Logger('info'); const logger = new Logger('info');
export const shopInfoRegist: EPR = async (info, data, send) => { export const shopInfoRegist: EPR = async (info, data, send) => {
@ -21,90 +24,343 @@ export const gameInfoGet: EPR = async (info, data, send) => {
const eventData = getEventDataResponse() const eventData = getEventDataResponse()
const extraData = getEncoreStageData(info) const extraData = getEncoreStageData(info)
const VER = getVersion(info)
await send.object({ if (VER == "galaxywave"){
now_date: K.ITEM('u64', BigInt(Date.now())), await send.object({
extra: { now_date: K.ITEM('u64', BigInt(Date.now())),
extra_lv: K.ITEM('u8', extraData.level), extra: {
extramusic: { extra_lv: K.ITEM('s32', extraData.level),
music: extraData.musics.map(mid => { extramusic: {
return { music: extraData.musics.map(mid => {
musicid: K.ITEM('s32', mid), return {
get_border: K.ITEM('u8', 0), 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) },
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),
},
}, },
}, infect_music: { term: K.ITEM('u8', 0) },
update_prog: { term: K.ITEM('u8', 0) }, unlock_challenge: { term: K.ITEM('s32', 0) },
rockwave: { event_list: {} }, battle: { term: K.ITEM('s32', 0) },
general_term: {}, battle_chara: { term: K.ITEM('s32', 0) },
jubeat_omiyage_challenge: {}, data_ver_limit: { term: K.ITEM('s32', 0) },
kac2017: {}, ea_pass_propel: { term: K.ITEM('s32', 0) },
nostalgia_concert: {}, monthly_skill: {
trbitemdata: {}, term: K.ITEM('u8', 0),
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: { target_music: {
music_id: K.ARRAY('s32', [0, 0, 0, 0, 0, 0]), music: {
musicid: K.ITEM('s32', 0),
},
}, },
}, },
}, update_prog: { term: K.ITEM('s32', 0) },
...eventData, rockwave: {
}); event_list: {
event: {
data_id: K.ITEM('s32', 0),
data_version: K.ITEM('s32', 0),
event_id: K.ITEM('s32', 0),
event_type: K.ITEM('s32', 0),
start_date: K.ITEM('u64', BigInt(0)),
end_date: K.ITEM('u64', BigInt(0)),
is_open: K.ITEM('bool', 0),
bg_no: K.ITEM('s32', 0),
target_musicid: K.ITEM('s32', 0),
clear_border: K.ITEM('s32', 0),
reward_musicid: K.ITEM('s32', 0),
reward_musicid_border_list: K.ITEM('s32', 0),
reward_stickerid: K.ITEM('s32', 0),
reward_stickerid_list: K.ITEM('s32', 0),
reward_stickerid_border_list: K.ITEM('s32', 0),
firstbit: K.ITEM('s32', 0),
quest_no: K.ITEM('s32', 0),
target_music_list: {
music: {
musicid: K.ITEM('s32', 0),
}
},
ranking_list: K.ITEM('u64', BigInt(0)),
}
}
},
general_term: {
termdata: {
type: K.ITEM('str', ''),
term: K.ITEM('s32', 0),
state: K.ITEM('s32', 0),
start_date_ms: K.ITEM('u64', BigInt(0)),
end_date_ms: K.ITEM('u64', BigInt(0)),
}
},
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]),
},
},
},
...eventData,
});
} else if (VER == "fuzzup"){
await send.object({
now_date: K.ITEM('u64', BigInt(Date.now())),
extra: {
extra_lv: K.ITEM('u8', extraData.level),
extramusic: {
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) },
battle: { term: K.ITEM('u8', 0) },
battle_chara: { term: K.ITEM('u8', 0) },
data_ver_limit: { term: K.ITEM('s32', 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]),
},
},
},
livehouse: {
event_list: {
event: {
is_open: K.ITEM('bool', 0),
term: K.ITEM('u8', 0),
start_date_ms: K.ITEM('u64', BigInt(0)),
end_date_ms: K.ITEM('u64', BigInt(0)),
livehouse_name: K.ITEM('str', 'Asphyxia'),
reward_list: {
reward: {
reward_id: K.ITEM('s32', 0),
reward_kind: K.ITEM('s32', 0),
reward_itemid: K.ITEM('s32', 0),
unlock_border: K.ITEM('s32', 0),
},
},
requirements_musicid: K.ITEM('s32', 0),
member_table: K.ITEM('s32', 0),
},
},
bonus: {
term: K.ITEM('u8', 0),
stage_bonus: K.ITEM('s32', 0),
charm_bonus: K.ITEM('s32', 0),
start_date_ms: K.ITEM('u64', BigInt(0)),
end_date_ms: K.ITEM('u64', BigInt(0)),
},
},
...eventData,
});
}//Older
else {
await send.object({
now_date: K.ITEM('u64', BigInt(Date.now())),
extra: {
extra_lv: K.ITEM('u8', extraData.level),
extramusic: {
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) },
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]),
},
},
},
KAC2016: {
is_entry: K.ITEM('bool', 0),
term: K.ITEM('u8', 0),
musicid: K.ITEM('s32', 0),
},
KAC2016_skill_ranking: { term: K.ITEM('u8', 0) },
season_sticker: { term: K.ITEM('u8', 0) },
paseli_point_lose: { term: K.ITEM('u8', 0) },
nostal_link: { term: K.ITEM('u8', 0) },
encore_advent: { term: K.ITEM('u8', 0) },
sdvx_stamprally: { term: K.ITEM('u8', 0) },
sdvx_stamprally2: { term: K.ITEM('u8', 0) },
floor_policy_2_info: { term: K.ITEM('u8', 0) },
long_otobear_fes_2: {
term: K.ITEM('u8', 0),
bonus_musicid: K.ITEM('s32', 0),
},
...eventData,
});
}
}; };
function getEventDataResponse() { function getEventDataResponse() {
@ -114,6 +370,41 @@ function getEventDataResponse() {
}, },
bear_fes: {}, bear_fes: {},
nextadium: {}, nextadium: {},
galaxy_parade: {
corner_list: {
corner: {
is_open: K.ITEM('bool', 0),
data_ver: K.ITEM('s32', 0),
genre: K.ITEM('s32', 0),
corner_id: K.ITEM('s32', 0),
corner_name: K.ITEM('str', ''),
start_date_ms: K.ITEM('u64', BigInt(0)),
end_date_ms: K.ITEM('u64', BigInt(0)),
requirements_musicid: K.ITEM('s32', 0),
reward_list: {
reward: {
reward_id: K.ITEM('s32', 0),
reward_kind: K.ITEM('s32', 0),
reward_itemid: K.ITEM('s32', 0),
unlock_border: K.ITEM('s32', 0),
}
},
}
},
gacha_table: {
chara_odds: {
chara_id: K.ITEM('s32', 0),
odds: K.ITEM('s32', 0),
}
},
bonus: {
term: K.ITEM('s32', 0),
stage_bonus: K.ITEM('s32', 0),
charm_bonus: K.ITEM('s32', 0),
start_date_ms: K.ITEM('u64', BigInt(0)),
end_date_ms: K.ITEM('u64', BigInt(0)),
}
},
}; };
const time = BigInt(31536000); const time = BigInt(31536000);
@ -129,7 +420,8 @@ function getEventDataResponse() {
term: K.ITEM('u8', 0), term: K.ITEM('u8', 0),
start_date_ms: K.ITEM('u64', time), start_date_ms: K.ITEM('u64', time),
end_date_ms: K.ITEM('u64', time), end_date_ms: K.ITEM('u64', time),
bonus_musicid: {}, //bonus_musicid: {},
bonus_musicid: K.ITEM('s32', 0),
}; };
addition[`sdvx_stamprally3`] = obj; addition[`sdvx_stamprally3`] = obj;
addition[`chronicle_1`] = obj; addition[`chronicle_1`] = obj;
@ -159,14 +451,42 @@ function getEventDataResponse() {
addition['monstar_subjugation'][`monstar_subjugation_${i}`] = obj; addition['monstar_subjugation'][`monstar_subjugation_${i}`] = obj;
addition['bear_fes'][`bear_fes_${i}`] = obj; addition['bear_fes'][`bear_fes_${i}`] = obj;
} }
if (i <= 2) {
addition[`gitadora_oracle_${i}`] = {
term: K.ITEM('u8', 0),
bonus_musicid: K.ITEM('s32', 0),
};
addition[`gitadora_oracle_${i}`] = {
term: K.ITEM('u8', 0),
bonus_musicid: K.ITEM('s32', 0),
};
}
if (i <= 3) { if (i <= 3) {
addition[`kouyou_challenge_${i}`] = { addition[`kouyou_challenge_${i}`] = {
term: K.ITEM('u8', 0), term: K.ITEM('u8', 0),
bonus_musicid: K.ITEM('s32', 0), bonus_musicid: K.ITEM('s32', 0),
}; };
addition[`dokidoki_valentine2_${i}`] = {
term: K.ITEM('u8', 0),
bonus_musicid: K.ITEM('s32', 0),
};
addition[`wakuteka_whiteday2_${i}`] = { term: K.ITEM('u8', 0) };
addition[`ohanami_challenge_${i}`] = {
term: K.ITEM('u8', 0),
bonus_musicid: K.ITEM('s32', 0),
};
addition[`otobear_in_the_tsubo_${i}`] = {
term: K.ITEM('u8', 0),
bonus_musicid: K.ITEM('s32', 0),
};
addition[`summer_craft_${i}`] = {
term: K.ITEM('u8', 0),
bonus_musicid: K.ITEM('s32', 0),
};
} }
} }
return addition return addition
} }

View File

@ -1,3 +1,5 @@
/// <reference lib="es2020.bigint" />
import { getDefaultPlayerInfo, PlayerInfo } from "../models/playerinfo"; import { getDefaultPlayerInfo, PlayerInfo } from "../models/playerinfo";
import { PlayerRanking } from "../models/playerranking"; import { PlayerRanking } from "../models/playerranking";
import { getDefaultProfile, Profile } from "../models/profile"; import { getDefaultProfile, Profile } from "../models/profile";
@ -8,7 +10,7 @@ import { getDefaultScores, Scores } from "../models/scores";
import { PLUGIN_VER } from "../const"; import { PLUGIN_VER } from "../const";
import Logger from "../utils/logger" import Logger from "../utils/logger"
import { isAsphyxiaDebugMode } from "../utils/index"; import { isAsphyxiaDebugMode, isSharedSongScoresEnabled } from "../utils/index";
import { SecretMusicEntry } from "../models/secretmusicentry"; import { SecretMusicEntry } from "../models/secretmusicentry";
import { CheckPlayerResponse, getCheckPlayerResponse } from "../models/Responses/checkplayerresponse"; import { CheckPlayerResponse, getCheckPlayerResponse } from "../models/Responses/checkplayerresponse";
import { getPlayerStickerResponse, PlayerStickerResponse } from "../models/Responses/playerstickerresponse"; import { getPlayerStickerResponse, PlayerStickerResponse } from "../models/Responses/playerstickerresponse";
@ -18,6 +20,7 @@ import { getDefaultBattleDataResponse } from "../models/Responses/battledataresp
import { applySharedFavoriteMusicToExtra, saveSharedFavoriteMusicFromExtra } from "./FavoriteMusic"; import { applySharedFavoriteMusicToExtra, saveSharedFavoriteMusicFromExtra } from "./FavoriteMusic";
import { getPlayerRecordResponse } from "../models/Responses/playerrecordresponse"; import { getPlayerRecordResponse } from "../models/Responses/playerrecordresponse";
import { getPlayerPlayInfoResponse, PlayerPlayInfoResponse } from "../models/Responses/playerplayinforesponse"; import { getPlayerPlayInfoResponse, PlayerPlayInfoResponse } from "../models/Responses/playerplayinforesponse";
import { getMergedSharedScores, mergeScoresIntoShared } from "./SharedScores";
const logger = new Logger("profiles") const logger = new Logger("profiles")
@ -70,6 +73,7 @@ export const getPlayer: EPR = async (info, data, send) => {
const time = BigInt(31536000); const time = BigInt(31536000);
const dm = isDM(info); const dm = isDM(info);
const game = dm ? 'dm' : 'gf'; const game = dm ? 'dm' : 'gf';
const sharedScoresEnabled = isSharedSongScoresEnabled();
logger.debugInfo(`Loading ${game} profile for player ${no} with refid: ${refid}`) logger.debugInfo(`Loading ${game} profile for player ${no} with refid: ${refid}`)
const name = await DB.FindOne<PlayerInfo>(refid, { const name = await DB.FindOne<PlayerInfo>(refid, {
@ -82,8 +86,8 @@ export const getPlayer: EPR = async (info, data, send) => {
const gfRecord = await getRecord(refid, version, 'gf') const gfRecord = await getRecord(refid, version, 'gf')
const dmExtra = await getExtra(refid, version, 'dm') const dmExtra = await getExtra(refid, version, 'dm')
const gfExtra = await getExtra(refid, version, 'gf') const gfExtra = await getExtra(refid, version, 'gf')
const dmScores = (await getScore(refid, version, 'dm')).scores const dmScores = sharedScoresEnabled ? await getMergedSharedScores(refid, 'dm') : (await getScore(refid, version, 'dm')).scores
const gfScores = (await getScore(refid, version, 'gf')).scores const gfScores = sharedScoresEnabled ? await getMergedSharedScores(refid, 'gf') : (await getScore(refid, version, 'gf')).scores
const profile = dm ? dmProfile : gfProfile; const profile = dm ? dmProfile : gfProfile;
const extra = dm ? dmExtra : gfExtra; const extra = dm ? dmExtra : gfExtra;
@ -213,13 +217,33 @@ export const getPlayer: EPR = async (info, data, send) => {
recommend_musicid_list: K.ARRAY('s32', extra.recommend_musicid_list ?? Array(5).fill(-1)), recommend_musicid_list: K.ARRAY('s32', extra.recommend_musicid_list ?? Array(5).fill(-1)),
record, record,
groove: { groove: {
extra_gauge: K.ITEM('s32', profile.extra_gauge), extra_gauge: K.ITEM('s32', (profile.extra_gauge+95)),
encore_gauge: K.ITEM('s32', profile.encore_gauge), encore_gauge: K.ITEM('s32', profile.encore_gauge),
encore_cnt: K.ITEM('s32', profile.encore_cnt), encore_cnt: K.ITEM('s32', profile.encore_cnt),
encore_success: K.ITEM('s32', profile.encore_success), encore_success: K.ITEM('s32', profile.encore_success),
unlock_point: K.ITEM('s32', profile.unlock_point), unlock_point: K.ITEM('s32', profile.unlock_point),
}, },
musiclist: { '@attr': { nr: musicdata.length }, musicdata }, musiclist: { '@attr': { nr: musicdata.length }, musicdata },
deluxe: {
deluxe_content: K.ITEM('s32', 0),
target_id: K.ITEM('s32', 0),
multiply: K.ITEM('s32', 0),
point: K.ITEM('s32', 0),
},
galaxy_parade: {
score_list: {},
last_corner_id: K.ITEM('s32', 0),
chara_list: {},
last_sort_category: K.ITEM('s32', 0),
last_sort_order: K.ITEM('s32', 0),
team_member: {
chara_id_guitar: K.ITEM('s32', 0),
chara_id_bass: K.ITEM('s32', 0),
chara_id_drum: K.ITEM('s32', 0),
chara_id_free1: K.ITEM('s32', 0),
chara_id_free2: K.ITEM('s32', 0),
},
},
}; };
const playerRanking = await getPlayerRanking(refid, version, game) const playerRanking = await getPlayerRanking(refid, version, game)
@ -227,15 +251,55 @@ export const getPlayer: EPR = async (info, data, send) => {
const addition: any = { const addition: any = {
monstar_subjugation: {}, monstar_subjugation: {},
bear_fes: {}, bear_fes: {},
galaxy_parade: {
corner_list: {
corner: {
is_open: K.ITEM('bool', 0),
data_ver: K.ITEM('s32', 0),
genre: K.ITEM('s32', 0),
corner_id: K.ITEM('s32', 0),
corner_name: K.ITEM('str', ''),
start_date_ms: K.ITEM('u64', BigInt(0)),
end_date_ms: K.ITEM('u64', BigInt(0)),
requirements_musicid: K.ITEM('s32', 0),
reward_list: {
reward: {
reward_id: K.ITEM('s32', 0),
reward_kind: K.ITEM('s32', 0),
reward_itemid: K.ITEM('s32', 0),
unlock_border: K.ITEM('s32', 0),
}
},
}
},
gacha_table: {
chara_odds: {
chara_id: K.ITEM('s32', 0),
odds: K.ITEM('s32', 0),
}
},
bonus: {
term: K.ITEM('s32', 0),
stage_bonus: K.ITEM('s32', 0),
charm_bonus: K.ITEM('s32', 0),
start_date_ms: K.ITEM('u64', BigInt(0)),
end_date_ms: K.ITEM('u64', BigInt(0)),
}
},
}; };
for (let i = 1; i <= 20; ++i) { for (let i = 1; i <= 20; ++i) {
const obj = { point: K.ITEM('s32', 0) }; const obj = { point: K.ITEM('s32', 0) };
if (i == 1) { if (i == 1) {
addition['long_otobear_fes_1'] = obj; addition['long_otobear_fes_1'] = obj;
addition['long_otobear_fes_2'] = obj;
addition['phrase_combo_challenge'] = obj; addition['phrase_combo_challenge'] = obj;
addition['sdvx_stamprally'] = obj;
addition['sdvx_stamprally2'] = obj;
addition['sdvx_stamprally3'] = obj; addition['sdvx_stamprally3'] = obj;
addition['chronicle_1'] = obj; addition['chronicle_1'] = obj;
} else { addition['gitadora_oracle_1'] = obj;
addition['gitadora_oracle_2'] = obj;
} else {
addition[`phrase_combo_challenge_${i}`] = obj; addition[`phrase_combo_challenge_${i}`] = obj;
} }
@ -254,6 +318,15 @@ export const getPlayer: EPR = async (info, data, send) => {
point_3: K.ITEM('s32', 0), point_3: K.ITEM('s32', 0),
}; };
addition[`kouyou_challenge_${i}`] = { point: K.ITEM('s32', 0) }; addition[`kouyou_challenge_${i}`] = { point: K.ITEM('s32', 0) };
addition[`dokidoki_valentine2_${i}`] = { point: K.ITEM('s32', 0) };
addition[`ohanami_challenge_${i}`] = { point: K.ITEM('s32', 0) };
addition[`otobear_in_the_tsubo_${i}`] = { point: K.ITEM('s32', 0) };
addition[`summer_craft_${i}`] = { point: K.ITEM('s32', 0) };
addition[`wakuteka_whiteday2_${i}`] = {
point_1: K.ITEM('s32', 0),
point_2: K.ITEM('s32', 0),
point_3: K.ITEM('s32', 0),
};
} }
} }
@ -307,6 +380,21 @@ export const getPlayer: EPR = async (info, data, send) => {
}, },
event_score: { eventlist: {} }, event_score: { eventlist: {} },
rockwave: { score_list: {} }, rockwave: { score_list: {} },
livehouse: {
score_list: {
score: {
term: K.ITEM('u8', -1),
reward_id: K.ITEM('s32', -1),
unlock_point: K.ITEM('s32', -1),
chara_id_guitar: K.ITEM('s32', -1),
chara_id_bass: K.ITEM('s32', -1),
chara_id_drum: K.ITEM('s32', -1),
chara_id_other: K.ITEM('s32', -1),
leader: K.ITEM('s32', -1),
},
last_livehouse: K.ITEM('s32', -1),
}
},
jubeat_omiyage_challenge: {}, jubeat_omiyage_challenge: {},
light_mode_reward_item: { itemid: K.ITEM('s32', -1), rarity: K.ITEM('s32', 0) }, 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) }, standard_mode_reward_item: { itemid: K.ITEM('s32', -1), rarity: K.ITEM('s32', 0) },
@ -325,6 +413,20 @@ export const getPlayer: EPR = async (info, data, send) => {
kac2017: { kac2017: {
entry_status: K.ITEM('s32', 0), entry_status: K.ITEM('s32', 0),
}, },
KAC2016: {
is_entry: K.ITEM('bool', 0),
},
KAC2016_skill_ranking: {
skill: {
skill: K.ITEM('s32', -1),
rank: K.ITEM('s32', -1),
total_nr: K.ITEM('s32', -1),
}
},
nostalgia_concert: {}, nostalgia_concert: {},
bemani_summer_2018: { bemani_summer_2018: {
linkage_id: K.ITEM('s32', -1), linkage_id: K.ITEM('s32', -1),
@ -424,6 +526,7 @@ export const savePlayers: EPR = async (info, data, send) => {
const version = getVersion(info); const version = getVersion(info);
const dm = isDM(info); const dm = isDM(info);
const game = dm ? 'dm' : 'gf'; const game = dm ? 'dm' : 'gf';
const sharedScoresEnabled = isSharedSongScoresEnabled();
let players = $(data).elements("player") let players = $(data).elements("player")
@ -449,7 +552,7 @@ export const savePlayers: EPR = async (info, data, send) => {
throw "Request data is missing required parameter: player.refid" throw "Request data is missing required parameter: player.refid"
} }
await saveSinglePlayer(player, refid, no, version, game); await saveSinglePlayer(player, refid, no, version, game, sharedScoresEnabled);
let ranking = await getPlayerRanking(refid, version, game) let ranking = await getPlayerRanking(refid, version, game)
let responsePart = getSaveProfileResponse(no, ranking) let responsePart = getSaveProfileResponse(no, ranking)
@ -469,7 +572,7 @@ export const savePlayers: EPR = async (info, data, send) => {
} }
}; };
async function saveSinglePlayer(dataplayer: KDataReader, refid: string, no: number, version: string, game: 'gf' | 'dm') async function saveSinglePlayer(dataplayer: KDataReader, refid: string, no: number, version: string, game: 'gf' | 'dm', sharedScoresEnabled: boolean)
{ {
logger.debugInfo(`Saving ${game} profile for player ${no} with refid: ${refid}`) logger.debugInfo(`Saving ${game} profile for player ${no} with refid: ${refid}`)
const profile = await getProfile(refid, version, game) as any; const profile = await getProfile(refid, version, game) as any;
@ -612,7 +715,11 @@ async function saveSinglePlayer(dataplayer: KDataReader, refid: string, no: numb
logStagesPlayed(playedStages) logStagesPlayed(playedStages)
const scores = await updatePlayerScoreCollection(refid, playedStages, version, game) const scores = await updatePlayerScoreCollection(refid, playedStages, version, game)
await saveScore(refid, version, game, scores); await saveScore(refid, version, game, scores);
if (sharedScoresEnabled) {
await mergeScoresIntoShared(refid, game, scores);
}
await saveSharedFavoriteMusicFromExtra(refid, extra) await saveSharedFavoriteMusicFromExtra(refid, extra)
} }

View File

@ -40,12 +40,19 @@ export function register() {
R.Config("shared_favorite_songs", { R.Config("shared_favorite_songs", {
name: "Shared Favorite Songs (Experimental)", name: "Shared Favorite Songs (Experimental)",
desc: "If disabled, players will be able to keep separate lists of favorite songs for each version of Gitadora, as well as between Guitar Freaks and Drummania. " + desc: "If disabled, players will be able to keep separate lists of favorite songs for each version of Gitadora, as well as between Guitar Freaks and Drummania. " +
"Enable this option to have a single unified list of favorite songs for both games, and across all versions. Default is false, to match original arcade behaviour.", "Enable this option to have a single unified list of favorite songs for both games, and across all versions. Default is false, to match original arcade behaviour.",
type: "boolean", type: "boolean",
default: false, default: false,
}) })
R.Config("shared_song_scores", {
name: "Shared Song Scores (Experimental)",
desc: "If disabled, players will keep separate scoreboards per version. Enable to merge best scores across all versions and games into a shared store.",
type: "boolean",
default: false,
})
R.DataFile("data/mdb/custom.xml", { R.DataFile("data/mdb/custom.xml", {
accept: ".xml", accept: ".xml",
name: "Custom MDB", name: "Custom MDB",
@ -56,11 +63,17 @@ export function register() {
const MultiRoute = (method: string, handler: EPR | boolean) => { const MultiRoute = (method: string, handler: EPR | boolean) => {
// Helper for register multiple versions. // Helper for register multiple versions.
R.Route(`${method}`, handler);
R.Route(`re_${method}`, handler);
R.Route(`matixx_${method}`, handler);
R.Route(`exchain_${method}`, handler); R.Route(`exchain_${method}`, handler);
R.Route(`matixx_${method}`, handler); R.Route(`matixx_${method}`, handler);
R.Route(`nextage_${method}`, handler) R.Route(`nextage_${method}`, handler)
R.Route(`highvoltage_${method}`, handler) R.Route(`highvoltage_${method}`, handler)
// TODO: TB, TBRE and more older version? R.Route(`fuzzup_${method}`, handler)
R.Route(`galaxywave_${method}`, handler)
R.Route(`galaxywave_delta_${method}`, handler)
// TODO: TB, and more older version?
}; };
// Info // Info

View File

@ -24,16 +24,16 @@ export interface BattleDataResponse
score: { score: {
battle_class: KITEM<'s32'>, battle_class: KITEM<'s32'>,
max_battle_class: KITEM<'s32'>, max_battle_class: KITEM<'s32'>,
battle_point: KITEM<'s32'>, battle_point: KITEM<'s32'>,
win: KITEM<'s32'>, win: KITEM<'s32'>,
lose: KITEM<'s32'>, lose: KITEM<'s32'>,
draw: KITEM<'s32'>, draw: KITEM<'s32'>,
consecutive_win: KITEM<'s32'>, consecutive_win: KITEM<'s32'>,
max_consecutive_win: KITEM<'s32'>, max_consecutive_win: KITEM<'s32'>,
glorious_win: KITEM<'s32'>, glorious_win: KITEM<'s32'>,
max_defeat_skill: KITEM<'s32'>, max_defeat_skill: KITEM<'s32'>,
latest_result: KITEM<'s32'>, latest_result: KITEM<'s32'>,
} }
history: {} history: {}

View File

@ -6,6 +6,7 @@ export interface CommonMusicDataField {
is_hot: KITEM<"bool">; is_hot: KITEM<"bool">;
data_ver: KITEM<"s32">; data_ver: KITEM<"s32">;
diff: KARRAY<"u16">; diff: KARRAY<"u16">;
seq_release_state: KITEM<"s32">;
} }
export interface CommonMusicData { export interface CommonMusicData {

View File

@ -34,8 +34,8 @@ export function getDefaultExtra(game: 'gf' | 'dm', version: string, id: number)
reward_status: Array(50).fill(0), reward_status: Array(50).fill(0),
} }
result.playstyle[1] = 1 // Note scroll speed (should default to 1.0x) result.playstyle[1] = 1 // Note scroll speed (should default to 1.0x)
result.playstyle[36] = 20 // Unknown result.playstyle[36] = 20 // Target Timing Adjustment
result.playstyle[48] = 20 // Unknown result.playstyle[48] = 20 // Note Display Adjustment
return result return result
} }

View File

@ -0,0 +1,8 @@
export interface PlayerStickerResponse {
id: KITEM<'s32'>,
pos_x: KITEM<'float'> ,
pos_y: KITEM<'float'>,
scale_x: KITEM<'float'> ,
scale_y: KITEM<'float'>,
rotate: KITEM<'float'>
}

View File

@ -0,0 +1,5 @@
export interface SecretMusicResponse {
musicid: KITEM<'s32'>;
seq: KITEM<'u16'>;
kind: KITEM<'s32'>;
}

View File

@ -8,7 +8,14 @@ export const isDM = (info: EamuseInfo) => {
export const getVersion = (info: EamuseInfo) => { export const getVersion = (info: EamuseInfo) => {
const moduleName: string = info.module; const moduleName: string = info.module;
return moduleName.match(/([^_]*)_(.*)/)[1]; const moduleMatch = moduleName.match(/([^_]*)_(.*)/);
if (moduleMatch && moduleMatch[1]) {
return moduleMatch[1];
}
console.error(`Unable to parse version from module name "${moduleName}".`);
return "unknown";
}; };
export function isRequiredCoreVersion(major: number, minor: number) { export function isRequiredCoreVersion(major: number, minor: number) {
@ -19,10 +26,14 @@ export function isRequiredCoreVersion(major: number, minor: number) {
}; };
export function isAsphyxiaDebugMode() : boolean { export function isAsphyxiaDebugMode() : boolean {
const argv = process.argv const argv = (globalThis as { process?: { argv?: string[] } }).process?.argv ?? [];
return argv.includes("--dev") || argv.includes("--console") return argv.includes("--dev") || argv.includes("--console");
} }
export function isSharedFavoriteMusicEnabled() : boolean{ export function isSharedFavoriteMusicEnabled() : boolean{
return Boolean(U.GetConfig("shared_favorite_songs")) return Boolean(U.GetConfig("shared_favorite_songs"))
} }
export function isSharedSongScoresEnabled() : boolean{
return Boolean(U.GetConfig("shared_song_scores"))
}

View File

@ -14,21 +14,31 @@
return "Unknown" return "Unknown"
} }
} }
function getFullVersionName(shortName) { function getFullGameVersion(shortVer) {
switch (shortName) { switch (shortVer) {
case "exchain" : case "re" :
return "GITADORA EXCHAIN" return "Tri-Boost Re:EVOLVE"
case "matixx":
return "Matixx"
case "EXCHAIN":
return "exchain"
case "nextage": case "nextage":
return "GITADORA NEX+AGE" return "NEX+AGE"
case "highvoltage": case "highvoltage":
return "GITADORA HIGH-VOLTAGE" return "HIGH-VOLTAGE"
default: case "fuzzup":
return "FUZZ-UP"
case "galaxywave":
return "GALAXY WAVE"
case "galaxywave_delta":
return "GALAXY WAVE DELTA"
default:
return "Unknown" return "Unknown"
} }
} }
const versions = ["exchain", "nextage", "highvoltage"] const versions = ["re", "matixx", "exchain", "nextage", "highvoltage", "fuzzup", "galaxywave", "galaxywave_delta"]
const games = ["gf", "dm"] const games = ["gf", "dm"]
function generateLeaderboards(infos, profiles) { function generateLeaderboards(infos, profiles) {
@ -77,7 +87,7 @@
- -
each board in generateLeaderboards(infos, profiles) each board in generateLeaderboards(infos, profiles)
h3 #{getFullVersionName(board.version)} #{getFullGameName(board.game)} h3 #{getFullGameName(board.game)} #{getFullGameVersion(board.version)}
table table
tr tr
th Rank th Rank

View File

@ -6,11 +6,34 @@
- -
function getFullGameName(shortName) { function getFullGameName(shortName) {
switch (shortName) { switch (shortName) {
case "gf":
return "GuitarFreaks"
case "dm" : case "dm" :
return "Drummania" return "DrumMania"
case "gf": default:
return "Guitar Freaks" return "Unknown"
default: }
}
function getFullGameVersion(shortVer) {
switch (shortVer) {
case "re" :
return "Tri-Boost Re:EVOLVE"
case "matixx":
return "Matixx"
case "EXCHAIN":
return "exchain"
case "nextage":
return "NEX+AGE"
case "highvoltage":
return "HIGH-VOLTAGE"
case "fuzzup":
return "FUZZ-UP"
case "galaxywave":
return "GALAXY WAVE"
case "galaxywave_delta":
return "GALAXY WAVE DELTA"
default:
return "Unknown" return "Unknown"
} }
} }
@ -23,7 +46,7 @@ div
p.card-header-title p.card-header-title
span.icon span.icon
i.mdi.mdi-account-edit i.mdi.mdi-account-edit
| User Detail (#{i.version}) | User Detail (#{getFullGameVersion(i.version)})
.card-content .card-content
form(method="post" action="/emit/updatePlayerInfo") form(method="post" action="/emit/updatePlayerInfo")
.field .field
@ -55,7 +78,7 @@ div
p.card-header-title p.card-header-title
span.icon span.icon
i.mdi.mdi-account-details i.mdi.mdi-account-details
| Profile Detail (#{getFullGameName(pr.game)} #{pr.version}) | Profile Detail (#{getFullGameName(pr.game)} #{getFullGameVersion(pr.version)})
.card-content .card-content
form(method="post") form(method="post")
.field .field