diff --git a/.gitignore b/.gitignore index 5f51ed9..7439c72 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # Editor configs .vscode +.idea # External modules node_modules diff --git a/README.md b/README.md index 8c17513..299e483 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ I don't actually follow any coding rules for this jank so neither should you. Th I'll do my best to merge PR, but please make sure you are submitting code targeted for "public" releases. (Unless it is some ancient rare stuff and you feel generous enough to provide support for it) - For new plugins: please use `@asphyxia` identifier for your plugin since you are submitting code as the community. + - This way we prevent third-party plugins (e.g. `popn` or `popn@someoneelse`) from conflicting with our database. - For existing plugins: please inlude a changelog in your PR so it is easier for me to tell what it is for. ## How do I make plugins? diff --git a/bst@asphyxia/README.md b/bst@asphyxia/README.md index e1e4ebc..d47c864 100644 --- a/bst@asphyxia/README.md +++ b/bst@asphyxia/README.md @@ -1,6 +1,6 @@ # BeatStream -Plugin Version: **v1.0.1** +Plugin Version: **v1.0.2** Supported Versions: diff --git a/bst@asphyxia/handlers/bst2/webui.ts b/bst@asphyxia/handlers/bst2/webui.ts index 85e6ffd..ecd0ce0 100644 --- a/bst@asphyxia/handlers/bst2/webui.ts +++ b/bst@asphyxia/handlers/bst2/webui.ts @@ -20,8 +20,10 @@ export namespace Bst2HandlersWebUI { sfxStreamNoteTail: number }) => { try { + let base = await DB.FindOne(data.refid, { collection: "bst.bst2.player.base" }) let customization = await DB.FindOne(data.refid, { collection: "bst.bst2.player.customization" }) - if (customization == null) throw new Error("No profile for refid=" + data.refid) + if (!customization || !base) throw new Error("No profile for refid=" + data.refid) + base.name = data.name customization.custom[0] = data.rippleNote customization.custom[2] = data.sfxNormalNote customization.custom[3] = data.sfxRippleNote @@ -30,6 +32,7 @@ export namespace Bst2HandlersWebUI { customization.custom[6] = data.backgroundBrightness customization.custom[7] = (data.judgeText << 0) | (data.rippleNoteGuide << 1) | (data.streamNoteGuide << 2) | (data.sfxStreamNoteTail << 3) | (data.sfxFine << 4) customization.custom[9] = data.judgeText + DBM.update(data.refid, { collection: "bst.bst2.player.base" }, base) DBM.update(data.refid, { collection: "bst.bst2.player.customization" }, customization) UtilityHandlersWebUI.pushMessage("Save BeatStream Animtribe settings succeeded!", 2, WebUIMessageType.success, data.refid) } catch (e) { diff --git a/jubeat@asphyxia/README.md b/jubeat@asphyxia/README.md new file mode 100644 index 0000000..c40587a --- /dev/null +++ b/jubeat@asphyxia/README.md @@ -0,0 +1,18 @@ +# Jubeat + +Plugin Version: **v1.0.0** + +### Supported Versions + +*** + +- knit +- knit APPEND + +### Changelogs + +*** + +#### 1.0.0 + +- Initial Release diff --git a/jubeat@asphyxia/handlers/common.ts b/jubeat@asphyxia/handlers/common.ts new file mode 100644 index 0000000..e5484a6 --- /dev/null +++ b/jubeat@asphyxia/handlers/common.ts @@ -0,0 +1,50 @@ +import {getVersion} from "../utils"; + +export const shopinfo: EPR = (info, data, send) => { + const locId = $(data.shop).content("locationid"); + const version = getVersion(info); + if (version === 0) return send.deny(); + + if (version === 3) return send.object({ + data: { + cabid: K.ITEM('u32', 1), + locationid: K.ITEM('str', locId), + is_send: K.ITEM("u8", 1) + } + }) + + return send.deny(); +} + +export const demodata = { + getNews: (_, __, send) => send.object({ data: { officialnews: K.ATTR({ count: "0" }) } }), + getData: (_, data, send) => { + const newsId = $(data).number('officialnews.newsid'); + return send.object({ + data: { + officialnews: { + data: { + newsid: K.ITEM('s16', newsId), + image: K.ITEM('u8', 0, { size: '0' }) + } + } + } + }); + }, + getHitchart: (_, __, send) => send.object({ + data: { + hitchart: { + update: K.ITEM('str', ''), + + hitchart_lic: K.ATTR({ count: "0" }), + hitchart_org: K.ATTR({ count: "0" }), + } + } + }), +}; + +export const netlog: EPR = (info, data, send) => { + const errMsg = $(data).str('msg'); + console.error(errMsg); + return send.success(); +} diff --git a/jubeat@asphyxia/handlers/matching.ts b/jubeat@asphyxia/handlers/matching.ts new file mode 100644 index 0000000..eabe516 --- /dev/null +++ b/jubeat@asphyxia/handlers/matching.ts @@ -0,0 +1,49 @@ +export const check: EPR = (info, data, send) => { + const enter = $(data).bool('data.enter'); + const time = $(data).number('data.time'); + return send.object({ + data: { + entrant_nr: K.ITEM('u32', 1, { time: String(time) }), + interval: K.ITEM('s16', 1), + entry_timeout: K.ITEM('s16', U.GetConfig("matching_entry_timeout")), + waitlist: K.ATTR({ count: "0" }) + } + }); +}; + +export const entry: EPR = (info, data, send) => { + const localMatchingNode = $(data).element("data.local_matching"); + const connectNode = $(data).element("data.connect"); + const musicNode = $(data).element('data.music'); + + const roomId = _.random(1, 999999999999999); + + // TODO Local matching support + + return send.object({ + data: { + roomid: K.ITEM('s64', BigInt(roomId), { master: "1" }), + refresh_intr: K.ITEM('s16', 3), + music: { + id: K.ITEM("u32", musicNode.number("id")), + seq: K.ITEM("u8", musicNode.number("seq")), + } + } + }); +}; + +export const refresh: EPR = (info, data, send) => { + return send.object({ + data: { + refresh_intr: K.ITEM('s16', 2), + } + }); +}; + +export const report: EPR = (info, data, send) => { + return send.object({ + data: { + refresh_intr: K.ITEM('s16', 1), + } + }); +}; diff --git a/jubeat@asphyxia/handlers/profile.ts b/jubeat@asphyxia/handlers/profile.ts new file mode 100644 index 0000000..14508f5 --- /dev/null +++ b/jubeat@asphyxia/handlers/profile.ts @@ -0,0 +1,292 @@ +import {getVersion} from "../utils"; +import Profile from '../models/profile'; +import {Score} from '../models/score'; + +export const profile: EPR = async (info, data, send) => { + let refId = $(data).str("data.player.pass.refid"); + if (!refId) return send.deny(); + + const version = getVersion(info); + if (version === 0) return send.deny(); + + const name = $(data).str("data.player.name"); + + let profile = await DB.FindOne(refId, { collection: "profile" }); + + if (!profile) { + if (!name) return send.deny(); + + const newProfile = new Profile(); + newProfile.jubeatId = _.random(1, 99999999); + newProfile.name = name; + newProfile.previous_version = version; + + await DB.Upsert(refId, { collection: "profile" }, newProfile); + + profile = newProfile; + } + + let migration = false; + if (profile.previous_version < version) { + migration = true; + profile.name = ""; + await DB.Update(refId, { collection: "profile" }, { $set: { name: "", previous_version: version } }); + } + + if (name) { + profile.name = name; + await DB.Update(refId, { collection: "profile" }, { $set: { name } }); + } + + if (version === 3) { + if (U.GetConfig("unlock_all_songs")) { + profile.knit.item = { + secretList: [-1, -1], + themeList: -1, + markerList: [-1, -1], + titleList: [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1] + }; + profile.knit.item_new = { + secretList: [0, 0], + themeList: 0, + markerList: [0, 0], + titleList: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + }; + } + return send.pugFile('templates/knit/profile.pug', { refId, migration, ...profile }, { compress: false }); + } + + return send.deny(); +}; + +export const saveProfile: EPR = async (info, { data }, send) => { + console.log(U.toXML(data)); + const player = $(data).element("player"); + + const refId = player.str("refid"); + if (!refId) return send.deny(); + + const version = getVersion(info); + if (version === 0) return send.deny(); + + const profile = await DB.FindOne(refId, { collection: "profile" }); + + if (version === 3) { + profile.name = player.str("name"); + + profile.last.shopname = player.str("shopname", profile.last.shopname); + profile.last.areaname = player.str("areaname", profile.last.areaname); + + profile.jubility = player.number("info.jubility", profile.jubility); + profile.jubilityYday = player.number("info.jubility_yday", profile.jubilityYday); + + profile.knit.acvProg = player.number("info.acv_prog", profile.knit.acvProg); + profile.knit.acvWool = player.number("info.acv_wool", profile.knit.acvWool); + profile.knit.acvRouteProg = player.numbers("info.acv_route_prog", profile.knit.acvRouteProg); + profile.knit.acvPoint = player.number("info.acv_point", profile.knit.acvPoint); + + profile.tuneCount = player.number("info.tune_cnt", profile.tuneCount); + profile.saveCount = player.number("info.save_cnt", profile.saveCount); + profile.savedCount = player.number("info.saved_cnt", profile.savedCount); + profile.fullcomboCount = player.number("info.fc_cnt", profile.fullcomboCount); + profile.fullcomboSeqCount = player.number("info.fc_seq_cnt", profile.fullcomboSeqCount); + profile.excellentCount = player.number("info.exc_cnt", profile.excellentCount); + profile.excellentSeqCount = player.number("info.exc_seq_cnt", profile.excellentSeqCount); + profile.matchCount = player.number("info.match_cnt", profile.matchCount); + profile.beatCount = player.number('info.beat_cnt', profile.beatCount); + profile.conciergeSelectedCount = player.number('info.con_sel_cnt', profile.conciergeSelectedCount); + profile.tagCount = player.number('info.tag_cnt', profile.tagCount); + profile.mynewsCount = player.number('info.mynews_cnt', profile.mynewsCount); + + if (!U.GetConfig("unlock_all_songs")) { + profile.knit.item.secretList = player.numbers('item.secret_list', profile.knit.item.secretList); + profile.knit.item.themeList = player.number('item.theme_list', profile.knit.item.themeList); + profile.knit.item.markerList = player.numbers('item.marker_list', profile.knit.item.markerList); + profile.knit.item.titleList = player.numbers('item.title_list', profile.knit.item.titleList); + + profile.knit.item_new.secretList = player.numbers('item.secret_new', profile.knit.item_new.secretList); + profile.knit.item_new.themeList = player.number('item.theme_new', profile.knit.item_new.themeList); + profile.knit.item_new.markerList = player.numbers('item.marker_new', profile.knit.item_new.markerList); + profile.knit.item_new.titleList = player.numbers('item.title_new', profile.knit.item_new.titleList); + } + + profile.last.conciergeSuggestId = player.number('info.con_suggest_id', profile.last.conciergeSuggestId); + profile.last.playTime = BigInt(new Date().getMilliseconds()); + + // Append + const collabo = player.element("collabo"); + if (collabo) { + profile.knit.collabo.success = collabo.bool("success"); + profile.knit.collabo.completed = collabo.bool("completed"); + } + + const result = $(data).element("result"); + + if (result) { + const tunes = result.elements("tune"); + + for (const tune of tunes) { + const musicId = tune.number("music", 0); + profile.last.musicId = musicId; + profile.last.seqId = parseInt(tune.attr("player.score").seq) || 0; + profile.last.title = tune.number("title", profile.last.title); + profile.last.theme = tune.number("theme", profile.last.theme); + profile.last.marker = tune.number("marker", profile.last.marker); + profile.last.sort = tune.number("sort", profile.last.sort); + profile.last.filter = tune.number("filter", profile.last.filter); + profile.last.showRank = tune.number("combo_disp", profile.last.showRank); + profile.last.showCombo = tune.number("rank_sort", profile.last.showCombo); + profile.last.mselStat = tune.number("msel_stat", profile.last.mselStat); + + const score = tune.number('player.score'); + const seq = parseInt(tune.attr('player.score').seq); + const clear = parseInt(tune.attr('player.score').clear); + const combo = parseInt(tune.attr('player.score').combo); + const bestScore = tune.number('player.best_score'); + const bestClear = tune.number('player.best_clear'); + const playCount = tune.number('player.play_cnt'); + const clearCount = tune.number('player.clear_cnt'); + const fullcomboCount = tune.number('player.fc_cnt'); + const excellentCount = tune.number('player.exc_cnt'); + const mbar = tune.numbers('player.mbar'); + + await updateScore(refId, musicId, seq, score, clear, mbar, { + playCount, + clearCount, + fullcomboCount, + excellentCount + }); + } + } + + await DB.Update(refId, { collection: "profile" }, profile); + + return send.object({ data: { player: { session_id: K.ITEM('s32', 1) } } }); + } + + return send.deny(); +}; + +export const loadScore: EPR = async (info, data, send) => { + const jubeatId = $(data).number("data.player.jid"); + if (!jubeatId) return send.deny(); + + const profile = await DB.FindOne(null, { collection: "profile", jubeatId }); + if (!profile) return send.deny(); + + const version = getVersion(info); + if (version === 0) return send.deny(); + + const scores = await DB.Find(profile.__refid, { collection: "score" }); + const scoreData: { [musicId: number]: any } = {}; + + for (const score of scores) { + if (!scoreData[score.musicId]) { + scoreData[score.musicId] = { + playCnt: [0, 0, 0], + clearCnt: [0, 0, 0], + fcCnt: [0, 0, 0], + exCnt: [0, 0, 0], + clear: [0, 0, 0], + score: [0, 0, 0], + bar: [Array(30).fill(0), Array(30).fill(0), Array(30).fill(0)] + }; + } + + const data = scoreData[score.musicId]; + data.playCnt[score.seq] = score.playCount; + data.clearCnt[score.seq] = score.clearCount; + data.fcCnt[score.seq] = score.fullcomboCount; + data.exCnt[score.seq] = score.excellentCount; + data.clear[score.seq] = score.clearType; + data.score[score.seq] = score.score; + data.bar[score.seq] = score.bar; + } + + if (version === 3) return send.object({ + data: { + player: { + playdata: K.ATTR({ count: String(Object.keys(scoreData).length) }, { + musicdata: (() => { + const musicdata = []; + Object.entries(scoreData).forEach(([k, v]) => { + musicdata.push(K.ATTR({ music_id: String(k) }, { + play_cnt: K.ARRAY('s32', v.playCnt), + clear_cnt: K.ARRAY('s32', v.clearCnt), + fc_cnt: K.ARRAY('s32', v.fcCnt), + ex_cnt: K.ARRAY('s32', v.exCnt), + clear: K.ARRAY('s8', v.clear), + score: K.ARRAY('s32', v.score), + bar: v.bar.map((v, i) => K.ARRAY('u8', v, { seq: String(i) })) + })); + }); + return musicdata; + })() + }) + } + } + }); + + return send.deny(); +}; + +const updateScore = async (refId: string, musicId: number, seq: number, score: number, clear: number, mbar: number[], data: any) => { + let raised; + + const oldScore = await DB.FindOne(refId, { collection: "score", musicId, seq }); + + let scoreData = oldScore; + + if (!oldScore) { + scoreData = new Score(); + scoreData.musicId = musicId; + scoreData.seq = seq; + raised = true; + } else { + raised = score > oldScore.score; + score = Math.max(oldScore.score, score); + } + + scoreData.clearType = Math.max(scoreData.clearType, clear); + scoreData.playCount = data.playCount; + scoreData.clearCount = data.clearCount; + scoreData.fullcomboCount = data.fullcomboCount; + scoreData.excellentCount = data.excellentCount; + scoreData.isHardmodeClear = false; + + if (mbar && raised) { + scoreData.score = score; + scoreData.bar = mbar; + } + + await DB.Upsert(refId, { collection: "score", musicId, seq }, scoreData); +}; + +export const meeting: EPR = (info, data, send) => { + return send.object({ + data: { + meeting: { + single: K.ATTR({ count: '0' }), + tag: K.ATTR({ count: '0' }), + }, + reward: { + total: K.ITEM('s32', 0), + point: K.ITEM('s32', 0) + } + } + }); +}; + +export const getCollabo: EPR = (info, data, send) => send.object({ + data: { + collabo: { + played: { + iidx: K.ITEM("s8", 1), + popn: K.ITEM("s8", 1), + ddr: K.ITEM("s8", 1), + reflec: K.ITEM("s8", 1), + gfdm: K.ITEM("s8", 1), + } + } + } +}); diff --git a/jubeat@asphyxia/index.ts b/jubeat@asphyxia/index.ts new file mode 100644 index 0000000..194f673 --- /dev/null +++ b/jubeat@asphyxia/index.ts @@ -0,0 +1,60 @@ +import {demodata, netlog, shopinfo} from "./handlers/common"; +import {check, entry, refresh, report} from "./handlers/matching"; +import {getCollabo, loadScore, meeting, profile, saveProfile} from "./handlers/profile"; + +export function register() { + if (CORE_VERSION_MAJOR <= 1 && CORE_VERSION_MINOR < 31) { + console.error("The current version of Asphyxia Core is not supported. Requires version '1.31' or later."); + return; + } + R.GameCode("J44"); + + R.Config("unlock_all_songs", { + name: "Unlock All Songs", + desc: "Tired of unlocking songs? Have this!", + type: "boolean", + default: false + }); + + R.Config("quick_matching_end", { + name: "Quick Matching End", + desc: "Supported from clan to festo.", + type: "boolean", + default: false + }); + + R.Config("matching_entry_timeout", { + name: "Online Matching Timeout", + desc: "If online matching songs are too boring, save time! (second)", + type: "integer", + default: 30, + range: [15, 99], + }); + + R.Route("gametop.regist", profile); + R.Route("gametop.get_pdata", profile); + R.Route("gametop.get_mdata", loadScore); + R.Route("gametop.get_meeting", meeting); + R.Route("gametop.get_collabo", getCollabo); + + R.Route('gameend.regist', saveProfile); + R.Route('gameend.log', true); + R.Route('gameend.set_collabo', true); + + R.Route("shopinfo.regist", shopinfo); + R.Route("netlog.send", netlog); + R.Route("demodata.get_news", demodata.getNews); + R.Route("demodata.get_data", demodata.getData); + R.Route("demodata.get_hitchart", demodata.getHitchart); + R.Route("lobby.check", check); + R.Route("lobby.entry", entry); + R.Route("lobby.refresh", refresh); + R.Route("lobby.report", report); + + R.Unhandled((info, data, send) => { + console.log(info.module, info.method); + console.log(U.toXML(data)); + + return send.deny(); + }); +} diff --git a/jubeat@asphyxia/models/profile.ts b/jubeat@asphyxia/models/profile.ts new file mode 100644 index 0000000..f0e3925 --- /dev/null +++ b/jubeat@asphyxia/models/profile.ts @@ -0,0 +1,101 @@ +export default class Profile { + collection: "profile" = "profile"; + + jubeatId: number = _.random(1, 99999999); + name: string = "JUBEAT"; + + previous_version = 0; + + jubility: number = 0; + jubilityYday: number = 0; + tuneCount: number = 0; + saveCount: number = 0; + savedCount: number = 0; + fullcomboCount: number = 0; + fullcomboSeqCount: number = 0; + excellentCount: number = 0; + excellentSeqCount: number = 0; + matchCount: number = 0; + beatCount: number = 0; + tagCount: number = 0; + mynewsCount: number = 0; + conciergeSelectedCount: number = 0; + + last: { + shopname: string; + areaname: string; + playTime: bigint; + title: number; + theme: number; + marker: number; + showRank: number; + showCombo: number; + musicId: number; + seqId: number; + seqEditId: string; + sort: number; + filter: number; + mselStat: number; + conciergeSuggestId: number; + } = { + shopname: "NONE", + areaname: "NONE", + playTime: BigInt(0), + title: 0, + theme: 0, + marker: 0, + showRank: 1, + showCombo: 1, + musicId: 0, + seqId: 0, + seqEditId: "", + sort: 0, + filter: 0, + mselStat: 0, + conciergeSuggestId: 0 + }; + + knit: { + acvProg: number; + acvWool: number; + acvRouteProg: number[]; + acvPoint: number; + item: { + secretList: number[], + themeList: number, + markerList: number[], + titleList: number[] + }, + item_new: { + secretList: number[], + themeList: number, + markerList: number[], + titleList: number[] + }, + collabo: { + success: boolean; + completed: boolean; + } + } = { + acvProg: 0, + acvWool: 0, + acvRouteProg: [0, 0, 0, 0], + acvPoint: 0, + item: { + secretList: [0, 0], + themeList: 0, + markerList: [0, 0], + titleList: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + }, + item_new: { + secretList: [0, 0], + themeList: 0, + markerList: [0, 0], + titleList: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + }, + collabo: { + success: false, + completed: false + } + }; +} diff --git a/jubeat@asphyxia/models/score.ts b/jubeat@asphyxia/models/score.ts new file mode 100644 index 0000000..440c813 --- /dev/null +++ b/jubeat@asphyxia/models/score.ts @@ -0,0 +1,14 @@ +export class Score { + collection: "score" = "score"; + + musicId: number; + seq: number; + score: number = 0; + clearType: number = 0; + playCount: number = 0; + clearCount: number = 0; + fullcomboCount: number = 0; + excellentCount: number = 0; + isHardmodeClear: boolean; + bar: number[]; +} diff --git a/jubeat@asphyxia/templates/knit/profile.pug b/jubeat@asphyxia/templates/knit/profile.pug new file mode 100644 index 0000000..d56ad73 --- /dev/null +++ b/jubeat@asphyxia/templates/knit/profile.pug @@ -0,0 +1,89 @@ +gametop + data + player + name(__type="str") #{name} + jid(__type="s32") #{jubeatId} + refid(__type="str") #{refId} + session_id(__type="s32") 1 + + info + inherit(__type="bool") #{migration ? 1 : 0} + jubility(__type="s16") #{jubility} + jubility_yday(__type="s16") #{jubilityYday} + acv_prog(__type="s8") #{knit.acvProg} + acv_wool(__type="s8") #{knit.acvWool} + acv_route_prog(__type="s8" __count="4") #{knit.acvRouteProg.join(" ")} + acv_point(__type="s32") #{knit.acvPoint} + tune_cnt(__type="s32") #{tuneCount} + save_cnt(__type="s32") #{saveCount} + saved_cnt(__type="s32") #{savedCount} + fc_cnt(__type="s32") #{fullcomboCount} + ex_cnt(__type="s32") #{excellentCount} + match_cnt(__type="s32") #{matchCount} + beat_cnt(__type="s32") #{beatCount} + mynews_cnt(__type="s32") #{mynewsCount} + con_sel_cnt(__type="s32") #{conciergeSelectedCount} + tag_cnt(__type="s32") #{tagCount} + mtg_entry_cnt(__type="s32") 0 + tag_entry_cnt(__type="s32") 0 + mtg_hold_cnt(__type="s32") 0 + tag_hold_cnt(__type="s32") 0 + mtg_result(__type="u8") 0 + + last + play_time(__type="s64") #{last.playTime || 0} + shopname(__type="str") #{last.shopname} + areaname(__type="str") #{last.areaname} + title(__type="s16") #{last.title} + theme(__type="s8") #{last.theme} + marker(__type="s8") #{last.marker} + rank_sort(__type="s8") #{last.showRank} + combo_disp(__type="s8") #{last.showCombo} + music_id(__type="s32") #{last.musicId} + seq_id(__type="s8") #{last.seqId} + sort(__type="s8") #{last.sort} + filter(__type="s32") #{last.filter} + msel_stat(__type="s8") #{last.mselStat} + con_suggest_id(__type="s8") #{last.conciergeSuggestId} + + item + secret_list(__type="s32" __count="2") #{knit.item.secretList.join(" ")} + theme_list(__type="s16") #{knit.item.themeList} + marker_list(__type="s32" __count="2") #{knit.item.markerList.join(" ")} + title_list(__type="s32" __count="24") #{knit.item.titleList.join(" ")} + + new + secret_list(__type="s32" __count="2") #{knit.item_new.secretList.join(" ")} + theme_list(__type="s16") #{knit.item_new.themeList} + marker_list(__type="s32" __count="2") #{knit.item_new.markerList.join(" ")} + title_list(__type="s32" __count="24") #{knit.item_new.titleList.join(" ")} + + today_music + music_id(__type="s32") 0 + + news + checked(__type="s16") 0 + + friendlist(count="0") + + lucky_music + music_id(__type="s32") 0 + + mylist(count="0") + + group + group_id(__type="s32") 0 + + bingo + reward + total(__type="s32") 0 + point(__type="s32") 0 + + collabo + success(__type="bool") #{knit.collabo.success ? 1 : 0} + completed(__type="bool") #{knit.collabo.completed ? 1 : 0} + + history + play_hist(count="0") + + match_hist(count="0") diff --git a/jubeat@asphyxia/utils.ts b/jubeat@asphyxia/utils.ts new file mode 100644 index 0000000..3647c04 --- /dev/null +++ b/jubeat@asphyxia/utils.ts @@ -0,0 +1,6 @@ +export function getVersion({ model }: EamuseInfo) { + const dateCode = model.split(':')[4]; + + if (model.startsWith("J44")) return 3; + return 0; +} diff --git a/sdvx@asphyxia/README.md b/sdvx@asphyxia/README.md index ca4cb63..1c0c309 100644 --- a/sdvx@asphyxia/README.md +++ b/sdvx@asphyxia/README.md @@ -4,5 +4,6 @@ Plugin Version: **v1.1** Supported Versions: +- BOOTH - HEAVENLY HAVEN - VIVID WAVE diff --git a/sdvx@asphyxia/handlers/common.ts b/sdvx@asphyxia/handlers/common.ts index da45335..a5b4d0b 100644 --- a/sdvx@asphyxia/handlers/common.ts +++ b/sdvx@asphyxia/handlers/common.ts @@ -6,6 +6,12 @@ export const common: EPR = async (info, data, send) => { let courses = []; let extend = []; + const version = parseInt(info.model.split(":")[4]); + + if (version <= 2013052900) { + return send.pugFile('templates/booth/common.pug'); + } + switch (info.method) { case 'sv4_common': { events = EVENT4; @@ -26,13 +32,13 @@ export const common: EPR = async (info, data, send) => { if (U.GetConfig('unlock_all_songs')) { for (let i = 1; i < 1700; ++i) { for (let j = 0; j < 5; ++j) { - + songs.push({ music_id: K.ITEM('s32', i), music_type: K.ITEM('u8', j), limited: K.ITEM('u8', 3), }); - + } } } diff --git a/sdvx@asphyxia/handlers/features.ts b/sdvx@asphyxia/handlers/features.ts index 371f8e2..fe5abec 100644 --- a/sdvx@asphyxia/handlers/features.ts +++ b/sdvx@asphyxia/handlers/features.ts @@ -1,17 +1,46 @@ -import { Profile } from '../models/profile'; -import { MusicRecord } from '../models/music_record'; -import { IDToCode, GetCounter } from '../utils'; -import { Mix } from '../models/mix'; +import {Profile} from '../models/profile'; +import {MusicRecord} from '../models/music_record'; +import {getVersion, IDToCode, GetCounter} from '../utils'; +import {Mix} from '../models/mix'; export const hiscore: EPR = async (info, data, send) => { const records = await DB.Find(null, { collection: 'music' }); + const version = getVersion(info); + const profiles = _.groupBy( await DB.Find(null, { collection: 'profile' }), '__refid' ); - send.object({ + if (version === 1) { + return send.object({ + hiscore: K.ATTR({ type: "1" }, { + music: _.map( + _.groupBy(records, r => { + return `${r.mid}:${r.type}`; + }), + r => _.maxBy(r, 'score') + ).map(r => (K.ATTR({ id: String(r.mid) }, { + note: (() => { + const notes = []; + + for (let i = 1; i <= 3; i++) { + if (r.type !== i) continue; + notes.push(K.ATTR({ type: String(r.type) }, { + name: K.ITEM('str', profiles[r.__refid][0].name), + score: K.ITEM('u32', r.score) + })) + } + + return notes; + })() + }))), + }) + }) + } + + return send.object({ sc: { d: _.map( _.groupBy(records, r => { @@ -40,7 +69,7 @@ export const rival: EPR = async (info, data, send) => { await DB.Find(null, { collection: 'profile' }) ).filter(p => p.__refid != refid); - send.object({ + return send.object({ rival: await Promise.all( rivals.map(async (p, index) => { return { @@ -84,7 +113,7 @@ export const saveMix: EPR = async (info, data, send) => { jacket: mix.number('jacket_id'), }); - send.object({ + return send.object({ automation: { mix_id: K.ITEM('s32', id), mix_code: K.ITEM('str', doc.code), @@ -105,11 +134,10 @@ export const loadMix: EPR = async (info, data, send) => { const mix = await DB.FindOne({ collection: 'mix', code }); if (!mix) { - send.object({ result: K.ITEM('s32', 1) }); - return; + return send.object({ result: K.ITEM('s32', 1) }); } - send.object({ + return send.object({ automation: { mix_id: K.ITEM('s32', mix.id), mix_code: K.ITEM('str', mix.code), diff --git a/sdvx@asphyxia/handlers/profiles.ts b/sdvx@asphyxia/handlers/profiles.ts index 217fbb2..3e55e7d 100644 --- a/sdvx@asphyxia/handlers/profiles.ts +++ b/sdvx@asphyxia/handlers/profiles.ts @@ -1,18 +1,12 @@ -import { Skill } from '../models/skill'; -import { SDVX_AUTOMATION_SONGS } from '../data/vvw'; -import { Item } from '../models/item'; -import { Param } from '../models/param'; -import { MusicRecord } from '../models/music_record'; -import { CourseRecord } from '../models/course_record'; -import { Profile } from '../models/profile'; -import { IDToCode } from '../utils'; -import { Mix } from '../models/mix'; - -function getVersion(info: EamuseInfo) { - if (info.method.startsWith('sv4')) return 4; - if (info.method.startsWith('sv5')) return 5; - return 0; -} +import {Skill} from '../models/skill'; +import {SDVX_AUTOMATION_SONGS} from '../data/vvw'; +import {Item} from '../models/item'; +import {Param} from '../models/param'; +import {MusicRecord} from '../models/music_record'; +import {CourseRecord} from '../models/course_record'; +import {Profile} from '../models/profile'; +import {getVersion, IDToCode} from '../utils'; +import {Mix} from '../models/mix'; async function getAutomationMixes(params: Param[]) { const mixids = params @@ -30,12 +24,37 @@ function unlockNavigators(items: Partial[]) { } export const loadScore: EPR = async (info, data, send) => { - const refid = $(data).str('refid'); + const refid = $(data).str('refid', $(data).attr().dataid); if (!refid) return send.deny(); const records = await DB.Find(refid, { collection: 'music' }); - send.object({ + const version = getVersion(info); + + if (version === 1) { + return send.object({ + music: records.map(r => (K.ATTR({ music_id: String(r.mid) }, { + type: (() => { + const records = []; + + for (let i = 1; i <= 3; i++) { + if (r.type != i) continue; + records.push(K.ATTR({ + type_id: String(i), + score: String(r.score), + clear_type: String(r.clear), + score_grade: String(r.grade), + cnt: "0" + })); + } + + return records; + })() + }))) + }); + } + + return send.object({ music: { info: records.map(r => ({ param: K.ARRAY('u32', [ @@ -62,9 +81,57 @@ export const loadScore: EPR = async (info, data, send) => { }; export const saveScore: EPR = async (info, data, send) => { - const refid = $(data).str('refid'); + const refid = $(data).str('refid', $(data).attr().dataid); if (!refid) return send.deny(); + const version = getVersion(info); + + // Booth - Save score + if (version === 1) { + try { + const mid = parseInt($(data).attr().music_id); + const type = parseInt($(data).attr().music_type); + + if (_.isNil(mid) || _.isNil(type)) return send.deny(); + + const record = (await DB.FindOne(refid, { + collection: 'music', + mid, + type, + })) || { + collection: 'music', + mid, + type, + score: 0, + clear: 0, + grade: 0, + buttonRate: 0, + longRate: 0, + volRate: 0, + }; + + const score = $(data).attr().score ? parseInt($(data).attr().score) : 0; + const clear = $(data).attr().clear_type ? parseInt($(data).attr().clear_type) : 0; + const grade = $(data).attr().score_grade ? parseInt($(data).attr().score_grade) : 0; + if (score > record.score) { + record.score = score; + } + + record.clear = Math.max(clear, record.clear); + record.grade = Math.max(grade, record.grade); + + await DB.Upsert( + refid, + { collection: 'music', mid, type }, + record + ); + + return send.success(); + } catch { + return send.deny(); + } + } + const mid = $(data).number('music_id'); const type = $(data).number('music_type'); @@ -103,7 +170,7 @@ export const saveScore: EPR = async (info, data, send) => { record ); - send.success(); + return send.success(); }; export const saveCourse: EPR = async (info, data, send) => { @@ -134,16 +201,47 @@ export const saveCourse: EPR = async (info, data, send) => { } ); - send.success(); + return send.success(); }; export const save: EPR = async (info, data, send) => { - const refid = $(data).str('refid'); + const refid = $(data).str('refid', $(data).attr().refid); if (!refid) return send.deny(); const version = getVersion(info); if (version == 0) return send.deny(); + if (version === 1) { + try { + // Save Profile + await DB.Update( + refid, + { collection: 'profile' }, + { + $set: { + headphone: $(data).number('headphone'), + hiSpeed: $(data).number('hispeed'), + appeal: $(data).number('appeal_id'), + boothFrame: [$(data).number('frame0'), $(data).number('frame1'), $(data).number('frame2'), $(data).number('frame3'), $(data).number('frame4')], + musicID: parseInt($(data).attr("last").music_id), + musicType: parseInt($(data).attr("last").music_type), + sortType: parseInt($(data).attr("last").sort_type), + mUserCnt: $(data).number('m_user_cnt'), + }, + $inc: { + expPoint: $(data).number('gain_exp'), + packets: $(data).number('earned_gamecoin_packet'), + blocks: $(data).number('earned_gamecoin_block'), + }, + } + ); + + return send.success(); + } catch { + return send.deny(); + } + } + // Save Profile await DB.Update( refid, @@ -226,11 +324,11 @@ export const save: EPR = async (info, data, send) => { } ); - send.success(); + return send.success(); }; export const load: EPR = async (info, data, send) => { - const refid = $(data).str('refid'); + const refid = $(data).str('refid', $(data).attr().dataid); if (!refid) return send.deny(); const version = getVersion(info); @@ -241,8 +339,8 @@ export const load: EPR = async (info, data, send) => { }); if (!profile) { - send.object({ result: K.ITEM('u8', 1) }); - return; + if (version === 1) return send.object(K.ATTR({ none: "1" })); + return send.object({ result: K.ITEM('u8', 1) }); } let skill = (await DB.FindOne(refid, { @@ -263,7 +361,11 @@ export const load: EPR = async (info, data, send) => { const currentTime = time.getTime(); const mixes = version == 5 ? await getAutomationMixes(params) : []; - send.pugFile('templates/load.pug', { + if (version === 1) { + return send.pugFile('templates/booth/load.pug', { code: IDToCode(profile.id), ...profile }); + } + + return send.pugFile('templates/load.pug', { courses, items: U.GetConfig('unlock_all_navigators') ? unlockNavigators(items) @@ -279,10 +381,10 @@ export const load: EPR = async (info, data, send) => { }; export const create: EPR = async (info, data, send) => { - const refid = $(data).str('refid'); + const refid = $(data).str('refid', $(data).attr().refid); if (!refid) return send.deny(); - const name = $(data).str('name', 'GUEST'); + const name = $(data).str('name', $(data).attr().name ? $(data).attr().name : 'GUEST'); let id = _.random(0, 99999999); while (await DB.FindOne(null, { collecttion: 'profile', id })) { id = _.random(0, 99999999); @@ -314,10 +416,13 @@ export const create: EPR = async (info, data, send) => { musicID: 0, musicType: 0, sortType: 0, + expPoint: 0, + mUserCnt: 0, + boothFrame: [0, 0, 0, 0, 0] }; await DB.Upsert(refid, { collection: 'profile' }, profile); - send.object({ result: K.ITEM('u8', 0) }); + return send.object({ result: K.ITEM('u8', 0) }); }; export const buy: EPR = async (info, data, send) => { @@ -356,11 +461,11 @@ export const buy: EPR = async (info, data, send) => { ); } - send.object({ + return send.object({ gamecoin_packet: K.ITEM('u32', updated.docs[0].packets), gamecoin_block: K.ITEM('u32', updated.docs[0].blocks), }); } else { - send.success(); + return send.success(); } }; diff --git a/sdvx@asphyxia/index.ts b/sdvx@asphyxia/index.ts index 31a5dd1..dabea66 100644 --- a/sdvx@asphyxia/index.ts +++ b/sdvx@asphyxia/index.ts @@ -1,5 +1,5 @@ -import { common } from './handlers/common'; -import { hiscore, rival, saveMix, loadMix } from './handlers/features'; +import {common} from './handlers/common'; +import {hiscore, rival, saveMix, loadMix} from './handlers/features'; import { updateProfile, updateMix, @@ -29,6 +29,7 @@ export function register() { const MultiRoute = (method: string, handler: EPR | boolean) => { // Helper for register multiple versions. + R.Route(`game.${method}`, handler); R.Route(`game.sv4_${method}`, handler); R.Route(`game.sv5_${method}`, handler); }; @@ -53,15 +54,24 @@ export function register() { MultiRoute('load_ap', loadMix); // Lazy - MultiRoute('lounge', false); - MultiRoute('shop', true); + MultiRoute('lounge', (_, __, send) => send.object({ + interval: K.ITEM('u32', 30) + })); + MultiRoute('shop', (_, __, send) => send.object({ + nxt_time: K.ITEM('u32', 1000 * 5 * 60) + })); MultiRoute('save_e', true); MultiRoute('play_e', true); MultiRoute('play_s', true); MultiRoute('entry_s', true); MultiRoute('entry_e', true); MultiRoute('exception', true); - R.Route('eventlog.write', true); + R.Route('eventlog.write', (_, __, send) => send.object({ + gamesession: K.ITEM('s64', BigInt(1)), + logsendflg: K.ITEM('s32', 0), + logerrlevel: K.ITEM('s32', 0), + evtidnosendflg: K.ITEM('s32', 0) + })); R.Unhandled(); } diff --git a/sdvx@asphyxia/models/profile.ts b/sdvx@asphyxia/models/profile.ts index 045fb67..e10e995 100644 --- a/sdvx@asphyxia/models/profile.ts +++ b/sdvx@asphyxia/models/profile.ts @@ -11,6 +11,9 @@ export interface Profile { packets: number; blocks: number; + expPoint: number; + mUserCnt: number; + musicID: number; musicType: number; sortType: number; @@ -28,4 +31,6 @@ export interface Profile { effCLeft: number; effCRight: number; narrowDown: number; + + boothFrame: number[]; } diff --git a/sdvx@asphyxia/templates/booth/common.pug b/sdvx@asphyxia/templates/booth/common.pug new file mode 100644 index 0000000..6882290 --- /dev/null +++ b/sdvx@asphyxia/templates/booth/common.pug @@ -0,0 +1,16 @@ +- let music = 0; +- let event = 0; +- let catalog = 0; + +game + limited + while music < 200 + music(id=music++, flag=2) + + event + while event < 16 + info(id=event++) + + catalog + while catalog < 256 + info(id=catalog++, currency=1, price=1) diff --git a/sdvx@asphyxia/templates/booth/load.pug b/sdvx@asphyxia/templates/booth/load.pug new file mode 100644 index 0000000..3562737 --- /dev/null +++ b/sdvx@asphyxia/templates/booth/load.pug @@ -0,0 +1,23 @@ +game + name(__type="str") #{name} + code(__type="str") #{code} + gamecoin_packet(__type="u32") #{packets} + gamecoin_block(__type="u32") #{blocks} + exp_point(__type="u32") #{expPoint ? expPoint : 0} + m_user_cnt(__type="u32") #{mUserCnt ? mUserCnt : 0} + have_item(__type="bool" __count=512) #{Array(512).fill(1).join(" ")} + have_note(__type="bool" __count=512) #{Array(512).fill(1).join(" ")} + + last( + music_id=musicID, + music_type=musicType, + sort_type=sortType, + headphone=headphone, + hispeed=hiSpeed, + appeal_id=appeal, + frame0=boothFrame ? boothFrame[0] : 0, + frame1=boothFrame ? boothFrame[1] : 0, + frame2=boothFrame ? boothFrame[2] : 0, + frame3=boothFrame ? boothFrame[3] : 0, + frame4=boothFrame ? boothFrame[4] : 0, + ) diff --git a/sdvx@asphyxia/utils.ts b/sdvx@asphyxia/utils.ts index 3fd5e88..93010c7 100644 --- a/sdvx@asphyxia/utils.ts +++ b/sdvx@asphyxia/utils.ts @@ -1,4 +1,5 @@ -import { Counter } from './models/counter'; +import {Counter} from './models/counter'; + export function IDToCode(id: number) { const padded = _.padStart(id.toString(), 8); return `${padded.slice(0, 4)}-${padded.slice(4)}`; @@ -12,3 +13,11 @@ export async function GetCounter(key: string) { ) ).docs[0].value; } + +export function getVersion(info: EamuseInfo) { + const dateCode = parseInt(info.model.split(":")[4]); + if (dateCode <= 2013052900) return 1; + if (info.method.startsWith('sv4')) return 4; + if (info.method.startsWith('sv5')) return 5; + return 0; +}