From fac8b71c36bbc7f381ee5505892257dd9358950b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Szab=C3=B3=20Benedek?= Date: Tue, 9 Jun 2026 11:43:40 +0200 Subject: [PATCH 1/2] Added new setting for calculating group scores by adding user's scores --- .../leaderboard/LeaderBoardComponent.kt | 2 + .../leaderboard/LeaderBoardService.kt | 97 ++++++++++++++++++- 2 files changed, 94 insertions(+), 5 deletions(-) diff --git a/backend/src/main/kotlin/hu/bme/sch/cmsch/component/leaderboard/LeaderBoardComponent.kt b/backend/src/main/kotlin/hu/bme/sch/cmsch/component/leaderboard/LeaderBoardComponent.kt index 88a53d56..a1a4ebb1 100644 --- a/backend/src/main/kotlin/hu/bme/sch/cmsch/component/leaderboard/LeaderBoardComponent.kt +++ b/backend/src/main/kotlin/hu/bme/sch/cmsch/component/leaderboard/LeaderBoardComponent.kt @@ -72,6 +72,8 @@ class LeaderBoardComponent( var tokenPercent by NumberSettingRef(100, serverSideOnly = true, strictConversion = false, fieldName = "QR Kódok szorzó (%)", description = "100 = 1x, 0 = nem számít bele") + var addUserScoresForGroupScore by BooleanSettingRef(serverSideOnly = true, fieldName = "Felhasználói pontok felhasználása pontszámításnál", description = "Ha igaz, akkor a USER Ownershippel rendelkező pontok is beleszámolódnak a Csapatos pontszámításba") + /// ------------------------------------------------------------------------------------------------------------------- val displayGroup by SettingGroup(fieldName = "Kijelzés") diff --git a/backend/src/main/kotlin/hu/bme/sch/cmsch/component/leaderboard/LeaderBoardService.kt b/backend/src/main/kotlin/hu/bme/sch/cmsch/component/leaderboard/LeaderBoardService.kt index ff87a503..6d2c29e0 100644 --- a/backend/src/main/kotlin/hu/bme/sch/cmsch/component/leaderboard/LeaderBoardService.kt +++ b/backend/src/main/kotlin/hu/bme/sch/cmsch/component/leaderboard/LeaderBoardService.kt @@ -25,6 +25,7 @@ import org.springframework.transaction.annotation.Isolation import org.springframework.transaction.annotation.Transactional import java.sql.SQLException import java.util.* +import kotlin.collections.listOf @Suppress("RedundantModalityModifier") // Spring transactional proxy requires it not to be final @Service @@ -142,13 +143,14 @@ open class LeaderBoardService( log.info("Recalculating is disabled now") return } - forceRecalculateForGroups() forceRecalculateForUsers() + forceRecalculateForGroups() } @Retryable(value = [ SQLException::class ], maxRetries = 5, delay = 500L, multiplier = 1.5) @Transactional(readOnly = true, isolation = Isolation.SERIALIZABLE) open fun forceRecalculateForGroups() { + val userIdToGroup by lazy{ users.findAll().associate { it.id to it.group } } val hintPercentage: Float = riddleComponent.map { it.hintScorePercent }.orElse(0) / 100f val details = mutableMapOf() log.info("Recalculating group top list cache; hint:{}", hintPercentage) @@ -166,7 +168,21 @@ open class LeaderBoardService( taskScore = (it.value.sumOf { s -> s.score } * tasksPercent).toInt()) } } - OwnershipType.USER -> listOf() + OwnershipType.USER -> { + if (leaderBoardComponent.addUserScoresForGroupScore) { + taskSubmissions.map { it.findAll() }.orElse(mutableListOf()) + .groupBy { userIdToGroup[it.userId ?: 0] } + .filter { it.key?.races?:false } + .map { + LeaderBoardAsGroupEntryDto( + it.key!!.id, + it.key!!.name, + taskScore = (it.value.sumOf { s -> s.score } * tasksPercent).toInt()) + } + } else { + listOf() + } + } } val tasksTitle = taskComponent.map { it.menuDisplayName }.orElse("") tasks.forEach { task -> @@ -192,7 +208,26 @@ open class LeaderBoardService( } * riddlesPercent).toInt()) } } - OwnershipType.USER -> listOf() + OwnershipType.USER -> { + if (leaderBoardComponent.addUserScoresForGroupScore) { + riddleSubmissions.map { it.findAll() }.orElse(mutableListOf()) + .groupBy { userIdToGroup[it.ownerUserId] } + .filter { it.key?.races ?: false } + .map { riddleGroup -> + LeaderBoardAsGroupEntryDto( + riddleGroup.key?.id ?: 0, + riddleGroup.key?.name ?: "n/a", + riddleScore = (riddleGroup.value + .filter { it.completed && !it.skipped } + .sumOf { s -> + ((riddleCache[s.riddleId]?.score?.toFloat() ?: 0f) * (if (s.hintUsed) hintPercentage else 1f)).toInt() + } * riddlesPercent).toInt() + ) + } + } else { + listOf() + } + } } val riddleTitle = riddleComponent.map { it.menuDisplayName }.orElse("") riddles.forEach { riddle -> @@ -220,13 +255,65 @@ open class LeaderBoardService( challengeScore = entity.value.sumOf { s -> (s.score * challengesPercent).toInt() }) } } - OwnershipType.USER -> listOf() + OwnershipType.USER -> { + if (leaderBoardComponent.addUserScoresForGroupScore) { + challengeSubmissions.map { it.findAll() }.orElse(mutableListOf()) + .groupBy { userIdToGroup[it.userId ?: 0] } + .filter { it.key?.races ?: false } + .map { entity -> + entity.value.forEach { challenge -> + details.computeIfAbsent(entity.key!!.id) { + key -> TopListDetails(key, entity.value[0].groupName) + }.items.compute(challenge.category) { + _, value -> (value ?: 0) + (challenge.score * challengesPercent).toInt() + } + } + LeaderBoardAsGroupEntryDto( + entity.key!!.id, + entity.key!!.name, + challengeScore = entity.value.sumOf { s -> (s.score * challengesPercent).toInt() } + ) + } + } else { + listOf() + } + } } val tokenPercent = leaderBoardComponent.tokenPercent / 100.0f val tokenTitle = tokenComponent.map { it.menuDisplayName }.orElse("") val pointsFromTokens = when (startupPropertyConfig.tokenOwnershipMode) { - OwnershipType.USER -> listOf() + OwnershipType.USER -> { + if (leaderBoardComponent.addUserScoresForGroupScore) { + tokenSubmissions.map { it.findAll() }.orElse(mutableListOf()) + .groupBy { userIdToGroup[it.ownerUser?.id ?: 0] } + .filter { it.key?.races ?: false } + .map { (group, tokens) -> + val groupId = group?.id ?: 0 + val groupName = group?.name ?: "n/a" + val tokenScore = (tokens.sumOf { s -> s.token?.score ?: 0 } * tokenPercent).toInt() + + val groupDetails = details.computeIfAbsent(groupId) { TopListDetails(groupId, groupName) } + groupDetails.items.compute(tokenTitle) { _, value -> (value ?: 0) + tokenScore } + + if (leaderBoardComponent.showTokenCountByRarity) + groupDetails.tokenRarities = tokens + .groupBy { it.token?.rarity } + .mapNotNull { (rarity, tokens) -> + if (rarity.isNullOrBlank()) null + else (rarity to tokens.size) + }.toMap() + + LeaderBoardAsGroupEntryDto( + groupId, + groupName, + tokenScore = tokenScore + ) + } + } else { + listOf() + } + } OwnershipType.GROUP -> { tokenSubmissions.map { submissions -> submissions From e63af6641b179e339162c403e0ac3614c49e2d5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Szab=C3=B3=20Benedek?= Date: Sat, 13 Jun 2026 10:51:40 +0200 Subject: [PATCH 2/2] Added filter for tokenSubmissions if the group is racing --- .../hu/bme/sch/cmsch/component/leaderboard/LeaderBoardService.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/src/main/kotlin/hu/bme/sch/cmsch/component/leaderboard/LeaderBoardService.kt b/backend/src/main/kotlin/hu/bme/sch/cmsch/component/leaderboard/LeaderBoardService.kt index 6d2c29e0..9b505217 100644 --- a/backend/src/main/kotlin/hu/bme/sch/cmsch/component/leaderboard/LeaderBoardService.kt +++ b/backend/src/main/kotlin/hu/bme/sch/cmsch/component/leaderboard/LeaderBoardService.kt @@ -319,6 +319,7 @@ open class LeaderBoardService( submissions .findAll() .groupBy { it.ownerGroup } + .filter { it.key?.races ?: false } .map { (group, tokens) -> val groupId = group?.id ?: 0 val groupName = group?.name ?: "n/a"