mirror of
https://github.com/asphyxia-core/plugins.git
synced 2026-06-18 13:19:29 -05:00
Merge remote-tracking branch 'upstream/stable' into stable
This commit is contained in:
commit
718ded19cf
9
bst@asphyxia/README.md
Normal file
9
bst@asphyxia/README.md
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# BeatStream
|
||||
|
||||
Plugin Version: **v1.0.1**
|
||||
|
||||
Supported Versions:
|
||||
|
||||
- BeatStream アニムトライヴ
|
||||
- Back end ✔
|
||||
- Web UI ✔
|
||||
247
bst@asphyxia/handlers/bst2/common.ts
Normal file
247
bst@asphyxia/handlers/bst2/common.ts
Normal file
|
|
@ -0,0 +1,247 @@
|
|||
import { Bst2EventParamsMap, getKEventControl } from "../../models/bst2/event_params"
|
||||
import { Bst2AccountMap, Bst2BiscoMap, Bst2CourseMap, Bst2MusicRecordMap, Bst2PlayerMap, Bst2SurveyMap, Bst2TipsMap, Bst2UnlockingInfoMap, IBst2Account, IBst2Base, IBst2Bisco, IBst2Course, IBst2CrysisLog, IBst2Customization, IBst2Hacker, IBst2MusicRecord, IBst2Player, IBst2Survey, IBst2Tips, IBst2UnlockingInfo } from "../../models/bst2/profile"
|
||||
import { Bst2CourseLogMap, Bst2StageLogMap, IBst2StageLog } from "../../models/bst2/stagelog"
|
||||
import { bacK, BigIntProxy, boolme, fromMap, mapK, s16me, s32me, s8me, strme, toBigInt } from "../../utility/mapping"
|
||||
import { isToday } from "../../utility/utility_functions"
|
||||
import { DBM } from "../utility/db_manager"
|
||||
import { readPlayerPostProcess, writePlayerPreProcess } from "./processing"
|
||||
|
||||
export namespace Bst2HandlersCommon {
|
||||
export const Common: EPR = async (_0, _1, send) => await send.object({ event_ctrl: { data: getKEventControl() } })
|
||||
|
||||
export const BootPcb: EPR = async (_0, _1, send) => await send.object({ sinfo: { nm: K.ITEM("str", "Asphyxia"), cl_enbl: K.ITEM("bool", 1), cl_h: K.ITEM("u8", 0), cl_m: K.ITEM("u8", 0) } })
|
||||
|
||||
export const StartPlayer: EPR = async (_, data, send) => {
|
||||
let params = fromMap(Bst2EventParamsMap)
|
||||
let rid = $(data).str("rid")
|
||||
let account = DB.FindOne<IBst2Account>(rid, { collection: "bst.bst2.player.account" })
|
||||
if (account == null) params.playerId = -1
|
||||
params.startTime = BigInt(Date.now())
|
||||
send.object(mapK(params, Bst2EventParamsMap))
|
||||
}
|
||||
|
||||
export const PlayerSucceeded: EPR = async (_, data, send) => {
|
||||
let rid = $(data).str("rid")
|
||||
let account: IBst2Account = await DB.FindOne<IBst2Account>(rid, { collection: "bst.bst2.player.account" })
|
||||
let result
|
||||
if (account == null) {
|
||||
result = {
|
||||
play: false,
|
||||
data: { name: "" },
|
||||
record: {},
|
||||
hacker: {},
|
||||
phantom: {}
|
||||
}
|
||||
} else {
|
||||
let base: IBst2Base = await DB.FindOne<IBst2Base>(rid, { collection: "bst.bst2.player.base" })
|
||||
let records: IBst2MusicRecord[] = await DB.Find<IBst2MusicRecord>({ collection: "bst.bst2.playData.musicRecord#userId", userId: account.userId })
|
||||
result = {
|
||||
play: true,
|
||||
data: { name: base.name },
|
||||
record: {},
|
||||
hacker: {},
|
||||
phantom: {}
|
||||
}
|
||||
}
|
||||
send.object(mapK(result, {
|
||||
play: boolme(),
|
||||
data: { name: strme() },
|
||||
record: {},
|
||||
hacker: {},
|
||||
phantom: {}
|
||||
}))
|
||||
}
|
||||
|
||||
export const ReadPlayer: EPR = async (_, data, send) => {
|
||||
let refid = $(data).str("rid")
|
||||
let account = await DB.FindOne<IBst2Account>(refid, { collection: "bst.bst2.player.account" })
|
||||
if (account == null) return await send.deny()
|
||||
|
||||
let base = await DB.FindOne<IBst2Base>(refid, { collection: "bst.bst2.player.base" })
|
||||
let survey = await DB.FindOne<IBst2Survey>(refid, { collection: "bst.bst2.player.survey" }) || fromMap(Bst2SurveyMap)
|
||||
let unlocking = await DB.Find<IBst2UnlockingInfo>(refid, { collection: "bst.bst2.player.unlockingInfo" })
|
||||
let customize = await DB.FindOne<IBst2Customization>(refid, { collection: "bst.bst2.player.customization" })
|
||||
let tips = await DB.FindOne<IBst2Tips>(refid, { collection: "bst.bst2.player.tips" }) || fromMap(Bst2TipsMap)
|
||||
let hacker = await DB.Find<IBst2Hacker>(refid, { collection: "bst.bst2.player.hacker" })
|
||||
let crysis = await DB.Find<IBst2CrysisLog>(refid, { collection: "bst.bst2.player.event.crysis" })
|
||||
let bisco = await DB.FindOne<IBst2Bisco>(refid, { collection: "bst.bst2.player.bisco" }) || fromMap(Bst2BiscoMap)
|
||||
let records = await DB.Find<IBst2MusicRecord>({ collection: "bst.bst2.playData.musicRecord#userId", userId: account.userId })
|
||||
let courses = await DB.Find<IBst2Course>({ collection: "bst.bst2.playData.course#userId", userId: account.userId })
|
||||
|
||||
account.previousStartTime = account.standardTime
|
||||
account.standardTime = BigInt(Date.now())
|
||||
account.ea = true
|
||||
account.intrvld = 0
|
||||
account.playCount++
|
||||
account.playCountToday++
|
||||
let eventPlayLog: { crysis?: IBst2CrysisLog[] } = {}
|
||||
if (crysis.length != 0) eventPlayLog.crysis = crysis
|
||||
|
||||
let player: IBst2Player = {
|
||||
pdata: {
|
||||
account: account,
|
||||
base: base,
|
||||
survey: survey,
|
||||
opened: {},
|
||||
item: (unlocking.length == 0) ? {} : { info: unlocking },
|
||||
customize: customize,
|
||||
tips: tips,
|
||||
hacker: (hacker.length == 0) ? {} : { info: hacker },
|
||||
playLog: eventPlayLog,
|
||||
bisco: { pinfo: bisco },
|
||||
record: (records.length == 0) ? {} : { rec: records },
|
||||
course: (courses.length == 0) ? {} : { record: courses }
|
||||
}
|
||||
}
|
||||
send.object(readPlayerPostProcess(mapK(player, Bst2PlayerMap)))
|
||||
}
|
||||
|
||||
export const WritePlayer: EPR = async (_, data, send) => {
|
||||
let player = bacK(writePlayerPreProcess(data), Bst2PlayerMap).data
|
||||
let refid = player.pdata.account.refid
|
||||
let userId = player.pdata.account.userId
|
||||
let now = BigIntProxy(BigInt(Date.now()))
|
||||
|
||||
let opm = new DBM.DBOperationManager()
|
||||
|
||||
let oldAccount = await DB.FindOne<IBst2Account>(refid, { collection: "bst.bst2.player.account" })
|
||||
if (!oldAccount) {
|
||||
do {
|
||||
userId = Math.round(Math.random() * 99999999)
|
||||
} while ((await DB.Find<IBst2Account>(null, { collection: "bst.bst2.player.account", userId: userId })).length > 0)
|
||||
oldAccount = fromMap(Bst2AccountMap)
|
||||
oldAccount.userId = userId
|
||||
} else {
|
||||
oldAccount.playCount++
|
||||
if (!isToday(toBigInt(oldAccount.standardTime))) {
|
||||
oldAccount.dayCount++
|
||||
oldAccount.playCountToday = 1
|
||||
} else oldAccount.playCountToday++
|
||||
}
|
||||
oldAccount.standardTime = BigIntProxy(BigInt(Date.now()))
|
||||
opm.upsert<IBst2Account>(refid, { collection: "bst.bst2.player.account" }, oldAccount)
|
||||
if (player.pdata.base) opm.upsert<IBst2Base>(refid, { collection: "bst.bst2.player.base" }, player.pdata.base)
|
||||
if (player.pdata.item?.info?.length > 0) for (let u of player.pdata.item.info) opm.upsert<IBst2UnlockingInfo>(refid, { collection: "bst.bst2.player.unlockingInfo", type: u.type, id: u.id }, u)
|
||||
if (player.pdata.customize) opm.upsert<IBst2Customization>(refid, { collection: "bst.bst2.player.customization" }, player.pdata.customize)
|
||||
if (player.pdata.tips) opm.upsert<IBst2Base>(refid, { collection: "bst.bst2.player.base" }, player.pdata.base)
|
||||
if (player.pdata.hacker?.info?.length > 0) for (let h of player.pdata.hacker.info) {
|
||||
h.updateTime = now
|
||||
opm.upsert<IBst2Hacker>(refid, { collection: "bst.bst2.player.hacker", id: h.id }, h)
|
||||
}
|
||||
if (player.pdata.playLog?.crysis?.length > 0) for (let c of player.pdata.playLog.crysis) opm.upsert<IBst2CrysisLog>(refid, { collection: "bst.bst2.player.event.crysis", id: c.id, stageId: c.stageId }, c)
|
||||
|
||||
await DBM.operate(opm)
|
||||
send.object({ uid: K.ITEM("s32", oldAccount.userId) })
|
||||
}
|
||||
|
||||
export const WriteStageLog: EPR = async (_, data, send) => {
|
||||
await updateRecordFromStageLog(bacK(data, Bst2StageLogMap).data, false)
|
||||
send.success()
|
||||
}
|
||||
|
||||
export const WriteCourseStageLog: EPR = async (_, data, send) => {
|
||||
await updateRecordFromStageLog(bacK(data, Bst2StageLogMap).data, true)
|
||||
send.success()
|
||||
}
|
||||
|
||||
async function updateRecordFromStageLog(stageLog: IBst2StageLog, isCourseStage: boolean) {
|
||||
let query: Query<IBst2MusicRecord> = { collection: "bst.bst2.playData.musicRecord#userId", userId: stageLog.userId, musicId: stageLog.musicId, chart: stageLog.chart }
|
||||
let oldRecord = await DB.FindOne<IBst2MusicRecord>(query)
|
||||
|
||||
let time = Date.now()
|
||||
stageLog.time = time
|
||||
stageLog.isCourseStage = isCourseStage
|
||||
|
||||
if (oldRecord == null) {
|
||||
oldRecord = fromMap(Bst2MusicRecordMap)
|
||||
oldRecord.musicId = stageLog.musicId
|
||||
oldRecord.chart = stageLog.chart
|
||||
oldRecord.clearCount = (stageLog.medal >= 3) ? 1 : 0
|
||||
oldRecord.score = stageLog.score
|
||||
oldRecord.grade = stageLog.grade
|
||||
oldRecord.gaugeTimes10 = stageLog.gaugeTimes10
|
||||
oldRecord.playCount = 1
|
||||
oldRecord.medal = stageLog.medal
|
||||
oldRecord.combo = stageLog.combo
|
||||
oldRecord.lastPlayTime = time
|
||||
oldRecord.updateTime = time
|
||||
oldRecord.userId = stageLog.userId
|
||||
} else {
|
||||
if (stageLog.medal >= 3) oldRecord.clearCount++
|
||||
if (oldRecord.score < stageLog.score) {
|
||||
oldRecord.updateTime = time
|
||||
oldRecord.score = stageLog.score
|
||||
}
|
||||
if (oldRecord.grade < stageLog.grade) {
|
||||
oldRecord.updateTime = time
|
||||
oldRecord.grade = stageLog.grade
|
||||
}
|
||||
if (oldRecord.gaugeTimes10 < stageLog.gaugeTimes10) {
|
||||
oldRecord.updateTime = time
|
||||
oldRecord.gaugeTimes10 = stageLog.gaugeTimes10
|
||||
}
|
||||
if (oldRecord.medal < stageLog.medal) {
|
||||
oldRecord.updateTime = time
|
||||
oldRecord.medal = stageLog.medal
|
||||
}
|
||||
if (oldRecord.combo < stageLog.combo) {
|
||||
oldRecord.updateTime = time
|
||||
oldRecord.combo = stageLog.combo
|
||||
}
|
||||
oldRecord.lastPlayTime = time
|
||||
oldRecord.playCount++
|
||||
}
|
||||
DBM.upsert(null, query, oldRecord)
|
||||
DBM.insert(null, stageLog)
|
||||
}
|
||||
|
||||
export const WriteCourseLog: EPR = async (_, data, send) => {
|
||||
let courseLog = bacK(data, Bst2CourseLogMap).data
|
||||
let query: Query<IBst2Course> = { collection: "bst.bst2.playData.course#userId", userId: courseLog.userId, courseId: courseLog.courseId }
|
||||
let oldRecord = await DB.FindOne<IBst2Course>(query)
|
||||
|
||||
let time = Date.now()
|
||||
courseLog.time = time
|
||||
|
||||
if (oldRecord == null) {
|
||||
oldRecord = fromMap(Bst2CourseMap)
|
||||
oldRecord.courseId = courseLog.courseId
|
||||
oldRecord.score = courseLog.score
|
||||
oldRecord.grade = courseLog.grade
|
||||
oldRecord.gauge = courseLog.gauge
|
||||
oldRecord.playCount = 1
|
||||
oldRecord.medal = courseLog.medal
|
||||
oldRecord.combo = courseLog.combo
|
||||
oldRecord.lastPlayTime = time
|
||||
oldRecord.updateTime = time
|
||||
oldRecord.userId = courseLog.userId
|
||||
} else {
|
||||
if (oldRecord.score < courseLog.score) {
|
||||
oldRecord.updateTime = time
|
||||
oldRecord.score = courseLog.score
|
||||
}
|
||||
if (oldRecord.grade < courseLog.grade) {
|
||||
oldRecord.updateTime = time
|
||||
oldRecord.grade = courseLog.grade
|
||||
}
|
||||
if (oldRecord.gauge < courseLog.gauge) {
|
||||
oldRecord.updateTime = time
|
||||
oldRecord.gauge = courseLog.gauge
|
||||
}
|
||||
if (oldRecord.medal < courseLog.medal) {
|
||||
oldRecord.updateTime = time
|
||||
oldRecord.medal = courseLog.medal
|
||||
}
|
||||
if (oldRecord.combo < courseLog.combo) {
|
||||
oldRecord.updateTime = time
|
||||
oldRecord.combo = courseLog.combo
|
||||
}
|
||||
oldRecord.lastPlayTime = time
|
||||
oldRecord.playCount++
|
||||
}
|
||||
DBM.upsert(null, query, oldRecord)
|
||||
DBM.insert(null, courseLog)
|
||||
|
||||
send.success()
|
||||
}
|
||||
}
|
||||
12
bst@asphyxia/handlers/bst2/processing.ts
Normal file
12
bst@asphyxia/handlers/bst2/processing.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import { IBst2Player } from "../../models/bst2/profile"
|
||||
import { KITEM2 } from "../../utility/mapping"
|
||||
import { toFullWidth, toHalfWidth } from "../../utility/utility_functions"
|
||||
|
||||
export function readPlayerPostProcess(player: KITEM2<IBst2Player>): KITEM2<IBst2Player> {
|
||||
if (player.pdata.base?.name != null) player.pdata.base.name["@content"] = toFullWidth(player.pdata.base.name["@content"])
|
||||
return player
|
||||
}
|
||||
export function writePlayerPreProcess(player: KITEM2<IBst2Player>): KITEM2<IBst2Player> {
|
||||
if (player.pdata.base?.name != null) player.pdata.base.name["@content"] = toHalfWidth(player.pdata.base.name["@content"])
|
||||
return player
|
||||
}
|
||||
39
bst@asphyxia/handlers/bst2/webui.ts
Normal file
39
bst@asphyxia/handlers/bst2/webui.ts
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
import { IBst2Base, IBst2Customization } from "../../models/bst2/profile"
|
||||
import { WebUIMessageType } from "../../models/utility/webui_message"
|
||||
import { DBM } from "../utility/db_manager"
|
||||
import { UtilityHandlersWebUI } from "../utility/webui"
|
||||
|
||||
export namespace Bst2HandlersWebUI {
|
||||
export const UpdateSettings = async (data: {
|
||||
refid: string
|
||||
name: string
|
||||
rippleNote: number
|
||||
sfxNormalNote: number
|
||||
sfxRippleNote: number
|
||||
sfxSlashNote: number
|
||||
sfxStreamNote: number
|
||||
backgroundBrightness: number
|
||||
judgeText: number
|
||||
rippleNoteGuide: number
|
||||
streamNoteGuide: number
|
||||
sfxFine: number
|
||||
sfxStreamNoteTail: number
|
||||
}) => {
|
||||
try {
|
||||
let customization = await DB.FindOne<IBst2Customization>(data.refid, { collection: "bst.bst2.player.customization" })
|
||||
if (customization == null) throw new Error("No profile for refid=" + data.refid)
|
||||
customization.custom[0] = data.rippleNote
|
||||
customization.custom[2] = data.sfxNormalNote
|
||||
customization.custom[3] = data.sfxRippleNote
|
||||
customization.custom[4] = data.sfxSlashNote
|
||||
customization.custom[5] = data.sfxStreamNote
|
||||
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<IBst2Customization>(data.refid, { collection: "bst.bst2.player.customization" }, customization)
|
||||
UtilityHandlersWebUI.pushMessage("Save BeatStream Animtribe settings succeeded!", 2, WebUIMessageType.success, data.refid)
|
||||
} catch (e) {
|
||||
UtilityHandlersWebUI.pushMessage("Error while save BeatStream Animtribe settings: " + e.message, 2, WebUIMessageType.error, data.refid)
|
||||
}
|
||||
}
|
||||
}
|
||||
21
bst@asphyxia/handlers/utility/batch.ts
Normal file
21
bst@asphyxia/handlers/utility/batch.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import { IBatchResult } from "../../models/utility/batch"
|
||||
import { IPluginVersion } from "../../models/utility/plugin_version"
|
||||
import { isHigherVersion } from "../../utility/utility_functions"
|
||||
import { DBM } from "./db_manager"
|
||||
|
||||
export namespace Batch {
|
||||
let registeredBatch = <{ id: string, version: string, batch: () => Promise<any> }[]>[]
|
||||
|
||||
export async function execute(version: string): Promise<void> {
|
||||
for (let b of registeredBatch) {
|
||||
if ((await DB.Find<IBatchResult>({ collection: "bst.batchResult", batchId: b.id })).length == 0) if (!isHigherVersion(version, b.version)) {
|
||||
await b.batch()
|
||||
await DBM.insert<IBatchResult>(null, { collection: "bst.batchResult", batchId: b.id })
|
||||
}
|
||||
}
|
||||
}
|
||||
export function register(id: string, version: string, batch: () => Promise<any>) {
|
||||
registeredBatch.push({ id: id, version: version, batch: batch })
|
||||
}
|
||||
|
||||
}
|
||||
7
bst@asphyxia/handlers/utility/batch_initialize.ts
Normal file
7
bst@asphyxia/handlers/utility/batch_initialize.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import { Batch } from "./batch"
|
||||
import { DBM } from "./db_manager"
|
||||
import { bufferToBase64, log } from "../../utility/utility_functions"
|
||||
|
||||
export function initializeBatch() {
|
||||
/* Register batch here **/
|
||||
}
|
||||
17
bst@asphyxia/handlers/utility/common.ts
Normal file
17
bst@asphyxia/handlers/utility/common.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
export namespace UtilityHandlersCommon {
|
||||
export const WriteShopInfo: EPR = async (__, ___, send) => {
|
||||
let result = {
|
||||
sinfo: {
|
||||
lid: K.ITEM("str", "ea"),
|
||||
nm: K.ITEM("str", "Asphyxia shop"),
|
||||
cntry: K.ITEM("str", "Japan"),
|
||||
rgn: K.ITEM("str", "1"),
|
||||
prf: K.ITEM("s16", 13),
|
||||
cl_enbl: K.ITEM("bool", 0),
|
||||
cl_h: K.ITEM("u8", 8),
|
||||
cl_m: K.ITEM("u8", 0)
|
||||
}
|
||||
}
|
||||
send.object(result)
|
||||
}
|
||||
}
|
||||
212
bst@asphyxia/handlers/utility/db_manager.ts
Normal file
212
bst@asphyxia/handlers/utility/db_manager.ts
Normal file
|
|
@ -0,0 +1,212 @@
|
|||
import { ICollection } from "../../models/utility/definitions"
|
||||
import { log } from "../../utility/utility_functions"
|
||||
|
||||
export namespace DBM {
|
||||
export interface IDBCollectionName extends ICollection<"dbManager.collectionName"> {
|
||||
name: string
|
||||
}
|
||||
export interface IDBOperation<T = any, TOperation extends "insert" | "update" | "upsert" | "remove" | "skip" = "insert" | "update" | "upsert" | "remove" | "skip"> {
|
||||
refid?: string
|
||||
query: TOperation extends "insert" ? null : Query<T>
|
||||
operation: TOperation
|
||||
doc: TOperation extends "remove" ? null : T | Doc<T>
|
||||
isPublicDoc?: boolean
|
||||
}
|
||||
export class DBOperationManager {
|
||||
public operations: IDBOperation[] = []
|
||||
|
||||
public push(...op: IDBOperation[]): void {
|
||||
this.operations.push(...op)
|
||||
}
|
||||
public update<T extends ICollection<any>>(refid: string | null, query: Query<T>, data: Doc<T>, isPublicDoc: boolean = true): void {
|
||||
for (let o of this.operations) if (o.doc && DBOperationManager.isMatch(o.doc, query)) o.operation = "skip"
|
||||
this.operations.push({ refid: refid, query: query, operation: "update", doc: data, isPublicDoc: isPublicDoc })
|
||||
}
|
||||
public upsert<T extends ICollection<any>>(refid: string | null, query: Query<T>, data: Doc<T>, isPublicDoc: boolean = true): void {
|
||||
for (let o of this.operations) if (o.doc && DBOperationManager.isMatch(o.doc, query)) o.operation = "skip"
|
||||
this.operations.push({ refid: refid, query: query, operation: "upsert", doc: data, isPublicDoc: isPublicDoc })
|
||||
}
|
||||
public insert<T extends ICollection<any>>(refid: string | null, data: Doc<T>, isPublicDoc: boolean = true): void {
|
||||
this.operations.push({ refid: refid, operation: "insert", query: null, doc: data, isPublicDoc: isPublicDoc })
|
||||
}
|
||||
public remove<T extends ICollection<any>>(refid: string | null, query: Query<T>, isPublicDoc: boolean = true): void {
|
||||
for (let o of this.operations) if (o.doc && DBOperationManager.isMatch(o.doc, query)) o.operation = "skip"
|
||||
this.operations.push({ refid: refid, query: query, operation: "remove", doc: null, isPublicDoc: isPublicDoc })
|
||||
}
|
||||
public async findOne<T extends ICollection<any>>(refid: string | null, query: Query<T>, isPublicDoc: boolean = true): Promise<T | Doc<T>> {
|
||||
for (let i = this.operations.length - 1; i >= 0; i--) {
|
||||
let o = this.operations[i]
|
||||
if (o.doc == null) continue
|
||||
if (DBOperationManager.isMatch(o.doc, query) && ((o.refid && refid) ? (o.refid == refid) : true)) return o.doc
|
||||
}
|
||||
return ((refid == null) && isPublicDoc) ? await DB.FindOne<T>(query) : await DB.FindOne<T>(refid, query)
|
||||
}
|
||||
public async find<T extends ICollection<any>>(refid: string | null, query: Query<T>, isPublicDoc: boolean = true): Promise<(T | Doc<T>)[]> {
|
||||
let result: (T | Doc<T>)[] = []
|
||||
for (let o of this.operations) {
|
||||
if (o.doc == null) continue
|
||||
if (DBOperationManager.isMatch(o.doc, query) && ((o.refid && refid) ? (o.refid == refid) : true)) result.push(o.doc)
|
||||
}
|
||||
return result.concat(await (((refid == null) && isPublicDoc) ? DB.Find<T>(query) : DB.Find<T>(refid, query)))
|
||||
}
|
||||
private static isMatch<T>(entry: T | Doc<T>, query: Query<T>): boolean {
|
||||
if (entry == null) return query == null
|
||||
if (query.$where && !query.$where.apply(entry)) return false
|
||||
let $orResult = null
|
||||
let skipKeys = ["$where", "_id"]
|
||||
for (let qk in query) {
|
||||
if (skipKeys.includes(qk)) continue
|
||||
switch (qk) {
|
||||
case "$or": {
|
||||
if ($orResult == null) $orResult = false
|
||||
for (let or of query.$or) if (this.isMatch(entry, or)) $orResult = true
|
||||
break
|
||||
}
|
||||
case "$and": {
|
||||
for (let and of query.$and) if (!this.isMatch(entry, and)) return false
|
||||
break
|
||||
}
|
||||
case "$not": {
|
||||
if (this.isMatch(entry, query.$not)) return false
|
||||
break
|
||||
}
|
||||
default: {
|
||||
let value = entry[qk]
|
||||
let q = query[qk]
|
||||
if (value == q) continue
|
||||
if ((typeof q != "object") && (typeof q != "function")) return false
|
||||
if ((q.$exists != null)) if ((q.$exists && (value == null)) || (!q.$exists && (value != null))) return false
|
||||
if (Array.isArray(value)) {
|
||||
if (q.$elemMatch && !this.isMatch(value, q.$elemMatch)) return false
|
||||
if (q.$size && (value.length != q.$size)) return false
|
||||
continue
|
||||
} else if ((typeof value == "number") || (typeof value == "string")) {
|
||||
if (q.$lt) if (value >= q.$lt) return false
|
||||
if (q.$lte) if (value > q.$lte) return false
|
||||
if (q.$gt) if (value <= q.$gt) return false
|
||||
if (q.$gte) if (value < q.$gte) return false
|
||||
if (q.$in) if (!value.toString().includes(q.$in)) return false
|
||||
if (q.$nin) if (value.toString().includes(q.$nin)) return false
|
||||
if (q.$ne) if (value == q.$ne) return false
|
||||
if (q.$regex) if (value.toString().match(q.$regex).length == 0) return false
|
||||
continue
|
||||
} else if (typeof value == "object") {
|
||||
if (!this.isMatch(value, q)) return false
|
||||
continue
|
||||
} else if (q != null) return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return ($orResult == null) || $orResult
|
||||
}
|
||||
}
|
||||
export async function getCollectionNames(filter?: string): Promise<IDBCollectionName[]> {
|
||||
let result = await DB.Find<IDBCollectionName>({ collection: "dbManager.collectionName" })
|
||||
if (filter != null) {
|
||||
let filters = filter.split(",")
|
||||
for (let i = 0; i < filter.length; i++) filters[i] = filters[i].trim()
|
||||
let i = 0
|
||||
while (i < result.length) {
|
||||
let removeFlag = false
|
||||
for (let f of filters) if (f.startsWith("!") ? !result[i].name.includes(f) : result[i].name.includes(f)) {
|
||||
result.splice(i, 1)
|
||||
removeFlag = true
|
||||
break
|
||||
}
|
||||
if (!removeFlag) i++
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
async function checkData<T extends ICollection<any>>(data: T): Promise<void> {
|
||||
for (let k in data) if (k.startsWith("__")) delete data[k]
|
||||
if (await DB.FindOne<IDBCollectionName>({ collection: "dbManager.collectionName", name: data.collection }) == null) {
|
||||
await DB.Insert<IDBCollectionName>({ collection: "dbManager.collectionName", name: data.collection })
|
||||
}
|
||||
}
|
||||
export async function update<T extends ICollection<any>>(refid: string | null, query: Query<T>, data: Doc<T>, isPublicDoc: boolean = true) {
|
||||
checkData(data)
|
||||
if (refid == null) return isPublicDoc ? await DB.Update(query, data) : await DB.Update(null, query, data)
|
||||
else return await DB.Update(refid, query, data)
|
||||
}
|
||||
export async function upsert<T extends ICollection<any>>(refid: string | null, query: Query<T>, data: Doc<T>, isPublicDoc: boolean = true) {
|
||||
checkData(data)
|
||||
if (refid == null) return isPublicDoc ? await DB.Upsert(query, data) : await DB.Upsert(null, query, data)
|
||||
else return await DB.Upsert(refid, query, data)
|
||||
}
|
||||
export async function insert<T extends ICollection<any>>(refid: string | null, data: Doc<T>, isPublicDoc: boolean = true) {
|
||||
checkData(data)
|
||||
if (refid == null) return isPublicDoc ? await DB.Insert(data) : await DB.Insert(null, data)
|
||||
else return await DB.Insert(refid, data)
|
||||
}
|
||||
export async function remove<T extends ICollection<any>>(refid: string | null, query: Query<T>, isPublicDoc: boolean = true) {
|
||||
if (refid == null) return isPublicDoc ? await DB.Remove(query) : await DB.Remove(null, query)
|
||||
else return await DB.Remove(refid, query)
|
||||
}
|
||||
|
||||
export async function operate(operations: DBOperationManager) {
|
||||
let result = []
|
||||
for (let o of operations.operations) {
|
||||
if (o.operation == "skip") continue
|
||||
if (o.doc) delete o.doc._id
|
||||
try {
|
||||
switch (o.operation) {
|
||||
case "insert":
|
||||
result.push(await insert(o.refid, o.doc, o.isPublicDoc))
|
||||
break
|
||||
case "update":
|
||||
result.push(await update(o.refid, o.query, o.doc, o.isPublicDoc))
|
||||
break
|
||||
case "upsert":
|
||||
result.push(await upsert(o.refid, o.query, o.doc, o.isPublicDoc))
|
||||
break
|
||||
case "remove":
|
||||
result.push(await remove(o.refid, o.query, o.isPublicDoc))
|
||||
break
|
||||
}
|
||||
} catch (e) {
|
||||
await log(new Date().toLocaleString() + " Error: " + (e as Error).message)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
export async function removeAllData(refid?: string, filter?: string) {
|
||||
for (let c of await getCollectionNames(filter)) remove(refid, { collection: c.name })
|
||||
|
||||
if ((refid == null) && (filter == null)) remove(null, { collection: "dbManager.collectionName" })
|
||||
}
|
||||
export async function overall(refid: string, userId: number, filter: string, operation: "delete" | "export" | "override", data?: any) {
|
||||
if (refid == null) return
|
||||
try {
|
||||
let collections = await DBM.getCollectionNames(filter)
|
||||
let traverse = async (f: (rid: string | null, query: Query<ICollection<any>>) => Promise<any>) => {
|
||||
let result = []
|
||||
for (let c of collections) {
|
||||
if (c.name.includes("#userId") && (userId != null)) result.concat(...await f(null, { collection: c.name, userId: userId }))
|
||||
else result.concat(...await f(refid, { collection: c.name }))
|
||||
}
|
||||
return result
|
||||
}
|
||||
switch (operation) {
|
||||
case "delete":
|
||||
await traverse((rid, query) => DBM.remove(rid, query))
|
||||
break
|
||||
case "export":
|
||||
let result = await traverse((rid, query) => DB.Find(rid, query))
|
||||
return JSON.stringify(result)
|
||||
case "override":
|
||||
if (!Array.isArray(data)) return "The data may not be an Asphyxia CORE savedata."
|
||||
await traverse((rid, query) => DBM.remove(rid, query))
|
||||
for (let d of data) if ((typeof (d?.collection) == "string") && (!d.collection.includes(filter))) DB.Insert(d)
|
||||
break
|
||||
}
|
||||
} catch (e) {
|
||||
return e.message
|
||||
}
|
||||
return null
|
||||
|
||||
}
|
||||
}
|
||||
15
bst@asphyxia/handlers/utility/initialize.ts
Normal file
15
bst@asphyxia/handlers/utility/initialize.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import { initializeBatch } from "./batch_initialize"
|
||||
import { IPluginVersion } from "../../models/utility/plugin_version"
|
||||
import { isHigherVersion } from "../../utility/utility_functions"
|
||||
import { Batch } from "./batch"
|
||||
import { DBM } from "./db_manager"
|
||||
import { version } from "../../utility/about"
|
||||
|
||||
export async function initialize() {
|
||||
let oldVersion = await DB.FindOne<IPluginVersion>({ collection: "bst.pluginVersion" })
|
||||
if ((oldVersion == null) || isHigherVersion(oldVersion.version, version)) {
|
||||
initializeBatch()
|
||||
await Batch.execute(version)
|
||||
await DBM.upsert<IPluginVersion>(null, { collection: "bst.pluginVersion" }, { collection: "bst.pluginVersion", version: version })
|
||||
}
|
||||
}
|
||||
12
bst@asphyxia/handlers/utility/webui.ts
Normal file
12
bst@asphyxia/handlers/utility/webui.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import { IWebUIMessage, WebUIMessageType } from "../../models/utility/webui_message"
|
||||
import { DBM } from "./db_manager"
|
||||
|
||||
export namespace UtilityHandlersWebUI {
|
||||
export function pushMessage(message: string, version: number, type: WebUIMessageType, rid?: string) {
|
||||
DBM.upsert<IWebUIMessage>(null, { collection: "utility.webuiMessage" }, { collection: "utility.webuiMessage", message: message, type: type, refid: rid, version: version })
|
||||
}
|
||||
|
||||
export const removeWebUIMessage = async () => {
|
||||
await DBM.remove<IWebUIMessage>(null, { collection: "utility.webuiMessage" })
|
||||
}
|
||||
}
|
||||
32
bst@asphyxia/index.ts
Normal file
32
bst@asphyxia/index.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import { UtilityHandlersCommon } from "./handlers/utility/common"
|
||||
import { UtilityHandlersWebUI } from "./handlers/utility/webui"
|
||||
import { initialize } from "./handlers/utility/initialize"
|
||||
import { Bst2HandlersCommon } from "./handlers/bst2/common"
|
||||
import { Bst2HandlersWebUI } from "./handlers/bst2/webui"
|
||||
|
||||
export function register() {
|
||||
R.GameCode("NBT")
|
||||
|
||||
routeBst2()
|
||||
|
||||
R.WebUIEvent("removeWebUIMessage", UtilityHandlersWebUI.removeWebUIMessage)
|
||||
|
||||
R.Unhandled()
|
||||
|
||||
initialize()
|
||||
}
|
||||
|
||||
function routeBst2() {
|
||||
R.Route("info2.common", Bst2HandlersCommon.Common)
|
||||
R.Route("pcb2.boot", Bst2HandlersCommon.BootPcb)
|
||||
R.Route("player2.start", Bst2HandlersCommon.StartPlayer)
|
||||
R.Route("player2.continue", Bst2HandlersCommon.StartPlayer)
|
||||
R.Route("player2.succeed", Bst2HandlersCommon.PlayerSucceeded)
|
||||
R.Route("player2.read", Bst2HandlersCommon.ReadPlayer)
|
||||
R.Route("player2.write", Bst2HandlersCommon.WritePlayer)
|
||||
R.Route("player2.stagedata_write", Bst2HandlersCommon.WriteStageLog)
|
||||
R.Route("player2.course_stage_data_write", Bst2HandlersCommon.WriteCourseStageLog)
|
||||
R.Route("player2.course_data_write", Bst2HandlersCommon.WriteCourseLog)
|
||||
|
||||
R.WebUIEvent("bst2UpdateSettings", Bst2HandlersWebUI.UpdateSettings)
|
||||
}
|
||||
47
bst@asphyxia/models/bst2/event_params.ts
Normal file
47
bst@asphyxia/models/bst2/event_params.ts
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
import { BigIntProxy, boolme, KITEM2, KM, s32me, u64me } from "../../utility/mapping"
|
||||
|
||||
export interface IFloorInfectionEventParams {
|
||||
id: number
|
||||
musicList: number
|
||||
isCompleted: boolean
|
||||
}
|
||||
export const FloorInfectionEventParamsMap: KM<IFloorInfectionEventParams> = {
|
||||
id: s32me("infection_id", 20),
|
||||
musicList: s32me("music_list", 7),
|
||||
isCompleted: boolme("is_complete", true)
|
||||
}
|
||||
|
||||
export interface IBst2EventParams {
|
||||
playerId: number
|
||||
startTime: bigint | BigIntProxy
|
||||
hasRbCollaboration: boolean
|
||||
hasPopnCollaboration: boolean
|
||||
floorInfection: { event: IFloorInfectionEventParams }
|
||||
museca: { isPlayedMuseca: boolean }
|
||||
}
|
||||
export const Bst2EventParamsMap: KM<IBst2EventParams> = {
|
||||
playerId: s32me("plyid"),
|
||||
startTime: u64me("start_time"),
|
||||
hasRbCollaboration: boolme("reflec_collabo", true),
|
||||
hasPopnCollaboration: boolme("pop_collabo", true),
|
||||
floorInfection: { event: FloorInfectionEventParamsMap, $targetKey: "floor_infection" },
|
||||
museca: { isPlayedMuseca: boolme("is_play_museca", true) },
|
||||
}
|
||||
|
||||
export interface IBst2EventControl {
|
||||
type: number
|
||||
phase: number
|
||||
}
|
||||
export const Bst2EventControlMap: KM<IBst2EventControl> = {
|
||||
type: s32me(),
|
||||
phase: s32me()
|
||||
}
|
||||
|
||||
let kEventControl: KITEM2<IBst2EventControl>[]
|
||||
export function getKEventControl(): KITEM2<IBst2EventControl>[] {
|
||||
if (kEventControl == null) {
|
||||
kEventControl = []
|
||||
for (let i = 0; i <= 40; i++) for (let j = 0; j <= 25; j++) kEventControl.push(<any>{ type: K.ITEM("s32", i), phase: K.ITEM("s32", j) })
|
||||
}
|
||||
return kEventControl
|
||||
}
|
||||
263
bst@asphyxia/models/bst2/profile.ts
Normal file
263
bst@asphyxia/models/bst2/profile.ts
Normal file
|
|
@ -0,0 +1,263 @@
|
|||
import { BigIntProxy, boolme, colme, ignoreme, KM, s16me, s32me, s8me, strme, u16me, u64me, u8me } from "../../utility/mapping"
|
||||
import { FixedSizeArray } from "../../utility/type"
|
||||
import { ICollection } from "../utility/definitions"
|
||||
|
||||
export interface IBst2Account extends ICollection<"bst.bst2.player.account"> {
|
||||
userId: number
|
||||
isTakeOver: number
|
||||
playerId: number
|
||||
continueCount: number
|
||||
playCount: number
|
||||
playCountToday: number
|
||||
crd: number
|
||||
brd: number
|
||||
dayCount: number
|
||||
refid: string
|
||||
lobbyId: string
|
||||
mode: number
|
||||
version: number
|
||||
pp: boolean
|
||||
ps: boolean
|
||||
pay: number
|
||||
payedPlayCount: number
|
||||
standardTime: bigint | BigIntProxy
|
||||
intrvld?: number
|
||||
previousStartTime?: bigint | BigIntProxy
|
||||
ea?: boolean
|
||||
}
|
||||
export const Bst2AccountMap: KM<IBst2Account> = {
|
||||
collection: colme<IBst2Account>("bst.bst2.player.account"),
|
||||
userId: s32me("usrid"),//
|
||||
isTakeOver: s32me("is_takeover"),//
|
||||
playerId: s32me("plyid"),
|
||||
continueCount: s32me("continue_cnt"),
|
||||
playCount: s32me("tpc"),//
|
||||
playCountToday: s32me("dpc"),//
|
||||
crd: s32me(),//
|
||||
brd: s32me(),//
|
||||
dayCount: s32me("tdc"),//
|
||||
refid: strme("rid"),
|
||||
lobbyId: strme("lid", "Asphyxia"),
|
||||
mode: u8me(null, 2),
|
||||
version: s16me("ver"),//
|
||||
pp: boolme(),
|
||||
ps: boolme(),
|
||||
pay: s16me(),
|
||||
payedPlayCount: s16me("pay_pc"),
|
||||
standardTime: u64me("st", BigInt(Date.now())),//
|
||||
intrvld: s32me(),//
|
||||
previousStartTime: u64me("pst"),//
|
||||
ea: boolme()//
|
||||
}
|
||||
|
||||
export interface IBst2Base extends ICollection<"bst.bst2.player.base"> {
|
||||
name: string
|
||||
brnk: number
|
||||
bcnum: number
|
||||
lcnum: number
|
||||
volt: number
|
||||
gold: number
|
||||
lastMusicId: number
|
||||
lastChart: number
|
||||
lastSort: number
|
||||
lastTab: number
|
||||
splv: number
|
||||
preference: number
|
||||
lcid: number
|
||||
hat: number
|
||||
}
|
||||
export const Bst2BaseMap: KM<IBst2Base> = {
|
||||
collection: colme<IBst2Base>("bst.bst2.player.base"),
|
||||
name: strme(),
|
||||
brnk: s8me(),
|
||||
bcnum: s8me(),
|
||||
lcnum: s8me(),
|
||||
volt: s32me(),
|
||||
gold: s32me(),
|
||||
lastMusicId: s32me("lmid"),
|
||||
lastChart: s8me("lgrd"),
|
||||
lastSort: s8me("lsrt"),
|
||||
lastTab: s8me("ltab"),
|
||||
splv: s8me(),
|
||||
preference: s8me("pref"),
|
||||
lcid: s32me(),
|
||||
hat: s32me()
|
||||
}
|
||||
|
||||
export interface IBst2Survey extends ICollection<"bst.bst2.player.survey"> {
|
||||
motivate: number
|
||||
}
|
||||
export const Bst2SurveyMap: KM<IBst2Survey> = {
|
||||
collection: colme<IBst2Survey>("bst.bst2.player.survey"),
|
||||
motivate: s8me()
|
||||
}
|
||||
|
||||
export interface IBst2UnlockingInfo extends ICollection<"bst.bst2.player.unlockingInfo"> {
|
||||
type: number
|
||||
id: number
|
||||
param: number
|
||||
count: number
|
||||
}
|
||||
export const Bst2UnlockingInfoMap: KM<IBst2UnlockingInfo> = {
|
||||
collection: colme<IBst2UnlockingInfo>("bst.bst2.player.unlockingInfo"),
|
||||
type: s32me(),
|
||||
id: s32me(),
|
||||
param: s32me(),
|
||||
count: s32me()
|
||||
}
|
||||
|
||||
export interface IBst2Customization extends ICollection<"bst.bst2.player.customization"> {
|
||||
// [rippleNote, rippleNoteColor, sfxNormalNote, sfxRippleNote, sfxSlashNote, sfxStreamNote, backgroundBrightnessTimes2, (000{sfxFine}{sfxStreamTail}{streamNoteGuide}{rippleNoteGuide}{judgeText}, ?, ?, ?, ?, ?, ?, ?, ?)]
|
||||
custom: FixedSizeArray<number, 16>
|
||||
}
|
||||
export const Bst2CustomizationMap: KM<IBst2Customization> = {
|
||||
collection: colme<IBst2Customization>("bst.bst2.player.customization"),
|
||||
custom: u16me(null, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
|
||||
}
|
||||
|
||||
export interface IBst2Tips extends ICollection<"bst.bst2.player.tips"> {
|
||||
lastTips: number
|
||||
}
|
||||
export const Bst2TipsMap: KM<IBst2Tips> = {
|
||||
collection: colme<IBst2Tips>("bst.bst2.player.tips"),
|
||||
lastTips: s32me("last_tips")
|
||||
}
|
||||
|
||||
export interface IBst2Hacker extends ICollection<"bst.bst2.player.hacker"> {
|
||||
id: number
|
||||
state0: number
|
||||
state1: number
|
||||
state2: number
|
||||
state3: number
|
||||
state4: number
|
||||
updateTime: bigint | BigIntProxy
|
||||
}
|
||||
export const Bst2HackerMap: KM<IBst2Hacker> = {
|
||||
collection: colme<IBst2Hacker>("bst.bst2.player.hacker"),
|
||||
id: s32me(),
|
||||
state0: s8me(),
|
||||
state1: s8me(),
|
||||
state2: s8me(),
|
||||
state3: s8me(),
|
||||
state4: s8me(),
|
||||
updateTime: u64me("update_time")
|
||||
}
|
||||
|
||||
export interface IBst2CrysisLog extends ICollection<"bst.bst2.player.event.crysis"> {
|
||||
id: number
|
||||
stageId: number
|
||||
step: number
|
||||
gauge: number
|
||||
state: number
|
||||
}
|
||||
export const Bst2CrysisLogMap: KM<IBst2CrysisLog> = {
|
||||
collection: colme<IBst2CrysisLog>("bst.bst2.player.event.crysis"),
|
||||
id: s32me(),
|
||||
stageId: s32me("stage_no"),
|
||||
step: s8me(),
|
||||
gauge: s32me("r_gauge"),
|
||||
state: s8me("r_state")
|
||||
}
|
||||
|
||||
export interface IBst2Bisco extends ICollection<"bst.bst2.player.bisco"> {
|
||||
bnum: number
|
||||
jbox: number
|
||||
}
|
||||
export const Bst2BiscoMap: KM<IBst2Bisco> = {
|
||||
collection: colme<IBst2Bisco>("bst.bst2.player.bisco"),
|
||||
bnum: s32me(),
|
||||
jbox: s32me(),
|
||||
}
|
||||
|
||||
export interface IBst2MusicRecord extends ICollection<"bst.bst2.playData.musicRecord#userId"> {
|
||||
musicId: number
|
||||
chart: number
|
||||
playCount: number
|
||||
clearCount: number
|
||||
gaugeTimes10: number
|
||||
score: number
|
||||
grade: number
|
||||
medal: number
|
||||
combo: number
|
||||
userId: number
|
||||
updateTime: number
|
||||
lastPlayTime: number
|
||||
}
|
||||
export const Bst2MusicRecordMap: KM<IBst2MusicRecord> = {
|
||||
collection: colme<IBst2MusicRecord>("bst.bst2.playData.musicRecord#userId"),
|
||||
musicId: s32me("music_id"),
|
||||
chart: s32me("note_level"),
|
||||
playCount: s32me("play_count"),
|
||||
clearCount: s32me("clear_count"),
|
||||
gaugeTimes10: s32me("best_gauge"),
|
||||
score: s32me("best_score"),
|
||||
grade: s32me("best_grade"),
|
||||
medal: s32me("best_medal"),
|
||||
combo: ignoreme(),
|
||||
userId: ignoreme(),
|
||||
updateTime: ignoreme(),
|
||||
lastPlayTime: ignoreme()
|
||||
}
|
||||
|
||||
export interface IBst2Course extends ICollection<"bst.bst2.playData.course#userId"> {
|
||||
courseId: number
|
||||
playCount: number
|
||||
isTouched: boolean
|
||||
clearType: number
|
||||
gauge: number
|
||||
score: number
|
||||
grade: number
|
||||
medal: number
|
||||
combo: number
|
||||
userId: number
|
||||
updateTime: number
|
||||
lastPlayTime: number
|
||||
}
|
||||
export const Bst2CourseMap: KM<IBst2Course> = {
|
||||
collection: colme<IBst2Course>("bst.bst2.playData.course#userId"),
|
||||
courseId: s32me("course_id"),
|
||||
playCount: s32me("play"),
|
||||
isTouched: boolme("is_touch"),
|
||||
clearType: s32me("clear"),
|
||||
gauge: s32me("gauge"),
|
||||
score: s32me(),
|
||||
grade: s32me(),
|
||||
medal: s32me(),
|
||||
combo: s32me(),
|
||||
userId: ignoreme(),
|
||||
updateTime: ignoreme(),
|
||||
lastPlayTime: ignoreme()
|
||||
}
|
||||
|
||||
export interface IBst2Player {
|
||||
pdata: {
|
||||
account: IBst2Account
|
||||
base: IBst2Base
|
||||
opened: {}
|
||||
survey: IBst2Survey
|
||||
item: { info?: IBst2UnlockingInfo[] }
|
||||
customize: IBst2Customization
|
||||
tips: IBst2Tips
|
||||
hacker: { info?: IBst2Hacker[] }
|
||||
playLog: { crysis?: IBst2CrysisLog[] }
|
||||
bisco: { pinfo: IBst2Bisco }
|
||||
record: { rec?: IBst2MusicRecord[] }
|
||||
course: { record?: IBst2Course[] }
|
||||
}
|
||||
}
|
||||
export const Bst2PlayerMap: KM<IBst2Player> = {
|
||||
pdata: {
|
||||
account: Bst2AccountMap,
|
||||
base: Bst2BaseMap,
|
||||
opened: {},
|
||||
survey: Bst2SurveyMap,
|
||||
item: { info: { 0: Bst2UnlockingInfoMap } },
|
||||
customize: Bst2CustomizationMap,
|
||||
tips: Bst2TipsMap,
|
||||
hacker: { info: { 0: Bst2HackerMap } },
|
||||
playLog: { crysis: { 0: Bst2CrysisLogMap }, $targetKey: "play_log" },
|
||||
bisco: { pinfo: Bst2BiscoMap },
|
||||
record: { rec: { 0: Bst2MusicRecordMap } },
|
||||
course: { record: { 0: Bst2CourseMap } }
|
||||
}
|
||||
}
|
||||
80
bst@asphyxia/models/bst2/stagelog.ts
Normal file
80
bst@asphyxia/models/bst2/stagelog.ts
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
import { colme, ignoreme, KM, s32me, strme } from "../../utility/mapping"
|
||||
import { ICollection } from "../utility/definitions"
|
||||
|
||||
export interface IBst2StageLog extends ICollection<"bst.bst2.playData.stageLog#userId"> {
|
||||
playerId: number
|
||||
continueCount: number
|
||||
stageId: number
|
||||
userId: number
|
||||
lobbyId: string
|
||||
musicId: number
|
||||
chart: number
|
||||
gaugeTimes10: number
|
||||
score: number
|
||||
combo: number
|
||||
grade: number
|
||||
medal: number
|
||||
fantasticCount: number
|
||||
greatCount: number
|
||||
fineCount: number
|
||||
missCount: number
|
||||
isCourseStage: boolean
|
||||
time: number
|
||||
}
|
||||
export const Bst2StageLogMap: KM<IBst2StageLog> = {
|
||||
collection: colme<IBst2StageLog>("bst.bst2.playData.stageLog#userId"),
|
||||
playerId: s32me("play_id"),
|
||||
continueCount: s32me("continue_count"),
|
||||
stageId: s32me("stage_no"),
|
||||
userId: s32me("user_id"),
|
||||
lobbyId: strme("location_id"),
|
||||
musicId: s32me("select_music_id"),
|
||||
chart: s32me("select_grade"),
|
||||
gaugeTimes10: s32me("result_clear_gauge"),
|
||||
score: s32me("result_score"),
|
||||
combo: s32me("result_max_combo"),
|
||||
grade: s32me("result_grade"),
|
||||
medal: s32me("result_medal"),
|
||||
fantasticCount: s32me("result_fanta"),
|
||||
greatCount: s32me("result_great"),
|
||||
fineCount: s32me("result_fine"),
|
||||
missCount: s32me("result_miss"),
|
||||
isCourseStage: ignoreme(),
|
||||
time: ignoreme(),
|
||||
}
|
||||
|
||||
export interface IBst2CourseLog extends ICollection<"bst.bst2.playData.courseLog#userId"> {
|
||||
playerId: number
|
||||
continueCount: number
|
||||
userId: number
|
||||
courseId: number
|
||||
gauge: number
|
||||
score: number
|
||||
grade: number
|
||||
medal: number
|
||||
combo: number
|
||||
fantasticCount: number
|
||||
greatCount: number
|
||||
fineCount: number
|
||||
missCount: number
|
||||
lobbyId: string
|
||||
time: number
|
||||
}
|
||||
export const Bst2CourseLogMap: KM<IBst2CourseLog> = {
|
||||
collection: colme<IBst2CourseLog>("bst.bst2.playData.courseLog#userId"),
|
||||
playerId: s32me("play_id"),
|
||||
continueCount: s32me("continue_count"),
|
||||
userId: s32me("user_id"),
|
||||
courseId: s32me("course_id"),
|
||||
lobbyId: strme("lid"),
|
||||
gauge: s32me(),
|
||||
score: s32me(),
|
||||
combo: s32me(),
|
||||
grade: s32me(),
|
||||
medal: s32me(),
|
||||
fantasticCount: s32me("fanta"),
|
||||
greatCount: s32me("great"),
|
||||
fineCount: s32me("fine"),
|
||||
missCount: s32me("miss"),
|
||||
time: ignoreme()
|
||||
}
|
||||
5
bst@asphyxia/models/utility/batch.ts
Normal file
5
bst@asphyxia/models/utility/batch.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import { ICollection } from "./definitions"
|
||||
|
||||
export interface IBatchResult extends ICollection<"bst.batchResult"> {
|
||||
batchId: string
|
||||
}
|
||||
3
bst@asphyxia/models/utility/definitions.d.ts
vendored
Normal file
3
bst@asphyxia/models/utility/definitions.d.ts
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export interface ICollection<TCollectionName extends string> {
|
||||
collection: TCollectionName
|
||||
}
|
||||
5
bst@asphyxia/models/utility/plugin_version.ts
Normal file
5
bst@asphyxia/models/utility/plugin_version.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import { ICollection } from "./definitions"
|
||||
|
||||
export interface IPluginVersion<TMajor extends number = number, TMinor extends number = number, TRevision extends number = number> extends ICollection<"bst.pluginVersion"> {
|
||||
version: string
|
||||
}
|
||||
14
bst@asphyxia/models/utility/webui_message.ts
Normal file
14
bst@asphyxia/models/utility/webui_message.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import { ICollection } from "./definitions"
|
||||
|
||||
export interface IWebUIMessage extends ICollection<"utility.webuiMessage"> {
|
||||
message: string
|
||||
type: WebUIMessageType
|
||||
refid?: string
|
||||
version: number
|
||||
}
|
||||
|
||||
export enum WebUIMessageType {
|
||||
info = 0,
|
||||
success = 1,
|
||||
error = 2
|
||||
}
|
||||
4
bst@asphyxia/utility/about.ts
Normal file
4
bst@asphyxia/utility/about.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
export type Game = "bst"
|
||||
export const game: Game = "bst"
|
||||
export type PluginVersion = "1.0.0"
|
||||
export const version: PluginVersion = "1.0.0"
|
||||
480
bst@asphyxia/utility/mapping.ts
Normal file
480
bst@asphyxia/utility/mapping.ts
Normal file
|
|
@ -0,0 +1,480 @@
|
|||
import { ICollection } from "../models/utility/definitions"
|
||||
|
||||
export type KArrayType = KNumberType | KBigIntType
|
||||
export type KGroupType = KNumberGroupType | KBigIntGroupType
|
||||
export type KType = KArrayType | KGroupType | "str" | "bin" | "ip4" | "bool"
|
||||
export type KTypeExtended = KType | null | "kignore"
|
||||
export type TypeForKItem = number | string | bigint | BigIntProxy | boolean | Buffer | number[] | bigint[] | boolean[] | BufferArray | NumberGroup<number[] | bigint[]>
|
||||
export type TypeForKObject<T> = T extends TypeForKItem ? never : T
|
||||
export type TypeForKArray = number[] | bigint[] | BufferArray
|
||||
|
||||
export type KKey<T> = keyof T & (
|
||||
T extends string ? Exclude<keyof T, keyof string> :
|
||||
T extends Buffer ? Exclude<keyof T, keyof Buffer> :
|
||||
T extends boolean ? Exclude<keyof T, keyof boolean> :
|
||||
T extends number[] | bigint[] | boolean[] ? Exclude<keyof T, (keyof number[]) | (keyof bigint[]) | (keyof boolean[])> :
|
||||
T extends any[] ? Exclude<keyof T, keyof any[]> | number :
|
||||
T extends number ? Exclude<keyof T, keyof number> :
|
||||
T extends bigint | BigIntProxy ? Exclude<keyof T, keyof bigint> :
|
||||
T extends BufferArray ? Exclude<keyof T, keyof BufferArray> :
|
||||
T extends NumberGroup<infer TGroup> ? Exclude<keyof T, keyof NumberGroup<TGroup>> :
|
||||
keyof T)
|
||||
|
||||
export type KTypeConvert<T extends string | Buffer | number | bigint | boolean | number[] | bigint[] | unknown> =
|
||||
T extends string ? "str" :
|
||||
T extends Buffer ? "bin" :
|
||||
T extends number ? KNumberType | "ip4" | "bool" :
|
||||
T extends bigint | BigIntProxy ? KBigIntType :
|
||||
T extends boolean | boolean[] ? "bool" :
|
||||
T extends number[] ? KNumberType : // KARRAY
|
||||
T extends bigint[] ? KBigIntType : // KARRAY
|
||||
T extends NumberGroup<number[]> ? KNumberGroupType :
|
||||
T extends NumberGroup<bigint[]> ? KBigIntGroupType :
|
||||
T extends BufferArray ? "u8" | "s8" :
|
||||
never
|
||||
|
||||
export type KArrayTypeConvert<T extends Buffer | number[] | bigint[] | unknown> =
|
||||
T extends Buffer ? "s8" | "u8" :
|
||||
T extends number[] ? KNumberType :
|
||||
T extends bigint[] ? KBigIntType :
|
||||
never
|
||||
|
||||
export type KTypeConvertBack<TKType extends KTypeExtended> =
|
||||
TKType extends "str" ? string :
|
||||
TKType extends "bin" ? { type: "Buffer"; data: number[] } :
|
||||
TKType extends "s8" | "u8" ? [number] | number[] | { type: "Buffer"; data: number[] } :
|
||||
TKType extends KNumberType ? [number] | number[] :
|
||||
TKType extends KBigIntType ? [bigint] | bigint[] :
|
||||
TKType extends KNumberGroupType ? number[] :
|
||||
TKType extends KBigIntGroupType ? bigint[] :
|
||||
unknown
|
||||
|
||||
export type NumberGroup<T extends number[] | bigint[] = number[]> = {
|
||||
"@numberGroupValue": T
|
||||
}
|
||||
export const NumberGroup = <T extends number[] | bigint[] = number[]>(ng: T) => <NumberGroup>{ "@numberGroupValue": ng }
|
||||
export function isNumberGroup(value: any): value is NumberGroup {
|
||||
try {
|
||||
return Array.isArray(BigInt(value["@numberGroupValue"]))
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
export type BufferArray = {
|
||||
"@bufferArrayValue": Buffer
|
||||
}
|
||||
export const BufferArray = (ba: Buffer) => <BufferArray>{ "@bufferArrayValue": ba }
|
||||
export function isBufferArray(value: any): value is BufferArray {
|
||||
try {
|
||||
return value["@bufferArrayValue"] instanceof Buffer
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
export type BigIntProxy = {
|
||||
"@serializedBigInt": string
|
||||
}
|
||||
export const BigIntProxy = (value: bigint) => <BigIntProxy>{ "@serializedBigInt": value.toString() }
|
||||
export function isBigIntProxy(value: any): value is BigIntProxy {
|
||||
try {
|
||||
return BigInt(value["@serializedBigInt"]).toString() == value["@serializedBigInt"]
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
export function toBigInt(value: bigint | BigIntProxy): bigint {
|
||||
if (value == null) return null
|
||||
if (value instanceof BigInt) return <bigint>value
|
||||
else if (value["@serializedBigInt"] != null) return BigInt(value["@serializedBigInt"])
|
||||
else return BigInt(0)
|
||||
}
|
||||
|
||||
export type KITEM2<T> = { [K in keyof T]?: K extends KKey<T> ? KITEM2<T[K]> : never } &
|
||||
{
|
||||
["@attr"]: KAttrMap2<T>
|
||||
["@content"]:
|
||||
T extends string | Buffer | boolean | number[] | bigint[] ? T :
|
||||
T extends number | bigint ? [T] :
|
||||
T extends BufferArray ? Buffer :
|
||||
T extends NumberGroup<infer TGroup> ? TGroup :
|
||||
T extends BigIntProxy ? [bigint] : never
|
||||
}
|
||||
|
||||
export type KAttrMap2<T> = { [key: string]: string } & {
|
||||
__type?: T extends TypeForKItem ? KTypeConvert<T> : never
|
||||
__count?: T extends TypeForKArray ? number : never
|
||||
}
|
||||
|
||||
export function ITEM2<T>(ktype: KTypeConvert<T>, value: T, attr?: KAttrMap2<T>): KITEM2<T> {
|
||||
// let result
|
||||
// if (value instanceof NumberGroup && IsNumberGroupKType(ktype)) {
|
||||
// result = K.ITEM(<KTypeConvert<T & NumberGroup>>ktype, value.value, attr)
|
||||
// } else if (Array.isArray(value) && IsNumericKType(ktype)) {
|
||||
// result = K.ARRAY(<KTypeConvert<T & number[]>>ktype, <any>value, <any>attr)
|
||||
// } else if (value instanceof BufferArray && IsNumericKType(ktype)) {
|
||||
// result = K.ARRAY(<KTypeConvert<T & BufferArray>>ktype, value.value, attr)
|
||||
// } else if (typeof value != "object" && typeof value != "function") {
|
||||
// result = K.ITEM(<any>ktype, <any>value, attr)
|
||||
// } else {
|
||||
// Object.assign(result, value, { ["@attr"]: attr })
|
||||
// result["@attr"].__type = ktype
|
||||
// }
|
||||
|
||||
// return <KITEM2<T>>result
|
||||
let result = <KITEM2<T>>{}
|
||||
result["@attr"] = Object.assign({}, attr, (!isNumberGroupKType(ktype) && isNumericKType(ktype) && Array.isArray(value)) ? { __type: ktype, __count: (<any[]>value).length } : { __type: ktype })
|
||||
|
||||
if ((ktype == "bool") && (typeof value == "boolean")) {
|
||||
result["@content"] = <any>(value ? [1] : [0])
|
||||
} else if ((ktype == "bin") && value instanceof Buffer) {
|
||||
result = <any>K.ITEM("bin", value, result["@attr"])
|
||||
} else if (((ktype == "s8") || (ktype == "u8")) && isBufferArray(value)) {
|
||||
result["@content"] = <any>value["@bufferArrayValue"].toJSON()
|
||||
result["@attr"].__count = <any>value["@bufferArrayValue"].byteLength
|
||||
} else if (isNumericKType(ktype) && !Array.isArray(value)) {
|
||||
result["@content"] = <any>[value]
|
||||
} else if (isNumberGroupKType(ktype) && isNumberGroup(value)) {
|
||||
result["@content"] = <any>value["@numberGroupValue"]
|
||||
} else if (isBigIntProxy(value)) {
|
||||
result["@content"] = <any>BigInt(value["@serializedBigInt"])
|
||||
}
|
||||
else {
|
||||
result["@content"] = <any>value
|
||||
}
|
||||
if (isKIntType(ktype) && Array.isArray(result["@content"])) for (let i = 0; i < result["@content"].length; i++) (<number[]>result["@content"])[i] = Math.trunc(result["@content"][i])
|
||||
return result
|
||||
}
|
||||
|
||||
export type KObjectMappingRecord<T> = { [K in KKey<T>]: T[K] extends TypeForKItem ? KObjectMappingElementInfer<T[K]> : KObjectMappingRecord<T[K]> } & KObjectMappingElementInfer<T>
|
||||
export interface KObjectMappingElement<T = any, TKType extends KTypeExtended = KTypeExtended> {
|
||||
$type?: TKType,
|
||||
$targetKey?: string,
|
||||
$convert?: (source: T) => T
|
||||
$convertBack?: (target: T) => T
|
||||
$fallbackValue?: TKType extends "kignore" ? T : never
|
||||
$defaultValue?: T
|
||||
}
|
||||
type KObjectMappingElementInfer<T> = KObjectMappingElement<T, (KTypeConvert<T> extends KType ? KTypeConvert<T> : never) | never | "kignore">
|
||||
|
||||
export type KAttrRecord<T> = { [K in keyof T]?: T extends TypeForKItem ? KAttrMap2<T[K]> : KAttrRecord<T[K]> } & { selfAttr?: KAttrMap2<T> }
|
||||
|
||||
export function getCollectionMappingElement<TCollection extends ICollection<any>>(collectionName: TCollection extends ICollection<infer TName> ? TName : never): KObjectMappingElement<TCollection extends ICollection<infer TName> ? TName : unknown, "kignore"> {
|
||||
return ignoreme("collection", collectionName)
|
||||
}
|
||||
|
||||
function isKType<TType>(type: TType): boolean {
|
||||
return (typeof (type) == "string") && ["s8", "u8", "s16", "u16", "s32", "u32", "time", "ip4", "float", "double", "bool", "s64", "u64", "2s8", "2u8", "2s16", "2u16", "2s32", "2u32", "2f", "2d", "3s8", "3u8", "3s16", "3u16", "3s32", "3u32", "3f", "3d", "4s8", "4u8", "4s16", "4u16", "4s32", "4u32", "4f", "4d", "2b", "3b", "4b", "vb", "2s64", "2u64", "3s64", "3u64", "4s64", "4u64", "vs8", "vu8", "vs16", "vu16", "str", "bin"].includes(type)
|
||||
}
|
||||
function isKIntType<TType>(type: TType): boolean {
|
||||
return (typeof (type) == "string") && ["s8", "u8", "s16", "u16", "s32", "u32", "2s8", "2u8", "2s16", "2u16", "2s32", "2u32", "3s8", "3u8", "3s16", "3u16", "3s32", "3u32", "4s8", "4u8", "4s16", "4u16", "4s32", "4u32", "2b", "3b", "4b", "vb", "vs8", "vu8", "vs16", "vu16"].includes(type)
|
||||
}
|
||||
function isKBigIntType<TType>(type: TType): boolean {
|
||||
return (typeof (type) == "string") && ["s64", "u64"].includes(type)
|
||||
}
|
||||
function isNumericKType<TType>(type: TType): boolean {
|
||||
return (typeof (type) == "string") && ["s8", "u8", "s16", "u16", "s32", "u32", "time", "ip4", "float", "double", "bool", "s64", "u64"].includes(type)
|
||||
}
|
||||
function isNumberGroupKType<TType>(type: TType): boolean {
|
||||
return (typeof (type) == "string") && ["2s8", "2u8", "2s16", "2u16", "2s32", "2u32", "2f", "2d", "3s8", "3u8", "3s16", "3u16", "3s32", "3u32", "3f", "3d", "4s8", "4u8", "4s16", "4u16", "4s32", "4u32", "4f", "4d", "2b", "3b", "4b", "vb", "2s64", "2u64", "3s64", "3u64", "4s64", "4u64", "vs8", "vu8", "vs16", "vu16"].includes(type)
|
||||
}
|
||||
function isNumericKey(k: any): boolean {
|
||||
return (typeof k == "number") || (parseInt(k).toString() == k)
|
||||
}
|
||||
function increaseNumericKey<T>(k: T, step: number = 1): T {
|
||||
return (typeof k == "number") ? <T><unknown>(k + step) : (typeof k == "string" && parseInt(k).toString() == k) ? <T><unknown>(parseInt(k) + step) : k
|
||||
}
|
||||
function isEmptyKObject(o: object): boolean {
|
||||
return (Object.keys(o).length == 0) || ((Object.keys(o).length == 1) && (o["@attr"] != null))
|
||||
}
|
||||
function isKMapRecordReservedKey(key: string): boolean {
|
||||
return ["$type", "$targetKey", "$convert", "$convertBack", "$fallbackValue", "$defaultValue"].includes(key)
|
||||
}
|
||||
function isKArray<T>(data: KITEM2<T>): boolean {
|
||||
return (data["@attr"] != null) && (data["@attr"].__count != null)
|
||||
}
|
||||
|
||||
export function appendMappingElement<T>(map: KObjectMappingRecord<T>, element: KObjectMappingElementInfer<T>): KObjectMappingRecord<T> {
|
||||
let result = <KObjectMappingRecord<T>>{}
|
||||
Object.assign(result, map, element)
|
||||
return result
|
||||
}
|
||||
|
||||
export function mapKObject<T>(data: T, kMapRecord: KObjectMappingRecord<T>, kAttrRecord: KAttrRecord<T> = <KAttrRecord<T>>{}): KITEM2<T> {
|
||||
if (data == null) return <KITEM2<T>>{}
|
||||
let result: KITEM2<T> = <any>(((0 in data) && data instanceof Object) ? [] : {})
|
||||
if (kAttrRecord.selfAttr != null) result["@attr"] = kAttrRecord.selfAttr
|
||||
|
||||
if (data instanceof Object) {
|
||||
for (let __k in data) {
|
||||
let k: keyof T = __k
|
||||
let mapK: keyof T = __k
|
||||
let attrK: keyof T = __k
|
||||
if (!(k in kMapRecord) && isNumericKey(k)) {
|
||||
for (let i = parseInt(<string>k) - 1; i >= 0; i--) if (kMapRecord[i]) {
|
||||
mapK = <keyof T>i
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!(k in kAttrRecord) && isNumericKey(k)) {
|
||||
for (let i = parseInt(<string>k) - 1; i >= 0; i--) if (kAttrRecord[i]) {
|
||||
attrK = <keyof T>i
|
||||
break
|
||||
}
|
||||
}
|
||||
if (mapK in kMapRecord) {
|
||||
let target = <KITEM2<T>[keyof T]>{}
|
||||
let targetMap = kMapRecord[<KKey<T>>mapK]
|
||||
let targetKey: keyof T = (targetMap.$targetKey != null) ? <keyof T>targetMap.$targetKey : k
|
||||
let targetValue = (targetMap.$convert != null) ? <KTypeConvertBack<KTypeConvert<T[keyof T]>>>targetMap.$convert(<any>data[k]) : data[k]
|
||||
let targetAttr = kAttrRecord[attrK]
|
||||
if (targetMap.$type) {
|
||||
let tt = targetMap.$type
|
||||
if (tt == "kignore") continue
|
||||
target["@attr"] = <any>Object.assign({}, targetAttr, (!isNumberGroupKType(tt) && isNumericKType(tt) && Array.isArray(data[k]) && Array.isArray(targetValue)) ? { __type: tt, __count: (<any[]>targetValue).length } : { __type: tt })
|
||||
|
||||
if ((tt == "bool") && (typeof targetValue == "boolean")) {
|
||||
target["@content"] = <any>(targetValue ? [1] : [0])
|
||||
} else if ((tt == "bin") && targetValue instanceof Buffer) {
|
||||
target = <any>K.ITEM("bin", targetValue, target["@attr"])
|
||||
} else if (((tt == "s8") || (tt == "u8")) && isBufferArray(targetValue)) {
|
||||
target["@content"] = <any>targetValue["@bufferArrayValue"]
|
||||
} else if (isNumericKType(tt) && !Array.isArray(targetValue)) {
|
||||
target["@content"] = <any>[targetValue]
|
||||
} else if (isNumberGroupKType(tt) && isNumberGroup(targetValue)) {
|
||||
target["@content"] = <any>targetValue["@numberGroupValue"]
|
||||
} else if (isBufferArray(targetValue)) {
|
||||
target["@content"] = <any>targetValue["@bufferArrayValue"].toJSON()
|
||||
target["@attr"].__count = <any>targetValue["@bufferArrayValue"].byteLength
|
||||
} else if (isBigIntProxy(targetValue)) {
|
||||
target["@content"] = <any>BigInt(targetValue["@serializedBigInt"])
|
||||
} else {
|
||||
target["@content"] = <any>targetValue
|
||||
}
|
||||
if (isKIntType(tt) && Array.isArray(target["@content"])) for (let i = 0; i < target["@content"].length; i++) (<number[]>target["@content"])[i] = Math.trunc(target["@content"][i])
|
||||
} else {
|
||||
target = <any>mapKObject(<T[keyof T]>targetValue, <KObjectMappingRecord<T[keyof T]>><unknown>targetMap, <KAttrRecord<T[keyof T]>>targetAttr)
|
||||
}
|
||||
result[targetKey] = target
|
||||
}
|
||||
}
|
||||
} else result = ITEM2<T>(<KTypeConvert<T>>kAttrRecord.selfAttr.$type, data, kAttrRecord.selfAttr)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
export type MapBackResult<T> = {
|
||||
data: T,
|
||||
attr?: KAttrRecord<T>
|
||||
}
|
||||
export function mapBackKObject<T extends object>(data: KITEM2<T>, kMapRecord?: KObjectMappingRecord<T>): MapBackResult<T> {
|
||||
if (kMapRecord == null) {
|
||||
if (data["@content"] || data["@attr"]) return { data: <any>data["@content"], attr: <any>data["@attr"] }
|
||||
else return { data: <T>data }
|
||||
}
|
||||
let result: T = <T>((Array.isArray(data) || 0 in kMapRecord) ? [] : {})
|
||||
let resultAttr: KAttrRecord<T> = <any>{ selfAttr: data["@attr"] ? data["@attr"] : null }
|
||||
|
||||
for (let __k in kMapRecord) {
|
||||
if (isKMapRecordReservedKey(__k)) continue
|
||||
let k = <keyof T>__k
|
||||
let preservK = <keyof T>__k
|
||||
do {
|
||||
let targetMap = kMapRecord[<KKey<T>>preservK]
|
||||
let targetKey = <keyof T>(targetMap.$targetKey ? targetMap.$targetKey : k)
|
||||
let doOnceFlag = (isNumericKey(targetKey) && (data[targetKey] == null) && !isEmptyKObject(data))
|
||||
let targetValue = <KITEM2<T>[keyof T]>(doOnceFlag ? data : data[targetKey])
|
||||
|
||||
if (targetMap.$type == "kignore") {
|
||||
result[k] = targetMap.$fallbackValue
|
||||
if ((targetValue != null) && (targetValue["@attr"] != null)) resultAttr[k] = <KAttrRecord<T>[keyof T]>{ selfAttr: targetValue["@attr"] }
|
||||
continue
|
||||
}
|
||||
|
||||
if (targetValue == null) {
|
||||
if (targetMap.$convertBack != null) result[k] = targetMap.$convertBack(<any>null)
|
||||
continue
|
||||
}
|
||||
|
||||
if (targetValue["@attr"] != null) {
|
||||
let targetAttr: KAttrMap2<T[keyof T]> = targetValue["@attr"]
|
||||
let targetResult
|
||||
|
||||
if (targetAttr.__type != null) { // KITEM
|
||||
targetResult = targetValue["@content"]
|
||||
if (isNumberGroupKType(targetAttr.__type)) { // KITEM2<NumberGroup>
|
||||
// TODO: bigint number group
|
||||
targetResult = NumberGroup(targetResult)
|
||||
} else if (targetAttr.__type == "bin") { // KITEM<"bin">
|
||||
targetResult = targetResult
|
||||
} else if ((targetAttr.__type == "s8" || targetAttr.__type == "u8") && (targetResult?.type == "Buffer") && Array.isArray(targetResult?.data)) { // KITEM2<BufferArray>
|
||||
targetResult = BufferArray(Buffer.from(<number[]>targetResult.data))
|
||||
} else if (targetAttr.__type == "bool") { // KITEM<"bool">
|
||||
targetResult = targetResult[0] == 1 ? true : false
|
||||
} else if (Array.isArray(targetResult) && (targetAttr.__count == null) && isNumericKType(targetAttr.__type)) { // KITEM<KNumberType>
|
||||
targetResult = ((targetAttr.__type == "s64") || (targetAttr.__type == "u64")) ? BigIntProxy(BigInt(targetResult[0])) : targetResult[0]
|
||||
}
|
||||
result[k] = (targetMap.$convertBack != null) ? targetMap.$convertBack(<any>targetResult) : targetResult
|
||||
} else { // KObject
|
||||
targetResult = (targetMap.$convertBack != null) ? targetMap.$convertBack(<any>targetValue) : targetValue;
|
||||
let partial = mapBackKObject<T[keyof T] & object>(targetResult, <any>targetMap)
|
||||
result[k] = partial.data
|
||||
resultAttr[k] = <any>partial.attr
|
||||
}
|
||||
} else { // KObject
|
||||
let targetResult = (targetMap.$convertBack != null) ? targetMap.$convertBack(<any>targetValue) : targetValue;
|
||||
let partial = <any>mapBackKObject<T[keyof T] & object>(<any>targetResult, <any>targetMap)
|
||||
result[k] = partial.data
|
||||
resultAttr[k] = <any>partial.attr
|
||||
}
|
||||
k = increaseNumericKey(k)
|
||||
if (doOnceFlag || (isNumericKey(k) && (data[<keyof T>(targetMap.$targetKey ? targetMap.$targetKey : k)] == null))) break
|
||||
} while (isNumericKey(k) && !(k in kMapRecord))
|
||||
}
|
||||
return { data: result, attr: resultAttr }
|
||||
}
|
||||
|
||||
export function s8me<T extends number | number[]>(targetKey?: string, defaultValue?: T, convert?: (source: T) => T, convertBack?: (target: T) => T): KObjectMappingElement<T, "s8"> {
|
||||
return {
|
||||
$type: "s8",
|
||||
$targetKey: targetKey,
|
||||
$convert: convert,
|
||||
$convertBack: convertBack,
|
||||
$defaultValue: defaultValue
|
||||
}
|
||||
}
|
||||
export function u8me<T extends number | number[]>(targetKey?: string, defaultValue?: T, convert?: (source: T) => T, convertBack?: (target: T) => T): KObjectMappingElement<T, "u8"> {
|
||||
return {
|
||||
$type: "u8",
|
||||
$targetKey: targetKey,
|
||||
$convert: convert,
|
||||
$convertBack: convertBack,
|
||||
$defaultValue: defaultValue
|
||||
}
|
||||
}
|
||||
export function s16me<T extends number | number[]>(targetKey?: string, defaultValue?: T, convert?: (source: T) => T, convertBack?: (target: T) => T): KObjectMappingElement<T, "s16"> {
|
||||
return {
|
||||
$type: "s16",
|
||||
$targetKey: targetKey,
|
||||
$convert: convert,
|
||||
$convertBack: convertBack,
|
||||
$defaultValue: defaultValue
|
||||
}
|
||||
}
|
||||
export function u16me<T extends number | number[]>(targetKey?: string, defaultValue?: T, convert?: (source: T) => T, convertBack?: (target: T) => T): KObjectMappingElement<T, "u16"> {
|
||||
return {
|
||||
$type: "u16",
|
||||
$targetKey: targetKey,
|
||||
$convert: convert,
|
||||
$convertBack: convertBack,
|
||||
$defaultValue: defaultValue
|
||||
}
|
||||
}
|
||||
export function s32me<T extends number | number[]>(targetKey?: string, defaultValue?: T, convert?: (source: T) => T, convertBack?: (target: T) => T): KObjectMappingElement<T, "s32"> {
|
||||
return {
|
||||
$type: "s32",
|
||||
$targetKey: targetKey,
|
||||
$convert: convert,
|
||||
$convertBack: convertBack,
|
||||
$defaultValue: defaultValue
|
||||
}
|
||||
}
|
||||
export function u32me<T extends number | number[]>(targetKey?: string, defaultValue?: T, convert?: (source: T) => T, convertBack?: (target: T) => T): KObjectMappingElement<T, "u32"> {
|
||||
return {
|
||||
$type: "u32",
|
||||
$targetKey: targetKey,
|
||||
$convert: convert,
|
||||
$convertBack: convertBack,
|
||||
$defaultValue: defaultValue
|
||||
}
|
||||
}
|
||||
export function s64me(targetKey?: string, defaultValue?: bigint | BigIntProxy, convert?: (source: bigint | BigIntProxy) => bigint | BigIntProxy, convertBack?: (target: bigint | BigIntProxy) => bigint | BigIntProxy): KObjectMappingElement<bigint | BigIntProxy, "s64"> {
|
||||
return {
|
||||
$type: "s64",
|
||||
$targetKey: targetKey,
|
||||
$convert: convert,
|
||||
$convertBack: convertBack,
|
||||
$defaultValue: defaultValue
|
||||
}
|
||||
}
|
||||
export function u64me(targetKey?: string, defaultValue?: bigint | BigIntProxy, convert?: (source: bigint | BigIntProxy) => bigint | BigIntProxy, convertBack?: (target: bigint | BigIntProxy) => bigint | BigIntProxy): KObjectMappingElement<bigint | BigIntProxy, "u64"> {
|
||||
return {
|
||||
$type: "u64",
|
||||
$targetKey: targetKey,
|
||||
$convert: convert,
|
||||
$convertBack: convertBack,
|
||||
$defaultValue: defaultValue
|
||||
}
|
||||
}
|
||||
|
||||
export function boolme<T extends boolean | boolean[]>(targetKey?: string, defaultValue?: T, convert?: (source: T) => T, convertBack?: (target: T) => T): KObjectMappingElement<T, "bool"> {
|
||||
return {
|
||||
$type: "bool",
|
||||
$targetKey: targetKey,
|
||||
$convert: convert,
|
||||
$convertBack: convertBack,
|
||||
$defaultValue: defaultValue
|
||||
}
|
||||
}
|
||||
export function strme<TName extends string>(targetKey?: string, defaultValue?: TName, convert?: (source: TName) => TName, convertBack?: (target: TName) => TName): KObjectMappingElement<TName, "str"> {
|
||||
return {
|
||||
$type: "str",
|
||||
$targetKey: targetKey,
|
||||
$convert: convert,
|
||||
$convertBack: convertBack,
|
||||
$defaultValue: defaultValue
|
||||
}
|
||||
}
|
||||
|
||||
export function binme(targetKey?: string, defaultValue?: Buffer, convert?: (source: Buffer) => Buffer, convertBack?: (target: Buffer) => Buffer): KObjectMappingElement<Buffer, "bin"> {
|
||||
return {
|
||||
$type: "bin",
|
||||
$targetKey: targetKey,
|
||||
$convert: convert,
|
||||
$convertBack: convertBack,
|
||||
$defaultValue: defaultValue
|
||||
}
|
||||
}
|
||||
|
||||
export function ignoreme<T = any>(targetKey?: string, fallbackValue?: T): KObjectMappingElement<T, "kignore"> {
|
||||
return {
|
||||
$type: "kignore",
|
||||
$fallbackValue: fallbackValue
|
||||
}
|
||||
}
|
||||
export function me<T extends object>(targetKey?: string, defaultValue?: T, convert?: (source: T) => T, convertBack?: (target: T) => T): KObjectMappingElement<T, null> {
|
||||
return {
|
||||
$targetKey: targetKey,
|
||||
$convert: convert,
|
||||
$convertBack: convertBack,
|
||||
$defaultValue: defaultValue
|
||||
}
|
||||
}
|
||||
export const colme = getCollectionMappingElement
|
||||
export const appendme = appendMappingElement
|
||||
export const mapK = mapKObject
|
||||
export const bacK = mapBackKObject
|
||||
|
||||
export function fromMap<T>(map: KObjectMappingRecord<T>): T {
|
||||
let result = <T>{}
|
||||
if (map.$type == "kignore") return map.$fallbackValue
|
||||
if (map.$defaultValue != null) return map.$defaultValue
|
||||
if (map.$type != null) {
|
||||
if (isNumericKType(map.$type)) {
|
||||
if (map.$type == "bool") return <any>false
|
||||
else return <any>0
|
||||
} else if (isKBigIntType(map.$type)) return <any>BigInt(0)
|
||||
else if (isNumberGroupKType(map.$type)) return <any>NumberGroup([0])
|
||||
else if (map.$type == "str") return <any>""
|
||||
|
||||
else return null
|
||||
}
|
||||
for (let k in map) {
|
||||
if (isKMapRecordReservedKey(k)) continue
|
||||
let value = fromMap(map[k])
|
||||
if (value != null) result[k] = value
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
export type KM<T> = KObjectMappingRecord<T>
|
||||
4
bst@asphyxia/utility/type.ts
Normal file
4
bst@asphyxia/utility/type.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
export type FixedSizeArray<T, TSize extends number> = [T, ...T[]] & { readonly length: TSize }
|
||||
export function fillArray<T, TSize extends number>(size: TSize, fillValue: T): FixedSizeArray<T, TSize> {
|
||||
return <any>Array(size).fill(fillValue)
|
||||
}
|
||||
67
bst@asphyxia/utility/utility_functions.ts
Normal file
67
bst@asphyxia/utility/utility_functions.ts
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
export function toFullWidth(s: string): string {
|
||||
let resultCharCodes: number[] = []
|
||||
for (let i = 0; i < s.length; i++) {
|
||||
let cc = s.charCodeAt(i)
|
||||
if ((cc >= 33) && (cc <= 126)) resultCharCodes.push(cc + 65281 - 33)
|
||||
else if (cc == 32) resultCharCodes.push(12288) // Full-width space
|
||||
else resultCharCodes.push(cc)
|
||||
}
|
||||
return String.fromCharCode(...resultCharCodes)
|
||||
}
|
||||
export function toHalfWidth(s: string): string {
|
||||
let resultCharCodes: number[] = []
|
||||
for (let i = 0; i < s.length; i++) {
|
||||
let cc = s.charCodeAt(i)
|
||||
if ((cc >= 65281) && (cc <= 65374)) resultCharCodes.push(cc - 65281 + 33)
|
||||
else if (cc == 12288) resultCharCodes.push(32) // Full-width space
|
||||
else resultCharCodes.push(cc)
|
||||
}
|
||||
return String.fromCharCode(...resultCharCodes)
|
||||
}
|
||||
export function isToday(st: bigint): boolean {
|
||||
let now = new Date()
|
||||
let today = new Date(now.getFullYear(), now.getMonth(), now.getDate())
|
||||
let tomorrow = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1)
|
||||
return (st >= (today.valueOf())) && (st < (tomorrow.valueOf()))
|
||||
}
|
||||
export async function log(data: any, file?: string) {
|
||||
if (file == null) file = "./log.txt"
|
||||
let s = IO.Exists(file) ? await IO.ReadFile(file, "") : ""
|
||||
if (typeof data == "string") s += data + "\n"
|
||||
else {
|
||||
let n = ""
|
||||
try {
|
||||
n = JSON.stringify(data)
|
||||
} catch { }
|
||||
s += n + "\n"
|
||||
}
|
||||
await IO.WriteFile(file, s)
|
||||
}
|
||||
export function base64ToBuffer(str: string, size?: number): Buffer {
|
||||
if (size != null) {
|
||||
let rem = size - Math.trunc(size / 3) * 3
|
||||
str = str.replace("=", "A").replace("=", "A").padEnd(Math.trunc(size / 3) * 4 + rem + 1, "A")
|
||||
if (rem == 1) str += "=="
|
||||
else if (rem == 2) str += "="
|
||||
let result = Buffer.alloc(size, str, "base64")
|
||||
return result
|
||||
}
|
||||
else return Buffer.from(str, "base64")
|
||||
}
|
||||
export function bufferToBase64(buffer: Buffer, isTrimZero: boolean = true): string {
|
||||
if (isTrimZero) for (let i = buffer.length - 1; i >= 0; i--) if (buffer.readInt8(i) != 0) return buffer.toString("base64", 0, i + 1)
|
||||
return buffer.toString("base64")
|
||||
}
|
||||
export function isHigherVersion(left: string, right: string): boolean {
|
||||
let splitedLeft = left.split(".")
|
||||
let splitedRight = right.split(".")
|
||||
|
||||
if (parseInt(splitedLeft[0]) < parseInt(splitedRight[0])) return true
|
||||
else if (parseInt(splitedLeft[0]) == parseInt(splitedRight[0])) {
|
||||
if (parseInt(splitedLeft[1]) < parseInt(splitedRight[1])) return true
|
||||
else if (parseInt(splitedLeft[1]) == parseInt(splitedRight[1])) {
|
||||
if (parseInt(splitedLeft[2]) < parseInt(splitedRight[2])) return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
243
bst@asphyxia/webui/css/webui_util.css
Normal file
243
bst@asphyxia/webui/css/webui_util.css
Normal file
|
|
@ -0,0 +1,243 @@
|
|||
#tab-content, .tab-content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#tab-content.is-active, .tab-content.is-active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
tr#tab-content.is-active, tr.tab-content.is-active {
|
||||
display: table-row;
|
||||
}
|
||||
|
||||
#tabs li.disabled a {
|
||||
background-color: #c0c0c0;
|
||||
border-color: #c0c0c0;
|
||||
color: #7f7f7f;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
#form-pagination ul.pagination-list {
|
||||
margin: 0!important;
|
||||
}
|
||||
.pagination-link, .pagination-next, .pagination-previous {
|
||||
border-color: transparent;
|
||||
transition: .2s linear;
|
||||
}
|
||||
.pagination-next, .pagination-previous {
|
||||
color: #209CEE;
|
||||
}
|
||||
.pagination-next:not([disabled]):hover, .pagination-previous:not([disabled]):hover {
|
||||
color: #118fe4;
|
||||
}
|
||||
/* Set all link color to Asphyxia CORE blue */
|
||||
::selection {
|
||||
color: white;
|
||||
background-color: #209CEE;
|
||||
}
|
||||
a {
|
||||
color: #209CEE;
|
||||
}
|
||||
.tabs.is-toggle li.is-active a {
|
||||
background-color: #209CEE;
|
||||
border-color: #209CEE;
|
||||
}
|
||||
.tabs li.is-active a {
|
||||
color: #209CEE;
|
||||
border-color: #209CEE;
|
||||
}
|
||||
.pagination-link.is-current {
|
||||
background-color: #209CEE;
|
||||
border-color: #209CEE;
|
||||
cursor: default;
|
||||
}
|
||||
.select:not(.is-multiple):not(.is-loading):after {
|
||||
border-color: #209CEE;
|
||||
}
|
||||
.select select:active, .select select:focus {
|
||||
border-color: #209CEE;
|
||||
}
|
||||
.button.is-link {
|
||||
background-color: #209CEE;
|
||||
}
|
||||
.button.is-link.is-active, .button.is-link:active, .button.is-link.is-hovered, .button.is-link:hover {
|
||||
background-color: #118fe4;
|
||||
}
|
||||
.input:active, .input:focus {
|
||||
border-color: #209CEE;
|
||||
}
|
||||
.table tr.is-selected {
|
||||
background-color: #209CEE;
|
||||
}
|
||||
|
||||
#card-content.is-hidden {
|
||||
display: none;
|
||||
}
|
||||
#card-content {
|
||||
display: block;
|
||||
}
|
||||
.marquee-label {
|
||||
display: inline-block;
|
||||
}
|
||||
.marquee-label-container {
|
||||
overflow-x: hidden;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
/* from Bulma */
|
||||
.button.is-danger.is-light {
|
||||
background-color: #feecf0;
|
||||
color: #cc0f35;
|
||||
}
|
||||
.button.is-link.is-light {
|
||||
background-color: #edf8ff;
|
||||
color: #209CEE;
|
||||
}
|
||||
.button.is-danger.is-light.is-hovered, .button.is-danger.is-light:hover {
|
||||
background-color: #fde0e6;
|
||||
color: #cc0f35;
|
||||
}
|
||||
.button.is-link.is-light.is-hovered, .button.is-link.is-light:hover {
|
||||
background-color: #e0f1fc;
|
||||
color: #209CEE;
|
||||
}
|
||||
.tag.is-link.is-light {
|
||||
background-color: #edf8ff;
|
||||
color: #0D7DC6;
|
||||
}
|
||||
.tag.is-link.is-light:hover {
|
||||
background-color: #209CEE;
|
||||
color: white;
|
||||
}
|
||||
.tag.is-delete:hover {
|
||||
background-color: #FF3860!important;
|
||||
color: white;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.pagination {
|
||||
flex-wrap: nowrap;
|
||||
justify-content: left;
|
||||
}
|
||||
}
|
||||
.pagination-list {
|
||||
flex-wrap: nowrap;
|
||||
list-style: none!important;
|
||||
margin-top: 0.25em!important;
|
||||
margin-bottom: 0.25em!important;
|
||||
}
|
||||
|
||||
.content li + li {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.one-quarter#forwide, .one-third#forwide {
|
||||
display: block;
|
||||
min-width: 100px;
|
||||
}
|
||||
.one-quarter#fornarrow, .one-third#fornarrow {
|
||||
display: none;
|
||||
min-width: 50px;
|
||||
}
|
||||
@media only screen and (max-width: 1023px) {
|
||||
.one-quarter#forwide {
|
||||
display: none;
|
||||
}
|
||||
.one-quarter#fornarrow {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
@media only screen and (max-width: 700px) {
|
||||
.one-third#forwide {
|
||||
display: none;
|
||||
}
|
||||
.one-third#fornarrow {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@keyframes notification-fadeout {
|
||||
0% {
|
||||
opacity: 1;
|
||||
display: block;
|
||||
}
|
||||
80% {
|
||||
opacity: 1;
|
||||
display: block;
|
||||
}
|
||||
99.99% {
|
||||
opacity: 0;
|
||||
display: block;
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.notification {
|
||||
animation: notification-fadeout 8s forwards;
|
||||
animation-play-state: paused;
|
||||
}
|
||||
.notification:hover {
|
||||
animation-play-state: paused;
|
||||
}
|
||||
.modal {
|
||||
padding-bottom: 13px;
|
||||
}
|
||||
@media screen and (max-width:1024px) {
|
||||
.modal {
|
||||
transition: padding-left .2s ease-in-out 50ms;
|
||||
}
|
||||
}
|
||||
@media screen and (min-width:1023px) {
|
||||
.modal {
|
||||
padding-left: 256px;
|
||||
transition: padding-left .2s ease-in-out 50ms;
|
||||
}
|
||||
}
|
||||
.tag {
|
||||
transition: linear .2s;
|
||||
}
|
||||
.tags .tag:not(:last-child) {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.modal table tr {
|
||||
border: solid #dbdbdb;
|
||||
border-width: 0 0 1px;
|
||||
}
|
||||
.modal table tbody tr:last-child {
|
||||
border-bottom-width: 0;
|
||||
}
|
||||
.hidden-wrapper {
|
||||
overflow: hidden;
|
||||
}
|
||||
.hidden-x-wrapper {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
.hidden-y-wrapper {
|
||||
overflow-y: hidden;
|
||||
}
|
||||
.scrolling-wrapper {
|
||||
overflow: auto;
|
||||
}
|
||||
.scrolling-x-wrapper {
|
||||
overflow-x: auto;
|
||||
}
|
||||
.scrolling-y-wrapper {
|
||||
overflow-y: auto;
|
||||
}
|
||||
a.pagination-previous {
|
||||
overflow: hidden;
|
||||
}
|
||||
a.pagination-next {
|
||||
overflow: hidden;
|
||||
}
|
||||
.button.checkbox, .button.checkbox .checkmark {
|
||||
transition: linear .2s;
|
||||
}
|
||||
618
bst@asphyxia/webui/js/webui_util.js
Normal file
618
bst@asphyxia/webui/js/webui_util.js
Normal file
|
|
@ -0,0 +1,618 @@
|
|||
function initializePaginatedContent() {
|
||||
let containers = document.querySelectorAll(".paginated-container")
|
||||
|
||||
for (let container of containers) {
|
||||
let pageSizeInput = container.querySelector("input.page-size")
|
||||
let paginations = container.querySelectorAll(".pagination")
|
||||
let contents = container.querySelectorAll(".paginated-content")
|
||||
let group = container.getAttribute("pagination-group")
|
||||
let flags = { isFirst: true }
|
||||
let refreshEllipsis = (param) => {
|
||||
if (flags.isFirst) return
|
||||
let maxWidth = container.offsetWidth / 2
|
||||
for (let pagination of paginations) {
|
||||
let buttons = pagination.querySelector("ul.pagination-list")
|
||||
if (buttons.childElementCount == 0) return
|
||||
let show = (index) => buttons.querySelector("li[tab-index=\"" + index + "\"]").style.display = "block"
|
||||
let hide = (index) => buttons.querySelector("li[tab-index=\"" + index + "\"]").style.display = "none"
|
||||
let previousButton = pagination.querySelector("a.pagination-previous")
|
||||
let nextButton = pagination.querySelector("a.pagination-next")
|
||||
let leftEllipsis = buttons.querySelector("li.ellipsis-left")
|
||||
let rightEllipsis = buttons.querySelector("li.ellipsis-right")
|
||||
let width = buttons.firstChild.offsetWidth.toString()
|
||||
leftEllipsis.style.width = width + "px"
|
||||
rightEllipsis.style.width = width + "px"
|
||||
let count = buttons.childElementCount - 2
|
||||
let maxButtonCount = Math.max((buttons.firstChild.offsetWidth == 0) ? 5 : Math.trunc(maxWidth / buttons.firstChild.offsetWidth), 5)
|
||||
let current = (param instanceof HTMLElement) ? param : buttons.querySelector("li.is-active")
|
||||
let index = parseInt((current == null) ? 0 : current.getAttribute("tab-index"))
|
||||
if (index == 0) previousButton.setAttribute("disabled", "")
|
||||
else previousButton.removeAttribute("disabled")
|
||||
if (index == (count - 1)) nextButton.setAttribute("disabled", "")
|
||||
else nextButton.removeAttribute("disabled")
|
||||
if (count <= maxButtonCount) {
|
||||
for (let i = 0; i < count; i++) buttons.querySelector("li[tab-index=\"" + i + "\"]").style.display = "block"
|
||||
leftEllipsis.style.display = "none"
|
||||
rightEllipsis.style.display = "none"
|
||||
} else {
|
||||
maxButtonCount = Math.trunc((maxButtonCount - 1) / 2) * 2 + 1
|
||||
let maxSurroundingButtonCount = (maxButtonCount - 5) / 2
|
||||
let maxNoEllipsisIndex = maxButtonCount - 2 - maxSurroundingButtonCount - 1
|
||||
|
||||
if (index <= maxNoEllipsisIndex) {
|
||||
for (let i = 0; i <= (maxNoEllipsisIndex + maxSurroundingButtonCount); i++) show(i)
|
||||
for (let i = (maxNoEllipsisIndex + maxSurroundingButtonCount) + 1; i < count - 1; i++) hide(i)
|
||||
show(count - 1)
|
||||
leftEllipsis.style.display = "none"
|
||||
rightEllipsis.style.display = "block"
|
||||
} else if (index >= (count - maxNoEllipsisIndex - 1)) {
|
||||
for (let i = 1; i < (count - maxNoEllipsisIndex - maxSurroundingButtonCount - 1); i++) hide(i)
|
||||
for (let i = (count - maxNoEllipsisIndex - maxSurroundingButtonCount - 1); i < count; i++) show(i)
|
||||
show(0)
|
||||
leftEllipsis.style.display = "block"
|
||||
rightEllipsis.style.display = "none"
|
||||
} else {
|
||||
for (let i = 1; i < (index - maxSurroundingButtonCount); i++) hide(i)
|
||||
for (let i = (index - maxSurroundingButtonCount); i <= (index + maxSurroundingButtonCount); i++) show(i)
|
||||
for (let i = (index + maxSurroundingButtonCount) + 1; i < count - 1; i++) hide(i)
|
||||
show(0)
|
||||
show(count - 1)
|
||||
leftEllipsis.style.display = "block"
|
||||
rightEllipsis.style.display = "block"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let refresh = () => {
|
||||
if ((pageSizeInput == null) || (parseInt(pageSizeInput.value) <= 0)) {
|
||||
for (let pagination of paginations) pagination.style.display = "none"
|
||||
return
|
||||
}
|
||||
let pageSize = parseInt(pageSizeInput.value)
|
||||
let pageCount = Math.ceil(contents.length / pageSize)
|
||||
if (!flags.isFirst && (flags.pageSize == pageSize) && (flags.pageCount == pageCount)) return
|
||||
for (let pagination of paginations) {
|
||||
let buttons = pagination.querySelector("ul.pagination-list")
|
||||
buttons.innerHTML = ""
|
||||
buttons.id = "tabs"
|
||||
}
|
||||
for (let i = 0; i < pageCount; i++) {
|
||||
for (let j = i * pageSize; j < (i + 1) * pageSize; j++) {
|
||||
if (contents[j] == null) break
|
||||
contents[j].classList.add("tab-content")
|
||||
contents[j].setAttribute("tab-group", group)
|
||||
contents[j].setAttribute("tab-index", i)
|
||||
if ((i == 0) && (flags.isFirst || (flags.pageCount != pageCount))) contents[j].classList.add("is-active")
|
||||
if (j == ((i + 1) * pageSize - 1)) for (let td of contents[j].querySelectorAll("td")) td.style.borderBottom = "0"
|
||||
}
|
||||
if (pageCount > 1) for (let pagination of paginations) {
|
||||
let buttons = pagination.querySelector("ul.pagination-list")
|
||||
let a = document.createElement("a")
|
||||
a.classList.add("pagination-link")
|
||||
a.innerText = i + 1
|
||||
let li = document.createElement("li")
|
||||
li.appendChild(a)
|
||||
if ((i == 0) && (flags.isFirst || (flags.pageCount != pageCount))) {
|
||||
li.classList.add("is-active")
|
||||
a.classList.add("is-current")
|
||||
}
|
||||
li.setAttribute("tab-group", group)
|
||||
li.setAttribute("tab-index", i)
|
||||
buttons.appendChild(li)
|
||||
li.addEventListener("click", () => {
|
||||
refreshEllipsis(li)
|
||||
})
|
||||
}
|
||||
}
|
||||
if (pageCount > 1) for (let pagination of paginations) {
|
||||
pagination.style.display = "flex"
|
||||
let buttons = pagination.querySelector("ul.pagination-list")
|
||||
let leftEllipsis = document.createElement("li")
|
||||
leftEllipsis.style.display = "none"
|
||||
leftEllipsis.classList.add("ellipsis-left", "ignore")
|
||||
leftEllipsis.innerHTML = "<span class=\"pagination-ellipsis\">…</span>"
|
||||
let rightEllipsis = document.createElement("li")
|
||||
rightEllipsis.style.display = "none"
|
||||
rightEllipsis.classList.add("ellipsis-right", "ignore")
|
||||
rightEllipsis.innerHTML = "<span class=\"pagination-ellipsis\">…</span>"
|
||||
buttons.firstChild.after(leftEllipsis)
|
||||
buttons.lastChild.before(rightEllipsis)
|
||||
|
||||
let previousButton = pagination.querySelector("a.pagination-previous")
|
||||
let nextButton = pagination.querySelector("a.pagination-next")
|
||||
previousButton.addEventListener("click", () => {
|
||||
let current = buttons.querySelector("li.is-active")
|
||||
let index = parseInt(current.getAttribute("tab-index"))
|
||||
if (index <= 0) return
|
||||
let prev = buttons.querySelector("li[tab-index=\"" + (index - 1) + "\"]")
|
||||
prev.dispatchEvent(new Event("click"))
|
||||
})
|
||||
nextButton.addEventListener("click", () => {
|
||||
let current = buttons.querySelector("li.is-active")
|
||||
let index = parseInt(current.getAttribute("tab-index"))
|
||||
if (index >= (buttons.childElementCount - 3)) return // includes left & right ellipsis
|
||||
let next = buttons.querySelector("li[tab-index=\"" + (index + 1) + "\"]")
|
||||
next.dispatchEvent(new Event("click"))
|
||||
})
|
||||
} else for (let pagination of paginations) pagination.style.display = "none"
|
||||
flags.pageCount = pageCount
|
||||
flags.pageSize = pageSize
|
||||
flags.isFirst = false
|
||||
}
|
||||
refresh()
|
||||
pageSizeInput.addEventListener("change", refresh)
|
||||
let o = new ResizeObserver(refreshEllipsis)
|
||||
o.observe(container)
|
||||
}
|
||||
}
|
||||
|
||||
function initializeTabs() {
|
||||
let tabs = document.querySelectorAll("#tabs li")
|
||||
let tabContents = document.querySelectorAll("#tab-content, .tab-content")
|
||||
let updateActiveTab = (tabGroup, tabIndex) => {
|
||||
for (let t of tabs) if (t && (t.getAttribute("tab-group") == tabGroup)) {
|
||||
if (t.getAttribute("tab-index") != tabIndex) {
|
||||
t.classList.remove("is-active")
|
||||
for (let a of t.querySelectorAll("a")) a.classList.remove("is-current")
|
||||
} else {
|
||||
t.classList.add("is-active")
|
||||
for (let a of t.querySelectorAll("a")) a.classList.add("is-current")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let updateActiveContent = (tabGroup, tabIndex) => {
|
||||
for (let item of tabContents) {
|
||||
let group = item.getAttribute("tab-group")
|
||||
let index = item.getAttribute("tab-index")
|
||||
if (item && (group == tabGroup)) item.classList.remove("is-active")
|
||||
if ((index == tabIndex) && (group == tabGroup)) item.classList.add("is-active")
|
||||
}
|
||||
}
|
||||
for (let t of tabs) {
|
||||
if (!t.classList.contains("disabled") && !t.classList.contains("ignore")) t.addEventListener("click", () => {
|
||||
let group = t.getAttribute("tab-group")
|
||||
let index = t.getAttribute("tab-index")
|
||||
updateActiveTab(group, index)
|
||||
updateActiveContent(group, index)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function initializeToggles() {
|
||||
let toggles = document.querySelectorAll(".card-header .card-toggle")
|
||||
let contents = document.querySelectorAll(".card-content")
|
||||
|
||||
for (let t of toggles) {
|
||||
let card = t.getAttribute("card")
|
||||
if (card == null) continue
|
||||
let cc = []
|
||||
for (let c of contents) if (c.getAttribute("card") == card) cc.push(c)
|
||||
t.style.transition = "0.2s linear"
|
||||
t.addEventListener("click", (e) => {
|
||||
if (e.currentTarget.style.transform == "rotate(180deg)") {
|
||||
e.currentTarget.style.transform = ""
|
||||
for (let c of cc) c.classList.remove("is-hidden")
|
||||
} else {
|
||||
e.currentTarget.style.transform = "rotate(180deg)"
|
||||
for (let c of cc) c.classList.add("is-hidden")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function initializeModals() {
|
||||
let modaltriggers = $(".modal-trigger")
|
||||
for (let t of modaltriggers) {
|
||||
let m = t.querySelector(".modal")
|
||||
let c = m.querySelectorAll("#close")
|
||||
t.addEventListener("click", (e) => { m.style.display = "flex" })
|
||||
for (let v of c) v.addEventListener("click", (e) => {
|
||||
m.style.display = "none"
|
||||
e.stopPropagation()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function initializeFormSelects() {
|
||||
let formSelects = document.querySelectorAll("#form-select")
|
||||
for (let s of formSelects) {
|
||||
let input = s.querySelector("input#form-select-input")
|
||||
let select = s.querySelector("select#form-select-select")
|
||||
let options = select.querySelectorAll("option")
|
||||
for (let i = 0; i < options.length; i++) {
|
||||
let o = options[i]
|
||||
let value = (o.getAttribute("value") == null) ? i : o.getAttribute("value")
|
||||
let enabled = (o.getAttribute("disabled") == null) ? true : false
|
||||
if (value == input.value) select.selectedIndex = i
|
||||
if (!enabled) o.style.display = "none"
|
||||
}
|
||||
select.addEventListener("change", () => {
|
||||
for (let i = 0; i < options.length; i++) {
|
||||
let o = options[i]
|
||||
if (o.selected) {
|
||||
input.value = (o.getAttribute("value") == null) ? i : o.getAttribute("value")
|
||||
input.dispatchEvent(new Event("change"))
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function initializeFormPaginations() {
|
||||
let formPags = document.querySelectorAll("#form-pagination")
|
||||
for (let p of formPags) {
|
||||
let input = p.querySelector("input#form-pagination-input")
|
||||
let options = p.querySelectorAll("ul.pagination-list li a.pagination-link")
|
||||
for (let i = 0; i < options.length; i++) {
|
||||
let o = options[i]
|
||||
let value = (o.getAttribute("value") == null) ? i : o.getAttribute("value")
|
||||
if (value == input.value) {
|
||||
if (!o.classList.contains("is-current")) o.classList.add("is-current")
|
||||
} else o.classList.remove("is-current")
|
||||
o.addEventListener("click", () => {
|
||||
for (let i = 0; i < options.length; i++) options[i].classList.remove("is-current")
|
||||
if (!o.classList.contains("is-current")) o.classList.add("is-current")
|
||||
input.value = (o.getAttribute("value") == null) ? i : o.getAttribute("value")
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function initializeFormValidation() {
|
||||
let forms = document.querySelectorAll("form#validatable")
|
||||
for (let f of forms) {
|
||||
let validatableFields = f.querySelectorAll(".field#validatable")
|
||||
let validatableButtons = f.querySelectorAll("button#validatable")
|
||||
|
||||
let getParams = (input) => {
|
||||
return {
|
||||
minLength: input.getAttribute("min-length"),
|
||||
maxLength: input.getAttribute("max-length"),
|
||||
recommendedLength: input.getAttribute("recommended-length"),
|
||||
minPattern: input.getAttribute("min-pattern"),
|
||||
recommendedPattern: input.getAttribute("recommended-pattern"),
|
||||
isNumeric: (input.getAttribute("numeric") != null) ? true : false
|
||||
}
|
||||
}
|
||||
let isValid = (value, params) => {
|
||||
let t = value.trim()
|
||||
if (params.minLength != null) if (t.length < parseInt(params.minLength)) return false
|
||||
if (params.maxLength != null) if (t.length > parseInt(params.maxLength)) return false
|
||||
if (params.minPattern != null) if (!(new RegExp(params.minPattern).test(t))) return false
|
||||
if (params.isNumeric == true) if (parseInt(t).toString() != t) return false
|
||||
return true
|
||||
}
|
||||
|
||||
let isFormValid = () => {
|
||||
for (let field of validatableFields) for (let i of field.querySelectorAll("input#validatable")) if (!isValid(i.value, getParams(i))) return false
|
||||
return true
|
||||
}
|
||||
|
||||
for (let field of validatableFields) {
|
||||
let inputs = field.querySelectorAll("input#validatable")
|
||||
let tips = field.querySelectorAll(".help")
|
||||
for (let i of inputs) i.addEventListener("change", () => {
|
||||
let params = getParams(i)
|
||||
// inputs
|
||||
if (isValid(i.value, params)) {
|
||||
i.classList.remove("is-danger")
|
||||
for (let t of tips) t.classList.remove("is-danger")
|
||||
} else if (!i.classList.contains("is-danger")) {
|
||||
i.classList.add("is-danger")
|
||||
for (let t of tips) t.classList.add("is-danger")
|
||||
}
|
||||
// buttons
|
||||
if (isFormValid()) {
|
||||
for (let b of validatableButtons) b.removeAttribute("disabled")
|
||||
} else {
|
||||
for (let b of validatableButtons) if (b.getAttribute("disabled") == null) b.setAttribute("disabled", "")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function initializeFormCollections() {
|
||||
let collections = document.querySelectorAll("#form-collection")
|
||||
for (let c of collections) {
|
||||
let maxLength = parseInt(c.getAttribute("max-length"))
|
||||
let fallbackValue = JSON.parse(c.getAttribute("fallback"))
|
||||
let input = c.querySelector("#form-collection-input")
|
||||
let tags = c.querySelectorAll("#form-collection-tag")
|
||||
let modButton = c.querySelector("#form-collection-modify")
|
||||
let modTable = c.querySelector("table#multi-select")
|
||||
let modInput = modTable.querySelector("input#multi-select-input")
|
||||
let modTitle = modTable.querySelector("input#multi-select-title")
|
||||
let deleteButtonClickEventListener = (tag) => () => {
|
||||
let tvalue = JSON.parse(tag.getAttribute("value"))
|
||||
let value = JSON.parse(input.value)
|
||||
value.splice(value.indexOf(tvalue), 1)
|
||||
if (fallbackValue != null) value.push(fallbackValue)
|
||||
input.value = JSON.stringify(value)
|
||||
modInput.value = input.value
|
||||
modInput.dispatchEvent(new Event("change"))
|
||||
tag.remove()
|
||||
}
|
||||
|
||||
for (let t of tags) {
|
||||
let d = t.querySelector(".delete, .is-delete")
|
||||
d.addEventListener("click", deleteButtonClickEventListener(t))
|
||||
}
|
||||
modInput.value = input.value
|
||||
modInput.setAttribute("max-length", maxLength)
|
||||
modInput.setAttribute("fallback", JSON.stringify(fallbackValue))
|
||||
modInput.addEventListener("change", () => {
|
||||
let fallbackValue = JSON.parse(c.getAttribute("fallback"))
|
||||
let oldValue = JSON.parse(input.value)
|
||||
let newValue = JSON.parse(modInput.value)
|
||||
let tags = c.querySelectorAll("#form-collection-tag")
|
||||
for (let o of oldValue) if (!newValue.includes(o) && (o != fallbackValue)) {
|
||||
for (let t of tags) if (JSON.parse(t.getAttribute("value")) == o) t.remove()
|
||||
}
|
||||
for (let n = 0; n < newValue.length; n++) if (!oldValue.includes(newValue[n]) && (newValue[n] != fallbackValue)) {
|
||||
let tag = document.createElement("div")
|
||||
tag.classList.add("control")
|
||||
tag.id = "form-collection-tag"
|
||||
tag.setAttribute("value", newValue[n])
|
||||
tag.innerHTML = "<span class=\"tags has-addons\"><span class=\"tag is-link is-light\" id=\"form-collection-tag-title\">" + JSON.parse(modTitle.value)[n] + "</span><a class=\"tag is-delete\" /></span>"
|
||||
tag.querySelector("a.is-delete").addEventListener("click", deleteButtonClickEventListener(tag))
|
||||
modButton.before(tag)
|
||||
}
|
||||
input.value = modInput.value
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function initializeMultiSelectTables() {
|
||||
let tables = document.querySelectorAll("table#multi-select")
|
||||
for (let table of tables) {
|
||||
let valueInput = table.querySelector("input#multi-select-input")
|
||||
let titleInput = table.querySelector("input#multi-select-title")
|
||||
let trimValues = (values, fallback) => {
|
||||
while (values.includes(fallback)) values.splice(values.indexOf(fallback), 1)
|
||||
return values
|
||||
}
|
||||
let fillValues = (values, fallback) => {
|
||||
let maxLength = (valueInput.getAttribute("max-length") == null) ? -1 : parseInt(valueInput.getAttribute("max-length"))
|
||||
while (values.length < maxLength) values.push(fallback)
|
||||
return values
|
||||
}
|
||||
let lines = table.querySelectorAll("tbody tr")
|
||||
let refresh = () => {
|
||||
let fallbackValue = JSON.parse(valueInput.getAttribute("fallback"))
|
||||
let value = trimValues(JSON.parse(valueInput.value), fallbackValue)
|
||||
let title = []
|
||||
for (let l of lines) {
|
||||
let lvalue = JSON.parse(l.getAttribute("multi-select-value"))
|
||||
if (value.includes(lvalue)) {
|
||||
if (!l.classList.contains("is-selected")) l.classList.add("is-selected")
|
||||
title[value.indexOf(lvalue)] = l.getAttribute("multi-select-title")
|
||||
l.style.fontWeight = "bold"
|
||||
} else {
|
||||
l.classList.remove("is-selected")
|
||||
l.style.fontWeight = ""
|
||||
}
|
||||
}
|
||||
titleInput.value = JSON.stringify(title)
|
||||
}
|
||||
|
||||
for (let l of lines) {
|
||||
l.onclick = () => {
|
||||
let fallbackValue = JSON.parse(valueInput.getAttribute("fallback"))
|
||||
let maxLength = (valueInput.getAttribute("max-length") == null) ? -1 : parseInt(valueInput.getAttribute("max-length"))
|
||||
let value = trimValues(JSON.parse(valueInput.value), fallbackValue)
|
||||
let lvalue = JSON.parse(l.getAttribute("multi-select-value"))
|
||||
if (value.includes(lvalue)) value.splice(value.indexOf(lvalue), 1)
|
||||
else if (maxLength >= 0) {
|
||||
if (value.length < maxLength) value.push(lvalue)
|
||||
else alert("Cannot add more items, items are up to " + maxLength + ".")
|
||||
} else value.push(lvalue)
|
||||
valueInput.value = JSON.stringify(fillValues(value, fallbackValue))
|
||||
refresh()
|
||||
valueInput.dispatchEvent(new Event("change"))
|
||||
}
|
||||
refresh()
|
||||
}
|
||||
valueInput.addEventListener("change", refresh)
|
||||
}
|
||||
}
|
||||
|
||||
function initializeFormNumerics() {
|
||||
let numerics = document.querySelectorAll("#form-numeric")
|
||||
for (let n of numerics) {
|
||||
let add = n.querySelector("#form-numeric-add")
|
||||
let sub = n.querySelector("#form-numeric-sub")
|
||||
let inputs = n.querySelectorAll("#form-numeric-input")
|
||||
add.addEventListener("click", (e) => {
|
||||
for (let i of inputs) {
|
||||
let maxValue = parseFloat(i.getAttribute("max-value"))
|
||||
let step = parseFloat(i.getAttribute("step"))
|
||||
|
||||
let digitCount = (i.getAttribute("digit-count") == null) ? -1 : parseInt(i.getAttribute("digit-count"))
|
||||
let value = (parseFloat(i.value) * 10 + step * 10) / 10
|
||||
if (value * Math.sign(step) <= maxValue * Math.sign(step)) i.value = (digitCount >= 0) ? value.toFixed(digitCount) : value
|
||||
}
|
||||
e.stopPropagation()
|
||||
})
|
||||
sub.addEventListener("click", (e) => {
|
||||
for (let i of inputs) {
|
||||
let minValue = parseFloat(i.getAttribute("min-value"))
|
||||
let step = parseFloat(i.getAttribute("step"))
|
||||
let digitCount = (i.getAttribute("digit-count") == null) ? -1 : parseInt(i.getAttribute("digit-count"))
|
||||
let value = (parseFloat(i.value) * 10 - step * 10) / 10
|
||||
if (value * Math.sign(step) >= minValue * Math.sign(step)) i.value = (digitCount >= 0) ? value.toFixed(digitCount) : value
|
||||
}
|
||||
e.stopPropagation()
|
||||
})
|
||||
for (let i of inputs) {
|
||||
let digitCount = (i.getAttribute("digit-count") == null) ? -1 : parseInt(i.getAttribute("digit-count"))
|
||||
let value = parseFloat(i.value)
|
||||
i.value = (digitCount >= 0) ? value.toFixed(digitCount) : value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function initializeUploader() {
|
||||
let uploaders = document.querySelectorAll("div#uploader")
|
||||
for (let uploader of uploaders) {
|
||||
let input = uploader.querySelector("input#uploader-input")
|
||||
let text = uploader.querySelector("input#uploader-text")
|
||||
let placeholder = uploader.querySelector("#uploader-placeholder")
|
||||
let remove = uploader.querySelector("#uploader-delete")
|
||||
let reader = new FileReader()
|
||||
input.addEventListener("change", () => {
|
||||
if (input.files.length > 0) {
|
||||
remove.style.display = "block"
|
||||
placeholder.innerText = input.files[0].name
|
||||
reader.readAsText(input.files[0])
|
||||
reader.onload = () => text.value = reader.result
|
||||
} else {
|
||||
placeholder.innerText = ""
|
||||
remove.style.display = "none"
|
||||
text.value = null
|
||||
}
|
||||
})
|
||||
remove.addEventListener("click", (e) => {
|
||||
e.stopPropagation()
|
||||
input.value = null
|
||||
input.dispatchEvent(new Event("change"))
|
||||
})
|
||||
|
||||
remove.style.display = "none"
|
||||
}
|
||||
}
|
||||
|
||||
function checkImg() {
|
||||
let imgs = document.querySelectorAll("#exist-or-not")
|
||||
for (let img of imgs) {
|
||||
let general = img.querySelector("img#general")
|
||||
let specified = img.querySelector("img#specified")
|
||||
|
||||
if (specified.width == 0) specified.style.display = "none"
|
||||
else general.style.display = "none"
|
||||
}
|
||||
}
|
||||
|
||||
function initializeMarqueeLabels() {
|
||||
let marqueeContainers = document.querySelectorAll(".marquee-label-container")
|
||||
for (let c of marqueeContainers) {
|
||||
let marquees = c.querySelectorAll(".marquee-label")
|
||||
for (let marquee of marquees) {
|
||||
if (marquee.closest(".marquee-label-container") != c) continue
|
||||
let refresh = () => {
|
||||
let lpad = parseInt(window.getComputedStyle(c, null).getPropertyValue("padding-left"))
|
||||
if (lpad == NaN) lpad = 0
|
||||
let rpad = parseInt(window.getComputedStyle(c, null).getPropertyValue("padding-right"))
|
||||
if (rpad == NaN) rpad = 20
|
||||
let hpad = lpad + rpad
|
||||
let speed = marquee.getAttribute("speed")
|
||||
if (speed == null) speed = 1
|
||||
let stopingTime = 0.5
|
||||
let duration = (20 * (marquee.offsetWidth - c.offsetWidth + hpad)) / speed + 2 * stopingTime
|
||||
if ((marquee.offsetWidth > 0) && (marquee.offsetWidth > c.offsetWidth - hpad)) {
|
||||
marquee.animate([
|
||||
{ transform: "translateX(0)", offset: 0 },
|
||||
{ transform: "translateX(0)", easing: "cubic-bezier(0.67, 0, 0.33, 1)", offset: stopingTime / duration },
|
||||
{ transform: "translateX(" + (c.offsetWidth - marquee.offsetWidth - hpad) + "px)", easing: "cubic-bezier(0.67, 0, 0.33, 1)", offset: 1 - stopingTime / duration },
|
||||
{ transform: "translateX(" + (c.offsetWidth - marquee.offsetWidth - hpad) + "px)", offset: 1 }
|
||||
], { duration: (20 * (marquee.offsetWidth - c.offsetWidth) + 1000) / speed, direction: "alternate-reverse", iterations: Infinity })
|
||||
} else marquee.style.animation = "none"
|
||||
}
|
||||
let o = new ResizeObserver(refresh)
|
||||
o.observe(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function initializeNotificatioAnimation() {
|
||||
let notifications = document.querySelectorAll(".notification.temporary")
|
||||
for (let n of notifications) {
|
||||
let remove = n.querySelector(".delete")
|
||||
let startSubmitter = n.querySelector("form.start")
|
||||
let startPath = startSubmitter.getAttribute("action")
|
||||
let startRequest = new XMLHttpRequest()
|
||||
startRequest.open("POST", startPath, true)
|
||||
startRequest.setRequestHeader("Content-type", "application/x-www-form-urlencoded")
|
||||
|
||||
let endSubmitter = n.querySelector("form.end")
|
||||
let endPath = startSubmitter.getAttribute("action")
|
||||
let endRequest = new XMLHttpRequest()
|
||||
endRequest.open("POST", endPath, true)
|
||||
endRequest.setRequestHeader("Content-type", "application/x-www-form-urlencoded")
|
||||
|
||||
if (startSubmitter != null) startRequest.send()
|
||||
let end = () => {
|
||||
n.style.display = "none"
|
||||
if (endSubmitter != null) endRequest.send()
|
||||
}
|
||||
|
||||
n.style.animationPlayState = "running"
|
||||
remove.addEventListener("click", end)
|
||||
n.addEventListener("animationend", end)
|
||||
n.addEventListener("webkitAnimationEnd", end)
|
||||
}
|
||||
}
|
||||
|
||||
function initializeCheckBoxes() {
|
||||
let checks = document.querySelectorAll(".checkbox")
|
||||
for (let c of checks) {
|
||||
let input = c.querySelector("input[type=checkbox]")
|
||||
let mark = c.querySelector(".checkmark")
|
||||
let refresh = (value) => {
|
||||
value = input.getAttribute("checked")
|
||||
if (value == null) {
|
||||
input.removeAttribute("checked")
|
||||
mark.style.opacity = 0
|
||||
if (!c.classList.contains("is-light")) c.classList.add("is-light")
|
||||
} else {
|
||||
input.setAttribute("checked", "checked")
|
||||
mark.style.opacity = 100
|
||||
c.classList.remove("is-light")
|
||||
}
|
||||
}
|
||||
c.addEventListener("click", () => {
|
||||
let value = input.getAttribute("checked")
|
||||
if (value == null) input.setAttribute("checked", "checked")
|
||||
else input.removeAttribute("checked")
|
||||
refresh()
|
||||
})
|
||||
refresh()
|
||||
}
|
||||
}
|
||||
|
||||
function removeLoadingModal() {
|
||||
let loading = document.querySelector(".loading")
|
||||
setTimeout(() => (loading == null) ? null : loading.remove(), 505)
|
||||
try {
|
||||
let a = loading.animate([
|
||||
{ offset: 0, opacity: 1 },
|
||||
{ offset: 0.25, opacity: 0 },
|
||||
{ offset: 1, opacity: 0 }
|
||||
], { duration: 2000 })
|
||||
a.onfinish = loading.remove
|
||||
a.play()
|
||||
} catch { }
|
||||
}
|
||||
|
||||
$(document).ready(() => {
|
||||
initializeNotificatioAnimation()
|
||||
initializePaginatedContent()
|
||||
initializeTabs()
|
||||
initializeToggles()
|
||||
initializeModals()
|
||||
initializeFormSelects()
|
||||
initializeFormNumerics()
|
||||
initializeFormPaginations()
|
||||
initializeFormValidation()
|
||||
initializeFormCollections()
|
||||
initializeMultiSelectTables()
|
||||
initializeUploader()
|
||||
checkImg()
|
||||
initializeMarqueeLabels()
|
||||
initializeCheckBoxes()
|
||||
|
||||
removeLoadingModal()
|
||||
})
|
||||
|
||||
750
bst@asphyxia/webui/profile_detail.pug
Normal file
750
bst@asphyxia/webui/profile_detail.pug
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -1,13 +1,32 @@
|
|||
# Nostalgia
|
||||
|
||||
Plugin Version: **v1.0.0**
|
||||
Plugin Version: **v1.2.0**
|
||||
|
||||
Supported Versions
|
||||
-------------------
|
||||
- ノスタルジア/ First Version (Experiment-Old)
|
||||
- Forte (Experiment-Old)
|
||||
- Op.2
|
||||
|
||||
About Experiment-Old Support
|
||||
----------------------------
|
||||
A version that marked as **Experiment-Old** is _Not_ Primary supported experiment version.
|
||||
Since This plugin is mainly focused on Op.2, other versions may not work correctly.
|
||||
|
||||
If you have a problem that move from old version to new version, There's webui for mitigate the issue.
|
||||
|
||||
Changelog
|
||||
=========
|
||||
1.2.0 (Current)
|
||||
---------------
|
||||
- Nostalgia First version support.
|
||||
|
||||
1.1.0
|
||||
-----
|
||||
- Fix saving issue with brooch, island, and kentei.
|
||||
- Moved to Base64 encoded data base.
|
||||
- Forte support.
|
||||
|
||||
1.0.0
|
||||
-----
|
||||
Initial Release.
|
||||
|
|
|
|||
3
nostalgia@asphyxia/data/.gitignore
vendored
Normal file
3
nostalgia@asphyxia/data/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
*.json
|
||||
*.json
|
||||
*.xml
|
||||
11
nostalgia@asphyxia/data/FirstMusic.ts
Normal file
11
nostalgia@asphyxia/data/FirstMusic.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import { CommonMusicData, readJSONOrXML } from "./ForteMusic";
|
||||
import { readB64JSON } from "./helper";
|
||||
|
||||
export async function processData(): Promise<CommonMusicData> {
|
||||
if (IO.Exists("data/first_mdb.json.b64")) {
|
||||
return await readB64JSON("data/first_mdb.json.b64");
|
||||
}
|
||||
const data = await readJSONOrXML("data/first_mdb.json", "data/first_mdb.xml")
|
||||
// await IO.WriteFile("data/first_mdb.json.b64", Buffer.from(JSON.stringify(data)).toString("base64"))
|
||||
return data;
|
||||
}
|
||||
70
nostalgia@asphyxia/data/ForteMusic.ts
Normal file
70
nostalgia@asphyxia/data/ForteMusic.ts
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
import { CommonMusicDataField, readB64JSON, readXML } from "./helper";
|
||||
|
||||
export async function processData(): Promise<CommonMusicData> {
|
||||
if (IO.Exists("data/forte_mdb.json.b64")) {
|
||||
return await readB64JSON("data/forte_mdb.json.b64");
|
||||
}
|
||||
const data = await readJSONOrXML("data/forte_mdb.json", "data/forte_mdb.xml")
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function processMdbData(path: string): Promise<CommonMusicData> {
|
||||
const data = await readXML(path);
|
||||
const attr = $(data).attr("music_list");
|
||||
const mdb = $(data).elements("music_list.music_spec");
|
||||
const music: CommonMusicDataField[] = [];
|
||||
for (const m of mdb) {
|
||||
music.push(K.ATTR({ index: m.attr().index }, {
|
||||
basename: K.ITEM("str", m.str("basename")),
|
||||
title: K.ITEM("str", m.str("title", "title")),
|
||||
title_kana: K.ITEM("str", m.str("title_kana", "title_kana")),
|
||||
artist: K.ITEM("str", m.str("artist", "artist")),
|
||||
artist_kana: K.ITEM("str", m.str("artist_kana", "artist_kana")),
|
||||
priority: K.ITEM("s8", m.number("priority")),
|
||||
category_flag: K.ARRAY("s32", m.numbers("category_flag")),
|
||||
primary_category: K.ITEM("s8", m.number("primary_category")),
|
||||
level_normal: K.ITEM("s8", m.number("level_normal")),
|
||||
level_hard: K.ITEM("s8", m.number("level_hard")),
|
||||
level_extreme: K.ITEM("s8", m.number("level_extreme")),
|
||||
demo_popular: K.ITEM("bool", m.bool("demo_popular")),
|
||||
demo_bemani: K.ITEM("bool", m.bool("demo_bemani")),
|
||||
destination_j: K.ITEM("bool", true),
|
||||
destination_a: K.ITEM("bool", true),
|
||||
destination_y: K.ITEM("bool", true),
|
||||
destination_k: K.ITEM("bool", true),
|
||||
offline: K.ITEM("bool", m.bool("offline")),
|
||||
unlock_type: K.ITEM("s8", m.number("unlock_type") == 3 ? 1 : m.number("unlock_type")),
|
||||
volume_bgm: K.ITEM("s8", m.number("volume_bgm")),
|
||||
volume_key: K.ITEM("s8", m.number("volume_key")),
|
||||
start_date: K.ITEM("str", m.str("start_date")),
|
||||
end_date: K.ITEM("str", "9999-12-31 23:59"),
|
||||
description: K.ITEM("str", m.str("description", "description"))
|
||||
}));
|
||||
}
|
||||
return K.ATTR({
|
||||
release_code: attr.release_code,
|
||||
revision: attr.revision,
|
||||
}, {
|
||||
music_spec: music,
|
||||
});
|
||||
}
|
||||
|
||||
export async function readJSONOrXML(jsonPath: string, xmlPath: string): Promise<CommonMusicData> {
|
||||
if (!IO.Exists(jsonPath)) {
|
||||
const data = await processMdbData(xmlPath)
|
||||
await IO.WriteFile(jsonPath, JSON.stringify(data))
|
||||
await IO.WriteFile(jsonPath.replace(".json", ".json.b64"), Buffer.from((JSON.stringify(data))).toString('base64'));
|
||||
return data
|
||||
} else {
|
||||
const json = JSON.parse(await IO.ReadFile(jsonPath, 'utf-8'))
|
||||
return json
|
||||
}
|
||||
}
|
||||
|
||||
export interface CommonMusicData {
|
||||
"@attr": {
|
||||
revision: string,
|
||||
release_code: string
|
||||
}
|
||||
music_spec: CommonMusicDataField[]
|
||||
}
|
||||
1
nostalgia@asphyxia/data/course.json.b64
Normal file
1
nostalgia@asphyxia/data/course.json.b64
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -1,699 +0,0 @@
|
|||
<?xml version="1.0" encoding="shift_jis"?>
|
||||
<course_data_list release_code="2019022700">
|
||||
<course_data index="1">
|
||||
<name __type="str">ベーシック10級検定</name>
|
||||
<priority __type="s32">40</priority>
|
||||
<req_nos __type="s32">1000</req_nos>
|
||||
<mode_type __type="s32">1</mode_type>
|
||||
<req_grade __type="s32">80000</req_grade>
|
||||
<seq_list>
|
||||
<tune tune_no="0">
|
||||
<index __type="s32">140</index>
|
||||
<difficulty __type="s32">0</difficulty>
|
||||
<clear_point __type="s32">850000</clear_point>
|
||||
</tune>
|
||||
<tune tune_no="1">
|
||||
<index __type="s32">225</index>
|
||||
<difficulty __type="s32">0</difficulty>
|
||||
<clear_point __type="s32">1725000</clear_point>
|
||||
</tune>
|
||||
<tune tune_no="2">
|
||||
<index __type="s32">170</index>
|
||||
<difficulty __type="s32">0</difficulty>
|
||||
<clear_point __type="s32">2625000</clear_point>
|
||||
</tune>
|
||||
</seq_list>
|
||||
<unlock_music __type="s32">0</unlock_music>
|
||||
<unlock_item_list />
|
||||
<start_date __type="str">2019-01-01 10:00</start_date>
|
||||
<end_date __type="str">9999-01-01 00:00</end_date>
|
||||
</course_data>
|
||||
<course_data index="2">
|
||||
<name __type="str">ベーシック9級検定</name>
|
||||
<priority __type="s32">39</priority>
|
||||
<req_nos __type="s32">1500</req_nos>
|
||||
<mode_type __type="s32">1</mode_type>
|
||||
<req_grade __type="s32">120000</req_grade>
|
||||
<seq_list>
|
||||
<tune tune_no="0">
|
||||
<index __type="s32">199</index>
|
||||
<difficulty __type="s32">0</difficulty>
|
||||
<clear_point __type="s32">875000</clear_point>
|
||||
</tune>
|
||||
<tune tune_no="1">
|
||||
<index __type="s32">209</index>
|
||||
<difficulty __type="s32">0</difficulty>
|
||||
<clear_point __type="s32">1775000</clear_point>
|
||||
</tune>
|
||||
<tune tune_no="2">
|
||||
<index __type="s32">212</index>
|
||||
<difficulty __type="s32">0</difficulty>
|
||||
<clear_point __type="s32">2700000</clear_point>
|
||||
</tune>
|
||||
</seq_list>
|
||||
<unlock_music __type="s32">0</unlock_music>
|
||||
<unlock_item_list />
|
||||
<start_date __type="str">2019-01-01 10:00</start_date>
|
||||
<end_date __type="str">9999-01-01 00:00</end_date>
|
||||
</course_data>
|
||||
<course_data index="3">
|
||||
<name __type="str">ベーシック8級検定</name>
|
||||
<priority __type="s32">38</priority>
|
||||
<req_nos __type="s32">2000</req_nos>
|
||||
<mode_type __type="s32">1</mode_type>
|
||||
<req_grade __type="s32">200000</req_grade>
|
||||
<seq_list>
|
||||
<tune tune_no="0">
|
||||
<index __type="s32">220</index>
|
||||
<difficulty __type="s32">1</difficulty>
|
||||
<clear_point __type="s32">900000</clear_point>
|
||||
</tune>
|
||||
<tune tune_no="1">
|
||||
<index __type="s32">232</index>
|
||||
<difficulty __type="s32">1</difficulty>
|
||||
<clear_point __type="s32">1825000</clear_point>
|
||||
</tune>
|
||||
<tune tune_no="2">
|
||||
<index __type="s32">82</index>
|
||||
<difficulty __type="s32">0</difficulty>
|
||||
<clear_point __type="s32">2775000</clear_point>
|
||||
</tune>
|
||||
</seq_list>
|
||||
<unlock_music __type="s32">0</unlock_music>
|
||||
<unlock_item_list />
|
||||
<start_date __type="str">2019-01-01 10:00</start_date>
|
||||
<end_date __type="str">9999-01-01 00:00</end_date>
|
||||
</course_data>
|
||||
<course_data index="4">
|
||||
<name __type="str">ベーシック7級検定</name>
|
||||
<priority __type="s32">37</priority>
|
||||
<req_nos __type="s32">2500</req_nos>
|
||||
<mode_type __type="s32">1</mode_type>
|
||||
<req_grade __type="s32">300000</req_grade>
|
||||
<seq_list>
|
||||
<tune tune_no="0">
|
||||
<index __type="s32">186</index>
|
||||
<difficulty __type="s32">1</difficulty>
|
||||
<clear_point __type="s32">925000</clear_point>
|
||||
</tune>
|
||||
<tune tune_no="1">
|
||||
<index __type="s32">141</index>
|
||||
<difficulty __type="s32">1</difficulty>
|
||||
<clear_point __type="s32">1875000</clear_point>
|
||||
</tune>
|
||||
<tune tune_no="2">
|
||||
<index __type="s32">32</index>
|
||||
<difficulty __type="s32">1</difficulty>
|
||||
<clear_point __type="s32">2850000</clear_point>
|
||||
</tune>
|
||||
</seq_list>
|
||||
<unlock_music __type="s32">0</unlock_music>
|
||||
<unlock_item_list />
|
||||
<start_date __type="str">2019-01-01 10:00</start_date>
|
||||
<end_date __type="str">9999-01-01 00:00</end_date>
|
||||
</course_data>
|
||||
<course_data index="5">
|
||||
<name __type="str">ベーシック6級検定</name>
|
||||
<priority __type="s32">36</priority>
|
||||
<req_nos __type="s32">3000</req_nos>
|
||||
<mode_type __type="s32">1</mode_type>
|
||||
<req_grade __type="s32">400000</req_grade>
|
||||
<seq_list>
|
||||
<tune tune_no="0">
|
||||
<index __type="s32">36</index>
|
||||
<difficulty __type="s32">1</difficulty>
|
||||
<clear_point __type="s32">925000</clear_point>
|
||||
</tune>
|
||||
<tune tune_no="1">
|
||||
<index __type="s32">87</index>
|
||||
<difficulty __type="s32">1</difficulty>
|
||||
<clear_point __type="s32">1875000</clear_point>
|
||||
</tune>
|
||||
<tune tune_no="2">
|
||||
<index __type="s32">242</index>
|
||||
<difficulty __type="s32">1</difficulty>
|
||||
<clear_point __type="s32">2850000</clear_point>
|
||||
</tune>
|
||||
</seq_list>
|
||||
<unlock_music __type="s32">0</unlock_music>
|
||||
<unlock_item_list />
|
||||
<start_date __type="str">2019-01-01 10:00</start_date>
|
||||
<end_date __type="str">9999-01-01 00:00</end_date>
|
||||
</course_data>
|
||||
<course_data index="6">
|
||||
<name __type="str">ベーシック5級検定</name>
|
||||
<priority __type="s32">35</priority>
|
||||
<req_nos __type="s32">3500</req_nos>
|
||||
<mode_type __type="s32">1</mode_type>
|
||||
<req_grade __type="s32">450000</req_grade>
|
||||
<seq_list>
|
||||
<tune tune_no="0">
|
||||
<index __type="s32">122</index>
|
||||
<difficulty __type="s32">2</difficulty>
|
||||
<clear_point __type="s32">925000</clear_point>
|
||||
</tune>
|
||||
<tune tune_no="1">
|
||||
<index __type="s32">40</index>
|
||||
<difficulty __type="s32">2</difficulty>
|
||||
<clear_point __type="s32">1875000</clear_point>
|
||||
</tune>
|
||||
<tune tune_no="2">
|
||||
<index __type="s32">85</index>
|
||||
<difficulty __type="s32">2</difficulty>
|
||||
<clear_point __type="s32">2850000</clear_point>
|
||||
</tune>
|
||||
</seq_list>
|
||||
<unlock_music __type="s32">0</unlock_music>
|
||||
<unlock_item_list />
|
||||
<start_date __type="str">2019-01-01 10:00</start_date>
|
||||
<end_date __type="str">9999-01-01 00:00</end_date>
|
||||
</course_data>
|
||||
<course_data index="7">
|
||||
<name __type="str">ベーシック4級検定</name>
|
||||
<priority __type="s32">34</priority>
|
||||
<req_nos __type="s32">4000</req_nos>
|
||||
<mode_type __type="s32">1</mode_type>
|
||||
<req_grade __type="s32">500000</req_grade>
|
||||
<seq_list>
|
||||
<tune tune_no="0">
|
||||
<index __type="s32">7</index>
|
||||
<difficulty __type="s32">2</difficulty>
|
||||
<clear_point __type="s32">925000</clear_point>
|
||||
</tune>
|
||||
<tune tune_no="1">
|
||||
<index __type="s32">28</index>
|
||||
<difficulty __type="s32">2</difficulty>
|
||||
<clear_point __type="s32">1875000</clear_point>
|
||||
</tune>
|
||||
<tune tune_no="2">
|
||||
<index __type="s32">49</index>
|
||||
<difficulty __type="s32">0</difficulty>
|
||||
<clear_point __type="s32">2850000</clear_point>
|
||||
</tune>
|
||||
</seq_list>
|
||||
<unlock_music __type="s32">0</unlock_music>
|
||||
<unlock_item_list />
|
||||
<start_date __type="str">2020-01-21 10:00</start_date>
|
||||
<end_date __type="str">9999-01-01 00:00</end_date>
|
||||
</course_data>
|
||||
<course_data index="8">
|
||||
<name __type="str">ベーシック3級検定</name>
|
||||
<priority __type="s32">33</priority>
|
||||
<req_nos __type="s32">4500</req_nos>
|
||||
<mode_type __type="s32">1</mode_type>
|
||||
<req_grade __type="s32">525000</req_grade>
|
||||
<seq_list>
|
||||
<tune tune_no="0">
|
||||
<index __type="s32">8</index>
|
||||
<difficulty __type="s32">2</difficulty>
|
||||
<clear_point __type="s32">925000</clear_point>
|
||||
</tune>
|
||||
<tune tune_no="1">
|
||||
<index __type="s32">29</index>
|
||||
<difficulty __type="s32">0</difficulty>
|
||||
<clear_point __type="s32">1875000</clear_point>
|
||||
</tune>
|
||||
<tune tune_no="2">
|
||||
<index __type="s32">50</index>
|
||||
<difficulty __type="s32">1</difficulty>
|
||||
<clear_point __type="s32">2850000</clear_point>
|
||||
</tune>
|
||||
</seq_list>
|
||||
<unlock_music __type="s32">0</unlock_music>
|
||||
<unlock_item_list />
|
||||
<start_date __type="str">2020-01-21 10:00</start_date>
|
||||
<end_date __type="str">9999-01-01 00:00</end_date>
|
||||
</course_data>
|
||||
<course_data index="9">
|
||||
<name __type="str">ベーシック2級検定</name>
|
||||
<priority __type="s32">32</priority>
|
||||
<req_nos __type="s32">5000</req_nos>
|
||||
<mode_type __type="s32">1</mode_type>
|
||||
<req_grade __type="s32">540000</req_grade>
|
||||
<seq_list>
|
||||
<tune tune_no="0">
|
||||
<index __type="s32">9</index>
|
||||
<difficulty __type="s32">0</difficulty>
|
||||
<clear_point __type="s32">925000</clear_point>
|
||||
</tune>
|
||||
<tune tune_no="1">
|
||||
<index __type="s32">30</index>
|
||||
<difficulty __type="s32">1</difficulty>
|
||||
<clear_point __type="s32">1875000</clear_point>
|
||||
</tune>
|
||||
<tune tune_no="2">
|
||||
<index __type="s32">51</index>
|
||||
<difficulty __type="s32">2</difficulty>
|
||||
<clear_point __type="s32">2850000</clear_point>
|
||||
</tune>
|
||||
</seq_list>
|
||||
<unlock_music __type="s32">0</unlock_music>
|
||||
<unlock_item_list />
|
||||
<start_date __type="str">2020-01-21 10:00</start_date>
|
||||
<end_date __type="str">9999-01-01 00:00</end_date>
|
||||
</course_data>
|
||||
<course_data index="10">
|
||||
<name __type="str">ベーシック1級検定</name>
|
||||
<priority __type="s32">31</priority>
|
||||
<req_nos __type="s32">6000</req_nos>
|
||||
<mode_type __type="s32">1</mode_type>
|
||||
<req_grade __type="s32">550000</req_grade>
|
||||
<seq_list>
|
||||
<tune tune_no="0">
|
||||
<index __type="s32">10</index>
|
||||
<difficulty __type="s32">1</difficulty>
|
||||
<clear_point __type="s32">925000</clear_point>
|
||||
</tune>
|
||||
<tune tune_no="1">
|
||||
<index __type="s32">31</index>
|
||||
<difficulty __type="s32">2</difficulty>
|
||||
<clear_point __type="s32">1875000</clear_point>
|
||||
</tune>
|
||||
<tune tune_no="2">
|
||||
<index __type="s32">52</index>
|
||||
<difficulty __type="s32">2</difficulty>
|
||||
<clear_point __type="s32">2850000</clear_point>
|
||||
</tune>
|
||||
</seq_list>
|
||||
<unlock_music __type="s32">0</unlock_music>
|
||||
<unlock_item_list />
|
||||
<start_date __type="str">2020-01-21 10:00</start_date>
|
||||
<end_date __type="str">9999-01-01 00:00</end_date>
|
||||
</course_data>
|
||||
<course_data index="11">
|
||||
<name __type="str">リサイタル10級検定</name>
|
||||
<priority __type="s32">30</priority>
|
||||
<req_nos __type="s32">1000</req_nos>
|
||||
<mode_type __type="s32">2</mode_type>
|
||||
<req_grade __type="s32">80000</req_grade>
|
||||
<seq_list>
|
||||
<tune tune_no="0">
|
||||
<index __type="s32">7</index>
|
||||
<difficulty __type="s32">1</difficulty>
|
||||
<clear_point __type="s32">34000</clear_point>
|
||||
</tune>
|
||||
<tune tune_no="1">
|
||||
<index __type="s32">238</index>
|
||||
<difficulty __type="s32">1</difficulty>
|
||||
<clear_point __type="s32">72000</clear_point>
|
||||
</tune>
|
||||
<tune tune_no="2">
|
||||
<index __type="s32">227</index>
|
||||
<difficulty __type="s32">1</difficulty>
|
||||
<clear_point __type="s32">114000</clear_point>
|
||||
</tune>
|
||||
</seq_list>
|
||||
<unlock_music __type="s32">0</unlock_music>
|
||||
<unlock_item_list />
|
||||
<start_date __type="str">2019-01-01 10:00</start_date>
|
||||
<end_date __type="str">9999-01-01 00:00</end_date>
|
||||
</course_data>
|
||||
<course_data index="12">
|
||||
<name __type="str">リサイタル9級検定</name>
|
||||
<priority __type="s32">29</priority>
|
||||
<req_nos __type="s32">1500</req_nos>
|
||||
<mode_type __type="s32">2</mode_type>
|
||||
<req_grade __type="s32">120000</req_grade>
|
||||
<seq_list>
|
||||
<tune tune_no="0">
|
||||
<index __type="s32">19</index>
|
||||
<difficulty __type="s32">1</difficulty>
|
||||
<clear_point __type="s32">36000</clear_point>
|
||||
</tune>
|
||||
<tune tune_no="1">
|
||||
<index __type="s32">220</index>
|
||||
<difficulty __type="s32">1</difficulty>
|
||||
<clear_point __type="s32">76000</clear_point>
|
||||
</tune>
|
||||
<tune tune_no="2">
|
||||
<index __type="s32">214</index>
|
||||
<difficulty __type="s32">1</difficulty>
|
||||
<clear_point __type="s32">120000</clear_point>
|
||||
</tune>
|
||||
</seq_list>
|
||||
<unlock_music __type="s32">0</unlock_music>
|
||||
<unlock_item_list />
|
||||
<start_date __type="str">2019-01-01 10:00</start_date>
|
||||
<end_date __type="str">9999-01-01 00:00</end_date>
|
||||
</course_data>
|
||||
<course_data index="13">
|
||||
<name __type="str">リサイタル8級検定</name>
|
||||
<priority __type="s32">28</priority>
|
||||
<req_nos __type="s32">2000</req_nos>
|
||||
<mode_type __type="s32">2</mode_type>
|
||||
<req_grade __type="s32">200000</req_grade>
|
||||
<seq_list>
|
||||
<tune tune_no="0">
|
||||
<index __type="s32">8</index>
|
||||
<difficulty __type="s32">1</difficulty>
|
||||
<clear_point __type="s32">38000</clear_point>
|
||||
</tune>
|
||||
<tune tune_no="1">
|
||||
<index __type="s32">63</index>
|
||||
<difficulty __type="s32">1</difficulty>
|
||||
<clear_point __type="s32">80000</clear_point>
|
||||
</tune>
|
||||
<tune tune_no="2">
|
||||
<index __type="s32">86</index>
|
||||
<difficulty __type="s32">2</difficulty>
|
||||
<clear_point __type="s32">126000</clear_point>
|
||||
</tune>
|
||||
</seq_list>
|
||||
<unlock_music __type="s32">0</unlock_music>
|
||||
<unlock_item_list />
|
||||
<start_date __type="str">2019-01-01 10:00</start_date>
|
||||
<end_date __type="str">9999-01-01 00:00</end_date>
|
||||
</course_data>
|
||||
<course_data index="14">
|
||||
<name __type="str">リサイタル7級検定</name>
|
||||
<priority __type="s32">27</priority>
|
||||
<req_nos __type="s32">2500</req_nos>
|
||||
<mode_type __type="s32">2</mode_type>
|
||||
<req_grade __type="s32">300000</req_grade>
|
||||
<seq_list>
|
||||
<tune tune_no="0">
|
||||
<index __type="s32">72</index>
|
||||
<difficulty __type="s32">2</difficulty>
|
||||
<clear_point __type="s32">40000</clear_point>
|
||||
</tune>
|
||||
<tune tune_no="1">
|
||||
<index __type="s32">71</index>
|
||||
<difficulty __type="s32">2</difficulty>
|
||||
<clear_point __type="s32">84000</clear_point>
|
||||
</tune>
|
||||
<tune tune_no="2">
|
||||
<index __type="s32">70</index>
|
||||
<difficulty __type="s32">2</difficulty>
|
||||
<clear_point __type="s32">132000</clear_point>
|
||||
</tune>
|
||||
</seq_list>
|
||||
<unlock_music __type="s32">0</unlock_music>
|
||||
<unlock_item_list />
|
||||
<start_date __type="str">2019-01-01 10:00</start_date>
|
||||
<end_date __type="str">9999-01-01 00:00</end_date>
|
||||
</course_data>
|
||||
<course_data index="15">
|
||||
<name __type="str">リサイタル6級検定</name>
|
||||
<priority __type="s32">26</priority>
|
||||
<req_nos __type="s32">3000</req_nos>
|
||||
<mode_type __type="s32">2</mode_type>
|
||||
<req_grade __type="s32">400000</req_grade>
|
||||
<seq_list>
|
||||
<tune tune_no="0">
|
||||
<index __type="s32">105</index>
|
||||
<difficulty __type="s32">1</difficulty>
|
||||
<clear_point __type="s32">40000</clear_point>
|
||||
</tune>
|
||||
<tune tune_no="1">
|
||||
<index __type="s32">101</index>
|
||||
<difficulty __type="s32">2</difficulty>
|
||||
<clear_point __type="s32">84000</clear_point>
|
||||
</tune>
|
||||
<tune tune_no="2">
|
||||
<index __type="s32">186</index>
|
||||
<difficulty __type="s32">2</difficulty>
|
||||
<clear_point __type="s32">132000</clear_point>
|
||||
</tune>
|
||||
</seq_list>
|
||||
<unlock_music __type="s32">0</unlock_music>
|
||||
<unlock_item_list />
|
||||
<start_date __type="str">2019-01-01 10:00</start_date>
|
||||
<end_date __type="str">9999-01-01 00:00</end_date>
|
||||
</course_data>
|
||||
<course_data index="16">
|
||||
<name __type="str">リサイタル5級検定</name>
|
||||
<priority __type="s32">25</priority>
|
||||
<req_nos __type="s32">3500</req_nos>
|
||||
<mode_type __type="s32">2</mode_type>
|
||||
<req_grade __type="s32">450000</req_grade>
|
||||
<seq_list>
|
||||
<tune tune_no="0">
|
||||
<index __type="s32">26</index>
|
||||
<difficulty __type="s32">2</difficulty>
|
||||
<clear_point __type="s32">40000</clear_point>
|
||||
</tune>
|
||||
<tune tune_no="1">
|
||||
<index __type="s32">160</index>
|
||||
<difficulty __type="s32">1</difficulty>
|
||||
<clear_point __type="s32">84000</clear_point>
|
||||
</tune>
|
||||
<tune tune_no="2">
|
||||
<index __type="s32">133</index>
|
||||
<difficulty __type="s32">2</difficulty>
|
||||
<clear_point __type="s32">132000</clear_point>
|
||||
</tune>
|
||||
</seq_list>
|
||||
<unlock_music __type="s32">0</unlock_music>
|
||||
<unlock_item_list />
|
||||
<start_date __type="str">2019-01-01 10:00</start_date>
|
||||
<end_date __type="str">9999-01-01 00:00</end_date>
|
||||
</course_data>
|
||||
<course_data index="17">
|
||||
<name __type="str">リサイタル4級検定</name>
|
||||
<priority __type="s32">24</priority>
|
||||
<req_nos __type="s32">4000</req_nos>
|
||||
<mode_type __type="s32">2</mode_type>
|
||||
<req_grade __type="s32">500000</req_grade>
|
||||
<seq_list>
|
||||
<tune tune_no="0">
|
||||
<index __type="s32">17</index>
|
||||
<difficulty __type="s32">0</difficulty>
|
||||
<clear_point __type="s32">40000</clear_point>
|
||||
</tune>
|
||||
<tune tune_no="1">
|
||||
<index __type="s32">38</index>
|
||||
<difficulty __type="s32">1</difficulty>
|
||||
<clear_point __type="s32">84000</clear_point>
|
||||
</tune>
|
||||
<tune tune_no="2">
|
||||
<index __type="s32">59</index>
|
||||
<difficulty __type="s32">2</difficulty>
|
||||
<clear_point __type="s32">132000</clear_point>
|
||||
</tune>
|
||||
</seq_list>
|
||||
<unlock_music __type="s32">0</unlock_music>
|
||||
<unlock_item_list />
|
||||
<start_date __type="str">2020-01-21 10:00</start_date>
|
||||
<end_date __type="str">9999-01-01 00:00</end_date>
|
||||
</course_data>
|
||||
<course_data index="18">
|
||||
<name __type="str">リサイタル3級検定</name>
|
||||
<priority __type="s32">23</priority>
|
||||
<req_nos __type="s32">4500</req_nos>
|
||||
<mode_type __type="s32">2</mode_type>
|
||||
<req_grade __type="s32">525000</req_grade>
|
||||
<seq_list>
|
||||
<tune tune_no="0">
|
||||
<index __type="s32">18</index>
|
||||
<difficulty __type="s32">1</difficulty>
|
||||
<clear_point __type="s32">40000</clear_point>
|
||||
</tune>
|
||||
<tune tune_no="1">
|
||||
<index __type="s32">39</index>
|
||||
<difficulty __type="s32">2</difficulty>
|
||||
<clear_point __type="s32">84000</clear_point>
|
||||
</tune>
|
||||
<tune tune_no="2">
|
||||
<index __type="s32">60</index>
|
||||
<difficulty __type="s32">2</difficulty>
|
||||
<clear_point __type="s32">132000</clear_point>
|
||||
</tune>
|
||||
</seq_list>
|
||||
<unlock_music __type="s32">0</unlock_music>
|
||||
<unlock_item_list />
|
||||
<start_date __type="str">2020-01-21 10:00</start_date>
|
||||
<end_date __type="str">9999-01-01 00:00</end_date>
|
||||
</course_data>
|
||||
<course_data index="19">
|
||||
<name __type="str">リサイタル2級検定</name>
|
||||
<priority __type="s32">22</priority>
|
||||
<req_nos __type="s32">5000</req_nos>
|
||||
<mode_type __type="s32">2</mode_type>
|
||||
<req_grade __type="s32">540000</req_grade>
|
||||
<seq_list>
|
||||
<tune tune_no="0">
|
||||
<index __type="s32">19</index>
|
||||
<difficulty __type="s32">2</difficulty>
|
||||
<clear_point __type="s32">40000</clear_point>
|
||||
</tune>
|
||||
<tune tune_no="1">
|
||||
<index __type="s32">40</index>
|
||||
<difficulty __type="s32">2</difficulty>
|
||||
<clear_point __type="s32">84000</clear_point>
|
||||
</tune>
|
||||
<tune tune_no="2">
|
||||
<index __type="s32">61</index>
|
||||
<difficulty __type="s32">0</difficulty>
|
||||
<clear_point __type="s32">132000</clear_point>
|
||||
</tune>
|
||||
</seq_list>
|
||||
<unlock_music __type="s32">0</unlock_music>
|
||||
<unlock_item_list />
|
||||
<start_date __type="str">2020-01-21 10:00</start_date>
|
||||
<end_date __type="str">9999-01-01 00:00</end_date>
|
||||
</course_data>
|
||||
<course_data index="20">
|
||||
<name __type="str">リサイタル1級検定</name>
|
||||
<priority __type="s32">21</priority>
|
||||
<req_nos __type="s32">6000</req_nos>
|
||||
<mode_type __type="s32">2</mode_type>
|
||||
<req_grade __type="s32">550000</req_grade>
|
||||
<seq_list>
|
||||
<tune tune_no="0">
|
||||
<index __type="s32">20</index>
|
||||
<difficulty __type="s32">2</difficulty>
|
||||
<clear_point __type="s32">40000</clear_point>
|
||||
</tune>
|
||||
<tune tune_no="1">
|
||||
<index __type="s32">41</index>
|
||||
<difficulty __type="s32">0</difficulty>
|
||||
<clear_point __type="s32">84000</clear_point>
|
||||
</tune>
|
||||
<tune tune_no="2">
|
||||
<index __type="s32">62</index>
|
||||
<difficulty __type="s32">1</difficulty>
|
||||
<clear_point __type="s32">132000</clear_point>
|
||||
</tune>
|
||||
</seq_list>
|
||||
<unlock_music __type="s32">0</unlock_music>
|
||||
<unlock_item_list />
|
||||
<start_date __type="str">2020-01-21 10:00</start_date>
|
||||
<end_date __type="str">9999-01-01 00:00</end_date>
|
||||
</course_data>
|
||||
<course_data index="21">
|
||||
<name __type="str">KACスペシャル検定(Normal)</name>
|
||||
<priority __type="s32">60</priority>
|
||||
<req_nos __type="s32">2000</req_nos>
|
||||
<mode_type __type="s32">1</mode_type>
|
||||
<req_grade __type="s32">0</req_grade>
|
||||
<seq_list>
|
||||
<tune tune_no="0">
|
||||
<index __type="s32">261</index>
|
||||
<difficulty __type="s32">0</difficulty>
|
||||
<clear_point __type="s32">900000</clear_point>
|
||||
</tune>
|
||||
<tune tune_no="1">
|
||||
<index __type="s32">262</index>
|
||||
<difficulty __type="s32">0</difficulty>
|
||||
<clear_point __type="s32">1800000</clear_point>
|
||||
</tune>
|
||||
<tune tune_no="2">
|
||||
<index __type="s32">263</index>
|
||||
<difficulty __type="s32">0</difficulty>
|
||||
<clear_point __type="s32">2750000</clear_point>
|
||||
</tune>
|
||||
</seq_list>
|
||||
<unlock_music __type="s32">263</unlock_music>
|
||||
<unlock_item_list>
|
||||
<unlock_item unlock_item_no="0">
|
||||
<type __type="s32">1</type>
|
||||
<index __type="s32">263</index>
|
||||
<unlock_condition __type="s32">2</unlock_condition>
|
||||
</unlock_item>
|
||||
</unlock_item_list>
|
||||
<start_date __type="str">2019-02-28 10:00</start_date>
|
||||
<end_date __type="str">9999-01-01 00:00</end_date>
|
||||
</course_data>
|
||||
<course_data index="22">
|
||||
<name __type="str">KACスペシャル検定(Hard)</name>
|
||||
<priority __type="s32">59</priority>
|
||||
<req_nos __type="s32">2000</req_nos>
|
||||
<mode_type __type="s32">1</mode_type>
|
||||
<req_grade __type="s32">0</req_grade>
|
||||
<seq_list>
|
||||
<tune tune_no="0">
|
||||
<index __type="s32">261</index>
|
||||
<difficulty __type="s32">1</difficulty>
|
||||
<clear_point __type="s32">925000</clear_point>
|
||||
</tune>
|
||||
<tune tune_no="1">
|
||||
<index __type="s32">262</index>
|
||||
<difficulty __type="s32">1</difficulty>
|
||||
<clear_point __type="s32">1875000</clear_point>
|
||||
</tune>
|
||||
<tune tune_no="2">
|
||||
<index __type="s32">263</index>
|
||||
<difficulty __type="s32">1</difficulty>
|
||||
<clear_point __type="s32">2850000</clear_point>
|
||||
</tune>
|
||||
</seq_list>
|
||||
<unlock_music __type="s32">263</unlock_music>
|
||||
<unlock_item_list>
|
||||
<unlock_item unlock_item_no="0">
|
||||
<type __type="s32">1</type>
|
||||
<index __type="s32">263</index>
|
||||
<unlock_condition __type="s32">2</unlock_condition>
|
||||
</unlock_item>
|
||||
</unlock_item_list>
|
||||
<start_date __type="str">2019-02-28 10:00</start_date>
|
||||
<end_date __type="str">9999-01-01 00:00</end_date>
|
||||
</course_data>
|
||||
<course_data index="23">
|
||||
<name __type="str">KACスペシャル検定(Expert)</name>
|
||||
<priority __type="s32">58</priority>
|
||||
<req_nos __type="s32">2000</req_nos>
|
||||
<mode_type __type="s32">1</mode_type>
|
||||
<req_grade __type="s32">0</req_grade>
|
||||
<seq_list>
|
||||
<tune tune_no="0">
|
||||
<index __type="s32">261</index>
|
||||
<difficulty __type="s32">2</difficulty>
|
||||
<clear_point __type="s32">925000</clear_point>
|
||||
</tune>
|
||||
<tune tune_no="1">
|
||||
<index __type="s32">262</index>
|
||||
<difficulty __type="s32">2</difficulty>
|
||||
<clear_point __type="s32">1875000</clear_point>
|
||||
</tune>
|
||||
<tune tune_no="2">
|
||||
<index __type="s32">263</index>
|
||||
<difficulty __type="s32">2</difficulty>
|
||||
<clear_point __type="s32">2850000</clear_point>
|
||||
</tune>
|
||||
</seq_list>
|
||||
<unlock_music __type="s32">263</unlock_music>
|
||||
<unlock_item_list>
|
||||
<unlock_item unlock_item_no="0">
|
||||
<type __type="s32">1</type>
|
||||
<index __type="s32">263</index>
|
||||
<unlock_condition __type="s32">2</unlock_condition>
|
||||
</unlock_item>
|
||||
</unlock_item_list>
|
||||
<start_date __type="str">2019-02-28 10:00</start_date>
|
||||
<end_date __type="str">9999-01-01 00:00</end_date>
|
||||
</course_data>
|
||||
<course_data index="24">
|
||||
<name __type="str">KACスペシャル検定(Real)</name>
|
||||
<priority __type="s32">57</priority>
|
||||
<req_nos __type="s32">2000</req_nos>
|
||||
<mode_type __type="s32">1</mode_type>
|
||||
<req_grade __type="s32">0</req_grade>
|
||||
<seq_list>
|
||||
<tune tune_no="0">
|
||||
<index __type="s32">261</index>
|
||||
<difficulty __type="s32">3</difficulty>
|
||||
<clear_point __type="s32">925000</clear_point>
|
||||
</tune>
|
||||
<tune tune_no="1">
|
||||
<index __type="s32">262</index>
|
||||
<difficulty __type="s32">3</difficulty>
|
||||
<clear_point __type="s32">1875000</clear_point>
|
||||
</tune>
|
||||
<tune tune_no="2">
|
||||
<index __type="s32">263</index>
|
||||
<difficulty __type="s32">3</difficulty>
|
||||
<clear_point __type="s32">2850000</clear_point>
|
||||
</tune>
|
||||
</seq_list>
|
||||
<unlock_music __type="s32">263</unlock_music>
|
||||
<unlock_item_list>
|
||||
<unlock_item unlock_item_no="0">
|
||||
<type __type="s32">1</type>
|
||||
<index __type="s32">263</index>
|
||||
<unlock_condition __type="s32">2</unlock_condition>
|
||||
</unlock_item>
|
||||
</unlock_item_list>
|
||||
<start_date __type="str">2019-02-28 10:00</start_date>
|
||||
<end_date __type="str">9999-01-01 00:00</end_date>
|
||||
</course_data>
|
||||
</course_data_list>
|
||||
1
nostalgia@asphyxia/data/first_mdb.json.b64
Normal file
1
nostalgia@asphyxia/data/first_mdb.json.b64
Normal file
File diff suppressed because one or more lines are too long
1
nostalgia@asphyxia/data/forte_mdb.json.b64
Normal file
1
nostalgia@asphyxia/data/forte_mdb.json.b64
Normal file
File diff suppressed because one or more lines are too long
43
nostalgia@asphyxia/data/helper.ts
Normal file
43
nostalgia@asphyxia/data/helper.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
export interface CommonMusicDataField {
|
||||
basename: KITEM<"str">;
|
||||
title: KITEM<"str">;
|
||||
title_kana: KITEM<"str">;
|
||||
artist: KITEM<"str">;
|
||||
artist_kana: KITEM<"str">
|
||||
priority: KITEM<"s8">;
|
||||
category_flag: KARRAY<"s32">;
|
||||
primary_category: KITEM<"s8">;
|
||||
level_normal: KITEM<"s8">;
|
||||
level_hard: KITEM<"s8">;
|
||||
level_extreme: KITEM<"s8">;
|
||||
demo_popular: KITEM<"bool">;
|
||||
demo_bemani: KITEM<"bool">
|
||||
destination_j: KITEM<"bool">;
|
||||
destination_a: KITEM<"bool">;
|
||||
destination_y: KITEM<"bool">;
|
||||
destination_k: KITEM<"bool">;
|
||||
unlock_type: KITEM<"s8">;
|
||||
offline: KITEM<"bool">;
|
||||
volume_bgm: KITEM<"s8">;
|
||||
volume_key: KITEM<"s8">;
|
||||
start_date: KITEM<"str">;
|
||||
end_date: KITEM<"str">;
|
||||
description: KITEM<"str">;
|
||||
}
|
||||
|
||||
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 readB64JSON(b64path: string) {
|
||||
const buff = await IO.ReadFile(b64path, 'utf-8');
|
||||
return JSON.parse(Buffer.from(buff, 'base64').toString('utf-8'));
|
||||
}
|
||||
1
nostalgia@asphyxia/data/island.json.b64
Normal file
1
nostalgia@asphyxia/data/island.json.b64
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
|
|
@ -1,4 +1,8 @@
|
|||
import * as path from "path";
|
||||
|
||||
import { processData as firstData } from "../data/FirstMusic";
|
||||
import { processData as forteData } from "../data/ForteMusic";
|
||||
import { readB64JSON } from "../data/helper";
|
||||
import { NosVersionHelper } from "../utils";
|
||||
|
||||
export const permitted_list = {
|
||||
flag: [
|
||||
|
|
@ -9,10 +13,22 @@ export const permitted_list = {
|
|||
],
|
||||
};
|
||||
|
||||
export const forte_permitted_list = {
|
||||
flag: [
|
||||
K.ARRAY('s32', Array(32).fill(-1), { sheet_type: '0' }),
|
||||
K.ARRAY('s32', Array(32).fill(-1), { sheet_type: '1' }),
|
||||
K.ARRAY('s32', Array(32).fill(-1), { sheet_type: '2' }),
|
||||
],
|
||||
}
|
||||
|
||||
async function ReadData(filename: string) {
|
||||
const xml = await IO.ReadFile(`data/${filename}.xml`, { encoding: 'utf-8'});
|
||||
if (!IO.Exists(`data/${filename}.json.b64`)) {
|
||||
const xml = await IO.ReadFile(`data/${filename}.xml`, 'utf-8');
|
||||
const json = U.parseXML(xml, false)
|
||||
// await IO.WriteFile(`data/${filename}.json.b64`, Buffer.from(JSON.stringify(json)).toString('base64'));
|
||||
return json
|
||||
}
|
||||
return readB64JSON(`data/${filename}.json.b64`)
|
||||
}
|
||||
|
||||
async function processIslandData() {
|
||||
|
|
@ -56,7 +72,7 @@ async function processCourseData() {
|
|||
return { course_data: courseData };
|
||||
}
|
||||
|
||||
export const get_common_info = async (req, data, send) => {
|
||||
export const get_common_info = async (info, data, send) => {
|
||||
send.object({
|
||||
permitted_list,
|
||||
olupdate: {
|
||||
|
|
@ -65,10 +81,12 @@ export const get_common_info = async (req, data, send) => {
|
|||
});
|
||||
};
|
||||
|
||||
export const get_music_info: EPR = async (req, data, send) => {
|
||||
export const get_music_info: EPR = async (info, data, send) => {
|
||||
const version = new NosVersionHelper(info)
|
||||
|
||||
const music_spec: any = [];
|
||||
for (let i = 1; i < 400; ++i) {
|
||||
music_spec.push(K.ATTR({ index: `${i}`}, {
|
||||
music_spec.push(K.ATTR({ index: `${i}` }, {
|
||||
jk_jpn: K.ITEM('bool', 1),
|
||||
jk_asia: K.ITEM('bool', 1),
|
||||
jk_kor: K.ITEM('bool', 1),
|
||||
|
|
@ -83,18 +101,27 @@ export const get_music_info: EPR = async (req, data, send) => {
|
|||
}));
|
||||
}
|
||||
|
||||
send.object({
|
||||
permitted_list,
|
||||
const music_list = async () => version.version === 'Forte' ? await forteData() : await firstData()
|
||||
|
||||
island_data_list: await processIslandData(),
|
||||
course_data_list: await processCourseData(),
|
||||
const versionObject = version.isFirstOrForte()
|
||||
? {
|
||||
permitted_list: forte_permitted_list,
|
||||
music_list: await music_list()
|
||||
}
|
||||
: {
|
||||
permitted_list,
|
||||
island_data_list: await processIslandData(),
|
||||
course_data_list: await processCourseData(),
|
||||
|
||||
overwrite_music_list: K.ATTR({
|
||||
overwrite_music_list: K.ATTR({
|
||||
revision: '16706',
|
||||
release_code: '2019100200',
|
||||
}, {
|
||||
music_spec: music_spec,
|
||||
}),
|
||||
music_spec: music_spec,
|
||||
}),
|
||||
};
|
||||
send.object({
|
||||
...versionObject,
|
||||
|
||||
gamedata_flag_list: {
|
||||
event: {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,7 @@
|
|||
// import { EAHandler } from '../../util/EAHandler';
|
||||
// import { get, _.isArray } from 'lodash';
|
||||
// import { Logger } from '../../util/Logger';
|
||||
import { Profile } from '../models/profile';
|
||||
import { Scores } from '../models/scores';
|
||||
import { permitted_list } from './common';
|
||||
// import { getValue, getArray, getAttr, getStr, getBigInt } from '../../util/Helper';
|
||||
|
||||
import { NosVersionHelper } from '../utils';
|
||||
import { permitted_list, forte_permitted_list } from './common';
|
||||
|
||||
// export const event_list = {
|
||||
// event: {
|
||||
|
|
@ -18,9 +14,10 @@ import { permitted_list } from './common';
|
|||
// },
|
||||
// };
|
||||
|
||||
const getEventInfo = () => {
|
||||
const getEventInfo = (version: NosVersionHelper) => {
|
||||
const event: any[] = [];
|
||||
for (let i = 1; i <= 17; ++i) {
|
||||
const event_num = version.getEventMaxIndex()
|
||||
for (let i = 1; i <= event_num; ++i) {
|
||||
event.push({
|
||||
type: K.ITEM('s32', 4),
|
||||
index: K.ITEM('s32', i),
|
||||
|
|
@ -34,8 +31,9 @@ const getEventInfo = () => {
|
|||
return event;
|
||||
};
|
||||
|
||||
const getPlayerData = async (refid: string, name?: string) => {
|
||||
const getPlayerData = async (refid: string, info: EamuseInfo, name?: string) => {
|
||||
const p = await readProfile(refid);
|
||||
const version = new NosVersionHelper(info)
|
||||
|
||||
if (name && name.length > 0) {
|
||||
p.name = name;
|
||||
|
|
@ -45,7 +43,7 @@ const getPlayerData = async (refid: string, name?: string) => {
|
|||
const param: any[] = [];
|
||||
for (const t in p.params) {
|
||||
const para = p.params[t];
|
||||
param.push(K.ATTR({type: t}, {
|
||||
param.push(K.ATTR({ type: t }, {
|
||||
count: K.ITEM('s32', para.length),
|
||||
params_array: K.ARRAY('s32', para),
|
||||
}));
|
||||
|
|
@ -53,6 +51,7 @@ const getPlayerData = async (refid: string, name?: string) => {
|
|||
|
||||
const brooch: any[] = [];
|
||||
for (const b in p.brooches) {
|
||||
if (parseInt(b, 10) > version.getBroochMaxIndex()) continue;
|
||||
const bData = p.brooches[b];
|
||||
brooch.push(K.ATTR({ index: b }, {
|
||||
watch_count: K.ITEM('s32', bData.watch),
|
||||
|
|
@ -64,6 +63,7 @@ const getPlayerData = async (refid: string, name?: string) => {
|
|||
|
||||
// Unlock brooches
|
||||
for (let i = 101; i <= 124; ++i) {
|
||||
if (i > version.getBroochMaxIndex()) continue;
|
||||
brooch.push(K.ATTR({ index: `${i}` }, {
|
||||
'watch_count': K.ITEM('s32', 0),
|
||||
'level': K.ITEM('s8', 1),
|
||||
|
|
@ -72,6 +72,20 @@ const getPlayerData = async (refid: string, name?: string) => {
|
|||
}));
|
||||
}
|
||||
|
||||
// Forte
|
||||
const stairs: any[] = [];
|
||||
for (const s in (p.cat_stairs || defaultProfile.cat_stairs)) {
|
||||
const stair = (p.cat_stairs || defaultProfile.cat_stairs)[s];
|
||||
|
||||
stairs.push(K.ATTR({ index: s }, {
|
||||
total_steps: K.ITEM("s32", stair.total),
|
||||
chapter_index: K.ITEM("s32", stair.index),
|
||||
chapter_steps: K.ITEM("s32", stair.steps),
|
||||
chapter_goal: K.ITEM("s32", stair.goal)
|
||||
}));
|
||||
}
|
||||
|
||||
// >= Op2
|
||||
const kentei_record: any[] = [];
|
||||
for (const k in p.kentei) {
|
||||
const kentei = p.kentei[k];
|
||||
|
|
@ -86,6 +100,7 @@ const getPlayerData = async (refid: string, name?: string) => {
|
|||
});
|
||||
}
|
||||
|
||||
// >= Op2
|
||||
const island_progress: any[] = [];
|
||||
for (const i in p.islands) {
|
||||
const island = p.islands[i];
|
||||
|
|
@ -119,40 +134,50 @@ const getPlayerData = async (refid: string, name?: string) => {
|
|||
}));
|
||||
}
|
||||
|
||||
const correct_permitted_list = !version.isFirstOrForte() ? permitted_list : forte_permitted_list
|
||||
const music_list = [
|
||||
K.ARRAY('s32', p.musicList.type_0, { sheet_type: '0' }),
|
||||
K.ARRAY('s32', p.musicList.type_1, { sheet_type: '1' }),
|
||||
K.ARRAY('s32', p.musicList.type_2, { sheet_type: '2' }),
|
||||
K.ARRAY('s32', p.musicList.type_3, { sheet_type: '3' }),
|
||||
];
|
||||
const music_list2 = [
|
||||
K.ARRAY('s32', p.musicList2.type_0, { sheet_type: '0' }),
|
||||
K.ARRAY('s32', p.musicList2.type_1, { sheet_type: '1' }),
|
||||
K.ARRAY('s32', p.musicList2.type_2, { sheet_type: '2' }),
|
||||
K.ARRAY('s32', p.musicList2.type_3, { sheet_type: '3' }),
|
||||
];
|
||||
|
||||
if(version.isFirstOrForte()) {
|
||||
music_list.pop();
|
||||
music_list2.pop();
|
||||
}
|
||||
|
||||
return {
|
||||
name: K.ITEM('str', p.name),
|
||||
play_count: K.ITEM('s32', p.playCount),
|
||||
today_play_count: K.ITEM('s32', p.todayPlayCount),
|
||||
permitted_list,
|
||||
event_info_list: { event: getEventInfo() },
|
||||
permitted_list: correct_permitted_list,
|
||||
event_info_list: { event: getEventInfo(version) }, // Op2
|
||||
event_control_list: { event: getEventInfo(version) }, // Forte
|
||||
music_list: {
|
||||
flag: [
|
||||
K.ARRAY('s32', p.musicList.type_0, { sheet_type: '0' }),
|
||||
K.ARRAY('s32', p.musicList.type_1, { sheet_type: '1' }),
|
||||
K.ARRAY('s32', p.musicList.type_2, { sheet_type: '2' }),
|
||||
K.ARRAY('s32', p.musicList.type_3, { sheet_type: '3' }),
|
||||
],
|
||||
flag: music_list,
|
||||
},
|
||||
free_for_play_music_list: {
|
||||
flag: [
|
||||
K.ARRAY('s32', p.musicList2.type_0, { sheet_type: '0' }),
|
||||
K.ARRAY('s32', p.musicList2.type_1, { sheet_type: '1' }),
|
||||
K.ARRAY('s32', p.musicList2.type_2, { sheet_type: '2' }),
|
||||
K.ARRAY('s32', p.musicList2.type_3, { sheet_type: '3' }),
|
||||
],
|
||||
flag: music_list2,
|
||||
},
|
||||
last: {
|
||||
music_index: K.ITEM('s32', p.music),
|
||||
sheet_type: K.ITEM('s8', p.sheet),
|
||||
brooch_index: K.ITEM('s32', p.brooch),
|
||||
music_index: K.ITEM('s32', version.numericHandler('music_index', p.music, 0)),
|
||||
sheet_type: K.ITEM('s8', version.numericHandler('sheet_type', p.sheet, 0)),
|
||||
brooch_index: K.ITEM('s32', version.numericHandler('brooch_index', p.brooch, 0)),
|
||||
hi_speed_level: K.ITEM('s32', p.hispeed),
|
||||
beat_guide: K.ITEM('s8', p.beatGuide),
|
||||
headphone_volume: K.ITEM('s8', p.headphone),
|
||||
judge_bar_pos: K.ITEM('s32', p.judgeBar),
|
||||
music_group: K.ITEM('s32', p.group),
|
||||
hands_mode: K.ITEM('s8', p.mode),
|
||||
hands_mode: version.isFirstOrForte() ? K.ITEM('s32', p.mode) : K.ITEM('s8', p.mode),
|
||||
near_setting: K.ITEM('s8', p.near),
|
||||
judge_delay_offset: K.ITEM('s8', p.offset),
|
||||
judge_delay_offset: version.isFirstOrForte() ? K.ITEM('s32', p.offset) : K.ITEM('s8', p.offset),
|
||||
bingo_index: K.ITEM('s32', p.bingo),
|
||||
total_skill_value: K.ITEM('u64', BigInt(p.skill)),
|
||||
key_beam_level: K.ITEM('s8', p.keyBeam),
|
||||
|
|
@ -169,14 +194,16 @@ const getPlayerData = async (refid: string, name?: string) => {
|
|||
judge_effect_adjust: K.ITEM('s8', p.judgeFX),
|
||||
simple_bg: K.ITEM('s8', p.simple),
|
||||
},
|
||||
brooch_list: {
|
||||
brooch,
|
||||
// TODO: Full unlock instead of saving?
|
||||
cat_progress: {
|
||||
stair: stairs
|
||||
},
|
||||
brooch_list: { brooch },
|
||||
extra_param: { param },
|
||||
present_list: {},
|
||||
various_music_list: {
|
||||
data: [
|
||||
K.ATTR({ list_type: '0' }, {
|
||||
K.ATTR({ list_type: '0' }, {
|
||||
cond_flag: K.ITEM('s32', 0),
|
||||
flag: K.ITEM('s32', 0, { sheet_type: '0' }),
|
||||
}),
|
||||
|
|
@ -202,14 +229,14 @@ export const regist_playdata: EPR = async (info, data, send) => {
|
|||
const name = $(data).str('name');
|
||||
console.debug(`nos op2 regist: ${name}`);
|
||||
|
||||
send.object(await getPlayerData(refid, name));
|
||||
send.object(await getPlayerData(refid, info, name));
|
||||
};
|
||||
|
||||
export const get_playdata: EPR = async (info, data, send) => {
|
||||
const refid = $(data).str('refid');
|
||||
if (!refid) return send.deny();
|
||||
|
||||
send.object(await getPlayerData(refid));
|
||||
send.object(await getPlayerData(refid, info));
|
||||
};
|
||||
|
||||
// export const set_stage_result: EPR = async (info, data, send) => {
|
||||
|
|
@ -220,6 +247,7 @@ export const set_total_result: EPR = async (info, data, send) => {
|
|||
const refid = $(data).str('refid');
|
||||
if (!refid) return send.deny();
|
||||
|
||||
const isForte = new NosVersionHelper(info).isFirstOrForte()
|
||||
const p = await readProfile(refid);
|
||||
|
||||
p.playCount = $(data).number('play_count', p.playCount);
|
||||
|
|
@ -321,7 +349,7 @@ export const set_total_result: EPR = async (info, data, send) => {
|
|||
// BROOCHES
|
||||
let broochs = $(data).elements('brooch_list.brooch');
|
||||
for (const brooch of broochs) {
|
||||
const index = parseInt(_.get(brooch, '@attr.index', '-1'));
|
||||
const index = parseInt(brooch.attr().index || '-1', 10);
|
||||
if (index < 0) continue;
|
||||
|
||||
p.brooches[index] = {
|
||||
|
|
@ -335,7 +363,7 @@ export const set_total_result: EPR = async (info, data, send) => {
|
|||
// ISLAND
|
||||
let islands = $(data).elements('island_progress_list.island_progress');
|
||||
for (const island of islands) {
|
||||
const index = parseInt(_.get(island, '@attr.index', '-1'));
|
||||
const index = parseInt(island.attr().index || '-1', 10);
|
||||
if (index < 0) continue;
|
||||
|
||||
const containers: Profile['islands']['0']['containers'] = {};
|
||||
|
|
@ -366,6 +394,23 @@ export const set_total_result: EPR = async (info, data, send) => {
|
|||
};
|
||||
}
|
||||
|
||||
// CAT STAIR
|
||||
let stairs = $(data).elements('cat_progress.stair');
|
||||
if (!p.cat_stairs) {
|
||||
p.cat_stairs = defaultProfile.cat_stairs
|
||||
}
|
||||
for (const stair of stairs) {
|
||||
const index = parseInt(stair.attr().index || '-1', 10);
|
||||
if (index < 0) continue;
|
||||
|
||||
p.cat_stairs[index] = {
|
||||
total: stair.number('total_steps', 0),
|
||||
index: stair.number('chapter_index', 1),
|
||||
steps: stair.number('chapter_steps', 0),
|
||||
goal: stair.number('chapter_goal', 0),
|
||||
};
|
||||
}
|
||||
|
||||
await writeProfile(refid, p);
|
||||
|
||||
const scoreData = await readScores(refid);
|
||||
|
|
@ -376,12 +421,12 @@ export const set_total_result: EPR = async (info, data, send) => {
|
|||
const type = stage.attr().sheet_type
|
||||
|
||||
const key = `${mid}:${type}`;
|
||||
const c = stage.element('common');
|
||||
const c = isForte ? stage : stage.element('common');
|
||||
const o = _.get(scoreData, `scores.${key}`, {});
|
||||
const isHigh = c.number('score', 0) >= _.get(o, 'score', 0);
|
||||
scoreData.scores[key] = {
|
||||
score: Math.max(c.number('score', 0), _.get(o, 'score', 0)),
|
||||
grade: Math.max(c.number('grade_basic', 0), _.get(o, 'grade', 0)),
|
||||
grade: Math.max(Math.max(c.number('grade_basic', 0), c.number('evaluation', 0)), _.get(o, 'grade', 0)),
|
||||
recital: Math.max(c.number('grade_recital', 0), _.get(o, 'recital', 0)),
|
||||
mode: isHigh ? c.number('hands_mode', 0) : _.get(o, 'mode', 0),
|
||||
count: Math.max(c.number('play_count', 0), _.get(o, 'count', 1)),
|
||||
|
|
@ -393,7 +438,7 @@ export const set_total_result: EPR = async (info, data, send) => {
|
|||
|
||||
// Save Recitals
|
||||
const rInfo = $(data).element('recital_info.recital');
|
||||
if (rInfo){
|
||||
if (rInfo) {
|
||||
const rIndex = rInfo.number('recital_index', -1);
|
||||
if (rIndex >= 0) {
|
||||
const r = rInfo.element('result');
|
||||
|
|
@ -423,6 +468,7 @@ export const get_musicdata: EPR = async (info, data, send) => {
|
|||
const refid = $(data).str('refid');
|
||||
if (!refid) return send.deny();
|
||||
|
||||
const version = new NosVersionHelper(info)
|
||||
const scoreData = await readScores(refid);
|
||||
|
||||
const recital_record: any[] = [];
|
||||
|
|
@ -450,18 +496,21 @@ export const get_musicdata: EPR = async (info, data, send) => {
|
|||
const mdata = m.split(':');
|
||||
const musi = scoreData.scores[m];
|
||||
|
||||
if (parseInt(mdata[0], 10) > version.getMusicMaxIndex()) continue;
|
||||
|
||||
music.push(K.ATTR({
|
||||
music_index: mdata[0],
|
||||
sheet_type: mdata[1],
|
||||
}, {
|
||||
'score': K.ITEM('s32', musi.score),
|
||||
'grade_basic': K.ITEM('u32', musi.grade),
|
||||
'grade_recital': K.ITEM('u32', musi.recital),
|
||||
'play_count': K.ITEM('s32', musi.count),
|
||||
'clear_count': K.ITEM('s32', musi.clear),
|
||||
'multi_count': K.ITEM('s32', musi.multi),
|
||||
'hands_mode': K.ITEM('s8', musi.mode),
|
||||
'clear_flag': K.ITEM('s32', musi.flag),
|
||||
music_index: mdata[0],
|
||||
sheet_type: mdata[1],
|
||||
}, {
|
||||
score: K.ITEM('s32', musi.score),
|
||||
evaluation: K.ITEM('u32', musi.grade), // Forte
|
||||
grade_basic: K.ITEM('u32', musi.grade),
|
||||
grade_recital: K.ITEM('u32', musi.recital),
|
||||
play_count: K.ITEM('s32', musi.count),
|
||||
clear_count: K.ITEM('s32', musi.clear),
|
||||
multi_count: K.ITEM('s32', musi.multi),
|
||||
hands_mode: K.ITEM('s8', musi.mode),
|
||||
clear_flag: K.ITEM('s32', musi.flag),
|
||||
}));
|
||||
}
|
||||
|
||||
|
|
@ -472,82 +521,90 @@ export const get_musicdata: EPR = async (info, data, send) => {
|
|||
};
|
||||
|
||||
async function readProfile(refid: string): Promise<Profile> {
|
||||
const profile = await DB.FindOne<Profile>(refid, { collection: 'profile'} )
|
||||
const profile = await DB.FindOne<Profile>(refid, { collection: 'profile' })
|
||||
return profile || defaultProfile
|
||||
}
|
||||
|
||||
async function writeProfile(refid: string, profile: Profile) {
|
||||
await DB.Upsert<Profile>(refid, { collection: 'profile'}, profile)
|
||||
await DB.Upsert<Profile>(refid, { collection: 'profile' }, profile)
|
||||
}
|
||||
|
||||
async function readScores(refid: string): Promise<Scores> {
|
||||
const score = await DB.FindOne<Scores>(refid, { collection: 'scores'} )
|
||||
return score || { collection: 'scores', recitals: {}, scores: {}}
|
||||
const score = await DB.FindOne<Scores>(refid, { collection: 'scores' })
|
||||
return score || { collection: 'scores', recitals: {}, scores: {} }
|
||||
}
|
||||
|
||||
async function writeScores(refid: string, scores: Scores) {
|
||||
await DB.Upsert<Scores>(refid, { collection: 'scores'}, scores)
|
||||
await DB.Upsert<Scores>(refid, { collection: 'scores' }, scores)
|
||||
}
|
||||
|
||||
const defaultProfile: Profile = {
|
||||
collection: 'profile',
|
||||
collection: 'profile',
|
||||
|
||||
name: 'GUEST',
|
||||
music: 0,
|
||||
sheet: 0,
|
||||
brooch: 0,
|
||||
hispeed: 0,
|
||||
beatGuide: 1,
|
||||
headphone: 0,
|
||||
judgeBar: 250,
|
||||
group: 0,
|
||||
mode: 0,
|
||||
near: 0,
|
||||
offset: 0,
|
||||
bingo: 0,
|
||||
skill: '0',
|
||||
playCount: 0,
|
||||
todayPlayCount: 0,
|
||||
keyBeam: 0,
|
||||
orbit: 0,
|
||||
noteHeight: 10,
|
||||
noteWidth: 0,
|
||||
judgeWidth: 0,
|
||||
beatVolume: 0,
|
||||
beatType: 0,
|
||||
keyVolume: 0,
|
||||
bgmVolume: 0,
|
||||
note: 0,
|
||||
sf: 0,
|
||||
judgeFX: 0,
|
||||
simple: 0,
|
||||
money: 0,
|
||||
fame: 0,
|
||||
fameId: 0,
|
||||
island: 0,
|
||||
brooches: {
|
||||
'1': {
|
||||
level: 1,
|
||||
watch: 0,
|
||||
steps: 0,
|
||||
new: 0,
|
||||
},
|
||||
},
|
||||
islands: {},
|
||||
kentei: {},
|
||||
params: {
|
||||
'1': [0],
|
||||
},
|
||||
musicList: {
|
||||
type_0: Array(32).fill(-1),
|
||||
type_1: Array(32).fill(-1),
|
||||
type_2: Array(32).fill(-1),
|
||||
type_3: Array(32).fill(-1),
|
||||
},
|
||||
musicList2: {
|
||||
type_0: Array(32).fill(-1),
|
||||
type_1: Array(32).fill(-1),
|
||||
type_2: Array(32).fill(-1),
|
||||
type_3: Array(32).fill(-1),
|
||||
name: 'GUEST',
|
||||
music: 0,
|
||||
sheet: 0,
|
||||
brooch: 0,
|
||||
hispeed: 0,
|
||||
beatGuide: 1,
|
||||
headphone: 0,
|
||||
judgeBar: 250,
|
||||
group: 0,
|
||||
mode: 0,
|
||||
near: 0,
|
||||
offset: 0,
|
||||
bingo: 0,
|
||||
skill: '0',
|
||||
playCount: 0,
|
||||
todayPlayCount: 0,
|
||||
keyBeam: 0,
|
||||
orbit: 0,
|
||||
noteHeight: 10,
|
||||
noteWidth: 0,
|
||||
judgeWidth: 0,
|
||||
beatVolume: 0,
|
||||
beatType: 0,
|
||||
keyVolume: 0,
|
||||
bgmVolume: 0,
|
||||
note: 0,
|
||||
sf: 0,
|
||||
judgeFX: 0,
|
||||
simple: 0,
|
||||
money: 0,
|
||||
fame: 0,
|
||||
fameId: 0,
|
||||
island: 0,
|
||||
brooches: {
|
||||
'1': {
|
||||
level: 1,
|
||||
watch: 0,
|
||||
steps: 0,
|
||||
new: 0,
|
||||
},
|
||||
},
|
||||
islands: {},
|
||||
kentei: {},
|
||||
cat_stairs: {
|
||||
'0': {
|
||||
total: 0,
|
||||
index: 0,
|
||||
steps: 0,
|
||||
goal: 0
|
||||
}
|
||||
},
|
||||
params: {
|
||||
'1': [0],
|
||||
},
|
||||
musicList: {
|
||||
type_0: Array(32).fill(-1),
|
||||
type_1: Array(32).fill(-1),
|
||||
type_2: Array(32).fill(-1),
|
||||
type_3: Array(32).fill(-1),
|
||||
},
|
||||
musicList2: {
|
||||
type_0: Array(32).fill(-1),
|
||||
type_1: Array(32).fill(-1),
|
||||
type_2: Array(32).fill(-1),
|
||||
type_3: Array(32).fill(-1),
|
||||
},
|
||||
}
|
||||
|
|
|
|||
20
nostalgia@asphyxia/handler/webui.ts
Normal file
20
nostalgia@asphyxia/handler/webui.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import { Profile } from "../models/profile";
|
||||
|
||||
export const fixIndexBug = async (data: {
|
||||
refid: string;
|
||||
confirm: string;
|
||||
}) => {
|
||||
if (data.confirm == "on") {
|
||||
console.warn(`refid "${data.refid}" performs index reset!`)
|
||||
await DB.Update<Profile>(
|
||||
data.refid,
|
||||
{ collection: 'profile' },
|
||||
{ $set: {
|
||||
music: 0,
|
||||
sheet: 0,
|
||||
brooch: 0
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
@ -1,21 +1,24 @@
|
|||
import { get_common_info, get_music_info } from "./handler/common";
|
||||
import { get_musicdata, get_playdata, regist_playdata, set_total_result} from "./handler/player"
|
||||
import { get_musicdata, get_playdata, regist_playdata, set_total_result } from "./handler/player"
|
||||
import { fixIndexBug } from "./handler/webui";
|
||||
|
||||
export function register() {
|
||||
R.GameCode('PAN');
|
||||
|
||||
|
||||
R.WebUIEvent("nosFixIndexBug", fixIndexBug)
|
||||
|
||||
const MultiRoute = (method: string, handler: EPR | boolean) => {
|
||||
// Helper for register multiple versions.
|
||||
// But.. only Opus 2 for now.
|
||||
R.Route(`op2_${method}`, handler);
|
||||
// Helper for register multiple versions.
|
||||
R.Route(method, handler); // First version and Forte.
|
||||
R.Route(`op2_${method}`, handler);
|
||||
};
|
||||
|
||||
const CommonRoute = (method: string, handler: EPR | boolean) =>
|
||||
const CommonRoute = (method: string, handler: EPR | boolean) =>
|
||||
MultiRoute(`common.${method}`, handler)
|
||||
|
||||
const PlayerRoute = (method: string, handler: EPR | boolean) =>
|
||||
const PlayerRoute = (method: string, handler: EPR | boolean) =>
|
||||
MultiRoute(`player.${method}`, handler)
|
||||
|
||||
|
||||
// Common
|
||||
CommonRoute('get_common_info', get_common_info);
|
||||
CommonRoute('get_music_info', get_music_info);
|
||||
|
|
@ -25,4 +28,13 @@ export function register() {
|
|||
PlayerRoute('get_playdata', get_playdata)
|
||||
PlayerRoute('regist_playdata', regist_playdata)
|
||||
PlayerRoute('set_total_result', set_total_result)
|
||||
|
||||
//TODO: Fix this things with actual working handler.
|
||||
PlayerRoute('set_stage_result', true)
|
||||
|
||||
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)}`)
|
||||
})
|
||||
}
|
||||
|
|
@ -1,82 +1,90 @@
|
|||
export interface Profile {
|
||||
collection: 'profile',
|
||||
|
||||
name: string;
|
||||
playCount: number;
|
||||
todayPlayCount: number;
|
||||
music: number;
|
||||
sheet: number;
|
||||
brooch: number;
|
||||
hispeed: number;
|
||||
beatGuide: number;
|
||||
headphone: number;
|
||||
judgeBar: number;
|
||||
group: number;
|
||||
mode: number;
|
||||
near: number;
|
||||
offset: number;
|
||||
bingo: number;
|
||||
skill: string;
|
||||
keyBeam: number;
|
||||
orbit: number;
|
||||
noteHeight: number;
|
||||
noteWidth: number;
|
||||
judgeWidth: number;
|
||||
beatVolume: number;
|
||||
beatType: number;
|
||||
keyVolume: number;
|
||||
bgmVolume: number;
|
||||
note: number;
|
||||
sf: number;
|
||||
judgeFX: number;
|
||||
simple: number;
|
||||
money: number;
|
||||
fame: number;
|
||||
fameId: number;
|
||||
island: number;
|
||||
params: {
|
||||
[key: string]: number[];
|
||||
collection: 'profile',
|
||||
|
||||
name: string;
|
||||
playCount: number;
|
||||
todayPlayCount: number;
|
||||
music: number;
|
||||
sheet: number;
|
||||
brooch: number;
|
||||
hispeed: number;
|
||||
beatGuide: number;
|
||||
headphone: number;
|
||||
judgeBar: number;
|
||||
group: number;
|
||||
mode: number;
|
||||
near: number;
|
||||
offset: number;
|
||||
bingo: number;
|
||||
skill: string;
|
||||
keyBeam: number;
|
||||
orbit: number;
|
||||
noteHeight: number;
|
||||
noteWidth: number;
|
||||
judgeWidth: number;
|
||||
beatVolume: number;
|
||||
beatType: number;
|
||||
keyVolume: number;
|
||||
bgmVolume: number;
|
||||
note: number;
|
||||
sf: number;
|
||||
judgeFX: number;
|
||||
simple: number;
|
||||
money: number;
|
||||
fame: number;
|
||||
fameId: number;
|
||||
island: number;
|
||||
params: {
|
||||
[key: string]: number[];
|
||||
};
|
||||
brooches: {
|
||||
[key: string]: {
|
||||
watch: number;
|
||||
level: number;
|
||||
steps: number;
|
||||
new: number;
|
||||
};
|
||||
brooches: {
|
||||
[key: string]: {
|
||||
watch: number;
|
||||
level: number;
|
||||
steps: number;
|
||||
new: number;
|
||||
};
|
||||
};
|
||||
islands: {
|
||||
[key: string]: {
|
||||
look: number;
|
||||
select: number;
|
||||
time: number;
|
||||
containers: {
|
||||
[key: string]: {
|
||||
prog: number;
|
||||
rewards: { [key: string]: number };
|
||||
};
|
||||
};
|
||||
islands: {
|
||||
[key: string]: {
|
||||
look: number;
|
||||
select: number;
|
||||
time: number;
|
||||
containers: {
|
||||
[key: string]: {
|
||||
prog: number;
|
||||
rewards: { [key: string]: number };
|
||||
};
|
||||
};
|
||||
};
|
||||
kentei: {
|
||||
[key: string]: {
|
||||
stage: number;
|
||||
score: number[];
|
||||
rate: number;
|
||||
flag: number;
|
||||
count: number;
|
||||
};
|
||||
};
|
||||
kentei: {
|
||||
[key: string]: {
|
||||
stage: number;
|
||||
score: number[];
|
||||
rate: number;
|
||||
flag: number;
|
||||
count: number;
|
||||
};
|
||||
musicList: {
|
||||
type_0: number[];
|
||||
type_1: number[];
|
||||
type_2: number[];
|
||||
type_3: number[];
|
||||
};
|
||||
musicList2: {
|
||||
type_0: number[];
|
||||
type_1: number[];
|
||||
type_2: number[];
|
||||
type_3: number[];
|
||||
};
|
||||
}
|
||||
};
|
||||
cat_stairs: {
|
||||
[key: string]: {
|
||||
total: number,
|
||||
index: number,
|
||||
steps: number,
|
||||
goal: number
|
||||
}
|
||||
};
|
||||
musicList: {
|
||||
type_0: number[];
|
||||
type_1: number[];
|
||||
type_2: number[];
|
||||
type_3: number[];
|
||||
};
|
||||
musicList2: {
|
||||
type_0: number[];
|
||||
type_1: number[];
|
||||
type_2: number[];
|
||||
type_3: number[];
|
||||
};
|
||||
}
|
||||
|
|
@ -1,30 +1,30 @@
|
|||
export interface Scores {
|
||||
collection: 'scores',
|
||||
|
||||
recitals: {
|
||||
[key: string]: {
|
||||
count: number;
|
||||
hall: number;
|
||||
cat: number[];
|
||||
audience: number;
|
||||
money: number;
|
||||
fame: number;
|
||||
player: number;
|
||||
score: number;
|
||||
start: string;
|
||||
end: string;
|
||||
};
|
||||
|
||||
recitals: {
|
||||
[key: string]: {
|
||||
count: number;
|
||||
hall: number;
|
||||
cat: number[];
|
||||
audience: number;
|
||||
money: number;
|
||||
fame: number;
|
||||
player: number;
|
||||
score: number;
|
||||
start: string;
|
||||
end: string;
|
||||
};
|
||||
scores: {
|
||||
[key: string]: {
|
||||
score: number;
|
||||
grade: number;
|
||||
recital: number;
|
||||
count: number;
|
||||
clear: number;
|
||||
multi: number;
|
||||
mode: number;
|
||||
flag: number;
|
||||
};
|
||||
};
|
||||
scores: {
|
||||
[key: string]: {
|
||||
score: number;
|
||||
grade: number;
|
||||
recital: number;
|
||||
count: number;
|
||||
clear: number;
|
||||
multi: number;
|
||||
mode: number;
|
||||
flag: number;
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
47
nostalgia@asphyxia/utils.ts
Normal file
47
nostalgia@asphyxia/utils.ts
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
type NostalgiaVersions = 'First' | 'Forte' | 'Op2' | 'Op3'
|
||||
type NostalgiaNumericTypes = 'music_index' | 'sheet_type' | 'brooch_index' | 'event_index'
|
||||
|
||||
export class NosVersionHelper {
|
||||
public version: NostalgiaVersions
|
||||
|
||||
private table = { // FIXME: All of Op3 values are placeholder
|
||||
music_index: { First: 87, Forte: 195, Op2: 315, Op3: 500 },
|
||||
brooch_index: { First: 120, Forte: 147, Op2: 148, Op3: 200 },
|
||||
sheet_type: { First: 2, Forte: 2, Op2: 3, Op3: 3 },
|
||||
event_index: { First: 10, Forte: 10, Op2: 17, Op3: 20 }
|
||||
}
|
||||
|
||||
constructor (info: EamuseInfo) {
|
||||
const version = parseInt(info.model.trim().substr(10), 10)
|
||||
if (version >= 2020000000) {
|
||||
this.version = 'Op3'
|
||||
} else if (version >= 2019000000) {
|
||||
this.version = 'Op2'
|
||||
} else if (version >= 2018000000) {
|
||||
this.version = 'Forte'
|
||||
} else {
|
||||
this.version = 'First'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
getMusicMaxIndex() {
|
||||
return this.table['music_index'][this.version]
|
||||
}
|
||||
|
||||
getBroochMaxIndex() {
|
||||
return this.table['brooch_index'][this.version]
|
||||
}
|
||||
|
||||
getEventMaxIndex() {
|
||||
return this.table['event_index'][this.version]
|
||||
}
|
||||
|
||||
numericHandler(type: NostalgiaNumericTypes, input: number, def: number = 0) {
|
||||
return input > this.table[type][this.version] ? def : input;
|
||||
}
|
||||
|
||||
isFirstOrForte() {
|
||||
return this.version === 'First' || this.version === 'Forte'
|
||||
}
|
||||
}
|
||||
26
nostalgia@asphyxia/webui/profile_fix_login.pug
Normal file
26
nostalgia@asphyxia/webui/profile_fix_login.pug
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
div
|
||||
.card
|
||||
.card-header
|
||||
p.card-header-title
|
||||
span.icon
|
||||
i.mdi.mdi-account-edit
|
||||
| Fix Error with Login
|
||||
.card-content
|
||||
p If you unable to login after travels of between versions, This may helpful.
|
||||
p Normally, login issue caused you played deleted song last time in previous version that not deleted time.
|
||||
p This page is about reset of last indexes that causing login problem.
|
||||
form(method="post" action="/emit/nosFixIndexBug")
|
||||
.field
|
||||
label.label ID
|
||||
.control
|
||||
input.input(type="text" name="refid", value=refid readonly)
|
||||
.field
|
||||
label.label Are you sure to reset indexes?
|
||||
.control
|
||||
input(type="checkbox" name="confirm")
|
||||
| This is not recoverable. Do this if you have a problem.
|
||||
.field
|
||||
button.button.is-primary(type="submit")
|
||||
span.icon
|
||||
i.mdi.mdi-check
|
||||
span Submit
|
||||
|
|
@ -13,6 +13,7 @@ export const EVENT5 = [
|
|||
'FACTORY\t10',
|
||||
'CONTINUATION',
|
||||
'APPEAL_CARD_GEN_NEW_PRICE',
|
||||
'APPEAL_CARD_UNLOCK\t0,30170914,0,30171014,0,30171116,0,30180201,0,30180607,0,30181206,0,30200326,0,30200611',
|
||||
'FAVORITE_APPEALCARD_MAX\t100',
|
||||
'FAVORITE_MUSIC_MAX\t500',
|
||||
'EVENTDATE_APRILFOOL',
|
||||
|
|
@ -31,6 +32,7 @@ export const EVENT5 = [
|
|||
'CREW_SELECT_ABLE',
|
||||
'PREMIUM_TIME_ENABLE',
|
||||
'OMEGA_ENABLE\t1,2,3,4,5,6,7,8,9',
|
||||
'HEXA_ENABLE\t1,2,3',
|
||||
];
|
||||
|
||||
export const COURSES5 = [
|
||||
|
|
@ -2164,7 +2166,7 @@ export const COURSES5 = [
|
|||
{
|
||||
id: 12,
|
||||
name: 'SKILL ANALYZER 第4回',
|
||||
isNew: 1,
|
||||
isNew: 0,
|
||||
courses: [
|
||||
{
|
||||
id: 1,
|
||||
|
|
@ -2441,31 +2443,645 @@ export const COURSES5 = [
|
|||
},
|
||||
],
|
||||
},
|
||||
// {
|
||||
// id: 12,
|
||||
// type: 0,
|
||||
// name: 'SKILL ANALYZER Level.∞',
|
||||
// level: 12,
|
||||
// nameID: 12,
|
||||
// assist: 0,
|
||||
// tracks: [
|
||||
// {
|
||||
// no: 0,
|
||||
// mid: 1542,
|
||||
// mty: 4,
|
||||
// },
|
||||
// {
|
||||
// no: 1,
|
||||
// mid: 1498,
|
||||
// mty: 4,
|
||||
// },
|
||||
// {
|
||||
// no: 2,
|
||||
// mid: 1462, // 666
|
||||
// mty: 4,
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
{
|
||||
id: 12,
|
||||
type: 0,
|
||||
name: 'SKILL ANALYZER Level.∞',
|
||||
level: 12,
|
||||
nameID: 12,
|
||||
assist: 0,
|
||||
tracks: [
|
||||
{
|
||||
no: 0,
|
||||
mid: 1542,
|
||||
mty: 4,
|
||||
},
|
||||
{
|
||||
no: 1,
|
||||
mid: 1498,
|
||||
mty: 4,
|
||||
},
|
||||
{
|
||||
no: 2,
|
||||
mid: 1580, // 666
|
||||
mty: 4,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 13,
|
||||
name: 'SKILL ANALYZER 第5回',
|
||||
isNew: 1,
|
||||
courses:[
|
||||
{
|
||||
id: 1,
|
||||
type: 0,
|
||||
name: 'SKILL ANALYZER Level.01',
|
||||
level: 1,
|
||||
nameID: 1,
|
||||
assist: 0,
|
||||
tracks: [
|
||||
{
|
||||
no: 0,
|
||||
mid: 1383,
|
||||
mty: 0,
|
||||
},
|
||||
{
|
||||
no: 1,
|
||||
mid: 303,
|
||||
mty: 0,
|
||||
},
|
||||
{
|
||||
no: 2,
|
||||
mid: 1136,
|
||||
mty: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
type: 0,
|
||||
name: 'SKILL ANALYZER Level.02',
|
||||
level: 2,
|
||||
nameID: 2,
|
||||
assist: 0,
|
||||
tracks: [
|
||||
{
|
||||
no: 0,
|
||||
mid: 768,
|
||||
mty: 1,
|
||||
},
|
||||
{
|
||||
no: 1,
|
||||
mid: 138,
|
||||
mty: 1,
|
||||
},
|
||||
{
|
||||
no: 2,
|
||||
mid: 984,
|
||||
mty: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
type: 0,
|
||||
name: 'SKILL ANALYZER Level.03',
|
||||
level: 3,
|
||||
nameID: 3,
|
||||
assist: 0,
|
||||
tracks: [
|
||||
{
|
||||
no: 0,
|
||||
mid: 1132,
|
||||
mty: 1,
|
||||
},
|
||||
{
|
||||
no: 1,
|
||||
mid: 608,
|
||||
mty: 1,
|
||||
},
|
||||
{
|
||||
no: 2,
|
||||
mid: 1080,
|
||||
mty: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
type: 0,
|
||||
name: 'SKILL ANALYZER Level.04',
|
||||
level: 4,
|
||||
nameID: 4,
|
||||
assist: 0,
|
||||
tracks: [
|
||||
{
|
||||
no: 0,
|
||||
mid: 1387,
|
||||
mty: 1,
|
||||
},
|
||||
{
|
||||
no: 1,
|
||||
mid: 752,
|
||||
mty: 1,
|
||||
},
|
||||
{
|
||||
no: 2,
|
||||
mid: 1342,
|
||||
mty: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
type: 0,
|
||||
name: 'SKILL ANALYZER Level.05',
|
||||
level: 5,
|
||||
nameID: 5,
|
||||
assist: 0,
|
||||
tracks: [
|
||||
{
|
||||
no: 0,
|
||||
mid: 1472,
|
||||
mty: 1,
|
||||
},
|
||||
{
|
||||
no: 1,
|
||||
mid: 552,
|
||||
mty: 1,
|
||||
},
|
||||
{
|
||||
no: 2,
|
||||
mid: 1373,
|
||||
mty: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
type: 0,
|
||||
name: 'SKILL ANALYZER Level.06',
|
||||
level: 6,
|
||||
nameID: 6,
|
||||
assist: 0,
|
||||
tracks: [
|
||||
{
|
||||
no: 0,
|
||||
mid: 1422,
|
||||
mty: 2,
|
||||
},
|
||||
{
|
||||
no: 1,
|
||||
mid: 184,
|
||||
mty: 2,
|
||||
},
|
||||
{
|
||||
no: 2,
|
||||
mid: 1151,
|
||||
mty: 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
type: 0,
|
||||
name: 'SKILL ANALYZER Level.07',
|
||||
level: 7,
|
||||
nameID: 7,
|
||||
assist: 0,
|
||||
tracks: [
|
||||
{
|
||||
no: 0,
|
||||
mid: 1449,
|
||||
mty: 2,
|
||||
},
|
||||
{
|
||||
no: 1,
|
||||
mid: 850,
|
||||
mty: 2,
|
||||
},
|
||||
{
|
||||
no: 2,
|
||||
mid: 1007,
|
||||
mty: 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
type: 0,
|
||||
name: 'SKILL ANALYZER Level.08',
|
||||
level: 8,
|
||||
nameID: 8,
|
||||
assist: 0,
|
||||
tracks: [
|
||||
{
|
||||
no: 0,
|
||||
mid: 553,
|
||||
mty: 2,
|
||||
},
|
||||
{
|
||||
no: 1,
|
||||
mid: 1069,
|
||||
mty: 4,
|
||||
},
|
||||
{
|
||||
no: 2,
|
||||
mid: 1298,
|
||||
mty: 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
type: 0,
|
||||
name: 'SKILL ANALYZER Level.09',
|
||||
level: 9,
|
||||
nameID: 9,
|
||||
assist: 0,
|
||||
tracks: [
|
||||
{
|
||||
no: 0,
|
||||
mid: 647,
|
||||
mty: 2,
|
||||
},
|
||||
{
|
||||
no: 1,
|
||||
mid: 908,
|
||||
mty: 2,
|
||||
},
|
||||
{
|
||||
no: 2,
|
||||
mid: 1355,
|
||||
mty: 4,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
type: 0,
|
||||
name: 'SKILL ANALYZER Level.10',
|
||||
level: 10,
|
||||
nameID: 10,
|
||||
assist: 0,
|
||||
tracks: [
|
||||
{
|
||||
no: 0,
|
||||
mid: 1399,
|
||||
mty: 4,
|
||||
},
|
||||
{
|
||||
no: 1,
|
||||
mid: 836,
|
||||
mty: 2,
|
||||
},
|
||||
{
|
||||
no: 2,
|
||||
mid: 1053,
|
||||
mty: 4,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 11,
|
||||
type: 0,
|
||||
name: 'SKILL ANALYZER Level.11',
|
||||
level: 11,
|
||||
nameID: 11,
|
||||
assist: 0,
|
||||
tracks: [
|
||||
{
|
||||
no: 0,
|
||||
mid: 1308,
|
||||
mty: 4,
|
||||
},
|
||||
{
|
||||
no: 1,
|
||||
mid: 1108,
|
||||
mty: 4,
|
||||
},
|
||||
{
|
||||
no: 2,
|
||||
mid: 1240,
|
||||
mty: 4,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 12,
|
||||
type: 0,
|
||||
name: 'SKILL ANALYZER Level.∞',
|
||||
level: 12,
|
||||
nameID: 12,
|
||||
assist: 0,
|
||||
tracks: [
|
||||
{
|
||||
no: 0,
|
||||
mid: 1583,
|
||||
mty: 4,
|
||||
},
|
||||
{
|
||||
no: 1,
|
||||
mid: 1104,
|
||||
mty: 4,
|
||||
},
|
||||
{
|
||||
no: 2,
|
||||
mid: 1584,
|
||||
mty: 4,
|
||||
},
|
||||
],
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 14,
|
||||
name: 'SKILL ANALYZER 第6回',
|
||||
isNew: 1,
|
||||
courses:[
|
||||
{
|
||||
id: 1,
|
||||
type: 0,
|
||||
name: 'SKILL ANALYZER Level.01',
|
||||
level: 1,
|
||||
nameID: 1,
|
||||
assist: 0,
|
||||
tracks: [
|
||||
{
|
||||
no: 0,
|
||||
mid: 1488,
|
||||
mty: 0,
|
||||
},
|
||||
{
|
||||
no: 1,
|
||||
mid: 266,
|
||||
mty: 1,
|
||||
},
|
||||
{
|
||||
no: 2,
|
||||
mid: 51,
|
||||
mty: 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
type: 0,
|
||||
name: 'SKILL ANALYZER Level.02',
|
||||
level: 2,
|
||||
nameID: 2,
|
||||
assist: 0,
|
||||
tracks: [
|
||||
{
|
||||
no: 0,
|
||||
mid: 167,
|
||||
mty: 1,
|
||||
},
|
||||
{
|
||||
no: 1,
|
||||
mid: 1330,
|
||||
mty: 1,
|
||||
},
|
||||
{
|
||||
no: 2,
|
||||
mid: 923,
|
||||
mty: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
type: 0,
|
||||
name: 'SKILL ANALYZER Level.03',
|
||||
level: 3,
|
||||
nameID: 3,
|
||||
assist: 0,
|
||||
tracks: [
|
||||
{
|
||||
no: 0,
|
||||
mid: 419,
|
||||
mty: 1,
|
||||
},
|
||||
{
|
||||
no: 1,
|
||||
mid: 1455,
|
||||
mty: 1,
|
||||
},
|
||||
{
|
||||
no: 2,
|
||||
mid: 1075,
|
||||
mty: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
type: 0,
|
||||
name: 'SKILL ANALYZER Level.04',
|
||||
level: 4,
|
||||
nameID: 4,
|
||||
assist: 0,
|
||||
tracks: [
|
||||
{
|
||||
no: 0,
|
||||
mid: 597,
|
||||
mty: 1,
|
||||
},
|
||||
{
|
||||
no: 1,
|
||||
mid: 952,
|
||||
mty: 1,
|
||||
},
|
||||
{
|
||||
no: 2,
|
||||
mid: 1391,
|
||||
mty: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
type: 0,
|
||||
name: 'SKILL ANALYZER Level.05',
|
||||
level: 5,
|
||||
nameID: 5,
|
||||
assist: 0,
|
||||
tracks: [
|
||||
{
|
||||
no: 0,
|
||||
mid: 103,
|
||||
mty: 2,
|
||||
},
|
||||
{
|
||||
no: 1,
|
||||
mid: 565,
|
||||
mty: 2,
|
||||
},
|
||||
{
|
||||
no: 2,
|
||||
mid: 933,
|
||||
mty: 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
type: 0,
|
||||
name: 'SKILL ANALYZER Level.06',
|
||||
level: 6,
|
||||
nameID: 6,
|
||||
assist: 0,
|
||||
tracks: [
|
||||
{
|
||||
no: 0,
|
||||
mid: 1313,
|
||||
mty: 2,
|
||||
},
|
||||
{
|
||||
no: 1,
|
||||
mid: 1469,
|
||||
mty: 2,
|
||||
},
|
||||
{
|
||||
no: 2,
|
||||
mid: 559,
|
||||
mty: 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
type: 0,
|
||||
name: 'SKILL ANALYZER Level.07',
|
||||
level: 7,
|
||||
nameID: 7,
|
||||
assist: 0,
|
||||
tracks: [
|
||||
{
|
||||
no: 0,
|
||||
mid: 480,
|
||||
mty: 3,
|
||||
},
|
||||
{
|
||||
no: 1,
|
||||
mid: 1540,
|
||||
mty: 2,
|
||||
},
|
||||
{
|
||||
no: 2,
|
||||
mid: 1393,
|
||||
mty: 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
type: 0,
|
||||
name: 'SKILL ANALYZER Level.08',
|
||||
level: 8,
|
||||
nameID: 8,
|
||||
assist: 0,
|
||||
tracks: [
|
||||
{
|
||||
no: 0,
|
||||
mid: 78,
|
||||
mty: 2,
|
||||
},
|
||||
{
|
||||
no: 1,
|
||||
mid: 576,
|
||||
mty: 2,
|
||||
},
|
||||
{
|
||||
no: 2,
|
||||
mid: 1351,
|
||||
mty: 4,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
type: 0,
|
||||
name: 'SKILL ANALYZER Level.09',
|
||||
level: 9,
|
||||
nameID: 9,
|
||||
assist: 0,
|
||||
tracks: [
|
||||
{
|
||||
no: 0,
|
||||
mid: 63,
|
||||
mty: 2,
|
||||
},
|
||||
{
|
||||
no: 1,
|
||||
mid: 228,
|
||||
mty: 2,
|
||||
},
|
||||
{
|
||||
no: 2,
|
||||
mid: 641,
|
||||
mty: 4,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
type: 0,
|
||||
name: 'SKILL ANALYZER Level.10',
|
||||
level: 10,
|
||||
nameID: 10,
|
||||
assist: 0,
|
||||
tracks: [
|
||||
{
|
||||
no: 0,
|
||||
mid: 1523,
|
||||
mty: 4,
|
||||
},
|
||||
{
|
||||
no: 1,
|
||||
mid: 1457,
|
||||
mty: 4,
|
||||
},
|
||||
{
|
||||
no: 2,
|
||||
mid: 1061,
|
||||
mty: 4,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 11,
|
||||
type: 0,
|
||||
name: 'SKILL ANALYZER Level.11',
|
||||
level: 11,
|
||||
nameID: 11,
|
||||
assist: 0,
|
||||
tracks: [
|
||||
{
|
||||
no: 0,
|
||||
mid: 1192,
|
||||
mty: 4,
|
||||
},
|
||||
{
|
||||
no: 1,
|
||||
mid: 304,
|
||||
mty: 3,
|
||||
},
|
||||
{
|
||||
no: 2,
|
||||
mid: 679,
|
||||
mty: 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 12,
|
||||
type: 0,
|
||||
name: 'SKILL ANALYZER Level.∞',
|
||||
level: 12,
|
||||
nameID: 12,
|
||||
assist: 0,
|
||||
tracks: [
|
||||
{
|
||||
no: 0,
|
||||
mid: 1593,
|
||||
mty: 4,
|
||||
},
|
||||
{
|
||||
no: 1,
|
||||
mid: 1177,
|
||||
mty: 4,
|
||||
},
|
||||
{
|
||||
no: 2,
|
||||
mid: 1660,
|
||||
mty: 4,
|
||||
},
|
||||
],
|
||||
}
|
||||
],
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -24,13 +24,15 @@ export const common: EPR = async (info, data, send) => {
|
|||
let songs = [];
|
||||
|
||||
if (U.GetConfig('unlock_all_songs')) {
|
||||
for (let i = 1; i < 1600; ++i) {
|
||||
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),
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -253,7 +253,14 @@ export const load: EPR = async (info, data, send) => {
|
|||
const courses = await DB.Find<CourseRecord>(refid, { collection: 'course' });
|
||||
const items = await DB.Find<Item>(refid, { collection: 'item' });
|
||||
const params = await DB.Find<Param>(refid, { collection: 'param' });
|
||||
|
||||
let time = new Date();
|
||||
let tempHour = time.getHours();
|
||||
let tempDate = time.getDate();
|
||||
tempHour += 12;
|
||||
tempDate += 1;
|
||||
time.setDate(tempDate);
|
||||
time.setHours(tempHour);
|
||||
const currentTime = time.getTime();
|
||||
const mixes = version == 5 ? await getAutomationMixes(params) : [];
|
||||
|
||||
send.pugFile('templates/load.pug', {
|
||||
|
|
@ -263,6 +270,7 @@ export const load: EPR = async (info, data, send) => {
|
|||
: items,
|
||||
params,
|
||||
skill,
|
||||
currentTime,
|
||||
mixes,
|
||||
automation: version == 5 ? SDVX_AUTOMATION_SONGS : [],
|
||||
code: IDToCode(profile.id),
|
||||
|
|
|
|||
|
|
@ -35,6 +35,8 @@ game
|
|||
packet_booster(__type="s32") 1
|
||||
if version != 5
|
||||
block_booster(__type="s32") 1
|
||||
blaster_pass_enable(__type="bool") 1
|
||||
blaster_pass_limit_date(__type="u64") #{currentTime}
|
||||
|
||||
eaappli
|
||||
relation(__type="s8") 1
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user