[chu3] X-VERSE & LINKED VERSE (#204)

This commit is contained in:
Raymond
2026-02-22 15:23:21 -05:00
committed by GitHub
parent e755bc9380
commit ebb2e2e4a4
17 changed files with 285 additions and 69 deletions
@@ -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);