Merge pull request #82 from JamesLewisLiu/GWD

Add DELTA support
This commit is contained in:
Freddie W 2026-06-05 21:36:20 +08:00 committed by GitHub
commit edfa133ac5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 1594 additions and 30 deletions

View File

@ -13,6 +13,7 @@ Supported Versions
- HIGH-VOLTAGE
- FUZZ-UP
- GALAXY WAVE
- GALAXY WAVE DEL
When Plugin Doesn't work correctly / Startup Error on Plugin
------------------------------------------------------------
@ -60,6 +61,10 @@ Scores are stored under `version: "shared"` but are automatically applied to the
Release Notes
=============
v1.5.0
----------------
* Added(more like bugfix lol) support for GALAXY WAVE DELTA
v1.4.0
----------------
* Added support for Tri-Boost Re:EVOLVE, HIGH-VOLTAGE, FUZZ-UP, GALAXY WAVE

View File

@ -1,4 +1,5 @@
import { getVersion } from "../utils";
import { isGalaxyWaveDeltaModel } from "../utils";
interface EncoreStageData {
level: number
@ -8,13 +9,34 @@ interface EncoreStageData {
export function getEncoreStageData(info: EamuseInfo): EncoreStageData {
const fallback = { level: 10, musics: [0] }
const level: number = U.GetConfig("encore_version")
const customLevel: number = U.GetConfig("encore_version")
const useCustomLevel: boolean = U.GetConfig("use_custom_encore_level")
const ntDummyEncore = U.GetConfig("nextage_dummy_encore")
const level = (ver: string) => useCustomLevel ? customLevel : getPredefinedLevel(ver);
// GALAXY WAVE DELTA uses same route prefix, detect by model instead
if (isGalaxyWaveDeltaModel(info.model)) {
return {
level: level('galaxywave_delta'),
musics: [
2939, // Hopeful Daybreak!!!
2956, // Over Time Groove
2942, // Bellatrix
3008, // Questions That Should Not Be Answered
3009, // LIQUID NOTES
3011, // D光石火
3017, // Peyotl
3018, // Neoverse
]
}
}
switch (getVersion(info)) {
case 'galaxywave':
return {
level,
musics: [
level: level('galaxywave'),
musics: [
2866, // Calm days
2893, // 愛はToxic! feat.Lilymone
2885, // Astrum
@ -30,8 +52,8 @@ export function getEncoreStageData(info: EamuseInfo): EncoreStageData {
}
case 'fuzzup':
return {
level,
musics: [
level: level('fuzzup'),
musics: [
2812, // THE LAST OF FIREFACE
2814, // ENCOUNT
2783, // Q転直下
@ -44,7 +66,7 @@ export function getEncoreStageData(info: EamuseInfo): EncoreStageData {
}
case 'highvoltage':
return {
level,
level: level('highvoltage'),
musics: [
2686, // CYCLONICxSTORM
2687, // Heptagram
@ -58,8 +80,8 @@ export function getEncoreStageData(info: EamuseInfo): EncoreStageData {
}
case 'nextage':
return {
level,
musics: !ntDummyEncore ? [
level: level('nextage'),
musics: !ntDummyEncore ? [
2587, // 悪魔のハニープリン
2531, // The ULTIMATES -reminiscence-
2612, // ECLIPSE 2
@ -75,26 +97,26 @@ export function getEncoreStageData(info: EamuseInfo): EncoreStageData {
}
case 'exchain':
return {
level,
level: level('exchain'),
musics: [
2246, // 箱庭の世界
2498, // Cinnamon
2500, // キヤロラ衛星の軌跡
2529, // グリーンリーフ症候群
2548, // Let's Dance
2587, // 悪魔のハニープリン
5020, // Timepiece phase II (CLASSIC)
5033, // MODEL FT2 Miracle Version (CLASSIC)
2586, // 美麗的夏日風
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)
5046, // Rock to Infinity (CLASSIC)
]
}
case 'matixx':
return {
level,
level: level('matixx'),
musics: [
2432, // Durian
2445, // ヤオヨロズランズ
@ -103,17 +125,17 @@ export function getEncoreStageData(info: EamuseInfo): EncoreStageData {
2444, // Aion
2381, // Duella Lyrica
2471, // triangulum
2476, // MODEL FT4
2476, // MODEL FT4
2486, // 煉獄事変
2496, // CAPTURING XANADU
2497, // Physical Decay
2499, // Cinnamon
2499, // Cinnamon
2498, // けもののおうじゃ★めうめう
]
}
case 're':
return {
level,
level: level('re'),
musics: [
2341, // Anathema
2384, // White Forest
@ -129,4 +151,19 @@ export function getEncoreStageData(info: EamuseInfo): EncoreStageData {
default:
return fallback
}
}
function getPredefinedLevel(ver: string): number {
// Placeholder values, to be replaced with real data
switch (ver) {
case 'galaxywave_delta': return 5;
case 'galaxywave': return 5;
case 'fuzzup': return 5;
case 'highvoltage': return 5;
case 'nextage': return 5;
case 'exchain': return 5;
case 'matixx': return 5;
case 're': return 5;
default: return 5;
}
}

File diff suppressed because one or more lines are too long

View File

@ -2,6 +2,7 @@ import Logger from "../../utils/logger";
import { CommonMusicData } from "../../models/commonmusicdata";
export enum DATAVersion {
GALAXYWAVEDELTA = "gwd",
GALAXYWAVE = "gw",
FUZZUP = "fz",
HIGHVOLTAGE = "hv",
@ -78,8 +79,10 @@ export async function readMDBFile(path: string, processHandler?: processRawDataH
return result
}
export function gameVerToDataVer(ver: string): DATAVersion {
export function gameVerToDataVer(ver: string, model?: string): DATAVersion {
switch(ver) {
case 'galaxywave_delta':
return DATAVersion.GALAXYWAVEDELTA
case 'galaxywave':
return DATAVersion.GALAXYWAVE
case 'fuzzup':
@ -93,6 +96,14 @@ export function gameVerToDataVer(ver: string): DATAVersion {
case 'matixx':
return DATAVersion.MATTIX
default:
// Fallback: detect version from model string
// GALAXY WAVE DELTA models: M32:J:C:A:... or M32:J:D:A:...
// GALAXY WAVE models: M32:J:A:A:... or M32:J:B:A:...
if (model) {
const t = model.split(':')[2];
if (t == 'C' || t == 'D') return DATAVersion.GALAXYWAVEDELTA;
if (t == 'A' || t == 'B') return DATAVersion.GALAXYWAVE;
}
return DATAVersion.TBRE
}
}
@ -133,8 +144,18 @@ export function findMDBFile(fileNameWithoutExtension: string, path: string = nul
return null
}
export async function loadSongsForGameVersion(gameVer: string, processHandler?: processRawDataHandler) {
const ver = gameVerToDataVer(gameVer)
export function modelToDataVer(model: string): DATAVersion {
// Detects GALAXY WAVE vs GALAXY WAVE DELTA from model string.
// Model format: M32:J:X:Y:ZZZZZZZZZZ
// X = A (GW GF), B (GW DM), C (GWD GF), D (GWD DM)
const t = model.split(':')[2];
if (t == 'C' || t == 'D') return DATAVersion.GALAXYWAVEDELTA;
if (t == 'A' || t == 'B') return DATAVersion.GALAXYWAVE;
return DATAVersion.TBRE;
}
export async function loadSongsForGameVersion(gameVer: string, processHandler?: processRawDataHandler, model?: string) {
const ver = gameVerToDataVer(gameVer, model)
let mdbFile = findMDBFile(ver, mdbFolder)

View File

@ -1,13 +1,19 @@
import { getVersion } from "../utils";
import { isGalaxyWaveDeltaModel } from "../utils";
import { findMDBFile, readMDBFile, loadSongsForGameVersion } from "../data/mdb";
import { CommonMusicDataField } from "../models/commonmusicdata";
import Logger from "../utils/logger"
import { getPlayableMusicResponse, PlayableMusicResponse } from "../models/Responses/playablemusicresponse";
import { isAsphyxiaDebugMode } from "../utils/index";
import { playableMusic as playableMusicDelta } from "./MusicList_delta";
const logger = new Logger("MusicList")
export const playableMusic: EPR = async (info, data, send) => {
if (isGalaxyWaveDeltaModel(info.model)) {
return playableMusicDelta(info, data, send);
}
const version = getVersion(info);
const start = Date.now()
let music: CommonMusicDataField[] = [];

View File

@ -0,0 +1,40 @@
import { getVersion } from "../utils";
import { findMDBFile, readMDBFile, loadSongsForGameVersion, modelToDataVer } from "../data/mdb";
import { CommonMusicDataField } from "../models/commonmusicdata";
import Logger from "../utils/logger"
import { getPlayableMusicResponse, PlayableMusicResponse } from "../models/Responses/playablemusicresponse";
import { isAsphyxiaDebugMode } from "../utils/index";
const logger = new Logger("MusicList_delta")
export const playableMusic: EPR = async (info, data, send) => {
const version = modelToDataVer(info.model);
const start = Date.now()
let music: CommonMusicDataField[] = [];
try {
if (U.GetConfig("enable_custom_mdb")) {
let customMdb = findMDBFile("custom")
music = (await readMDBFile(customMdb)).music
}
} catch (e) {
logger.warn("Read Custom MDB failed. Using default MDB as a fallback.")
logger.debugWarn(e.stack);
music = [];
}
if (music.length == 0) {
music = (await loadSongsForGameVersion(version, undefined, info.model)).music
}
const end = Date.now()
const timeDiff = end - start
logger.debugInfo(`MDB loading took ${timeDiff} ms`)
let response : PlayableMusicResponse = getPlayableMusicResponse(music)
await send.object(response)
if (isAsphyxiaDebugMode()) {
await IO.WriteFile(`apisamples/playableMusicList.json`, JSON.stringify(music, null, 4))
}
};

View File

@ -2,10 +2,14 @@
import { getEncoreStageData } from "../data/extrastage";
import Logger from "../utils/logger";
import { getVersion } from "../utils";
import { getVersion, isGalaxyWaveDeltaModel } from "../utils";
import { gameInfoGet as gameInfoGetDelta, shopInfoRegist as shopInfoRegistDelta } from "./info_delta";
const logger = new Logger('info');
export const shopInfoRegist: EPR = async (info, data, send) => {
if (isGalaxyWaveDeltaModel(info.model)) {
return shopInfoRegistDelta(info, data, send);
}
send.object({
data: {
cabid: K.ITEM('u32', 1),
@ -21,7 +25,10 @@ export const shopInfoRegist: EPR = async (info, data, send) => {
}
export const gameInfoGet: EPR = async (info, data, send) => {
if (isGalaxyWaveDeltaModel(info.model)) {
return gameInfoGetDelta(info, data, send);
}
const eventData = getEventDataResponse()
const extraData = getEncoreStageData(info)
const VER = getVersion(info)

View File

@ -0,0 +1,491 @@
/// <reference lib="es2020.bigint" />
import { getEncoreStageData } from "../data/extrastage";
import Logger from "../utils/logger";
import { getVersion } from "../utils";
const logger = new Logger('info_delta');
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 eventData = getEventDataResponse()
const extraData = getEncoreStageData(info)
const VER = getVersion(info)
if (VER == "galaxywave"){
await send.object({
now_date: K.ITEM('u64', BigInt(Date.now())),
extra: {
extra_lv: K.ITEM('s32', extraData.level),
extramusic: {
music: extraData.musics.map(mid => {
return {
musicid: K.ITEM('s32', mid),
}
})
}
},
unlock_challenge: { term: K.ITEM('s32', 0) },
battle: { term: K.ITEM('s32', 0) },
battle_chara: { term: K.ITEM('s32', 0) },
data_ver_limit: { term: K.ITEM('s32', 0) },
ea_pass_propel: { term: K.ITEM('s32', 0) },
monthly_skill: {
term: K.ITEM('u8', 0),
target_music: {
music: {
musicid: K.ITEM('s32', 0),
},
},
},
update_prog: { term: K.ITEM('s32', 0) },
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)),
}
},
galaxy_parade: {
corner_list: {},
gacha_table: {},
},
gitadoradon: {},
entry_information :{},
trbitemdata: {},
ctrl_movie: {},
ng_jacket: {},
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() {
const addition: any = {
monstar_subjugation: {
bonus_musicid: K.ITEM('s32', 0),
},
bear_fes: {},
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);
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: {},
bonus_musicid: K.ITEM('s32', 0),
};
addition[`sdvx_stamprally3`] = obj;
addition[`chronicle_1`] = obj;
addition[`paseli_point_lottery`] = obj;
addition['sticker_campaign'] = {
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;
}
if (i <= 4) {
addition['monstar_subjugation'][`monstar_subjugation_${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) {
addition[`kouyou_challenge_${i}`] = {
term: K.ITEM('u8', 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
}

View File

@ -5,7 +5,7 @@ import { PlayerRanking } from "../models/playerranking";
import { getDefaultProfile, Profile } from "../models/profile";
import { getDefaultRecord, Record } from "../models/record";
import { Extra, getDefaultExtra } from "../models/extra";
import { getVersion, isDM } from "../utils";
import { getVersion, isDM, isGalaxyWaveDeltaModel } from "../utils";
import { getDefaultScores, Scores } from "../models/scores";
import { PLUGIN_VER } from "../const";
@ -21,10 +21,14 @@ import { applySharedFavoriteMusicToExtra, saveSharedFavoriteMusicFromExtra } fro
import { getPlayerRecordResponse } from "../models/Responses/playerrecordresponse";
import { getPlayerPlayInfoResponse, PlayerPlayInfoResponse } from "../models/Responses/playerplayinforesponse";
import { getMergedSharedScores, mergeScoresIntoShared } from "./SharedScores";
import { regist as registDelta, check as checkDelta, getPlayer as getPlayerDelta, savePlayers as savePlayersDelta } from "./profiles_delta";
const logger = new Logger("profiles")
export const regist: EPR = async (info, data, send) => {
if (isGalaxyWaveDeltaModel(info.model)) {
return registDelta(info, data, send);
}
const refid = $(data).str('player.refid');
if (!refid) {
@ -46,6 +50,9 @@ export const regist: EPR = async (info, data, send) => {
}
export const check: EPR = async (info, data, send) => {
if (isGalaxyWaveDeltaModel(info.model)) {
return checkDelta(info, data, send);
}
const refid = $(data).str('player.refid');
if (!refid) {
@ -62,6 +69,10 @@ export const check: EPR = async (info, data, send) => {
}
export const getPlayer: EPR = async (info, data, send) => {
if (isGalaxyWaveDeltaModel(info.model)) {
return getPlayerDelta(info, data, send);
}
const refid = $(data).str('player.refid');
if (!refid) {
logger.error("Request data is missing required parameter: player.refid")
@ -522,6 +533,9 @@ async function registerUser(refid: string, version: string, id = _.random(0, 999
}
export const savePlayers: EPR = async (info, data, send) => {
if (isGalaxyWaveDeltaModel(info.model)) {
return savePlayersDelta(info, data, send);
}
const version = getVersion(info);
const dm = isDM(info);

View File

@ -0,0 +1,901 @@
/// <reference lib="es2020.bigint" />
import { getDefaultPlayerInfo, PlayerInfo } from "../models/playerinfo";
import { PlayerRanking } from "../models/playerranking";
import { getDefaultProfile, Profile } from "../models/profile";
import { getDefaultRecord, Record } from "../models/record";
import { Extra, getDefaultExtra } from "../models/extra";
import { isDM } from "../utils";
const DELTA_VERSION = "galaxywave_delta";
import { getDefaultScores, Scores } from "../models/scores";
import { PLUGIN_VER } from "../const";
import Logger from "../utils/logger"
import { isAsphyxiaDebugMode, isSharedSongScoresEnabled } from "../utils/index";
import { SecretMusicEntry } from "../models/secretmusicentry";
import { CheckPlayerResponse, getCheckPlayerResponse } from "../models/Responses/checkplayerresponse";
import { getPlayerStickerResponse, PlayerStickerResponse } from "../models/Responses/playerstickerresponse";
import { getSecretMusicResponse, SecretMusicResponse } from "../models/Responses/secretmusicresponse";
import { getSaveProfileResponse } from "../models/Responses/saveprofileresponse";
import { getDefaultBattleDataResponse } from "../models/Responses/battledataresponse";
import { applySharedFavoriteMusicToExtra, saveSharedFavoriteMusicFromExtra } from "./FavoriteMusic";
import { getPlayerRecordResponse } from "../models/Responses/playerrecordresponse";
import { getPlayerPlayInfoResponse, PlayerPlayInfoResponse } from "../models/Responses/playerplayinforesponse";
import { getMergedSharedScores, mergeScoresIntoShared } from "./SharedScores";
const logger = new Logger("profiles_delta")
export const regist: EPR = async (info, data, send) => {
const refid = $(data).str('player.refid');
if (!refid) {
logger.error("Request data is missing required parameter: player.refid")
return send.deny();
}
const no = getPlayerNo(data);
const version = DELTA_VERSION;
const playerInfo = await getOrRegisterPlayerInfo(refid, version, no);
await send.object({
player: K.ATTR({ no: `${no}` }, {
is_succession: K.ITEM("bool", 0),
did: K.ITEM("s32", playerInfo.id)
})
})
}
export const check: EPR = async (info, data, send) => {
const refid = $(data).str('player.refid');
if (!refid) {
logger.error("Request data is missing required parameter: player.refid")
return send.deny();
}
const no = getPlayerNo(data);
const version = DELTA_VERSION;
const playerInfo = await getOrRegisterPlayerInfo(refid, version, no)
const result : CheckPlayerResponse = getCheckPlayerResponse(no, playerInfo.name, playerInfo.id)
await send.object(result)
}
export const getPlayer: EPR = async (info, data, send) => {
const refid = $(data).str('player.refid');
if (!refid) {
logger.error("Request data is missing required parameter: player.refid")
return send.deny();
}
const no = getPlayerNo(data);
const version = DELTA_VERSION;
const time = BigInt(31536000);
const dm = isDM(info);
const game = dm ? 'dm' : 'gf';
const sharedScoresEnabled = isSharedSongScoresEnabled();
logger.debugInfo(`Loading ${game} profile for player ${no} with refid: ${refid}`)
const name = await DB.FindOne<PlayerInfo>(refid, {
collection: 'playerinfo',
version
})
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 = sharedScoresEnabled ? await getMergedSharedScores(refid, 'dm') : (await getScore(refid, version, 'dm')).scores
const gfScores = sharedScoresEnabled ? await getMergedSharedScores(refid, 'gf') : (await getScore(refid, version, 'gf')).scores
const profile = dm ? dmProfile : gfProfile;
const extra = dm ? dmExtra : gfExtra;
applyPlayHudPreset(extra.playstyle);
await applySharedFavoriteMusicToExtra(refid, extra)
const record: any = {
gf: getPlayerRecordResponse(gfProfile, gfRecord),
dm: getPlayerRecordResponse(dmProfile, dmRecord),
};
// 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.clear', false) ? _.get(score, 'diffs.1.perc', -2) : -1,
_.get(score, 'diffs.2.clear', false) ? _.get(score, 'diffs.2.perc', -2) : -1,
_.get(score, 'diffs.3.clear', false) ? _.get(score, 'diffs.3.perc', -2) : -1,
_.get(score, 'diffs.4.clear', false) ? _.get(score, 'diffs.4.perc', -2) : -1,
_.get(score, 'diffs.5.clear', false) ? _.get(score, 'diffs.5.perc', -2) : -1,
_.get(score, 'diffs.6.clear', false) ? _.get(score, 'diffs.6.perc', -2) : -1,
_.get(score, 'diffs.7.clear', false) ? _.get(score, 'diffs.7.perc', -2) : -1,
_.get(score, 'diffs.8.clear', false) ? _.get(score, 'diffs.8.perc', -2) : -1,
_.get(score, 'diffs.1.clear', false) ? _.get(score, 'diffs.1.rank', 0) : -1,
_.get(score, 'diffs.2.clear', false) ? _.get(score, 'diffs.2.rank', 0) : -1,
_.get(score, 'diffs.3.clear', false) ? _.get(score, 'diffs.3.rank', 0) : -1,
_.get(score, 'diffs.4.clear', false) ? _.get(score, 'diffs.4.rank', 0) : -1,
_.get(score, 'diffs.5.clear', false) ? _.get(score, 'diffs.5.rank', 0) : -1,
_.get(score, 'diffs.6.clear', false) ? _.get(score, 'diffs.6.rank', 0) : -1,
_.get(score, 'diffs.7.clear', false) ? _.get(score, 'diffs.7.rank', 0) : -1,
_.get(score, 'diffs.8.clear', false) ? _.get(score, 'diffs.8.rank', 0) : -1,
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: PlayerStickerResponse[] = getPlayerStickerResponse(name.card);
const playinfo: PlayerPlayInfoResponse = getPlayerPlayInfoResponse(profile);
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: playinfo,
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),
},
recommend_musicid_list: K.ARRAY('s32', extra.recommend_musicid_list ?? Array(5).fill(-1)),
record,
groove: {
extra_gauge: K.ITEM('s32', (profile.extra_gauge+95)),
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 },
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 addition: any = {
monstar_subjugation: {},
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) {
const obj = { point: K.ITEM('s32', 0) };
if (i == 1) {
addition['long_otobear_fes_1'] = obj;
addition['long_otobear_fes_2'] = obj;
addition['phrase_combo_challenge'] = obj;
addition['sdvx_stamprally'] = obj;
addition['sdvx_stamprally2'] = obj;
addition['sdvx_stamprally3'] = obj;
addition['chronicle_1'] = obj;
addition['gitadora_oracle_1'] = obj;
addition['gitadora_oracle_2'] = 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) };
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),
};
}
}
const innerSecretMusic = getSecretMusicResponse(profile)
const innerFriendData = getFriendDataResponse(profile)
const innerBattleData = getDefaultBattleDataResponse()
const response = {
player: K.ATTR({ 'no': `${no}` }, {
now_date: K.ITEM('u64', time),
secretmusic: {
music: innerSecretMusic
},
chara_list: {},
title_parts: {},
information: {
info: K.ARRAY('u32', Array(50).fill(0)),
},
reward: {
status: K.ARRAY('u32', extra.reward_status ?? Array(50).fill(0)),
},
rivaldata: {},
frienddata: {
friend: innerFriendData
},
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', extra.recommend_musicid_list ?? Array(5).fill(-1)),
skindata: {
skin: K.ARRAY('u32', Array(100).fill(-1)),
},
battledata: innerBattleData,
is_free_ok: K.ITEM('bool', 0),
ranking: {
skill: { rank: K.ITEM('s32', playerRanking.skill), total_nr: K.ITEM('s32', playerRanking.totalPlayers) },
all_skill: { rank: K.ITEM('s32', playerRanking.all_skill), total_nr: K.ITEM('s32', playerRanking.totalPlayers) },
},
stage_result: {},
monthly_skill: {},
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: {} },
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: {},
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),
},
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: {},
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),
},
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),
}),
}
if (isAsphyxiaDebugMode()) {
await IO.WriteFile(`apisamples/lastGetPlayerRequest.json`, JSON.stringify(data, null, 4))
await IO.WriteFile(`apisamples/lastGetPlayerResponse.json`, JSON.stringify(response, null, 4))
}
send.object(response);
}
async function getOrRegisterPlayerInfo(refid: string, version: string, no: number) {
let playerInfo = await DB.FindOne<PlayerInfo>(refid, {
collection: 'playerinfo',
version
});
if (!playerInfo) {
logger.debugInfo(`Registering new profile for player ${no} with refid: ${refid}`);
playerInfo = await registerUser(refid, version);
}
return playerInfo;
}
function getPlayerNo(data: any): number {
return parseInt($(data).attr("player").no || '1', 10)
}
async function registerUser(refid: string, version: string, id = _.random(0, 99999999)) {
while (await DB.FindOne<Profile>(null, { collection: 'profile', id })) {
id = _.random(0, 99999999);
}
const defaultInfo: PlayerInfo = getDefaultPlayerInfo(version, id)
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 }, getDefaultProfile('gf', version, id));
await DB.Upsert(refid, { collection: 'profile', ...dm }, getDefaultProfile('dm', version, id));
await DB.Upsert(refid, { collection: 'record', ...gf }, getDefaultRecord('gf', version));
await DB.Upsert(refid, { collection: 'record', ...dm }, getDefaultRecord('dm', version));
await DB.Upsert(refid, { collection: 'extra', ...gf }, getDefaultExtra('gf', version, id));
await DB.Upsert(refid, { collection: 'extra', ...dm }, getDefaultExtra('dm', version, id));
await DB.Upsert(refid, { collection: 'scores', ...gf }, getDefaultScores('gf', version));
await DB.Upsert(refid, { collection: 'scores', ...dm }, getDefaultScores('dm', version));
return defaultInfo
}
export const savePlayers: EPR = async (info, data, send) => {
const version = DELTA_VERSION;
const dm = isDM(info);
const game = dm ? 'dm' : 'gf';
const sharedScoresEnabled = isSharedSongScoresEnabled();
let players = $(data).elements("player")
let response = {
player: [],
gamemode: _.get(data, 'gamemode'),
};
try
{
for (let player of players) {
const no = parseInt(player.attr().no || '1', 10)
// Only save players that are using a profile. Don't try to save guest players.
const hasCard = player.attr().card === 'use'
if (!hasCard) {
logger.debugInfo(`Skipping save for guest ${game} player ${no}.`)
continue
}
const refid = player.str('refid')
if (!refid) {
throw "Request data is missing required parameter: player.refid"
}
await saveSinglePlayer(player, refid, no, version, game, sharedScoresEnabled);
let ranking = await getPlayerRanking(refid, version, game)
let responsePart = getSaveProfileResponse(no, ranking)
response.player.push(responsePart)
}
if (isAsphyxiaDebugMode()) {
await IO.WriteFile(`apisamples/lastSavePlayersRequest.json`, JSON.stringify(data, null, 4))
await IO.WriteFile(`apisamples/lastSavePlayersResponse.json`, JSON.stringify(response, null, 4))
}
await send.object(response);
}
catch (e) {
logger.error(e)
logger.error(e.stack)
return send.deny();
}
};
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}`)
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 autoSet = function (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])
}
};
let newSecretMusic = parseSecretMusic(dataplayer)
profile.secretmusic = {
music: newSecretMusic
}
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('recommend_musicid_list', 'recommend_musicid_list', true);
autoExtra('playstyle', 'customdata.playstyle', true);
autoExtra('custom', 'customdata.custom', true);
autoExtra('reward_status', 'reward.status', 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 playedStages = dataplayer.elements('stage');
logStagesPlayed(playedStages)
const scores = await updatePlayerScoreCollection(refid, playedStages, version, game)
await saveScore(refid, version, game, scores);
if (sharedScoresEnabled) {
await mergeScoresIntoShared(refid, game, scores);
}
await saveSharedFavoriteMusicFromExtra(refid, extra)
}
async function updatePlayerScoreCollection(refid, playedStages, version, game) {
const scores = (await getScore(refid, version, game)).scores;
for (const stage of playedStages) {
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 newMeter = stage.bool('is_new_meter');
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] = { //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: newMeter ? meter.toString() : _.get(scores[mid].diffs[seq], 'meter', 0),
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,
};
}
return scores
}
function applyPlayHudPreset(playstyle: number[]): void {
if (!Array.isArray(playstyle)) return;
// Only pad array to required length for version upgrades (50→70).
// Do NOT overwrite any existing values — let the user's saved settings persist.
if (playstyle.length < 70) {
for (let i = playstyle.length; i < 70; i++) {
playstyle[i] = 0;
}
}
}
async function getPlayerRanking(refid: string, version: string, game: 'gf' | 'dm') : Promise<PlayerRanking> {
let profiles = await getAllProfiles(version, game)
let playerCount = profiles.length
let sortedProfilesA = profiles.sort((a,b) => b.skill - a.skill)
let sortedProfilesB = profiles.sort((a,b) => b.all_skill - a.all_skill)
let idxA = _.findIndex(sortedProfilesA, (e) => e.__refid === refid)
idxA = idxA > -1 ? idxA + 1 : playerCount // Default to last place if not found in the DB.
let idxB = _.findIndex(sortedProfilesB, (e) => e.__refid === refid)
idxB = idxB > -1 ? idxB + 1 : playerCount // Default to last place if not found in the DB.
return {
refid,
skill: idxA,
all_skill: idxB,
totalPlayers: playerCount
}
}
async function getAllProfiles( version: string, game: 'gf' | 'dm') {
return await DB.Find<Profile>(null, {
collection: 'profile',
version: version,
game: game
})
}
async function getProfile(refid: string, version: string, game: 'gf' | 'dm') {
return await DB.FindOne<Profile>(refid, {
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
})
}
function parseSecretMusic(playerData: KDataReader) : SecretMusicEntry[]
{
let response : SecretMusicEntry[] = []
let elements = playerData.element('secretmusic')?.elements('music')
if (!elements) {
return response
}
for (let el of elements) {
let item : SecretMusicEntry = {
musicid: el.number('musicid'),
seq: el.number('seq'),
kind: el.number('kind')
}
response.push(item)
}
return response
}
function getFriendDataResponse(profile: Profile) {
let response = []
return response;
}
function logStagesPlayed(playedStages: KDataReader[]) {
let result = "Stages played: "
for (let stage of playedStages) {
let id = stage.number('musicid')
result += `${id}, `
}
logger.debugLog(result)
}

View File

@ -21,6 +21,14 @@ export function register() {
default: 13,
})
R.Config("use_custom_encore_level", {
name: "Use Custom Encore Level",
desc: "If enabled, the encore level is controlled by the 'Encore Version' setting above. " +
"If disabled, predefined encore levels per version are used instead.",
type: "boolean",
default: true,
})
R.Config("nextage_dummy_encore", {
name: "Dummy Encore for SPE (Nextage Only)",
desc: "Since Nextage's Special Premium Encore system is bit complicated, \n"
@ -70,9 +78,8 @@ export function register() {
R.Route(`matixx_${method}`, handler);
R.Route(`nextage_${method}`, handler)
R.Route(`highvoltage_${method}`, handler)
R.Route(`fuzzup_${method}`, handler)
R.Route(`fuzzup_${method}`, handler)
R.Route(`galaxywave_${method}`, handler)
R.Route(`galaxywave_delta_${method}`, handler)
// TODO: TB, and more older version?
};

View File

@ -18,6 +18,7 @@ export interface Extra {
}
export function getDefaultExtra(game: 'gf' | 'dm', version: string, id: number) : Extra {
const playstyleLength = version == 'galaxywave' || version == 'galaxywave_delta' ? 70 : 50;
const result : Extra = {
collection: 'extra',
pluginVer: PLUGIN_VER,
@ -25,7 +26,7 @@ export function getDefaultExtra(game: 'gf' | 'dm', version: string, id: number)
game,
version,
id,
playstyle: Array(50).fill(0),
playstyle: Array(playstyleLength).fill(0),
custom: Array(50).fill(0),
list_1: Array(100).fill(-1),
list_2: Array(100).fill(-1),
@ -34,8 +35,34 @@ export function getDefaultExtra(game: 'gf' | 'dm', version: string, id: number)
reward_status: Array(50).fill(0),
}
result.playstyle[1] = 1 // Note scroll speed (should default to 1.0x)
result.playstyle[25] = 0
result.playstyle[26] = 1
result.playstyle[27] = 1
result.playstyle[28] = 1
result.playstyle[29] = 1
result.playstyle[30] = 1
result.playstyle[31] = 1
result.playstyle[32] = 1
result.playstyle[33] = 1
result.playstyle[34] = 1
result.playstyle[35] = 3
result.playstyle[36] = 20 // Target Timing Adjustment
result.playstyle[48] = 20 // Note Display Adjustment
if (playstyleLength >= 70) {
result.playstyle[21] = 50 //DELTA 判定ライン位置
result.playstyle[26] = 0 //DELTA 判定エフェクト位置
result.playstyle[27] = 0 //DELTA SHUTTER IN
result.playstyle[28] = 0 //DELTA SHUTTER OUT
result.playstyle[56] = 0
result.playstyle[57] = 1
result.playstyle[58] = 1
result.playstyle[59] = 1
result.playstyle[60] = 17
result.playstyle[61] = 1
result.playstyle[62] = 1
result.playstyle[63] = 1
}
return result
}

View File

@ -1,9 +1,16 @@
export const isGF = (info: EamuseInfo) => {
return info.model.split(':')[2] == 'A';
const t = info.model.split(':')[2];
return t == 'A' || t == 'C';
};
export const isDM = (info: EamuseInfo) => {
return info.model.split(':')[2] == 'B';
const t = info.model.split(':')[2];
return t == 'B' || t == 'D';
};
export const isGalaxyWaveDeltaModel = (model: string) => {
const t = model.split(':')[2];
return t == 'C' || t == 'D';
};
export const getVersion = (info: EamuseInfo) => {