Museca 1+1/2 and Museca Plus

This commit is contained in:
DitFranXX 2020-11-30 23:45:03 +09:00
parent aefac94c05
commit 9cf950c759
13 changed files with 510 additions and 0 deletions

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

@ -0,0 +1,20 @@
MUSECA
======
Plugin Version: **v1.0.0**
Supported Versions
------------------
- 1+1/2
- [MUSECA PLUS](https://museca.plus/)
Only Initial support for now.
=============================
Mission and some other features may not work properly.
Currently, Score saving is only feature that verified.
Changelog
=========
1.0.0 (Current)
---------------
Initial Support.

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

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

View File

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

View File

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

View File

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

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

View File

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

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

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

View File

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

View File

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

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

@ -0,0 +1,4 @@
export function IDToCode(id: number) {
const padded = _.padStart(id.toString(), 8);
return `${padded.slice(0, 4)}-${padded.slice(4)}`;
}