[+] Add support for geki cm and fix chusan cm implementation (#175)

This commit is contained in:
alexay7
2025-09-30 07:47:31 +02:00
committed by GitHub
parent b0a49d6626
commit 4971f2be78
11 changed files with 1351 additions and 3 deletions

View File

@@ -35,7 +35,7 @@ class ChusanController(
val chusan: Chusan
): MeowApi({ api, resp ->
if (resp is String) resp
else (if ("CM" in api) cmMapper else mapper).write(resp)
else (if ("CM" in api || api == "GetUserItemApi" ) cmMapper else mapper).write(resp)
}) {
val log = LoggerFactory.getLogger(ChusanController::class.java)

View File

@@ -54,10 +54,31 @@ fun ChusanController.cmApiInit() {
db.userGacha.save(it)
}
//Set the hasCompleted to true
val printState = db.userCardPrintState.findByUserAndGachaIdAndHasCompleted(u, gachaId, false)
if (printState.isEmpty()) return@api null
printState.forEach { it.hasCompleted = true }
db.userCardPrintState.saveAll(printState)
// Append the order ID to the response with the key "orderId"
val fullPrintState = printState.map {
mapOf(
"orderId" to it.id,
"hasCompleted" to it.hasCompleted,
"limitDate" to it.limitDate.toString(),
"placeId" to it.placeId,
"cardId" to it.cardId,
"gachaId" to it.gachaId,
"userId" to uid
)
}
mapOf(
"returnCode" to 1,
"apiName" to "CMUpsertUserGachaApi",
"userCardPrintStateList" to db.userCardPrintState.findByUserAndGachaIdAndHasCompleted(u, gachaId, false)
"userCardPrintStateList" to fullPrintState
)
}

View File

@@ -3,6 +3,10 @@ package icu.samnyan.aqua.sega.chusan.model.userdata
import com.fasterxml.jackson.annotation.JsonIgnore
import com.fasterxml.jackson.annotation.JsonInclude
import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.JsonDeserializer
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
import com.fasterxml.jackson.databind.annotation.JsonSerialize
import icu.samnyan.aqua.net.games.BaseEntity
import icu.samnyan.aqua.net.games.IUserData
@@ -10,8 +14,22 @@ 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.*
import kotlinx.io.IOException
import lombok.NoArgsConstructor
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
class FlexibleDateTimeDeserializer : JsonDeserializer<LocalDateTime?>() {
@Throws(IOException::class)
public override fun deserialize(p: JsonParser, ctxt: DeserializationContext?): LocalDateTime {
return LocalDateTime.parse(p.getText(), FORMATTER)
}
companion object {
// Card Maker needs the date ending with ".0" and chunithm sends the dates without it so we need a flexible parser
private val FORMATTER: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss[.S]")
}
}
@Entity(name = "ChusanUserData")
@Table(name = "chusan_user_data")
@@ -50,12 +68,14 @@ class Chu3UserData : BaseEntity(), IUserData {
var totalExpertHighScore: Long = 0
var totalMasterHighScore: Long = 0
var totalUltimaHighScore: Long = 0
@JsonDeserialize(using = FlexibleDateTimeDeserializer::class)
var eventWatchedDate: LocalDateTime = LocalDateTime.now()
var friendCount = 0
var firstGameId: String = ""
var firstRomVersion: String = ""
var firstDataVersion: String = ""
@JsonDeserialize(using = FlexibleDateTimeDeserializer::class)
override var firstPlayDate: LocalDateTime = LocalDateTime.now()
var lastGameId: String = ""
@@ -65,6 +85,7 @@ class Chu3UserData : BaseEntity(), IUserData {
@JsonIgnore
var lastLoginDate: LocalDateTime = LocalDateTime.now()
@JsonDeserialize(using = FlexibleDateTimeDeserializer::class)
override var lastPlayDate: LocalDateTime = LocalDateTime.now()
var lastPlaceId = 0
var lastPlaceName: String = ""

View File

@@ -1,5 +1,6 @@
package icu.samnyan.aqua.sega.chusan.model.userdata
import com.fasterxml.jackson.annotation.JsonIgnore
import jakarta.persistence.Entity
import jakarta.persistence.Table
import java.time.LocalDateTime
@@ -8,6 +9,8 @@ import java.time.LocalDateTime
@Table(name = "chusan_user_print_state")
class UserCardPrintState(
var hasCompleted: Boolean = false,
@JsonIgnore
var limitDate: LocalDateTime = LocalDateTime.now(),
var placeId: Int = 0,
var cardId: Int = 0,

View File

@@ -9,6 +9,7 @@ fun OngekiController.ongekiInit() {
fun <T> List<T>.staticLst(key: String) = mapOf("length" to size, key to this)
initUser()
cmApiInit()
initUpsertAll()
// Has type, but type is always 1

View File

@@ -0,0 +1,345 @@
package icu.samnyan.aqua.sega.ongeki
import ext.asDateTime
import ext.empty
import ext.int
import ext.invoke
import ext.long
import ext.mapApply
import ext.minus
import ext.parsing
import icu.samnyan.aqua.sega.ongeki.model.*
import java.time.LocalDateTime
fun OngekiController.cmApiInit() {
"CMGetUserData" {
val user = db.data.findByCard_ExtId(uid)() ?: (400 - "User not found")
mapOf("userId" to uid, "userData" to user)
}
"PrinterLogin" { mapOf("returnCode" to 1) }
"PrinterLogout" { mapOf("returnCode" to 1) }
"CMGetUserCard".unpaged("userCardList"){
db.card.findByUser_Card_ExtId(uid).map { it.apply { printCount = 99 } }
}
"CMGetUserCharacter".unpaged("userCharacterList") { db.character.findByUser_Card_ExtId(uid)}
"CMGetUserItem" {
val kind = (parsing { data["nextIndex"]!!.long } / 10000000000L).toInt()
var dat = db.item.findByUser_Card_ExtIdAndItemKind(uid, kind)
// Check if user have infinite kaika
if (kind == OgkItemType.KaikaItem.ordinal) {
val u = db.data.findByCard_ExtId(uid)()
u?.card?.aquaUser?.gameOptions?.let {
if (it.ongekiInfiniteKaika) {
dat = listOf(UserItem().apply {
user = u
itemKind = OgkItemType.KaikaItem.ordinal
itemId = 1
stock = 999
})
}
}
}
mapOf("userId" to uid, "length" to dat.size, "nextIndex" to -1, "itemKind" to kind, "userItemList" to dat)
}
"GetGameGacha" {
gdb.gacha.findAll().let {
mapOf("length" to it.size, "gameGachaList" to it, "registIdList" to emptyList<Any>())
}
}
"GetGameGachaCardById" {
val gachaId = parsing { data["gachaId"]!!.long }
gdb.gachaCard.findAllByGachaId(gachaId).let {
mapOf("gachaId" to gachaId, "length" to it.size, "isPickup" to false, "gameGachaCardList" to it,
"emissionList" to empty, "afterCalcList" to empty
)
}
}
"GetUserGacha" {
db.gacha.findByUser_Card_ExtId(uid).let{
mapOf("userId" to uid, "length" to it.size, "nextIndex" to 0, "userGachaList" to it)
}
}
"RollGacha" {
val (gachaId, tmpTimes) = parsing { data["gachaId"]!!.long to data["times"]!!.int }
var times = tmpTimes
val user = db.data.findByCard_ExtId(uid)() ?: (400 - "User not found")
val foundGacha = gdb.gacha.findById(gachaId)() ?: (404 - "Gacha not found")
val foundUserGacha = db.gacha.findByUserAndGachaId(user, gachaId)
// Official gacha percentages: R 76% SR 21% SSR 3%
val probabilities = listOf(76, 21, 3)
var rarityResults: List<Int> = emptyList()
// If this is the first x5 pull or any x11 pull, an SR is guaranteed
if(foundUserGacha != null && ((times == 5 && foundUserGacha.fiveGachaCnt==0) || times==11)){
rarityResults = listOf(3)
times -= 1
}
rarityResults = rarityResults + List(times) {
val rand = (1..100).random()
when {
rand <= probabilities[0] -> 2
rand <= probabilities[0] + probabilities[1] -> 3
else -> 4
}
}
val pulledCards = if(foundGacha.kind==0 || foundGacha.kind==3) {
// If it is the free or the permanent gacha, include cards from the gacha itself
gdb.gachaCard.findAllByGachaId(gachaId).groupBy { it.rarity }
} else {
// If it is a normal gacha, include all cards from the permanent gacha
gdb.gachaCard.findAllByGachaIdAndPermanent(gachaId).groupBy { it.rarity }
}
val finalPulls = rarityResults.map { rarity -> pulledCards[rarity]?.random()}
mapOf("length" to finalPulls.size, "gameGachaCardList" to finalPulls)
}
"CMUpsertUserGacha" api@ {
val all: OngekiCMUpsertUserGacha = mapper.convert(data["cmUpsertUserGacha"]!!)
val gacha = parsing { data["gachaId"]!!.long }
val pullCount = parsing { data["gachaCnt"]!!.int }
val earnedSelectPoints = parsing { data["selectPoint"]!!.int }
// User data
val oldUser = db.data.findByCard_ExtId(uid)()
val u: UserData = all.userData?.get(0)?.apply {
id = oldUser?.id ?: 0
card = oldUser?.card ?: us.cardRepo.findByExtId(uid)() ?: (404 - "Card not found")
// Set eventWatchedDate with lastPlayDate, because client doesn't update it
cmEventWatchedDate = oldUser?.lastPlayDate ?: ""
db.data.save(this)
} ?: oldUser ?: return@api null
db.gacha.findByUserAndGachaId(u, gacha)?.apply {
totalGachaCnt += pullCount
// If the user pulled a select gacha increase the stats
if (earnedSelectPoints > 0) {
selectPoint += earnedSelectPoints
ceilingGachaCnt += pullCount
}
// Stats related with the group of pulls the user makes
if (pullCount == 5) {
fiveGachaCnt += 1
}else if (pullCount == 11) {
elevenGachaCnt+=1
}
// Update the daily gacha
val parsedDailyGachaDate = dailyGachaDate.asDateTime()!!
if (
parsedDailyGachaDate.dayOfMonth != LocalDateTime.now().dayOfMonth ||
parsedDailyGachaDate.monthValue != LocalDateTime.now().monthValue ||
parsedDailyGachaDate.year != LocalDateTime.now().year
) {
dailyGachaCnt += pullCount
dailyGachaDate = LocalDateTime.now().toString()
}else{
dailyGachaCnt += pullCount
}
}?: UserGacha().apply {
user = u
gachaId = gacha
totalGachaCnt = pullCount
selectPoint = earnedSelectPoints
ceilingGachaCnt = if (earnedSelectPoints > 0) 1 else 0
fiveGachaCnt = if (pullCount == 5) 1 else 0
elevenGachaCnt = if (pullCount == 11) 1 else 0
dailyGachaCnt = pullCount
db.gacha.save(this)
}
all.run {
// Set users
listOfNotNull(
userCharacterList, userCardList, userItemList
).flatten().forEach { it.user = u }
// UserCharacterList
userCharacterList?.let { list ->
db.character.saveAll(list.distinctBy { it.characterId }.mapApply {
id = db.character.findByUserAndCharacterId(u, characterId)()?.id ?: 0
})
}
// UserCardList
userCardList?.let { list ->
db.card.saveAll(list.distinctBy { it.cardId }.mapApply {
id = db.card.findByUserAndCardId(u, cardId)()?.id ?: 0
})
}
// UserItemList
userItemList?.let { list ->
db.item.saveAll(list.distinctBy { it.itemKind to it.itemId }.mapApply {
id = db.item.findByUserAndItemKindAndItemId(u, itemKind, itemId)()?.id ?: 0
})
}
}
null
}
"CMUpsertUserAll" api@{
val all: OngekiCMUpsertAll = mapper.convert(data["cmUpsertUserAll"]!!)
// User data
val oldUser = db.data.findByCard_ExtId(uid)()
val u: UserData = all.userData?.get(0)?.apply {
id = oldUser?.id ?: 0
card = oldUser?.card ?: us.cardRepo.findByExtId(uid)() ?: (404 - "Card not found")
// Set eventWatchedDate with lastPlayDate, because client doesn't update it
cmEventWatchedDate = oldUser?.lastPlayDate ?: ""
db.data.save(this)
} ?: oldUser ?: return@api null
all.run {
// Set users
listOfNotNull(
userActivityList, userCharacterList, userCardList, userItemList
).flatten().forEach { it.user = u }
// UserActivityList
userActivityList?.let { list ->
db.activity.saveAll(list.distinctBy { it.activityId to it.kind }.mapApply {
id = db.activity.findByUserAndKindAndActivityId(u, kind, activityId)()?.id ?: 0
})
}
// UserCharacterList
userCharacterList?.let { list ->
db.character.saveAll(list.distinctBy { it.characterId }.mapApply {
id = db.character.findByUserAndCharacterId(u, characterId)()?.id ?: 0
})
}
// UserCardList
userCardList?.let { list ->
db.card.saveAll(list.distinctBy { it.cardId }.mapApply {
id = db.card.findByUserAndCardId(u, cardId)()?.id ?: 0
})
}
// UserItemList
userItemList?.let { list ->
db.item.saveAll(list.distinctBy { it.itemKind to it.itemId }.mapApply {
id = db.item.findByUserAndItemKindAndItemId(u, itemKind, itemId)()?.id ?: 0
})
}
}
null
}
"CMUpsertUserSelectGacha" api@ {
val all: OngekiCMUpsertUserSelectGacha = mapper.convert(data["cmUpsertUserSelectGacha"]!!)
val selectLog:List<OngekiCMSelectGachaLog> = mapper.convert(data["selectGachaLogList"]!!)
// User data
val oldUser = db.data.findByCard_ExtId(uid)()
val u: UserData = all.userData?.get(0)?.apply {
id = oldUser?.id ?: 0
card = oldUser?.card ?: us.cardRepo.findByExtId(uid)() ?: (404 - "Card not found")
// Set eventWatchedDate with lastPlayDate, because client doesn't update it
cmEventWatchedDate = oldUser?.lastPlayDate ?: ""
db.data.save(this)
} ?: oldUser ?: return@api null
if(selectLog.isNotEmpty()) {
val selectionInfo = selectLog.first()
db.gacha.findByUserAndGachaId(u, selectionInfo.gachaId)?.apply {
// Total reset of selectPoints
selectPoint = 0
// Set the flag for the selection gacha so that the user cant use it anymore
useSelectPoint = 1
} ?: UserGacha().apply {
// It should be impossible to reach this
user = u
gachaId = selectionInfo.gachaId
db.gacha.save(this)
}
}
all.run {
// Set users
listOfNotNull(
userCharacterList, userCardList
).flatten().forEach { it.user = u }
if (all.isNewCharacterList?.contains("1") ?: false) {
// UserCharacterList
userCharacterList?.let { list ->
db.character.saveAll(list.distinctBy { it.characterId }.mapApply {
id = db.character.findByUserAndCharacterId(u, characterId)()?.id ?: 0
})
}
}
if (all.isNewCardList?.contains("1") ?: false) {
// UserCardList
userCardList?.let { list ->
db.card.saveAll(list.distinctBy { it.cardId }.mapApply {
id = db.card.findByUserAndCardId(u, cardId)()?.id ?: 0
})
}
}
}
null
}
"CMUpsertUserPrintPlaylog" api@ {
// User payment logs, useless
null
}
"CMUpsertUserPrint" api@ {
// User print information, useless
null
}
"CMUpsertUserPrintlog" api@ {
// User print logs, useless
null
}
"CMGetUserGachaSupply" {
// Mock function, not sure of functionality
mapOf("supplyId" to 0, "length" to 0, "supplyCardList" to emptyList<Any>())
}
"GetGameTheater" {
// Mock function, not sure of functionality
mapOf("length" to 0, "gameTheaterList" to emptyList<Any>(), "registIdList" to emptyList<Any>())
}
}

View File

@@ -151,6 +151,19 @@ interface OgkUserRegionsRepo: OngekiUserLinked<UserRegions> {
fun findByUserAndRegionId(user: UserData, regionId: Int): UserRegions?
}
interface OgkGameGachaCardRepo : JpaRepository<GameGachaCard, Long> {
fun findAllByGachaId(gachaId: Long): List<GameGachaCard>
@Query("SELECT g FROM OngekiGameGachaCard g WHERE g.gachaId = :gachaId OR g.gachaId = 1112")
fun findAllByGachaIdAndPermanent(gachaId: Long): List<GameGachaCard>
}
interface OgkGameGachaRepo : JpaRepository<GameGacha, Long>
interface OgkUserGachaRepo : OngekiUserLinked<UserGacha> {
fun findByUserAndGachaId(user: UserData, gachaId: Long): UserGacha?
}
// Re:Fresh
interface OgkUserEventMapRepo : OngekiUserLinked<UserEventMap>
interface OgkUserSkinRepo : OngekiUserLinked<UserSkin>
@@ -195,6 +208,7 @@ class OngekiUserRepos(
val eventMap: OgkUserEventMapRepo,
val skin: OgkUserSkinRepo,
val regions: OgkUserRegionsRepo,
val gacha: OgkUserGachaRepo,
)
@Component
@@ -207,6 +221,8 @@ class OngekiGameRepos(
val present: OgkGamePresentRepo,
val reward: OgkGameRewardRepo,
val skill: OgkGameSkillRepo,
val gachaCard: OgkGameGachaCardRepo,
val gacha:OgkGameGachaRepo
)
@Component

View File

@@ -1,6 +1,7 @@
package icu.samnyan.aqua.sega.ongeki.model
import com.fasterxml.jackson.annotation.JsonIgnore
import com.fasterxml.jackson.annotation.JsonProperty
import jakarta.persistence.*
@Entity(name = "OngekiGameCard")
@@ -112,3 +113,41 @@ class GameSkill {
var category: String = ""
var info: String = ""
}
@Entity(name = "OngekiGameGachaCard")
@Table(name = "ongeki_game_gacha_card")
class GameGachaCard {
@Id
var cardId: Long = 0
var gachaId: Long = 0
var rarity: Int = 0
var weight: Int = 0
@JsonProperty("isPickup")
var isPickup: Boolean = false
@JsonProperty("isSelect")
var isSelect: Boolean = false
}
@Entity(name = "OngekiGameGacha")
@Table(name = "ongeki_game_gacha")
class GameGacha {
@Id
var gachaId: Long = 0
var gachaName: String = ""
var type: Int = 0
var kind: Int = 0
var maxSelectPoint: Int = 0
var ceilingCnt: Int = 0
var changeRateCnt1: Int = 0
var changeRateCnt2: Int = 0
var startDate: String = ""
var endDate: String = ""
var noticeStartDate: String = ""
var noticeEndDate: String = ""
var convertEndDate: String = ""
@JsonProperty("isCeiling")
var isCeiling: Boolean = false
}

View File

@@ -0,0 +1,44 @@
package icu.samnyan.aqua.sega.ongeki.model
class OngekiCMUpsertUserGacha {
var userData: List<UserData>? = null
var userCharacterList: List<UserCharacter>? = null
var userCardList: List<UserCard>? = null
var gameGachaCardList: List<GameGachaCard>? = null
var userItemList: List<UserItem>? = null
// These are strings with as many 1s as new elements are present
var isNewCharacterList: String? = null
var isNewCardList: String? = null
var isNewItemList: String? = null
}
class OngekiCMUpsertAll {
var userData: List<UserData>? = null
var userActivityList: List<UserActivity>? = null
var userCharacterList: List<UserCharacter>? = null
var userCardList: List<UserCard>? = null
var userItemList: List<UserItem>? = null
// These are strings with as many 1s as new elements are given
var isNewCharacterList: String? = null
var isNewCardList: String? = null
var isNewItemList: String? = null
}
class OngekiCMUpsertUserSelectGacha {
var userData: List<UserData>? = null
var userCharacterList: List<UserCharacter>? = null
var userCardList: List<UserCard>? = null
// These are strings with as many 1s as new elements are given
var isNewCharacterList: String? = null
var isNewCardList: String? = null
}
class OngekiCMSelectGachaLog {
var gachaId: Long = 0
var useSelectPoint: Int = 0
var convertType: Int = 0
var convertItem: Int = 0
}

View File

@@ -8,6 +8,7 @@ import icu.samnyan.aqua.sega.general.model.Card
import icu.samnyan.aqua.sega.util.jackson.AccessCodeSerializer
import jakarta.persistence.*
import java.time.LocalDate
import java.time.LocalDateTime
@MappedSuperclass
class OngekiUserEntity : BaseEntity(), IUserEntity<UserData> {
@@ -517,10 +518,27 @@ class UserSkin : OngekiUserEntity() {
@Entity(name = "OngekiUserRegions")
@Table(
name = "ongeki_user_regions",
uniqueConstraints = [UniqueConstraint(columnNames = ["user_id", "regionId"])]
uniqueConstraints = [UniqueConstraint(columnNames = ["user_id", "region_id"])]
)
class UserRegions : OngekiUserEntity() {
var regionId = 0
var playCount = 1
var created: String = LocalDate.now().toString()
}
@Entity(name="OngekiUserGacha")
@Table(
name = "ongeki_user_gacha",
uniqueConstraints = [UniqueConstraint(columnNames = ["user_id", "gacha_id"])]
)
class UserGacha : OngekiUserEntity() {
var gachaId:Long = 0
var totalGachaCnt = 0
var ceilingGachaCnt = 0
var selectPoint = 0
var useSelectPoint = 0
var dailyGachaCnt = 0
var fiveGachaCnt = 0
var elevenGachaCnt = 0
var dailyGachaDate: String = LocalDateTime.now().toString()
}