16 Commits

Author SHA1 Message Date
Azalea
c7e493d7f5 [F] Fix null 2024-02-26 17:05:32 -05:00
Azalea
759519d374 [PR] #13 from Becods: Extra fields from bud
[+] Extra fields from bud
2024-02-26 10:40:14 -05:00
Becod
3d713b13da [+] Extra fields from bud 2024-02-26 21:42:00 +08:00
Azalea
20468e612d Merge branch 'master' of https://github.com/hykilpikonna/AquaDX 2024-02-23 00:11:05 -05:00
Azalea
c175173821 Merge pull request #12 from Zaphkito/master
Add maimai 140 h041 event data
2024-02-21 05:46:55 -05:00
zaphkito
52e9285551 Add maimai 140 h041 event data 2024-02-21 18:39:39 +08:00
Azalea
f4280c0768 Merge pull request #11 from Zaphkito/master
Maimai 140 h031 event data and charge data
2024-02-18 15:57:25 -05:00
zaphkito
295ae14658 Add maimai2 charge 2024-02-19 04:40:48 +08:00
zaphkito
ccc2bcffce Maimai 140 h031 event data 2024-02-19 04:12:53 +08:00
Azalea
a47ed71799 [F] Fix typos in readme 2024-02-16 20:49:44 -08:00
Azalea
006a49cfdb [F] Fix dependency CVE 2024-02-16 15:54:09 -05:00
Azalea
9794ee259a [U] Upgrade gradle wrapper 2024-02-16 15:52:05 -05:00
Azalea
643e0e0c1f [O] Lint 2024-02-16 01:46:11 -05:00
Azalea
6afcb364d1 [+] Add eslint 2024-02-16 01:43:32 -05:00
Azalea
6d4a38404c [O] Sort recent by date, display level 2024-02-16 01:04:29 -05:00
Azalea
b925c2ef20 [U] Update readme 2024-02-12 11:26:37 -05:00
44 changed files with 1990 additions and 1156 deletions

5
.gitignore vendored
View File

