mirror of
https://github.com/MewoLab/AquaDX.git
synced 2026-09-27 01:00:37 +03:00
[chu3] X-VERSE & LINKED VERSE (#204)
This commit is contained in:
@@ -3,9 +3,10 @@
|
||||
import { CHU3_MATCHINGS } from "../../libs/config.js";
|
||||
import type { ChusanMatchingOption, GameOption } from "../../libs/generalTypes.js";
|
||||
import { t, ts } from "../../libs/i18n.js";
|
||||
import { DATA, SETTING } from "../../libs/sdk.js";
|
||||
import { DATA, GAME, SETTING, USER } from "../../libs/sdk.js";
|
||||
import StatusOverlays from "../StatusOverlays.svelte";
|
||||
import GameSettingFields from "./GameSettingFields.svelte";
|
||||
import { EN_REF } from "../../libs/i18n/en_ref.js";
|
||||
|
||||
let custom = false
|
||||
let overlay = false
|
||||
@@ -16,6 +17,7 @@
|
||||
let symbols: Record<number, number> = {};
|
||||
let allItems: Record<string, Record<string, { name: string }>> = {}
|
||||
let submitting: string | undefined | null;
|
||||
let settings: Record<string, GameOption> = {};
|
||||
|
||||
let existingUrl = "";
|
||||
SETTING.get().then(s => {
|
||||
@@ -30,6 +32,7 @@
|
||||
if (opt.key.substring(0, symbolKey.length) == symbolKey && opt.value)
|
||||
symbols[parseInt(opt.key.substring(symbolKey.length))] = opt.value;
|
||||
})
|
||||
settings = Object.fromEntries(s.map(v => [v.key, v]))
|
||||
})
|
||||
|
||||
async function fetchSymbolData() {
|
||||
@@ -49,6 +52,14 @@
|
||||
return true
|
||||
}
|
||||
|
||||
async function updateLvDifficulty() {
|
||||
if (submitting) return false
|
||||
submitting = `chusanLvDifficulty`
|
||||
await SETTING.set("chusanLvDifficulty", settings["chusanLvDifficulty"].value).catch(e => error = e.message).finally(() => submitting = null);
|
||||
changed = changed.filter(v => v != `chusanLvDifficulty`)
|
||||
return true
|
||||
}
|
||||
|
||||
// Click on "Custom" option"
|
||||
function clickCustom() {
|
||||
custom = true
|
||||
@@ -66,13 +77,47 @@
|
||||
existingUrl = opt.matching
|
||||
}).catch(e => error = e.message)
|
||||
}
|
||||
|
||||
let linkedVerseAvailable = false;
|
||||
USER.me().then(async me => {
|
||||
let summary = await GAME.userSummary(me.username, "chu3")
|
||||
let version = summary.lastVersion.split(".");
|
||||
if (version[0] == "2" && parseInt(version[1]) >= 40)
|
||||
linkedVerseAvailable = true;
|
||||
})
|
||||
</script>
|
||||
|
||||
<StatusOverlays {error} {loading}/>
|
||||
|
||||
<div class="matching">
|
||||
<h2>{t("userbox.header.matching")}</h2>
|
||||
<p class="notice">{t("settings.cabNotice")}</p>
|
||||
<h2>{t("userbox.header.matching")}{linkedVerseAvailable ? ` & ${t("userbox.header.linkedVerse")}` : ""}</h2>
|
||||
<blockquote class="info">
|
||||
{t("settings.cabNotice")}
|
||||
{#if linkedVerseAvailable}
|
||||
{t("userbox.lv.diffnotice")}
|
||||
{/if}
|
||||
</blockquote>
|
||||
|
||||
{#if linkedVerseAvailable}
|
||||
<GameSettingFields game="chu3-linked-verse" />
|
||||
{#if settings["chusanLvDifficulty"]}
|
||||
<div class="field">
|
||||
<label for={`chusanLvDifficulty`}>{ts(`userbox.lv.difficulty`)}</label>
|
||||
<div>
|
||||
<select bind:value={settings["chusanLvDifficulty"].value} id={`chusanLvDifficulty`} on:change={() => {changed = [...changed, `chusanLvDifficulty`];}}>
|
||||
{#each {length: 5}, i}
|
||||
<option value={i + 1}>{t(`userbox.lv.difficulty.${i + 1}` as keyof typeof EN_REF)}</option>
|
||||
{/each}
|
||||
</select>
|
||||
{#if changed.includes(`chusanLvDifficulty`)}
|
||||
<button transition:slide={{axis: "x"}} disabled={!!submitting} on:click={updateLvDifficulty}>
|
||||
{t("settings.profile.save")}
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<div class="matching-selector">
|
||||
<button on:click={_ => overlay = true}>{t('userbox.matching.select')}</button>
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
// Available (unlocked) options for each kind of item
|
||||
// In allItems: 'namePlate', 'frame', 'trophy', 'mapIcon', 'systemVoice', 'avatarAccessory'
|
||||
let allItems: Record<string, Record<string, { name: string }>> = {}
|
||||
let iKinds = { namePlate: 1, frame: 2, trophy: 3, trophySub1: 4, trophySub2: 5, mapIcon: 8, systemVoice: 9, avatarAccessory: 11 }
|
||||
let iKinds = { namePlate: 1, frame: 2, trophy: 3, trophySub1: 4, trophySub2: 5, mapIcon: 8, systemVoice: 9, avatarAccessory: 11, stage: 13 }
|
||||
// In userbox: 'nameplateId', 'frameId', 'trophyId', 'mapIconId', 'voiceId', 'avatar{Wear/Head/Face/Skin/Item/Front/Back}'
|
||||
let userbox: UserBox
|
||||
let avatarKinds = ['Wear', 'Head', 'Face', 'Skin', 'Item', 'Front', 'Back'] as const
|
||||
@@ -256,7 +256,7 @@
|
||||
|
||||
type OnlyNumberPropsOf<T extends Record<string, any>> = {[Prop in keyof T as (T[Prop] extends number ? Prop : never)]: T[Prop]}
|
||||
let userboxSelected: keyof OnlyNumberPropsOf<UserBox> = "avatarWear";
|
||||
const userboxNewOptions = ["systemVoice", "frame", "trophy", "mapIcon"]
|
||||
const userboxNewOptions = ["systemVoice", "frame", "trophy", "mapIcon", "stage"]
|
||||
|
||||
async function userboxSafeDrop(event: Event & { currentTarget: EventTarget & HTMLInputElement; }) {
|
||||
if (!event.target) return null;
|
||||
@@ -317,21 +317,23 @@
|
||||
{#if !USERBOX_ENABLED.value || !USERBOX_INSTALLED}
|
||||
<div class="fields">
|
||||
{#each userItems as { iKey, ubKey, items }, i}
|
||||
<div class="field">
|
||||
<label for={ubKey}>{ts(`userbox.${ubKey}`)}</label>
|
||||
<div>
|
||||
<select bind:value={userbox[ubKey]} id={ubKey} on:change={() => changed = [...changed, ubKey]}>
|
||||
{#each items as option}
|
||||
<option value={option.itemId}>{allItems[iKey][option.itemId]?.name || `(unknown ${option.itemId})`}</option>
|
||||
{/each}
|
||||
</select>
|
||||
{#if changed.includes(ubKey)}
|
||||
<button transition:slide={{axis: "x"}} on:click={() => submit(ubKey)} disabled={!!submitting}>
|
||||
{t("settings.profile.save")}
|
||||
</button>
|
||||
{/if}
|
||||
{#if items.length > 0}
|
||||
<div class="field">
|
||||
<label for={ubKey}>{ts(`userbox.${ubKey}`)}</label>
|
||||
<div>
|
||||
<select bind:value={userbox[ubKey]} id={ubKey} on:change={() => changed = [...changed, ubKey]}>
|
||||
{#each items as option}
|
||||
<option value={option.itemId}>{allItems[iKey][option.itemId]?.name || `(unknown ${option.itemId})`}</option>
|
||||
{/each}
|
||||
</select>
|
||||
{#if changed.includes(ubKey)}
|
||||
<button transition:slide={{axis: "x"}} on:click={() => submit(ubKey)} disabled={!!submitting}>
|
||||
{t("settings.profile.save")}
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
@@ -372,21 +374,23 @@
|
||||
</div>
|
||||
<div class="fields">
|
||||
{#each userItems.filter(i => userboxNewOptions.includes(i.iKey)) as { iKey, ubKey, items }, i}
|
||||
<div class="field">
|
||||
<label for={ubKey}>{ts(`userbox.${ubKey}`)}</label>
|
||||
<div>
|
||||
<select bind:value={userbox[ubKey]} id={ubKey} on:change={() => changed = [...changed, ubKey]}>
|
||||
{#each items as option}
|
||||
<option value={option.itemId}>{allItems[iKey][option.itemId]?.name || `(unknown ${option.itemId})`}</option>
|
||||
{/each}
|
||||
</select>
|
||||
{#if changed.includes(ubKey)}
|
||||
<button transition:slide={{axis: "x"}} on:click={() => submit(ubKey)} disabled={!!submitting}>
|
||||
{t("settings.profile.save")}
|
||||
</button>
|
||||
{/if}
|
||||
{#if items.length > 0}
|
||||
<div class="field">
|
||||
<label for={ubKey}>{ts(`userbox.${ubKey}`)}</label>
|
||||
<div>
|
||||
<select bind:value={userbox[ubKey]} id={ubKey} on:change={() => changed = [...changed, ubKey]}>
|
||||
{#each items as option}
|
||||
<option value={option.itemId}>{allItems[iKey][option.itemId]?.name || `(unknown ${option.itemId})`}</option>
|
||||
{/each}
|
||||
</select>
|
||||
{#if changed.includes(ubKey)}
|
||||
<button transition:slide={{axis: "x"}} on:click={() => submit(ubKey)} disabled={!!submitting}>
|
||||
{t("settings.profile.save")}
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -29,6 +29,14 @@ export const HAS_USERBOX_ASSETS = true
|
||||
|
||||
// Matching servers
|
||||
export const CHU3_MATCHINGS: ChusanMatchingOption[] = [
|
||||
{
|
||||
name: "Yukiotoko",
|
||||
ui: "https://yukiotoko.metatable.sh/",
|
||||
guide: "https://github.com/MewoLab/AquaDX/blob/v1-dev/docs/chu3-national-matching.md",
|
||||
matching: "http://yukiotoko.chara.lol:9004/",
|
||||
reflector: "http://yukiotoko.chara.lol:50201/",
|
||||
coop: ["Missless", "CozyNet", "GMG"]
|
||||
},
|
||||
{
|
||||
name: "林国对战",
|
||||
ui: "https://chu3-match.sega.ink/rooms",
|
||||
@@ -37,12 +45,4 @@ export const CHU3_MATCHINGS: ChusanMatchingOption[] = [
|
||||
reflector: "http://reflector.naominet.live:18080/",
|
||||
coop: ["RinNET", "MysteriaNET"],
|
||||
},
|
||||
{
|
||||
name: "Yukiotoko",
|
||||
ui: "https://yukiotoko.metatable.sh/",
|
||||
guide: "https://github.com/MewoLab/AquaDX/blob/v1-dev/docs/chu3-national-matching.md",
|
||||
matching: "http://yukiotoko.chara.lol:9004/",
|
||||
reflector: "http://yukiotoko.chara.lol:50201/",
|
||||
coop: ["Missless", "CozyNet", "GMG"]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -194,6 +194,8 @@ export const EN_REF_SETTINGS = {
|
||||
'settings.fields.chusanTeamName.desc': 'Customize the text displayed on the top of your profile.',
|
||||
'settings.fields.chusanInfinitePenguins.name': 'Infinite Penguins',
|
||||
'settings.fields.chusanInfinitePenguins.desc': 'Set penguin statues for character level prompting to 999.',
|
||||
'settings.fields.chusanLvUnlockAll.name': 'Unlock Linked Gates',
|
||||
'settings.fields.chusanLvUnlockAll.desc': 'Incurs a long animated sequence, cannot be undone',
|
||||
'settings.fields.chusanMatchingReflector.name': 'Matching Server Reflector',
|
||||
'settings.fields.chusanMatchingReflector.desc': 'URL of the national matching server\'s UDP reflector.',
|
||||
'settings.fields.chusanMatchingServer.name': 'Matching Server',
|
||||
@@ -224,7 +226,7 @@ export const EN_REF_SETTINGS = {
|
||||
'settings.profile.unchanged': 'Unchanged',
|
||||
'settings.export': 'Export Player Data',
|
||||
'settings.batchManualExport': "Export in Batch Manual (for Tachi)",
|
||||
'settings.cabNotice': "Note: These settings will only affect your own cab/setup. If you're playing on someone else's setup, please contact them to change these settings.",
|
||||
'settings.cabNotice': "These settings only apply for your keychip. If you're playing on someone else's setup, please ask them to change these settings.",
|
||||
'settings.siteNotice': "These settings only apply to the website.",
|
||||
'settings.regionNotice': "These settings are shared amongst Mai, Ongeki and Chuni.",
|
||||
'settings.regionSelector.title': "Prefecture Selector",
|
||||
@@ -236,6 +238,7 @@ export const EN_REF_USERBOX = {
|
||||
'userbox.header.general': 'General Settings',
|
||||
'userbox.header.matching': 'National Matching',
|
||||
'userbox.header.matching.symbolChat': 'Chat Symbols (Matching)',
|
||||
'userbox.header.linkedVerse': 'Linked Verse',
|
||||
'userbox.header.userbox': 'UserBox Settings',
|
||||
'userbox.header.preview': 'UserBox Preview',
|
||||
'userbox.nameplateId': 'Nameplate',
|
||||
@@ -245,6 +248,7 @@ export const EN_REF_USERBOX = {
|
||||
'userbox.trophyIdSub2': 'Trophy Sub #2 (Title)',
|
||||
'userbox.mapIconId': 'Map Icon',
|
||||
'userbox.voiceId': 'System Voice',
|
||||
'userbox.stageId': 'Stage',
|
||||
'userbox.avatarWear': 'Avatar Wear',
|
||||
'userbox.avatarHead': 'Avatar Head',
|
||||
'userbox.avatarFace': 'Avatar Face',
|
||||
@@ -265,6 +269,14 @@ export const EN_REF_USERBOX = {
|
||||
'userbox.matching.symbolChat': 'Message Choice',
|
||||
'userbox.matching.symbolChat.default': 'Default',
|
||||
|
||||
'userbox.lv.diffnotice': 'In Linked Verse, you will be matched separately based on your difficulty.',
|
||||
'userbox.lv.difficulty': 'Linked Verse Difficulty',
|
||||
'userbox.lv.difficulty.1': 'LEVEL V (1000 life, MASTER only)',
|
||||
'userbox.lv.difficulty.2': 'LEVEL IV (3000 life, MASTER only)',
|
||||
'userbox.lv.difficulty.3': 'LEVEL III (5000 life, MASTER only)',
|
||||
'userbox.lv.difficulty.4': 'LEVEL II (5000 life, MASTER & EXPERT only)',
|
||||
'userbox.lv.difficulty.5': 'LEVEL I (5000 life, ALL difficulties)',
|
||||
|
||||
'userbox.new.name': 'AquaBox',
|
||||
'userbox.new.setup': 'Drag and drop your Chuni game folder (Lumi or newer) into the box below to display UserBoxes with their nameplate & avatar. All files are handled in-browser.',
|
||||
'userbox.new.setup.notice': 'Select the highest folder containing your game data.',
|
||||
@@ -286,7 +298,7 @@ export const EN_REF_USERBOX = {
|
||||
|
||||
export const EN_REF_MAI_PHOTO = {
|
||||
'maiphoto.title': 'Mai Memorial Photo Gallery',
|
||||
'maiphoto.url_warning': 'Note: If you want to share a photo with your friend, please save the photo. Do not copy image URL because the URL contains sensitive information.',
|
||||
'maiphoto.url_warning': 'If you want to share a photo with your friend, please save the photo. Do not copy image URL because the URL contains sensitive information.',
|
||||
'maiphoto.none': 'No photo found. You can upload photo by clicking upload at the end of each game session.',
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ allnet.server.hide-port=true
|
||||
allnet.server.place-name=AquaDX
|
||||
## This enables client serial validation during power on request using keychip table in database.
|
||||
## Only enable this if you know what you are doing.
|
||||
allnet.server.check-keychip=false
|
||||
allnet.server.check-keychip=true
|
||||
## Interval between keychip session clean up checks in ms. Default is 1 day.
|
||||
allnet.server.keychip-ses-clean-interval=86400000
|
||||
## Token that haven't been used for this amount of time will be removed from the database. Default is 2 days.
|
||||
@@ -49,10 +49,10 @@ game.chunithm.team-name=
|
||||
## This enables team function if you set team name here. Leave this blank to use default
|
||||
game.chusan.team-name=
|
||||
## This sets the reflector url for global matching
|
||||
game.chusan.reflector-url=http://reflector.naominet.live:18080/
|
||||
game.chusan.reflector-url=http://yukiotoko.chara.lol:50201/
|
||||
## 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/
|
||||
game.chusan.external-matching=http://yukiotoko.chara.lol:9004/
|
||||
## 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
|
||||
|
||||
@@ -48,6 +48,11 @@ class AquaGameOptions(
|
||||
|
||||
@SettingField("chu3-matching")
|
||||
var chusanMatchingReflector: String = "",
|
||||
|
||||
@SettingField("chu3-linked-verse")
|
||||
var chusanLvUnlockAll: Boolean = false,
|
||||
@SettingField("chu3-linked-verse")
|
||||
var chusanLvDifficulty: Int = 1,
|
||||
|
||||
@SettingField("chu3-matching-chat")
|
||||
var chusanSymbolChat1: Int? = null,
|
||||
|
||||
@@ -41,6 +41,7 @@ class Chusan(
|
||||
"trophyIdSub2" to { u, v -> u.trophyIdSub2 = v.int },
|
||||
"mapIconId" to { u, v -> u.mapIconId = v.int },
|
||||
"voiceId" to { u, v -> u.voiceId = v.int },
|
||||
"stageId" to { u, v -> u.stageId = v.int },
|
||||
"characterId" to { u, v -> u.characterId = v.int },
|
||||
"avatarWear" to { u, v -> u.avatarWear = v.int },
|
||||
"avatarHead" to { u, v -> u.avatarHead = v.int },
|
||||
|
||||
@@ -13,16 +13,16 @@ class ChusanVersionHelper(val db: Chu3Repos) {
|
||||
val cache: MutableMap<String, String> = mutableMapOf()
|
||||
|
||||
// Obtain the cabinet's most recent version
|
||||
operator fun get(clientId: String): String {
|
||||
fun get(clientId: String): String? {
|
||||
// Try to find the version in the cache
|
||||
cache[clientId]?.let { return it }
|
||||
|
||||
// Not found, check the most recent user
|
||||
return db.userData.findTopByLastClientIdOrderByLastPlayDateDesc(clientId)?.lastDataVersion
|
||||
?.also { cache[clientId] = it } ?: "2.25.13".also { log.warn("No version found for $clientId") }
|
||||
?.also { cache[clientId] = it }
|
||||
}
|
||||
|
||||
operator fun set(clientId: String, version: String) {
|
||||
fun set(clientId: String, version: String) {
|
||||
cache[clientId] = version
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import icu.samnyan.aqua.sega.chusan.model.userdata.Chu3UserItem
|
||||
import icu.samnyan.aqua.sega.chusan.model.userdata.UserMusicDetail
|
||||
import icu.samnyan.aqua.sega.general.model.CardStatus
|
||||
import icu.samnyan.aqua.sega.general.model.UserRecentRating
|
||||
import org.springframework.data.repository.findByIdOrNull
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
@@ -28,13 +29,19 @@ fun ChusanController.chusanInit() {
|
||||
mapOf("type" to type, "length" to 0, "gameRankingList" to lst)
|
||||
}
|
||||
|
||||
// VERSE VERSE VERSE VERSE VERSE VERSE VERSE VERSE VERSE VERSE VERSE VERSE VERSE VERSE VERSE VERSE VERSE VERSE
|
||||
"GetGameCourseLevel" {
|
||||
// gameCourseLevelList: [{courseId: int, startDate: date, endDate: date}]
|
||||
mapOf("length" to 0, "gameCourseLevelList" to listOf(
|
||||
val opts = TokenChecker.getCurrentSession()?.user?.gameOptions
|
||||
val lst = listOf(
|
||||
// Unlock Challenge
|
||||
mapOf("courseId" to 300004, "startDate" to "2019-01-01 00:00:00", "endDate" to "2077-01-01 11:45:14"),
|
||||
mapOf("courseId" to 300009, "startDate" to "2019-01-01 00:00:00", "endDate" to "2077-01-01 11:45:14")
|
||||
))
|
||||
mapOf("courseId" to 300009, "startDate" to "2019-01-01 00:00:00", "endDate" to "2077-01-01 11:45:14"),
|
||||
) + (0..9).toList().map {
|
||||
// Linked Verse
|
||||
mapOf("courseId" to 500000 + (opts?.chusanLvDifficulty ?: 5) + (it * 100), "startDate" to "2019-01-01 00:00:00", "endDate" to "2077-01-01 11:45:14")
|
||||
}
|
||||
|
||||
mapOf("length" to lst.size, "gameCourseLevelList" to lst)
|
||||
}
|
||||
|
||||
"GetGameUCCondition" {
|
||||
@@ -55,6 +62,40 @@ fun ChusanController.chusanInit() {
|
||||
db.userChallenge.findByUser_Card_ExtId(uid)
|
||||
}
|
||||
|
||||
// The implementation here isn't preferable, but it's functional
|
||||
// Condition checks if the user beat a song from a previous stage and unlocks it if so
|
||||
fun getLinkedVerseCampaign(): List<Any> {
|
||||
val opts = TokenChecker.getCurrentSession()?.user?.gameOptions
|
||||
// No other conditions appear to work properly, so the request for the gates is pretty large. Sorry
|
||||
fun getChartConditions(musicId: Int) =
|
||||
if (opts != null && opts.chusanLvUnlockAll) {
|
||||
listOf( mapOf("type" to 3, "conditionId" to 0, "logicalOpe" to 1, "startDate" to "2024-03-08 01:00:00", "endDate" to "2099-12-31 00:00:00") )
|
||||
} else {
|
||||
(0..5).toList().map {
|
||||
mapOf("type" to 26, "conditionId" to (musicId * 100) + it, "logicalOpe" to 2, "startDate" to "2024-03-08 01:00:00", "endDate" to "2099-12-31 00:00:00") }}
|
||||
|
||||
return db.gameLinkedVerse.findAll().map {
|
||||
val lst = getChartConditions(it.musicId)
|
||||
mapOf("linkedVerseId" to it.id + 1, "length" to lst.size, "conditionList" to lst)
|
||||
} + // ORIGIN is always left unlocked by default
|
||||
listOf(mapOf("linkedVerseId" to 10001, "length" to 1, "conditionList" to listOf(
|
||||
mapOf("type" to 3, "conditionId" to 0, "logicalOpe" to 1, "startDate" to "2024-03-08 01:00:00", "endDate" to "2099-12-31 00:00:00")
|
||||
)))
|
||||
}
|
||||
"GetGameLVConditionOpen" {
|
||||
val lst = getLinkedVerseCampaign()
|
||||
mapOf("length" to lst.size, "gameLinkedVerseConditionOpenList" to lst)
|
||||
}
|
||||
"GetGameLVConditionUnlock" {
|
||||
val lst = getLinkedVerseCampaign()
|
||||
mapOf("length" to lst.size, "gameLinkedVerseConditionUnlockList" to lst)
|
||||
}
|
||||
|
||||
"GetUserLV" {
|
||||
val lst = db.userLinkedVerse.findByUser_Card_ExtId(uid)
|
||||
mapOf("length" to lst.size, "userLinkedVerseList" to lst, "userId" to uid)
|
||||
}
|
||||
|
||||
"GetUserRecMusic".paged("userRecMusicList") {
|
||||
// musicId: int, recMusicList: string
|
||||
// musicId cannot be the same with the id in recMusicList
|
||||
@@ -66,14 +107,9 @@ fun ChusanController.chusanInit() {
|
||||
}
|
||||
|
||||
"GetUserRecRating".paged("userRecRatingList") {
|
||||
// ratingMin: int, ratingMax: int, recMusicList: string
|
||||
// This doesn't work
|
||||
// listOf(
|
||||
// mapOf("ratingMin" to 0, "ratingMax" to 30, "recMusicList" to "2387,1;2658,1")
|
||||
// )
|
||||
// Unimplemented for now
|
||||
empty
|
||||
}
|
||||
// VERSE VERSE VERSE VERSE VERSE VERSE VERSE VERSE VERSE VERSE VERSE VERSE VERSE VERSE VERSE VERSE VERSE VERSE
|
||||
|
||||
// Stub handlers
|
||||
"GetGameIdlist" { """{"type":"${data["type"]}","length":"0","gameIdlistList":[]}""" }
|
||||
@@ -277,12 +313,16 @@ fun ChusanController.chusanInit() {
|
||||
"GetUserTeam" {
|
||||
val playDate = parsing { data["playDate"] as String }
|
||||
val team = db.userData.findByCard_ExtId(uid)?.card?.aquaUser?.gameOptions?.chusanTeamName?.some
|
||||
?: props.teamName?.some ?: "一緒に歌おう!"
|
||||
?: props.teamName?.some
|
||||
|
||||
mapOf(
|
||||
"userId" to uid, "teamId" to 1, "teamRank" to 1, "teamName" to team,
|
||||
"userTeamPoint" to mapOf("userId" to uid, "teamId" to 1, "orderId" to 1, "teamPoint" to 1, "aggrDate" to playDate)
|
||||
)
|
||||
if (team.isNullOrEmpty())
|
||||
mapOf("userId" to uid, "teamId" to 0)
|
||||
else
|
||||
// TODO: true team implementation
|
||||
mapOf(
|
||||
"userId" to uid, "teamId" to 1, "teamRank" to 1, "teamName" to team,
|
||||
"userTeamPoint" to mapOf("userId" to uid, "teamId" to 1, "orderId" to 1, "teamPoint" to 1, "aggrDate" to playDate)
|
||||
)
|
||||
}
|
||||
|
||||
"GetUserRegion" {
|
||||
@@ -313,7 +353,7 @@ fun ChusanController.chusanInit() {
|
||||
mapOf(
|
||||
"gameSetting" to mapOf(
|
||||
"romVersion" to "$version.00",
|
||||
"dataVersion" to versionHelper[data["clientId"].toString()],
|
||||
"dataVersion" to (versionHelper.get(data["clientId"].toString()) ?: "$version.01"),
|
||||
"isMaintenance" to false,
|
||||
"requestInterval" to 0,
|
||||
"rebootStartTime" to now.minusHours(4).format(fmt),
|
||||
@@ -322,8 +362,6 @@ fun ChusanController.chusanInit() {
|
||||
"maxCountCharacter" to 300,
|
||||
"maxCountItem" to 300,
|
||||
"maxCountMusic" to 300,
|
||||
// "matchStartTime" to now.minusHours(1).format(fmt),
|
||||
// "matchEndTime" to now.plusHours(7).format(fmt),
|
||||
"matchStartTime" to now.withHour(0).withMinute(1).withSecond(0).format(fmt),
|
||||
"matchEndTime" to now.withHour(23).withMinute(59).withSecond(0).format(fmt),
|
||||
"matchTimeLimit" to 10,
|
||||
|
||||
@@ -6,6 +6,7 @@ import icu.samnyan.aqua.sega.chusan.model.request.Chu3UserAll
|
||||
import icu.samnyan.aqua.sega.chusan.model.userdata.*
|
||||
import icu.samnyan.aqua.sega.general.model.CardStatus
|
||||
import icu.samnyan.aqua.sega.general.model.UserRecentRating
|
||||
import kotlinx.serialization.encodeToString
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun ChusanController.upsertApiInit() {
|
||||
@@ -55,7 +56,7 @@ fun ChusanController.upsertApiInit() {
|
||||
db.userRegions.save(region)
|
||||
}
|
||||
|
||||
versionHelper[u.lastClientId] = u.lastDataVersion
|
||||
versionHelper.set(u.lastClientId, u.lastDataVersion)
|
||||
|
||||
// Set users
|
||||
listOfNotNull(
|
||||
@@ -159,6 +160,13 @@ fun ChusanController.upsertApiInit() {
|
||||
db.userChallenge.saveAll(list.distinctBy { it.unlockChallengeId }.mapApply {
|
||||
id = db.userChallenge.findByUserAndUnlockChallengeId(u, unlockChallengeId)?.id ?: 0 }) }
|
||||
|
||||
userLinkedVerseList?.let { list ->
|
||||
db.userLinkedVerse.saveAll(list.map{
|
||||
it.apply{ it.user = u }
|
||||
}.distinctBy { it.linkedVerseId }.mapApply {
|
||||
id = db.userLinkedVerse.findByUserAndLinkedVerseId(u, linkedVerseId)?.id ?: 0 })
|
||||
}
|
||||
|
||||
// Need testing
|
||||
// userLoginBonusList?.let { list ->
|
||||
// db.userLoginBonus.saveAll(list.distinctBy { it["presetId"] as String }.map {
|
||||
|
||||
@@ -91,4 +91,13 @@ class GameLoginBonusPreset : IdExposedEntity() {
|
||||
var version = 0
|
||||
var presetName: String? = null
|
||||
var isEnabled = false
|
||||
}
|
||||
|
||||
@Entity(name = "ChusanGameLinkedVerse")
|
||||
@Table(name = "chusan_game_linked_verse")
|
||||
class GameLinkedVerse: IdExposedEntity() {
|
||||
var musicId = 0
|
||||
var name: String? = null
|
||||
var startDate: LocalDateTime? = null
|
||||
var endDate: LocalDateTime? = null
|
||||
}
|
||||
@@ -68,6 +68,13 @@ interface Chu3UserCourseRepo : Chu3UserLinked<UserCourse> {
|
||||
fun findByUserAndCourseId(user: Chu3UserData, courseId: Int): UserCourse?
|
||||
}
|
||||
|
||||
interface Chu3UserLinkedVerseRepo : Chu3UserLinked<Chu3UserLinkedVerse> {
|
||||
fun findAllByUser(user: Chu3UserData): List<Chu3UserLinkedVerse?>
|
||||
fun findByUserAndLinkedVerseId(user: Chu3UserData, linkedVerseId: Int): Chu3UserLinkedVerse?
|
||||
}
|
||||
|
||||
interface Chu3GameLinkedVerseRepo : JpaRepository<GameLinkedVerse, Int>
|
||||
|
||||
interface Chu3UserDataRepo : GenericUserDataRepo<Chu3UserData> {
|
||||
fun findTopByLastClientIdOrderByLastPlayDateDesc(lastClientId: String): Chu3UserData?
|
||||
}
|
||||
@@ -201,10 +208,12 @@ class Chu3Repos(
|
||||
val netBattleLog: Chu3NetBattleLogRepo,
|
||||
val userMisc: Chu3UserMiscRepo,
|
||||
val userChallenge: Chu3UserChallengeRepo,
|
||||
val userLinkedVerse: Chu3UserLinkedVerseRepo,
|
||||
val gameCharge: Chu3GameChargeRepo,
|
||||
val gameEvent: Chu3GameEventRepo,
|
||||
val gameGachaCard: Chu3GameGachaCardRepo,
|
||||
val gameGacha: Chu3GameGachaRepo,
|
||||
val gameLoginBonusPresets: Chu3GameLoginBonusPresetsRepo,
|
||||
val gameLoginBonus: Chu3GameLoginBonusRepo
|
||||
val gameLoginBonus: Chu3GameLoginBonusRepo,
|
||||
val gameLinkedVerse: Chu3GameLinkedVerseRepo
|
||||
)
|
||||
@@ -64,4 +64,5 @@ class Chu3UserAll(
|
||||
var userCMissionList: List<UserCMissionResp>? = null,
|
||||
var userFavoriteMusicList: List<FavNewMusic>? = null,
|
||||
var userUnlockChallengeList: List<Chu3UserChallenge>? = null,
|
||||
var userLinkedVerseList: List<Chu3UserLinkedVerse>? = null
|
||||
)
|
||||
|
||||
@@ -116,6 +116,7 @@ class Chu3UserData : BaseEntity(), IUserData {
|
||||
var netBattleConsecutiveWinCount = 0
|
||||
var charaIllustId = 0
|
||||
var skillId = 0
|
||||
var stageId = 0
|
||||
var overPowerPoint = 0
|
||||
var overPowerRate = 0
|
||||
var overPowerLowerRank = 0
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
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 = "ChusanUserLinkedVerse")
|
||||
@Table(name = "chusan_user_linked_verse", uniqueConstraints = [UniqueConstraint(columnNames = ["user_id", "linked_verse_id"])])
|
||||
class Chu3UserLinkedVerse : Chu3UserEntity() {
|
||||
var linkedVerseId = 0
|
||||
var clearCourseId = 0
|
||||
var clearCourseLevel = 0
|
||||
var clearDate: LocalDateTime = LocalDateTime.now()
|
||||
var clearUserId1: Long = 0
|
||||
var clearUserId2: Long = 0
|
||||
var clearUserId3: Long = 0
|
||||
var clearUserName0 = ""
|
||||
var clearUserName1 = ""
|
||||
var clearUserName2 = ""
|
||||
var clearUserName3 = ""
|
||||
var isFirstClear = false
|
||||
var numClear = 0
|
||||
var statusOpen = 0
|
||||
var statusUnlock = 0
|
||||
var progress: String = ""
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE chusan_user_data
|
||||
ADD stage_id INT NOT NULL DEFAULT 0;
|
||||
@@ -0,0 +1,53 @@
|
||||
ALTER TABLE aqua_game_options
|
||||
ADD chusan_lv_difficulty INT NOT NULL DEFAULT 5;
|
||||
ALTER TABLE aqua_game_options
|
||||
ADD chusan_lv_unlock_all BOOLEAN DEFAULT FALSE;
|
||||
|
||||
CREATE TABLE chusan_game_linked_verse (
|
||||
id BIGINT auto_increment PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
start_date DATETIME NOT NULL,
|
||||
end_date DATETIME NOT NULL,
|
||||
music_id INTEGER NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE chusan_user_linked_verse
|
||||
(
|
||||
id BIGINT AUTO_INCREMENT NOT NULL PRIMARY KEY,
|
||||
progress VARCHAR(64),
|
||||
user_id BIGINT NOT NULL,
|
||||
linked_verse_id INTEGER NOT NULL,
|
||||
clear_course_id INTEGER NOT NULL,
|
||||
clear_course_level INTEGER NOT NULL,
|
||||
clear_date DATETIME NOT NULL,
|
||||
clear_user_id1 BIGINT NOT NULL,
|
||||
clear_user_id2 BIGINT NOT NULL,
|
||||
clear_user_id3 BIGINT NOT NULL,
|
||||
clear_user_name0 VARCHAR(8) NOT NULL,
|
||||
clear_user_name1 VARCHAR(8) NOT NULL,
|
||||
clear_user_name2 VARCHAR(8) NOT NULL,
|
||||
clear_user_name3 VARCHAR(8) NOT NULL,
|
||||
is_first_clear BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
num_clear INTEGER NOT NULL,
|
||||
status_open INTEGER NOT NULL,
|
||||
status_unlock INTEGER NOT NULL,
|
||||
CONSTRAINT fku_chusan_user_linked_verse FOREIGN KEY (user_id) REFERENCES chusan_user_data (id) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
CONSTRAINT unique_user_linked_verse UNIQUE (user_id, linked_verse_id)
|
||||
);
|
||||
|
||||
ALTER TABLE chusan_user_playlog
|
||||
MODIFY COLUMN played_user_id1 BIGINT;
|
||||
ALTER TABLE chusan_user_playlog
|
||||
MODIFY COLUMN played_user_id2 BIGINT;
|
||||
ALTER TABLE chusan_user_playlog
|
||||
MODIFY COLUMN played_user_id3 BIGINT;
|
||||
|
||||
INSERT INTO chusan_game_linked_verse (id, name, start_date, end_date, music_id)
|
||||
VALUES
|
||||
(10001, 'Linked GATE ORIGIN', '2019-01-01 00:00:00.000000', '2029-01-01 00:00:00.000000',2838),
|
||||
(10002, 'Linked GATE AIR', '2019-01-01 00:00:00.000000', '2029-01-01 00:00:00.000000',2846),
|
||||
(10003, 'Linked GATE STAR', '2019-01-01 00:00:00.000000', '2029-01-01 00:00:00.000000',2858),
|
||||
(10004, 'Linked GATE AMAZON', '2019-01-01 00:00:00.000000', '2029-01-01 00:00:00.000000',2869),
|
||||
(10005, 'Linked GATE CRYSTAL', '2019-01-01 00:00:00.000000', '2029-01-01 00:00:00.000000',2880),
|
||||
(10006, 'Linked GATE PARADISE', '2019-01-01 00:00:00.000000', '2029-01-01 00:00:00.000000',2891),
|
||||
(10007, 'Linked GATE NEW', '2019-01-01 00:00:00.000000', '2029-01-01 00:00:00.000000',2919);
|
||||
Reference in New Issue
Block a user