Merge branch 'v1-dev' into pr/99

pull/99/head
Azalea 2025-01-05 06:54:15 -05:00
commit 3a69717a9d
122 changed files with 1592 additions and 3401 deletions

View File

@ -92,7 +92,7 @@
user = u
return fetchData()
}).catch((e) => { loading = false; error = e.message });
let DDSreader: DDS | undefined;
let USERBOX_PROGRESS = 0;
@ -150,7 +150,7 @@
if (databaseExists || USERBOX_URL_STATE.value) {
DDSreader = new DDS(ddsDB);
USERBOX_INSTALLED = databaseExists || USERBOX_URL_STATE.value != "";
}
}
})
</script>
@ -183,10 +183,10 @@
</div>
{:else}
<div class="chuni-userbox-container">
<ChuniUserplateComponent chuniIsUserbox={true} on:click={() => userboxSelected = "nameplateId"} chuniCharacter={userbox.characterId} chuniLevel={userbox.level.toString()} chuniRating={userbox.playerRating / 100}
<ChuniUserplateComponent chuniIsUserbox={true} on:click={() => userboxSelected = "nameplateId"} chuniCharacter={userbox.characterId} chuniLevel={userbox.level.toString()} chuniRating={userbox.playerRating / 100}
chuniNameplate={userbox.nameplateId} chuniName={userbox.userName} chuniTrophyName={allItems.trophy[userbox.trophyId].name}></ChuniUserplateComponent>
<ChuniPenguinComponent chuniWear={userbox.avatarWear} chuniHead={userbox.avatarHead} chuniBack={userbox.avatarBack}
chuniFront={userbox.avatarFront} chuniFace={userbox.avatarFace} chuniItem={userbox.avatarItem}
<ChuniPenguinComponent chuniWear={userbox.avatarWear} chuniHead={userbox.avatarHead} chuniBack={userbox.avatarBack}
chuniFront={userbox.avatarFront} chuniFace={userbox.avatarFace} chuniItem={userbox.avatarItem}
chuniSkin={userbox.avatarSkin}></ChuniPenguinComponent>
</div>
<div class="chuni-userbox-row">
@ -258,26 +258,11 @@
<p>
<button on:click={() => USERBOX_SETUP_RUN = !USERBOX_SETUP_RUN}>{t(!USERBOX_INSTALLED ? `userbox.new.activate_first` : `userbox.new.activate_update`)}</button>
</p>
{/if}
{/if}
<ChuniMatchingSettings/>
<!--{#if !USERBOX_SUPPORT || !USERBOX_INSTALLED || !USERBOX_ENABLED.value}
<h2>{t("userbox.header.preview")}</h2>
<p class="notice">{t("userbox.preview.notice")}</p>
<input bind:value={preview} placeholder={t("userbox.preview.url")}/>
{#if preview}
<div class="preview">
{#each userItems.filter(v => v.iKey != 'trophy' && v.iKey != 'systemVoice') as { iKey, ubKey, items }, i}
<div>
<span>{ts(`userbox.${ubKey}`)}</span>
<img src={`${preview}/${iKey}/${userbox[ubKey].toString().padStart(8, '0')}.png`} alt="" on:error={coverNotFound} />
</div>
{/each}
</div>
{/if}
{/if}-->
</div>
{/if}
{#if USERBOX_SETUP_RUN && !error}
<div class="overlay" transition:fade>
<div>
@ -328,7 +313,7 @@ p.notice
opacity: 0.6
margin-top: 0
.progress
.progress
width: 100%
height: 10px
box-shadow: 0 0 1px 1px vars.$ov-lighter
@ -463,10 +448,10 @@ p.notice
&.focused
filter: brightness(75%)
.chuni-userbox
.chuni-userbox
width: calc(100% - 20px)
height: 350px
display: flex
flex-direction: row
flex-wrap: wrap

View File

@ -161,4 +161,4 @@ export interface ChusanMatchingOption {
matching: string
reflector: string
coop: string[]
}
}

View File

@ -102,7 +102,7 @@ const zhHome: typeof EN_REF_HOME = {
'home.linkcard.account-card': "账户卡",
'home.linkcard.registered': "注册于",
'home.linkcard.lastused': "上次使用",
'home.linkcard.enter-info': "请输入以下信息",
'home.linkcard.enter-info': "请输入以下信息,或将 aime.txt / felica.txt 文件拖放到此区域",
'home.linkcard.access-code': "卡背面的20位卡号 (如果没有, 请尝试在游戏中扫描您的卡, 并输入屏幕上显示的卡号)",
'home.linkcard.enter-sn1': "在您的手机",
'home.linkcard.enter-sn2': "上下载 NFC Tools 并扫描您的卡。然后输入显示的 SN 号。",
@ -148,10 +148,14 @@ const zhSettings: typeof EN_REF_SETTINGS = {
'settings.fields.waccaInfiniteWp.desc': '将 WP 设置为 999999',
'settings.fields.waccaAlwaysVip.name': 'Wacca: 永久会员',
'settings.fields.waccaAlwaysVip.desc': '将 VIP 到期时间设置为 2077-01-01',
'settings.fields.chusanTeamName.name': '中二: 队伍名称',
'settings.fields.chusanTeamName.name': '队伍名称',
'settings.fields.chusanTeamName.desc': '自定义显示在个人资料顶部的文本。',
'settings.fields.chusanInfinitePenguins.name': '中二: 无限企鹅',
'settings.fields.chusanInfinitePenguins.name': '我是桐谷遥',
'settings.fields.chusanInfinitePenguins.desc': '将角色界限突破的企鹅雕像数量设置为 999。',
'settings.fields.chusanMatchingReflector.name': '全国对战 Reflector',
'settings.fields.chusanMatchingReflector.desc': '全国对战服务器的 UDP 反射服务器的 URL',
'settings.fields.chusanMatchingServer.name': '全国对战服务器',
'settings.fields.chusanMatchingServer.desc': '全国对战服务器的 URL',
'settings.fields.rounding.name': '分数舍入',
'settings.fields.rounding.desc': '把分数四舍五入到一位小数',
'settings.fields.optOutOfLeaderboard.name': '不参与排行榜',
@ -168,10 +172,12 @@ const zhSettings: typeof EN_REF_SETTINGS = {
'settings.profile.unset': '未设置',
'settings.profile.unchanged': '未更改',
'settings.export': '导出玩家数据',
'settings.cabNotice': '注意:下面这些设置只会影响你自己的机器,如果你是在其他人的机器上玩的话,请联系机主来改设置'
}
export const zhUserbox: typeof EN_REF_USERBOX = {
'userbox.header.general': '游戏设置',
'userbox.header.matching': '全国对战',
'userbox.header.userbox': 'UserBox 设置',
'userbox.header.preview': 'UserBox 预览',
'userbox.nameplateId': '名牌',
@ -189,7 +195,15 @@ export const zhUserbox: typeof EN_REF_USERBOX = {
'userbox.preview.notice': '「生存战略」:为了尊重版权,我们不会提供游戏内物品的图片。但是如果你认识其他愿意提供图床的人,在这里输入 URL 就可以显示出预览。',
'userbox.preview.url': '图床 URL',
'userbox.error.nodata': '未找到中二数据',
'userbox.matching.select': '选择对战服务器',
'userbox.matching.select.sub': '选择你想加入的跨服全国对战服务器',
'userbox.matching.option.ui': '房间列表',
'userbox.matching.option.guide': '教程',
'userbox.matching.option.collab': '合作伙伴',
'userbox.matching.custom.name': '自定义',
'userbox.matching.custom.sub': '输入其他的匹配 URL',
'userbox.new.name': 'AquaBox',
'userbox.new.setup': '将 ChuniLumi 或更高版本)的游戏文件夹拖放到下方区域,以显示带有名牌和头像的 UserBox。所有文件都在浏览器中处理。',
'userbox.new.setup.processing_file': '正在处理文件',

View File

@ -312,4 +312,6 @@ export const SETTING = {
post('/api/v2/settings/get', {}),
set: (key: string, value: any) =>
post('/api/v2/settings/set', { key, value: `${value}` }),
detailSet: (game: string, field: string, value: any) =>
post(`/api/v2/game/${game}/user-detail-set`, { field, value }),
}

View File

@ -25,7 +25,7 @@ const validateDirectories = async (base: FileSystemDirectoryEntry, path: string)
let newDirectory = await getDirectory(directory, part).catch(_ => null);
if (newDirectory && isDirectory(newDirectory)) {
directory = newDirectory;
} else
} else
return false;
};
return true
@ -38,7 +38,7 @@ const getDirectoryFromPath = async (base: FileSystemDirectoryEntry, path: string
let newDirectory = await getDirectory(directory, part).catch(_ => null);
if (newDirectory && isDirectory(newDirectory)) {
directory = newDirectory;
} else
} else
return null;
};
return directory;
@ -81,7 +81,7 @@ const DIRECTORY_PATHS = ([
processName: "Surfboard Textures",
useFileName: true,
path: "surfboard",
filter: (name: string) =>
filter: (name: string) =>
([
"CHU_UI_Common_Avatar_body_00.dds",
"CHU_UI_Common_Avatar_face_00.dds",
@ -134,7 +134,7 @@ export const scanOptionFolder = async (optionFolder: FileSystemDirectoryEntry, p
let objectStore = transaction.objectStore('dds');
for (let object of data)
objectStore.put(object)
// await transaction completion
await new Promise(r => transaction.addEventListener("complete", r, {once: true}))
};
@ -163,7 +163,7 @@ export function initializeDb() : Promise<void> {
export async function userboxFileProcess(folder: FileSystemEntry, progressUpdate: (progress: number, progressString: string) => void): Promise<string | null> {
if (!isDirectory(folder))
return t("userbox.new.error.invalidFolder")
if (!(await validateDirectories(folder, "bin/option")) || !(await validateDirectories(folder, "data/A000")))
if (!(await validateDirectories(folder, "bin/option")) && !(await validateDirectories(folder, "data/A000")))
return t("userbox.new.error.invalidFolder");
initializeDb();
@ -179,4 +179,4 @@ export async function userboxFileProcess(folder: FileSystemEntry, progressUpdate
location.reload();
return null
}
}

View File

@ -161,13 +161,10 @@
let inputAC = ""
let errorAC = ""
function inputACChange(e: any) {
e = e as InputEvent
function inputACChange() {
// Add spaces to the input
const old = inputAC
if (e.inputType === "insertText" && inputAC.length % 5 === 4 && inputAC.length < 24)
inputAC += " "
inputAC = inputAC.slice(0, 24)
inputAC = inputAC.replace(/\D/g, '').replace(/(.{4})/g, '$1 ').replace(/ $/, '')
if (inputAC !== old) errorAC = ""
}
@ -176,13 +173,10 @@
let inputSN = ""
let errorSN = ""
function inputSNChange(e: any) {
e = e as InputEvent
function inputSNChange() {
// Add colons to the input
const old = inputSN
if (e.inputType === "insertText" && inputSN.length % 3 === 2 && inputSN.length < 23)
inputSN += ":"
inputSN = inputSN.toUpperCase().slice(0, 23)
inputSN = inputSN.toUpperCase().replace(/[^0-9A-F]/g, '').replace(/(.{2})/g, '$1:').replace(/:$/, '')
if (inputSN !== old) errorSN = ""
}
@ -209,9 +203,29 @@
function isInput(e: KeyboardEvent) {
return e.key.length === 1 && !e.altKey && !e.ctrlKey && !e.metaKey && !e.shiftKey
}
async function dropFile(e: DragEvent) {
e.preventDefault()
e.stopPropagation()
const file = e.dataTransfer?.files[0]
if (!file) return
switch (file.name.toLowerCase()) {
case "aime.txt":
inputSN = ""
inputAC = await file.text()
inputACChange()
break
case "felica.txt":
inputAC = ""
inputSN = await file.text()
inputSNChange()
break
}
}
</script>
<div class="link-card">
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div class="link-card" on:drop={dropFile} on:dragover={(e) => e.preventDefault()}>
<h2>{t('home.linkcard.cards')}</h2>
<p>{t('home.linkcard.description')}:</p>

View File

@ -195,7 +195,7 @@
<div class="rank">
<span>{t('UserHome.ServerRank')}</span>
<span>#{+d.user.serverRank.toLocaleString() + 1}</span>
<span>#{(d.user.serverRank + 1).toLocaleString()}</span>
</div>
</div>

View File

@ -1,251 +1,255 @@
<script lang="ts">
import { Turnstile } from "svelte-turnstile";
import { slide } from 'svelte/transition';
import { TURNSTILE_SITE_KEY } from "../libs/config";
import Icon from "@iconify/svelte";
import { USER } from "../libs/sdk";
import { t } from "../libs/i18n"
let params = new URLSearchParams(window.location.search)
let state = "home"
$: isSignup = state === "signup"
let submitting = false
let email = ""
let password = ""
let username = ""
let turnstile = ""
let turnstileReset: () => void | undefined;
let error = ""
let verifyMsg = ""
if (params.get('confirm-email')) {
state = 'verify'
verifyMsg = t("welcome.verifying")
submitting = true
// Send request to server
USER.confirmEmail(params.get('confirm-email')!)
.then(() => {
verifyMsg = t('welcome.verified')
submitting = false
// Clear the query param
window.history.replaceState({}, document.title, window.location.pathname)
})
.catch(e => verifyMsg = t('welcome.verification-failed', { message: e.message }))
}
async function submit(): Promise<any> {
submitting = true
// Check if username and password are valid
if (email === "" || password === "") {
error = t("welcome.email-password-missing")
return submitting = false
}
if (turnstile === "") {
// Sleep for 100ms to allow Turnstile to finish
error = t("welcome.waiting-turnstile")
return setTimeout(submit, 100)
}
// Signup
if (isSignup) {
if (username === "") {
error = t("welcome.username-missing")
return submitting = false
}
// Send request to server
await USER.register({ username, email, password, turnstile })
.then(() => {
// Show verify email message
state = 'verify'
verifyMsg = t("welcome.verification-sent", { email })
})
.catch(e => {
error = e.message
submitting = false
turnstileReset()
})
}
else {
// Send request to server
await USER.login({ email, password, turnstile })
.then(() => window.location.href = "/home")
.catch(e => {
if (e.message === 'Email not verified - STATE_0') {
state = 'verify'
verifyMsg = t("welcome.verify-state-0")
}
else if (e.message === 'Email not verified - STATE_1') {
state = 'verify'
verifyMsg = t("welcome.verify-state-1")
}
else if (e.message === 'Email not verified - STATE_2') {
state = 'verify'
verifyMsg = t("welcome.verify-state-2")
}
else {
error = e.message
submitting = false
turnstileReset()
}
})
}
submitting = false
}
</script>
<main id="home" class="no-margin">
<div>
<h1 id="title">AquaNet</h1>
{#if state === "home"}
<div class="btn-group" transition:slide>
<button on:click={() => state = 'login'}>{t('welcome.btn-login')}</button>
<button on:click={() => state = 'signup'}>{t('welcome.btn-signup')}</button>
</div>
{:else if state === "login" || state === "signup"}
<div class="login-form" transition:slide>
{#if error}
<span class="error">{error}</span>
{/if}
<div on:click={() => state = 'home'} on:keypress={() => state = 'home'}
role="button" tabindex="0" class="clickable">
<Icon icon="line-md:chevron-small-left" />
<span>{t('back')}</span>
</div>
{#if isSignup}
<input type="text" placeholder={t('username')} bind:value={username}>
{/if}
<input type="email" placeholder={t('email')} bind:value={email}>
<input type="password" placeholder={t('password')} bind:value={password}>
<button on:click={submit}>
{#if submitting}
<Icon icon="line-md:loading-twotone-loop"/>
{:else}
{isSignup ? t('welcome.btn-signup') : t('welcome.btn-login')}
{/if}
</button>
<Turnstile siteKey={TURNSTILE_SITE_KEY} bind:reset={turnstileReset}
on:turnstile-callback={e => console.log(turnstile = e.detail.token)}
on:turnstile-error={_ => console.log(error = t("welcome.turnstile-error"))}
on:turnstile-expired={_ => window.location.reload()}
on:turnstile-timeout={_ => console.log(error = t('welcome.turnstile-timeout'))} />
</div>
{:else if state === "verify"}
<div class="login-form" transition:slide>
<span>{verifyMsg}</span>
{#if !submitting}
<button on:click={() => state = 'home'} transition:slide>{t('back')}</button>
{/if}
</div>
{/if}
</div>
<div class="light-pollution">
<div class="l1"></div>
<div class="l2"></div>
<div class="l3"></div>
</div>
</main>
<style lang="sass">
@use "../vars"
.login-form
display: flex
flex-direction: column
gap: 8px
width: calc(100% - 12px)
max-width: 300px
div.clickable
display: flex
align-items: center
#home
color: vars.$c-main
position: relative
width: 100%
height: 100%
padding-left: 100px
overflow: hidden
background-color: black
box-sizing: border-box
display: flex
flex-direction: column
justify-content: center
margin-top: -(vars.$nav-height)
// Content container
> div
display: flex
flex-direction: column
align-items: flex-start
width: max-content
// Switching state container
> div
transition: vars.$transition
#title
font-family: Quicksand, vars.$font
user-select: none
// Gap between text characters
letter-spacing: 0.2em
margin-top: 0
margin-bottom: 32px
opacity: 0.9
.btn-group
display: flex
gap: 8px
.light-pollution
pointer-events: none
opacity: 0.8
> div
position: absolute
z-index: 1
.l1
left: -560px
top: 90px
height: 1130px
width: 1500px
$color: rgb(158, 110, 230)
background: radial-gradient(50% 50% at 50% 50%, rgba($color, 0.28) 0%, rgba(0,0,0,0) 100%)
.l2
left: -200px
top: 560px
height: 1200px
width: 1500px
$color: rgb(92, 195, 250)
background: radial-gradient(50% 50% at 50% 50%, rgba($color, 0.28) 0%, rgba(0,0,0,0) 100%)
.l3
left: -600px
opacity: 0.7
top: -630px
width: 1500px
height: 1000px
$color: rgb(230, 110, 156)
background: radial-gradient(50% 50% at 50% 50%, rgba($color, 0.28) 0%, rgba(0,0,0,0) 100%)
@media (max-width: 500px)
align-items: center
padding-left: 0
</style>
<script lang="ts">
import { Turnstile } from "svelte-turnstile";
import { slide } from 'svelte/transition';
import { TURNSTILE_SITE_KEY } from "../libs/config";
import Icon from "@iconify/svelte";
import { USER } from "../libs/sdk";
import { t } from "../libs/i18n"
let params = new URLSearchParams(window.location.search)
let state = "home"
$: isSignup = state === "signup"
let submitting = false
let email = ""
let password = ""
let username = ""
let turnstile = ""
let turnstileReset: () => void | undefined;
let error = ""
let verifyMsg = ""
if (USER.isLoggedIn()) {
window.location.href = "/home"
}
if (params.get('confirm-email')) {
state = 'verify'
verifyMsg = t("welcome.verifying")
submitting = true
// Send request to server
USER.confirmEmail(params.get('confirm-email')!)
.then(() => {
verifyMsg = t('welcome.verified')
submitting = false
// Clear the query param
window.history.replaceState({}, document.title, window.location.pathname)
})
.catch(e => verifyMsg = t('welcome.verification-failed', { message: e.message }))
}
async function submit(): Promise<any> {
submitting = true
// Check if username and password are valid
if (email === "" || password === "") {
error = t("welcome.email-password-missing")
return submitting = false
}
if (turnstile === "") {
// Sleep for 100ms to allow Turnstile to finish
error = t("welcome.waiting-turnstile")
return setTimeout(submit, 100)
}
// Signup
if (isSignup) {
if (username === "") {
error = t("welcome.username-missing")
return submitting = false
}
// Send request to server
await USER.register({ username, email, password, turnstile })
.then(() => {
// Show verify email message
state = 'verify'
verifyMsg = t("welcome.verification-sent", { email })
})
.catch(e => {
error = e.message
submitting = false
turnstileReset()
})
}
else {
// Send request to server
await USER.login({ email, password, turnstile })
.then(() => window.location.href = "/home")
.catch(e => {
if (e.message === 'Email not verified - STATE_0') {
state = 'verify'
verifyMsg = t("welcome.verify-state-0")
}
else if (e.message === 'Email not verified - STATE_1') {
state = 'verify'
verifyMsg = t("welcome.verify-state-1")
}
else if (e.message === 'Email not verified - STATE_2') {
state = 'verify'
verifyMsg = t("welcome.verify-state-2")
}
else {
error = e.message
submitting = false
turnstileReset()
}
})
}
submitting = false
}
</script>
<main id="home" class="no-margin">
<div>
<h1 id="title">AquaNet</h1>
{#if state === "home"}
<div class="btn-group" transition:slide>
<button on:click={() => state = 'login'}>{t('welcome.btn-login')}</button>
<button on:click={() => state = 'signup'}>{t('welcome.btn-signup')}</button>
</div>
{:else if state === "login" || state === "signup"}
<div class="login-form" transition:slide>
{#if error}
<span class="error">{error}</span>
{/if}
<div on:click={() => state = 'home'} on:keypress={() => state = 'home'}
role="button" tabindex="0" class="clickable">
<Icon icon="line-md:chevron-small-left" />
<span>{t('back')}</span>
</div>
{#if isSignup}
<input type="text" placeholder={t('username')} bind:value={username}>
{/if}
<input type="email" placeholder={t('email')} bind:value={email}>
<input type="password" placeholder={t('password')} bind:value={password}>
<button on:click={submit}>
{#if submitting}
<Icon icon="line-md:loading-twotone-loop"/>
{:else}
{isSignup ? t('welcome.btn-signup') : t('welcome.btn-login')}
{/if}
</button>
<Turnstile siteKey={TURNSTILE_SITE_KEY} bind:reset={turnstileReset}
on:turnstile-callback={e => console.log(turnstile = e.detail.token)}
on:turnstile-error={_ => console.log(error = t("welcome.turnstile-error"))}
on:turnstile-expired={_ => window.location.reload()}
on:turnstile-timeout={_ => console.log(error = t('welcome.turnstile-timeout'))} />
</div>
{:else if state === "verify"}
<div class="login-form" transition:slide>
<span>{verifyMsg}</span>
{#if !submitting}
<button on:click={() => state = 'home'} transition:slide>{t('back')}</button>
{/if}
</div>
{/if}
</div>
<div class="light-pollution">
<div class="l1"></div>
<div class="l2"></div>
<div class="l3"></div>
</div>
</main>
<style lang="sass">
@use "../vars"
.login-form
display: flex
flex-direction: column
gap: 8px
width: calc(100% - 12px)
max-width: 300px
div.clickable
display: flex
align-items: center
#home
color: vars.$c-main
position: relative
width: 100%
height: 100%
padding-left: 100px
overflow: hidden
background-color: black
box-sizing: border-box
display: flex
flex-direction: column
justify-content: center
margin-top: -(vars.$nav-height)
// Content container
> div
display: flex
flex-direction: column
align-items: flex-start
width: max-content
// Switching state container
> div
transition: vars.$transition
#title
font-family: Quicksand, vars.$font
user-select: none
// Gap between text characters
letter-spacing: 0.2em
margin-top: 0
margin-bottom: 32px
opacity: 0.9
.btn-group
display: flex
gap: 8px
.light-pollution
pointer-events: none
opacity: 0.8
> div
position: absolute
z-index: 1
.l1
left: -560px
top: 90px
height: 1130px
width: 1500px
$color: rgb(158, 110, 230)
background: radial-gradient(50% 50% at 50% 50%, rgba($color, 0.28) 0%, rgba(0,0,0,0) 100%)
.l2
left: -200px
top: 560px
height: 1200px
width: 1500px
$color: rgb(92, 195, 250)
background: radial-gradient(50% 50% at 50% 50%, rgba($color, 0.28) 0%, rgba(0,0,0,0) 100%)
.l3
left: -600px
opacity: 0.7
top: -630px
width: 1500px
height: 1000px
$color: rgb(230, 110, 156)
background: radial-gradient(50% 50% at 50% 50%, rgba($color, 0.28) 0%, rgba(0,0,0,0) 100%)
@media (max-width: 500px)
align-items: center
padding-left: 0
</style>

View File

@ -16,7 +16,7 @@ Below is a list of games supported by this server.
| Game | Ver | Codename | Thanks to |
|----------------------------|------|---------------|--------------------------------------------|
| SDHD: CHUNITHM (Chusan) | 2.27 | LUMINOUS PLUS | [@rinsama](https://github.com/mxihan) |
| SDEZ: MaiMai DX | 1.40 | BUDDiES | [@肥宅虾哥](https://github.com/FeiZhaixiage) |
| SDEZ: MaiMai DX | 1.45 | BUDDiES PLUS | [@肥宅虾哥](https://github.com/FeiZhaixiage) |
| SDGA: MaiMai DX (International) | 1.45 | BUDDiES PLUS | [@Clansty](https://github.com/clansty) |
| SDED: Card Maker | 1.39 | | [@Becods](https://github.com/Becods) |
| SBZV: Project DIVA Arcade | 7.10 | Future Tone | |

View File

@ -13,8 +13,8 @@ plugins {
kotlin("plugin.jpa") version ktVer
kotlin("plugin.serialization") version ktVer
kotlin("plugin.allopen") version ktVer
id("io.freefair.lombok") version "8.11"
id("org.springframework.boot") version "3.4.1"
id("io.freefair.lombok") version "8.6"
id("org.springframework.boot") version "3.2.3"
id("com.github.ben-manes.versions") version "0.51.0"
id("org.hibernate.orm") version "6.4.4.Final"
application
@ -38,13 +38,13 @@ dependencies {
implementation("io.netty:netty-all")
implementation("org.apache.commons:commons-lang3:3.14.0")
implementation("org.apache.httpcomponents.client5:httpclient5")
implementation("org.flywaydb:flyway-core:11.1.0")
implementation("org.flywaydb:flyway-mysql:11.1.0")
implementation("org.flywaydb:flyway-core:10.10.0")
implementation("org.flywaydb:flyway-mysql:10.10.0")
testImplementation("org.springframework.boot:spring-boot-starter-test") {
exclude(group = "org.junit.vintage", module = "junit-vintage-engine")
}
testImplementation("org.springframework.security:spring-security-test")
implementation("net.logstash.logback:logstash-logback-encoder:8.0")
implementation("net.logstash.logback:logstash-logback-encoder:7.4")
// Metrics
implementation("org.springframework.boot:spring-boot-starter-actuator")
@ -64,11 +64,11 @@ dependencies {
// =============================
// Network
implementation("io.ktor:ktor-client-core:2.3.13")
implementation("io.ktor:ktor-client-cio:2.3.13")
implementation("io.ktor:ktor-client-content-negotiation:2.3.13")
implementation("io.ktor:ktor-client-encoding:2.3.13")
implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.13")
implementation("io.ktor:ktor-client-core:2.3.8")
implementation("io.ktor:ktor-client-cio:2.3.8")
implementation("io.ktor:ktor-client-content-negotiation:2.3.8")
implementation("io.ktor:ktor-client-encoding:2.3.8")
implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.8")
implementation("org.jetbrains.kotlin:kotlin-reflect")
// Somehow these are needed for ktor even though they're not in the documentation
@ -76,16 +76,16 @@ dependencies {
runtimeOnly("org.jetbrains.kotlinx:kotlinx-coroutines-reactor:1.8.0")
// Email
implementation("org.simplejavamail:simple-java-mail:8.12.4")
implementation("org.simplejavamail:spring-module:8.12.4")
implementation("org.simplejavamail:simple-java-mail:8.6.3")
implementation("org.simplejavamail:spring-module:8.6.3")
// GeoIP
implementation("com.maxmind.geoip2:geoip2:4.2.1")
implementation("com.maxmind.geoip2:geoip2:4.2.0")
// JWT Authentication
implementation("io.jsonwebtoken:jjwt-api:0.12.6")
runtimeOnly("io.jsonwebtoken:jjwt-impl:0.12.6")
runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.12.6")
implementation("io.jsonwebtoken:jjwt-api:0.12.5")
runtimeOnly("io.jsonwebtoken:jjwt-impl:0.12.5")
runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.12.5")
// Content validation
implementation("org.apache.tika:tika-core:2.9.1")
@ -94,8 +94,7 @@ dependencies {
implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.17.0")
// Serialization
// DO NOT UPDATE THIS TO >2.18.0: https://github.com/FasterXML/jackson-module-kotlin/issues/874
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.17.3")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
// Testing
testImplementation("io.kotest:kotest-runner-junit5-jvm:5.8.1")

View File

@ -53,9 +53,6 @@ game.chusan.reflector-url=http://reflector.naominet.live:18080/
## This sets the matching server url.
## When this is set, we will sync with the external matching url so that we can match with more players.
game.chusan.external-matching=https://chu3-match.sega.ink/
## When this is set to true, we will proxy all matching requests sent to the external matching server.
## This option enhances security by masking the user ID and keychip.
game.chusan.proxied-matching=false
## This enables user use login bonus function if set to true.
## NOTE: THIS IS NOT TESTED, it's implemented by someone very inexperienced and might not work.
game.chusan.loginbonus-enable=false

View File

@ -0,0 +1,79 @@
# Chunithm National Matching Guide
The national matching game mode allows up to 4 players on any server (YES, ANY SERVER) to play together.
In this game mode, for example, you can play with RinNET or Missless players as well.
This is a guide on how to set up your client for national matching.
This is tested on Chusan 2.27.
## Pre-requisites
- Play the normal game at least once so that you have a profile on the server.
- NAT Type must not be Symmetric ([Check here](https://www.checkmynat.com/))
- Your firewall must be turned off (or [add a rule that allows chusanApp](#firewall-rules))
## Setting Up
![](img/chu3-matching.png)
1. Go to the AquaNet website and set your matching server to "Yukiotoko"
(To go to the settings page, click on the gear icon in the top right corner of your profile, switch to chuni tab, scroll down, click "Select Matching Server")
2. Make sure you use [Dniel97's open-source segatools](https://gitea.tendokyu.moe/Dniel97/SEGAguide/wiki/SDHD)
If you're using fufubot segatools, please override it with Dniel97's version (don't forget to update `segatools.ini`).
3. Patch your `chusanApp.exe` using [Two-Torial's open-source patcher](https://patcher.two-torial.xyz/)
(Make sure you disable "Set all timer to 999", enable "No encryption", "No TLS", "Patch for head-to-head play")
4. Add the option [ARRR](https://pixeldrain.com/u/D2jjN3of).
If you're playing with friends, make sure they have the same options as you.
5. Pet your cat 🐈
6. Launch!
### Firewall Rules
Below is a simple command to add firewall rules for Chunithm.
```shell
@echo off
set /p gamedirectory = Make sure this is run as admin and enter game path (e.g. C:\SegaGames\Chunithm\bin\chusanApp.exe)\n
netsh advfirewall firewall add rule name="Chunithm National Matching Inbound" dir=in action=allow profile=any program="%gamedirectory%" enable=yes
netsh advfirewall firewall add rule name="Chunithm National Matching Outbound" dir=out action=allow profile=any program="%gamedirectory%" enable=yes
```
## Troubleshooting
**Q: Game crashes when entering match mode**
Make sure you are using Dniel97's segatools.
**Q: After matching, timer shows 999 seconds and nobody can start**
Make sure you have patched your `chusanApp.exe` correctly.
**Q: Online battle icon gray / "Unable to select after the event time"**
Make sure your time zone is set to JST (UTC+9).
**Q: This window show up when joining.**
![](img/chu3-matching-error.png)
If there is only one player, then yea it's because there are not enough players.
Otherwise, it's because one of the players has a bad network environment (e.g. Symmetric NAT).
Try again with someone who played this mode before, if it still doesn't work, then it's probably you.
**Q: Why did I play two of the same songs in a row?**
When other people picked a song that you don't have, the game will play the same song as the previous one.
Make sure you have up-to-date options in your game.
(Or they might have selected a custom chart, in that case there's not much you can do)
## How to Play
When you enter the matching mode, it will assign to you a matching room if other people are online, or create a new room otherwise.
Then, after four people are present or after a specific amount of time has passed, the game will start.
Everyone will be asked to pick a song at the start, even though your song might not be the first one to be played.
After songs are picked, other players will play the song on the SAME DIFFICULTY as what you picked.
(So be a nice person and don't pick 15 if there are new players alright? 🥺)
If there are less than 4 players when the timer runs out, the game will fill in the empty slots with bots.
The bots will randomly select a song (mostly under Lv10).

Binary file not shown.

After

Width:  |  Height:  |  Size: 297 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

View File

@ -6,6 +6,8 @@ import io.ktor.client.engine.cio.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.serialization.kotlinx.json.*
import jakarta.persistence.Query
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.apache.tika.Tika
@ -16,6 +18,7 @@ import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity.BodyBuilder
import org.springframework.web.bind.annotation.*
import java.lang.reflect.Field
import java.nio.charset.StandardCharsets
import java.nio.file.Path
import java.security.MessageDigest
import java.time.LocalDate
@ -44,6 +47,19 @@ typealias JavaSerializable = java.io.Serializable
typealias JDict = Map<String, Any?>
typealias MutJDict = MutableMap<String, Any?>
fun HttpServletRequest.details() = mapOf(
"method" to method,
"uri" to requestURI,
"query" to queryString,
"remote" to remoteAddr,
"headers" to headerNames.asSequence().associateWith { getHeader(it) }
)
fun HttpServletResponse.details() = mapOf(
"status" to status,
"headers" to headerNames.asSequence().associateWith { getHeader(it) },
)
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY, AnnotationTarget.PROPERTY_GETTER)
@Retention(AnnotationRetention.RUNTIME)
annotation class Doc(
@ -120,6 +136,7 @@ fun LocalDateTime.isoDateTime() = format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
fun String.isoDateTime() = LocalDateTime.parse(this, DateTimeFormatter.ISO_LOCAL_DATE_TIME)
val URL_SAFE_DT = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss")
fun LocalDateTime.urlSafeStr() = format(URL_SAFE_DT)
val DATE_2018 = LocalDateTime.parse("2018-01-01T00:00:00")
val ALT_DATETIME_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
fun Str.asDateTime() = try { LocalDateTime.parse(this, DateTimeFormatter.ISO_LOCAL_DATE_TIME) }
@ -173,17 +190,22 @@ val Any?.truthy get() = when (this) {
fun <T> ls(vararg args: T) = args.toList()
inline fun <reified T> arr(vararg args: T) = arrayOf(*args)
operator fun <K, V> Map<K, V>.plus(map: Map<K, V>) =
(if (this is MutableMap) this else toMutableMap()).apply { putAll(map) }
(if (this is MutableMap) this else mut).apply { putAll(map) }
operator fun <K, V> MutableMap<K, V>.plusAssign(map: Map<K, V>) { putAll(map) }
fun <K, V: Any> Map<K, V?>.vNotNull(): Map<K, V> = filterValues { it != null }.mapValues { it.value!! }
fun <T> MutableList<T>.popAll(list: List<T>) = list.also { removeAll(it) }
fun <T> MutableList<T>.popAll(vararg items: T) = popAll(items.toList())
inline fun <T> Iterable<T>.mapApply(block: T.() -> Unit) = map { it.apply(block) }
inline fun <T> Iterable<T>.mapApplyI(block: T.(Int) -> Unit) = mapIndexed { i, e -> e.apply { block(i) } }
@Suppress("UNCHECKED_CAST")
fun <K, V: Any> Map<K, V?>.recursiveNotNull(): Map<K, V> = mapNotNull { (k, v) ->
k to if (v is Map<*, *>) (v as Map<Any?, Any?>).recursiveNotNull() else v
}.toMap() as Map<K, V>
val <T> List<T>.mut get() = toMutableList()
val <K, V> Map<K, V>.mut get() = toMutableMap()
val <T> Set<T>.mut get() = toMutableSet()
// Optionals
operator fun <T> Optional<T>.invoke(): T? = orElse(null)
fun <T> Optional<T>.expect(message: Str = "Value is not present") = orElseGet { (400 - message) }
@ -195,6 +217,7 @@ fun Str.center(width: Int, padChar: Char = ' ') = padStart((length + width) / 2,
fun Str.splitLines() = replace("\r\n", "\n").split('\n')
@OptIn(ExperimentalStdlibApi::class)
fun Str.md5() = MD5.digest(toByteArray(Charsets.UTF_8)).toHexString()
fun Str.fromChusanUsername() = String(this.toByteArray(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8)
// Coroutine
suspend fun <T> async(block: suspend kotlinx.coroutines.CoroutineScope.() -> T): T = withContext(Dispatchers.IO) { block() }

View File

@ -1,316 +0,0 @@
package icu.samnyan.aqua.api.controller.sega.game.chuni.v2;
import com.fasterxml.jackson.core.type.TypeReference;
import icu.samnyan.aqua.api.model.MessageResponse;
import icu.samnyan.aqua.api.model.ReducedPageResponse;
import icu.samnyan.aqua.api.model.resp.sega.chuni.v2.RecentResp;
import icu.samnyan.aqua.api.model.resp.sega.chuni.v2.external.Chu3DataExport;
import icu.samnyan.aqua.api.util.ApiMapper;
import icu.samnyan.aqua.sega.chusan.model.userdata.*;
import icu.samnyan.aqua.sega.chusan.service.*;
import icu.samnyan.aqua.sega.general.service.CardService;
import lombok.AllArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.*;
/**
* For all aimeId parameter, should use String
* @author samnyan (privateamusement@protonmail.com)
*/
@RestController
@RequestMapping("api/game/chuni/v2")
@ConditionalOnProperty(prefix = "aquaviewer.api", name = "enabled", havingValue = "true")
@AllArgsConstructor
public class ApiChuniV2PlayerDataController {
private static final Logger logger = LoggerFactory.getLogger(ApiChuniV2PlayerDataController.class);
private final ApiMapper mapper;
private final CardService cardService;
private final UserActivityService userActivityService;
private final UserCharacterService userCharacterService;
private final UserChargeService userChargeService;
private final UserCourseService userCourseService;
private final UserDataService userDataService;
private final UserDuelService userDuelService;
private final UserGameOptionService userGameOptionService;
private final UserItemService userItemService;
private final UserMapAreaService userMapAreaService;
private final UserMusicDetailService userMusicDetailService;
private final UserPlaylogService userPlaylogService;
private final UserGeneralDataService userGeneralDataService;
@PutMapping("profile/username")
public Chu3UserData updateName(@RequestBody Map<String, Object> request) {
Chu3UserData profile = userDataService.getUserByExtId((String) request.get("aimeId")).orElseThrow();
profile.setUserName((String) request.get("userName"));
return userDataService.saveUserData(profile);
}
@PutMapping("profile/romversion")
public Chu3UserData updateRomVersion(@RequestBody Map<String, Object> request) {
Chu3UserData profile = userDataService.getUserByExtId((String) request.get("aimeId")).orElseThrow();
profile.setLastRomVersion((String) request.get("romVersion"));
return userDataService.saveUserData(profile);
}
@PutMapping("profile/dataversion")
public Chu3UserData updateDataVersion(@RequestBody Map<String, Object> request) {
Chu3UserData profile = userDataService.getUserByExtId((String) request.get("aimeId")).orElseThrow();
profile.setLastDataVersion((String) request.get("dataVersion"));
return userDataService.saveUserData(profile);
}
@PutMapping("profile/plate")
public Chu3UserData updatePlate(@RequestBody Map<String, Object> request) {
Chu3UserData profile = userDataService.getUserByExtId((String) request.get("aimeId")).orElseThrow();
profile.setNameplateId((Integer) request.get("nameplateId"));
return userDataService.saveUserData(profile);
}
@PutMapping("profile/frame")
public Chu3UserData updateFrame(@RequestBody Map<String, Object> request) {
Chu3UserData profile = userDataService.getUserByExtId((String) request.get("aimeId")).orElseThrow();
profile.setFrameId((Integer) request.get("frameId"));
return userDataService.saveUserData(profile);
}
@PutMapping("profile/trophy")
public Chu3UserData updateTrophy(@RequestBody Map<String, Object> request) {
Chu3UserData profile = userDataService.getUserByExtId((String) request.get("aimeId")).orElseThrow();
profile.setTrophyId((Integer) request.get("trophyId"));
return userDataService.saveUserData(profile);
}
@PutMapping("profile/mapicon")
public Chu3UserData updateMapIcon(@RequestBody Map<String, Object> request) {
Chu3UserData profile = userDataService.getUserByExtId((String) request.get("aimeId")).orElseThrow();
profile.setMapIconId((Integer) request.get("mapiconId"));
return userDataService.saveUserData(profile);
}
@PutMapping("profile/sysvoice")
public Chu3UserData updateSystemVoice(@RequestBody Map<String, Object> request) {
Chu3UserData profile = userDataService.getUserByExtId((String) request.get("aimeId")).orElseThrow();
profile.setVoiceId((Integer) request.get("voiceId"));
return userDataService.saveUserData(profile);
}
@PutMapping("profile/avatar")
public Chu3UserData updateAvatar(@RequestBody Map<String, Object> request) {
Chu3UserData profile = userDataService.getUserByExtId((String) request.get("aimeId")).orElseThrow();
int category = (Integer) request.get("category");
switch (category) {
case 1:
profile.setAvatarWear((Integer) request.get("accId"));
break;
case 2:
profile.setAvatarHead((Integer) request.get("accId"));
break;
case 3:
profile.setAvatarFace((Integer) request.get("accId"));
break;
case 4:
profile.setAvatarSkin((Integer) request.get("accId"));
break;
case 5:
profile.setAvatarItem((Integer) request.get("accId"));
break;
case 6:
profile.setAvatarFront((Integer) request.get("accId"));
break;
case 7:
profile.setAvatarBack((Integer) request.get("accId"));
break;
}
return userDataService.saveUserData(profile);
}
@PutMapping("profile/privacy")
public ResponseEntity<Object> updatePrivacy(@RequestBody Map<String, Object> request) {
Chu3UserData profile = userDataService.getUserByExtId((String) request.get("aimeId")).orElseThrow();
UserGameOption option = userGameOptionService.getByUser(profile).orElseThrow();
int privacy = (Integer) request.get("privacy");
if (privacy != 1 && privacy != 0) {
return ResponseEntity.badRequest().body(new MessageResponse("Wrong data"));
}
option.setPrivacy(privacy);
return ResponseEntity.ok(userDataService.saveUserData(profile));
}
@GetMapping("recent")
public ReducedPageResponse<RecentResp> getRecentPlay(@RequestParam String aimeId,
@RequestParam(required = false, defaultValue = "0") int page,
@RequestParam(required = false, defaultValue = "10") int size) {
Page<UserPlaylog> playLogs = userPlaylogService.getRecentPlays(aimeId, PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "userPlayDate")));
return new ReducedPageResponse<>(mapper.convert(playLogs.getContent(), new TypeReference<>() {
}), playLogs.getPageable().getPageNumber(), playLogs.getTotalPages(), playLogs.getTotalElements());
}
@GetMapping("song/{id}")
public List<UserMusicDetail> getSongDetail(@RequestParam String aimeId, @PathVariable int id) {
return userMusicDetailService.getByUserIdAndMusicId(aimeId, id);
}
@GetMapping("song/{id}/{level}")
public List<UserPlaylog> getLevelPlaylog(@RequestParam String aimeId, @PathVariable int id, @PathVariable int level) {
return userPlaylogService.getByUserIdAndMusicIdAndLevel(aimeId, id, level);
}
@GetMapping("song/{id}/isfavorite")
public boolean getSongFavorite(@RequestParam String aimeId, @PathVariable String id) {
Optional<UserGeneralData> favOptional;
favOptional = userGeneralDataService.getByUserIdAndKey(aimeId, "favorite_music");
if(favOptional.isPresent()) {
String val = favOptional.get().getPropertyValue();
if(StringUtils.isNotBlank(val) && val.contains(",")) {
String[] records = val.split(",");
for (String record : records) {
if (record.equals(id)) return true;
}
}
}
return false;
}
@PutMapping("song/{id}/favorite")
public void updateSongFavorite(@RequestParam String aimeId, @PathVariable String id) {
Chu3UserData profile = userDataService.getUserByExtId(aimeId).orElseThrow();
UserGeneralData userGeneralData = userGeneralDataService.getByUserAndKey(profile, "favorite_music")
.orElseGet(() -> new UserGeneralData(profile, "favorite_music"));
List<String> favoriteSongs = new LinkedList<String>(Arrays.asList(userGeneralData.getPropertyValue().split(",")));
if(!favoriteSongs.remove(id))
{
favoriteSongs.add(id);
}
StringBuilder sb = new StringBuilder();
favoriteSongs.forEach(favSong -> {
if(!favSong.isEmpty()) sb.append(favSong).append(",");
});
userGeneralData.setPropertyValue(sb.toString());
userGeneralDataService.save(userGeneralData);
}
@GetMapping("character")
public ReducedPageResponse<UserCharacter> getCharacter(@RequestParam String aimeId,
@RequestParam(required = false, defaultValue = "0") int page,
@RequestParam(required = false, defaultValue = "10") int size) {
Page<UserCharacter> characters = userCharacterService.getByUserId(aimeId, page, size);
return new ReducedPageResponse<>(characters.getContent(), characters.getPageable().getPageNumber(), characters.getTotalPages(), characters.getTotalElements());
}
@PostMapping("character")
public ResponseEntity<Object> updateCharacter(@RequestBody Map<String, Object> request) {
Chu3UserData profile = userDataService.getUserByExtId((String) request.get("aimeId")).orElseThrow();
Integer characterId = (Integer) request.get("characterId");
Optional<UserCharacter> characterOptional = userCharacterService.getByUserAndCharacterId(profile, characterId);
UserCharacter character;
if(characterOptional.isPresent()) {
character = characterOptional.get();
} else {
character = new UserCharacter(profile);
character.setCharacterId(characterId);
}
if(request.containsKey("level")) {
character.setLevel((Integer) request.get("level"));
}
return ResponseEntity.ok(userCharacterService.save(character));
}
@GetMapping("course")
public List<UserCourse> getCourse(@RequestParam String aimeId) {
return userCourseService.getByUserId(aimeId);
}
@GetMapping("duel")
public List<UserDuel> getDuel(@RequestParam String aimeId) {
return userDuelService.getByUserId(aimeId);
}
@GetMapping("item")
public ReducedPageResponse<UserItem> getItem(@RequestParam String aimeId,
@RequestParam(required = false, defaultValue = "0") int page,
@RequestParam(required = false, defaultValue = "10") int size) {
Page<UserItem> items = userItemService.getByUserId(aimeId, page, size);
return new ReducedPageResponse<>(items.getContent(), items.getPageable().getPageNumber(), items.getTotalPages(), items.getTotalElements());
}
@GetMapping("item/{itemKind}")
public List<UserItem> getItemByKind(@RequestParam String aimeId, @PathVariable int itemKind) {
return userItemService.getByUserAndItemKind(aimeId, itemKind);
}
@PostMapping("item")
public ResponseEntity<Object> updateItem(@RequestBody Map<String, Object> request) {
Chu3UserData profile = userDataService.getUserByExtId((String) request.get("aimeId")).orElseThrow();
Integer itemId = (Integer) request.get("itemId");
Integer itemKind = (Integer) request.get("itemKind");
Optional<UserItem> itemOptional = userItemService.getByUserAndItemIdAndKind(profile, itemId,itemKind);
UserItem item;
if(itemOptional.isPresent()) {
item = itemOptional.get();
} else {
item = new UserItem(profile);
item.setItemId(itemId);
item.setItemKind(itemKind);
}
if(request.containsKey("stock")) {
item.setStock((Integer) request.get("stock"));
}
return ResponseEntity.ok(userItemService.save(item));
}
@GetMapping("general")
public ResponseEntity<Object> getGeneralData(@RequestParam String aimeId, @RequestParam String key) {
Optional<UserGeneralData> userGeneralDataOptional = userGeneralDataService.getByUserIdAndKey(aimeId,key);
return userGeneralDataOptional.<ResponseEntity<Object>>map(ResponseEntity::ok)
.orElseGet(() -> ResponseEntity.status(HttpStatus.NOT_FOUND).body(new MessageResponse("User or value not found.")));
}
@GetMapping("export")
public ResponseEntity<Object> exportAllUserData(@RequestParam String aimeId) {
Chu3DataExport data = new Chu3DataExport();
try {
data.setGameId("SDHD");
data.setUserData(userDataService.getUserByExtId(aimeId).orElseThrow());
data.setUserActivityList(userActivityService.getByUserId(aimeId));
data.setUserCharacterList(userCharacterService.getByUserId(aimeId));
data.setUserChargeList(userChargeService.getByUserId(aimeId));
data.setUserCourseList(userCourseService.getByUserId(aimeId));
data.setUserDuelList(userDuelService.getByUserId(aimeId));
data.setUserGameOption(userGameOptionService.getByUserId(aimeId).orElseThrow());
data.setUserItemList(userItemService.getByUserId(aimeId));
data.setUserMapList(userMapAreaService.getByUserId(aimeId));
data.setUserMusicDetailList(userMusicDetailService.getByUserId(aimeId));
data.setUserPlaylogList(userPlaylogService.getByUserId(aimeId));
} catch (NoSuchElementException e) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(new MessageResponse("User not found"));
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(new MessageResponse("Error during data export. Reason: " + e.getMessage()));
}
// Set filename
HttpHeaders headers = new HttpHeaders();
headers.set("content-disposition", "attachment; filename=chusan_" + aimeId + "_exported.json");
return new ResponseEntity<>(data, headers, HttpStatus.OK);
}
}

View File

@ -57,11 +57,11 @@ class BotController(
us.cardRepo.findByExtId(cardId.long)(),
us.cardRepo.findByLuid(cardId)(),
us.cardRepo.findById(cardId.long)(),
).toMutableList()
).mut
cards += cards.flatMap {
(it.aquaUser?.cards ?: emptyList()) + listOfNotNull(it.aquaUser?.ghostCard)
}
cards = cards.distinctBy { it.id }.toMutableList()
cards = cards.distinctBy { it.id }.mut
return cards.map { card ->
// Find all games played by this card

View File

@ -1,6 +1,7 @@
package icu.samnyan.aqua.net
import ext.HTTP
import ext.mut
import ext.toJson
import icu.samnyan.aqua.net.games.BaseEntity
import io.ktor.client.call.*
@ -54,8 +55,8 @@ class AquaNetSafetyService(
*/
suspend fun isSafeBatch(rawContents: List<String>): List<Boolean> {
val contents = rawContents.map { Normalizer.normalize(it, Normalizer.Form.NFKC) }
val origMap = safety.findAll().associateBy { it.content }.toMutableMap()
val map = safety.findAll().associateBy { it.content.lowercase().trim() }.toMutableMap()
val origMap = safety.findAll().associateBy { it.content }.mut
val map = safety.findAll().associateBy { it.content.lowercase().trim() }.mut
// Process unseen content with OpenAI
val news = contents.filter { it.lowercase().trim() !in map && it !in contents }.map { inp ->

View File

@ -37,6 +37,12 @@ class AquaGameOptions(
@SettingField("chu3")
var chusanInfinitePenguins: Boolean = false,
@SettingField("chu3-matching")
var chusanMatchingServer: String = "",
@SettingField("chu3-matching")
var chusanMatchingReflector: String = "",
)
interface AquaGameOptionsRepo : JpaRepository<AquaGameOptions, Long>

View File

@ -24,6 +24,7 @@ abstract class GameApiController<T : IUserData>(val name: String, userDataClass:
abstract val playlogRepo: GenericPlaylogRepo<*>
abstract val shownRanks: List<Pair<Int, String>>
abstract val settableFields: Map<String, (T, String) -> Unit>
open val gettableFields: Set<String> = setOf()
@API("trend")
abstract suspend fun trend(@RP username: String): List<TrendOut>
@ -110,7 +111,8 @@ abstract class GameApiController<T : IUserData>(val name: String, userDataClass:
fun playlog(@RP id: Long): IGenericGamePlaylog = playlogRepo.findById(id).getOrNull() ?: (404 - "Playlog not found")
val userDetailFields by lazy { userDataClass.gettersMap().let { vm ->
settableFields.map { (k, _) -> k to (vm[k] ?: error("Field $k not found")) }.toMap()
(settableFields.keys.toSet() + gettableFields)
.associateWith { k -> (vm[k] ?: error("Field $k not found")) }
} }
@API("user-detail")
@ -144,13 +146,13 @@ abstract class GameApiController<T : IUserData>(val name: String, userDataClass:
plays.forEach { play ->
val lvl = musicMapping[play.musicId]?.notes?.getOrNull(if (play.level == 10) 0 else play.level)?.lv ?: return@forEach
shownRanks.find { (s, _) -> play.achievement > s }?.let { (_, v) ->
val ranks = detailedRanks.getOrPut(lvl.toInt()) { rankMap.toMutableMap() }
val ranks = detailedRanks.getOrPut(lvl.toInt()) { rankMap.mut }
ranks[v] = ranks[v]!! + 1
}
}
// Collapse detailed ranks to get non-detailed ranks map<rank, count>
val ranks = shownRanks.associate { (_, v) -> v to 0 }.toMutableMap().also { ranks ->
val ranks = shownRanks.associate { (_, v) -> v to 0 }.mut.also { ranks ->
plays.forEach { play ->
shownRanks.find { (s, _) -> play.achievement > s }?.let { (_, v) -> ranks[v] = ranks[v]!! + 1 }
}

View File

@ -2,6 +2,7 @@ package icu.samnyan.aqua.net.games
import ext.isoDate
import ext.minus
import ext.mut
import java.time.LocalDate
const val LETTERS = "" +
@ -20,7 +21,7 @@ fun usernameCheck(chars: String): (IUserData, String) -> Unit = { u, v ->
u.userName = v
if (v.isBlank()) { 400 - "Username cannot be blank" }
if (v.length > 8) { 400 - "Username too long" }
v.find { it !in chars }?.let { 400 - "Invalid character '$it' in username" }
// v.find { it !in chars }?.let { 400 - "Invalid character '$it' in username" }
}
fun toFullWidth(input: String): String {
@ -60,7 +61,7 @@ fun findTrend(log: List<TrendLog>): List<TrendOut> {
val trend = d.distinctBy { it.date }
.map { TrendOut(it.date, maxRating[it.date] ?: 0,
playCounts[it.date] ?: 0) }
.sortedBy { it.date }.toMutableList()
.sortedBy { it.date }.mut
// Fill in the missing dates (min date and current date)
trend[0].let { if (it.date > minDate) trend.add(0, TrendOut(minDate, 0, 0)) }

View File

@ -38,6 +38,7 @@ class Chusan(
"avatarFront" to { u, v -> u.avatarFront = v.int },
"avatarBack" to { u, v -> u.avatarBack = v.int },
) }
override val gettableFields: Set<String> = setOf("level", "playerRating", "characterId")
override suspend fun userSummary(@RP username: Str, @RP token: String?) = us.cardByName(username) { card ->
// Summary values: total plays, player rating, server-wide ranking
@ -61,15 +62,4 @@ class Chusan(
val u = userDataRepo.findByCard(it.ghostCard) ?: (404 - "Game data not found")
mapOf("user" to u, "items" to rp.userItem.findAllByUser(u))
}
@API("user-box-all-items")
fun userBoxAllItems() = allItems
val allItems by lazy { mapOf(
"nameplate" to rp.gameNamePlate.findAll(),
"frame" to rp.gameFrame.findAll(),
"trophy" to rp.gameTrophy.findAll(),
"mapicon" to rp.gameMapIcon.findAll(),
"sysvoice" to rp.gameSystemVoice.findAll(),
"avatar" to rp.gameAvatarAcc.findAll(),
) }
}

View File

@ -26,9 +26,7 @@ class Maimai2(
// Only show > S rank
override val shownRanks = mai2Scores.filter { it.first >= 97 * 10000 }
override val settableFields: Map<String, (Mai2UserDetail, String) -> Unit> by lazy {
mapOf(
"userName" to usernameCheck(SEGA_USERNAME_CAHRS),
)
mapOf("userName" to usernameCheck(SEGA_USERNAME_CAHRS))
}
override suspend fun userSummary(@RP username: Str, @RP token: String?) = us.cardByName(username) { card ->
@ -127,7 +125,7 @@ class Maimai2(
user = repos.userData.findByCardExtId(myCard.extId).orElse(null) ?: (404 - "User not found")
propertyKey = "favorite_rival"
}
val myRivalList = myRival.propertyValue.split(',').filter { it.isNotEmpty() }.toMutableSet()
val myRivalList = myRival.propertyValue.split(',').filter { it.isNotEmpty() }.mut
if (isAdd && myRivalList.size >= 4) {
(400 - "Rival list is full")

View File

@ -2,9 +2,9 @@ package icu.samnyan.aqua.sega.aimedb
import ext.toHex
import icu.samnyan.aqua.net.db.AquaUserServices
import icu.samnyan.aqua.sega.allnet.AllNetProps
import icu.samnyan.aqua.sega.general.model.Card
import icu.samnyan.aqua.sega.general.service.CardService
import icu.samnyan.aqua.sega.allnet.AllNetProps
import io.netty.buffer.ByteBuf
import io.netty.buffer.ByteBufUtil
import io.netty.buffer.Unpooled
@ -67,7 +67,8 @@ class AimeDB(
logger.info("AimeDB /${handler.name} : (game ${base.gameId}, keychip ${base.keychipId})")
// Check keychip
if (!us.validKeychip(base.keychipId)) {
// We do not check for type 0x13 because of a bug in duolinguo.dll
if (!us.validKeychip(base.keychipId) && type != 0x13) {
if (allNetProps.keychipPermissiveForTesting) {
logger.warn("> Accepted invalid keychip ${base.keychipId} in permissive mode")
} else {

View File

@ -139,7 +139,7 @@ class AllNet(
val ver = reqMap["ver"] ?: "1.0"
val formatVer = reqMap["format_ver"] ?: ""
val resp = props.map.toMutableMap() + mapOf(
val resp = props.map.mut + mapOf(
"uri" to switchUri(localAddr, localPort, gameId, ver, session),
"host" to props.host.ifBlank { localAddr },
)

View File

@ -4,9 +4,10 @@ import ext.*
import icu.samnyan.aqua.net.db.AquaUserServices
import icu.samnyan.aqua.net.utils.simpleDescribe
import icu.samnyan.aqua.sega.allnet.TokenChecker
import icu.samnyan.aqua.sega.chusan.handler.*
import icu.samnyan.aqua.sega.chusan.handler.chusanInit
import icu.samnyan.aqua.sega.chusan.model.Chu3Repos
import icu.samnyan.aqua.sega.general.*
import icu.samnyan.aqua.sega.general.MeowApi
import icu.samnyan.aqua.sega.general.RequestContext
import icu.samnyan.aqua.sega.util.jackson.BasicMapper
import icu.samnyan.aqua.sega.util.jackson.StringMapper
import icu.samnyan.aqua.spring.Metrics
@ -14,7 +15,6 @@ import jakarta.servlet.http.HttpServletRequest
import org.slf4j.LoggerFactory
import org.springframework.web.bind.annotation.RestController
import kotlin.collections.set
import kotlin.reflect.full.declaredMemberProperties
/**
* @author samnyan (privateamusement@protonmail.com)
@ -23,12 +23,6 @@ import kotlin.reflect.full.declaredMemberProperties
@RestController
@API(value = ["/g/chu3/{version}/ChuniServlet", "/g/chu3/{version}"])
class ChusanController(
val gameLogin: GameLoginHandler,
val upsertUserAll: UpsertUserAllHandler,
val cmUpsertUserGacha: CMUpsertUserGachaHandler,
val cmUpsertUserPrintSubtract: CMUpsertUserPrintSubtractHandler,
val cmUpsertUserPrintCancel: CMUpsertUserPrintCancelHandler,
val mapper: StringMapper,
val cmMapper: BasicMapper,
val db: Chu3Repos,
@ -41,23 +35,12 @@ class ChusanController(
}) {
val log = LoggerFactory.getLogger(ChusanController::class.java)
// Below are code related to handling the handlers
val externalHandlers = mutableListOf("GameLoginApi", "UpsertUserAllApi",
"CMUpsertUserGachaApi", "CMUpsertUserPrintCancelApi", "CMUpsertUserPrintSubtractApi")
val noopEndpoint = setOf("UpsertClientBookkeepingApi", "UpsertClientDevelopApi", "UpsertClientErrorApi",
"UpsertClientSettingApi", "UpsertClientTestmodeApi", "CreateTokenApi", "RemoveTokenApi", "UpsertClientUploadApi",
"PrinterLoginApi", "PrinterLogoutApi", "Ping", "GameLogoutApi", "RemoveMatchingMemberApi")
init { chusanInit() }
val members = this::class.declaredMemberProperties
val handlers: Map<String, SpecialHandler> = initH + externalHandlers.associateWith { api ->
val name = api.replace("Api", "").lowercase()
(members.find { it.name.lowercase() == name } ?: members.find { it.name.lowercase() == name.replace("cm", "") })
?.let { (it.call(this) as BaseHandler).toSpecial() }
?: throw IllegalArgumentException("Chu3: No handler found for $api")
}
val handlers = initH
@API("/{endpoint}", "/MatchingServer/{endpoint}")
fun handle(@PV endpoint: Str, @RB data: MutableMap<Str, Any>, @PV version: Str, req: HttpServletRequest): Any {
@ -72,6 +55,8 @@ class ChusanController(
}
if (api.startsWith("CM") && api !in handlers) api = api.removePrefix("CM")
val token = TokenChecker.getCurrentSession()?.token?.substring(0, 6) ?: "NO-TOKEN"
log.info("Chu3 < $api : ${data.toJson()} : [$token]")
if (api !in noopEndpoint && !handlers.containsKey(api)) {
log.warn("Chu3 > $api not found")
@ -84,8 +69,6 @@ class ChusanController(
log.info("Chu3 > $api no-op")
return """{"returnCode":"1"}"""
}
val token = TokenChecker.getCurrentSession()?.token?.substring(0, 6) ?: "NO-TOKEN"
log.info("Chu3 < $api : ${data.toJson()} : [$token]")
return try {
Metrics.timer("aquadx_chusan_api_latency", "api" to api).recordCallable {

View File

@ -10,5 +10,4 @@ class ChusanProps {
var loginBonusEnable = false
var externalMatching: String? = null
var reflectorUrl: String? = null
var proxiedMatching: Boolean = false
}

View File

@ -1,101 +0,0 @@
package icu.samnyan.aqua.sega.chusan.handler;
import com.fasterxml.jackson.core.JsonProcessingException;
import icu.samnyan.aqua.sega.chusan.model.Chu3UserCardPrintStateRepo;
import icu.samnyan.aqua.sega.chusan.model.Chu3UserGachaRepo;
import icu.samnyan.aqua.sega.general.BaseHandler;
import icu.samnyan.aqua.sega.chusan.model.gamedata.GameGachaCard;
import icu.samnyan.aqua.sega.chusan.model.request.UpsertUserGacha;
import icu.samnyan.aqua.sega.chusan.model.userdata.UserCardPrintState;
import icu.samnyan.aqua.sega.chusan.model.userdata.Chu3UserData;
import icu.samnyan.aqua.sega.chusan.model.userdata.UserGacha;
import icu.samnyan.aqua.sega.chusan.model.userdata.UserItem;
import icu.samnyan.aqua.sega.chusan.service.UserDataService;
import icu.samnyan.aqua.sega.chusan.service.UserItemService;
import icu.samnyan.aqua.sega.util.jackson.BasicMapper;
import lombok.AllArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.util.*;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@AllArgsConstructor
@Component("ChusanCMUpsertUserGachaHandler")
public class CMUpsertUserGachaHandler implements BaseHandler {
private static final Logger logger = LoggerFactory.getLogger(CMUpsertUserGachaHandler.class);
private final Chu3UserCardPrintStateRepo userCardPrintStateRepository;
private final Chu3UserGachaRepo userGachaRepository;
private final UserDataService userDataService;
private final UserItemService userItemService;
private final BasicMapper mapper;
@Override
public Object handle(Map<String, ?> request) throws JsonProcessingException {
String userId = String.valueOf(request.get("userId"));
int gachaId = ((Number) request.get("gachaId")).intValue();
int placeId = ((Number) request.get("placeId")).intValue();
UpsertUserGacha upsertUserGacha = mapper.convert(request.get("cmUpsertUserGacha"), UpsertUserGacha.class);
List<UserCardPrintState> userCardPrintStateList = new ArrayList<>();
Chu3UserData userData;
Optional<Chu3UserData> userOptional = userDataService.getUserByExtId(userId);
if (userOptional.isPresent()) {
userData = userOptional.get();
} else {
logger.error("User not found. userId: {}", userId);
return null;
}
if (upsertUserGacha.getGameGachaCardList() != null) {
for (GameGachaCard gameGachaCard : upsertUserGacha.getGameGachaCardList()) {
UserCardPrintState userCardPrintState = new UserCardPrintState();
userCardPrintState.setHasCompleted(false);
userCardPrintState.setLimitDate(LocalDateTime.of(2029, 01, 01, 0, 0));
userCardPrintState.setPlaceId(placeId);
userCardPrintState.setCardId(gameGachaCard.getCardId());
userCardPrintState.setGachaId(gachaId);
userCardPrintState.setUser(userData);
userCardPrintStateRepository.save(userCardPrintState);
}
}
List<UserItem> userItemListToSave = new ArrayList<>();
List<UserItem> userItemList = upsertUserGacha.getUserItemList();
userItemList.forEach(newUserItem -> {
int itemId = newUserItem.getItemId();
int itemKind = newUserItem.getItemKind();
Optional<UserItem> userItemOptional = userItemService.getByUserAndItemIdAndKind(userData, itemId, itemKind);
UserItem userItem = userItemOptional.orElseGet(() -> new UserItem(userData));
newUserItem.setId(userItem.getId());
newUserItem.setUser(userItem.getUser());
userItemListToSave.add(newUserItem);
});
userItemService.saveAll(userItemListToSave);
if (upsertUserGacha.getUserGacha() != null) {
UserGacha newUserGacha = upsertUserGacha.getUserGacha();
newUserGacha.setUser(userData);
userGachaRepository.save(newUserGacha);
}
userCardPrintStateList = userCardPrintStateRepository.findByUserAndGachaIdAndHasCompleted(userData, gachaId, false);
Map<String, Object> resultMap = new LinkedHashMap<>();
resultMap.put("returnCode", 1);
resultMap.put("apiName", "CMUpsertUserGachaApi");
resultMap.put("userCardPrintStateList", userCardPrintStateList);
return resultMap;
}
}

View File

@ -1,57 +0,0 @@
package icu.samnyan.aqua.sega.chusan.handler;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import icu.samnyan.aqua.sega.chusan.model.Chu3UserCardPrintStateRepo;
import icu.samnyan.aqua.sega.general.BaseHandler;
import icu.samnyan.aqua.sega.chusan.model.userdata.UserCardPrintState;
import icu.samnyan.aqua.sega.util.jackson.BasicMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Component("ChusanCMUpsertUserPrintCancelHandler")
public class CMUpsertUserPrintCancelHandler implements BaseHandler {
private static final Logger logger = LoggerFactory.getLogger(CMUpsertUserPrintCancelHandler.class);
private final Chu3UserCardPrintStateRepo userCardPrintStateRepository;
private final BasicMapper mapper;
@Autowired
public CMUpsertUserPrintCancelHandler(Chu3UserCardPrintStateRepo userCardPrintStateRepository, BasicMapper mapper) {
this.userCardPrintStateRepository = userCardPrintStateRepository;
this.mapper = mapper;
}
@Override
public String handle(Map<String, ?> request) throws JsonProcessingException {
String userId = String.valueOf(request.get("userId"));
List<Integer> orderIdList = mapper.convert(request.get("orderIdList"), new TypeReference<List<Integer>>() {});
for (Integer orderId : orderIdList) {
Optional<UserCardPrintState> userCardPrintStateOptional = userCardPrintStateRepository.findById(orderId.longValue());
if (userCardPrintStateOptional.isPresent()) {
UserCardPrintState newUserCardPrintState = userCardPrintStateOptional.get();
newUserCardPrintState.setHasCompleted(true);
userCardPrintStateRepository.save(newUserCardPrintState);
}
}
Map<String, Object> resultMap = new LinkedHashMap<>();
resultMap.put("returnCode", 1);
resultMap.put("apiName", "CMUpsertUserPrintCancelApi");
String json = mapper.write(resultMap);
logger.info("Response: " + json);
return json;
}
}

View File

@ -1,81 +0,0 @@
package icu.samnyan.aqua.sega.chusan.handler;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import icu.samnyan.aqua.sega.chusan.model.Chu3UserCardPrintStateRepo;
import icu.samnyan.aqua.sega.general.BaseHandler;
import icu.samnyan.aqua.sega.chusan.model.userdata.UserCardPrintState;
import icu.samnyan.aqua.sega.chusan.model.userdata.Chu3UserData;
import icu.samnyan.aqua.sega.chusan.model.userdata.UserItem;
import icu.samnyan.aqua.sega.chusan.service.UserDataService;
import icu.samnyan.aqua.sega.chusan.service.UserItemService;
import icu.samnyan.aqua.sega.util.jackson.BasicMapper;
import lombok.AllArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.*;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@AllArgsConstructor
@Component("ChusanCMUpsertUserPrintSubtractHandler")
public class CMUpsertUserPrintSubtractHandler implements BaseHandler {
private static final Logger logger = LoggerFactory.getLogger(CMUpsertUserPrintSubtractHandler.class);
private final Chu3UserCardPrintStateRepo userCardPrintStateRepository;
private final UserItemService userItemService;
private final UserDataService userDataService;
private final BasicMapper mapper;
@Override
public String handle(Map<String, ?> request) throws JsonProcessingException {
String userId = String.valueOf(request.get("userId"));
UserCardPrintState userCardPrintState = mapper.convert(request.get("userCardPrintState"), UserCardPrintState.class);
List<UserItem> userItemList = mapper.convert(request.get("userItemList"), new TypeReference<List<UserItem>>() {});
Chu3UserData userData;
Optional<Chu3UserData> userOptional = userDataService.getUserByExtId(userId);
if (userOptional.isPresent()) {
userData = userOptional.get();
} else {
logger.error("User not found. userId: {}", userId);
return null;
}
List<UserItem> userItemListToSave = new ArrayList<>();
userItemList.forEach(newUserItem -> {
int itemId = newUserItem.getItemId();
int itemKind = newUserItem.getItemKind();
Optional<UserItem> userItemOptional = userItemService.getByUserAndItemIdAndKind(userData, itemId, itemKind);
UserItem userItem = userItemOptional.orElseGet(() -> new UserItem(userData));
newUserItem.setId(userItem.getId());
newUserItem.setUser(userItem.getUser());
userItemListToSave.add(newUserItem);
});
userItemService.saveAll(userItemListToSave);
Optional<UserCardPrintState> userCardPrintStateOptional = userCardPrintStateRepository.findById(userCardPrintState.getId());
if (userCardPrintStateOptional.isPresent()) {
UserCardPrintState newUserCardPrintState = userCardPrintStateOptional.get();
newUserCardPrintState.setHasCompleted(true);
userCardPrintStateRepository.save(newUserCardPrintState);
}
Map<String, Object> resultMap = new LinkedHashMap<>();
resultMap.put("returnCode", 1);
resultMap.put("apiName", "CMUpsertUserPrintSubtractApi");
String json = mapper.write(resultMap);
logger.info("Response: " + json);
return json;
}
}

View File

@ -1,9 +1,10 @@
package icu.samnyan.aqua.sega.chusan
package icu.samnyan.aqua.sega.chusan.handler
import ext.*
import icu.samnyan.aqua.sega.allnet.TokenChecker
import icu.samnyan.aqua.sega.chusan.ChusanController
import icu.samnyan.aqua.sega.chusan.ChusanData
import icu.samnyan.aqua.sega.chusan.model.request.UserCMissionResp
import icu.samnyan.aqua.sega.chusan.model.response.data.UserEmoney
import icu.samnyan.aqua.sega.chusan.model.userdata.UserCharge
import icu.samnyan.aqua.sega.chusan.model.userdata.UserItem
import icu.samnyan.aqua.sega.chusan.model.userdata.UserMusicDetail
import icu.samnyan.aqua.sega.general.model.response.UserRecentRating
@ -12,6 +13,8 @@ import java.time.format.DateTimeFormatter
@Suppress("UNCHECKED_CAST")
fun ChusanController.chusanInit() {
matchingApiInit()
cmApiInit()
upsertApiInit()
// Stub handlers
"GetGameRanking" { """{"type":"${data["type"]}","length":"0","gameRankingList":[]}""" }
@ -25,11 +28,18 @@ fun ChusanController.chusanInit() {
"GetUserRegion" { """{"userId":"${data["userId"]}","length":"0","userRegionList":[]}""" }
"GetUserPrintedCard" { """{"userId":"${data["userId"]}","length":0,"nextIndex":-1,"userPrintedCardList":[]}""" }
"GetUserSymbolChatSetting" { """{"userId":"${data["userId"]}","length":"0","symbolChatInfoList":[]}""" }
"GetUserNetBattleData" { """{"userId":"${data["userId"]}","userNetBattleData":{"recentNBSelectMusicList":[],"recentNBMusicList":[]}}""" }
"GetUserNetBattleRankingInfo" { """{"userId":"${data["userId"]}","length":"0","userNetBattleRankingInfoList":{}}""" }
"CMUpsertUserPrint" { """{"returnCode":1,"orderId":"0","serialId":"FAKECARDIMAG12345678","apiName":"CMUpsertUserPrintApi"}""" }
"CMUpsertUserPrintlog" { """{"returnCode":1,"orderId":"0","serialId":"FAKECARDIMAG12345678","apiName":"CMUpsertUserPrintlogApi"}""" }
// Net battle data
"GetUserNetBattleData" api@ {
val u = db.userData.findByCard_ExtId(uid)() ?: return@api null
val misc = db.userMisc.findSingleByUser(u)()
val recent = db.netBattleLog.findTop20ByUserOrderByIdDesc(u)
mapOf("userId" to uid, "userNetBattleData" to mapOf(
"recentNBSelectMusicList" to (misc?.recentNbSelect ?: empty),
"recentNBMusicList" to recent.map { it.toDict(u.userName) },
))
}
"GetUserNetBattleRankingInfo" { """{"userId":"${data["userId"]}","length":"0","userNetBattleRankingInfoList":{}}""" }
// User handlers
"GetUserData" {
@ -57,7 +67,6 @@ fun ChusanController.chusanInit() {
"GetUserCMission" {
parsing { UserCMissionResp().apply {
userId = uid
missionId = parsing { data["missionId"]!!.int }
} }.apply {
db.userCMission.findByUser_Card_ExtIdAndMissionId(uid, missionId)()?.let {
@ -92,7 +101,7 @@ fun ChusanController.chusanInit() {
data["nextIndex"] = rawIndex % 10000000000L
mapOf("itemKind" to kind) grabs {
// TODO: All unlock
val items = db.userItem.findAllByUser_Card_ExtIdAndItemKind(uid, kind).toMutableList()
val items = db.userItem.findAllByUser_Card_ExtIdAndItemKind(uid, kind).mut
// Check game options
db.userData.findByCard_ExtId(uid)()?.card?.aquaUser?.gameOptions?.let {
@ -112,14 +121,12 @@ fun ChusanController.chusanInit() {
"GetUserFavoriteItem".pagedWithKind("userFavoriteItemList") {
val kind = parsing { data["kind"]!!.int }
mapOf("kind" to kind) grabs {
// TODO: Actually store this info at UpsertUserAll
val fav = when (kind) {
1 -> "favorite_music"
3 -> "favorite_chara"
else -> null
}?.let { db.userGeneralData.findByUser_Card_ExtIdAndPropertyKey(uid, it)() }?.propertyValue
fav?.ifBlank { null }?.split(",")?.map { it.int } ?: emptyList()
val misc = db.userMisc.findSingleByUser_Card_ExtId(uid)()
when (kind) {
1 -> misc?.favMusic ?: empty
3 -> empty // TODO: Favorite character
else -> empty
}.map { mapOf("id" to it) }
}
}
@ -147,7 +154,7 @@ fun ChusanController.chusanInit() {
// Compatibility: Older chusan uses boolean for isSuccess
fun checkAncient(d: List<UserMusicDetail>) =
data["version"]?.double?.let { if (it >= 2.15) d else d.map {
d.toJson().jsonMap().toMutableMap().apply { this["isSuccess"] = this["isSuccess"].truthy }
d.toJson().jsonMap().mut.apply { this["isSuccess"] = this["isSuccess"].truthy }
} } ?: d
db.userMusicDetail.findByUser_Card_ExtId(uid).groupBy { it.musicId }
@ -193,19 +200,16 @@ fun ChusanController.chusanInit() {
// Special thanks to skogaby
// Hardcode so that the reboot time always started 3 hours ago and ended 2 hours ago
val fmt = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss")
// Get the request url as te address
val addr = (req.getHeader("wrapper original url") ?: req.requestURL.toString())
.removeSuffix("GetGameSettingApi").removeSuffix("ChuniServlet/")
val now = jstNow()
val matching = if (!props.externalMatching.isNullOrBlank() && !props.proxiedMatching) mapOf(
"matchingUri" to props.externalMatching,
"matchingUriX" to props.externalMatching,
) else mapOf(
"matchingUri" to addr,
"matchingUriX" to addr
)
// Set the matching & reflector to the one set in the game options, or the external matching server
val opts = TokenChecker.getCurrentSession()?.user?.gameOptions
val matching = opts?.chusanMatchingServer?.ifBlank { null } ?:
props.externalMatching?.ifBlank { null } ?:
(req.getHeader("wrapper original url") ?: req.requestURL.toString())
.removeSuffix("GetGameSettingApi").removeSuffix("ChuniServlet/")
val reflector = opts?.chusanMatchingReflector?.ifBlank { null } ?:
props.reflectorUrl
mapOf(
"gameSetting" to mapOf(
@ -223,39 +227,70 @@ fun ChusanController.chusanInit() {
"matchEndTime" to now.plusHours(7).format(fmt),
"matchTimeLimit" to 10,
"matchErrorLimit" to 10,
"matchingUri" to addr,
"matchingUriX" to addr,
"udpHolePunchUri" to props.reflectorUrl,
"reflectorUri" to props.reflectorUrl,
) + matching,
"matchingUri" to matching.ensureEndingSlash(),
"matchingUriX" to matching.ensureEndingSlash(),
"udpHolePunchUri" to reflector?.ensureEndingSlash(),
"reflectorUri" to reflector?.ensureEndingSlash(),
),
"isDumpUpload" to false,
"isAou" to false
)
}
// Upserts
"UpsertUserChargelog" {
val charge = parsing { mapper.convert<UserCharge>(data["userCharge"] as JDict) }
charge.user = db.userData.findByCard_ExtId(uid)() ?: (400 - "User not found")
charge.id = db.userCharge.findByUser_Card_ExtIdAndChargeId(uid, charge.chargeId)?.id ?: 0
db.userCharge.save(charge)
"""{"returnCode":"1"}"""
}
// Static
"GetGameEvent" static { db.gameEvent.findByEnable(true).let { mapOf("type" to 1, "length" to it.size, "gameEventList" to it) } }
"GetGameCharge" static { db.gameCharge.findAll().let { mapOf("length" to it.size, "gameChargeList" to it) } }
"GetGameGacha" static { db.gameGacha.findAll().let { mapOf("length" to it.size, "gameGachaList" to it, "registIdList" to empty) } }
"GetGameMapAreaCondition" static { ChusanData.mapAreaCondition }
// CardMaker (TODO: Somebody test this, I don't have a card maker)
"CMGetUserData" {
val user = db.userData.findByCard_ExtId(uid)() ?: (400 - "User not found")
user.userEmoney = UserEmoney()
mapOf("userId" to uid, "userData" to user, "userEmoney" to user.userEmoney)
}
"CMGetUserPreview" {
val user = db.userData.findByCard_ExtId(uid)() ?: (400 - "User not found")
mapOf("userName" to user.userName, "level" to user.level, "medal" to user.medal, "lastDataVersion" to user.lastDataVersion, "isLogin" to false)
// TODO: Test login bonus
"GameLogin" {
// fun process() {
// val u = db.userData.findByCard_ExtId(uid)() ?: return
// db.userData.save(u.apply { lastLoginDate = LocalDateTime.now() })
//
// if (!props.loginBonusEnable) return
// val bonusList = db.gameLoginBonusPresets.findLoginBonusPresets(1, 1)
//
// bonusList.forEach { preset ->
// // Check if a user already has some progress and if not, add the login bonus entry
// val bonus = db.userLoginBonus.findLoginBonus(uid.int, 1, preset.id)()
// ?: UserLoginBonus(1, uid.int, preset.id).let { db.userLoginBonus.save(it) }
// if (bonus.isFinished) return@forEach
//
// // last login is 24 hours+ ago
// if (bonus.lastUpdateDate.toEpochSecond(ZoneOffset.ofHours(0)) <
// (LocalDateTime.now().minusHours(24).toEpochSecond(ZoneOffset.ofHours(0)))
// ) {
// var bCount = bonus.bonusCount + 1
// val lastUpdate = LocalDateTime.now()
// val allLoginBonus = db.gameLoginBonus.findGameLoginBonus(1, preset.id)
// .ifEmpty { return@forEach }
// val maxNeededDays = allLoginBonus[0].needLoginDayCount
//
// // if all items are redeemed, then don't show the login bonuses.
// var finished = false
// if (bCount > maxNeededDays) {
// if (preset.id < 3000) bCount = 1
// else finished = true
// }
// db.gameLoginBonus.findByRequiredDays(1, preset.id, bCount)()?.let {
// db.userItem.save(UserItem(6, it.presentId, it.itemNum).apply { user = u })
// }
// val toSave = db.userLoginBonus.findLoginBonus(uid.int, 1, preset.id)()
// ?: UserLoginBonus().apply { user = uid.int; presetId = preset.id; version = 1 }
//
// db.userLoginBonus.save(toSave.apply {
// bonusCount = bCount
// lastUpdateDate = lastUpdate
// isWatched = false
// isFinished = finished
// })
// }
// }
// }
// process()
"""{"returnCode":"1"}"""
}
}

View File

@ -0,0 +1,99 @@
package icu.samnyan.aqua.sega.chusan.handler
import com.fasterxml.jackson.core.type.TypeReference
import ext.*
import icu.samnyan.aqua.sega.chusan.ChusanController
import icu.samnyan.aqua.sega.chusan.model.request.UpsertUserGacha
import icu.samnyan.aqua.sega.chusan.model.request.UserEmoney
import icu.samnyan.aqua.sega.chusan.model.userdata.UserCardPrintState
import icu.samnyan.aqua.sega.chusan.model.userdata.UserItem
import java.time.LocalDateTime
fun ChusanController.cmApiInit() {
"CMUpsertUserPrint" { """{"returnCode":1,"orderId":"0","serialId":"FAKECARDIMAG12345678","apiName":"CMUpsertUserPrintApi"}""" }
"CMUpsertUserPrintlog" { """{"returnCode":1,"orderId":"0","serialId":"FAKECARDIMAG12345678","apiName":"CMUpsertUserPrintlogApi"}""" }
// CardMaker (TODO: Somebody test this, I don't have a card maker)
"CMGetUserData" {
val user = db.userData.findByCard_ExtId(uid)() ?: (400 - "User not found")
user.userEmoney = UserEmoney()
mapOf("userId" to uid, "userData" to user, "userEmoney" to user.userEmoney)
}
"CMGetUserPreview" {
val user = db.userData.findByCard_ExtId(uid)() ?: (400 - "User not found")
mapOf("userName" to user.userName, "level" to user.level, "medal" to user.medal, "lastDataVersion" to user.lastDataVersion, "isLogin" to false)
}
"CMUpsertUserGacha" api@ {
val (gachaId, placeId) = parsing { data["gachaId"]!!.int to data["placeId"]!!.int }
val u = db.userData.findByCard_ExtId(uid)() ?: return@api null
val upsertUserGacha = parsing { mapper.convert(data["cmUpsertUserGacha"], UpsertUserGacha::class.java) }
upsertUserGacha.gameGachaCardList?.let { lst ->
db.userCardPrintState.saveAll(lst.map {
UserCardPrintState(
hasCompleted = false,
limitDate = LocalDateTime.of(2029, 1, 1, 0, 0),
placeId = placeId,
cardId = it.cardId,
gachaId = gachaId
).apply { user = u }
})
}
upsertUserGacha.userItemList?.let {
db.userItem.saveAll(it.mapApply {
user = u
id = db.userItem.findByUserAndItemIdAndItemKind(u, itemId, itemKind)?.id ?: 0
})
}
upsertUserGacha.userGacha?.let {
it.user = u
db.userGacha.save(it)
}
mapOf(
"returnCode" to 1,
"apiName" to "CMUpsertUserGachaApi",
"userCardPrintStateList" to db.userCardPrintState.findByUserAndGachaIdAndHasCompleted(u, gachaId, false)
)
}
"CMUpsertUserPrintCancel" {
val orderIdList: List<Long> = cmMapper.convert(data["orderIdList"], object : TypeReference<List<Long>>() {})
db.userCardPrintState.saveAll(orderIdList.mapNotNull {
// TODO: The original code by Eori writes findById but I don't think that is correct...
db.userCardPrintState.findById(it)()?.apply {
hasCompleted = true
}
})
mapOf("returnCode" to 1, "apiName" to "CMUpsertUserPrintCancelApi")
}
"CMUpsertUserPrintSubtract" api@ {
val userCardPrintState = cmMapper.convert(data["userCardPrintState"], UserCardPrintState::class.java)
val userItemList = cmMapper.convert(data["userItemList"], object : TypeReference<List<UserItem>>() {})
val u = db.userData.findByCard_ExtId(uid)() ?: return@api null
db.userItem.saveAll(
userItemList.mapApply {
id = db.userItem.findByUserAndItemIdAndItemKind(u, itemId, itemKind)?.id ?: 0
user = u
}
)
// TODO: I also doubt this is correct... it shouldn't be ID
db.userCardPrintState.findById(userCardPrintState.id)()?.apply {
hasCompleted = true
db.userCardPrintState.save(this)
}
mapOf("returnCode" to 1, "apiName" to "CMUpsertUserPrintSubtractApi")
}
}

View File

@ -1,15 +1,27 @@
@file:Suppress("UNCHECKED_CAST")
package icu.samnyan.aqua.sega.chusan
package icu.samnyan.aqua.sega.chusan.handler
import ext.*
import icu.samnyan.aqua.sega.chusan.model.response.data.MatchingWaitState
import ext.JDict
import ext.int
import ext.millis
import ext.parsing
import icu.samnyan.aqua.sega.chusan.ChusanController
import icu.samnyan.aqua.sega.chusan.model.request.MatchingWaitState
import icu.samnyan.aqua.sega.chusan.model.userdata.Chu3MatchingMemberReq
import kotlin.collections.MutableList
import kotlin.collections.find
import kotlin.collections.indices
import kotlin.collections.listOf
import kotlin.collections.map
import kotlin.collections.mapOf
import kotlin.collections.mutableListOf
import kotlin.collections.mutableMapOf
import kotlin.collections.set
fun ChusanController.matchingApiInit() {
if (props.externalMatching.isNullOrBlank()) serverOnlyMatching()
else if (props.proxiedMatching) proxyMatching()
}
/**
@ -56,49 +68,3 @@ fun ChusanController.serverOnlyMatching() {
)
}
}
/**
* Matching implementation
*/
fun ChusanController.proxyMatching() {
val ext = props.externalMatching!!
// ID Cache <obfuscated: original> is used to obfuscate the user ID
val processedCache = mutableSetOf<Long>()
val idCache = mutableMapOf<Long, Long>()
fun Chu3MatchingMemberReq.checkFromAquaDX(): Boolean {
if (userId in idCache) return true
if (userId in processedCache) return false
// Check if this user is from our server
val user = db.userData.findByCard_ExtId(userId)()
if (user == null) {
// User is from another server, check if they have been checked in
if (db.matchingMember.existsByUserIdAndUserName(userId, userName)) {
// Check in
db.matchingMember.save(this)
log.info("[Matching] User $userId ($userName) not found, checking in.")
}
processedCache.add(userId)
}
else {
// Is from our server, obfuscate the user ID to enhance security
val randomId = (0..Int.MAX_VALUE).random().toLong()
idCache[randomId] = userId
userId = randomId
log.info("[Matching] User $userId ($userName) is from our server, obfuscated to $randomId.")
}
return user != null
}
"BeginMatching" {
val member = parsing { mapper.convert<Chu3MatchingMemberReq>(data["matchingMemberInfo"] as JDict) }
member.checkFromAquaDX()
// Forward BeginMatching to external server
// val res =
}
TODO("The external matching API is not implemented yet.")
}

View File

@ -0,0 +1,162 @@
package icu.samnyan.aqua.sega.chusan.handler
import ext.*
import icu.samnyan.aqua.sega.chusan.ChusanController
import icu.samnyan.aqua.sega.chusan.model.request.UpsertUserAll
import icu.samnyan.aqua.sega.chusan.model.userdata.*
import icu.samnyan.aqua.sega.general.model.response.UserRecentRating
@Suppress("UNCHECKED_CAST")
fun ChusanController.upsertApiInit() {
"UpsertUserChargelog" {
val charge = parsing { mapper.convert<UserCharge>(data["userCharge"] as JDict) }
charge.user = db.userData.findByCard_ExtId(uid)() ?: (400 - "User not found")
charge.id = db.userCharge.findByUser_Card_ExtIdAndChargeId(uid, charge.chargeId)?.id ?: 0
db.userCharge.save(charge)
"""{"returnCode":"1"}"""
}
"UpsertUserAll" api@ {
val req = mapper.convert(data["upsertUserAll"], UpsertUserAll::class.java)
req.run {
// UserData
val oldUser = db.userData.findByCard_ExtId(uid)()
val u = (userData?.get(0) ?: return@api null).apply {
id = oldUser?.id ?: 0
card = oldUser?.card ?: us.cardRepo.findByExtId(uid).expect("Card not found")
userName = userName.fromChusanUsername()
userNameEx = ""
}.also { db.userData.saveAndFlush(it) }
versionHelper[u.lastClientId] = u.lastDataVersion
// Set users
listOfNotNull(
userPlaylogList, userGameOption, userMapAreaList, userCharacterList, userItemList,
userMusicDetailList, userActivityList, userChargeList, userCourseList, userDuelList,
userNetBattlelogList
).flatten().forEach { it.user = u }
// Ratings
fun Iterable<UserRecentRating>.str() = joinToString(",") { "${it.musicId}:${it.difficultId}:${it.score}" }
ls(
userRecentRatingList to "recent_rating_list", userRatingBaseList to "rating_base_list",
userRatingBaseHotList to "rating_hot_list", userRatingBaseNextList to "rating_next_list",
).filter { it.first != null }.forEach { (list, key) ->
val d = db.userGeneralData.findByUserAndPropertyKey(u, key)()
?: UserGeneralData().apply { user = u; propertyKey = key }
db.userGeneralData.save(d.apply { propertyValue = list!!.str() })
}
val misc = db.userMisc.findSingleByUser(u)() ?: Chu3UserMisc().apply { user = u }
// Favorites
userFavoriteMusicList?.filter { it.musicId != -1 }?.ifEmpty { null }?.let { list ->
misc.favMusic = list.map { it.musicId }.mut
}
// Net battle data
userNetBattleData?.getOrNull(0)?.let {
misc.recentNbSelect = it.recentNBSelectMusicList.map { it.musicId }.mut
}
// Add the battle log songs to misc
if (userNetBattlelogList != null) {
val music = userMusicDetailList?.map { it.musicId } ?: emptyList()
misc.recentNbMusic = (misc.recentNbMusic + music).distinct().takeLast(10).mut
}
db.userMisc.save(misc)
// Playlog
userPlaylogList?.let { db.userPlaylog.saveAll(it) }
userNetBattlelogList?.let { db.netBattleLog.saveAll(it.mapApplyI { i ->
userPlaylogList?.getOrNull(i)?.let {
musicId = it.musicId
difficultyId = it.level
score = it.score
}
selectUserName = selectUserName.fromChusanUsername()
opponentUserName1 = opponentUserName1.fromChusanUsername()
opponentUserName2 = opponentUserName2.fromChusanUsername()
opponentUserName3 = opponentUserName3.fromChusanUsername()
}) }
// List data
userGameOption?.get(0)?.let { obj ->
db.userGameOption.saveAndFlush(obj.apply {
id = db.userGameOption.findSingleByUser(u)()?.id ?: 0 }) }
userMapAreaList?.let { list ->
db.userMap.saveAll(list.distinctBy { it.mapAreaId }.mapApply {
id = db.userMap.findByUserAndMapAreaId(u, mapAreaId)?.id ?: 0 }) }
userCharacterList?.let { list ->
db.userCharacter.saveAll(list.distinctBy { it.characterId }.mapApply {
id = db.userCharacter.findByUserAndCharacterId(u, characterId)?.id ?: 0 }) }
userItemList?.let { list ->
db.userItem.saveAll(list.distinctBy { it.itemId to it.itemKind }.mapApply {
id = db.userItem.findByUserAndItemIdAndItemKind(u, itemId, itemKind)?.id ?: 0 }) }
userMusicDetailList?.let { list ->
db.userMusicDetail.saveAll(list.distinctBy { it.musicId to it.level }.mapApply {
id = db.userMusicDetail.findByUserAndMusicIdAndLevel(u, musicId, level)?.id ?: 0 }) }
userActivityList?.let { list ->
db.userActivity.saveAll(list.distinctBy { it.activityId to it.kind }.mapApply {
id = db.userActivity.findByUserAndActivityIdAndKind(u, activityId, kind)?.id ?: 0 }) }
userChargeList?.let { list ->
db.userCharge.saveAll(list.distinctBy { it.chargeId }.mapApply {
id = db.userCharge.findByUserAndChargeId(u, chargeId)()?.id ?: 0 }) }
userCourseList?.let { list ->
db.userCourse.saveAll(list.distinctBy { it.courseId }.mapApply {
id = db.userCourse.findByUserAndCourseId(u, courseId)?.id ?: 0 }) }
userDuelList?.let { list ->
db.userDuel.saveAll(list.distinctBy { it.duelId }.mapApply {
id = db.userDuel.findByUserAndDuelId(u, duelId)?.id ?: 0 }) }
// Need testing
// userLoginBonusList?.let { list ->
// db.userLoginBonus.saveAll(list.distinctBy { it["presetId"] as String }.map {
// val id = it["presetId"]!!.int
// (db.userLoginBonus.findLoginBonus(uid.int, 1, id)() ?: UserLoginBonus()).apply {
// user = u.id.toInt()
// presetId = id
// lastUpdateDate = LocalDateTime.now()
// isWatched = true
// }
// })
// }
req.userCMissionList?.forEach { d ->
(db.userCMission.findByUser_Card_ExtIdAndMissionId(uid, d.missionId)()
?: UserCMission().apply {
missionId = d.missionId
user = u
}
).apply { point = d.point }.also { db.userCMission.save(it) }
d.userCMissionProgressList?.forEach inner@ { p ->
(db.userCMissionProgress.findByUser_Card_ExtIdAndMissionIdAndOrder(uid, d.missionId, p.order)()
?: UserCMissionProgress().apply {
missionId = d.missionId
order = p.order
user = u
}
).apply {
progress = p.progress
stage = p.stage
}.also { db.userCMissionProgress.save(it) }
}
}
}
"""{"returnCode":1}"""
}
}

View File

@ -1,81 +0,0 @@
package icu.samnyan.aqua.sega.chusan.handler
import ext.int
import ext.invoke
import ext.long
import icu.samnyan.aqua.sega.chusan.ChusanProps
import icu.samnyan.aqua.sega.chusan.model.Chu3Repos
import icu.samnyan.aqua.sega.chusan.model.userdata.UserItem
import icu.samnyan.aqua.sega.chusan.model.userdata.UserLoginBonus
import icu.samnyan.aqua.sega.general.BaseHandler
import lombok.AllArgsConstructor
import org.springframework.stereotype.Component
import java.time.LocalDateTime
import java.time.ZoneOffset
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@AllArgsConstructor
@Component("ChusanGameLoginHandler")
class GameLoginHandler(
val props: ChusanProps,
val db: Chu3Repos
) : BaseHandler {
override fun handle(request: Map<String, Any>): Any? {
val uid = request["userId"]!!.long
fun process() {
val u = db.userData.findByCard_ExtId(uid)() ?: return
db.userData.save(u.apply { lastLoginDate = LocalDateTime.now() })
if (!props.loginBonusEnable) return
val bonusList = db.gameLoginBonusPresets.findLoginBonusPresets(1, 1)
bonusList.forEach { preset ->
// Check if a user already has some progress and if not, add the login bonus entry
val bonus = db.userLoginBonus.findLoginBonus(uid.int, 1, preset.id)()
?: UserLoginBonus(1, uid.int, preset.id).let { db.userLoginBonus.save(it) }
if (bonus.isFinished) return@forEach
// last login is 24 hours+ ago
if (bonus.lastUpdateDate.toEpochSecond(ZoneOffset.ofHours(0)) <
(LocalDateTime.now().minusHours(24).toEpochSecond(ZoneOffset.ofHours(0)))
) {
var bCount = bonus.bonusCount + 1
val lastUpdate = LocalDateTime.now()
val allLoginBonus = db.gameLoginBonus.findGameLoginBonus(1, preset.id)
.ifEmpty { return@forEach }
val maxNeededDays = allLoginBonus[0].needLoginDayCount
// if all items are redeemed, then don't show the login bonuses.
var finished = false
if (bCount > maxNeededDays) {
if (preset.id < 3000) bCount = 1
else finished = true
}
db.gameLoginBonus.findByRequiredDays(1, preset.id, bCount)()?.let {
db.userItem.save(UserItem(u).apply {
itemId = it.presentId
itemKind = 6
stock = it.itemNum
isValid = true
})
}
val toSave = db.userLoginBonus.findLoginBonus(uid.int, 1, preset.id)()
?: UserLoginBonus().apply { user = uid.int; presetId = preset.id; version = 1 }
db.userLoginBonus.save(toSave.apply {
bonusCount = bCount
lastUpdateDate = lastUpdate
isWatched = false
isFinished = finished
})
}
}
}
process()
return """{"returnCode":"1"}"""
}
}

View File

@ -1,153 +0,0 @@
package icu.samnyan.aqua.sega.chusan.handler
import ext.*
import icu.samnyan.aqua.sega.chusan.ChusanVersionHelper
import icu.samnyan.aqua.sega.chusan.model.Chu3Repos
import icu.samnyan.aqua.sega.chusan.model.request.UpsertUserAll
import icu.samnyan.aqua.sega.chusan.model.userdata.UserCMission
import icu.samnyan.aqua.sega.chusan.model.userdata.UserCMissionProgress
import icu.samnyan.aqua.sega.chusan.model.userdata.UserGeneralData
import icu.samnyan.aqua.sega.chusan.model.userdata.UserLoginBonus
import icu.samnyan.aqua.sega.general.BaseHandler
import icu.samnyan.aqua.sega.general.dao.CardRepository
import icu.samnyan.aqua.sega.general.model.response.UserRecentRating
import icu.samnyan.aqua.sega.util.jackson.StringMapper
import lombok.AllArgsConstructor
import org.springframework.stereotype.Component
import java.nio.charset.StandardCharsets
import java.time.LocalDateTime
/**
* The handler for save user data. Only send in the end of the session.
*
* @author samnyan (privateamusement@protonmail.com)
*/
@AllArgsConstructor
@Component("ChusanUpsertUserAllHandler")
class UpsertUserAllHandler(
val mapper: StringMapper,
val rp: Chu3Repos,
val cardRepo: CardRepository,
val versionHelper: ChusanVersionHelper,
) : BaseHandler {
val logger = logger()
override fun handle(request: Map<String, Any>): Any? {
val ext = request["userId"]?.long ?: return null
val req = mapper.convert(request["upsertUserAll"], UpsertUserAll::class.java)
req.run {
// UserData
val oldUser = rp.userData.findByCard_ExtId(ext)()
val u = (userData?.get(0) ?: return null).apply {
id = oldUser?.id ?: 0
card = oldUser?.card ?: cardRepo.findByExtId(ext).expect("Card not found")
userName = String(userName.toByteArray(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8)
userNameEx = ""
}.also { rp.userData.saveAndFlush(it) }
versionHelper[u.lastClientId] = u.lastDataVersion
// Set users
listOfNotNull(
userPlaylogList, userGameOption, userMapAreaList, userCharacterList, userItemList,
userMusicDetailList, userActivityList, userChargeList, userCourseList, userDuelList,
).flatten().forEach { it.user = u }
// Ratings
fun Iterable<UserRecentRating>.str() = joinToString(",") { "${it.musicId}:${it.difficultId}:${it.score}" }
ls(
userRecentRatingList to "recent_rating_list", userRatingBaseList to "rating_base_list",
userRatingBaseHotList to "rating_hot_list", userRatingBaseNextList to "rating_next_list",
).filter { it.first != null }.forEach { (list, key) ->
val d = rp.userGeneralData.findByUserAndPropertyKey(u, key)()
?: UserGeneralData().apply { user = u; propertyKey = key }
rp.userGeneralData.save(d.apply { propertyValue = list!!.str() })
}
userFavoriteMusicList?.let { list ->
val d = rp.userGeneralData.findByUserAndPropertyKey(u, "favorite_music")()
?: UserGeneralData().apply { user = u; propertyKey = "favorite_music" }
rp.userGeneralData.save(d.apply { propertyValue = list.joinToString(",") { it.musicId.toString() } })
}
// Playlog
userPlaylogList?.let { rp.userPlaylog.saveAll(it) }
// List data
userGameOption?.get(0)?.let { obj ->
rp.userGameOption.saveAndFlush(obj.apply {
id = rp.userGameOption.findSingleByUser(u)()?.id ?: 0 }) }
userMapAreaList?.let { list ->
rp.userMap.saveAll(list.distinctBy { it.mapAreaId }.mapApply {
id = rp.userMap.findByUserAndMapAreaId(u, mapAreaId)?.id ?: 0 }) }
userCharacterList?.let { list ->
rp.userCharacter.saveAll(list.distinctBy { it.characterId }.mapApply {
id = rp.userCharacter.findByUserAndCharacterId(u, characterId)?.id ?: 0 }) }
userItemList?.let { list ->
rp.userItem.saveAll(list.distinctBy { it.itemId to it.itemKind }.mapApply {
id = rp.userItem.findByUserAndItemIdAndItemKind(u, itemId, itemKind)?.id ?: 0 }) }
userMusicDetailList?.let { list ->
rp.userMusicDetail.saveAll(list.distinctBy { it.musicId to it.level }.mapApply {
id = rp.userMusicDetail.findByUserAndMusicIdAndLevel(u, musicId, level)?.id ?: 0 }) }
userActivityList?.let { list ->
rp.userActivity.saveAll(list.distinctBy { it.activityId to it.kind }.mapApply {
id = rp.userActivity.findByUserAndActivityIdAndKind(u, activityId, kind)?.id ?: 0 }) }
userChargeList?.let { list ->
rp.userCharge.saveAll(list.distinctBy { it.chargeId }.mapApply {
id = rp.userCharge.findByUserAndChargeId(u, chargeId)()?.id ?: 0 }) }
userCourseList?.let { list ->
rp.userCourse.saveAll(list.distinctBy { it.courseId }.mapApply {
id = rp.userCourse.findByUserAndCourseId(u, courseId)?.id ?: 0 }) }
userDuelList?.let { list ->
rp.userDuel.saveAll(list.distinctBy { it.duelId }.mapApply {
id = rp.userDuel.findByUserAndDuelId(u, duelId)?.id ?: 0 }) }
// Need testing
userLoginBonusList?.let { list ->
rp.userLoginBonus.saveAll(list.distinctBy { it["presetId"] as String }.map {
val id = it["presetId"]!!.int
(rp.userLoginBonus.findLoginBonus(ext.int, 1, id)() ?: UserLoginBonus()).apply {
user = u.id.toInt()
presetId = id
lastUpdateDate = LocalDateTime.now()
isWatched = true
}
})
}
req.userCMissionList?.forEach { d ->
(rp.userCMission.findByUser_Card_ExtIdAndMissionId(ext, d.missionId)()
?: UserCMission().apply {
missionId = d.missionId
user = u
}
).apply { point = d.point }.also { rp.userCMission.save(it) }
d.userCMissionProgressList?.forEach inner@ { p ->
(rp.userCMissionProgress.findByUser_Card_ExtIdAndMissionIdAndOrder(ext, d.missionId, p.order)()
?: UserCMissionProgress().apply {
missionId = d.missionId
order = p.order
user = u
}
).apply {
progress = p.progress
stage = p.stage
}.also { rp.userCMissionProgress.save(it) }
}
}
}
return """{"returnCode":1}"""
}
}

View File

@ -0,0 +1,94 @@
package icu.samnyan.aqua.sega.chusan.model
import com.fasterxml.jackson.annotation.JsonIgnore
import com.fasterxml.jackson.annotation.JsonProperty
import icu.samnyan.aqua.net.games.BaseEntity
import jakarta.persistence.*
import java.time.LocalDateTime
// BaseEntity does not expose id to json, but IdExposedEntity does
@MappedSuperclass
class IdExposedEntity {
@Id
var id: Long = 0
}
@Entity(name = "ChusanGameCharge")
@Table(name = "chusan_game_charge")
class GameCharge: BaseEntity() {
var orderId = 0
@Column(unique = true)
var chargeId = 0
var price = 0
var startDate: LocalDateTime? = null
var endDate: LocalDateTime? = null
var salePrice = 0
var saleStartDate: LocalDateTime? = null
var saleEndDate: LocalDateTime? = null
}
@Entity(name = "ChusanGameEvent")
@Table(name = "chusan_game_event")
class GameEvent: IdExposedEntity() {
val type = 0
val startDate: LocalDateTime? = null
val endDate: LocalDateTime? = null
@JsonIgnore
val enable = false
}
@Entity(name = "ChusanGameGacha")
@Table(name = "chusan_game_gacha")
class GameGacha : IdExposedEntity() {
var gachaId = 0
var gachaName: String? = null
var type = 0
var kind = 0
@JsonProperty("isCeiling")
var isCeiling = false
var ceilingCnt = 0
var changeRateCnt1 = 0
var changeRateCnt2 = 0
var startDate: LocalDateTime? = null
var endDate: LocalDateTime? = null
var noticeStartDate: LocalDateTime? = null
var noticeEndDate: LocalDateTime? = null
}
@Entity(name = "ChusanGameGachaCard")
@Table(name = "chusan_game_gacha_card")
class GameGachaCard : IdExposedEntity() {
var gachaId = 0
var cardId = 0
var rarity = 0
var weight = 0
@JsonProperty("isPickup")
var isPickup = false
}
@Entity(name = "ChusanGameLoginBonus")
@Table(name = "chusan_game_login_bonus")
class GameLoginBonus : IdExposedEntity() {
var version = 0
var presetId = 0
var loginBonusId = 0
var loginBonusName: String? = null
var presentId = 0
var presentName: String? = null
var itemNum = 0
var needLoginDayCount = 0
var loginBonusCategoryType = 0
}
@Entity(name = "ChusanGameLoginBonusPreset")
@Table(name = "chusan_game_login_bonus_preset")
class GameLoginBonusPreset : IdExposedEntity() {
var version = 0
var presetName: String? = null
var isEnabled = false
}

View File

@ -5,7 +5,6 @@ package icu.samnyan.aqua.sega.chusan.model
import icu.samnyan.aqua.net.games.GenericPlaylogRepo
import icu.samnyan.aqua.net.games.GenericUserDataRepo
import icu.samnyan.aqua.net.games.IUserRepo
import icu.samnyan.aqua.sega.chusan.model.gamedata.*
import icu.samnyan.aqua.sega.chusan.model.userdata.*
import org.springframework.data.domain.Page
import org.springframework.data.domain.Pageable
@ -37,7 +36,7 @@ interface Chu3UserLoginBonusRepo : JpaRepository<UserLoginBonus, Long> {
value = "select * from chusan_user_login_bonus where user = ?1 and version = ?2 and preset_id = ?3 limit 1",
nativeQuery = true
)
fun findLoginBonus(userId: Int, version: Int, presetId: Int): Optional<UserLoginBonus>
fun findLoginBonus(userId: Int, version: Int, presetId: Long): Optional<UserLoginBonus>
}
interface Chu3UserActivityRepo : Chu3UserLinked<UserActivity> {
@ -128,13 +127,11 @@ interface Chu3UserCMissionProgressRepo : Chu3UserLinked<UserCMissionProgress> {
fun findByUser_Card_ExtIdAndMissionIdAndOrder(extId: Long, missionId: Int, order: Int): Optional<UserCMissionProgress>
}
interface Chu3MatchingMemberRepo : JpaRepository<Chu3MatchingMember, Long> {
fun existsByUserIdAndUserName(userId: Long, userName: String): Boolean
interface Chu3NetBattleLogRepo : Chu3UserLinked<Chu3NetBattleLog> {
fun findTop20ByUserOrderByIdDesc(user: Chu3UserData): List<Chu3NetBattleLog>
}
interface Chu3GameAvatarAccRepo : JpaRepository<AvatarAcc, Long>
interface Chu3GameCharacterRepo : JpaRepository<Character, Long>
interface Chu3UserMiscRepo : Chu3UserLinked<Chu3UserMisc>
interface Chu3GameChargeRepo : JpaRepository<GameCharge, Long>
@ -142,15 +139,13 @@ interface Chu3GameEventRepo : JpaRepository<GameEvent, Int> {
fun findByEnable(enable: Boolean): List<GameEvent>
}
interface Chu3GameFrameRepo : JpaRepository<Frame, Long>
interface Chu3GameGachaCardRepo : JpaRepository<GameGachaCard, Long> {
fun findAllByGachaId(gachaId: Int): List<GameGachaCard>
}
interface Chu3GameGachaRepo : JpaRepository<GameGacha, Long>
interface Chu3GameLoginBonusPresetsRepo : JpaRepository<GameLoginBonusPreset, Int> {
interface Chu3GameLoginBonusPresetsRepo : JpaRepository<GameLoginBonusPreset, Long> {
@Query(
value = "select * from chusan_game_login_bonus_preset where version = ?1 and is_enabled = ?2",
nativeQuery = true
@ -172,18 +167,6 @@ interface Chu3GameLoginBonusRepo : JpaRepository<GameLoginBonus, Int> {
fun findByRequiredDays(version: Int, presetId: Int, requiredDays: Int): Optional<GameLoginBonus>
}
interface Chu3GameMapIconRepo : JpaRepository<MapIcon, Long>
interface Chu3GameMusicRepo : JpaRepository<Music, Long> {
fun findByMusicId(musicId: Int): Optional<Music>
}
interface Chu3GameNamePlateRepo : JpaRepository<NamePlate, Long>
interface Chu3GameSystemVoiceRepo : JpaRepository<SystemVoice, Long>
interface Chu3GameTrophyRepo : JpaRepository<Trophy, Long>
@Component
class Chu3Repos(
val userLoginBonus: Chu3UserLoginBonusRepo,
@ -203,19 +186,12 @@ class Chu3Repos(
val userPlaylog: Chu3UserPlaylogRepo,
val userCMission: Chu3UserCMissionRepo,
val userCMissionProgress: Chu3UserCMissionProgressRepo,
val matchingMember: Chu3MatchingMemberRepo,
val gameAvatarAcc: Chu3GameAvatarAccRepo,
val gameCharacter: Chu3GameCharacterRepo,
val netBattleLog: Chu3NetBattleLogRepo,
val userMisc: Chu3UserMiscRepo,
val gameCharge: Chu3GameChargeRepo,
val gameEvent: Chu3GameEventRepo,
val gameFrame: Chu3GameFrameRepo,
val gameGachaCard: Chu3GameGachaCardRepo,
val gameGacha: Chu3GameGachaRepo,
val gameLoginBonusPresets: Chu3GameLoginBonusPresetsRepo,
val gameLoginBonus: Chu3GameLoginBonusRepo,
val gameMapIcon: Chu3GameMapIconRepo,
val gameMusic: Chu3GameMusicRepo,
val gameNamePlate: Chu3GameNamePlateRepo,
val gameSystemVoice: Chu3GameSystemVoiceRepo,
val gameTrophy: Chu3GameTrophyRepo
val gameLoginBonus: Chu3GameLoginBonusRepo
)

View File

@ -1,30 +0,0 @@
package icu.samnyan.aqua.sega.chusan.model.gamedata;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Entity(name = "AvatarAcc")
@Table(name = "chusan_avatar")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class AvatarAcc implements Serializable {
private static final long serialVersionUID = 1L;
@Id
private long id;
private String name;
private int category;
}

View File

@ -1,37 +0,0 @@
package icu.samnyan.aqua.sega.chusan.model.gamedata;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Entity(name = "ChusanCharacter")
@Table(name = "chusan_game_character")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Character implements Serializable {
private static final long serialVersionUID = 1L;
@Id
private long id;
private String name;
private String releaseTag;
private String worksName;
private String illustratorName;
private String addImages;
}

View File

@ -1,28 +0,0 @@
package icu.samnyan.aqua.sega.chusan.model.gamedata;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Entity(name = "ChusanFrame")
@Table(name = "chusan_frame")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Frame implements Serializable {
private static final long serialVersionUID = 1L;
@Id
private long id;
private String name;
}

View File

@ -1,46 +0,0 @@
package icu.samnyan.aqua.sega.chusan.model.gamedata;
import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Entity(name = "ChusanGameCharge")
@Table(name = "chusan_game_charge")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class GameCharge implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@JsonIgnore
private long id;
private int orderId;
@Column(unique = true)
private int chargeId;
private int price;
private LocalDateTime startDate;
private LocalDateTime endDate;
private int salePrice;
private LocalDateTime saleStartDate;
private LocalDateTime saleEndDate;
}

View File

@ -1,37 +0,0 @@
package icu.samnyan.aqua.sega.chusan.model.gamedata;
import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Entity(name = "ChusanGameEvent")
@Table(name = "chusan_game_event")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class GameEvent implements Serializable {
private static final long serialVersionUID = 1L;
@Id
private int id;
private int type;
private LocalDateTime startDate;
private LocalDateTime endDate;
@JsonIgnore
private boolean enable;
}

View File

@ -1,42 +0,0 @@
package icu.samnyan.aqua.sega.chusan.model.gamedata;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Entity(name = "ChusanGameGacha")
@Table(name = "chusan_game_gacha")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class GameGacha implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@JsonIgnore
private int id;
private int gachaId;
private String gachaName;
private int type;
private int kind; // 0
@JsonProperty("isCeiling")
private boolean isCeiling;
private int ceilingCnt;
private int changeRateCnt1;
private int changeRateCnt2;
private LocalDateTime startDate;
private LocalDateTime endDate;
private LocalDateTime noticeStartDate;
private LocalDateTime noticeEndDate;
}

View File

@ -1,34 +0,0 @@
package icu.samnyan.aqua.sega.chusan.model.gamedata;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Entity(name = "ChusanGameGachaCard")
@Table(name = "chusan_game_gacha_card")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class GameGachaCard implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@JsonIgnore
private int id;
private int gachaId;
private int cardId;
private int rarity;
private int weight;
@JsonProperty("isPickup")
private boolean isPickup;
}

View File

@ -1,31 +0,0 @@
package icu.samnyan.aqua.sega.chusan.model.gamedata;
import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Entity(name = "ChusanGameLoginBonus")
@Table(name = "chusan_game_login_bonus")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class GameLoginBonus implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@JsonIgnore
private int id;
private int version;
private int presetId;
private int loginBonusId;
private String loginBonusName;
private int presentId;
private String presentName;
private int itemNum;
private int needLoginDayCount;
private int loginBonusCategoryType;
}

View File

@ -1,27 +0,0 @@
package icu.samnyan.aqua.sega.chusan.model.gamedata;
import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Entity(name = "ChusanGameLoginBonusPreset")
@Table(name = "chusan_game_login_bonus_preset")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class GameLoginBonusPreset implements Serializable {
// No one cares about chuni lol
// Maimai and Ongeki all got their login bonus but nothing for chunithm
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@JsonIgnore
private int id;
private int version;
private String presetName;
private boolean isEnabled;
}

View File

@ -1,32 +0,0 @@
package icu.samnyan.aqua.sega.chusan.model.gamedata;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
public enum Genre {
POPS_ANIME("POPS & ANIME"),
GAME("GAME"),
NICONICO("niconico"),
TOUHOU("東方Project"),
RESERVE2("Reserve2"),
ORIGINAL("Original"),
VARIETY("Variety"),
IRODORI("イロドリミドリ"),
KOTONOHA("言葉Project"),
GEKICHUMA("ゲキマイ");
private String displayName;
Genre(String displayName) {
this.displayName = displayName;
}
public String displayName() {
return displayName;
}
@Override
public String toString() {
return displayName;
}
}

View File

@ -1,40 +0,0 @@
package icu.samnyan.aqua.sega.chusan.model.gamedata;
import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Entity(name = "ChusanMusicLevel")
@Table(name = "chusan_music_level")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Level implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@JsonIgnore
private long id;
@JsonIgnore
@ManyToOne
@JoinColumn(name = "music_id")
private Music music;
private boolean enable;
private int level;
private int levelDecimal;
private int diff;
}

View File

@ -1,28 +0,0 @@
package icu.samnyan.aqua.sega.chusan.model.gamedata;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Entity(name = "ChusanMapIcon")
@Table(name = "chusan_mapicon")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class MapIcon implements Serializable {
private static final long serialVersionUID = 1L;
@Id
private long id;
private String name;
}

View File

@ -1,40 +0,0 @@
package icu.samnyan.aqua.sega.chusan.model.gamedata;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.Map;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Entity(name = "ChusanMusic")
@Table(name = "chusan_music")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Music implements Serializable {
private static final long serialVersionUID = 1L;
@Id
private int musicId;
private String name;
private String sortName;
private String artistName;
private Genre genre;
private String releaseVersion;
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "music")
@MapKey(name = "diff")
private Map<Integer, Level> levels;
}

View File

@ -1,28 +0,0 @@
package icu.samnyan.aqua.sega.chusan.model.gamedata;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Entity(name = "ChusanNamePlate")
@Table(name = "chusan_nameplate")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class NamePlate implements Serializable {
private static final long serialVersionUID = 1L;
@Id
private long id;
private String name;
}

View File

@ -1,28 +0,0 @@
package icu.samnyan.aqua.sega.chusan.model.gamedata;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Entity(name = "ChusanSystemVoice")
@Table(name = "chusan_voice")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class SystemVoice implements Serializable {
private static final long serialVersionUID = 1L;
@Id
private long id;
private String name;
}

View File

@ -1,28 +0,0 @@
package icu.samnyan.aqua.sega.chusan.model.gamedata;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Entity(name = "ChusanTrophy")
@Table(name = "chusan_trophy")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Trophy implements Serializable {
private static final long serialVersionUID = 1L;
@Id
private long id;
private String name;
}

View File

@ -1,4 +1,4 @@
package icu.samnyan.aqua.sega.chusan.model.response.data
package icu.samnyan.aqua.sega.chusan.model.request
import com.fasterxml.jackson.annotation.JsonProperty
import icu.samnyan.aqua.sega.chusan.model.userdata.Chu3MatchingMemberReq

View File

@ -5,7 +5,6 @@ import icu.samnyan.aqua.sega.chusan.model.userdata.*
import icu.samnyan.aqua.sega.general.model.response.UserRecentRating
class UserCMissionResp {
var userId: Long? = 0
var missionId = 0
var point = 0
var userCMissionProgressList: List<UserCMissionProgress>? = null
@ -16,6 +15,28 @@ class FavNewMusic(
var orderId: Int = 0,
)
class UpsertTeamPoint(
// userId exists here, but it should not be used
// So I will not include it in the data class
var teamId: Long = 0,
var orderId: Int = 0,
var teamPoint: Long = 0,
var aggrDate: String = "",
)
data class UpsertNetBattleData(
val recentNBSelectMusicList: List<MusicIdWrapper> = emptyList(),
val isRankUpChallengeFailed: Boolean = false,
val highestBattleRankId: Long = 0,
val battleIconId: Long = 0,
val battleIconNum: Long = 0,
val avatarEffectPoint: Long = 0,
)
data class MusicIdWrapper(
val musicId: Int = 0,
)
class UpsertUserAll(
var userData: List<Chu3UserData>? = null,
var userGameOption: List<UserGameOption>? = null,
@ -28,16 +49,17 @@ class UpsertUserAll(
var userChargeList: List<UserCharge>? = null,
var userCourseList: List<UserCourse>? = null,
var userDuelList: List<UserDuel>? = null,
var userTeamPoint: List<JDict>? = null,
// TODO: Actually implement team
var userTeamPoint: List<UpsertTeamPoint>? = null,
var userRatingBaseHotList: List<UserRecentRating>? = null,
var userRatingBaseList: List<UserRecentRating>? = null,
var userRatingBaseNextList: List<UserRecentRating>? = null,
var userLoginBonusList: List<JDict>? = null,
var userMapAreaList: List<UserMap>? = null,
var userOverPowerList: List<JDict>? = null,
var userNetBattlelogList: List<JDict>? = null,
var userNetBattlelogList: List<Chu3NetBattleLog>? = null,
var userEmoneyList: List<JDict>? = null,
var userNetBattleData: List<JDict>? = null,
var userNetBattleData: List<UpsertNetBattleData>? = null,
var userCMissionList: List<UserCMissionResp>? = null,
var userFavoriteMusicList: List<FavNewMusic>? = null,
)

View File

@ -1,50 +0,0 @@
package icu.samnyan.aqua.sega.chusan.model.request;
import com.fasterxml.jackson.annotation.JsonProperty;
import icu.samnyan.aqua.sega.chusan.model.gamedata.GameGachaCard;
import icu.samnyan.aqua.sega.chusan.model.userdata.Chu3UserData;
import icu.samnyan.aqua.sega.chusan.model.userdata.UserGacha;
import icu.samnyan.aqua.sega.chusan.model.userdata.UserItem;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.lang.Nullable;
import java.io.Serializable;
import java.util.List;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UpsertUserGacha implements Serializable {
@Nullable
private Chu3UserData userData;
@Nullable
private UserGacha userGacha;
@Nullable
private List<Object> userCharacterList;
@Nullable
private List<Object> userCardList;
@Nullable
private List<GameGachaCard> gameGachaCardList;
@Nullable
private List<UserItem> userItemList;
@Nullable
@JsonProperty("isNewCharacterList")
private String isNewCharacterList;
@Nullable
@JsonProperty("isNewCardList")
private String isNewCardList;
}

View File

@ -0,0 +1,22 @@
package icu.samnyan.aqua.sega.chusan.model.request
import com.fasterxml.jackson.annotation.JsonProperty
import icu.samnyan.aqua.sega.chusan.model.GameGachaCard
import icu.samnyan.aqua.sega.chusan.model.userdata.Chu3UserData
import icu.samnyan.aqua.sega.chusan.model.userdata.UserGacha
import icu.samnyan.aqua.sega.chusan.model.userdata.UserItem
import java.io.Serializable
class UpsertUserGacha : Serializable {
var userData: Chu3UserData? = null
var userGacha: UserGacha? = null
var userCharacterList: List<Any>? = null
var userCardList: List<Any>? = null
var gameGachaCardList: List<GameGachaCard>? = null
var userItemList: List<UserItem>? = null
@JsonProperty("isNewCharacterList")
var isNewCharacterList: String? = null
@JsonProperty("isNewCardList")
var isNewCardList: String? = null
}

View File

@ -0,0 +1,11 @@
package icu.samnyan.aqua.sega.chusan.model.request
class UserEmoney(
var type: Int = 0,
var emoneyCredit: Int = 69,
var emoneyBrand: Int = 2,
var ext1: Int = 0,
var ext2: Int = 0,
var ext3: Int = 0
)

View File

@ -1,16 +0,0 @@
package icu.samnyan.aqua.sega.chusan.model.response.data;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class GameRanking {
private int id;
private long point;
}

View File

@ -1,19 +0,0 @@
package icu.samnyan.aqua.sega.chusan.model.response.data;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class SymbolChatInfo {
private int sceneId;
private int symbolChatId;
private int orderId;
}

View File

@ -1,22 +0,0 @@
package icu.samnyan.aqua.sega.chusan.model.response.data;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserEmoney {
private int type = 0;
private int emoneyCredit = 69;
private int emoneyBrand = 2;
private int ext1 = 0;
private int ext2 = 0;
private int ext3 = 0;
}

View File

@ -1,17 +0,0 @@
package icu.samnyan.aqua.sega.chusan.model.response.data;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserFavorite {
private int id;
}

View File

@ -1,13 +1,8 @@
package icu.samnyan.aqua.sega.chusan.model.userdata
import com.fasterxml.jackson.annotation.JsonProperty
import icu.samnyan.aqua.net.games.BaseEntity
import jakarta.persistence.Entity
import jakarta.persistence.Table
@Entity(name = "ChusanMatchingMember")
@Table(name = "chusan_matching_member")
class Chu3MatchingMember : BaseEntity() {
open class Chu3MatchingMember {
var userId: Long = 0
var regionId = 0
var placeId = 0

View File

@ -0,0 +1,76 @@
package icu.samnyan.aqua.sega.chusan.model.userdata
import jakarta.persistence.Entity
import jakarta.persistence.Table
@Entity(name = "ChusanNetBattleLog")
@Table(name = "chusan_net_battle_log")
class Chu3NetBattleLog(
var roomId: Long = 0,
var track: Int = 0,
// From playlog
var musicId: Int = 0,
var difficultyId: Int = 0,
var score: Int = 0,
var selectUserId: Long = 0,
var selectUserName: String = "",
var opponentUserId1: Long = 0,
var opponentUserId2: Long = 0,
var opponentUserId3: Long = 0,
var opponentUserName1: String = "",
var opponentUserName2: String = "",
var opponentUserName3: String = "",
var opponentRegionId1: Int = 0,
var opponentRegionId2: Int = 0,
var opponentRegionId3: Int = 0,
var opponentRating1: Int = 0,
var opponentRating2: Int = 0,
var opponentRating3: Int = 0,
var opponentBattleRankId1: Int = 0,
var opponentBattleRankId2: Int = 0,
var opponentBattleRankId3: Int = 0,
var opponentClassEmblemMedal1: Int = 0,
var opponentClassEmblemMedal2: Int = 0,
var opponentClassEmblemMedal3: Int = 0,
var opponentClassEmblemBase1: Int = 0,
var opponentClassEmblemBase2: Int = 0,
var opponentClassEmblemBase3: Int = 0,
var opponentScore1: Int = 0,
var opponentScore2: Int = 0,
var opponentScore3: Int = 0,
var opponentCharaIllustId1: Int = 0,
var opponentCharaIllustId2: Int = 0,
var opponentCharaIllustId3: Int = 0,
var opponentCharaLv1: Int = 0,
var opponentCharaLv2: Int = 0,
var opponentCharaLv3: Int = 0,
var opponentRatingEffectColorId1: Int = 0,
var opponentRatingEffectColorId2: Int = 0,
var opponentRatingEffectColorId3: Int = 0,
var battleRuleId: Int = 0,
var monthPoLong: Int = 0,
var eventPoLong: Int = 0
) : Chu3UserEntity() {
// musicId, difficultyId, userName, score, memberName{1-3}, memberScore{1-3}, selectedMemberNum
fun toDict(username: String) = mapOf(
"musicId" to musicId,
"difficultyId" to difficultyId,
"score" to score,
"userName" to username,
"memberName1" to opponentUserName1,
"memberScore1" to opponentScore1,
"memberName2" to opponentUserName2,
"memberScore2" to opponentScore2,
"memberName3" to opponentUserName3,
"memberScore3" to opponentScore3,
"selectedMemberNum" to listOf(
true,
selectUserId == opponentUserId1,
selectUserId == opponentUserId2,
selectUserId == opponentUserId3
).lastIndexOf(true)
)
}

View File

@ -6,7 +6,7 @@ import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.databind.annotation.JsonSerialize
import icu.samnyan.aqua.net.games.BaseEntity
import icu.samnyan.aqua.net.games.IUserData
import icu.samnyan.aqua.sega.chusan.model.response.data.UserEmoney
import icu.samnyan.aqua.sega.chusan.model.request.UserEmoney
import icu.samnyan.aqua.sega.general.model.Card
import icu.samnyan.aqua.sega.util.jackson.AccessCodeSerializer
import jakarta.persistence.*

View File

@ -8,9 +8,9 @@ import jakarta.persistence.ManyToOne
import jakarta.persistence.MappedSuperclass
@MappedSuperclass
open class Chu3UserEntity : BaseEntity(), IUserEntity<Chu3UserData> {
class Chu3UserEntity : BaseEntity(), IUserEntity<Chu3UserData> {
@JsonIgnore
@ManyToOne
@JoinColumn(name = "user_id")
public override var user: Chu3UserData = Chu3UserData()
override var user: Chu3UserData = Chu3UserData()
}

View File

@ -0,0 +1,18 @@
package icu.samnyan.aqua.sega.chusan.model.userdata
import icu.samnyan.aqua.sega.general.IntegerListConverter
import jakarta.persistence.Convert
import jakarta.persistence.Entity
@Entity(name = "ChusanUserMisc")
class Chu3UserMisc(
@Convert(converter = IntegerListConverter::class)
var recentNbSelect: MutableList<Int> = mutableListOf(),
@Convert(converter = IntegerListConverter::class)
var recentNbMusic: MutableList<Int> = mutableListOf(),
@Convert(converter = IntegerListConverter::class)
var favMusic: MutableList<Int> = mutableListOf()
): Chu3UserEntity()

View File

@ -8,9 +8,7 @@ import jakarta.persistence.UniqueConstraint
@Entity(name = "ChusanUserActivity")
@Table(name = "chusan_user_activity", uniqueConstraints = [UniqueConstraint(columnNames = ["user_id", "kind", "activity_id"])])
class UserActivity(user: Chu3UserData) : Chu3UserEntity() {
init { this.user = user }
class UserActivity : Chu3UserEntity() {
var kind = 0
@JsonProperty("id")
@Column(name = "activity_id")

View File

@ -1,15 +1,11 @@
package icu.samnyan.aqua.sega.chusan.model.userdata
import jakarta.persistence.Column
import jakarta.persistence.Entity
import jakarta.persistence.Table
@Entity(name = "ChusanUserCMission")
@Table(name = "chusan_user_cmission")
class UserCMission : Chu3UserEntity() {
@Column(name = "mission_id")
var missionId = 0
@Column(name = "point")
var point = 0
}

View File

@ -6,10 +6,11 @@ import java.time.LocalDateTime
@Entity(name = "ChusanUserCardPrintState")
@Table(name = "chusan_user_print_state")
class UserCardPrintState : Chu3UserEntity() {
var hasCompleted = false
var limitDate: LocalDateTime = LocalDateTime.now()
var placeId = 0
var cardId = 0
var gachaId = 0
}
class UserCardPrintState(
var hasCompleted: Boolean = false,
var limitDate: LocalDateTime = LocalDateTime.now(),
var placeId: Int = 0,
var cardId: Int = 0,
var gachaId: Int = 0
) : Chu3UserEntity()

View File

@ -1,49 +0,0 @@
package icu.samnyan.aqua.sega.chusan.model.userdata;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Entity(name = "ChusanUserCharacter")
@Table(name = "chusan_user_character", uniqueConstraints = {@UniqueConstraint(columnNames = {"user_id", "character_id"})})
@Data
@NoArgsConstructor
@AllArgsConstructor
@JsonPropertyOrder({"characterId", "playCount", "level", "friendshipExp", "isValid", "isNewMark", "exMaxLv", "assignIllust", "param1", "param2"})
public class UserCharacter extends Chu3UserEntity {
@Column(name = "character_id")
private int characterId;
private int playCount = 0;
private int level = 1;
private int friendshipExp = 0;
@JsonProperty("isValid")
private boolean isValid = true;
@JsonProperty("isNewMark")
private boolean isNewMark = true;
private int exMaxLv = 0;
private int assignIllust = 0;
private int param1 = 0;
private int param2 = 0;
public UserCharacter(Chu3UserData userData) {
setUser(userData);
}
}

View File

@ -0,0 +1,25 @@
package icu.samnyan.aqua.sega.chusan.model.userdata
import com.fasterxml.jackson.annotation.JsonProperty
import jakarta.persistence.Column
import jakarta.persistence.Entity
import jakarta.persistence.Table
import jakarta.persistence.UniqueConstraint
@Entity(name = "ChusanUserCharacter")
@Table(name = "chusan_user_character", uniqueConstraints = [UniqueConstraint(columnNames = ["user_id", "character_id"])])
class UserCharacter : Chu3UserEntity() {
@Column(name = "character_id")
var characterId = 0
var playCount = 0
var level = 1
var friendshipExp = 0
@JsonProperty("isValid")
var isValid = true
@JsonProperty("isNewMark")
var isNewMark = true
var exMaxLv = 0
var assignIllust = 0
var param1 = 0
var param2 = 0
}

View File

@ -1,42 +0,0 @@
package icu.samnyan.aqua.sega.chusan.model.userdata;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Entity(name = "ChusanUserCharge")
@Table(name = "chusan_user_charge", uniqueConstraints = {@UniqueConstraint(columnNames = {"user_id", "charge_id"})})
@Data
@NoArgsConstructor
@AllArgsConstructor
@JsonPropertyOrder({"chargeId", "stock", "purchaseDate", "validDate", "param1", "param2", "paramDate"})
public class UserCharge extends Chu3UserEntity {
@Column(name = "charge_id")
private int chargeId;
private int stock;
private LocalDateTime purchaseDate;
private LocalDateTime validDate;
private int param1;
private int param2;
private LocalDateTime paramDate;
public UserCharge(Chu3UserData user) {
setUser(user);
}
}

View File

@ -0,0 +1,18 @@
package icu.samnyan.aqua.sega.chusan.model.userdata
import jakarta.persistence.Entity
import jakarta.persistence.Table
import jakarta.persistence.UniqueConstraint
import java.time.LocalDateTime
@Entity(name = "ChusanUserCharge")
@Table(name = "chusan_user_charge", uniqueConstraints = [UniqueConstraint(columnNames = ["user_id", "charge_id"])])
class UserCharge : Chu3UserEntity() {
var chargeId = 0
var stock = 0
var purchaseDate: LocalDateTime? = null
var validDate: LocalDateTime? = null
var param1 = 0
var param2 = 0
var paramDate: LocalDateTime? = null
}

View File

@ -1,71 +0,0 @@
package icu.samnyan.aqua.sega.chusan.model.userdata;
import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Entity(name = "ChusanUserCourse")
@Table(name = "chusan_user_course", uniqueConstraints = {@UniqueConstraint(columnNames = {"user_id", "course_id"})})
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserCourse extends Chu3UserEntity {
@Column(name = "course_id")
private int courseId;
private int classId;
private int playCount;
private int theoryCount;
private int scoreMax;
@JsonProperty("isFullCombo")
private boolean isFullCombo;
@JsonProperty("isAllJustice")
private boolean isAllJustice;
@JsonProperty("isSuccess")
private boolean isSuccess;
private int scoreRank;
private int eventId;
private LocalDateTime lastPlayDate;
private int param1;
private int param2;
private int param3;
private int param4;
private int orderId;
private int playerRating;
@JsonProperty("isClear")
private boolean isClear;
public UserCourse(Chu3UserData userData) {
setUser(userData);
}
public UserCourse(int classId) {
this.classId = classId;
}
}

View File

@ -0,0 +1,33 @@
package icu.samnyan.aqua.sega.chusan.model.userdata
import com.fasterxml.jackson.annotation.JsonProperty
import jakarta.persistence.Entity
import jakarta.persistence.Table
import jakarta.persistence.UniqueConstraint
import java.time.LocalDateTime
@Entity(name = "ChusanUserCourse")
@Table(name = "chusan_user_course", uniqueConstraints = [UniqueConstraint(columnNames = ["user_id", "course_id"])])
class UserCourse : Chu3UserEntity() {
var courseId = 0
var classId = 0
var playCount = 0
var theoryCount = 0
var scoreMax = 0
@JsonProperty("isFullCombo")
var isFullCombo = false
@JsonProperty("isAllJustice")
var isAllJustice = false
@JsonProperty("isSuccess")
var isSuccess = false
var scoreRank = 0
var eventId = 0
var lastPlayDate: LocalDateTime? = null
var param1 = 0
var param2 = 0
var param3 = 0
var param4 = 0
var orderId = 0
var playerRating = 0
@JsonProperty("isClear")
var isClear = false
}

View File

@ -1,46 +0,0 @@
package icu.samnyan.aqua.sega.chusan.model.userdata;
import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Entity(name = "ChusanUserDuel")
@Table(name = "chusan_user_duel", uniqueConstraints = {@UniqueConstraint(columnNames = {"user_id", "duel_id"})})
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserDuel extends Chu3UserEntity {
@Column(name = "duel_id")
private int duelId;
private int progress;
private int point;
@JsonProperty("isClear")
private boolean isClear;
private LocalDateTime lastPlayDate;
private int param1;
private int param2;
private int param3;
private int param4;
public UserDuel(Chu3UserData userData) {
setUser(userData);
}
}

View File

@ -0,0 +1,23 @@
package icu.samnyan.aqua.sega.chusan.model.userdata
import com.fasterxml.jackson.annotation.JsonProperty
import jakarta.persistence.Column
import jakarta.persistence.Entity
import jakarta.persistence.Table
import jakarta.persistence.UniqueConstraint
import java.time.LocalDateTime
@Entity(name = "ChusanUserDuel")
@Table(name = "chusan_user_duel", uniqueConstraints = [UniqueConstraint(columnNames = ["user_id", "duel_id"])])
class UserDuel : Chu3UserEntity() {
@Column(name = "duel_id")
var duelId = 0
var progress = 0
var point = 0
@JsonProperty("isClear")
var isClear = false
var lastPlayDate: LocalDateTime? = null
var param1 = 0
var param2 = 0
var param3 = 0
var param4 = 0
}

View File

@ -1,36 +0,0 @@
package icu.samnyan.aqua.sega.chusan.model.userdata;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Entity(name = "ChusanUserGacha")
@Table(name = "chusan_user_gacha", uniqueConstraints = {@UniqueConstraint(columnNames = {"user_id", "gacha_id"})})
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserGacha extends Chu3UserEntity {
@Column(name = "gacha_id")
private int gachaId;
private int totalGachaCnt;
private int ceilingGachaCnt;
private int dailyGachaCnt;
private int fiveGachaCnt;
private int elevenGachaCnt;
private LocalDateTime dailyGachaDate;
}

View File

@ -0,0 +1,17 @@
package icu.samnyan.aqua.sega.chusan.model.userdata
import jakarta.persistence.Entity
import jakarta.persistence.Table
import java.time.LocalDateTime
@Entity(name = "ChusanUserGacha")
@Table(name = "chusan_user_gacha")
class UserGacha : Chu3UserEntity() {
var gachaId = 0
var totalGachaCnt = 0
var ceilingGachaCnt = 0
var dailyGachaCnt = 0
var fiveGachaCnt = 0
var elevenGachaCnt = 0
var dailyGachaDate: LocalDateTime? = null
}

View File

@ -1,175 +0,0 @@
package icu.samnyan.aqua.sega.chusan.model.userdata;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Entity(name = "ChusanUserGameOption")
@Table(name = "chusan_user_game_option")
@Data
@NoArgsConstructor
@AllArgsConstructor
@JsonPropertyOrder({
"bgInfo",
"fieldColor",
"guideSound",
"soundEffect",
"guideLine",
"speed",
"optionSet",
"matching",
"judgePos",
"rating",
"judgeCritical",
"judgeJustice",
"judgeAttack",
"headphone",
"playerLevel",
"successTap",
"successExTap",
"successSlideHold",
"successAir",
"successFlick",
"successSkill",
"successTapTimbre",
"privacy",
"mirrorFumen",
"selectMusicFilterLv",
"sortMusicFilterLv",
"sortMusicGenre",
"categoryDetail",
"judgeTimingOffset",
"playTimingOffset",
"fieldWallPosition",
"resultVoiceShort",
"notesThickness",
"judgeAppendSe",
"trackSkip",
"hardJudge",
"speed_120",
"fieldWallPosition_120",
"playTimingOffset_120",
"judgeTimingOffset_120",
"ext1",
"ext2",
"ext3",
"ext4",
"ext5",
"ext6",
"ext7",
"ext8",
"ext9",
"ext10"
})
public class UserGameOption extends Chu3UserEntity {
private int bgInfo;
private int fieldColor;
private int guideSound;
private int soundEffect;
private int guideLine;
private int speed;
private int optionSet;
private int matching;
private int judgePos;
private int rating;
private int judgeCritical;
private int judgeJustice;
private int judgeAttack;
private int headphone;
private int playerLevel;
private int successTap;
private int successExTap;
private int successSlideHold;
private int successAir;
private int successFlick;
private int successSkill;
private int successTapTimbre;
private int privacy;
private int mirrorFumen;
private int selectMusicFilterLv;
private int sortMusicFilterLv;
private int sortMusicGenre;
private int categoryDetail;
private int judgeTimingOffset;
private int playTimingOffset;
private int fieldWallPosition;
private int resultVoiceShort;
private int notesThickness;
private int judgeAppendSe;
private int trackSkip;
private int hardJudge;
private int speed_120;
private int fieldWallPosition_120;
private int playTimingOffset_120;
private int judgeTimingOffset_120;
private int ext1;
private int ext2;
private int ext3;
private int ext4;
private int ext5;
private int ext6;
private int ext7;
private int ext8;
private int ext9;
private int ext10;
public UserGameOption(Chu3UserData userData) {
setUser(userData);
}
}

View File

@ -0,0 +1,58 @@
package icu.samnyan.aqua.sega.chusan.model.userdata
import jakarta.persistence.Entity
import jakarta.persistence.Table
@Entity(name = "ChusanUserGameOption")
@Table(name = "chusan_user_game_option")
class UserGameOption : Chu3UserEntity() {
var bgInfo = 0
var fieldColor = 0
var guideSound = 0
var soundEffect = 0
var guideLine = 0
var speed = 0
var optionSet = 0
var matching = 0
var judgePos = 0
var rating = 0
var judgeCritical = 0
var judgeJustice = 0
var judgeAttack = 0
var headphone = 0
var playerLevel = 0
var successTap = 0
var successExTap = 0
var successSlideHold = 0
var successAir = 0
var successFlick = 0
var successSkill = 0
var successTapTimbre = 0
var privacy = 0
var mirrorFumen = 0
var selectMusicFilterLv = 0
var sortMusicFilterLv = 0
var sortMusicGenre = 0
var categoryDetail = 0
var judgeTimingOffset = 0
var playTimingOffset = 0
var fieldWallPosition = 0
var resultVoiceShort = 0
var notesThickness = 0
var judgeAppendSe = 0
var trackSkip = 0
var hardJudge = 0
var speed_120 = 0
var fieldWallPosition_120 = 0
var playTimingOffset_120 = 0
var judgeTimingOffset_120 = 0
var ext1 = 0
var ext2 = 0
var ext3 = 0
var ext4 = 0
var ext5 = 0
var ext6 = 0
var ext7 = 0
var ext8 = 0
var ext9 = 0
var ext10 = 0
}

View File

@ -1,30 +0,0 @@
package icu.samnyan.aqua.sega.chusan.model.userdata;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* This is for storing the other data that doesn't need to save it in a separate table
* @author samnyan (privateamusement@protonmail.com)
*/
@Entity(name = "ChusanUserGeneralData")
@Table(name = "chusan_user_general_data")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserGeneralData extends Chu3UserEntity {
private String propertyKey;
@Column(columnDefinition = "TEXT")
private String propertyValue;
public UserGeneralData(Chu3UserData userData, String key) {
setUser(userData);
this.propertyKey = key;
this.propertyValue = "";
}
}

View File

@ -0,0 +1,13 @@
package icu.samnyan.aqua.sega.chusan.model.userdata
import jakarta.persistence.Column
import jakarta.persistence.Entity
import jakarta.persistence.Table
@Entity(name = "ChusanUserGeneralData")
@Table(name = "chusan_user_general_data")
class UserGeneralData(
@Column(columnDefinition = "TEXT")
var propertyValue: String = "",
var propertyKey: String = "",
) : Chu3UserEntity()

View File

@ -1,38 +0,0 @@
package icu.samnyan.aqua.sega.chusan.model.userdata;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Entity(name = "ChusanUserItem")
@Table(name = "chusan_user_item", uniqueConstraints = {@UniqueConstraint(columnNames = {"user_id", "item_id", "item_kind"})})
@Data
@NoArgsConstructor
@AllArgsConstructor
@JsonPropertyOrder({"itemKind", "itemId", "stock", "isValid"})
public class UserItem extends Chu3UserEntity {
// Kind ,Type
@Column(name = "item_kind")
private int itemKind;
@Column(name = "item_id")
private int itemId;
private int stock = 1;
@JsonProperty("isValid")
private boolean isValid = true;
public UserItem(Chu3UserData userData) {
setUser(userData);
}
}

View File

@ -0,0 +1,16 @@
package icu.samnyan.aqua.sega.chusan.model.userdata
import com.fasterxml.jackson.annotation.JsonProperty
import jakarta.persistence.Entity
import jakarta.persistence.Table
import jakarta.persistence.UniqueConstraint
@Entity(name = "ChusanUserItem")
@Table(name = "chusan_user_item", uniqueConstraints = [UniqueConstraint(columnNames = ["user_id", "item_id", "item_kind"])])
class UserItem(
var itemKind: Int = 0,
var itemId: Int = 0,
var stock: Int = 1,
@JsonProperty("isValid")
var isValid: Boolean = true
) : Chu3UserEntity()

View File

@ -1,35 +0,0 @@
package icu.samnyan.aqua.sega.chusan.model.userdata;
import icu.samnyan.aqua.net.games.BaseEntity;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Entity(name = "ChusanUserLoginBonus")
@Table(name = "chusan_user_login_bonus")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserLoginBonus extends BaseEntity {
private int version;
private int user;
private int presetId;
private int bonusCount;
private LocalDateTime lastUpdateDate;
private boolean isWatched;
private boolean isFinished;
public UserLoginBonus(int version, int user, int presetId) {
this.version = version;
this.user = user;
this.presetId = presetId;
this.bonusCount = 0;
this.lastUpdateDate = LocalDateTime.parse("2018-01-01T00:00:00");
this.isWatched = false;
this.isFinished = false;
}
}

View File

@ -0,0 +1,20 @@
package icu.samnyan.aqua.sega.chusan.model.userdata
import ext.DATE_2018
import icu.samnyan.aqua.net.games.BaseEntity
import jakarta.persistence.Entity
import jakarta.persistence.Table
import java.time.LocalDateTime
@Entity(name = "ChusanUserLoginBonus")
@Table(name = "chusan_user_login_bonus")
class UserLoginBonus(
var version: Int = 0,
// TODO: Fix this (should be User linked)
var user: Int = 0,
var presetId: Int = 0,
var bonusCount: Int = 0,
var lastUpdateDate: LocalDateTime = DATE_2018,
var isWatched: Boolean = false, // TODO: Check if this should be "watched" or "isWatched" in json
var isFinished: Boolean = false,
) : BaseEntity()

View File

@ -1,51 +0,0 @@
package icu.samnyan.aqua.sega.chusan.model.userdata;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Entity(name = "ChusanUserMapArea")
@Table(name = "chusan_user_map_area", uniqueConstraints = {@UniqueConstraint(columnNames = {"user_id", "map_area_id"})})
@Data
@NoArgsConstructor
@AllArgsConstructor
@JsonPropertyOrder({
"mapAreaId",
"position",
"isClear",
"rate",
"statusCount",
"remainGridCount",
"isLocked"
})
public class UserMap extends Chu3UserEntity {
@Column(name = "map_area_id")
private int mapAreaId;
private int position;
@JsonProperty("isClear")
private boolean isClear;
private int rate;
private int statusCount;
private int remainGridCount;
@JsonProperty("isLocked")
private boolean isLocked;
public UserMap(Chu3UserData userData) {
setUser(userData);
}
}

View File

@ -0,0 +1,23 @@
package icu.samnyan.aqua.sega.chusan.model.userdata
import com.fasterxml.jackson.annotation.JsonProperty
import jakarta.persistence.Entity
import jakarta.persistence.Table
import jakarta.persistence.UniqueConstraint
@Entity(name = "ChusanUserMapArea")
@Table(name = "chusan_user_map_area", uniqueConstraints = [UniqueConstraint(columnNames = ["user_id", "map_area_id"])])
class UserMap : Chu3UserEntity() {
var mapAreaId = 0
var position = 0
@JsonProperty("isClear")
var isClear = false
var rate = 0
var statusCount = 0
var remainGridCount = 0
@JsonProperty("isLocked")
var isLocked = false
}

View File

@ -1,79 +0,0 @@
package icu.samnyan.aqua.sega.chusan.model.userdata;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import icu.samnyan.aqua.sega.util.jackson.BooleanToIntegerDeserializer;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Entity(name = "ChusanUserMusicDetail")
@Table(name = "chusan_user_music_detail", uniqueConstraints = {@UniqueConstraint(columnNames = {"user_id", "music_id", "level"})})
@Data
@NoArgsConstructor
@AllArgsConstructor
@JsonPropertyOrder({
"musicId",
"level",
"playCount",
"scoreMax",
"missCount",
"maxComboCount",
"isFullCombo",
"isAllJustice",
"isSuccess",
"fullChain",
"maxChain",
"isLock",
"theoryCount",
"ext1"
})
public class UserMusicDetail extends Chu3UserEntity {
@Column(name = "music_id")
private int musicId;
private int level;
private int playCount;
private int scoreMax;
private int missCount;
private int maxComboCount;
@JsonProperty("isFullCombo")
private boolean isFullCombo;
@JsonProperty("isAllJustice")
private boolean isAllJustice;
@JsonDeserialize(using = BooleanToIntegerDeserializer.class)
@JsonProperty("isSuccess")
private int isSuccess;
private int fullChain;
private int maxChain;
private int scoreRank;
@JsonProperty("isLock")
private boolean isLock;
private int theoryCount;
private int ext1;
public UserMusicDetail(Chu3UserData userData) {
setUser(userData);
}
}

View File

@ -0,0 +1,45 @@
package icu.samnyan.aqua.sega.chusan.model.userdata
import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
import icu.samnyan.aqua.sega.util.jackson.BooleanToIntegerDeserializer
import jakarta.persistence.Entity
import jakarta.persistence.Table
import jakarta.persistence.UniqueConstraint
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Entity(name = "ChusanUserMusicDetail")
@Table(
name = "chusan_user_music_detail",
uniqueConstraints = [UniqueConstraint(columnNames = ["user_id", "music_id", "level"])]
)
class UserMusicDetail : Chu3UserEntity() {
var musicId = 0
var level = 0
var playCount = 0
var scoreMax = 0
var missCount = 0
var maxComboCount = 0
@JsonProperty("isFullCombo")
var isFullCombo = false
@JsonProperty("isAllJustice")
var isAllJustice = false
@JsonDeserialize(using = BooleanToIntegerDeserializer::class)
@JsonProperty("isSuccess")
var isSuccess = 0
var fullChain = 0
var maxChain = 0
var scoreRank = 0
@JsonProperty("isLock")
var isLock = false
var theoryCount = 0
var ext1 = 0
}

View File

@ -1,167 +0,0 @@
package icu.samnyan.aqua.sega.chusan.model.userdata;
import com.fasterxml.jackson.annotation.JsonProperty;
import icu.samnyan.aqua.net.games.IGenericGamePlaylog;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Entity(name = "ChusanUserPlaylog")
@Table(name = "chusan_user_playlog")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserPlaylog extends Chu3UserEntity implements IGenericGamePlaylog {
private String romVersion;
private int orderId;
private int sortNumber;
private int placeId;
private LocalDateTime playDate;
private LocalDateTime userPlayDate;
private int musicId;
private int level;
private int customId;
private long playedUserId1;
private long playedUserId2;
private long playedUserId3;
private String playedUserName1;
private String playedUserName2;
private String playedUserName3;
private int playedMusicLevel1;
private int playedMusicLevel2;
private int playedMusicLevel3;
private int playedCustom1;
private int playedCustom2;
private int playedCustom3;
private int track;
private int score;
@Column(name = "\"rank\"")
private int rank;
private int maxCombo;
private int maxChain;
private int rateTap;
private int rateHold;
private int rateSlide;
private int rateAir;
private int rateFlick;
private int judgeGuilty;
private int judgeAttack;
private int judgeJustice;
private int judgeCritical;
private int judgeHeaven;
private int eventId;
private int playerRating;
@JsonProperty("isNewRecord")
private boolean isNewRecord;
@JsonProperty("isFullCombo")
private boolean isFullCombo;
private int fullChainKind;
@JsonProperty("isAllJustice")
private boolean isAllJustice;
@JsonProperty("isContinue")
private boolean isContinue;
@JsonProperty("isFreeToPlay")
private boolean isFreeToPlay;
private int characterId;
private int charaIllustId;
private int skillId;
private int playKind;
@JsonProperty("isClear")
private boolean isClear;
private int skillLevel;
private int skillEffect;
private String placeName;
private int commonId;
// SUN
private int regionId;
private int machineType;
// SUN PLUS
private int ticketId;
public UserPlaylog(Chu3UserData userData) {
setUser(userData);
}
@Override
public int getAchievement() {
return score;
}
@Override
public int getAfterRating() {
return playerRating;
}
@Override
public int getBeforeRating() {
return playerRating; // TODO: Get before rating
}
@Override
public boolean isAllPerfect() {
return isAllJustice;
}
}

View File

@ -0,0 +1,81 @@
package icu.samnyan.aqua.sega.chusan.model.userdata
import com.fasterxml.jackson.annotation.JsonProperty
import icu.samnyan.aqua.net.games.IGenericGamePlaylog
import jakarta.persistence.Column
import jakarta.persistence.Entity
import jakarta.persistence.Table
import java.time.LocalDateTime
@Entity(name = "ChusanUserPlaylog")
@Table(name = "chusan_user_playlog")
class UserPlaylog : Chu3UserEntity(), IGenericGamePlaylog {
var romVersion: String? = null
var orderId = 0
var sortNumber = 0
var placeId = 0
var playDate: LocalDateTime? = null
override var userPlayDate: LocalDateTime = LocalDateTime.now()
override var musicId: Int = 0
override var level: Int = 0
var customId = 0
var playedUserId1: Long = 0
var playedUserId2: Long = 0
var playedUserId3: Long = 0
var playedUserName1: String? = null
var playedUserName2: String? = null
var playedUserName3: String? = null
var playedMusicLevel1 = 0
var playedMusicLevel2 = 0
var playedMusicLevel3 = 0
var playedCustom1 = 0
var playedCustom2 = 0
var playedCustom3 = 0
var track = 0
var score = 0
@Column(name = "\"rank\"")
var rank = 0
override var maxCombo: Int = 0
var maxChain = 0
var rateTap = 0
var rateHold = 0
var rateSlide = 0
var rateAir = 0
var rateFlick = 0
var judgeGuilty = 0
var judgeAttack = 0
var judgeJustice = 0
var judgeCritical = 0
var judgeHeaven = 0
var eventId = 0
var playerRating = 0
@JsonProperty("isNewRecord")
var isNewRecord = false
@JsonProperty("isFullCombo")
override var isFullCombo: Boolean = false
var fullChainKind = 0
@JsonProperty("isAllJustice")
var isAllJustice = false
@JsonProperty("isContinue")
var isContinue = false
@JsonProperty("isFreeToPlay")
var isFreeToPlay = false
var characterId = 0
var charaIllustId = 0
var skillId = 0
var playKind = 0
@JsonProperty("isClear")
var isClear = false
var skillLevel = 0
var skillEffect = 0
var placeName: String? = null
var commonId = 0
var regionId = 0
var machineType = 0
var ticketId = 0
override val achievement: Int get() = score
override val afterRating: Int get() = playerRating
override val beforeRating: Int get() = playerRating // TODO: Implement this
override val isAllPerfect: Boolean get() = isAllJustice
}

View File

@ -1,22 +0,0 @@
package icu.samnyan.aqua.sega.chusan.service;
import icu.samnyan.aqua.sega.chusan.model.Chu3GameLoginBonusPresetsRepo;
import icu.samnyan.aqua.sega.chusan.model.gamedata.GameLoginBonusPreset;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service("ChusanGameLoginBonusPresetService")
public class GameLoginBonusPresetService {
private final Chu3GameLoginBonusPresetsRepo gameLoginBonusPresetsRepository;
@Autowired
public GameLoginBonusPresetService(Chu3GameLoginBonusPresetsRepo gameLoginBonusPresetsRepository){
this.gameLoginBonusPresetsRepository = gameLoginBonusPresetsRepository;
}
public List<GameLoginBonusPreset> getGameLoginBonusPresets(int version){
return this.gameLoginBonusPresetsRepository.findLoginBonusPresets(version, 1);
}
}

View File

@ -1,28 +0,0 @@
package icu.samnyan.aqua.sega.chusan.service;
import icu.samnyan.aqua.sega.chusan.model.Chu3GameLoginBonusRepo;
import icu.samnyan.aqua.sega.chusan.model.gamedata.GameLoginBonus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
@Service("ChusanGameLoginBonusService")
public class GameLoginBonusService {
private final Chu3GameLoginBonusRepo gameLoginBonusRepository;
@Autowired
public GameLoginBonusService(Chu3GameLoginBonusRepo gameLoginBonusRepository){
this.gameLoginBonusRepository = gameLoginBonusRepository;
}
public List<GameLoginBonus> getAllGameLoginBonus(int presetId){
return this.gameLoginBonusRepository.findGameLoginBonus(1, presetId);
}
public Optional<GameLoginBonus> getGameLoginBonusByDay(int presetId, int day){
return this.gameLoginBonusRepository.findByRequiredDays(1, presetId, day);
}
}

View File

@ -1,28 +0,0 @@
package icu.samnyan.aqua.sega.chusan.service;
import icu.samnyan.aqua.sega.chusan.model.Chu3UserActivityRepo;
import icu.samnyan.aqua.sega.chusan.model.userdata.UserActivity;
import icu.samnyan.aqua.sega.chusan.model.userdata.Chu3UserData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Service("ChusanUserActivityService")
public class UserActivityService {
private final Chu3UserActivityRepo userActivityRepository;
@Autowired
public UserActivityService(Chu3UserActivityRepo userActivityRepository) {
this.userActivityRepository = userActivityRepository;
}
public List<UserActivity> getByUserId(String userId) {
return userActivityRepository.findByUser_Card_ExtId(Long.parseLong(userId));
}
}

View File

@ -1,49 +0,0 @@
package icu.samnyan.aqua.sega.chusan.service;
import icu.samnyan.aqua.sega.chusan.model.Chu3UserCharacterRepo;
import icu.samnyan.aqua.sega.chusan.model.userdata.UserCharacter;
import icu.samnyan.aqua.sega.chusan.model.userdata.Chu3UserData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Service("ChusanUserCharacterService")
public class UserCharacterService {
private final Chu3UserCharacterRepo userCharacterRepository;
@Autowired
public UserCharacterService(Chu3UserCharacterRepo userCharacterRepository) {
this.userCharacterRepository = userCharacterRepository;
}
public UserCharacter save(UserCharacter userCharacter) {
return userCharacterRepository.save(userCharacter);
}
public List<UserCharacter> saveAll(Iterable<UserCharacter> userCharacter) {
return userCharacterRepository.saveAll(userCharacter);
}
public List<UserCharacter> getByUserId(String userId) {
return userCharacterRepository.findByUser_Card_ExtId(Long.parseLong(userId));
}
public Page<UserCharacter> getByUserId(String userId, int pageNumber, int maxCount) {
Pageable pageable = PageRequest.of(pageNumber, maxCount);
return userCharacterRepository.findByUser_Card_ExtId(Long.parseLong(userId), pageable);
}
public Optional<UserCharacter> getByUserAndCharacterId(Chu3UserData user, int characterId) {
return userCharacterRepository.findTopByUserAndCharacterIdOrderByIdDesc(user, characterId);
}
}

View File

@ -1,39 +0,0 @@
package icu.samnyan.aqua.sega.chusan.service;
import icu.samnyan.aqua.sega.chusan.model.Chu3UserChargeRepo;
import icu.samnyan.aqua.sega.chusan.model.userdata.UserCharge;
import icu.samnyan.aqua.sega.chusan.model.userdata.Chu3UserData;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Service("ChusanUserChargeService")
public class UserChargeService {
private final Chu3UserChargeRepo userChargeRepository;
public UserChargeService(Chu3UserChargeRepo userChargeRepository) {
this.userChargeRepository = userChargeRepository;
}
public UserCharge save(UserCharge userCharge) {
return userChargeRepository.save(userCharge);
}
public List<UserCharge> saveAll(List<UserCharge> newUserChargeList) {
return userChargeRepository.saveAll(newUserChargeList);
}
public List<UserCharge> getByUserId(String userId) {
return userChargeRepository.findByUser_Card_ExtId(Long.parseLong(userId));
}
public Optional<UserCharge> getByUserAndChargeId(Chu3UserData user, int chargeId) {
return userChargeRepository.findByUserAndChargeId(user, chargeId);
}
}

Some files were not shown because too many files have changed in this diff Show More