@@ -75,7 +75,4 @@ gradle-app.setting
### Gradle Patch ### ### Gradle Patch ###
# Java heap dump # Java heap dump
*.hprof *.hprof
### Docker ###
/db/*

View File

@@ -1,8 +0,0 @@
{
"trailingComma": "es5",
"semi": false,
"singleQuote": true,
"bracketSpacing": false,
"plugins": ["prettier-plugin-svelte"],
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
}

View File

@@ -11,6 +11,6 @@ TicketUnlock=true
# Skip the warning screen and logo shown after the POST sequence # Skip the warning screen and logo shown after the POST sequence
SkipWarningScreen=true SkipWarningScreen=true
# Single player: Show 1P only, at the center of the screen # Single player: Show 1P only, at the center of the screen
SinglePlayer=false SinglePlayer=true
# !!EXPERIMENTAL!! Skip from the card-scanning screen directly to music selection screen # !!EXPERIMENTAL!! Skip from the card-scanning screen directly to music selection screen
SkipToMusicSelection=true SkipToMusicSelection=false

41
AquaNet/.eslintrc.cjs Normal file
View 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',
},
}

View File

@@ -1,6 +1,6 @@
# AquaNet # AquaNet
This is the codebase for the new frontend of AquaDX. This is the codebase for the new frontend of AquaDX.
This project is also heavily WIP, so more details will be added later on. This project is also heavily WIP, so more details will be added later on.
## Development ## Development
@@ -12,7 +12,8 @@ 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

View File

@@ -1,35 +1,35 @@
## Technical considerations ## Technical considerations
**Why use this over SvelteKit?** **Why use this over SvelteKit?**
- It brings its own routing solution which might not be preferable for some users. - It brings its own routing solution which might not be preferable for some users.
- It is first and foremost a framework that just happens to use Vite under the hood, not a Vite app. - It is first and foremost a framework that just happens to use Vite under the hood, not a Vite app.
This template contains as little as possible to get started with Vite + TypeScript + Svelte, while taking into account the developer experience with regards to HMR and intellisense. It demonstrates capabilities on par with the other `create-vite` templates and is a good starting point for beginners dipping their toes into a Vite + Svelte project. This template contains as little as possible to get started with Vite + TypeScript + Svelte, while taking into account the developer experience with regards to HMR and intellisense. It demonstrates capabilities on par with the other `create-vite` templates and is a good starting point for beginners dipping their toes into a Vite + Svelte project.
Should you later need the extended capabilities and extensibility provided by SvelteKit, the template has been structured similarly to SvelteKit so that it is easy to migrate. Should you later need the extended capabilities and extensibility provided by SvelteKit, the template has been structured similarly to SvelteKit so that it is easy to migrate.
**Why `global.d.ts` instead of `compilerOptions.types` inside `jsconfig.json` or `tsconfig.json`?** **Why `global.d.ts` instead of `compilerOptions.types` inside `jsconfig.json` or `tsconfig.json`?**
Setting `compilerOptions.types` shuts out all other types not explicitly listed in the configuration. Using triple-slash references keeps the default TypeScript setting of accepting type information from the entire workspace, while also adding `svelte` and `vite/client` type information. Setting `compilerOptions.types` shuts out all other types not explicitly listed in the configuration. Using triple-slash references keeps the default TypeScript setting of accepting type information from the entire workspace, while also adding `svelte` and `vite/client` type information.
**Why include `.vscode/extensions.json`?** **Why include `.vscode/extensions.json`?**
Other templates indirectly recommend extensions via the README, but this file allows VS Code to prompt the user to install the recommended extension upon opening the project. Other templates indirectly recommend extensions via the README, but this file allows VS Code to prompt the user to install the recommended extension upon opening the project.
**Why enable `allowJs` in the TS template?** **Why enable `allowJs` in the TS template?**
While `allowJs: false` would indeed prevent the use of `.js` files in the project, it does not prevent the use of JavaScript syntax in `.svelte` files. In addition, it would force `checkJs: false`, bringing the worst of both worlds: not being able to guarantee the entire codebase is TypeScript, and also having worse typechecking for the existing JavaScript. In addition, there are valid use cases in which a mixed codebase may be relevant. While `allowJs: false` would indeed prevent the use of `.js` files in the project, it does not prevent the use of JavaScript syntax in `.svelte` files. In addition, it would force `checkJs: false`, bringing the worst of both worlds: not being able to guarantee the entire codebase is TypeScript, and also having worse typechecking for the existing JavaScript. In addition, there are valid use cases in which a mixed codebase may be relevant.
**Why is HMR not preserving my local component state?** **Why is HMR not preserving my local component state?**
HMR state preservation comes with a number of gotchas! It has been disabled by default in both `svelte-hmr` and `@sveltejs/vite-plugin-svelte` due to its often surprising behavior. You can read the details [here](https://github.com/rixo/svelte-hmr#svelte-hmr). HMR state preservation comes with a number of gotchas! It has been disabled by default in both `svelte-hmr` and `@sveltejs/vite-plugin-svelte` due to its often surprising behavior. You can read the details [here](https://github.com/rixo/svelte-hmr#svelte-hmr).
If you have state that's important to retain within a component, consider creating an external store which would not be replaced by HMR. If you have state that's important to retain within a component, consider creating an external store which would not be replaced by HMR.
```ts ```ts
// store.ts // store.ts
// An extremely simple external store // An extremely simple external store
import {writable} from 'svelte/store' import { writable } from 'svelte/store'
export default writable(0) export default writable(0)
``` ```

View File

@@ -6,36 +6,15 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>AquaNet</title> <title>AquaNet</title>
<link <link rel="apple-touch-icon" sizes="180x180" href="/assets/icons/apple-touch-icon.png">
rel="apple-touch-icon" <link rel="icon" type="image/png" sizes="32x32" href="/assets/icons/favicon-32x32.png">
sizes="180x180" <link rel="icon" type="image/png" sizes="16x16" href="/assets/icons/favicon-16x16.png">
href="/assets/icons/apple-touch-icon.png" <link rel="manifest" href="/assets/icons/site.webmanifest">
/> <link rel="mask-icon" href="/assets/icons/safari-pinned-tab.svg" color="#b3c6ff">
<link <link rel="shortcut icon" href="/assets/icons/favicon.ico">
rel="icon" <meta name="msapplication-TileColor" content="#ffffff">
type="image/png" <meta name="msapplication-config" content="/assets/icons/browserconfig.xml">
sizes="32x32" <meta name="theme-color" content="#ffffff">
href="/assets/icons/favicon-32x32.png"
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href="/assets/icons/favicon-16x16.png"
/>
<link rel="manifest" href="/assets/icons/site.webmanifest" />
<link
rel="mask-icon"
href="/assets/icons/safari-pinned-tab.svg"
color="#b3c6ff"
/>
<link rel="shortcut icon" href="/assets/icons/favicon.ico" />
<meta name="msapplication-TileColor" content="#ffffff" />
<meta
name="msapplication-config"
content="/assets/icons/browserconfig.xml"
/>
<meta name="theme-color" content="#ffffff" />
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>

View File

@@ -8,21 +8,22 @@
"build": "vite build", "build": "vite build",
"preview": "vite preview", "preview": "vite preview",
"check": "svelte-check --tsconfig ./tsconfig.json", "check": "svelte-check --tsconfig ./tsconfig.json",
"format": "prettier --write ." "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",
"prettier": "^3.2.5", "eslint": "^8.56.0",
"prettier-plugin-svelte": "^3.1.2", "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": {

View File

@@ -1,19 +1,19 @@
{ {
"name": "", "name": "",
"short_name": "", "short_name": "",
"icons": [ "icons": [
{ {
"src": "/assets/icons/android-chrome-192x192.png", "src": "/assets/icons/android-chrome-192x192.png",
"sizes": "192x192", "sizes": "192x192",
"type": "image/png" "type": "image/png"
}, },
{ {
"src": "/assets/icons/android-chrome-512x512.png", "src": "/assets/icons/android-chrome-512x512.png",
"sizes": "512x512", "sizes": "512x512",
"type": "image/png" "type": "image/png"
} }
], ],
"theme_color": "#ffffff", "theme_color": "#ffffff",
"background_color": "#ffffff", "background_color": "#ffffff",
"display": "standalone" "display": "standalone"
} }

View File

@@ -1,11 +1,11 @@
<script lang="ts"> <script lang="ts">
import {Router, Route} from 'svelte-routing' import { Router, Route } from "svelte-routing";
import Home from './pages/Home.svelte' import Home from "./pages/Home.svelte";
import MaimaiRating from './pages/MaimaiRating.svelte' import MaimaiRating from "./pages/MaimaiRating.svelte";
import UserHome from './pages/UserHome.svelte' import UserHome from "./pages/UserHome.svelte";
import Icon from '@iconify/svelte' import Icon from '@iconify/svelte';
export let url = '' export let url = "";
</script> </script>
<nav> <nav>
@@ -53,4 +53,4 @@
@media (max-width: $w-mobile) @media (max-width: $w-mobile)
justify-content: center justify-content: center
</style> </style>

View File

@@ -2,4 +2,4 @@ export interface TrendEntry {
date: string date: string
rating: number rating: number
plays: number plays: number
} }

View File

@@ -1,54 +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) => return fetch(`${data_host}/maimai/meta/00/all-music.json`).then(it => it.json())
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) => Object.keys(params).forEach(key => url.searchParams.append(key, params[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( export async function getMaimaiUser(userId: number): Promise<MaimaiUserSummaryEntry> {
userId: number return await getMaimaiApi('user-summary', { userId })
): Promise<MaimaiUserSummaryEntry> { }
return await getMaimaiApi('user-summary', {userId})
}

View File

@@ -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;
} }

View File

@@ -6,17 +6,13 @@ import {
LineElement, LineElement,
LinearScale, LinearScale,
PointElement, PointElement,
CategoryScale, CategoryScale, TimeScale, type ChartOptions, type LineOptions,
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}`
@@ -35,72 +31,62 @@ export function registerChart() {
) )
} }
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: {
type: 'ghDay',
radius: 2,
width: 11,
height: 11,
gutter: 4,
},
range: 12,
data: {source: d, x: 'date', y: 'value'},
scale: {
color: {
type: 'linear',
range: ['#14432a', '#4dd05a'],
domain: [0, d.reduce((a, b) => Math.max(a, b.value), 0)],
},
},
date: {start: moment().subtract(1, 'year').add(1, 'month').toDate()},
theme: 'dark',
}, },
[ subDomain: {
[ type: 'ghDay',
CalTooltip, radius: 2, width: 11, height: 11, gutter: 4
{ },
text: (_: Date, v: number, d: any) => range: 12,
`${v ?? 'No'} songs played on ${d.format('MMMM D, YYYY')}`, data: { source: d, x: 'date', y: 'value' },
}, scale: {
], color: {
] type: 'linear',
) range: [ '#14432a', '#4dd05a' ],
domain: [ 0, d.reduce((a, b) => Math.max(a, b.value), 0) ]
},
},
date: { start: moment().subtract(1, 'year').add(1, 'month').toDate() },
theme: 'dark',
}, [
[ CalTooltip, { text: (_: Date, v: number, d: any) =>
`${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
}, }
}, },
} }
@@ -109,8 +95,6 @@ export const CHARTJS_OPT: ChartOptions<'line'> = {
* *
* @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) return Object.keys(obj).filter(k => obj[k]).join(' ')
.filter((k) => obj[k])
.join(' ')
} }

View File

@@ -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

View File

@@ -1,83 +1,83 @@
<main id="home" class="no-margin"> <main id="home" class="no-margin">
<h1>AquaNet</h1> <h1>AquaNet</h1>
<div class="btn-group"> <div class="btn-group">
<button>Login</button> <button>Login</button>
<button>Sign Up</button> <button>Sign Up</button>
</div> </div>
<div class="light-pollution"> <div class="light-pollution">
<div class="l1"></div> <div class="l1"></div>
<div class="l2"></div> <div class="l2"></div>
<div class="l3"></div> <div class="l3"></div>
</div> </div>
</main> </main>
<style lang="sass"> <style lang="sass">
@import "../vars" @import "../vars"
#home #home
color: $c-main color: $c-main
position: relative position: relative
width: 100% width: 100%
height: 100% height: 100%
padding-left: 100px padding-left: 100px
overflow: hidden overflow: hidden
box-sizing: border-box box-sizing: border-box
display: flex display: flex
flex-direction: column flex-direction: column
justify-content: center justify-content: center
margin-top: -$nav-height margin-top: -$nav-height
> h1 > h1
font-family: Quicksand, $font font-family: Quicksand, $font
user-select: none user-select: none
// Gap between text characters // Gap between text characters
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
.light-pollution .light-pollution
pointer-events: none pointer-events: none
opacity: 0.6 opacity: 0.6
> div > div
position: absolute position: absolute
z-index: -1 z-index: -1
.l1 .l1
left: -560px left: -560px
top: 90px top: 90px
height: 1130px height: 1130px
width: 1500px width: 1500px
$color: rgb(158, 110, 230) $color: rgb(158, 110, 230)
background: radial-gradient(50% 50% at 50% 50%, rgba($color, 0.28) 0%, rgba($color, 0) 100%) background: radial-gradient(50% 50% at 50% 50%, rgba($color, 0.28) 0%, rgba($color, 0) 100%)
.l2 .l2
left: -200px left: -200px
top: 560px top: 560px
height: 1200px height: 1200px
width: 1500px width: 1500px
$color: rgb(92, 195, 250) $color: rgb(92, 195, 250)
background: radial-gradient(50% 50% at 50% 50%, rgba($color, 0.28) 0%, rgba($color, 0) 100%) background: radial-gradient(50% 50% at 50% 50%, rgba($color, 0.28) 0%, rgba($color, 0) 100%)
.l3 .l3
left: -600px left: -600px
opacity: 0.7 opacity: 0.7
top: -630px top: -630px
width: 1500px width: 1500px
height: 1000px height: 1000px
$color: rgb(230, 110, 156) $color: rgb(230, 110, 156)
background: radial-gradient(50% 50% at 50% 50%, rgba($color, 0.28) 0%, rgba($color, 0) 100%) background: radial-gradient(50% 50% at 50% 50%, rgba($color, 0.28) 0%, rgba($color, 0) 100%)
@media (max-width: 500px) @media (max-width: 500px)
align-items: center align-items: center
padding-left: 0 padding-left: 0
</style> </style>

View File

@@ -1,219 +1,199 @@
<script lang="ts"> <script lang="ts">
import {data_host} from '../libs/config' import {data_host} from "../libs/config";
import {getMaimaiAllMusic, getMaimai, getMult} from '../libs/maimai' import {getMaimaiAllMusic, getMaimai, getMult} from "../libs/maimai";
import type {ParsedRating, Rating} from '../libs/maimaiTypes' import type {ParsedRating, Rating} from "../libs/maimaiTypes";
export let userId: any export let userId: any
userId = +userId userId = +userId
if (!userId) console.error('No user ID provided') if (!userId) console.error("No user ID provided")
Promise.all([ Promise.all([
getMaimai('GetUserRatingApi', {userId}), getMaimai("GetUserRatingApi", {userId}),
getMaimaiAllMusic(), getMaimaiAllMusic()
]).then(([rating, music]) => { ]).then(([rating, music]) => {
data = rating data = rating
musicInfo = music musicInfo = music
if (!data || !musicInfo) { if (!data || !musicInfo) {
console.error('Failed to fetch data') console.error("Failed to fetch data")
return return
} }
parsedRatings = { parsedRatings = {
old: parseRating(data.userRating.ratingList), old: parseRating(data.userRating.ratingList),
new: parseRating(data.userRating.newRatingList), new: parseRating(data.userRating.newRatingList)
} }
}) })
function parseRating(arr: Rating[]) { function parseRating(arr: Rating[]) {
return arr return arr.map(x => {
.map((x) => { const music = musicInfo[x.musicId]
const music = musicInfo[x.musicId]
if (!music) {
if (!music) { console.error(`Music not found: ${x.musicId}`)
console.error(`Music not found: ${x.musicId}`) return null
return null }
}
music.note = music.notes[x.level]
music.note = music.notes[x.level] const mult = getMult(x.achievement)
const mult = getMult(x.achievement) return {
return { ...x,
...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], }
} }).filter(x => x != null) as ParsedRating[]
}) }
.filter((x) => x != null) as ParsedRating[]
} let parsedRatings: {
old: ParsedRating[],
let parsedRatings: { new: ParsedRating[]
old: ParsedRating[] } | null = null
new: ParsedRating[]
} | null = null let data: {
userRating: {
let data: { rating: number,
userRating: { ratingList: Rating[],
rating: number newRatingList: Rating[]
ratingList: Rating[] }
newRatingList: Rating[] } | null = null
}
} | null = null let musicInfo: any = null
</script>
let musicInfo: any = null
</script> <main>
<!-- Display all parsed ratings -->
<main> {#if parsedRatings}
<!-- Display all parsed ratings --> {#each [{title: "Old", data: parsedRatings.old}, {title: "New", data: parsedRatings.new}] as section}
{#if parsedRatings} <h2>{section.title}</h2>
{#each [{title: 'Old', data: parsedRatings.old}, {title: 'New', data: parsedRatings.new}] as section} <div class="rating-cards">
<h2>{section.title}</h2> {#each section.data as rating}
<div class="rating-cards"> <div class="level-{rating.level}">
{#each section.data as rating} <img class="cover"
<div class="level-{rating.level}"> src={`${data_host}/maimai/assetbundle/jacket_s/00${rating.musicId.toString().padStart(6, '0').substring(2)}.png`}
<img alt="">
class="cover"
src={`${data_host}/maimai/assetbundle/jacket_s/00${rating.musicId <div class="detail">
.toString() <span class="name">{rating.music.name}</span>
.padStart(6, '0') <span class="rating">
.substring(2)}.png`} <span>{(rating.achievement / 10000).toFixed(2)}%</span>
alt="" <img class="rank" src={`${data_host}/maimai/sprites/rankimage/UI_GAM_Rank_${rating.rank}.png`} alt="">
/> </span>
<span>{rating.calc.toFixed(1)}</span>
<div class="detail"> </div>
<span class="name">{rating.music.name}</span> <img class="ver"
<span class="rating"> src={`${data_host}/maimai/sprites/tab/title/UI_CMN_TabTitle_MaimaiTitle_Ver${rating.music.ver.toString().substring(0, 3)}.png`}
<span>{(rating.achievement / 10000).toFixed(2)}%</span> alt="">
<img <div class="lv">{rating.music.note.lv}</div>
class="rank" </div>
src={`${data_host}/maimai/sprites/rankimage/UI_GAM_Rank_${rating.rank}.png`} {/each}
alt="" </div>
/> {/each}
</span> {/if}
<span>{rating.calc.toFixed(1)}</span> </main>
</div>
<img <style lang="sass">
class="ver" .rating-cards
src={`${data_host}/maimai/sprites/tab/title/UI_CMN_TabTitle_MaimaiTitle_Ver${rating.music.ver display: grid
.toString() gap: 2rem
.substring(0, 3)}.png`} width: 100%
alt=""
/> // Fill as many columns as possible
<div class="lv">{rating.music.note.lv}</div> grid-template-columns: repeat(auto-fill, minmax(200px, 1fr))
</div>
{/each} // Center the cards
</div> justify-items: center
{/each} align-items: center
{/if}
</main> // Style each card
> div
<style lang="sass"> $border-radius: 20px
.rating-cards width: 200px
display: grid height: 200px
gap: 2rem border-radius: $border-radius
width: 100%
display: flex
// Fill as many columns as possible position: relative
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr))
// Difficulty border
// Center the cards border: 5px solid var(--lv-color)
justify-items: center
align-items: center img
object-fit: cover
// Style each card pointer-events: none
> div
$border-radius: 20px img.cover
width: 200px width: 100%
height: 200px height: 100%
border-radius: $border-radius border-radius: calc($border-radius - 3px)
display: flex img.ver
position: relative position: absolute
top: -20px
// Difficulty border left: -30px
border: 5px solid var(--lv-color, #60aaff) height: 50px
&.level-1
--lv-color: #aaff60 // Information
&.level-2 .detail
--lv-color: #f25353 position: absolute
&.level-3 bottom: 0
--lv-color: #e881ff left: 0
right: 0
img padding: 10px
object-fit: cover background: rgba(0, 0, 0, 0.5)
pointer-events: none border-radius: 0 0 calc($border-radius - 3px) calc($border-radius - 3px)
img.cover // Blur
width: 100% backdrop-filter: blur(3px)
height: 100%
border-radius: calc($border-radius - 3px) display: flex
flex-direction: column
img.ver text-align: left
position: absolute
top: -20px > span
left: -30px // Disable text wrapping, max 2 lines
height: 50px overflow: hidden
text-overflow: ellipsis
// Information white-space: nowrap
.detail
position: absolute .name
bottom: 0 font-size: 1.2em
left: 0 font-weight: bold
right: 0
padding: 10px .rating
background: rgba(0, 0, 0, 0.5) display: flex
border-radius: 0 0 calc($border-radius - 3px) calc($border-radius - 3px)
img
// Blur height: 1.5em
backdrop-filter: blur(3px)
.lv
display: flex position: absolute
flex-direction: column bottom: 0
text-align: left right: 0
padding: 5px 10px
> span background: var(--lv-color)
// Disable text wrapping, max 2 lines // Top left border radius
overflow: hidden border-radius: 10px 0
text-overflow: ellipsis
white-space: nowrap font-size: 1.3em
.name &:before
font-size: 1.2em content: "Lv"
font-weight: bold font-size: 0.8em
.rating // Mobile
display: flex @media (max-width: 500px)
img margin-left: -1rem
height: 1.5em margin-right: -1rem
width: calc(100% + 2rem)
.lv grid-template-columns: repeat(auto-fill, minmax(130px, 1fr))
position: absolute font-size: 0.8em
bottom: 0 > div
right: 0 width: 150px
padding: 5px 10px height: 150px
background: var(--lv-color)
// Top left border radius img.ver
border-radius: 10px 0 height: 45px
left: -20px
font-size: 1.3em </style>
&: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>

View File

@@ -1,426 +1,398 @@
<script lang="ts"> <script lang="ts">
import {CHARTJS_OPT, clazz, registerChart, renderCal, title} from '../libs/ui' import {CHARTJS_OPT, clazz, registerChart, renderCal, title} from "../libs/ui";
import { import {getMaimaiAllMusic, getMaimaiTrend, getMaimaiUser, getMult} from "../libs/maimai";
getMaimaiAllMusic, import type {MaimaiMusic, MaimaiUserPlaylog, MaimaiUserSummaryEntry} from "../libs/maimaiTypes";
getMaimaiTrend, import type {TrendEntry} from "../libs/generalTypes";
getMaimaiUser, import {data_host} from "../libs/config";
getMult, import 'cal-heatmap/cal-heatmap.css';
} from '../libs/maimai' import { Line } from 'svelte-chartjs';
import type { import moment from "moment";
MaimaiMusic, import 'chartjs-adapter-moment';
MaimaiUserPlaylog,
MaimaiUserSummaryEntry, registerChart()
} from '../libs/maimaiTypes'
import type {TrendEntry} from '../libs/generalTypes' export let userId: any;
import {data_host} from '../libs/config' userId = +userId
import 'cal-heatmap/cal-heatmap.css' let calElement: HTMLElement
import {Line} from 'svelte-chartjs'
import moment from 'moment' title(`User ${userId}`)
import 'chartjs-adapter-moment'
interface MusicAndPlay extends MaimaiMusic, MaimaiUserPlaylog {}
registerChart()
let d: {
export let userId: any user: MaimaiUserSummaryEntry,
userId = +userId trend: TrendEntry[]
let calElement: HTMLElement recent: MusicAndPlay[]
} | null = null
title(`User ${userId}`)
Promise.all([
interface MusicAndPlay extends MaimaiMusic, MaimaiUserPlaylog {} getMaimaiUser(userId),
getMaimaiTrend(userId),
let d: { getMaimaiAllMusic()
user: MaimaiUserSummaryEntry ]).then(([user, trend, music]) => {
trend: TrendEntry[] console.log(user)
recent: MusicAndPlay[] console.log(trend)
} | null = null console.log(music)
Promise.all([ // Sort recent by date
getMaimaiUser(userId), user.recent.sort((a, b) => b.userPlayDate < a.userPlayDate ? -1 : 1)
getMaimaiTrend(userId),
getMaimaiAllMusic(), d = {user, trend, recent: user.recent.map(it => {return {...music[it.musicId], ...it}})}
]).then(([user, trend, music]) => { localStorage.setItem("tmp-user-details", JSON.stringify(d))
console.log(user) renderCal(calElement, trend.map(it => {return {date: it.date, value: it.plays}}))
console.log(trend) })
console.log(music) </script>
d = { <main id="user-home">
user, {#if d !== null}
trend, <div class="user-pfp">
recent: user.recent.map((it) => { <img src={`${data_host}/maimai/assetbundle/icon/${d.user.iconId.toString().padStart(6, "0")}.png`} alt="" class="pfp">
return {...music[it.musicId], ...it} <h2>{d.user.name}</h2>
}), </div>
}
localStorage.setItem('tmp-user-details', JSON.stringify(d)) <div>
renderCal( <h2>Rating Statistics</h2>
calElement, <div class="scoring-info">
trend.map((it) => { <div class="chart">
return {date: it.date, value: it.plays} <div class="info-top">
}) <div class="rating">
) <span>DX Rating</span>
}) <span>{d.user.rating.toLocaleString()}</span>
</script> </div>
<main id="user-home"> <div class="rank">
{#if d !== null} <span>Server Rank</span>
<div class="user-pfp"> <span>#{d.user.serverRank.toLocaleString()}</span>
<img </div>
src={`${data_host}/maimai/assetbundle/icon/${d.user.iconId.toString().padStart(6, '0')}.png`} </div>
alt=""
class="pfp" <div class="trend">
/> <!-- ChartJS cannot be fully responsive unless there is a parent div that's independent from its size and helps it determine its size -->
<h2>{d.user.name}</h2> <div class="chartjs-box-reference">
</div> <Line data={{
datasets: [
<div> {
<h2>Rating Statistics</h2> label: 'Rating',
<div class="scoring-info"> data: d.trend.map(it => {return {x: Date.parse(it.date), y: it.rating}}),
<div class="chart"> borderColor: '#646cff',
<div class="info-top"> tension: 0.1,
<div class="rating">
<span>DX Rating</span> // TODO: Set X axis span to 3 months
<span>{d.user.rating.toLocaleString()}</span> }
</div> ]
}} options={CHARTJS_OPT} />
<div class="rank"> </div>
<span>Server Rank</span> </div>
<span>#{d.user.serverRank.toLocaleString()}</span>
</div> <div class="info-bottom">
</div> {#each d.user.ranks as r}
<div>
<div class="trend"> <span>{r.name}</span>
<!-- ChartJS cannot be fully responsive unless there is a parent div that's independent from its size and helps it determine its size --> <span>{r.count}</span>
<div class="chartjs-box-reference"> </div>
<Line {/each}
data={{ </div>
datasets: [ </div>
{
label: 'Rating', <div class="other-info">
data: d.trend.map((it) => { <div class="accuracy">
return {x: Date.parse(it.date), y: it.rating} <span>Accuracy</span>
}), <span>{(d.user.accuracy / 10000).toFixed(2)}%</span>
borderColor: '#646cff', </div>
tension: 0.1,
<div class="max-combo">
// TODO: Set X axis span to 3 months <span>Max Combo</span>
}, <span>{d.user.maxCombo}</span>
], </div>
}}
options={CHARTJS_OPT} <div class="full-combo">
/> <span>Full Combo</span>
</div> <span>{d.user.fullCombo}</span>
</div> </div>
<div class="info-bottom"> <div class="all-perfect">
{#each d.user.ranks as r} <span>All Perfect</span>
<div> <span>{d.user.allPerfect}</span>
<span>{r.name}</span> </div>
<span>{r.count}</span>
</div> <div class="total-dx-score">
{/each} <span>DX Score</span>
</div> <span>{d.user.totalDxScore.toLocaleString()}</span>
</div> </div>
</div>
<div class="other-info"> </div>
<div class="accuracy"> </div>
<span>Accuracy</span>
<span>{(d.user.accuracy / 10000).toFixed(2)}%</span> <div>
</div> <h2>Play Activity</h2>
<div class="activity-info">
<div class="max-combo"> <div id="cal-heatmap" bind:this={calElement} />
<span>Max Combo</span>
<span>{d.user.maxCombo}</span> <div class="info-bottom">
</div> <div class="plays">
<span>Plays</span>
<div class="full-combo"> <span>{d.user.plays}</span>
<span>Full Combo</span> </div>
<span>{d.user.fullCombo}</span>
</div> <div class="time">
<span>Play Time</span>
<div class="all-perfect"> <span>{(d.user.totalPlayTime / 60 / 60).toFixed(1)} hr</span>
<span>All Perfect</span> </div>
<span>{d.user.allPerfect}</span>
</div> <div class="first-play">
<span>First Seen</span>
<div class="total-dx-score"> <span>{moment(d.user.joined).format("YYYY-MM-DD")}</span>
<span>DX Score</span> </div>
<span>{d.user.totalDxScore.toLocaleString()}</span>
</div> <div class="last-play">
</div> <span>Last Seen</span>
</div> <span>{moment(d.user.lastSeen).format("YYYY-MM-DD")}</span>
</div> </div>
<div> <div class="last-version">
<h2>Play Activity</h2> <span>Last Version</span>
<div class="activity-info"> <span>{d.user.lastVersion}</span>
<div id="cal-heatmap" bind:this={calElement} /> </div>
</div>
<div class="info-bottom"> </div>
<div class="plays"> </div>
<span>Plays</span>
<span>{d.user.plays}</span> <div class="recent">
</div> <h2>Recent Scores</h2>
<div class="scores">
<div class="time"> {#each d.recent as r, i}
<span>Play Time</span> <div class={clazz({alt: i % 2 === 0})}>
<span>{(d.user.totalPlayTime / 60 / 60).toFixed(1)} hr</span> <img src={`${data_host}/maimai/assetbundle/jacket_s/00${r.musicId.toString().padStart(6, '0').substring(2)}.png`} alt="">
</div> <div class="info">
<div>
<div class="first-play"> <span class="name">{r.name}</span>
<span>First Seen</span> </div>
<span>{moment(d.user.joined).format('YYYY-MM-DD')}</span> <div>
</div> <span class={`lv level-${r.level}`}>{r.notes[r.level].lv}</span>
<span class={"rank-" + ("" + getMult(r.achievement)[2])[0]}>
<div class="last-play"> <span class="rank-text">{("" + getMult(r.achievement)[2]).replace("p", "+")}</span>
<span>Last Seen</span> <span class="rank-num">{(r.achievement / 10000).toFixed(2)}%</span>
<span>{moment(d.user.lastSeen).format('YYYY-MM-DD')}</span> </span>
</div> <span class={"dx-change " + clazz({increased: r.afterDeluxRating - r.beforeDeluxRating > 0})}>
{r.afterDeluxRating - r.beforeDeluxRating}
<div class="last-version"> </span>
<span>Last Version</span> </div>
<span>{d.user.lastVersion}</span> </div>
</div> </div>
</div> {/each}
</div> </div>
</div> </div>
{:else}
<div class="recent"> <p>Loading...</p>
<h2>Recent Scores</h2> {/if}
<div class="scores"> </main>
{#each d.recent as r, i}
<div class={clazz({alt: i % 2 === 0})}> <style lang="sass">
<img @import "../vars"
src={`${data_host}/maimai/assetbundle/jacket_s/00${r.musicId.toString().padStart(6, '0').substring(2)}.png`}
alt="" $gap: 20px
/>
<div class="info"> #user-home
<span class="name">{r.name}</span> display: flex
<div> flex-direction: column
<span class={'rank-' + ('' + getMult(r.achievement)[2])[0]}> gap: $gap
<span class="rank-text" margin: 100px auto 0
>{('' + getMult(r.achievement)[2]).replace('p', '+')}</span padding: 0 32px 32px
> min-height: 100%
<span class="rank-num" max-width: $w-max
>{(r.achievement / 10000).toFixed(2)}%</span
> background-color: rgba(black, 0.2)
</span> border-radius: 16px 16px 0 0
<span
class={'dx-change ' + @media (max-width: #{$w-max + (64px) * 2})
clazz({ margin: 100px 32px 0
increased: r.afterDeluxRating - r.beforeDeluxRating > 0, padding: 0 32px 16px
})}
> @media (max-width: $w-mobile)
{r.afterDeluxRating - r.beforeDeluxRating} margin: 100px 0 0
</span> padding: 0 32px 16px
</div>
</div> .user-pfp
</div> display: flex
{/each} align-items: flex-end
</div> gap: $gap
</div> margin-top: -40px
{:else}
<p>Loading...</p> h2
{/if} font-size: 2rem
</main> margin: 0
<style lang="sass"> .pfp
@import "../vars" width: 100px
height: 100px
$gap: 20px border-radius: 5px
object-fit: cover
#user-home
display: flex .info-bottom, .info-top, .other-info
flex-direction: column display: flex
gap: $gap gap: $gap
margin: 100px auto 0
padding: 0 32px 32px > div
min-height: 100% display: flex
max-width: $w-max flex-direction: column
background-color: rgba(black, 0.2) > span:first-child
border-radius: 16px 16px 0 0 font-weight: bold
font-size: 0.8rem
@media (max-width: #{$w-max + (64px) * 2})
margin: 100px 32px 0 // character spacing
padding: 0 32px 16px letter-spacing: 0.1em
color: $c-main
@media (max-width: $w-mobile)
margin: 100px 0 0 .info-top > div > span:last-child
padding: 0 32px 16px font-size: 1.5rem
.user-pfp .scoring-info
display: flex display: flex
align-items: flex-end gap: $gap
gap: $gap max-height: 250px
margin-top: -40px
.chart
h2 flex: 0 1 790px
font-size: 2rem display: flex
margin: 0 flex-direction: column
.pfp .other-info
width: 100px flex: 1 0 100px
height: 100px flex-direction: column
border-radius: 5px gap: 0
object-fit: cover justify-content: space-between
.info-bottom, .info-top, .other-info .trend
display: flex height: 300px
gap: $gap width: 100%
max-width: 790px
> div
display: flex position: relative
flex-direction: column
> .chartjs-box-reference
> span:first-child position: absolute
font-weight: bold inset: 0
font-size: 0.8rem
@media (max-width: $w-mobile)
// character spacing flex-direction: column
letter-spacing: 0.1em max-height: unset
color: $c-main
.chart
.info-top > div > span:last-child flex: 0
font-size: 1.5rem
.trend
.scoring-info max-height: 130px
display: flex
gap: $gap .other-info
max-height: 250px > div
flex-direction: row
.chart justify-content: space-between
flex: 0 1 790px
display: flex .info-bottom
flex-direction: column justify-content: space-between
.other-info .activity-info
flex: 1 0 100px display: flex
flex-direction: column flex-direction: column
gap: 0 gap: $gap
justify-content: space-between
#cal-heatmap
.trend overflow-x: auto
height: 300px
width: 100% @media (max-width: $w-mobile)
max-width: 790px #cal-heatmap
width: 100%
position: relative
.info-bottom
> .chartjs-box-reference flex-direction: column
position: absolute gap: 0
inset: 0
> div
@media (max-width: $w-mobile) flex-direction: row
flex-direction: column justify-content: space-between
max-height: unset
// Recent Scores section
.chart .recent
flex: 0 .scores
display: flex
.trend flex-direction: column
max-height: 130px flex-wrap: wrap
gap: $gap
.other-info
> div > div.alt
flex-direction: row background-color: rgba(white, 0.03)
justify-content: space-between border-radius: 10px
.info-bottom // Image and song info
justify-content: space-between > div
display: flex
.activity-info align-items: center
display: flex gap: $gap
flex-direction: column padding-right: 16px
gap: $gap max-width: 100%
box-sizing: border-box
#cal-heatmap
overflow-x: auto img
width: 50px
@media (max-width: $w-mobile) height: 50px
#cal-heatmap border-radius: 10px
width: 100% object-fit: cover
.info-bottom // Song info and score
flex-direction: column > div
gap: 0 flex: 1
display: flex
> div justify-content: space-between
flex-direction: row
justify-content: space-between // Limit song name to one line
overflow: hidden
// Recent Scores section .name
.recent overflow: hidden
.scores overflow-wrap: anywhere
display: flex white-space: nowrap
flex-direction: column text-overflow: ellipsis
flex-wrap: wrap
gap: $gap @media (max-width: $w-mobile)
flex-direction: column
> div.alt gap: 0
background-color: rgba(white, 0.03)
border-radius: 10px .rank-text
text-align: left
// Image and song info
> div .rank-S
display: flex // Gold green gradient on text
align-items: center background: linear-gradient(90deg, #ffee94, #ffb798, #ffa3e5, #ebff94)
gap: $gap -webkit-background-clip: text
padding-right: 16px color: transparent
max-width: 100%
box-sizing: border-box .rank-A
color: #ff8a8a
img
width: 50px .rank-B
height: 50px color: #6ba6ff
border-radius: 10px
object-fit: cover .lv
background: var(--lv-color)
// Song info and score padding: 0 6px
> div border-radius: 10px
flex: 1 opacity: 0.8
display: flex margin-right: 10px
justify-content: space-between
span
// Limit song name to one line display: inline-block
overflow: hidden text-align: right
.name
overflow: hidden // Vertical table-like alignment
overflow-wrap: anywhere span.rank-text
white-space: nowrap min-width: 30px
text-overflow: ellipsis span.rank-num
min-width: 60px
@media (max-width: $w-mobile) span.dx-change
flex-direction: column min-width: 30px
gap: 0
span.increased
span &:before
text-align: left content: "+"
color: $c-good
.rank-S </style>
// Gold green gradient on text
background: linear-gradient(90deg, #ffee94, #ffb798, #ffa3e5, #ebff94)
-webkit-background-clip: text
color: transparent
.rank-A
color: #ff8a8a
.rank-B
color: #6ba6ff
span
display: inline-block
text-align: right
// Vertical table-like alignment
span.rank-text
min-width: 30px
span.rank-num
min-width: 60px
span.dx-change
min-width: 30px
span.increased
&:before
content: "+"
color: $c-good
</style>

View File

@@ -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

View File

@@ -1,4 +1,4 @@
import {vitePreprocess} from '@sveltejs/vite-plugin-svelte' import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
export default { export default {
// Consult https://svelte.dev/docs#compile-time-svelte-preprocess // Consult https://svelte.dev/docs#compile-time-svelte-preprocess

View File

@@ -16,5 +16,5 @@
"isolatedModules": true "isolatedModules": true
}, },
"include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"], "include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"],
"references": [{"path": "./tsconfig.node.json"}] "references": [{ "path": "./tsconfig.node.json" }]
} }

View File

@@ -1,5 +1,5 @@
import {defineConfig} from 'vite' import { defineConfig } from 'vite'
import {svelte} from '@sveltejs/vite-plugin-svelte' import { svelte } from '@sveltejs/vite-plugin-svelte'
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig({

File diff suppressed because it is too large Load Diff

View File

@@ -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

View File

@@ -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

View File

@@ -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
View File

@@ -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

View File

@@ -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;
} }

View File

@@ -255,4 +255,9 @@ public class UserPlaylog implements Serializable {
private int extNum2; private int extNum2;
private int extNum4;
@JsonProperty("extBool1")
private boolean extBool1;
} }

View File

@@ -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');

View File

@@ -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');

View File

@@ -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');

View 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;

View File

@@ -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;

View File

@@ -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');

View File

@@ -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');

View File

@@ -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');

View 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;

View File

@@ -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;

View File

@@ -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');

View File

@@ -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');

View File

@@ -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');

View File

@@ -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;

View File

@@ -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;