From ebb2e2e4a4e3f11af88d28ec2765f9eaf9d8892a Mon Sep 17 00:00:00 2001 From: Raymond <101374892+raymonable@users.noreply.github.com> Date: Sun, 22 Feb 2026 15:23:21 -0500 Subject: [PATCH] [chu3] X-VERSE & LINKED VERSE (#204) --- .../settings/ChuniMatchingSettings.svelte | 51 ++++++++++++- .../components/settings/ChuniSettings.svelte | 64 ++++++++-------- AquaNet/src/libs/config.ts | 16 ++-- AquaNet/src/libs/i18n/en_ref.ts | 16 +++- config/application.properties | 6 +- .../samnyan/aqua/net/db/AquaGameOptions.kt | 5 ++ .../icu/samnyan/aqua/net/games/chu3/Chusan.kt | 1 + .../aqua/sega/chusan/ChusanVersionHelper.kt | 6 +- .../aqua/sega/chusan/handler/ChusanApis.kt | 74 ++++++++++++++----- .../sega/chusan/handler/ChusanUpsertApis.kt | 10 ++- .../aqua/sega/chusan/model/Chu3GameData.kt | 9 +++ .../aqua/sega/chusan/model/Chu3Repos.kt | 11 ++- .../sega/chusan/model/request/Chu3UserAll.kt | 1 + .../chusan/model/userdata/Chu3UserData.kt | 1 + .../model/userdata/Chu3UserLinkedVerse.kt | 28 +++++++ src/main/resources/db/80/V1000_67__xverse.sql | 2 + .../db/80/V1000_68__linked_verse.sql | 53 +++++++++++++ 17 files changed, 285 insertions(+), 69 deletions(-) create mode 100644 src/main/java/icu/samnyan/aqua/sega/chusan/model/userdata/Chu3UserLinkedVerse.kt create mode 100644 src/main/resources/db/80/V1000_67__xverse.sql create mode 100644 src/main/resources/db/80/V1000_68__linked_verse.sql diff --git a/AquaNet/src/components/settings/ChuniMatchingSettings.svelte b/AquaNet/src/components/settings/ChuniMatchingSettings.svelte index 912b0332..e7916848 100644 --- a/AquaNet/src/components/settings/ChuniMatchingSettings.svelte +++ b/AquaNet/src/components/settings/ChuniMatchingSettings.svelte @@ -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 = {}; let allItems: Record> = {} let submitting: string | undefined | null; + let settings: Record = {}; 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; + })
-

{t("userbox.header.matching")}

-

{t("settings.cabNotice")}

+

{t("userbox.header.matching")}{linkedVerseAvailable ? ` & ${t("userbox.header.linkedVerse")}` : ""}

+
+ {t("settings.cabNotice")} + {#if linkedVerseAvailable} + {t("userbox.lv.diffnotice")} + {/if} +
+ + {#if linkedVerseAvailable} + + {#if settings["chusanLvDifficulty"]} +
+ +
+ + {#if changed.includes(`chusanLvDifficulty`)} + + {/if} +
+
+ {/if} + {/if}
diff --git a/AquaNet/src/components/settings/ChuniSettings.svelte b/AquaNet/src/components/settings/ChuniSettings.svelte index faa4777c..db669105 100644 --- a/AquaNet/src/components/settings/ChuniSettings.svelte +++ b/AquaNet/src/components/settings/ChuniSettings.svelte @@ -31,7 +31,7 @@ // Available (unlocked) options for each kind of item // In allItems: 'namePlate', 'frame', 'trophy', 'mapIcon', 'systemVoice', 'avatarAccessory' let allItems: Record> = {} - 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> = {[Prop in keyof T as (T[Prop] extends number ? Prop : never)]: T[Prop]} let userboxSelected: keyof OnlyNumberPropsOf = "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}
{#each userItems as { iKey, ubKey, items }, i} -
- -
- - {#if changed.includes(ubKey)} - - {/if} + {#if items.length > 0} +
+ +
+ + {#if changed.includes(ubKey)} + + {/if} +
-
+ {/if} {/each}
{:else} @@ -372,21 +374,23 @@
{#each userItems.filter(i => userboxNewOptions.includes(i.iKey)) as { iKey, ubKey, items }, i} -
- -
- - {#if changed.includes(ubKey)} - - {/if} + {#if items.length > 0} +
+ +
+ + {#if changed.includes(ubKey)} + + {/if} +
-
+ {/if} {/each}
{/if} diff --git a/AquaNet/src/libs/config.ts b/AquaNet/src/libs/config.ts index e8cb12e6..df32060b 100644 --- a/AquaNet/src/libs/config.ts +++ b/AquaNet/src/libs/config.ts @@ -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"] - } ] diff --git a/AquaNet/src/libs/i18n/en_ref.ts b/AquaNet/src/libs/i18n/en_ref.ts index fb727efa..e7548aba 100644 --- a/AquaNet/src/libs/i18n/en_ref.ts +++ b/AquaNet/src/libs/i18n/en_ref.ts @@ -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.', } diff --git a/config/application.properties b/config/application.properties index bcda3649..4b23d72e 100644 --- a/config/application.properties +++ b/config/application.properties @@ -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 diff --git a/src/main/java/icu/samnyan/aqua/net/db/AquaGameOptions.kt b/src/main/java/icu/samnyan/aqua/net/db/AquaGameOptions.kt index a81679e4..baba4fc0 100644 --- a/src/main/java/icu/samnyan/aqua/net/db/AquaGameOptions.kt +++ b/src/main/java/icu/samnyan/aqua/net/db/AquaGameOptions.kt @@ -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, diff --git a/src/main/java/icu/samnyan/aqua/net/games/chu3/Chusan.kt b/src/main/java/icu/samnyan/aqua/net/games/chu3/Chusan.kt index 303b5262..8e12503f 100644 --- a/src/main/java/icu/samnyan/aqua/net/games/chu3/Chusan.kt +++ b/src/main/java/icu/samnyan/aqua/net/games/chu3/Chusan.kt @@ -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 }, diff --git a/src/main/java/icu/samnyan/aqua/sega/chusan/ChusanVersionHelper.kt b/src/main/java/icu/samnyan/aqua/sega/chusan/ChusanVersionHelper.kt index dfb93ab7..d851c504 100644 --- a/src/main/java/icu/samnyan/aqua/sega/chusan/ChusanVersionHelper.kt +++ b/src/main/java/icu/samnyan/aqua/sega/chusan/ChusanVersionHelper.kt @@ -13,16 +13,16 @@ class ChusanVersionHelper(val db: Chu3Repos) { val cache: MutableMap = 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 } } \ No newline at end of file diff --git a/src/main/java/icu/samnyan/aqua/sega/chusan/handler/ChusanApis.kt b/src/main/java/icu/samnyan/aqua/sega/chusan/handler/ChusanApis.kt index 88249541..72bb8881 100644 --- a/src/main/java/icu/samnyan/aqua/sega/chusan/handler/ChusanApis.kt +++ b/src/main/java/icu/samnyan/aqua/sega/chusan/handler/ChusanApis.kt @@ -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 { + 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, diff --git a/src/main/java/icu/samnyan/aqua/sega/chusan/handler/ChusanUpsertApis.kt b/src/main/java/icu/samnyan/aqua/sega/chusan/handler/ChusanUpsertApis.kt index 306d9391..30bc7091 100644 --- a/src/main/java/icu/samnyan/aqua/sega/chusan/handler/ChusanUpsertApis.kt +++ b/src/main/java/icu/samnyan/aqua/sega/chusan/handler/ChusanUpsertApis.kt @@ -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 { diff --git a/src/main/java/icu/samnyan/aqua/sega/chusan/model/Chu3GameData.kt b/src/main/java/icu/samnyan/aqua/sega/chusan/model/Chu3GameData.kt index 2397e94e..ba9125c6 100644 --- a/src/main/java/icu/samnyan/aqua/sega/chusan/model/Chu3GameData.kt +++ b/src/main/java/icu/samnyan/aqua/sega/chusan/model/Chu3GameData.kt @@ -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 } \ No newline at end of file diff --git a/src/main/java/icu/samnyan/aqua/sega/chusan/model/Chu3Repos.kt b/src/main/java/icu/samnyan/aqua/sega/chusan/model/Chu3Repos.kt index 40c0cd2f..0c6edc4e 100644 --- a/src/main/java/icu/samnyan/aqua/sega/chusan/model/Chu3Repos.kt +++ b/src/main/java/icu/samnyan/aqua/sega/chusan/model/Chu3Repos.kt @@ -68,6 +68,13 @@ interface Chu3UserCourseRepo : Chu3UserLinked { fun findByUserAndCourseId(user: Chu3UserData, courseId: Int): UserCourse? } +interface Chu3UserLinkedVerseRepo : Chu3UserLinked { + fun findAllByUser(user: Chu3UserData): List + fun findByUserAndLinkedVerseId(user: Chu3UserData, linkedVerseId: Int): Chu3UserLinkedVerse? +} + +interface Chu3GameLinkedVerseRepo : JpaRepository + interface Chu3UserDataRepo : GenericUserDataRepo { 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 ) \ No newline at end of file diff --git a/src/main/java/icu/samnyan/aqua/sega/chusan/model/request/Chu3UserAll.kt b/src/main/java/icu/samnyan/aqua/sega/chusan/model/request/Chu3UserAll.kt index f17c42aa..7046c9d3 100644 --- a/src/main/java/icu/samnyan/aqua/sega/chusan/model/request/Chu3UserAll.kt +++ b/src/main/java/icu/samnyan/aqua/sega/chusan/model/request/Chu3UserAll.kt @@ -64,4 +64,5 @@ class Chu3UserAll( var userCMissionList: List? = null, var userFavoriteMusicList: List? = null, var userUnlockChallengeList: List? = null, + var userLinkedVerseList: List? = null ) diff --git a/src/main/java/icu/samnyan/aqua/sega/chusan/model/userdata/Chu3UserData.kt b/src/main/java/icu/samnyan/aqua/sega/chusan/model/userdata/Chu3UserData.kt index c488a709..31f15a11 100644 --- a/src/main/java/icu/samnyan/aqua/sega/chusan/model/userdata/Chu3UserData.kt +++ b/src/main/java/icu/samnyan/aqua/sega/chusan/model/userdata/Chu3UserData.kt @@ -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 diff --git a/src/main/java/icu/samnyan/aqua/sega/chusan/model/userdata/Chu3UserLinkedVerse.kt b/src/main/java/icu/samnyan/aqua/sega/chusan/model/userdata/Chu3UserLinkedVerse.kt new file mode 100644 index 00000000..fb91c3ac --- /dev/null +++ b/src/main/java/icu/samnyan/aqua/sega/chusan/model/userdata/Chu3UserLinkedVerse.kt @@ -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 = "" +} diff --git a/src/main/resources/db/80/V1000_67__xverse.sql b/src/main/resources/db/80/V1000_67__xverse.sql new file mode 100644 index 00000000..c5877083 --- /dev/null +++ b/src/main/resources/db/80/V1000_67__xverse.sql @@ -0,0 +1,2 @@ +ALTER TABLE chusan_user_data + ADD stage_id INT NOT NULL DEFAULT 0; diff --git a/src/main/resources/db/80/V1000_68__linked_verse.sql b/src/main/resources/db/80/V1000_68__linked_verse.sql new file mode 100644 index 00000000..6b5fe2ba --- /dev/null +++ b/src/main/resources/db/80/V1000_68__linked_verse.sql @@ -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); \ No newline at end of file