From e85533686eeb00f4cd62af7cdd11b25e7f62f828 Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Fri, 15 Mar 2024 00:37:30 -0400 Subject: [PATCH] [F] Fix detailed ranks --- .gitignore | 3 +- src/main/java/ext/Ext.kt | 13 ++++--- .../java/icu/samnyan/aqua/net/games/Chusan.kt | 2 +- .../icu/samnyan/aqua/net/games/Maimai2.kt | 2 +- .../java/icu/samnyan/aqua/net/games/Models.kt | 35 +++++++++++++++---- .../java/icu/samnyan/aqua/net/games/Ongeki.kt | 2 +- .../icu/samnyan/aqua/net/utils/GameHelper.kt | 30 ++++++++++------ src/main/resources/meta/update.sh | 4 +++ 8 files changed, 67 insertions(+), 24 deletions(-) create mode 100644 src/main/resources/meta/update.sh diff --git a/.gitignore b/.gitignore index 28723d90..a093c1e3 100644 --- a/.gitignore +++ b/.gitignore @@ -76,4 +76,5 @@ gradle-app.setting ### Gradle Patch ### # Java heap dump *.hprof -.jpb \ No newline at end of file +.jpb +src/main/resources/meta/*/*.json diff --git a/src/main/java/ext/Ext.kt b/src/main/java/ext/Ext.kt index 3617213d..b9bf11f2 100644 --- a/src/main/java/ext/Ext.kt +++ b/src/main/java/ext/Ext.kt @@ -7,7 +7,9 @@ import io.ktor.client.plugins.contentnegotiation.* import io.ktor.serialization.kotlinx.json.* import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext +import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonNamingStrategy import org.apache.tika.Tika import org.apache.tika.mime.MimeTypes import org.springframework.http.HttpStatus @@ -47,12 +49,15 @@ val emailRegex = "^(?=.{1,64}@)[\\p{L}0-9_-]+(\\.[\\p{L}0-9_-]+)*@[^-][\\p{L}0-9 fun Str.isValidEmail(): Bool = emailRegex.matches(this) // Global tools +@OptIn(ExperimentalSerializationApi::class) +val JSON = Json { + ignoreUnknownKeys = true + isLenient = true + namingStrategy = JsonNamingStrategy.SnakeCase +} val HTTP = HttpClient(CIO) { install(ContentNegotiation) { - json(Json { - ignoreUnknownKeys = true - isLenient = true - }) + json(JSON) } } val TIKA = Tika() diff --git a/src/main/java/icu/samnyan/aqua/net/games/Chusan.kt b/src/main/java/icu/samnyan/aqua/net/games/Chusan.kt index edd1e627..c2a6a2b0 100644 --- a/src/main/java/icu/samnyan/aqua/net/games/Chusan.kt +++ b/src/main/java/icu/samnyan/aqua/net/games/Chusan.kt @@ -19,7 +19,7 @@ class Chusan( val userPlaylogRepository: UserPlaylogRepository, val userDataRepository: UserDataRepository, val userGeneralDataRepository: UserGeneralDataRepository -): GameApiController +): GameApiController("chu3") { override suspend fun trend(@RP username: Str): List = us.cardByName(username) { card -> findTrend(userPlaylogRepository.findByUser_Card_ExtId(card.extId) diff --git a/src/main/java/icu/samnyan/aqua/net/games/Maimai2.kt b/src/main/java/icu/samnyan/aqua/net/games/Maimai2.kt index 6a02ecd5..644ba2c5 100644 --- a/src/main/java/icu/samnyan/aqua/net/games/Maimai2.kt +++ b/src/main/java/icu/samnyan/aqua/net/games/Maimai2.kt @@ -19,7 +19,7 @@ class Maimai2( val userPlaylogRepository: UserPlaylogRepository, val userDataRepository: UserDataRepository, val userGeneralDataRepository: UserGeneralDataRepository -): GameApiController +): GameApiController("mai2") { override suspend fun trend(@RP username: Str): List = us.cardByName(username) { card -> findTrend(userPlaylogRepository.findByUser_Card_ExtId(card.extId) diff --git a/src/main/java/icu/samnyan/aqua/net/games/Models.kt b/src/main/java/icu/samnyan/aqua/net/games/Models.kt index 3b88f9a7..ae94e30b 100644 --- a/src/main/java/icu/samnyan/aqua/net/games/Models.kt +++ b/src/main/java/icu/samnyan/aqua/net/games/Models.kt @@ -1,8 +1,10 @@ package icu.samnyan.aqua.net.games import ext.API +import ext.JSON import ext.RP import icu.samnyan.aqua.net.utils.IGenericGamePlaylog +import kotlinx.serialization.Serializable data class TrendOut(val date: String, val rating: Int, val plays: Int) @@ -19,6 +21,7 @@ data class GenericGameSummary( val rating: Int, val ratingHighest: Int, val ranks: List, + val detailedRanks: Map>, val maxCombo: Int, val fullCombo: Int, val allPerfect: Int, @@ -46,15 +49,35 @@ data class GenericRankingPlayer( val lastSeen: String ) -interface GameApiController { +@Serializable +data class GenericMusicMeta( + val name: String?, + val ver: String, + val notes: List +) + +@Serializable +data class GenericNoteMeta( + val lv: Double, + val lvId: Int +) + +abstract class GameApiController(name: String) { + val musicMapping: Map = GameApiController::class.java + .getResourceAsStream("/meta/$name/music.json") + .use { it?.reader()?.readText() } + ?.let { JSON.decodeFromString>(it) } + ?.mapKeys { it.key.toInt() } + ?: emptyMap() + @API("trend") - suspend fun trend(@RP username: String): List + abstract suspend fun trend(@RP username: String): List @API("user-summary") - suspend fun userSummary(@RP username: String): GenericGameSummary + abstract suspend fun userSummary(@RP username: String): GenericGameSummary @API("ranking") - suspend fun ranking(): List + abstract suspend fun ranking(): List @API("playlog") - suspend fun playlog(@RP id: Long): IGenericGamePlaylog + abstract suspend fun playlog(@RP id: Long): IGenericGamePlaylog @API("recent") - suspend fun recent(@RP username: String): List + abstract suspend fun recent(@RP username: String): List } \ No newline at end of file diff --git a/src/main/java/icu/samnyan/aqua/net/games/Ongeki.kt b/src/main/java/icu/samnyan/aqua/net/games/Ongeki.kt index f8d6753a..ebda6dc2 100644 --- a/src/main/java/icu/samnyan/aqua/net/games/Ongeki.kt +++ b/src/main/java/icu/samnyan/aqua/net/games/Ongeki.kt @@ -17,7 +17,7 @@ class Ongeki( val userPlaylogRepository: UserPlaylogRepository, val userDataRepository: UserDataRepository, val userGeneralDataRepository: UserGeneralDataRepository -): GameApiController { +): GameApiController("ongeki") { override suspend fun trend(username: String) = us.cardByName(username) { card -> findTrend(userPlaylogRepository.findByUser_Card_ExtId(card.extId) .map { TrendLog(it.playDate, it.playerRating) }) diff --git a/src/main/java/icu/samnyan/aqua/net/utils/GameHelper.kt b/src/main/java/icu/samnyan/aqua/net/utils/GameHelper.kt index 10705f85..b39cf280 100644 --- a/src/main/java/icu/samnyan/aqua/net/utils/GameHelper.kt +++ b/src/main/java/icu/samnyan/aqua/net/utils/GameHelper.kt @@ -3,10 +3,7 @@ package icu.samnyan.aqua.net.utils import ext.isoDate import ext.millis import ext.minus -import icu.samnyan.aqua.net.games.GenericGameSummary -import icu.samnyan.aqua.net.games.GenericRankingPlayer -import icu.samnyan.aqua.net.games.RankCount -import icu.samnyan.aqua.net.games.TrendOut +import icu.samnyan.aqua.net.games.* import icu.samnyan.aqua.sega.general.model.Card import org.springframework.data.jpa.repository.JpaRepository import org.springframework.data.repository.NoRepositoryBean @@ -83,7 +80,7 @@ interface GenericPlaylogRepo { fun List.acc() = if (isEmpty()) 0.0 else sumOf { it.achievement }.toDouble() / size / 10000.0 -fun genericUserSummary( +fun GameApiController.genericUserSummary( card: Card, userDataRepo: GenericUserDataRepo<*, *>, userPlaylogRepo: GenericPlaylogRepo, @@ -95,11 +92,23 @@ fun genericUserSummary( val user = userDataRepo.findByCard(card) ?: (404 - "Game data not found") val plays = userPlaylogRepo.findByUserCardExtId(card.extId) - // O(6n) ranks algorithm: Loop through the entire list of plays, - // count the number of each rank - val ranks = shownRanks.associate { (_, v) -> v to 0 }.toMutableMap() - plays.forEach { - shownRanks.find { (s, _) -> it.achievement > s }?.let { (_, v) -> ranks[v] = ranks[v]!! + 1 } + // Detailed ranks: Find the number of each rank in each level category + // map> + val rankMap = shownRanks.associate { (_, v) -> v to 0 } + val detailedRanks = HashMap>() + 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() } + ranks[v] = ranks[v]!! + 1 + } + } + + // Collapse detailed ranks to get non-detailed ranks map + val ranks = shownRanks.associate { (_, v) -> v to 0 }.toMutableMap().also { ranks -> + plays.forEach { play -> + shownRanks.find { (s, _) -> play.achievement > s }?.let { (_, v) -> ranks[v] = ranks[v]!! + 1 } + } } return GenericGameSummary( @@ -111,6 +120,7 @@ fun genericUserSummary( rating = user.playerRating, ratingHighest = user.highestRating, ranks = ranks.map { (k, v) -> RankCount(k, v) }, + detailedRanks = detailedRanks, maxCombo = plays.maxOfOrNull { it.maxCombo } ?: 0, fullCombo = plays.count { it.isFullCombo }, allPerfect = plays.count { it.isAllPerfect }, diff --git a/src/main/resources/meta/update.sh b/src/main/resources/meta/update.sh new file mode 100644 index 00000000..147f5933 --- /dev/null +++ b/src/main/resources/meta/update.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +curl "https://aquadx.net/d/ongeki/00/all-music.json" -o "ongeki/music.json" +curl "https://aquadx.net/d/mai2/00/all-music.json" -o "mai2/music.json" +curl "https://aquadx.net/d/chu3/00/all-music.json" -o "chu3/music.json"