diff --git a/build.gradle.kts b/build.gradle.kts index 012a520..35e33e1 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -26,7 +26,7 @@ implementation("com.sun.mail:jakarta.mail:2.0.1") implementation("commons-codec:commons-codec:1.16.0") implementation("commons-io:commons-io:2.15.1") - implementation("io.netty:netty-all:4.1.102.Final") + implementation("io.netty:netty-all:4.1.103.Final") implementation("jakarta.mail:jakarta.mail-api:2.1.2") implementation("net.dv8tion:JDA:5.0.0-beta.16") implementation("org.apache.commons:commons-compress:1.25.0") diff --git a/src/main/kotlin/net/taehui/twilight/system/AbilitySystem.kt b/src/main/kotlin/net/taehui/twilight/system/AbilitySystem.kt index 96b73fd..508bc55 100644 --- a/src/main/kotlin/net/taehui/twilight/system/AbilitySystem.kt +++ b/src/main/kotlin/net/taehui/twilight/system/AbilitySystem.kt @@ -13,6 +13,7 @@ import java.io.IOException import java.nio.file.Files import java.util.TreeSet +import java.util.concurrent.atomic.AtomicInteger object AbilitySystem : Logger { class DefaultAbility { @@ -27,16 +28,16 @@ } class AbilityMap { - val hashMap128 = mutableMapOf() - val hashMap256 = mutableMapOf() + val noteID128Map = mutableMapOf() + val noteID256Map = mutableMapOf() fun wipe() { - hashMap128.clear() - hashMap256.clear() + noteID128Map.clear() + noteID256Map.clear() } fun getValue(noteID128: String, noteID256: String, defaultValue: T): T { - return hashMap128.getOrDefault(noteID128, hashMap256.getOrDefault(noteID256, defaultValue)) + return noteID128Map.getOrDefault(noteID128, noteID256Map.getOrDefault(noteID256, defaultValue)) } } @@ -49,6 +50,7 @@ private val noteIDAbilityIDsMap = AbilityMap>() private val noteIDAbilityNamesMap = AbilityMap>() private val abilityNameAbilityIDsMap = mutableMapOf>() + private val abilityNameAbilityIDNoteIDCountMap = mutableMapOf>() var isLoading = false fun loadAbility() { @@ -122,6 +124,7 @@ noteIDAbilityIDsMap.wipe() noteIDAbilityNamesMap.wipe() abilityNameAbilityIDsMap.clear() + abilityNameAbilityIDNoteIDCountMap.clear() Files.list(TwilightComponent.ABILITY_ENTRY_PATH).use { abilityFilePaths -> abilityFilePaths.filter { it.fileName.toString().startsWith("#") }.sorted( Comparator.comparingInt { abilityFilePath -> @@ -151,25 +154,25 @@ val ability = abilityMap[abilityID] ?: 0.0 if (ability > 0.0) { if (abilityTableData.md5.isNotEmpty()) { - noteIDAbilityMap?.hashMap128?.putIfAbsent(abilityTableData.md5, ability) + noteIDAbilityMap?.noteID128Map?.putIfAbsent(abilityTableData.md5, ability) } if (abilityTableData.sha256.isNotEmpty()) { - noteIDAbilityMap?.hashMap256?.putIfAbsent(abilityTableData.sha256, ability) + noteIDAbilityMap?.noteID256Map?.putIfAbsent(abilityTableData.sha256, ability) } } if (abilityTableData.md5.isNotEmpty()) { - noteIDAbilityIDsMap.hashMap128.computeIfAbsent( + noteIDAbilityIDsMap.noteID128Map.computeIfAbsent( abilityTableData.md5 ) { mutableListOf() }.add(abilityID) - noteIDAbilityNamesMap.hashMap128.computeIfAbsent( + noteIDAbilityNamesMap.noteID128Map.computeIfAbsent( abilityTableData.md5 ) { mutableListOf() }.add(abilityName) } if (abilityTableData.sha256.isNotEmpty()) { - noteIDAbilityIDsMap.hashMap256.computeIfAbsent( + noteIDAbilityIDsMap.noteID256Map.computeIfAbsent( abilityTableData.sha256 ) { mutableListOf() }.add(abilityID) - noteIDAbilityNamesMap.hashMap256.computeIfAbsent( + noteIDAbilityNamesMap.noteID256Map.computeIfAbsent( abilityTableData.md5 ) { mutableListOf() }.add(abilityName) } @@ -178,6 +181,11 @@ levels.indexOf(o1).compareTo(levels.indexOf(o2)) } }.add(abilityID) + abilityNameAbilityIDNoteIDCountMap.computeIfAbsent(abilityName) { + mutableMapOf() + }.computeIfAbsent(abilityID) { + AtomicInteger() + }.incrementAndGet() } logInfo("Loaded Ability (${abilityTable.name})") } @@ -228,4 +236,12 @@ fun getAbilityNames(): Collection { return abilityNameAbilityIDsMap.keys } + + fun getNoteIDCount(abilityName: String, abilityID: String): Int { + return if (abilityID == "*") abilityNameAbilityIDNoteIDCountMap.getOrDefault( + abilityName, + emptyMap() + ).values.sumOf { it.get() } else abilityNameAbilityIDNoteIDCountMap.getOrDefault(abilityName, emptyMap()) + .getOrDefault(abilityID, AtomicInteger()).get() + } } \ No newline at end of file diff --git a/src/main/kotlin/net/taehui/twilight/system/DB.kt b/src/main/kotlin/net/taehui/twilight/system/DB.kt index d7c481d..57724b4 100644 --- a/src/main/kotlin/net/taehui/twilight/system/DB.kt +++ b/src/main/kotlin/net/taehui/twilight/system/DB.kt @@ -2806,7 +2806,86 @@ } } - interface ILevelVSItem { + interface IAvatarHandledItem { + val noteID: String + + @get:JsonIgnore + val noteID128: String + + @get:JsonIgnore + val noteID256: String + val stand: Int + val handled: Component.Handled + } + + fun getAvatarHandled(avatarID: String, levelName: String): CompletableFuture { + return logValueFuture { + val data = mutableMapOf() + + val noteIDAvatarHandledItemMap = mutableMapOf() + pool.connection.use { db -> + db.prepareStatement( + """ + SELECT tw_note.Note_ID, Note_ID_128, Note_ID_256, Title, Artist, Genre, Level_Text, Level, Stand + FROM tw_note, tw_comment + WHERE tw_note.Note_ID = tw_comment.Note_ID AND tw_comment.Avatar = ? AND Is_Max = true + """.trimIndent() + ).use { dbStatement -> + dbStatement.setString(1, avatarID) + dbStatement.executeQuery().use { + while (it.next()) { + val noteID = it.getString("Note_ID") + noteIDAvatarHandledItemMap[noteID] = object : IAvatarHandledItem { + override val noteID = noteID + override val noteID128 = it.getString("Note_ID_128") + override val noteID256 = it.getString("Note_ID_256") + val artist = it.getString("Artist") + val title = it.getString("Title") + val genre = it.getString("Genre") + val levelText = it.getString("Level_Text") + val level = it.getInt("Level") + override val stand = it.getInt("Stand") + override val handled = getHandled(avatarID, noteID) + } + } + } + } + } + + fun setLevelID(levelID: String) { + val handledAvatarHandledItemMap = (if (levelID == "*") noteIDAvatarHandledItemMap.filter { + AbilitySystem.getAbilityNames(it.value.noteID128, it.value.noteID256).contains(levelName) + } else noteIDAvatarHandledItemMap.filter { + AbilitySystem.getAbilityIDs(it.value.noteID128, it.value.noteID256).contains(levelID) + }).values.groupBy { it.handled } + + data[levelID] = object { + val handledBand1Count = + handledAvatarHandledItemMap.getOrDefault(Component.Handled.BAND1, emptyList()).size + val handledHighestClearCount = + handledAvatarHandledItemMap.getOrDefault(Component.Handled.HIGHEST_CLEAR, emptyList()).size + val handledHigherClearCount = + handledAvatarHandledItemMap.getOrDefault(Component.Handled.HIGHER_CLEAR, emptyList()).size + val handledClearCount = + handledAvatarHandledItemMap.getOrDefault(Component.Handled.CLEAR, emptyList()).size + val noteIDCount = AbilitySystem.getNoteIDCount(levelName, levelID) + val avatarHandledItems = handledAvatarHandledItemMap.map { + Pair(it.key, it.value.sortedByDescending { it.stand }.take(50)) + }.toMap() + } + } + + if (levelName == "*") { + setLevelID("*") + } else { + arrayOf("*").plus(AbilitySystem.getAbilityIDs(levelName)).forEach { setLevelID(it) } + } + + data + } + } + + interface IAvatarLevelVSItem { val noteID: String @get:JsonIgnore @@ -2822,7 +2901,7 @@ fun getAvatarLevelVS(avatarID: String, targetID: String, levelName: String): CompletableFuture { fun getLevelVSItems( avatarID: String, - levelVSMap: MutableMap + noteIDAvatarLevelVSMap: MutableMap ): CompletableFuture { return logFuture { pool.connection.use { db -> @@ -2837,7 +2916,7 @@ dbStatement.executeQuery().use { while (it.next()) { val noteID = it.getString("Note_ID") - levelVSMap[noteID] = object : ILevelVSItem { + noteIDAvatarLevelVSMap[noteID] = object : IAvatarLevelVSItem { override val noteID = noteID override val noteID128 = it.getString("Note_ID_128") override val noteID256 = it.getString("Note_ID_256") @@ -2859,29 +2938,29 @@ val data = mutableMapOf() - val avatarLevelVSMap = mutableMapOf() - val targetLevelVSMap = mutableMapOf() + val noteIDAvatarLevelVSItemMap = mutableMapOf() + val noteIDTargetLevelVSItemMap = mutableMapOf() return CompletableFuture.allOf( - getLevelVSItems(avatarID, avatarLevelVSMap), - getLevelVSItems(targetID, targetLevelVSMap) + getLevelVSItems(avatarID, noteIDAvatarLevelVSItemMap), + getLevelVSItems(targetID, noteIDTargetLevelVSItemMap) ).thenApply { fun setLevelID(levelID: String) { - val avatarLevelVSItems = mutableSetOf() - val targetLevelVSItems = mutableSetOf() - (if (levelName == "*") avatarLevelVSMap.keys.intersect(targetLevelVSMap.keys) else if (levelID == "*") avatarLevelVSMap.filter { + val avatarLevelVSItems = mutableSetOf() + val targetLevelVSItems = mutableSetOf() + (if (levelName == "*") noteIDAvatarLevelVSItemMap.keys.intersect(noteIDTargetLevelVSItemMap.keys) else if (levelID == "*") noteIDAvatarLevelVSItemMap.filter { AbilitySystem.getAbilityNames(it.value.noteID128, it.value.noteID256).contains(levelName) }.keys - .intersect(targetLevelVSMap.filter { + .intersect(noteIDTargetLevelVSItemMap.filter { AbilitySystem.getAbilityNames(it.value.noteID128, it.value.noteID256).contains(levelName) - }.keys) else avatarLevelVSMap.filter { + }.keys) else noteIDAvatarLevelVSItemMap.filter { AbilitySystem.getAbilityIDs(it.value.noteID128, it.value.noteID256).contains(levelID) }.keys - .intersect(targetLevelVSMap.filter { + .intersect(noteIDTargetLevelVSItemMap.filter { AbilitySystem.getAbilityIDs(it.value.noteID128, it.value.noteID256).contains(levelID) }.keys)) .forEach { noteID -> - val avatarLevelVSItem = avatarLevelVSMap[noteID] - val targetLevelVSItem = targetLevelVSMap[noteID] + val avatarLevelVSItem = noteIDAvatarLevelVSItemMap[noteID] + val targetLevelVSItem = noteIDTargetLevelVSItemMap[noteID] if (avatarLevelVSItem != null && targetLevelVSItem != null) { val levelVSStand = avatarLevelVSItem.stand - targetLevelVSItem.stand if (levelVSStand > 0) { diff --git a/src/main/kotlin/net/taehui/twilight/www/WwwAvatar.kt b/src/main/kotlin/net/taehui/twilight/www/WwwAvatar.kt index be46312..186f6b0 100644 --- a/src/main/kotlin/net/taehui/twilight/www/WwwAvatar.kt +++ b/src/main/kotlin/net/taehui/twilight/www/WwwAvatar.kt @@ -267,10 +267,6 @@ send(ctx, it) } - "/qwilight/www/avatar/wwwLevels" -> DB.getAvatarWwwLevels(avatarID).thenAccept { - send(ctx, it) - } - "/qwilight/www/avatar/ability/5K" -> DB.getAvatarAbility( Component.InputMode.INPUT_MODE_5_1, avatarID @@ -292,6 +288,17 @@ send(ctx, it) } + "/qwilight/www/avatar/wwwLevels" -> DB.getAvatarWwwLevels(avatarID).thenAccept { + send(ctx, it) + } + + "/qwilight/www/avatar/handled" -> DB.getAvatarHandled( + avatarID, + params.getOrDefault("levelName", "") + ).thenAccept { + send(ctx, it) + } + "/qwilight/www/avatar/levelVS" -> { val targetID = params.getOrDefault("targetID", "") if (avatarID.isEmpty() || targetID.isEmpty() || avatarID == targetID) {