mirror of
https://github.com/MewoLab/AquaDX.git
synced 2026-02-05 04:17:26 +08:00
Compare commits
19 Commits
js-formatt
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
02dc142eea | ||
|
|
5973b3bfe5 | ||
|
|
f4e3be8d15 | ||
|
|
c7e493d7f5 | ||
|
|
759519d374 | ||
|
|
3d713b13da | ||
|
|
20468e612d | ||
|
|
c175173821 | ||
|
|
52e9285551 | ||
|
|
f4280c0768 | ||
|
|
295ae14658 | ||
|
|
ccc2bcffce | ||
|
|
a47ed71799 | ||
|
|
006a49cfdb | ||
|
|
9794ee259a | ||
|
|
643e0e0c1f | ||
|
|
6afcb364d1 | ||
|
|
6d4a38404c | ||
|
|
b925c2ef20 |
41
AquaNet/.eslintrc.cjs
Normal file
41
AquaNet/.eslintrc.cjs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
// ..eslintrc.cjs example
|
||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
env: {
|
||||||
|
browser: true,
|
||||||
|
es2023: true
|
||||||
|
},
|
||||||
|
extends: [
|
||||||
|
'eslint:recommended',
|
||||||
|
|
||||||
|
'plugin:@typescript-eslint/recommended',
|
||||||
|
'plugin:svelte/recommended',
|
||||||
|
],
|
||||||
|
ignorePatterns: ['dist', '..eslintrc.cjs'],
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 'latest',
|
||||||
|
sourceType: 'module'
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
// Custom styling rules
|
||||||
|
'comma-dangle': ['warn', 'only-multiline'],
|
||||||
|
'indent': ['warn', 2],
|
||||||
|
'semi': ['warn', 'never'],
|
||||||
|
'quotes': ['warn', 'single'],
|
||||||
|
'arrow-parens': ['warn', 'as-needed'],
|
||||||
|
'linebreak-style': ['warn', 'unix'],
|
||||||
|
'object-curly-spacing': ['warn', 'always'],
|
||||||
|
'array-bracket-spacing': ["error", "always", {
|
||||||
|
"singleValue": false,
|
||||||
|
"objectsInArrays": false,
|
||||||
|
"arraysInArrays": false
|
||||||
|
}],
|
||||||
|
|
||||||
|
// Disabled rules
|
||||||
|
'no-unused-vars': 'off',
|
||||||
|
'@typescript-eslint/no-unused-vars': 'off',
|
||||||
|
'no-constant-condition': 'off',
|
||||||
|
'@typescript-eslint/no-explicit-any': 'off',
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -12,11 +12,11 @@ Please check out [SVELTE.md](SVELTE.md) for more details on the technical aspect
|
|||||||
### Running locally
|
### Running locally
|
||||||
|
|
||||||
First, you would need to install Node.js and yarn.
|
First, you would need to install Node.js and yarn.
|
||||||
Then, you would need to start your testing AquaDX server and configure the `src/libs/config.ts` to use your URL.
|
Then, you would need to start your testing AquaDX server and configure the `aqua_host` in `src/libs/config.ts` to use your URL.
|
||||||
|
Please leave `data_host` unchanged if you're not sure what it is.
|
||||||
Finally, run:
|
Finally, run:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
yarn install
|
yarn install
|
||||||
yarn dev
|
yarn dev
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -7,19 +7,23 @@
|
|||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"check": "svelte-check --tsconfig ./tsconfig.json"
|
"check": "svelte-check --tsconfig ./tsconfig.json",
|
||||||
|
"lint": "eslint . --ext ts,tsx,svelte --max-warnings 0 --fix"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@iconify/svelte": "^3.1.6",
|
"@iconify/svelte": "^3.1.6",
|
||||||
"@sveltejs/vite-plugin-svelte": "^3.0.1",
|
"@sveltejs/vite-plugin-svelte": "^3.0.1",
|
||||||
"@tsconfig/svelte": "^5.0.2",
|
"@tsconfig/svelte": "^5.0.2",
|
||||||
"chartjs-adapter-moment": "^1.0.1",
|
"chartjs-adapter-moment": "^1.0.1",
|
||||||
|
"eslint": "^8.56.0",
|
||||||
|
"eslint-plugin-svelte": "^2.35.1",
|
||||||
"sass": "^1.70.0",
|
"sass": "^1.70.0",
|
||||||
"svelte": "^4.2.10",
|
"svelte": "^4.2.10",
|
||||||
"svelte-check": "^3.6.4",
|
"svelte-check": "^3.6.4",
|
||||||
"svelte-routing": "^2.12.0",
|
"svelte-routing": "^2.12.0",
|
||||||
"tslib": "^2.6.2",
|
"tslib": "^2.6.2",
|
||||||
"typescript": "^5.2.2",
|
"typescript": "^5.2.2",
|
||||||
|
"typescript-eslint": "^7.0.1",
|
||||||
"vite": "^5.1.1"
|
"vite": "^5.1.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
export interface TrendEntry {
|
export interface TrendEntry {
|
||||||
date: string
|
date: string
|
||||||
rating: number
|
rating: number
|
||||||
plays: number
|
plays: number
|
||||||
}
|
}
|
||||||
@@ -1,51 +1,51 @@
|
|||||||
import {aqua_host, data_host} from "./config";
|
import { aqua_host, data_host } from './config'
|
||||||
import type {TrendEntry} from "./generalTypes";
|
import type { TrendEntry } from './generalTypes'
|
||||||
import type {MaimaiUserSummaryEntry} from "./maimaiTypes";
|
import type { MaimaiUserSummaryEntry } from './maimaiTypes'
|
||||||
|
|
||||||
|
|
||||||
const multTable = [
|
const multTable = [
|
||||||
[100.5, 22.4, "SSSp"],
|
[ 100.5, 22.4, 'SSSp' ],
|
||||||
[100, 21.6, "SSS"],
|
[ 100, 21.6, 'SSS' ],
|
||||||
[99.5, 21.1, "SSp"],
|
[ 99.5, 21.1, 'SSp' ],
|
||||||
[99, 20.8, "SS"],
|
[ 99, 20.8, 'SS' ],
|
||||||
[98, 20.3, "Sp"],
|
[ 98, 20.3, 'Sp' ],
|
||||||
[97, 20, "S"],
|
[ 97, 20, 'S' ],
|
||||||
[94, 16.8, "AAA"],
|
[ 94, 16.8, 'AAA' ],
|
||||||
[90, 15.2, "AA"],
|
[ 90, 15.2, 'AA' ],
|
||||||
[80, 13.6, "A"]
|
[ 80, 13.6, 'A' ]
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
export function getMult(achievement: number) {
|
export function getMult(achievement: number) {
|
||||||
achievement /= 10000
|
achievement /= 10000
|
||||||
for (let i = 0; i < multTable.length; i++) {
|
for (let i = 0; i < multTable.length; i++) {
|
||||||
if (achievement >= (multTable[i][0] as number)) return multTable[i]
|
if (achievement >= (multTable[i][0] as number)) return multTable[i]
|
||||||
}
|
}
|
||||||
return [0, 0, 0]
|
return [ 0, 0, 0 ]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export async function getMaimai(endpoint: string, params: any) {
|
export async function getMaimai(endpoint: string, params: any) {
|
||||||
return await fetch(`${aqua_host}/Maimai2Servlet/${endpoint}`, {
|
return await fetch(`${aqua_host}/Maimai2Servlet/${endpoint}`, {
|
||||||
method: "POST",
|
method: 'POST',
|
||||||
body: JSON.stringify(params)
|
body: JSON.stringify(params)
|
||||||
}).then(res => res.json())
|
}).then(res => res.json())
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getMaimaiAllMusic(): Promise<{ [key: string]: any }> {
|
export async function getMaimaiAllMusic(): Promise<{ [key: string]: any }> {
|
||||||
return fetch(`${data_host}/maimai/meta/00/all-music.json`).then(it => it.json())
|
return fetch(`${data_host}/maimai/meta/00/all-music.json`).then(it => it.json())
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getMaimaiApi(endpoint: string, params: any) {
|
export async function getMaimaiApi(endpoint: string, params: any) {
|
||||||
let url = new URL(`${aqua_host}/api/game/maimai2new/${endpoint}`)
|
const url = new URL(`${aqua_host}/api/game/maimai2new/${endpoint}`)
|
||||||
Object.keys(params).forEach(key => url.searchParams.append(key, params[key]))
|
Object.keys(params).forEach(key => url.searchParams.append(key, params[key]))
|
||||||
return await fetch(url).then(res => res.json())
|
return await fetch(url).then(res => res.json())
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getMaimaiTrend(userId: number): Promise<TrendEntry[]> {
|
export async function getMaimaiTrend(userId: number): Promise<TrendEntry[]> {
|
||||||
return await getMaimaiApi("trend", {userId})
|
return await getMaimaiApi('trend', { userId })
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getMaimaiUser(userId: number): Promise<MaimaiUserSummaryEntry> {
|
export async function getMaimaiUser(userId: number): Promise<MaimaiUserSummaryEntry> {
|
||||||
return await getMaimaiApi("user-summary", {userId})
|
return await getMaimaiApi('user-summary', { userId })
|
||||||
}
|
}
|
||||||
@@ -1,115 +1,118 @@
|
|||||||
export interface Rating {
|
export interface Rating {
|
||||||
musicId: number
|
musicId: number
|
||||||
level: number
|
level: number
|
||||||
achievement: number
|
achievement: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ParsedRating extends Rating {
|
export interface ParsedRating extends Rating {
|
||||||
music: MaimaiMusic,
|
music: MaimaiMusic,
|
||||||
calc: number,
|
calc: number,
|
||||||
rank: string
|
rank: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MaimaiMusic {
|
export interface MaimaiMusic {
|
||||||
name: string,
|
name: string,
|
||||||
composer: string,
|
composer: string,
|
||||||
bpm: number,
|
bpm: number,
|
||||||
ver: number,
|
ver: number,
|
||||||
note: {
|
note: {
|
||||||
lv: number
|
lv: number
|
||||||
designer: string
|
designer: string
|
||||||
lv_id: number
|
lv_id: number
|
||||||
notes: number
|
notes: number
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MaimaiUserSummaryEntry {
|
export interface MaimaiUserSummaryEntry {
|
||||||
name: string
|
name: string
|
||||||
iconId: number
|
iconId: number
|
||||||
serverRank: number
|
serverRank: number
|
||||||
accuracy: number
|
accuracy: number
|
||||||
rating: number
|
rating: number
|
||||||
ratingHighest: number
|
ratingHighest: number
|
||||||
ranks: { name: string, count: number }[]
|
ranks: { name: string, count: number }[]
|
||||||
maxCombo: number
|
maxCombo: number
|
||||||
fullCombo: number
|
fullCombo: number
|
||||||
allPerfect: number
|
allPerfect: number
|
||||||
totalDxScore: number
|
totalDxScore: number
|
||||||
plays: number
|
plays: number
|
||||||
totalPlayTime: number
|
totalPlayTime: number
|
||||||
joined: string
|
joined: string
|
||||||
lastSeen: string
|
lastSeen: string
|
||||||
lastVersion: string
|
lastVersion: string
|
||||||
best35: string
|
best35: string
|
||||||
best15: string
|
best15: string
|
||||||
recent: MaimaiUserPlaylog[]
|
recent: MaimaiUserPlaylog[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MaimaiUserPlaylog {
|
export interface MaimaiUserPlaylog {
|
||||||
id: number;
|
id: number;
|
||||||
musicId: number;
|
musicId: number;
|
||||||
level: number;
|
level: number;
|
||||||
trackNo: number;
|
userPlayDate: string;
|
||||||
vsRank: number;
|
trackNo: number;
|
||||||
achievement: number;
|
vsRank: number;
|
||||||
deluxscore: number;
|
achievement: number;
|
||||||
scoreRank: number;
|
deluxscore: number;
|
||||||
maxCombo: number;
|
scoreRank: number;
|
||||||
totalCombo: number;
|
maxCombo: number;
|
||||||
maxSync: number;
|
totalCombo: number;
|
||||||
totalSync: number;
|
maxSync: number;
|
||||||
tapCriticalPerfect: number;
|
totalSync: number;
|
||||||
tapPerfect: number;
|
tapCriticalPerfect: number;
|
||||||
tapGreat: number;
|
tapPerfect: number;
|
||||||
tapGood: number;
|
tapGreat: number;
|
||||||
tapMiss: number;
|
tapGood: number;
|
||||||
holdCriticalPerfect: number;
|
tapMiss: number;
|
||||||
holdPerfect: number;
|
holdCriticalPerfect: number;
|
||||||
holdGreat: number;
|
holdPerfect: number;
|
||||||
holdGood: number;
|
holdGreat: number;
|
||||||
holdMiss: number;
|
holdGood: number;
|
||||||
slideCriticalPerfect: number;
|
holdMiss: number;
|
||||||
slidePerfect: number;
|
slideCriticalPerfect: number;
|
||||||
slideGreat: number;
|
slidePerfect: number;
|
||||||
slideGood: number;
|
slideGreat: number;
|
||||||
slideMiss: number;
|
slideGood: number;
|
||||||
touchCriticalPerfect: number;
|
slideMiss: number;
|
||||||
touchPerfect: number;
|
touchCriticalPerfect: number;
|
||||||
touchGreat: number;
|
touchPerfect: number;
|
||||||
touchGood: number;
|
touchGreat: number;
|
||||||
touchMiss: number;
|
touchGood: number;
|
||||||
breakCriticalPerfect: number;
|
touchMiss: number;
|
||||||
breakPerfect: number;
|
breakCriticalPerfect: number;
|
||||||
breakGreat: number;
|
breakPerfect: number;
|
||||||
breakGood: number;
|
breakGreat: number;
|
||||||
breakMiss: number;
|
breakGood: number;
|
||||||
isTap: boolean;
|
breakMiss: number;
|
||||||
isHold: boolean;
|
isTap: boolean;
|
||||||
isSlide: boolean;
|
isHold: boolean;
|
||||||
isTouch: boolean;
|
isSlide: boolean;
|
||||||
isBreak: boolean;
|
isTouch: boolean;
|
||||||
isCriticalDisp: boolean;
|
isBreak: boolean;
|
||||||
isFastLateDisp: boolean;
|
isCriticalDisp: boolean;
|
||||||
fastCount: number;
|
isFastLateDisp: boolean;
|
||||||
lateCount: number;
|
fastCount: number;
|
||||||
isAchieveNewRecord: boolean;
|
lateCount: number;
|
||||||
isDeluxscoreNewRecord: boolean;
|
isAchieveNewRecord: boolean;
|
||||||
comboStatus: number;
|
isDeluxscoreNewRecord: boolean;
|
||||||
syncStatus: number;
|
comboStatus: number;
|
||||||
isClear: boolean;
|
syncStatus: number;
|
||||||
beforeRating: number;
|
isClear: boolean;
|
||||||
afterRating: number;
|
beforeRating: number;
|
||||||
beforeGrade: number;
|
afterRating: number;
|
||||||
afterGrade: number;
|
beforeGrade: number;
|
||||||
afterGradeRank: number;
|
afterGrade: number;
|
||||||
beforeDeluxRating: number;
|
afterGradeRank: number;
|
||||||
afterDeluxRating: number;
|
beforeDeluxRating: number;
|
||||||
isPlayTutorial: boolean;
|
afterDeluxRating: number;
|
||||||
isEventMode: boolean;
|
isPlayTutorial: boolean;
|
||||||
isFreedomMode: boolean;
|
isEventMode: boolean;
|
||||||
playMode: number;
|
isFreedomMode: boolean;
|
||||||
isNewFree: boolean;
|
playMode: number;
|
||||||
trialPlayAchievement: number;
|
isNewFree: boolean;
|
||||||
extNum1: number;
|
trialPlayAchievement: number;
|
||||||
extNum2: number;
|
extNum1: number;
|
||||||
}
|
extNum2: number;
|
||||||
|
extNum4: number;
|
||||||
|
extBool1: boolean;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,101 +1,100 @@
|
|||||||
import {
|
import {
|
||||||
Chart as ChartJS,
|
Chart as ChartJS,
|
||||||
Title,
|
Title,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Legend,
|
Legend,
|
||||||
LineElement,
|
LineElement,
|
||||||
LinearScale,
|
LinearScale,
|
||||||
PointElement,
|
PointElement,
|
||||||
CategoryScale, TimeScale, type ChartOptions, type LineOptions,
|
CategoryScale, TimeScale, type ChartOptions, type LineOptions,
|
||||||
} from 'chart.js';
|
} from 'chart.js'
|
||||||
import moment from "moment/moment";
|
import moment from 'moment/moment'
|
||||||
// @ts-ignore
|
// @ts-expect-error Cal-heatmap does not have proper types
|
||||||
import CalHeatmap from "cal-heatmap";
|
import CalHeatmap from 'cal-heatmap'
|
||||||
// @ts-ignore
|
// @ts-expect-error Cal-heatmap does not have proper types
|
||||||
import CalTooltip from 'cal-heatmap/plugins/Tooltip';
|
import CalTooltip from 'cal-heatmap/plugins/Tooltip'
|
||||||
import type {Line} from "svelte-chartjs";
|
|
||||||
|
export function title(t: string) {
|
||||||
export function title(t: string) {
|
document.title = `AquaNet - ${t}`
|
||||||
document.title = `AquaNet - ${t}`
|
}
|
||||||
}
|
|
||||||
|
export function registerChart() {
|
||||||
export function registerChart() {
|
ChartJS.register(
|
||||||
ChartJS.register(
|
Title,
|
||||||
Title,
|
Tooltip,
|
||||||
Tooltip,
|
Legend,
|
||||||
Legend,
|
LineElement,
|
||||||
LineElement,
|
LinearScale,
|
||||||
LinearScale,
|
PointElement,
|
||||||
PointElement,
|
CategoryScale,
|
||||||
CategoryScale,
|
TimeScale
|
||||||
TimeScale
|
)
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
export function renderCal(el: HTMLElement, d: {date: any, value: any}[]) {
|
||||||
export function renderCal(el: HTMLElement, d: {date: any, value: any}[]) {
|
const cal = new CalHeatmap()
|
||||||
const cal = new CalHeatmap();
|
return cal.paint({
|
||||||
return cal.paint({
|
itemSelector: el,
|
||||||
itemSelector: el,
|
domain: {
|
||||||
domain: {
|
type: 'month',
|
||||||
type: 'month',
|
label: { text: 'MMM', textAlign: 'start', position: 'top' },
|
||||||
label: { text: 'MMM', textAlign: 'start', position: 'top' },
|
},
|
||||||
},
|
subDomain: {
|
||||||
subDomain: {
|
type: 'ghDay',
|
||||||
type: 'ghDay',
|
radius: 2, width: 11, height: 11, gutter: 4
|
||||||
radius: 2, width: 11, height: 11, gutter: 4
|
},
|
||||||
},
|
range: 12,
|
||||||
range: 12,
|
data: { source: d, x: 'date', y: 'value' },
|
||||||
data: {source: d, x: 'date', y: 'value'},
|
scale: {
|
||||||
scale: {
|
color: {
|
||||||
color: {
|
type: 'linear',
|
||||||
type: 'linear',
|
range: [ '#14432a', '#4dd05a' ],
|
||||||
range: ['#14432a', '#4dd05a'],
|
domain: [ 0, d.reduce((a, b) => Math.max(a, b.value), 0) ]
|
||||||
domain: [0, d.reduce((a, b) => Math.max(a, b.value), 0)]
|
},
|
||||||
},
|
},
|
||||||
},
|
date: { start: moment().subtract(1, 'year').add(1, 'month').toDate() },
|
||||||
date: {start: moment().subtract(1, 'year').add(1, 'month').toDate()},
|
theme: 'dark',
|
||||||
theme: "dark",
|
}, [
|
||||||
}, [
|
[ CalTooltip, { text: (_: Date, v: number, d: any) =>
|
||||||
[CalTooltip, {text: (_: Date, v: number, d: any) =>
|
`${v ?? 'No'} songs played on ${d.format('MMMM D, YYYY')}` }]
|
||||||
`${v ?? "No"} songs played on ${d.format('MMMM D, YYYY')}`}]
|
])
|
||||||
]);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
export const CHARTJS_OPT: ChartOptions<'line'> = {
|
||||||
export const CHARTJS_OPT: ChartOptions<"line"> = {
|
responsive: true,
|
||||||
responsive: true,
|
maintainAspectRatio: false,
|
||||||
maintainAspectRatio: false,
|
// TODO: Show point on hover
|
||||||
// TODO: Show point on hover
|
elements: {
|
||||||
elements: {
|
point: {
|
||||||
point: {
|
radius: 0
|
||||||
radius: 0
|
}
|
||||||
}
|
},
|
||||||
},
|
scales: {
|
||||||
scales: {
|
xAxis: {
|
||||||
xAxis: {
|
type: 'time',
|
||||||
type: 'time',
|
display: false
|
||||||
display: false
|
},
|
||||||
},
|
y: {
|
||||||
y: {
|
display: false,
|
||||||
display: false,
|
}
|
||||||
}
|
},
|
||||||
},
|
plugins: {
|
||||||
plugins: {
|
legend: {
|
||||||
legend: {
|
display: false
|
||||||
display: false
|
},
|
||||||
},
|
tooltip: {
|
||||||
tooltip: {
|
mode: 'index',
|
||||||
mode: "index",
|
intersect: false
|
||||||
intersect: false
|
}
|
||||||
}
|
},
|
||||||
},
|
}
|
||||||
}
|
|
||||||
|
/**
|
||||||
/**
|
* Usage: clazz({a: false, b: true}) -> "b"
|
||||||
* Usage: clazz({a: false, b: true}) -> "b"
|
*
|
||||||
*
|
* @param obj HashMap<string, boolean>
|
||||||
* @param obj HashMap<string, boolean>
|
*/
|
||||||
*/
|
export function clazz(obj: { [key: string]: boolean }) {
|
||||||
export function clazz(obj: { [key: string]: boolean }) {
|
return Object.keys(obj).filter(k => obj[k]).join(' ')
|
||||||
return Object.keys(obj).filter(k => obj[k]).join(" ")
|
}
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import './app.sass'
|
import './app.sass'
|
||||||
import App from './App.svelte'
|
import App from './App.svelte'
|
||||||
|
|
||||||
// @ts-ignore
|
const app = new App({ target: document.getElementById('app')! })
|
||||||
const app = new App({target: document.getElementById('app')})
|
|
||||||
|
|
||||||
export default app
|
export default app
|
||||||
@@ -39,7 +39,7 @@
|
|||||||
letter-spacing: 0.2em
|
letter-spacing: 0.2em
|
||||||
margin-top: 0
|
margin-top: 0
|
||||||
opacity: 0.9
|
opacity: 0.9
|
||||||
|
|
||||||
.btn-group
|
.btn-group
|
||||||
display: flex
|
display: flex
|
||||||
gap: 8px
|
gap: 8px
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
Promise.all([
|
Promise.all([
|
||||||
getMaimai("GetUserRatingApi", {userId}),
|
getMaimai("GetUserRatingApi", {userId}),
|
||||||
getMaimaiAllMusic().then(it => it.json())
|
getMaimaiAllMusic()
|
||||||
]).then(([rating, music]) => {
|
]).then(([rating, music]) => {
|
||||||
data = rating
|
data = rating
|
||||||
musicInfo = music
|
musicInfo = music
|
||||||
@@ -37,7 +37,8 @@
|
|||||||
|
|
||||||
music.note = music.notes[x.level]
|
music.note = music.notes[x.level]
|
||||||
const mult = getMult(x.achievement)
|
const mult = getMult(x.achievement)
|
||||||
return {...x,
|
return {
|
||||||
|
...x,
|
||||||
music: music,
|
music: music,
|
||||||
calc: (mult[1] as number) * music.note.lv,
|
calc: (mult[1] as number) * music.note.lv,
|
||||||
rank: mult[2]
|
rank: mult[2]
|
||||||
@@ -69,7 +70,9 @@
|
|||||||
<div class="rating-cards">
|
<div class="rating-cards">
|
||||||
{#each section.data as rating}
|
{#each section.data as rating}
|
||||||
<div class="level-{rating.level}">
|
<div class="level-{rating.level}">
|
||||||
<img class="cover" src={`${data_host}/maimai/assetbundle/jacket_s/00${rating.musicId.toString().padStart(6, '0').substring(2)}.png`} alt="">
|
<img class="cover"
|
||||||
|
src={`${data_host}/maimai/assetbundle/jacket_s/00${rating.musicId.toString().padStart(6, '0').substring(2)}.png`}
|
||||||
|
alt="">
|
||||||
|
|
||||||
<div class="detail">
|
<div class="detail">
|
||||||
<span class="name">{rating.music.name}</span>
|
<span class="name">{rating.music.name}</span>
|
||||||
@@ -79,7 +82,9 @@
|
|||||||
</span>
|
</span>
|
||||||
<span>{rating.calc.toFixed(1)}</span>
|
<span>{rating.calc.toFixed(1)}</span>
|
||||||
</div>
|
</div>
|
||||||
<img class="ver" src={`${data_host}/maimai/sprites/tab/title/UI_CMN_TabTitle_MaimaiTitle_Ver${rating.music.ver.toString().substring(0, 3)}.png`} alt="">
|
<img class="ver"
|
||||||
|
src={`${data_host}/maimai/sprites/tab/title/UI_CMN_TabTitle_MaimaiTitle_Ver${rating.music.ver.toString().substring(0, 3)}.png`}
|
||||||
|
alt="">
|
||||||
<div class="lv">{rating.music.note.lv}</div>
|
<div class="lv">{rating.music.note.lv}</div>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
@@ -89,111 +94,106 @@
|
|||||||
</main>
|
</main>
|
||||||
|
|
||||||
<style lang="sass">
|
<style lang="sass">
|
||||||
.rating-cards
|
.rating-cards
|
||||||
display: grid
|
display: grid
|
||||||
gap: 2rem
|
gap: 2rem
|
||||||
width: 100%
|
width: 100%
|
||||||
|
|
||||||
// Fill as many columns as possible
|
// Fill as many columns as possible
|
||||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr))
|
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr))
|
||||||
|
|
||||||
// Center the cards
|
// Center the cards
|
||||||
justify-items: center
|
justify-items: center
|
||||||
align-items: center
|
align-items: center
|
||||||
|
|
||||||
// Style each card
|
// Style each card
|
||||||
> div
|
> div
|
||||||
$border-radius: 20px
|
$border-radius: 20px
|
||||||
width: 200px
|
width: 200px
|
||||||
height: 200px
|
height: 200px
|
||||||
border-radius: $border-radius
|
border-radius: $border-radius
|
||||||
|
|
||||||
display: flex
|
|
||||||
position: relative
|
|
||||||
|
|
||||||
// Difficulty border
|
|
||||||
border: 5px solid var(--lv-color, #60aaff)
|
|
||||||
&.level-1
|
|
||||||
--lv-color: #aaff60
|
|
||||||
&.level-2
|
|
||||||
--lv-color: #f25353
|
|
||||||
&.level-3
|
|
||||||
--lv-color: #e881ff
|
|
||||||
|
|
||||||
img
|
|
||||||
object-fit: cover
|
|
||||||
pointer-events: none
|
|
||||||
|
|
||||||
img.cover
|
|
||||||
width: 100%
|
|
||||||
height: 100%
|
|
||||||
border-radius: calc($border-radius - 3px)
|
|
||||||
|
|
||||||
img.ver
|
|
||||||
position: absolute
|
|
||||||
top: -20px
|
|
||||||
left: -30px
|
|
||||||
height: 50px
|
|
||||||
|
|
||||||
// Information
|
|
||||||
.detail
|
|
||||||
position: absolute
|
|
||||||
bottom: 0
|
|
||||||
left: 0
|
|
||||||
right: 0
|
|
||||||
padding: 10px
|
|
||||||
background: rgba(0, 0, 0, 0.5)
|
|
||||||
border-radius: 0 0 calc($border-radius - 3px) calc($border-radius - 3px)
|
|
||||||
|
|
||||||
// Blur
|
|
||||||
backdrop-filter: blur(3px)
|
|
||||||
|
|
||||||
display: flex
|
display: flex
|
||||||
flex-direction: column
|
position: relative
|
||||||
text-align: left
|
|
||||||
|
|
||||||
> span
|
// Difficulty border
|
||||||
// Disable text wrapping, max 2 lines
|
border: 5px solid var(--lv-color)
|
||||||
overflow: hidden
|
|
||||||
text-overflow: ellipsis
|
|
||||||
white-space: nowrap
|
|
||||||
|
|
||||||
.name
|
img
|
||||||
font-size: 1.2em
|
object-fit: cover
|
||||||
font-weight: bold
|
pointer-events: none
|
||||||
|
|
||||||
.rating
|
img.cover
|
||||||
display: flex
|
width: 100%
|
||||||
img
|
height: 100%
|
||||||
height: 1.5em
|
border-radius: calc($border-radius - 3px)
|
||||||
|
|
||||||
.lv
|
|
||||||
position: absolute
|
|
||||||
bottom: 0
|
|
||||||
right: 0
|
|
||||||
padding: 5px 10px
|
|
||||||
background: var(--lv-color)
|
|
||||||
// Top left border radius
|
|
||||||
border-radius: 10px 0
|
|
||||||
|
|
||||||
font-size: 1.3em
|
|
||||||
|
|
||||||
&:before
|
|
||||||
content: "Lv"
|
|
||||||
font-size: 0.8em
|
|
||||||
|
|
||||||
// Mobile
|
|
||||||
@media (max-width: 500px)
|
|
||||||
margin-left: -1rem
|
|
||||||
margin-right: -1rem
|
|
||||||
width: calc(100% + 2rem)
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax(130px, 1fr))
|
|
||||||
font-size: 0.8em
|
|
||||||
> div
|
|
||||||
width: 150px
|
|
||||||
height: 150px
|
|
||||||
|
|
||||||
img.ver
|
img.ver
|
||||||
height: 45px
|
position: absolute
|
||||||
left: -20px
|
top: -20px
|
||||||
|
left: -30px
|
||||||
|
height: 50px
|
||||||
|
|
||||||
|
// Information
|
||||||
|
.detail
|
||||||
|
position: absolute
|
||||||
|
bottom: 0
|
||||||
|
left: 0
|
||||||
|
right: 0
|
||||||
|
padding: 10px
|
||||||
|
background: rgba(0, 0, 0, 0.5)
|
||||||
|
border-radius: 0 0 calc($border-radius - 3px) calc($border-radius - 3px)
|
||||||
|
|
||||||
|
// Blur
|
||||||
|
backdrop-filter: blur(3px)
|
||||||
|
|
||||||
|
display: flex
|
||||||
|
flex-direction: column
|
||||||
|
text-align: left
|
||||||
|
|
||||||
|
> span
|
||||||
|
// Disable text wrapping, max 2 lines
|
||||||
|
overflow: hidden
|
||||||
|
text-overflow: ellipsis
|
||||||
|
white-space: nowrap
|
||||||
|
|
||||||
|
.name
|
||||||
|
font-size: 1.2em
|
||||||
|
font-weight: bold
|
||||||
|
|
||||||
|
.rating
|
||||||
|
display: flex
|
||||||
|
|
||||||
|
img
|
||||||
|
height: 1.5em
|
||||||
|
|
||||||
|
.lv
|
||||||
|
position: absolute
|
||||||
|
bottom: 0
|
||||||
|
right: 0
|
||||||
|
padding: 5px 10px
|
||||||
|
background: var(--lv-color)
|
||||||
|
// Top left border radius
|
||||||
|
border-radius: 10px 0
|
||||||
|
|
||||||
|
font-size: 1.3em
|
||||||
|
|
||||||
|
&:before
|
||||||
|
content: "Lv"
|
||||||
|
font-size: 0.8em
|
||||||
|
|
||||||
|
// Mobile
|
||||||
|
@media (max-width: 500px)
|
||||||
|
margin-left: -1rem
|
||||||
|
margin-right: -1rem
|
||||||
|
width: calc(100% + 2rem)
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(130px, 1fr))
|
||||||
|
font-size: 0.8em
|
||||||
|
> div
|
||||||
|
width: 150px
|
||||||
|
height: 150px
|
||||||
|
|
||||||
|
img.ver
|
||||||
|
height: 45px
|
||||||
|
left: -20px
|
||||||
</style>
|
</style>
|
||||||
@@ -34,6 +34,9 @@
|
|||||||
console.log(trend)
|
console.log(trend)
|
||||||
console.log(music)
|
console.log(music)
|
||||||
|
|
||||||
|
// Sort recent by date
|
||||||
|
user.recent.sort((a, b) => b.userPlayDate < a.userPlayDate ? -1 : 1)
|
||||||
|
|
||||||
d = {user, trend, recent: user.recent.map(it => {return {...music[it.musicId], ...it}})}
|
d = {user, trend, recent: user.recent.map(it => {return {...music[it.musicId], ...it}})}
|
||||||
localStorage.setItem("tmp-user-details", JSON.stringify(d))
|
localStorage.setItem("tmp-user-details", JSON.stringify(d))
|
||||||
renderCal(calElement, trend.map(it => {return {date: it.date, value: it.plays}}))
|
renderCal(calElement, trend.map(it => {return {date: it.date, value: it.plays}}))
|
||||||
@@ -161,8 +164,11 @@
|
|||||||
<div class={clazz({alt: i % 2 === 0})}>
|
<div class={clazz({alt: i % 2 === 0})}>
|
||||||
<img src={`${data_host}/maimai/assetbundle/jacket_s/00${r.musicId.toString().padStart(6, '0').substring(2)}.png`} alt="">
|
<img src={`${data_host}/maimai/assetbundle/jacket_s/00${r.musicId.toString().padStart(6, '0').substring(2)}.png`} alt="">
|
||||||
<div class="info">
|
<div class="info">
|
||||||
<span class="name">{r.name}</span>
|
|
||||||
<div>
|
<div>
|
||||||
|
<span class="name">{r.name}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span class={`lv level-${r.level}`}>{r.notes[r.level].lv}</span>
|
||||||
<span class={"rank-" + ("" + getMult(r.achievement)[2])[0]}>
|
<span class={"rank-" + ("" + getMult(r.achievement)[2])[0]}>
|
||||||
<span class="rank-text">{("" + getMult(r.achievement)[2]).replace("p", "+")}</span>
|
<span class="rank-text">{("" + getMult(r.achievement)[2]).replace("p", "+")}</span>
|
||||||
<span class="rank-num">{(r.achievement / 10000).toFixed(2)}%</span>
|
<span class="rank-num">{(r.achievement / 10000).toFixed(2)}%</span>
|
||||||
@@ -351,7 +357,7 @@ $gap: 20px
|
|||||||
flex-direction: column
|
flex-direction: column
|
||||||
gap: 0
|
gap: 0
|
||||||
|
|
||||||
span
|
.rank-text
|
||||||
text-align: left
|
text-align: left
|
||||||
|
|
||||||
.rank-S
|
.rank-S
|
||||||
@@ -366,6 +372,13 @@ $gap: 20px
|
|||||||
.rank-B
|
.rank-B
|
||||||
color: #6ba6ff
|
color: #6ba6ff
|
||||||
|
|
||||||
|
.lv
|
||||||
|
background: var(--lv-color)
|
||||||
|
padding: 0 6px
|
||||||
|
border-radius: 10px
|
||||||
|
opacity: 0.8
|
||||||
|
margin-right: 10px
|
||||||
|
|
||||||
span
|
span
|
||||||
display: inline-block
|
display: inline-block
|
||||||
text-align: right
|
text-align: right
|
||||||
|
|||||||
@@ -6,4 +6,13 @@ $c-bg: #242424
|
|||||||
|
|
||||||
$nav-height: 4rem
|
$nav-height: 4rem
|
||||||
$w-mobile: 560px
|
$w-mobile: 560px
|
||||||
$w-max: 900px
|
$w-max: 900px
|
||||||
|
|
||||||
|
.level-0
|
||||||
|
--lv-color: #6ED43E
|
||||||
|
.level-1
|
||||||
|
--lv-color: #F7B807
|
||||||
|
.level-2
|
||||||
|
--lv-color: #FF828D
|
||||||
|
.level-3
|
||||||
|
--lv-color: #A051DC
|
||||||
File diff suppressed because it is too large
Load Diff
30
README.md
30
README.md
@@ -1,6 +1,6 @@
|
|||||||
# AquaDX
|
# AquaDX
|
||||||
|
|
||||||
Multipurpose game server powered by Spring Boot, for ALL.Net based games
|
Multipurpose game server powered by Spring Boot, for ALL.Net-based games
|
||||||
|
|
||||||
This is an attempt to rebuild the [original Aqua server](https://dev.s-ul.net/NeumPhis/aqua)
|
This is an attempt to rebuild the [original Aqua server](https://dev.s-ul.net/NeumPhis/aqua)
|
||||||
|
|
||||||
@@ -26,9 +26,9 @@ Check out these docs for more information.
|
|||||||
* [Frequently asked questions](docs/frequently_asked_questions.md)
|
* [Frequently asked questions](docs/frequently_asked_questions.md)
|
||||||
|
|
||||||
### Notes
|
### Notes
|
||||||
* Some game may require additional patches and these will not provided in this project and repository. You already found this, so you know where to find related resources too.
|
* Some games may require additional patches and these will not provided in this project and repository. You already found this, so you know where to find related resources too.
|
||||||
* This repository may contain untested, experimental implementation for few games which I can't test properly. If you couldn't find your wanted game in the above list, do not expect support.
|
* This repository may contain untested, experimental implementations for a few games which I can't test properly. If you couldn't find your wanted game in the above list, do not expect support.
|
||||||
* This server also provides a simple API for viewing play records and edit settings for some games.
|
* This server also provides a simple API for viewing play records and editing settings for some games.
|
||||||
|
|
||||||
### Usage
|
### Usage
|
||||||
|
|
||||||
@@ -37,28 +37,28 @@ Check out these docs for more information.
|
|||||||
3. Extract the zip file to a folder.
|
3. Extract the zip file to a folder.
|
||||||
4. Run `java -jar aqua.jar` in the folder.
|
4. Run `java -jar aqua.jar` in the folder.
|
||||||
|
|
||||||
By default, Aqua will use sqlite and save user data in data/db.sqlite.
|
By default, Aqua will use SQLite and save user data in `data/db.sqlite`.
|
||||||
|
|
||||||
If you want to use optional databases, edit configuration file then it will auto create the table and import some initial data.
|
If you want to use optional databases, please edit the configuration file then it will auto-create the table and import some initial data.
|
||||||
|
|
||||||
### Configuration
|
### Configuration
|
||||||
Configuration is saved in `config/application.properties`, spring loads this file automatically.
|
Configuration is saved in `config/application.properties`, spring loads this file automatically.
|
||||||
|
|
||||||
* The host and port of game title servers can be overritten in `allnet.server.host` and `allnet.server.port`. By default it will send the same host and port the client used the request this information.
|
* The host and port of game title servers can be overwritten in `allnet.server.host` and `allnet.server.port`. By default it will send the same host and port the client used the request this information.
|
||||||
This will be sent to the game at booting and being used by following request.
|
This will be sent to the game at booting and being used by the following request.
|
||||||
* You can switch to MariaDB (or MySQL) database by commenting the Sqlite part.
|
* You can switch to the MariaDB (or MySQL) database by commenting the Sqlite part.
|
||||||
* For some game, you might need to change some game specific config entries.
|
* For some games, you might need to change some game-specific config entries.
|
||||||
|
|
||||||
### Building
|
### Building
|
||||||
You need to install JDK on your system. However, you don't need to care about Gradle, as wrapper script is included.
|
You need to install JDK on your system. However, you don't need to install Gradle separately, as the `gradlew` wrapper script is included.
|
||||||
```
|
```
|
||||||
gradlew clean build
|
gradlew clean build
|
||||||
```
|
```
|
||||||
The `build/libs` folder will contain an jar file.
|
The `build/libs` folder will contain a jar file.
|
||||||
|
|
||||||
### Credit
|
### Credit
|
||||||
* **samnyan**: The creator and developer of the original Aqua server
|
* **samnyan**: The creator and developer of the original Aqua server
|
||||||
* **Akasaka Ryuunosuke** : providing all the DIVA protocol information
|
* **Akasaka Ryuunosuke**: providing all the DIVA protocol information
|
||||||
* Dom Eori : Developer of forked Aqua server, from v0.0.17 and up
|
* Dom Eori: Developer of forked Aqua server, from v0.0.17 and up
|
||||||
* All devs who contribute to the [MiniMe server](https://dev.s-ul.net/djhackers/minime)
|
* All devs who contribute to the [MiniMe server](https://dev.s-ul.net/djhackers/minime)
|
||||||
* All contributors by merge request, issues and other channels
|
* All contributors by merge requests, issues and other channels
|
||||||
|
|||||||
@@ -36,9 +36,9 @@ dependencies {
|
|||||||
testImplementation("org.springframework.security:spring-security-test")
|
testImplementation("org.springframework.security:spring-security-test")
|
||||||
|
|
||||||
// Database
|
// Database
|
||||||
runtimeOnly("com.mysql:mysql-connector-j:8.0.33")
|
runtimeOnly("com.mysql:mysql-connector-j:8.3.0")
|
||||||
runtimeOnly("org.mariadb.jdbc:mariadb-java-client:3.1.3")
|
runtimeOnly("org.mariadb.jdbc:mariadb-java-client:3.3.2")
|
||||||
runtimeOnly("org.xerial:sqlite-jdbc:3.41.2.1")
|
runtimeOnly("org.xerial:sqlite-jdbc:3.45.1.0")
|
||||||
implementation("com.github.gwenn:sqlite-dialect:0.1.4")
|
implementation("com.github.gwenn:sqlite-dialect:0.1.4")
|
||||||
|
|
||||||
// JSR305 for nullable
|
// JSR305 for nullable
|
||||||
|
|||||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,6 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
|
||||||
networkTimeout=10000
|
networkTimeout=10000
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
184
gradlew.bat
vendored
184
gradlew.bat
vendored
@@ -1,92 +1,92 @@
|
|||||||
@rem
|
@rem
|
||||||
@rem Copyright 2015 the original author or authors.
|
@rem Copyright 2015 the original author or authors.
|
||||||
@rem
|
@rem
|
||||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@rem you may not use this file except in compliance with the License.
|
@rem you may not use this file except in compliance with the License.
|
||||||
@rem You may obtain a copy of the License at
|
@rem You may obtain a copy of the License at
|
||||||
@rem
|
@rem
|
||||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||||
@rem
|
@rem
|
||||||
@rem Unless required by applicable law or agreed to in writing, software
|
@rem Unless required by applicable law or agreed to in writing, software
|
||||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
@rem See the License for the specific language governing permissions and
|
@rem See the License for the specific language governing permissions and
|
||||||
@rem limitations under the License.
|
@rem limitations under the License.
|
||||||
@rem
|
@rem
|
||||||
|
|
||||||
@if "%DEBUG%"=="" @echo off
|
@if "%DEBUG%"=="" @echo off
|
||||||
@rem ##########################################################################
|
@rem ##########################################################################
|
||||||
@rem
|
@rem
|
||||||
@rem Gradle startup script for Windows
|
@rem Gradle startup script for Windows
|
||||||
@rem
|
@rem
|
||||||
@rem ##########################################################################
|
@rem ##########################################################################
|
||||||
|
|
||||||
@rem Set local scope for the variables with windows NT shell
|
@rem Set local scope for the variables with windows NT shell
|
||||||
if "%OS%"=="Windows_NT" setlocal
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
set DIRNAME=%~dp0
|
set DIRNAME=%~dp0
|
||||||
if "%DIRNAME%"=="" set DIRNAME=.
|
if "%DIRNAME%"=="" set DIRNAME=.
|
||||||
@rem This is normally unused
|
@rem This is normally unused
|
||||||
set APP_BASE_NAME=%~n0
|
set APP_BASE_NAME=%~n0
|
||||||
set APP_HOME=%DIRNAME%
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||||
|
|
||||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||||
|
|
||||||
@rem Find java.exe
|
@rem Find java.exe
|
||||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
set JAVA_EXE=java.exe
|
set JAVA_EXE=java.exe
|
||||||
%JAVA_EXE% -version >NUL 2>&1
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
if %ERRORLEVEL% equ 0 goto execute
|
if %ERRORLEVEL% equ 0 goto execute
|
||||||
|
|
||||||
echo.
|
echo.
|
||||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
echo.
|
echo.
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
echo location of your Java installation.
|
echo location of your Java installation.
|
||||||
|
|
||||||
goto fail
|
goto fail
|
||||||
|
|
||||||
:findJavaFromJavaHome
|
:findJavaFromJavaHome
|
||||||
set JAVA_HOME=%JAVA_HOME:"=%
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
if exist "%JAVA_EXE%" goto execute
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
echo.
|
echo.
|
||||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||||
echo.
|
echo.
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
echo location of your Java installation.
|
echo location of your Java installation.
|
||||||
|
|
||||||
goto fail
|
goto fail
|
||||||
|
|
||||||
:execute
|
:execute
|
||||||
@rem Setup the command line
|
@rem Setup the command line
|
||||||
|
|
||||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
@rem Execute Gradle
|
@rem Execute Gradle
|
||||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||||
|
|
||||||
:end
|
:end
|
||||||
@rem End local scope for the variables with windows NT shell
|
@rem End local scope for the variables with windows NT shell
|
||||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||||
|
|
||||||
:fail
|
:fail
|
||||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
rem the _cmd.exe /c_ return code!
|
rem the _cmd.exe /c_ return code!
|
||||||
set EXIT_CODE=%ERRORLEVEL%
|
set EXIT_CODE=%ERRORLEVEL%
|
||||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||||
exit /b %EXIT_CODE%
|
exit /b %EXIT_CODE%
|
||||||
|
|
||||||
:mainEnd
|
:mainEnd
|
||||||
if "%OS%"=="Windows_NT" endlocal
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|
||||||
:omega
|
:omega
|
||||||
|
|||||||
@@ -153,4 +153,6 @@ public class UserDetail implements Serializable {
|
|||||||
@Transient
|
@Transient
|
||||||
private int cmLastEmoneyCredit = 69;
|
private int cmLastEmoneyCredit = 69;
|
||||||
private int mapStock;
|
private int mapStock;
|
||||||
|
private int currentPlayCount;
|
||||||
|
private int renameCredit;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -255,4 +255,9 @@ public class UserPlaylog implements Serializable {
|
|||||||
|
|
||||||
private int extNum2;
|
private int extNum2;
|
||||||
|
|
||||||
|
private int extNum4;
|
||||||
|
|
||||||
|
@JsonProperty("extBool1")
|
||||||
|
private boolean extBool1;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
INSERT INTO `maimai2_game_event` (`id`, `end_date`, `start_date`, `type`, `enable`) VALUES
|
||||||
|
(23120811, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||||
|
(23120821, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||||
|
(23120822, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||||
|
(23120823, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||||
|
(23120824, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||||
|
(23120825, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||||
|
(23120841, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||||
|
(23120842, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||||
|
(23120843, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||||
|
(23120844, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||||
|
(23120851, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||||
|
(23120852, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||||
|
(23122271, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1');
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
INSERT INTO maimai2_game_charge (id, order_id, charge_id, price, start_date, end_date) VALUES (1, 1, 2, 1, '2019-01-01 00:00:00.000000', '2099-01-01 00:00:00.000000');
|
||||||
|
INSERT INTO maimai2_game_charge (id, order_id, charge_id, price, start_date, end_date) VALUES (2, 2, 3, 2, '2019-01-01 00:00:00.000000', '2099-01-01 00:00:00.000000');
|
||||||
|
INSERT INTO maimai2_game_charge (id, order_id, charge_id, price, start_date, end_date) VALUES (3, 3, 4, 3, '2019-01-01 00:00:00.000000', '2099-01-01 00:00:00.000000');
|
||||||
|
INSERT INTO maimai2_game_charge (id, order_id, charge_id, price, start_date, end_date) VALUES (4, 4, 5, 4, '2019-01-01 00:00:00.000000', '2099-01-01 00:00:00.000000');
|
||||||
|
INSERT INTO maimai2_game_charge (id, order_id, charge_id, price, start_date, end_date) VALUES (5, 5, 6, 5, '2019-01-01 00:00:00.000000', '2099-01-01 00:00:00.000000');
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
INSERT INTO `maimai2_game_event` (`id`, `end_date`, `start_date`, `type`, `enable`) VALUES
|
||||||
|
(24011111, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||||
|
(24011121, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||||
|
(24011141, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1');
|
||||||
17
src/main/resources/db/migration/mariadb/V255_2__fix_null.sql
Normal file
17
src/main/resources/db/migration/mariadb/V255_2__fix_null.sql
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
-- maimai2_user_playlog
|
||||||
|
-- Set ext_bool1 as NOT NULL and give it a default value (e.g., FALSE)
|
||||||
|
UPDATE maimai2_user_playlog SET ext_bool1 = FALSE WHERE ext_bool1 IS NULL;
|
||||||
|
ALTER TABLE maimai2_user_playlog MODIFY COLUMN ext_bool1 BOOLEAN NOT NULL DEFAULT FALSE;
|
||||||
|
|
||||||
|
-- Set ext_num4 as NOT NULL (assuming it already has a default value of 0)
|
||||||
|
UPDATE maimai2_user_playlog SET ext_num4 = 0 WHERE ext_num4 IS NULL;
|
||||||
|
ALTER TABLE maimai2_user_playlog MODIFY COLUMN ext_num4 INTEGER NOT NULL DEFAULT 0;
|
||||||
|
|
||||||
|
-- maimai2_user_detail
|
||||||
|
-- Add default value for current_play_count and set it as NOT NULL
|
||||||
|
UPDATE maimai2_user_detail SET current_play_count = 0 WHERE current_play_count IS NULL;
|
||||||
|
ALTER TABLE maimai2_user_detail MODIFY COLUMN current_play_count INTEGER NOT NULL DEFAULT 0;
|
||||||
|
|
||||||
|
-- Add default value for rename_credit and set it as NOT NULL
|
||||||
|
UPDATE maimai2_user_detail SET rename_credit = 0 WHERE rename_credit IS NULL;
|
||||||
|
ALTER TABLE maimai2_user_detail MODIFY COLUMN rename_credit INTEGER NOT NULL DEFAULT 0;
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
-- maimai2_user_playlog
|
||||||
|
ALTER TABLE maimai2_user_playlog ADD COLUMN ext_bool1 BOOLEAN;
|
||||||
|
ALTER TABLE maimai2_user_playlog ADD COLUMN ext_num4 INTEGER;
|
||||||
|
UPDATE maimai2_user_playlog SET ext_num4=0;
|
||||||
|
|
||||||
|
-- maimai2_user_detail
|
||||||
|
ALTER TABLE maimai2_user_detail ADD COLUMN current_play_count INTEGER;
|
||||||
|
ALTER TABLE maimai2_user_detail ADD COLUMN rename_credit INTEGER;
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
INSERT INTO `maimai2_game_event` (`id`, `end_date`, `start_date`, `type`, `enable`) VALUES
|
||||||
|
(23120811, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||||
|
(23120821, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||||
|
(23120822, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||||
|
(23120823, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||||
|
(23120824, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||||
|
(23120825, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||||
|
(23120841, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||||
|
(23120842, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||||
|
(23120843, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||||
|
(23120844, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||||
|
(23120851, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||||
|
(23120852, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||||
|
(23122271, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1');
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
INSERT INTO maimai2_game_charge (id, order_id, charge_id, price, start_date, end_date) VALUES (1, 1, 2, 1, '2019-01-01 00:00:00.000000', '2099-01-01 00:00:00.000000');
|
||||||
|
INSERT INTO maimai2_game_charge (id, order_id, charge_id, price, start_date, end_date) VALUES (2, 2, 3, 2, '2019-01-01 00:00:00.000000', '2099-01-01 00:00:00.000000');
|
||||||
|
INSERT INTO maimai2_game_charge (id, order_id, charge_id, price, start_date, end_date) VALUES (3, 3, 4, 3, '2019-01-01 00:00:00.000000', '2099-01-01 00:00:00.000000');
|
||||||
|
INSERT INTO maimai2_game_charge (id, order_id, charge_id, price, start_date, end_date) VALUES (4, 4, 5, 4, '2019-01-01 00:00:00.000000', '2099-01-01 00:00:00.000000');
|
||||||
|
INSERT INTO maimai2_game_charge (id, order_id, charge_id, price, start_date, end_date) VALUES (5, 5, 6, 5, '2019-01-01 00:00:00.000000', '2099-01-01 00:00:00.000000');
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
INSERT INTO `maimai2_game_event` (`id`, `end_date`, `start_date`, `type`, `enable`) VALUES
|
||||||
|
(24011111, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||||
|
(24011121, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||||
|
(24011141, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1');
|
||||||
17
src/main/resources/db/migration/mysql/V255_2__fix_null.sql
Normal file
17
src/main/resources/db/migration/mysql/V255_2__fix_null.sql
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
-- maimai2_user_playlog
|
||||||
|
-- Set ext_bool1 as NOT NULL and give it a default value (e.g., FALSE)
|
||||||
|
UPDATE maimai2_user_playlog SET ext_bool1 = FALSE WHERE ext_bool1 IS NULL;
|
||||||
|
ALTER TABLE maimai2_user_playlog MODIFY COLUMN ext_bool1 BOOLEAN NOT NULL DEFAULT FALSE;
|
||||||
|
|
||||||
|
-- Set ext_num4 as NOT NULL (assuming it already has a default value of 0)
|
||||||
|
UPDATE maimai2_user_playlog SET ext_num4 = 0 WHERE ext_num4 IS NULL;
|
||||||
|
ALTER TABLE maimai2_user_playlog MODIFY COLUMN ext_num4 INTEGER NOT NULL DEFAULT 0;
|
||||||
|
|
||||||
|
-- maimai2_user_detail
|
||||||
|
-- Add default value for current_play_count and set it as NOT NULL
|
||||||
|
UPDATE maimai2_user_detail SET current_play_count = 0 WHERE current_play_count IS NULL;
|
||||||
|
ALTER TABLE maimai2_user_detail MODIFY COLUMN current_play_count INTEGER NOT NULL DEFAULT 0;
|
||||||
|
|
||||||
|
-- Add default value for rename_credit and set it as NOT NULL
|
||||||
|
UPDATE maimai2_user_detail SET rename_credit = 0 WHERE rename_credit IS NULL;
|
||||||
|
ALTER TABLE maimai2_user_detail MODIFY COLUMN rename_credit INTEGER NOT NULL DEFAULT 0;
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
-- maimai2_user_playlog
|
||||||
|
ALTER TABLE maimai2_user_playlog ADD COLUMN ext_bool1 BOOLEAN;
|
||||||
|
ALTER TABLE maimai2_user_playlog ADD COLUMN ext_num4 INTEGER;
|
||||||
|
UPDATE maimai2_user_playlog SET ext_num4=0;
|
||||||
|
|
||||||
|
-- maimai2_user_detail
|
||||||
|
ALTER TABLE maimai2_user_detail ADD COLUMN current_play_count INTEGER;
|
||||||
|
ALTER TABLE maimai2_user_detail ADD COLUMN rename_credit INTEGER;
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
INSERT INTO `maimai2_game_event` (`id`, `end_date`, `start_date`, `type`, `enable`) VALUES
|
||||||
|
(23120811, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||||
|
(23120821, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||||
|
(23120822, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||||
|
(23120823, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||||
|
(23120824, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||||
|
(23120825, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||||
|
(23120841, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||||
|
(23120842, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||||
|
(23120843, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||||
|
(23120844, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||||
|
(23120851, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||||
|
(23120852, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||||
|
(23122271, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1');
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
INSERT INTO maimai2_game_charge (id, order_id, charge_id, price, start_date, end_date) VALUES (1, 1, 2, 1, '2019-01-01 00:00:00.000000', '2099-01-01 00:00:00.000000');
|
||||||
|
INSERT INTO maimai2_game_charge (id, order_id, charge_id, price, start_date, end_date) VALUES (2, 2, 3, 2, '2019-01-01 00:00:00.000000', '2099-01-01 00:00:00.000000');
|
||||||
|
INSERT INTO maimai2_game_charge (id, order_id, charge_id, price, start_date, end_date) VALUES (3, 3, 4, 3, '2019-01-01 00:00:00.000000', '2099-01-01 00:00:00.000000');
|
||||||
|
INSERT INTO maimai2_game_charge (id, order_id, charge_id, price, start_date, end_date) VALUES (4, 4, 5, 4, '2019-01-01 00:00:00.000000', '2099-01-01 00:00:00.000000');
|
||||||
|
INSERT INTO maimai2_game_charge (id, order_id, charge_id, price, start_date, end_date) VALUES (5, 5, 6, 5, '2019-01-01 00:00:00.000000', '2099-01-01 00:00:00.000000');
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
INSERT INTO `maimai2_game_event` (`id`, `end_date`, `start_date`, `type`, `enable`) VALUES
|
||||||
|
(24011111, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||||
|
(24011121, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||||
|
(24011141, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1');
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
UPDATE maimai2_user_playlog SET ext_bool1=false;
|
||||||
|
UPDATE maimai2_user_detail SET current_play_count=0;
|
||||||
|
UPDATE maimai2_user_detail SET rename_credit=0;
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
-- maimai2_user_playlog
|
||||||
|
ALTER TABLE maimai2_user_playlog ADD COLUMN ext_bool1 BOOLEAN;
|
||||||
|
ALTER TABLE maimai2_user_playlog ADD COLUMN ext_num4 INTEGER;
|
||||||
|
UPDATE maimai2_user_playlog SET ext_num4=0;
|
||||||
|
|
||||||
|
-- maimai2_user_detail
|
||||||
|
ALTER TABLE maimai2_user_detail ADD COLUMN current_play_count INTEGER;
|
||||||
|
ALTER TABLE maimai2_user_detail ADD COLUMN rename_credit INTEGER;
|
||||||
@@ -7,10 +7,14 @@ from pathlib import Path
|
|||||||
import orjson
|
import orjson
|
||||||
import xmltodict
|
import xmltodict
|
||||||
from hypy_utils import write
|
from hypy_utils import write
|
||||||
|
from hypy_utils.logging_utils import setup_logger
|
||||||
from hypy_utils.tqdm_utils import pmap
|
from hypy_utils.tqdm_utils import pmap
|
||||||
|
from wand.image import Image
|
||||||
|
|
||||||
|
log = setup_logger()
|
||||||
|
|
||||||
|
|
||||||
def convert_one(file: Path):
|
def convert_path(file: Path):
|
||||||
# Get path relative to source
|
# Get path relative to source
|
||||||
rel = file.relative_to(src)
|
rel = file.relative_to(src)
|
||||||
|
|
||||||
@@ -18,8 +22,29 @@ def convert_one(file: Path):
|
|||||||
if len(rel.parts) <= 2:
|
if len(rel.parts) <= 2:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Generate target file path
|
||||||
|
# Ignore the first segment of the relative path, and append to the destination
|
||||||
|
# Also collapse the single-item directory into the filename
|
||||||
|
# e.g. {src}/A000/music/music000001/Music.xml -> {dst}/music/000001.json
|
||||||
|
target = dst / '/'.join(rel.parts[1:-2])
|
||||||
|
file_id = ''.join(filter(str.isdigit, rel.parts[-2]))
|
||||||
|
file_id = file_id.zfill(6)
|
||||||
|
target = target / f'{file_id}.json'
|
||||||
|
|
||||||
|
return target
|
||||||
|
|
||||||
|
|
||||||
|
def convert_one(file: Path):
|
||||||
|
target = convert_path(file)
|
||||||
|
if target is None:
|
||||||
|
return
|
||||||
|
|
||||||
# Read xml
|
# Read xml
|
||||||
xml = xmltodict.parse(file.read_text())
|
try:
|
||||||
|
xml = xmltodict.parse(file.read_text())
|
||||||
|
except Exception as e:
|
||||||
|
log.info(f'Error parsing {file}: {e}')
|
||||||
|
return
|
||||||
|
|
||||||
# There should only be one root element, expand it
|
# There should only be one root element, expand it
|
||||||
assert len(xml) == 1, f'Expected 1 root element, got {len(xml)}'
|
assert len(xml) == 1, f'Expected 1 root element, got {len(xml)}'
|
||||||
@@ -31,51 +56,103 @@ def convert_one(file: Path):
|
|||||||
if '@xmlns:xsd' in xml:
|
if '@xmlns:xsd' in xml:
|
||||||
del xml['@xmlns:xsd']
|
del xml['@xmlns:xsd']
|
||||||
|
|
||||||
# Generate target file path
|
if target.exists():
|
||||||
# Ignore the first segment of the relative path, and append to the destination
|
log.info(f'Overwriting {target}')
|
||||||
# Also collapse the single-item directory into the filename
|
|
||||||
# e.g. {src}/A000/music/music000001/Music.xml -> {dst}/music/000001.json
|
|
||||||
target = dst / '/'.join(rel.parts[1:-2])
|
|
||||||
file_id = ''.join(filter(str.isdigit, rel.parts[-2]))
|
|
||||||
target = target / f'{file_id}.json'
|
|
||||||
|
|
||||||
# Create directories if they don't exist
|
|
||||||
target.parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
|
|
||||||
# Write json
|
# Write json
|
||||||
write(target, orjson.dumps(xml))
|
write(target, orjson.dumps(xml))
|
||||||
|
|
||||||
|
|
||||||
def combine_music():
|
def convert_dds(file: Path):
|
||||||
# Read all music json files
|
target = convert_path(file)
|
||||||
music_files = list(dst.rglob('music/*.json'))
|
if target is None:
|
||||||
print(f'> Found {len(music_files)} music files')
|
return
|
||||||
jsons = [orjson.loads(f.read_text()) for f in music_files]
|
|
||||||
|
|
||||||
# Combine all music
|
# Convert dds to jpg
|
||||||
combined = {d['name']['id']: {
|
try:
|
||||||
|
with Image(filename=str(file)) as img:
|
||||||
|
img.format = 'jpeg'
|
||||||
|
img.save(filename=str(target.with_suffix('.png')))
|
||||||
|
except Exception as e:
|
||||||
|
log.info(f'Error converting {file}: {e}')
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def get(d: dict, *keys: str):
|
||||||
|
"""
|
||||||
|
Get the first key that exists in the dictionary
|
||||||
|
|
||||||
|
:param d: Dictionary
|
||||||
|
:param keys: Recursive key in the format of keya.keyb.keyc...
|
||||||
|
"""
|
||||||
|
for k in keys:
|
||||||
|
ks = k.split('.')
|
||||||
|
cd = d
|
||||||
|
while len(ks) > 0:
|
||||||
|
cd = cd.get(ks.pop(0))
|
||||||
|
if cd is None:
|
||||||
|
break
|
||||||
|
if cd is not None:
|
||||||
|
return cd
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def convert_music_mai2(d: dict) -> (str, dict):
|
||||||
|
return d['name']['id'], {
|
||||||
'name': d['name']['str'],
|
'name': d['name']['str'],
|
||||||
'ver': int(d['version']),
|
'ver': d.get('version') or d.get('releaseTagName')['str'],
|
||||||
'composer': d['artistName']['str'],
|
'composer': d['artistName']['str'],
|
||||||
'genre': d['genreName']['str'],
|
'genre': d['genreName']['str'] or d['genreNames'],
|
||||||
'bpm': int(d['bpm']),
|
'bpm': int(d['bpm']),
|
||||||
'lock': f"{d['lockType']} {d['subLockType']}",
|
'lock': f"{d['lockType']} {d['subLockType']}",
|
||||||
'notes': [{
|
'notes': [{
|
||||||
'lv': int(n['level']) + (int(n['levelDecimal']) / 10),
|
'lv': int(n['level']) + (int(n['levelDecimal']) / 10.0),
|
||||||
'designer': n['notesDesigner']['str'],
|
'designer': n['notesDesigner']['str'],
|
||||||
'lv_id': n['musicLevelID'],
|
'lv_id': n['musicLevelID'],
|
||||||
'notes': int(n['maxNotes']),
|
'notes': int(n['maxNotes']),
|
||||||
} for n in d['notesData']['Notes'] if n['isEnable'] != 'false']
|
} for n in d['notesData']['Notes'] if n['isEnable'] != 'false']
|
||||||
} for d in jsons}
|
}
|
||||||
|
|
||||||
# Write combined music
|
|
||||||
write(dst / '00/all-music.json', orjson.dumps(combined))
|
def convert_music_chu3(d: dict) -> (str, dict):
|
||||||
|
return d['name']['id'], {
|
||||||
|
'name': d['name']['str'],
|
||||||
|
'ver': d['releaseTagName']['str'],
|
||||||
|
'composer': d['artistName']['str'],
|
||||||
|
'genre': get(d, 'genreName.list.StringID.str'),
|
||||||
|
'lock': d['firstLock'],
|
||||||
|
'notes': [{
|
||||||
|
'lv': int(n['level']) + (int(n['levelDecimal']) / 100.0),
|
||||||
|
'designer': n.get('notesDesigner'),
|
||||||
|
'lv_id': n['type']['id'],
|
||||||
|
} for n in d['fumens']['MusicFumenData'] if n['enable'] != 'false']
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def convert_music_ongeki(d: dict) -> (str, dict):
|
||||||
|
return d['Name']['id'], {
|
||||||
|
'name': d['Name']['str'],
|
||||||
|
'ver': d['VersionID']['id'],
|
||||||
|
'composer': d['ArtistName']['str'],
|
||||||
|
'genre': d['Genre']['str'],
|
||||||
|
'lock': f"{d['CostToUnlock']} {d['IsLockedAtTheBeginning']}",
|
||||||
|
'notes': [{
|
||||||
|
'lv': int(n['FumenConstIntegerPart']) + (int(n['FumenConstFractionalPart']) / 100.0),
|
||||||
|
'lv_id': i,
|
||||||
|
} for i, n in enumerate(d['FumenData']['FumenData']) if n['FumenFile']['path'] is not None],
|
||||||
|
'lunatic': d['IsLunatic']
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
agupa = argparse.ArgumentParser()
|
agupa = argparse.ArgumentParser()
|
||||||
|
# Source can be one of the following:
|
||||||
|
# - maimai/Package/Sinmai_Data/StreamingAssets
|
||||||
|
# - chusan/App/data
|
||||||
|
# - ongeki/package/mu3_Data/StreamingAssets/GameData
|
||||||
agupa.add_argument('source', type=str, help='Package/Sinmai_Data/StreamingAssets directory')
|
agupa.add_argument('source', type=str, help='Package/Sinmai_Data/StreamingAssets directory')
|
||||||
agupa.add_argument('destination', type=str, help='Directory to extract to')
|
agupa.add_argument('destination', type=str, help='Directory to extract to')
|
||||||
|
agupa.add_argument('-g', '--game', type=str, help='Game to convert', default='mai2', choices=['mai2', 'chu3', 'ongeki'])
|
||||||
args = agupa.parse_args()
|
args = agupa.parse_args()
|
||||||
|
|
||||||
src = Path(args.source)
|
src = Path(args.source)
|
||||||
@@ -92,7 +169,7 @@ if __name__ == '__main__':
|
|||||||
if not d.is_dir():
|
if not d.is_dir():
|
||||||
continue
|
continue
|
||||||
|
|
||||||
print(f'Relocating {d}')
|
log.info(f'Relocating {d}')
|
||||||
for file in d.rglob('*.png'):
|
for file in d.rglob('*.png'):
|
||||||
id = ''.join(filter(str.isdigit, file.stem))
|
id = ''.join(filter(str.isdigit, file.stem))
|
||||||
shutil.move(file, d / f'{id}.png')
|
shutil.move(file, d / f'{id}.png')
|
||||||
@@ -105,19 +182,36 @@ if __name__ == '__main__':
|
|||||||
# Assert that target directory does not exist
|
# Assert that target directory does not exist
|
||||||
if dst.exists():
|
if dst.exists():
|
||||||
if input(f'{dst} already exists, delete? (y/n): ') == 'y':
|
if input(f'{dst} already exists, delete? (y/n): ') == 'y':
|
||||||
print(f'Deleting {dst}')
|
log.info(f'Deleting {dst}')
|
||||||
shutil.rmtree(dst)
|
shutil.rmtree(dst)
|
||||||
|
|
||||||
# Find all xml files in the source directory
|
# Find all xml files in the source directory
|
||||||
files = list(src.rglob('*.xml'))
|
files = list(src.rglob('*.xml'))
|
||||||
print(f'Found {len(files)} xml files')
|
log.info(f'Found {len(files)} xml files')
|
||||||
|
|
||||||
# Multithreaded map
|
# Multithreaded map
|
||||||
pmap(convert_one, files, desc='Converting', unit='file', chunksize=50)
|
pmap(convert_one, files, desc='Converting', unit='file', chunksize=50)
|
||||||
print('> Finished converting')
|
log.info('> Finished converting')
|
||||||
|
|
||||||
|
# Find all .dds files in the source A000 directory
|
||||||
|
dds_files = list(src.rglob('*.dds'))
|
||||||
|
log.info(f'Found {len(dds_files)} dds files')
|
||||||
|
|
||||||
|
# Convert and copy dds files (CPU-intensive)
|
||||||
|
pmap(convert_dds, dds_files, desc='Converting DDS', unit='file', chunksize=50, max_workers=os.cpu_count() - 2)
|
||||||
|
log.info('> Finished converting DDS')
|
||||||
|
|
||||||
# Convert all music
|
# Convert all music
|
||||||
print('Combining music')
|
log.info('Combining music')
|
||||||
combine_music()
|
music_files = list(dst.rglob('music/*.json'))
|
||||||
|
log.info(f'> Found {len(music_files)} music files')
|
||||||
|
jsons = [orjson.loads(f.read_text()) for f in music_files]
|
||||||
|
|
||||||
|
converter = {'mai2': convert_music_mai2, 'chu3': convert_music_chu3, 'ongeki': convert_music_ongeki}[args.game]
|
||||||
|
combined = {k: v for k, v in [converter(d) for d in jsons]}
|
||||||
|
|
||||||
|
# Write combined music
|
||||||
|
write(dst / '00/all-music.json', orjson.dumps(combined))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user