From ae9dd420b7a333a55ab4e21899215929d628cd05 Mon Sep 17 00:00:00 2001 From: Arthur Melton Date: Tue, 16 May 2023 18:19:24 -0400 Subject: [PATCH 01/28] add glicko 2 dependency --- build.gradle.kts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/build.gradle.kts b/build.gradle.kts index 99f71d6..7063c9d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -15,6 +15,7 @@ plugins { repositories { mavenCentral() + maven { url = uri("https://jitpack.io") } } group = "de.bigboot.ggtools" @@ -81,6 +82,9 @@ dependencies { // CopyDown implementation("io.github.furstenheim:copy_down:1.1") + // Glicko2 + implementation("io.github.gorgtopalski:glicko2-team:3666e16") + // JUnit testImplementation("org.junit.jupiter:junit-jupiter-api:5.9.1") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.9.1") From c280fff6966a909d334f895122c095c6049bc873 Mon Sep 17 00:00:00 2001 From: Arthur Melton Date: Tue, 16 May 2023 19:08:30 -0400 Subject: [PATCH 02/28] replace skill with users rating --- .../de/bigboot/ggtools/fang/db/Users.kt | 4 +-- .../de/bigboot/ggtools/fang/db/UsersRating.kt | 25 ++++++++++++++++ .../db/migrations/v11__add_match_making.kt | 29 +++++++++++++++++++ 3 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 src/main/kotlin/de/bigboot/ggtools/fang/db/UsersRating.kt create mode 100644 src/main/kotlin/de/bigboot/ggtools/fang/db/migrations/v11__add_match_making.kt diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/db/Users.kt b/src/main/kotlin/de/bigboot/ggtools/fang/db/Users.kt index af79f70..9cb034b 100644 --- a/src/main/kotlin/de/bigboot/ggtools/fang/db/Users.kt +++ b/src/main/kotlin/de/bigboot/ggtools/fang/db/Users.kt @@ -10,11 +10,11 @@ class User(id: EntityID) : UUIDEntity(id) { companion object : UUIDEntityClass(Users) var snowflake by Users.snowflake - var skill by Users.skill var groups by Group via UsersGroups + + val skill by UserRating referrersOn UsersRating.user } object Users : UUIDTable() { val snowflake = long("snowflake") - val skill = integer("skill").default(1) } diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/db/UsersRating.kt b/src/main/kotlin/de/bigboot/ggtools/fang/db/UsersRating.kt new file mode 100644 index 0000000..2ff4c2b --- /dev/null +++ b/src/main/kotlin/de/bigboot/ggtools/fang/db/UsersRating.kt @@ -0,0 +1,25 @@ +package de.bigboot.ggtools.fang.db + +import org.jetbrains.exposed.dao.UUIDEntity +import org.jetbrains.exposed.dao.UUIDEntityClass +import org.jetbrains.exposed.dao.id.EntityID +import org.jetbrains.exposed.dao.id.UUIDTable +import java.util.* + +class UserRating(id: EntityID) : UUIDEntity(id) { + companion object : UUIDEntityClass(UsersRating) + + var rating by UsersRating.rating + var ratingDeviation by UsersRating.ratingDeviation + var volatility by UsersRating.volatility + + var user by User referencedOn UsersRating.user +} + +object UsersRating : UUIDTable() { + val rating = double("rating") + val ratingDeviation = double("ratingDeviation") + val volatility = double("volatility") + + val user = reference("user", Users) +} diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/db/migrations/v11__add_match_making.kt b/src/main/kotlin/de/bigboot/ggtools/fang/db/migrations/v11__add_match_making.kt new file mode 100644 index 0000000..bcd1a62 --- /dev/null +++ b/src/main/kotlin/de/bigboot/ggtools/fang/db/migrations/v11__add_match_making.kt @@ -0,0 +1,29 @@ +@file:Suppress("ClassName", "ClassNaming", "unused", "LongMethod") + +package de.bigboot.ggtools.fang.db.migrations + +import org.flywaydb.core.api.migration.BaseJavaMigration +import org.flywaydb.core.api.migration.Context + +class V11__add_match_making : BaseJavaMigration() { + override fun migrate(context: Context) { + context.connection.prepareStatement(""" + |alter table Users + | drop column skill; + """.trimMargin()).execute() + + context.connection.prepareStatement(""" + |create table if not exists UsersRating + |( + | id binary(16) not null + | primary key, + | rating double not null, + | ratingDeviation double not null, + | volatility double not null, + | user binary(16) not null, + | constraint fk_UsersRating_user_id + | foreign key (user) references Users (id) + |); + """.trimMargin()).execute() + } +} From 897bd3cc16a3c805e24327f33a9d816c618b1bad Mon Sep 17 00:00:00 2001 From: Arthur Melton Date: Tue, 16 May 2023 19:34:23 -0400 Subject: [PATCH 03/28] User controlls the rating --- .../kotlin/de/bigboot/ggtools/fang/db/Users.kt | 4 +++- .../de/bigboot/ggtools/fang/db/UsersRating.kt | 5 +---- .../fang/db/migrations/v11__add_match_making.kt | 16 ++++++++++++---- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/db/Users.kt b/src/main/kotlin/de/bigboot/ggtools/fang/db/Users.kt index 9cb034b..a28ad67 100644 --- a/src/main/kotlin/de/bigboot/ggtools/fang/db/Users.kt +++ b/src/main/kotlin/de/bigboot/ggtools/fang/db/Users.kt @@ -12,9 +12,11 @@ class User(id: EntityID) : UUIDEntity(id) { var snowflake by Users.snowflake var groups by Group via UsersGroups - val skill by UserRating referrersOn UsersRating.user + var rating by UserRating referencedOn Users.rating } object Users : UUIDTable() { val snowflake = long("snowflake") + + val rating = reference("rating", UsersRating) } diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/db/UsersRating.kt b/src/main/kotlin/de/bigboot/ggtools/fang/db/UsersRating.kt index 2ff4c2b..9f9ede8 100644 --- a/src/main/kotlin/de/bigboot/ggtools/fang/db/UsersRating.kt +++ b/src/main/kotlin/de/bigboot/ggtools/fang/db/UsersRating.kt @@ -9,17 +9,14 @@ import java.util.* class UserRating(id: EntityID) : UUIDEntity(id) { companion object : UUIDEntityClass(UsersRating) - var rating by UsersRating.rating var ratingDeviation by UsersRating.ratingDeviation var volatility by UsersRating.volatility - var user by User referencedOn UsersRating.user + val rating by User referrersOn Users.rating } object UsersRating : UUIDTable() { val rating = double("rating") val ratingDeviation = double("ratingDeviation") val volatility = double("volatility") - - val user = reference("user", Users) } diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/db/migrations/v11__add_match_making.kt b/src/main/kotlin/de/bigboot/ggtools/fang/db/migrations/v11__add_match_making.kt index bcd1a62..1288249 100644 --- a/src/main/kotlin/de/bigboot/ggtools/fang/db/migrations/v11__add_match_making.kt +++ b/src/main/kotlin/de/bigboot/ggtools/fang/db/migrations/v11__add_match_making.kt @@ -12,6 +12,17 @@ class V11__add_match_making : BaseJavaMigration() { | drop column skill; """.trimMargin()).execute() + context.connection.prepareStatement(""" + |alter table Users + | add rating binary(16); + """.trimMargin()).execute() + + context.connection.prepareStatement(""" + |alter table Users + | add constraint fk_Users_userRating_id + | foreign key (rating) references UsersRating (id); + """.trimMargin()).execute() + context.connection.prepareStatement(""" |create table if not exists UsersRating |( @@ -19,10 +30,7 @@ class V11__add_match_making : BaseJavaMigration() { | primary key, | rating double not null, | ratingDeviation double not null, - | volatility double not null, - | user binary(16) not null, - | constraint fk_UsersRating_user_id - | foreign key (user) references Users (id) + | volatility double not null |); """.trimMargin()).execute() } From 5ec3fc4d1d92be57fe585f8b31b7d251842f83bb Mon Sep 17 00:00:00 2001 From: Arthur Melton Date: Tue, 16 May 2023 19:57:14 -0400 Subject: [PATCH 04/28] Create RatingService --- .../de/bigboot/ggtools/fang/db/UsersRating.kt | 3 +- .../ggtools/fang/service/RatingService.kt | 10 ++++++ .../ggtools/fang/service/RatingServiceImpl.kt | 31 +++++++++++++++++++ 3 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 src/main/kotlin/de/bigboot/ggtools/fang/service/RatingService.kt create mode 100644 src/main/kotlin/de/bigboot/ggtools/fang/service/RatingServiceImpl.kt diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/db/UsersRating.kt b/src/main/kotlin/de/bigboot/ggtools/fang/db/UsersRating.kt index 9f9ede8..79634db 100644 --- a/src/main/kotlin/de/bigboot/ggtools/fang/db/UsersRating.kt +++ b/src/main/kotlin/de/bigboot/ggtools/fang/db/UsersRating.kt @@ -9,10 +9,11 @@ import java.util.* class UserRating(id: EntityID) : UUIDEntity(id) { companion object : UUIDEntityClass(UsersRating) + var rating by UsersRating.rating var ratingDeviation by UsersRating.ratingDeviation var volatility by UsersRating.volatility - val rating by User referrersOn Users.rating + val userRating by User referrersOn Users.rating } object UsersRating : UUIDTable() { diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/service/RatingService.kt b/src/main/kotlin/de/bigboot/ggtools/fang/service/RatingService.kt new file mode 100644 index 0000000..eeab014 --- /dev/null +++ b/src/main/kotlin/de/bigboot/ggtools/fang/service/RatingService.kt @@ -0,0 +1,10 @@ +package de.bigboot.ggtools.fang.service + +import de.bigboot.ggtools.fang.db.UserRating +import org.goochjs.glicko2.Rating; + +interface RatingService { + fun newRating(): Boolean + + fun findUser(snowflake: Long): UserRating? +} diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/service/RatingServiceImpl.kt b/src/main/kotlin/de/bigboot/ggtools/fang/service/RatingServiceImpl.kt new file mode 100644 index 0000000..51e8b38 --- /dev/null +++ b/src/main/kotlin/de/bigboot/ggtools/fang/service/RatingServiceImpl.kt @@ -0,0 +1,31 @@ +package de.bigboot.ggtools.fang.service + +import de.bigboot.ggtools.fang.Config +import de.bigboot.ggtools.fang.db.* +import org.goochjs.glicko2.Rating; +import org.goochjs.glicko2.RatingCalculator; +import org.jetbrains.exposed.sql.* +import org.jetbrains.exposed.sql.transactions.transaction +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject +import java.util.* + +class RatingServiceImpl : RatingService, KoinComponent { + private val database: Database by inject() + + override fun newRating() = transaction(database) { + val ratingSystem = RatingCalculator(0.06, 0.5); + + UserRating.new { + this.rating = ratingSystem.getDefaultRating() + this.ratingDeviation = ratingSystem.getDefaultRatingDeviation() + this.volatility = ratingSystem.getDefaultVolatility() + } + + return@transaction true + } + + override fun findUser(snowflake: Long): UserRating? { + return User.find { Users.snowflake eq snowflake }.firstOrNull()?.rating; + } +} From 43857417f06b7c776e1bed2d66ed21854536fb82 Mon Sep 17 00:00:00 2001 From: Arthur Melton Date: Tue, 16 May 2023 22:49:53 -0400 Subject: [PATCH 05/28] ratingService starting to work --- .../de/bigboot/ggtools/fang/db/Users.kt | 4 +-- .../de/bigboot/ggtools/fang/db/UsersRating.kt | 2 +- .../bigboot/ggtools/fang/di/serviceModule.kt | 1 + .../fang/service/QueueMessageService.kt | 7 +++++ .../ggtools/fang/service/RatingService.kt | 3 --- .../ggtools/fang/service/RatingServiceImpl.kt | 26 ++++++++++++------- 6 files changed, 27 insertions(+), 16 deletions(-) diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/db/Users.kt b/src/main/kotlin/de/bigboot/ggtools/fang/db/Users.kt index a28ad67..2c27028 100644 --- a/src/main/kotlin/de/bigboot/ggtools/fang/db/Users.kt +++ b/src/main/kotlin/de/bigboot/ggtools/fang/db/Users.kt @@ -12,11 +12,11 @@ class User(id: EntityID) : UUIDEntity(id) { var snowflake by Users.snowflake var groups by Group via UsersGroups - var rating by UserRating referencedOn Users.rating + var rating by UserRating optionalReferencedOn Users.rating } object Users : UUIDTable() { val snowflake = long("snowflake") - val rating = reference("rating", UsersRating) + val rating = reference("rating", UsersRating).nullable() } diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/db/UsersRating.kt b/src/main/kotlin/de/bigboot/ggtools/fang/db/UsersRating.kt index 79634db..f01ea6f 100644 --- a/src/main/kotlin/de/bigboot/ggtools/fang/db/UsersRating.kt +++ b/src/main/kotlin/de/bigboot/ggtools/fang/db/UsersRating.kt @@ -13,7 +13,7 @@ class UserRating(id: EntityID) : UUIDEntity(id) { var ratingDeviation by UsersRating.ratingDeviation var volatility by UsersRating.volatility - val userRating by User referrersOn Users.rating + val userRating by User optionalReferrersOn Users.rating } object UsersRating : UUIDTable() { diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/di/serviceModule.kt b/src/main/kotlin/de/bigboot/ggtools/fang/di/serviceModule.kt index 90db579..b69e358 100644 --- a/src/main/kotlin/de/bigboot/ggtools/fang/di/serviceModule.kt +++ b/src/main/kotlin/de/bigboot/ggtools/fang/di/serviceModule.kt @@ -12,6 +12,7 @@ val serviceModule = module { single { ServerServiceImpl() } bind ServerService::class single { ChangelogServiceImpl() } bind ChangelogService::class single { PreferencesServiceImpl() } bind PreferencesService::class + single { RatingServiceImpl() } bind RatingService::class single { SetupGuildServiceImpl() } binds arrayOf(AutostartService::class, SetupGuildService::class) single { CommandsService() } bind AutostartService::class diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt b/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt index 7624ae7..3de5eea 100644 --- a/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt +++ b/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt @@ -72,6 +72,7 @@ class QueueMessageService : AutostartService, KoinComponent { private val setupGuildService by inject() private val preferencesService by inject() private val serverService by inject() + private val ratingService by inject() private val matchReuests = hashMapOf() @@ -142,6 +143,12 @@ class QueueMessageService : AutostartService, KoinComponent { { val request = matchReuests[matchId] ?: return + /* + request.pop.allPlayers.forEach { + println("${ratingService.findUser(it)}"); + } + */ + if(request.state != MatchState.MATCH_READY) return request.message.editCompat { diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/service/RatingService.kt b/src/main/kotlin/de/bigboot/ggtools/fang/service/RatingService.kt index eeab014..53823bd 100644 --- a/src/main/kotlin/de/bigboot/ggtools/fang/service/RatingService.kt +++ b/src/main/kotlin/de/bigboot/ggtools/fang/service/RatingService.kt @@ -1,10 +1,7 @@ package de.bigboot.ggtools.fang.service import de.bigboot.ggtools.fang.db.UserRating -import org.goochjs.glicko2.Rating; interface RatingService { - fun newRating(): Boolean - fun findUser(snowflake: Long): UserRating? } diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/service/RatingServiceImpl.kt b/src/main/kotlin/de/bigboot/ggtools/fang/service/RatingServiceImpl.kt index 51e8b38..41ad4aa 100644 --- a/src/main/kotlin/de/bigboot/ggtools/fang/service/RatingServiceImpl.kt +++ b/src/main/kotlin/de/bigboot/ggtools/fang/service/RatingServiceImpl.kt @@ -13,19 +13,25 @@ import java.util.* class RatingServiceImpl : RatingService, KoinComponent { private val database: Database by inject() - override fun newRating() = transaction(database) { - val ratingSystem = RatingCalculator(0.06, 0.5); + override fun findUser(snowflake: Long) = transaction(database) { + var user = User.find { Users.snowflake eq snowflake }.firstOrNull(); - UserRating.new { - this.rating = ratingSystem.getDefaultRating() - this.ratingDeviation = ratingSystem.getDefaultRatingDeviation() - this.volatility = ratingSystem.getDefaultVolatility() + if (user == null) { + return@transaction null } - return@transaction true - } + if (user.rating == null) { + val ratingSystem = RatingCalculator(0.06, 0.5); + + val rating = UserRating.new { + this.rating = ratingSystem.getDefaultRating() + this.ratingDeviation = ratingSystem.getDefaultRatingDeviation() + this.volatility = ratingSystem.getDefaultVolatility() + } - override fun findUser(snowflake: Long): UserRating? { - return User.find { Users.snowflake eq snowflake }.firstOrNull()?.rating; + user.rating = rating; + + } + return@transaction user.rating } } From e6dabb87fb6b0598ef85446b4a48b9be2b43775c Mon Sep 17 00:00:00 2001 From: Arthur Melton Date: Wed, 17 May 2023 07:54:10 -0400 Subject: [PATCH 06/28] move user rating to be separet from user --- .../de/bigboot/ggtools/fang/db/Users.kt | 4 ---- .../de/bigboot/ggtools/fang/db/UsersRating.kt | 4 ++-- .../db/migrations/v11__add_match_making.kt | 12 +----------- .../ggtools/fang/service/RatingServiceImpl.kt | 19 +++++-------------- 4 files changed, 8 insertions(+), 31 deletions(-) diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/db/Users.kt b/src/main/kotlin/de/bigboot/ggtools/fang/db/Users.kt index 2c27028..80c7433 100644 --- a/src/main/kotlin/de/bigboot/ggtools/fang/db/Users.kt +++ b/src/main/kotlin/de/bigboot/ggtools/fang/db/Users.kt @@ -11,12 +11,8 @@ class User(id: EntityID) : UUIDEntity(id) { var snowflake by Users.snowflake var groups by Group via UsersGroups - - var rating by UserRating optionalReferencedOn Users.rating } object Users : UUIDTable() { val snowflake = long("snowflake") - - val rating = reference("rating", UsersRating).nullable() } diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/db/UsersRating.kt b/src/main/kotlin/de/bigboot/ggtools/fang/db/UsersRating.kt index f01ea6f..b95f92f 100644 --- a/src/main/kotlin/de/bigboot/ggtools/fang/db/UsersRating.kt +++ b/src/main/kotlin/de/bigboot/ggtools/fang/db/UsersRating.kt @@ -9,14 +9,14 @@ import java.util.* class UserRating(id: EntityID) : UUIDEntity(id) { companion object : UUIDEntityClass(UsersRating) + var snowflake by UsersRating.snowflake var rating by UsersRating.rating var ratingDeviation by UsersRating.ratingDeviation var volatility by UsersRating.volatility - - val userRating by User optionalReferrersOn Users.rating } object UsersRating : UUIDTable() { + val snowflake = long("snowflake") val rating = double("rating") val ratingDeviation = double("ratingDeviation") val volatility = double("volatility") diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/db/migrations/v11__add_match_making.kt b/src/main/kotlin/de/bigboot/ggtools/fang/db/migrations/v11__add_match_making.kt index 1288249..c531194 100644 --- a/src/main/kotlin/de/bigboot/ggtools/fang/db/migrations/v11__add_match_making.kt +++ b/src/main/kotlin/de/bigboot/ggtools/fang/db/migrations/v11__add_match_making.kt @@ -12,22 +12,12 @@ class V11__add_match_making : BaseJavaMigration() { | drop column skill; """.trimMargin()).execute() - context.connection.prepareStatement(""" - |alter table Users - | add rating binary(16); - """.trimMargin()).execute() - - context.connection.prepareStatement(""" - |alter table Users - | add constraint fk_Users_userRating_id - | foreign key (rating) references UsersRating (id); - """.trimMargin()).execute() - context.connection.prepareStatement(""" |create table if not exists UsersRating |( | id binary(16) not null | primary key, + | snowflake long not null, | rating double not null, | ratingDeviation double not null, | volatility double not null diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/service/RatingServiceImpl.kt b/src/main/kotlin/de/bigboot/ggtools/fang/service/RatingServiceImpl.kt index 41ad4aa..95ffb78 100644 --- a/src/main/kotlin/de/bigboot/ggtools/fang/service/RatingServiceImpl.kt +++ b/src/main/kotlin/de/bigboot/ggtools/fang/service/RatingServiceImpl.kt @@ -14,24 +14,15 @@ class RatingServiceImpl : RatingService, KoinComponent { private val database: Database by inject() override fun findUser(snowflake: Long) = transaction(database) { - var user = User.find { Users.snowflake eq snowflake }.firstOrNull(); + val ratingSystem = RatingCalculator(0.06, 0.5); - if (user == null) { - return@transaction null - } + return@transaction UserRating.find { UsersRating.snowflake eq snowflake } + .firstOrNull() ?: UserRating.new { - if (user.rating == null) { - val ratingSystem = RatingCalculator(0.06, 0.5); - - val rating = UserRating.new { + this.snowflake = snowflake this.rating = ratingSystem.getDefaultRating() this.ratingDeviation = ratingSystem.getDefaultRatingDeviation() this.volatility = ratingSystem.getDefaultVolatility() - } - - user.rating = rating; - - } - return@transaction user.rating + }; } } From 94d78fce441d1e16ae2a6c1a3754ad60ad3905b3 Mon Sep 17 00:00:00 2001 From: Arthur Melton Date: Wed, 17 May 2023 18:42:31 -0400 Subject: [PATCH 07/28] update results --- .../fang/service/QueueMessageService.kt | 2 + .../ggtools/fang/service/RatingService.kt | 4 ++ .../ggtools/fang/service/RatingServiceImpl.kt | 43 ++++++++++++++++++- 3 files changed, 47 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt b/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt index 3de5eea..52cb02d 100644 --- a/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt +++ b/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt @@ -147,6 +147,8 @@ class QueueMessageService : AutostartService, KoinComponent { request.pop.allPlayers.forEach { println("${ratingService.findUser(it)}"); } + + ratingService.addResult(listOf(0), listOf(1)); */ if(request.state != MatchState.MATCH_READY) return diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/service/RatingService.kt b/src/main/kotlin/de/bigboot/ggtools/fang/service/RatingService.kt index 53823bd..28e0ac0 100644 --- a/src/main/kotlin/de/bigboot/ggtools/fang/service/RatingService.kt +++ b/src/main/kotlin/de/bigboot/ggtools/fang/service/RatingService.kt @@ -3,5 +3,9 @@ package de.bigboot.ggtools.fang.service import de.bigboot.ggtools.fang.db.UserRating interface RatingService { + fun findUser(snowflake: Long): UserRating? + + fun addResult(winning: List, loosing: List) + } diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/service/RatingServiceImpl.kt b/src/main/kotlin/de/bigboot/ggtools/fang/service/RatingServiceImpl.kt index 95ffb78..8b8f9b8 100644 --- a/src/main/kotlin/de/bigboot/ggtools/fang/service/RatingServiceImpl.kt +++ b/src/main/kotlin/de/bigboot/ggtools/fang/service/RatingServiceImpl.kt @@ -9,13 +9,13 @@ import org.jetbrains.exposed.sql.transactions.transaction import org.koin.core.component.KoinComponent import org.koin.core.component.inject import java.util.* +import org.topalski.teams.*; class RatingServiceImpl : RatingService, KoinComponent { private val database: Database by inject() + private val ratingSystem = RatingCalculator(0.06, 0.5); override fun findUser(snowflake: Long) = transaction(database) { - val ratingSystem = RatingCalculator(0.06, 0.5); - return@transaction UserRating.find { UsersRating.snowflake eq snowflake } .firstOrNull() ?: UserRating.new { @@ -25,4 +25,43 @@ class RatingServiceImpl : RatingService, KoinComponent { this.volatility = ratingSystem.getDefaultVolatility() }; } + + override fun addResult(winning: List, loosing: List) = transaction(database) { + var winning = winning.map{findUser(it)}; + var loosing = loosing.map{findUser(it)}; + + var glickoWinning = winning.map{Rating(it.snowflake.toString(), it.rating, it.ratingDeviation, it.volatility)}.toMutableSet(); + var glickoLoosing = loosing.map{Rating(it.snowflake.toString(), it.rating, it.ratingDeviation, it.volatility)}.toMutableSet(); + + var game = TeamIndividualUpdate(); + + var teamWinning = Team(mutableSetOf()); // for some reason when you put glickWinning right into this it does not actually set it + var teamLoosing = Team(mutableSetOf()); + + glickoWinning.forEach { + teamWinning.addTeamPlayer(it) + } + glickoLoosing.forEach { + teamLoosing.addTeamPlayer(it) + } + + game.addResult(teamWinning, teamLoosing); + game.updateRating(ratingSystem); + + var i = 0; + glickoWinning.forEach { + winning[i].rating = it.getRating(); + winning[i].ratingDeviation = it.getRatingDeviation(); + winning[i].volatility = it.getVolatility(); + i++; + } + + i = 0; + glickoLoosing.forEach { + loosing[i].rating = it.getRating(); + loosing[i].ratingDeviation = it.getRatingDeviation(); + loosing[i].volatility = it.getVolatility(); + i++; + } + } } From d75f9d82fefcf09eca44ae0e3570adf6f4bb8c46 Mon Sep 17 00:00:00 2001 From: Arthur Melton Date: Wed, 17 May 2023 19:33:19 -0400 Subject: [PATCH 08/28] make teams --- .../fang/service/QueueMessageService.kt | 4 ++- .../ggtools/fang/service/RatingService.kt | 2 ++ .../ggtools/fang/service/RatingServiceImpl.kt | 31 +++++++++++++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt b/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt index 52cb02d..1cd8454 100644 --- a/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt +++ b/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt @@ -148,7 +148,9 @@ class QueueMessageService : AutostartService, KoinComponent { println("${ratingService.findUser(it)}"); } - ratingService.addResult(listOf(0), listOf(1)); + ratingService.addResult(listOf(0, 1), listOf(2, 3)); + + println("${ratingService.makeTeams(listOf(0, 1, 2, 3))}") */ if(request.state != MatchState.MATCH_READY) return diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/service/RatingService.kt b/src/main/kotlin/de/bigboot/ggtools/fang/service/RatingService.kt index 28e0ac0..c02d8f4 100644 --- a/src/main/kotlin/de/bigboot/ggtools/fang/service/RatingService.kt +++ b/src/main/kotlin/de/bigboot/ggtools/fang/service/RatingService.kt @@ -8,4 +8,6 @@ interface RatingService { fun addResult(winning: List, loosing: List) + fun makeTeams(players: List): Pair, List> + } diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/service/RatingServiceImpl.kt b/src/main/kotlin/de/bigboot/ggtools/fang/service/RatingServiceImpl.kt index 8b8f9b8..4a94548 100644 --- a/src/main/kotlin/de/bigboot/ggtools/fang/service/RatingServiceImpl.kt +++ b/src/main/kotlin/de/bigboot/ggtools/fang/service/RatingServiceImpl.kt @@ -64,4 +64,35 @@ class RatingServiceImpl : RatingService, KoinComponent { i++; } } + + override fun makeTeams(players: List): Pair, List> { + var ratedTeams = players.map{findUser(it)}; + + var createdTeams: MutableList> = mutableListOf(); + + val halfSize = ratedTeams.size/2; + val halfScore = ratedTeams.map{it.rating}.sum()/2; + + ratedTeams.forEach { + val current = it; + for (i in 0 until createdTeams.size) { + var new = createdTeams[i].toMutableList(); + if (new.size != halfSize && new.map{it.rating}.sum()+current.rating <= halfScore) { + new.add(current); + createdTeams.add(new); + } + } + createdTeams.add(mutableListOf(it)); + } + + val teamOne = createdTeams.filter {it.size == halfSize}.sortedBy {it.map{it.rating}.sum()}.last(); + + var teamTwo = ratedTeams.toMutableList(); + + teamOne.forEach { + teamTwo.remove(it) + } + + return Pair(teamOne.map{it.snowflake}, teamTwo.map{it.snowflake}) + } } From ccf6dc16a2d9511f60cea43b529050cda49b60f0 Mon Sep 17 00:00:00 2001 From: Arthur Melton Date: Wed, 17 May 2023 19:49:04 -0400 Subject: [PATCH 09/28] Tell the teams --- .../ggtools/fang/service/QueueMessageService.kt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt b/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt index 1cd8454..a4e8509 100644 --- a/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt +++ b/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt @@ -56,6 +56,7 @@ data class MatchRequest( var creatures: Triple = Triple(null, null, null), var state: MatchState = MatchState.QUEUE_POP, var timeToJoin: Instant? = null, + var teams: Pair, List>? = null, ) { fun getMapVoteResult() = mapVotes .values @@ -149,10 +150,10 @@ class QueueMessageService : AutostartService, KoinComponent { } ratingService.addResult(listOf(0, 1), listOf(2, 3)); - - println("${ratingService.makeTeams(listOf(0, 1, 2, 3))}") */ + request.teams = ratingService.makeTeams(request.pop.allPlayers.toList()); + if(request.state != MatchState.MATCH_READY) return request.message.editCompat { @@ -178,7 +179,7 @@ class QueueMessageService : AutostartService, KoinComponent { if(request.serverSetupPlayer != null) { val value = when { - request.openUrl != null -> "`open ${request.openUrl}`\nby <@${request.serverSetupPlayer!!.asLong()}>" + request.openUrl != null -> "`open ${request.openUrl}?team=0`\n`open ${request.openUrl}?team=1`\nby <@${request.serverSetupPlayer!!.asLong()}>" else -> "Being set up by <@${request.serverSetupPlayer!!.asLong()}>" } addField("Server", value, false) @@ -186,7 +187,8 @@ class QueueMessageService : AutostartService, KoinComponent { components.add(ButtonMatchSetupServer(matchId)) } - addField("Players", request.pop.allPlayers.joinToString(" ") { "<@$it>" }, false) + addField("Team 1", request.teams!!.first.joinToString(" ") { "<@$it>" }, false) + addField("Team 2", request.teams!!.second.joinToString(" ") { "<@$it>" }, false) if(request.timeToJoin != null) { addField("Time to join", when { From 980f81194d7a9d83ecd99c4cd6c6b61d0e93549c Mon Sep 17 00:00:00 2001 From: Arthur Melton Date: Wed, 17 May 2023 19:59:01 -0400 Subject: [PATCH 10/28] team differential --- .../de/bigboot/ggtools/fang/service/QueueMessageService.kt | 2 ++ .../kotlin/de/bigboot/ggtools/fang/service/RatingService.kt | 2 ++ .../de/bigboot/ggtools/fang/service/RatingServiceImpl.kt | 4 ++++ 3 files changed, 8 insertions(+) diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt b/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt index a4e8509..66760bc 100644 --- a/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt +++ b/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt @@ -177,6 +177,8 @@ class QueueMessageService : AutostartService, KoinComponent { addField("Map", Maps.fromId(request.getMapVoteResult())!!.name, true) + addField("Team Differantial", String.format("%.1f%%", ratingService.teamDifferential(request.teams!!)*100-100), true) + if(request.serverSetupPlayer != null) { val value = when { request.openUrl != null -> "`open ${request.openUrl}?team=0`\n`open ${request.openUrl}?team=1`\nby <@${request.serverSetupPlayer!!.asLong()}>" diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/service/RatingService.kt b/src/main/kotlin/de/bigboot/ggtools/fang/service/RatingService.kt index c02d8f4..fab7e5a 100644 --- a/src/main/kotlin/de/bigboot/ggtools/fang/service/RatingService.kt +++ b/src/main/kotlin/de/bigboot/ggtools/fang/service/RatingService.kt @@ -10,4 +10,6 @@ interface RatingService { fun makeTeams(players: List): Pair, List> + fun teamDifferential(teams: Pair, List>): Double + } diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/service/RatingServiceImpl.kt b/src/main/kotlin/de/bigboot/ggtools/fang/service/RatingServiceImpl.kt index 4a94548..bc5c537 100644 --- a/src/main/kotlin/de/bigboot/ggtools/fang/service/RatingServiceImpl.kt +++ b/src/main/kotlin/de/bigboot/ggtools/fang/service/RatingServiceImpl.kt @@ -95,4 +95,8 @@ class RatingServiceImpl : RatingService, KoinComponent { return Pair(teamOne.map{it.snowflake}, teamTwo.map{it.snowflake}) } + + override fun teamDifferential(teams: Pair, List>): Double { + return teams.first.map{findUser(it).rating}.sum()/teams.second.map{findUser(it).rating}.sum() + } } From 387703cf55c9788d2ae8d1f6d5c29e5d2e571fa2 Mon Sep 17 00:00:00 2001 From: arthurmelton Date: Thu, 18 May 2023 15:40:50 -0400 Subject: [PATCH 11/28] Add changes to changelog --- .../de/bigboot/ggtools/fang/service/ChangelogServiceImpl.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/service/ChangelogServiceImpl.kt b/src/main/kotlin/de/bigboot/ggtools/fang/service/ChangelogServiceImpl.kt index df5bd2f..80e9c7d 100644 --- a/src/main/kotlin/de/bigboot/ggtools/fang/service/ChangelogServiceImpl.kt +++ b/src/main/kotlin/de/bigboot/ggtools/fang/service/ChangelogServiceImpl.kt @@ -83,6 +83,10 @@ private val CHANGELOG = listOf( ), listOf( "Add ability to have a time to join" + ), + listOf( + "Added raking system", + "Added matchmaking" ) ) From 69a94baf9307d89e17e14b13949066f92eac4310 Mon Sep 17 00:00:00 2001 From: Arthur Melton Date: Fri, 26 May 2023 14:43:41 -0400 Subject: [PATCH 12/28] Add a import command --- .../de/bigboot/ggtools/fang/commands/Root.kt | 2 + .../ggtools/fang/commands/rating/Rating.kt | 65 +++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 src/main/kotlin/de/bigboot/ggtools/fang/commands/rating/Rating.kt diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/commands/Root.kt b/src/main/kotlin/de/bigboot/ggtools/fang/commands/Root.kt index 6a11311..038dedb 100644 --- a/src/main/kotlin/de/bigboot/ggtools/fang/commands/Root.kt +++ b/src/main/kotlin/de/bigboot/ggtools/fang/commands/Root.kt @@ -5,6 +5,7 @@ import de.bigboot.ggtools.fang.CommandGroupSpec import de.bigboot.ggtools.fang.commands.admin.Admin import de.bigboot.ggtools.fang.commands.queue.Queue import de.bigboot.ggtools.fang.commands.server.Server +import de.bigboot.ggtools.fang.commands.rating.Rating import de.bigboot.ggtools.fang.utils.createEmbedCompat import de.bigboot.ggtools.fang.utils.formatCommandHelp import de.bigboot.ggtools.fang.utils.formatCommandTree @@ -15,6 +16,7 @@ class Root : CommandGroupSpec("", "") { group(Admin()) group(Queue()) group(Server()) + group(Rating()) command("help", "show this help") { onCall { diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/commands/rating/Rating.kt b/src/main/kotlin/de/bigboot/ggtools/fang/commands/rating/Rating.kt new file mode 100644 index 0000000..14e30d9 --- /dev/null +++ b/src/main/kotlin/de/bigboot/ggtools/fang/commands/rating/Rating.kt @@ -0,0 +1,65 @@ +package de.bigboot.ggtools.fang.commands.rating + +import de.bigboot.ggtools.fang.CommandGroupBuilder +import de.bigboot.ggtools.fang.CommandGroupSpec +import de.bigboot.ggtools.fang.Config +import de.bigboot.ggtools.fang.service.RatingService +import de.bigboot.ggtools.fang.utils.* +import discord4j.common.util.Snowflake +import kotlinx.coroutines.reactive.awaitSingle +import okhttp3.Request +import okhttp3.OkHttpClient +import org.koin.core.component.inject + +class Rating : CommandGroupSpec("rating", "Commands for ratings") { + private val ratingService by inject() + + override val build: CommandGroupBuilder.() -> Unit = { + command("import", "import many games into the rating system") { + onCall { + val attachments = message.attachments; + if (!attachments.isEmpty()) { + var client = OkHttpClient(); + val message = channel().createMessageCompat { + addEmbedCompat { + description("Adding scores") + } + }.awaitSingle() + attachments.forEach { + val request = Request.Builder().url(it.url).build(); + val response = client.newCall(request).execute().body(); + if (response != null) { + response.string().lines().forEach { + if (it.trim() != "") { + val split = it.split(" ").map{it.toLong()}; + val half = split.size/2; + ratingService.addResult(split.slice(0..half-1), split.slice(half..split.size-1)); + } + } + } + else { + channel().createMessageCompat { + addEmbedCompat { + description("Failed to read the contents of your attachment, please try in a few minuets") + } + }.awaitSingle() + return@onCall + } + } + message.editCompat { + addEmbedCompat { + description("Added all of the results!") + } + }.awaitSingle() + } + else { + channel().createMessageCompat { + addEmbedCompat { + description("No attachments were provided") + } + }.awaitSingle() + } + } + } + } +} From d3ac93b8b80e80077f9bc7e5a57e5180d2e07b51 Mon Sep 17 00:00:00 2001 From: Arthur Melton Date: Sat, 27 May 2023 12:11:09 -0400 Subject: [PATCH 13/28] Add a configuration item for the rating --- .../kotlin/de/bigboot/ggtools/fang/Config.kt | 1 + .../fang/service/QueueMessageService.kt | 19 ++++++++++++++----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/Config.kt b/src/main/kotlin/de/bigboot/ggtools/fang/Config.kt index a523ecf..4560afc 100644 --- a/src/main/kotlin/de/bigboot/ggtools/fang/Config.kt +++ b/src/main/kotlin/de/bigboot/ggtools/fang/Config.kt @@ -50,6 +50,7 @@ data class BotConfig( val queues: List = listOf(), val highscore_channel: String = "", val time_to_join: Int = 600, + val rating: Boolean = true, ) @JsonClass(generateAdapter = true) diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt b/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt index 66760bc..e4349a2 100644 --- a/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt +++ b/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt @@ -177,11 +177,18 @@ class QueueMessageService : AutostartService, KoinComponent { addField("Map", Maps.fromId(request.getMapVoteResult())!!.name, true) - addField("Team Differantial", String.format("%.1f%%", ratingService.teamDifferential(request.teams!!)*100-100), true) + if (Config.bot.rating) { + addField("Team Differantial", String.format("%.1f%%", ratingService.teamDifferential(request.teams!!)*100-100), true) + } if(request.serverSetupPlayer != null) { val value = when { - request.openUrl != null -> "`open ${request.openUrl}?team=0`\n`open ${request.openUrl}?team=1`\nby <@${request.serverSetupPlayer!!.asLong()}>" + request.openUrl != null -> if (Config.bot.rating) { + "`open ${request.openUrl}?team=0`\n`open ${request.openUrl}?team=1`\nby <@${request.serverSetupPlayer!!.asLong()}>" + } + else { + "`open ${request.openUrl}`\nby <@${request.serverSetupPlayer!!.asLong()}>" + } else -> "Being set up by <@${request.serverSetupPlayer!!.asLong()}>" } addField("Server", value, false) @@ -189,9 +196,11 @@ class QueueMessageService : AutostartService, KoinComponent { components.add(ButtonMatchSetupServer(matchId)) } - addField("Team 1", request.teams!!.first.joinToString(" ") { "<@$it>" }, false) - addField("Team 2", request.teams!!.second.joinToString(" ") { "<@$it>" }, false) - + if (Config.bot.rating) { + addField("Team 1", request.teams!!.first.joinToString(" ") { "<@$it>" }, false) + addField("Team 2", request.teams!!.second.joinToString(" ") { "<@$it>" }, false) + } + if(request.timeToJoin != null) { addField("Time to join", when { Instant.now().compareTo(request.timeToJoin) >= 0 -> "If someone is still not in please report them." From 8cac106bfa3076c9c8f06f175a6af9cd44059f05 Mon Sep 17 00:00:00 2001 From: Arthur Melton Date: Sun, 28 May 2023 10:31:53 -0400 Subject: [PATCH 14/28] add a button to set a game as unranked --- .../kotlin/de/bigboot/ggtools/fang/Config.kt | 1 + .../ggtools/fang/commands/rating/Rating.kt | 24 ++++++++++ .../components/queue/ButtonMatchUnranked.kt | 18 +++++++ .../fang/service/QueueMessageService.kt | 47 +++++++++++++++++++ 4 files changed, 90 insertions(+) create mode 100644 src/main/kotlin/de/bigboot/ggtools/fang/components/queue/ButtonMatchUnranked.kt diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/Config.kt b/src/main/kotlin/de/bigboot/ggtools/fang/Config.kt index 4560afc..d0b125b 100644 --- a/src/main/kotlin/de/bigboot/ggtools/fang/Config.kt +++ b/src/main/kotlin/de/bigboot/ggtools/fang/Config.kt @@ -13,6 +13,7 @@ data class EmojisConfig( val deny: String = "\uD83D\uDC4E", val match_finished: String = "\uD83C\uDFC1", val match_drop: String = "\uD83D\uDC4E", + val match_unranked: String = "\uD83C\uDFC5", val queue_empty: String = "\uD83D\uDE22", val join_queue: String = "\uD83D\uDC4D", val leave_queue: String = "\uD83D\uDC4E", diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/commands/rating/Rating.kt b/src/main/kotlin/de/bigboot/ggtools/fang/commands/rating/Rating.kt index 14e30d9..47dbc1b 100644 --- a/src/main/kotlin/de/bigboot/ggtools/fang/commands/rating/Rating.kt +++ b/src/main/kotlin/de/bigboot/ggtools/fang/commands/rating/Rating.kt @@ -61,5 +61,29 @@ class Rating : CommandGroupSpec("rating", "Commands for ratings") { } } } + + command("maketeam", "make a team") { + arg("one", "") + arg("two", "") + arg("three", "") + arg("four", "") + arg("five","") + arg("six", "") + arg("seven", "") + arg("eight", "") + arg("nine", "") + arg("ten", "") + + onCall { + var teams = ratingService.makeTeams(listOf(args["one"].toLong(), args["two"].toLong(), args["three"].toLong(), args["four"].toLong(), args["five"].toLong(), args["six"].toLong(), args["seven"].toLong(), args["eight"].toLong(), args["nine"].toLong(), args["ten"].toLong())); + var diff = ratingService.teamDifferential(teams); + + channel().createMessageCompat { + addEmbedCompat { + description("team one: <@${teams.first.joinToString("> <@")}>\nteam two: <@${teams.second.joinToString("> <@")}>\ndiff: ${diff}") + } + }.awaitSingle() + } + } } } diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/components/queue/ButtonMatchUnranked.kt b/src/main/kotlin/de/bigboot/ggtools/fang/components/queue/ButtonMatchUnranked.kt new file mode 100644 index 0000000..876346d --- /dev/null +++ b/src/main/kotlin/de/bigboot/ggtools/fang/components/queue/ButtonMatchUnranked.kt @@ -0,0 +1,18 @@ +package de.bigboot.ggtools.fang.components.queue + +import de.bigboot.ggtools.fang.Config +import de.bigboot.ggtools.fang.utils.asReaction +import discord4j.core.`object`.component.ActionComponent +import discord4j.core.`object`.component.Button +import java.util.UUID + +data class ButtonMatchUnranked(val matchId: UUID, val final: Boolean): QueueComponentSpec { + override fun id() = "$PREFIX:${matchId}:${final}" + override fun component(): ActionComponent = Button.primary(id(), Config.emojis.match_unranked.asReaction(), "Set unranked") + + companion object { + private val PREFIX = "${QueueComponentSpec.ID_PREFIX}:BUTTON:MATCH_UNRANKED" + private val ID_REGEX = Regex("$PREFIX:([^:]+):(true|false)") + fun parse(id: String) = ID_REGEX.find(id)?.destructured?.let { (matchId, final) -> ButtonMatchUnranked(UUID.fromString(matchId), final.toBoolean()) } + } +} diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt b/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt index e4349a2..ea66151 100644 --- a/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt +++ b/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt @@ -57,6 +57,7 @@ data class MatchRequest( var state: MatchState = MatchState.QUEUE_POP, var timeToJoin: Instant? = null, var teams: Pair, List>? = null, + var ranked: Snowflake? = null ) { fun getMapVoteResult() = mapVotes .values @@ -160,6 +161,10 @@ class QueueMessageService : AutostartService, KoinComponent { addEmbedCompat { val components = mutableListOf(ButtonMatchFinished(matchId), ButtonMatchDrop(matchId)) + if (Config.bot.rating && request.ranked == null) { + components.add(ButtonMatchUnranked(matchId, false)); + } + title("Match ready!") description(""" |Everybody get ready, you've got a match. @@ -201,6 +206,10 @@ class QueueMessageService : AutostartService, KoinComponent { addField("Team 2", request.teams!!.second.joinToString(" ") { "<@$it>" }, false) } + if (request.ranked != null) { + addField("Set Unraked by", "<@${request.ranked!!.asLong()}>", false) + } + if(request.timeToJoin != null) { addField("Time to join", when { Instant.now().compareTo(request.timeToJoin) >= 0 -> "If someone is still not in please report them." @@ -590,6 +599,43 @@ class QueueMessageService : AutostartService, KoinComponent { }.awaitSafe() } + private suspend fun handleInteraction(event: ComponentInteractionEvent, button: ButtonMatchUnranked) { + val request = matchReuests[button.matchId] + + if(request == null || request.ranked != null) { + event.deferEdit().awaitSafe() + return + } + + if (!button.final) { + event + .deferReply(InteractionCallbackSpec.builder().ephemeral(true).build()) + .awaitSafe() + + updateMatchReadyMessage(button.matchId) + + event.editReplyCompat { + addEmbedCompat { + description("Are you sure you want to set it to unranked? Ill intent use of this will get you reported? If you believe this should be unranked press the set ranked button underthis, if not dismiss this message.") + } + addComponent(ActionRow.of(ButtonMatchUnranked(button.matchId, true).component())) + }.awaitSafe() + } + else { + event.deferEdit().withEphemeral(true).awaitSafe() + event.editReplyCompat { + addEmbedCompat { + description("You have set this message to be unranked, you can dissmis this message.") + } + addAllComponents(emptyList()) + }.awaitSafe() + + request.ranked = event.interaction.user.id + + updateMatchReadyMessage(button.matchId) + } + } + private suspend fun handleInteraction(event: ComponentInteractionEvent, button: SelectMatchSetupCreatures) { val request = matchReuests[button.matchId] ?: return @@ -680,6 +726,7 @@ class QueueMessageService : AutostartService, KoinComponent { ButtonMapVote.parse(event.customId)?.also { handleInteraction(event, it); return } ButtonMatchDrop.parse(event.customId)?.also { handleInteraction(event, it); return } ButtonMatchFinished.parse(event.customId)?.also { handleInteraction(event, it); return } + ButtonMatchUnranked.parse(event.customId)?.also { handleInteraction(event, it); return } ButtonMatchSetupServer.parse(event.customId)?.also { handleInteraction(event, it); return } SelectMatchSetupServer.parse(event.customId)?.also { handleInteraction(event, it); return } SelectMatchSetupCreatures.parse(event.customId)?.also { handleInteraction(event, it); return } From 32434f5538ac0b245dcb94f65fb9a1dde7883769 Mon Sep 17 00:00:00 2001 From: Arthur Melton Date: Sun, 28 May 2023 10:42:11 -0400 Subject: [PATCH 15/28] did not mean to push file but ig its staying --- .../kotlin/de/bigboot/ggtools/fang/commands/rating/Rating.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/commands/rating/Rating.kt b/src/main/kotlin/de/bigboot/ggtools/fang/commands/rating/Rating.kt index 47dbc1b..b97af34 100644 --- a/src/main/kotlin/de/bigboot/ggtools/fang/commands/rating/Rating.kt +++ b/src/main/kotlin/de/bigboot/ggtools/fang/commands/rating/Rating.kt @@ -62,6 +62,7 @@ class Rating : CommandGroupSpec("rating", "Commands for ratings") { } } + // This is just a command for testing the teams created, this will not actually be pushed into production command("maketeam", "make a team") { arg("one", "") arg("two", "") From 64a89f4a914c0fd58e6e647d1ad0e696bdb0572f Mon Sep 17 00:00:00 2001 From: Arthur Melton Date: Sun, 28 May 2023 10:44:33 -0400 Subject: [PATCH 16/28] Set the teams from 1 and 2 to 0 and 2 Some new players got confused in testing becaues they had to go to team 2 but there was only a ?team=0 and ?team=1 --- .../de/bigboot/ggtools/fang/service/QueueMessageService.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt b/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt index ea66151..a090d78 100644 --- a/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt +++ b/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt @@ -202,8 +202,8 @@ class QueueMessageService : AutostartService, KoinComponent { } if (Config.bot.rating) { - addField("Team 1", request.teams!!.first.joinToString(" ") { "<@$it>" }, false) - addField("Team 2", request.teams!!.second.joinToString(" ") { "<@$it>" }, false) + addField("Team 0", request.teams!!.first.joinToString(" ") { "<@$it>" }, false) + addField("Team 1", request.teams!!.second.joinToString(" ") { "<@$it>" }, false) } if (request.ranked != null) { From d0fb8ecdf3b21e69fee9c8d86e619ea76f6b6a46 Mon Sep 17 00:00:00 2001 From: Arthur Melton Date: Tue, 30 May 2023 17:57:06 -0400 Subject: [PATCH 17/28] Automatically adding result based on BigBoot/GiganticEmu#5 --- .../ggtools/fang/api/agent/ServerApi.kt | 3 ++ .../fang/api/agent/model/ResultRequest.kt | 10 +++++ .../fang/api/agent/model/ResultResponse.kt | 10 +++++ .../fang/service/QueueMessageService.kt | 39 ++++++++++++++----- 4 files changed, 53 insertions(+), 9 deletions(-) create mode 100644 src/main/kotlin/de/bigboot/ggtools/fang/api/agent/model/ResultRequest.kt create mode 100644 src/main/kotlin/de/bigboot/ggtools/fang/api/agent/model/ResultResponse.kt diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/api/agent/ServerApi.kt b/src/main/kotlin/de/bigboot/ggtools/fang/api/agent/ServerApi.kt index 0e1dd9d..ac7cafb 100644 --- a/src/main/kotlin/de/bigboot/ggtools/fang/api/agent/ServerApi.kt +++ b/src/main/kotlin/de/bigboot/ggtools/fang/api/agent/ServerApi.kt @@ -24,4 +24,7 @@ interface ServerApi { @POST("events") suspend fun getEvents(@Body eventsRequest: EventsRequest): EventsResponse + + @POST("result") + suspend fun getResult(@Body resultRequest: ResultRequest): ResultResponse } diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/api/agent/model/ResultRequest.kt b/src/main/kotlin/de/bigboot/ggtools/fang/api/agent/model/ResultRequest.kt new file mode 100644 index 0000000..dda6a4c --- /dev/null +++ b/src/main/kotlin/de/bigboot/ggtools/fang/api/agent/model/ResultRequest.kt @@ -0,0 +1,10 @@ +package de.bigboot.ggtools.fang.api.agent.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class ResultRequest( + @field:Json(name = "id") + val id: Int +) diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/api/agent/model/ResultResponse.kt b/src/main/kotlin/de/bigboot/ggtools/fang/api/agent/model/ResultResponse.kt new file mode 100644 index 0000000..2dd226b --- /dev/null +++ b/src/main/kotlin/de/bigboot/ggtools/fang/api/agent/model/ResultResponse.kt @@ -0,0 +1,10 @@ +package de.bigboot.ggtools.fang.api.agent.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class ResultResponse( + @field:Json(name = "winner") + val winner: String? +) diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt b/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt index a090d78..615b2cd 100644 --- a/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt +++ b/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt @@ -3,6 +3,8 @@ package de.bigboot.ggtools.fang.service import de.bigboot.ggtools.fang.Config import de.bigboot.ggtools.fang.api.agent.model.StartRequest import de.bigboot.ggtools.fang.api.agent.model.StartResponse +import de.bigboot.ggtools.fang.api.agent.model.ResultRequest +import de.bigboot.ggtools.fang.api.agent.model.ResultResponse import de.bigboot.ggtools.fang.components.queue.QueueComponentSpec import de.bigboot.ggtools.fang.components.queue.* import de.bigboot.ggtools.fang.utils.* @@ -145,14 +147,6 @@ class QueueMessageService : AutostartService, KoinComponent { { val request = matchReuests[matchId] ?: return - /* - request.pop.allPlayers.forEach { - println("${ratingService.findUser(it)}"); - } - - ratingService.addResult(listOf(0, 1), listOf(2, 3)); - */ - request.teams = ratingService.makeTeams(request.pop.allPlayers.toList()); if(request.state != MatchState.MATCH_READY) return @@ -565,13 +559,40 @@ class QueueMessageService : AutostartService, KoinComponent { } private suspend fun handleInteraction(event: ComponentInteractionEvent, button: ButtonMatchFinished) { - event.deferEdit().awaitSafe() + event.deferEdit().withEphemeral(true).awaitSafe() val request = matchReuests[button.matchId] ?: return + if(request.pop.allPlayers.contains(event.interaction.user.id.asLong())) { request.finishedPlayers += event.interaction.user.id.asLong() if(request.finishedPlayers.size >= 2) { + if (Config.bot.rating && request.ranked == null) { + val server = request.server ?: return + + val api = serverService.getClient(server) ?: return + + val result = try { + api.getResult( + ResultRequest( + id = request.openUrl!!.split(":").last().toInt(), + ) + ) + } catch (ex: Exception) { + ResultResponse(null) + } + + result.winner.let { + + if (it == "GRIFFIN") { + ratingService.addResult(request.teams!!.first, request.teams!!.second); + } + else { + ratingService.addResult(request.teams!!.second, request.teams!!.first); + } + } + } + handleMatchFinished(request) matchReuests.remove(button.matchId) } From d4603b251ccd0a00dd7bbf25e6994290cfdba8e7 Mon Sep 17 00:00:00 2001 From: Arthur Melton Date: Tue, 30 May 2023 21:39:09 -0400 Subject: [PATCH 18/28] Remove the testing command --- .../ggtools/fang/commands/rating/Rating.kt | 25 ------------------- 1 file changed, 25 deletions(-) diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/commands/rating/Rating.kt b/src/main/kotlin/de/bigboot/ggtools/fang/commands/rating/Rating.kt index b97af34..14e30d9 100644 --- a/src/main/kotlin/de/bigboot/ggtools/fang/commands/rating/Rating.kt +++ b/src/main/kotlin/de/bigboot/ggtools/fang/commands/rating/Rating.kt @@ -61,30 +61,5 @@ class Rating : CommandGroupSpec("rating", "Commands for ratings") { } } } - - // This is just a command for testing the teams created, this will not actually be pushed into production - command("maketeam", "make a team") { - arg("one", "") - arg("two", "") - arg("three", "") - arg("four", "") - arg("five","") - arg("six", "") - arg("seven", "") - arg("eight", "") - arg("nine", "") - arg("ten", "") - - onCall { - var teams = ratingService.makeTeams(listOf(args["one"].toLong(), args["two"].toLong(), args["three"].toLong(), args["four"].toLong(), args["five"].toLong(), args["six"].toLong(), args["seven"].toLong(), args["eight"].toLong(), args["nine"].toLong(), args["ten"].toLong())); - var diff = ratingService.teamDifferential(teams); - - channel().createMessageCompat { - addEmbedCompat { - description("team one: <@${teams.first.joinToString("> <@")}>\nteam two: <@${teams.second.joinToString("> <@")}>\ndiff: ${diff}") - } - }.awaitSingle() - } - } } } From 3ef93b469d9add0ab14b330facc43d18111b8aff Mon Sep 17 00:00:00 2001 From: Arthur Melton Date: Wed, 31 May 2023 07:38:08 -0400 Subject: [PATCH 19/28] No ephemeral on match finished --- .../de/bigboot/ggtools/fang/service/QueueMessageService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt b/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt index 615b2cd..2c78f6e 100644 --- a/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt +++ b/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt @@ -559,7 +559,7 @@ class QueueMessageService : AutostartService, KoinComponent { } private suspend fun handleInteraction(event: ComponentInteractionEvent, button: ButtonMatchFinished) { - event.deferEdit().withEphemeral(true).awaitSafe() + event.deferEdit().awaitSafe() val request = matchReuests[button.matchId] ?: return From 9ae4236834b03f1f96d4c2c79d8ee8ec9bc58754 Mon Sep 17 00:00:00 2001 From: Arthur Melton Date: Wed, 31 May 2023 07:39:31 -0400 Subject: [PATCH 20/28] Fix some spelling --- .../de/bigboot/ggtools/fang/service/QueueMessageService.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt b/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt index 2c78f6e..89ad6d9 100644 --- a/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt +++ b/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt @@ -637,7 +637,7 @@ class QueueMessageService : AutostartService, KoinComponent { event.editReplyCompat { addEmbedCompat { - description("Are you sure you want to set it to unranked? Ill intent use of this will get you reported? If you believe this should be unranked press the set ranked button underthis, if not dismiss this message.") + description("Are you sure you want to set it to unranked? Ill intent use of this will get you reported? If you believe this should be unranked press the set ranked button under this, if not dismiss this message.") } addComponent(ActionRow.of(ButtonMatchUnranked(button.matchId, true).component())) }.awaitSafe() @@ -646,7 +646,7 @@ class QueueMessageService : AutostartService, KoinComponent { event.deferEdit().withEphemeral(true).awaitSafe() event.editReplyCompat { addEmbedCompat { - description("You have set this message to be unranked, you can dissmis this message.") + description("You have set this match to be unranked, you can dissmis this message.") } addAllComponents(emptyList()) }.awaitSafe() From fdf69d05601b800d50d0b24e9419ec27892cf188 Mon Sep 17 00:00:00 2001 From: Acoliver102 <23olivera@cistercianmail.net> Date: Wed, 31 May 2023 15:11:27 -0500 Subject: [PATCH 21/28] minor spelling fix I noticed --- .../de/bigboot/ggtools/fang/service/QueueMessageService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt b/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt index 66760bc..2f20262 100644 --- a/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt +++ b/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt @@ -177,7 +177,7 @@ class QueueMessageService : AutostartService, KoinComponent { addField("Map", Maps.fromId(request.getMapVoteResult())!!.name, true) - addField("Team Differantial", String.format("%.1f%%", ratingService.teamDifferential(request.teams!!)*100-100), true) + addField("Team Differential", String.format("%.1f%%", ratingService.teamDifferential(request.teams!!)*100-100), true) if(request.serverSetupPlayer != null) { val value = when { From 4c669e05e7a4d79ff2ec615985c2a130c06ca235 Mon Sep 17 00:00:00 2001 From: Arthur Melton Date: Sun, 4 Jun 2023 11:01:09 -0400 Subject: [PATCH 22/28] Only set teams once --- .../de/bigboot/ggtools/fang/service/QueueMessageService.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt b/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt index 3b6d9f3..abcd514 100644 --- a/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt +++ b/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt @@ -147,7 +147,9 @@ class QueueMessageService : AutostartService, KoinComponent { { val request = matchReuests[matchId] ?: return - request.teams = ratingService.makeTeams(request.pop.allPlayers.toList()); + if (request.teams == null) { + request.teams = ratingService.makeTeams(request.pop.allPlayers.toList()); + } if(request.state != MatchState.MATCH_READY) return From 21177cf826f1b3b4af108134e2eb7cf6f17043bf Mon Sep 17 00:00:00 2001 From: Arthur Melton Date: Sun, 4 Jun 2023 11:03:12 -0400 Subject: [PATCH 23/28] Dont show queue things when match is not ranked --- .../bigboot/ggtools/fang/service/QueueMessageService.kt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt b/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt index abcd514..9fb4a84 100644 --- a/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt +++ b/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt @@ -177,13 +177,14 @@ class QueueMessageService : AutostartService, KoinComponent { }, true) addField("Map", Maps.fromId(request.getMapVoteResult())!!.name, true) - - addField("Team Differential", String.format("%.1f%%", ratingService.teamDifferential(request.teams!!)*100-100), true) + if (Config.bot.rating && request.ranked == null) { + addField("Team Differential", String.format("%.1f%%", ratingService.teamDifferential(request.teams!!)*100-100), true) + } if(request.serverSetupPlayer != null) { val value = when { - request.openUrl != null -> if (Config.bot.rating) { + request.openUrl != null -> if (Config.bot.rating && request.ranked == null) { "`open ${request.openUrl}?team=0`\n`open ${request.openUrl}?team=1`\nby <@${request.serverSetupPlayer!!.asLong()}>" } else { @@ -196,7 +197,7 @@ class QueueMessageService : AutostartService, KoinComponent { components.add(ButtonMatchSetupServer(matchId)) } - if (Config.bot.rating) { + if (Config.bot.rating && request.ranked == null) { addField("Team 0", request.teams!!.first.joinToString(" ") { "<@$it>" }, false) addField("Team 1", request.teams!!.second.joinToString(" ") { "<@$it>" }, false) } From 5029175db58c6461398e8139962fa47797f4d16a Mon Sep 17 00:00:00 2001 From: Arthur Melton Date: Thu, 8 Jun 2023 08:56:32 -0400 Subject: [PATCH 24/28] Add request drop feature --- .../fang/components/queue/ButtonAccept.kt | 11 +- .../components/queue/ButtonRequestFill.kt | 18 ++ .../queue/ButtonRequestFillCancel.kt | 19 ++ .../ggtools/fang/service/MatchService.kt | 2 + .../ggtools/fang/service/MatchServiceImpl.kt | 8 + .../fang/service/QueueMessageService.kt | 231 +++++++++++++++--- 6 files changed, 253 insertions(+), 36 deletions(-) create mode 100644 src/main/kotlin/de/bigboot/ggtools/fang/components/queue/ButtonRequestFill.kt create mode 100644 src/main/kotlin/de/bigboot/ggtools/fang/components/queue/ButtonRequestFillCancel.kt diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/components/queue/ButtonAccept.kt b/src/main/kotlin/de/bigboot/ggtools/fang/components/queue/ButtonAccept.kt index 298c89e..0a84882 100644 --- a/src/main/kotlin/de/bigboot/ggtools/fang/components/queue/ButtonAccept.kt +++ b/src/main/kotlin/de/bigboot/ggtools/fang/components/queue/ButtonAccept.kt @@ -1,16 +1,17 @@ package de.bigboot.ggtools.fang.components.queue +import discord4j.common.util.Snowflake import discord4j.core.`object`.component.ActionComponent import discord4j.core.`object`.component.Button import java.util.* -data class ButtonAccept(val matchId: UUID): QueueComponentSpec { - override fun id() = "$PREFIX:${matchId}" +data class ButtonAccept(val matchId: UUID, val dropper: Snowflake?): QueueComponentSpec { + override fun id() = "$PREFIX:${matchId}:${if (dropper != null) dropper.asLong() else ""}" override fun component(): ActionComponent = Button.success(id(), "Accept") companion object { private val PREFIX = "${QueueComponentSpec.ID_PREFIX}:BUTTON:ACCEPT" - private val ID_REGEX = Regex("$PREFIX:([^:]+)") - fun parse(id: String) = ID_REGEX.find(id)?.destructured?.let { (matchId) -> ButtonAccept(UUID.fromString(matchId)) } + private val ID_REGEX = Regex("$PREFIX:([^:]+):([^:]+)?") + fun parse(id: String) = ID_REGEX.find(id)?.destructured?.let { (matchId, dropper) -> ButtonAccept(UUID.fromString(matchId), if (dropper != "") Snowflake.of(dropper) else null) } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/components/queue/ButtonRequestFill.kt b/src/main/kotlin/de/bigboot/ggtools/fang/components/queue/ButtonRequestFill.kt new file mode 100644 index 0000000..8cf646c --- /dev/null +++ b/src/main/kotlin/de/bigboot/ggtools/fang/components/queue/ButtonRequestFill.kt @@ -0,0 +1,18 @@ +package de.bigboot.ggtools.fang.components.queue + +import de.bigboot.ggtools.fang.Config +import de.bigboot.ggtools.fang.utils.asReaction +import discord4j.core.`object`.component.ActionComponent +import discord4j.core.`object`.component.Button +import java.util.UUID + +data class ButtonRequestFill(val matchId: UUID): QueueComponentSpec { + override fun id() = "$PREFIX:${matchId}" + override fun component(): ActionComponent = Button.primary(id(), "Request Fill") + + companion object { + private val PREFIX = "${QueueComponentSpec.ID_PREFIX}:BUTTON:REQUEST_FILL" + private val ID_REGEX = Regex("$PREFIX:([^:]+)") + fun parse(id: String) = ID_REGEX.find(id)?.destructured?.let { (matchId) -> ButtonRequestFill(UUID.fromString(matchId)) } + } +} diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/components/queue/ButtonRequestFillCancel.kt b/src/main/kotlin/de/bigboot/ggtools/fang/components/queue/ButtonRequestFillCancel.kt new file mode 100644 index 0000000..1b4e744 --- /dev/null +++ b/src/main/kotlin/de/bigboot/ggtools/fang/components/queue/ButtonRequestFillCancel.kt @@ -0,0 +1,19 @@ +package de.bigboot.ggtools.fang.components.queue + +import de.bigboot.ggtools.fang.Config +import de.bigboot.ggtools.fang.utils.asReaction +import discord4j.common.util.Snowflake +import discord4j.core.`object`.component.ActionComponent +import discord4j.core.`object`.component.Button +import java.util.UUID + +data class ButtonRequestFillCancel(val matchId: UUID, val dropper: Snowflake): QueueComponentSpec { + override fun id() = "$PREFIX:${matchId}:${dropper.asLong()}" + override fun component(): ActionComponent = Button.primary(id(), "Cancel") + + companion object { + private val PREFIX = "${QueueComponentSpec.ID_PREFIX}:BUTTON:REQUEST_FILL_CANCEL" + private val ID_REGEX = Regex("$PREFIX:([^:]+):([^:]+)") + fun parse(id: String) = ID_REGEX.find(id)?.destructured?.let { (matchId, dropper) -> ButtonRequestFillCancel(UUID.fromString(matchId), Snowflake.of(dropper)) } + } +} diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/service/MatchService.kt b/src/main/kotlin/de/bigboot/ggtools/fang/service/MatchService.kt index 53e31b7..d235791 100644 --- a/src/main/kotlin/de/bigboot/ggtools/fang/service/MatchService.kt +++ b/src/main/kotlin/de/bigboot/ggtools/fang/service/MatchService.kt @@ -6,6 +6,8 @@ interface MatchService { fun leave(queue: String, snowflake: Long, matchOnly: Boolean = false): Boolean + fun setInMatch(queue: String, snowflake: Long) + fun canPop(queue: String): Boolean fun force(queue: String) diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/service/MatchServiceImpl.kt b/src/main/kotlin/de/bigboot/ggtools/fang/service/MatchServiceImpl.kt index 5622831..9c27e7b 100644 --- a/src/main/kotlin/de/bigboot/ggtools/fang/service/MatchServiceImpl.kt +++ b/src/main/kotlin/de/bigboot/ggtools/fang/service/MatchServiceImpl.kt @@ -52,6 +52,14 @@ class MatchServiceImpl : MatchService, KoinComponent { } } + override fun setInMatch(queue: String, snowflake: Long) { + return transaction { + val player = Player.find { (Players.snowflake eq snowflake) and (Players.queue eq queue) } + .firstOrNull() ?: return@transaction + player.inMatch = true + } + } + override fun canPop(queue: String): Boolean = force.contains(queue) || requests.containsKey(queue) || getNumPlayers(queue) >= Config.bot.required_players diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt b/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt index 9fb4a84..709d3ec 100644 --- a/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt +++ b/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt @@ -40,6 +40,19 @@ enum class MatchState { MATCH_READY, } +enum class DropState { + WAITING, + WAITING_PLAYER, + DONE, +} + +data class FillRequest( + val matchRequest: UUID, + var message: Message? = null, + var state: DropState = DropState.WAITING, + var filler: Snowflake? = null, +) + data class MatchRequest( val queue: String, val popEndTime: Instant, @@ -59,7 +72,8 @@ data class MatchRequest( var state: MatchState = MatchState.QUEUE_POP, var timeToJoin: Instant? = null, var teams: Pair, List>? = null, - var ranked: Snowflake? = null + var ranked: Snowflake? = null, + var drops: HashMap = hashMapOf(), ) { fun getMapVoteResult() = mapVotes .values @@ -101,6 +115,10 @@ class QueueMessageService : AutostartService, KoinComponent { } private suspend fun updateQueueMessage(queue: String, msg: Message) { + matchReuests.forEach { (uuid, match) -> + match.drops.forEach { (dropper, _) -> fillRequest(uuid, dropper, false) } + } + if (matchService.canPop(queue)) { val channel = client.getChannelById(msg.channelId).awaitSingle() as MessageChannel handleQueuePop(queue, matchService.pop(queue), channel) @@ -143,24 +161,41 @@ class QueueMessageService : AutostartService, KoinComponent { }.awaitSafe() } + private fun getPlayers(request: MatchRequest): Set { + var players = request.pop.allPlayers + request.drops.forEach { + (dropper, fillRequest) -> + if (fillRequest.state == DropState.DONE) { + players -= dropper.asLong() + players += fillRequest.filler!!.asLong() + } + } + + return players + } + private suspend fun updateMatchReadyMessage(matchId: UUID) { val request = matchReuests[matchId] ?: return if (request.teams == null) { - request.teams = ratingService.makeTeams(request.pop.allPlayers.toList()); + request.teams = ratingService.makeTeams(getPlayers(request).toList()); } if(request.state != MatchState.MATCH_READY) return request.message.editCompat { addEmbedCompat { + content(getPlayers(request).joinToString(" ") { "<@$it>" }) + val components = mutableListOf(ButtonMatchFinished(matchId), ButtonMatchDrop(matchId)) if (Config.bot.rating && request.ranked == null) { components.add(ButtonMatchUnranked(matchId, false)); } + components.add(ButtonRequestFill(matchId)); + title("Match ready!") description(""" |Everybody get ready, you've got a match. @@ -209,7 +244,7 @@ class QueueMessageService : AutostartService, KoinComponent { if(request.timeToJoin != null) { addField("Time to join", when { Instant.now().compareTo(request.timeToJoin) >= 0 -> "If someone is still not in please report them." - else -> "" + else -> "" }, false) } @@ -233,8 +268,8 @@ class QueueMessageService : AutostartService, KoinComponent { } addComponent(ActionRow.of(when(canDeny) { - true -> listOf(ButtonAccept(matchId), ButtonDecline(matchId)) - else -> listOf(ButtonAccept(matchId)) + true -> listOf(ButtonAccept(matchId, null), ButtonDecline(matchId)) + else -> listOf(ButtonAccept(matchId, null)) }.map { it.component() }.toMutableList())) }.awaitSafe() ?: return @@ -269,9 +304,9 @@ class QueueMessageService : AutostartService, KoinComponent { request.message.delete().await() matchReuests[matchId]!!.message = request.message.channel.awaitSingle().createMessageCompat { - content(request.pop.allPlayers.joinToString(" ") { "<@$it>" }) + content(getPlayers(request).joinToString(" ") { "<@$it>" }) }.awaitSingle() - + updateMapVoteMessage(matchId) CoroutineScope(Dispatchers.Default).launch { @@ -282,12 +317,12 @@ class QueueMessageService : AutostartService, KoinComponent { private suspend fun handleMapVoteFinished(matchId: UUID) { val request = matchReuests[matchId] ?: return - + request.state = MatchState.MATCH_READY updateMatchReadyMessage(matchId) request.message.deleteAfter(90.minutes) { - for (player in request.pop.allPlayers) { + for (player in getPlayers(request)) { matchService.leave(request.queue, player, true) } matchReuests.remove(matchId) @@ -296,7 +331,7 @@ class QueueMessageService : AutostartService, KoinComponent { private suspend fun handleMatchCancelled(request: MatchRequest) { val message = request.message - val accepted = request.pop.allPlayers - request.missingPlayers + val accepted = getPlayers(request) - request.missingPlayers for (player in request.missingPlayers) { matchService.leave(request.queue, player) @@ -326,7 +361,7 @@ class QueueMessageService : AutostartService, KoinComponent { } private suspend fun handleMatchFinished(request: MatchRequest) { - for (player in request.pop.allPlayers - request.dropPlayers) { + for (player in getPlayers(request) - request.dropPlayers) { matchService.join(request.queue, player, true) } @@ -434,6 +469,116 @@ class QueueMessageService : AutostartService, KoinComponent { )) } + private suspend fun acceptQueue(event: ComponentInteractionEvent, button: ButtonAccept) { + event.deferEdit().awaitSafe() + + val request = matchReuests[button.matchId] ?: return + request.missingPlayers -= event.interaction.user.id.asLong() + + + if(request.missingPlayers.isEmpty()) { + request.matchReady.complete(null) + } else { + event.editReplyCompat { + addEmbedCompat { + printQueuePop(request, this) + } + }.awaitSafe() + } + } + + private suspend fun acceptFill(event: ComponentInteractionEvent, button: ButtonAccept) { + event.deferEdit().awaitSafe() + + val request = matchReuests[button.matchId] ?: return + val dropper = button.dropper ?: return + val filler = event.interaction.user.id.asLong() + val fill = request.drops[dropper] ?: return + val fillFiller = fill.filler ?: return + + if (filler != fillFiller.asLong()) { + return + } + + fill.state = DropState.DONE + fill.message!!.delete().await() + request.teams = null + updateMatchReadyMessage(button.matchId) + } + + private suspend fun fillRequest(matchId: UUID, dropper: Snowflake, timeout: Boolean) { + val request = matchReuests[matchId] ?: return + val channel = request.message.channel.awaitSingle(); + + var fill = request.drops[dropper] ?: FillRequest(matchId) + + if (fill.state == DropState.DONE || (fill.state == DropState.WAITING_PLAYER && !timeout)) { + return + } + + var message = fill.message; + + if(matchService.getNumPlayers(request.queue, request.pop.server) >= 1) { + if (fill.state == DropState.WAITING) { + if (message != null) { + message.delete().await() + } + + fill.state = DropState.WAITING_PLAYER + + val newPlayer = matchService.pop(request.queue, request.pop.server, setOf()).players.first(); + val endTime = Instant.now().plusSeconds(Config.bot.accept_timeout.toLong()) + + notifyPlayer(Snowflake.of(newPlayer), channel.id) + + fill.message = channel.createMessageCompat { + content("<@${newPlayer}>") + + addEmbedCompat { + title("Fill Request") + description("<@${dropper.asLong()}> would like to drop, press the Accept button to take the spot.") + addField("Time remaining", "", true) + } + + addComponent(ActionRow.of(ButtonAccept(matchId, dropper).component())) + }.awaitSingle() + + fill.filler = Snowflake.of(newPlayer) + + matchService.setInMatch(request.queue, newPlayer) + updateQueueMessage(request.queue) + + val fillReady = CompletableFuture() + + fillReady.completeOnTimeout(null, Config.bot.accept_timeout.toLong(), TimeUnit.SECONDS) + + CoroutineScope(Dispatchers.Default).launch { + fillReady.await() + if (fill.state == DropState.WAITING_PLAYER) { + matchService.leave(request.queue, newPlayer, true) + } + fillRequest(matchId, dropper, true) + } + } + } else { + if (fill.state == DropState.WAITING_PLAYER && message != null) { + message.delete().await() + } + fill.state = DropState.WAITING + + fill.message = channel.createMessageCompat { + addEmbedCompat { + title("Fill Request") + description("No one is in the queue to fill for <@${dropper.asLong()}>, please wait for someone to join queue or press cancle") + } + + addComponent(ActionRow.of(ButtonRequestFillCancel(matchId, dropper).component())) + }.awaitSingle() + } + + request.drops[dropper] = fill + } + private suspend fun handleInteraction(event: ComponentInteractionEvent, button: ButtonJoin) { event.deferEdit().awaitSafe() matchService.join(button.queue, event.interaction.user.id.asLong()) @@ -505,20 +650,11 @@ class QueueMessageService : AutostartService, KoinComponent { } private suspend fun handleInteraction(event: ComponentInteractionEvent, button: ButtonAccept) { - event.deferEdit().awaitSafe() - - val request = matchReuests[button.matchId] ?: return - request.missingPlayers -= event.interaction.user.id.asLong() - - - if(request.missingPlayers.isEmpty()) { - request.matchReady.complete(null) - } else { - event.editReplyCompat { - addEmbedCompat { - printQueuePop(request, this) - } - }.awaitSafe() + if (button.dropper == null) { + acceptQueue(event, button) + } + else { + acceptFill(event, button) } } @@ -544,7 +680,7 @@ class QueueMessageService : AutostartService, KoinComponent { event.deferEdit().awaitSafe() val request = matchReuests[button.matchId] ?: return - if(request.pop.allPlayers.contains(event.interaction.user.id.asLong())) { + if(getPlayers(request).contains(event.interaction.user.id.asLong())) { request.mapVotes += Pair(event.interaction.user.id.asLong(), button.map) updateMapVoteMessage(button.matchId) } @@ -554,7 +690,7 @@ class QueueMessageService : AutostartService, KoinComponent { event.deferEdit().awaitSafe() val request = matchReuests[button.matchId] ?: return - if(request.pop.allPlayers.contains(event.interaction.user.id.asLong())) + if(getPlayers(request).contains(event.interaction.user.id.asLong())) { request.dropPlayers += event.interaction.user.id.asLong() } @@ -565,7 +701,7 @@ class QueueMessageService : AutostartService, KoinComponent { val request = matchReuests[button.matchId] ?: return - if(request.pop.allPlayers.contains(event.interaction.user.id.asLong())) { + if(getPlayers(request).contains(event.interaction.user.id.asLong())) { request.finishedPlayers += event.interaction.user.id.asLong() if(request.finishedPlayers.size >= 2) { @@ -614,7 +750,7 @@ class QueueMessageService : AutostartService, KoinComponent { event .deferReply(InteractionCallbackSpec.builder().ephemeral(true).build()) .awaitSafe() - + updateMatchReadyMessage(button.matchId) event.editReplyCompat { @@ -659,6 +795,37 @@ class QueueMessageService : AutostartService, KoinComponent { } } + private suspend fun handleInteraction(event: ComponentInteractionEvent, button: ButtonRequestFill) { + val matchId = button.matchId; + val dropper = event.interaction.user.id + + val request = matchReuests[matchId] ?: return + + if(getPlayers(request).contains(event.interaction.user.id.asLong()) && request.drops[dropper] == null) { + fillRequest(matchId, dropper, false); + } + + event.deferEdit().awaitSafe() + } + + private suspend fun handleInteraction(event: ComponentInteractionEvent, button: ButtonRequestFillCancel) { + val request = matchReuests[button.matchId] ?: return + val dropper = event.interaction.user.id + + if (button.dropper != dropper) { + return + } + + var fill = request.drops[dropper] ?: return + + if (fill.message != null) { + fill.message!!.delete().await() + } + + request.drops.remove(dropper) + event.deferEdit().awaitSafe() + } + private suspend fun handleInteraction(event: ComponentInteractionEvent, button: SelectMatchSetupCreatures) { val request = matchReuests[button.matchId] ?: return @@ -697,7 +864,7 @@ class QueueMessageService : AutostartService, KoinComponent { api.start( StartRequest( map = "lv_${request.getMapVoteResult()}", - maxPlayers = request.pop.allPlayers.size, + maxPlayers = getPlayers(request).size, creature0 = request.creatures.first, creature1 = request.creatures.second, creature2 = request.creatures.third, @@ -729,7 +896,7 @@ class QueueMessageService : AutostartService, KoinComponent { }.awaitSafe() updateMatchReadyMessage(button.matchId) - + CoroutineScope(Dispatchers.Default).launch { delay(Config.bot.time_to_join.seconds) updateMatchReadyMessage(button.matchId) @@ -750,6 +917,8 @@ class QueueMessageService : AutostartService, KoinComponent { ButtonMatchDrop.parse(event.customId)?.also { handleInteraction(event, it); return } ButtonMatchFinished.parse(event.customId)?.also { handleInteraction(event, it); return } ButtonMatchUnranked.parse(event.customId)?.also { handleInteraction(event, it); return } + ButtonRequestFill.parse(event.customId)?.also { handleInteraction(event, it); return } + ButtonRequestFillCancel.parse(event.customId)?.also { handleInteraction(event, it); return } ButtonMatchSetupServer.parse(event.customId)?.also { handleInteraction(event, it); return } SelectMatchSetupServer.parse(event.customId)?.also { handleInteraction(event, it); return } SelectMatchSetupCreatures.parse(event.customId)?.also { handleInteraction(event, it); return } From ac87ba395b722b8edb1de2c7e20ed716b2898a8b Mon Sep 17 00:00:00 2001 From: Arthur Melton Date: Sat, 10 Jun 2023 10:12:22 -0400 Subject: [PATCH 25/28] Suggest a swap added --- .../kotlin/de/bigboot/ggtools/fang/Config.kt | 2 +- .../fang/components/queue/ButtonDownvote.kt | 18 ++ .../components/queue/ButtonSuggestSwap.kt | 18 ++ .../fang/components/queue/ButtonUpvote.kt | 18 ++ .../fang/components/queue/SelectPickSwap.kt | 24 ++ .../fang/service/QueueMessageService.kt | 226 +++++++++++++++++- .../ggtools/fang/service/RatingServiceImpl.kt | 10 +- 7 files changed, 309 insertions(+), 7 deletions(-) create mode 100644 src/main/kotlin/de/bigboot/ggtools/fang/components/queue/ButtonDownvote.kt create mode 100644 src/main/kotlin/de/bigboot/ggtools/fang/components/queue/ButtonSuggestSwap.kt create mode 100644 src/main/kotlin/de/bigboot/ggtools/fang/components/queue/ButtonUpvote.kt create mode 100644 src/main/kotlin/de/bigboot/ggtools/fang/components/queue/SelectPickSwap.kt diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/Config.kt b/src/main/kotlin/de/bigboot/ggtools/fang/Config.kt index d0b125b..dde96ba 100644 --- a/src/main/kotlin/de/bigboot/ggtools/fang/Config.kt +++ b/src/main/kotlin/de/bigboot/ggtools/fang/Config.kt @@ -44,7 +44,7 @@ data class BotConfig( val token: String, val prefix: String = "!", val accept_timeout: Int = 120, - val mapvote_time: Int = 30, + val vote_time: Int = 30, val statusupdate_poll_rate: Long = 2000L, val required_players: Int = 10, val log_level: String = "info", diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/components/queue/ButtonDownvote.kt b/src/main/kotlin/de/bigboot/ggtools/fang/components/queue/ButtonDownvote.kt new file mode 100644 index 0000000..eccdf9f --- /dev/null +++ b/src/main/kotlin/de/bigboot/ggtools/fang/components/queue/ButtonDownvote.kt @@ -0,0 +1,18 @@ +package de.bigboot.ggtools.fang.components.queue + +import discord4j.common.util.Snowflake +import discord4j.core.`object`.component.ActionComponent +import discord4j.core.`object`.component.Button +import java.util.* +import de.bigboot.ggtools.fang.utils.asReaction + +data class ButtonDownvote(val matchId: UUID, val suggester: Snowflake): QueueComponentSpec { + override fun id() = "$PREFIX:${matchId}:${suggester.asLong()}" + override fun component(): ActionComponent = Button.danger(id(), "👎".asReaction(), "") + + companion object { + private val PREFIX = "${QueueComponentSpec.ID_PREFIX}:BUTTON:DOWNVOTE" + private val ID_REGEX = Regex("$PREFIX:([^:]+):([^:]+)") + fun parse(id: String) = ID_REGEX.find(id)?.destructured?.let { (matchId, suggester) -> ButtonDownvote(UUID.fromString(matchId), Snowflake.of(suggester) ) } + } +} diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/components/queue/ButtonSuggestSwap.kt b/src/main/kotlin/de/bigboot/ggtools/fang/components/queue/ButtonSuggestSwap.kt new file mode 100644 index 0000000..99af51a --- /dev/null +++ b/src/main/kotlin/de/bigboot/ggtools/fang/components/queue/ButtonSuggestSwap.kt @@ -0,0 +1,18 @@ +package de.bigboot.ggtools.fang.components.queue + +import de.bigboot.ggtools.fang.Config +import de.bigboot.ggtools.fang.utils.asReaction +import discord4j.core.`object`.component.ActionComponent +import discord4j.core.`object`.component.Button +import java.util.UUID + +data class ButtonSuggestSwap(val matchId: UUID, val final: Boolean): QueueComponentSpec { + override fun id() = "$PREFIX:${matchId}:${final}" + override fun component(): ActionComponent = Button.primary(id(), "Suggest Swap") + + companion object { + private val PREFIX = "${QueueComponentSpec.ID_PREFIX}:BUTTON:SUGGEST_SWAP" + private val ID_REGEX = Regex("$PREFIX:([^:]+):([^:]+)") + fun parse(id: String) = ID_REGEX.find(id)?.destructured?.let { (matchId, final) -> ButtonSuggestSwap(UUID.fromString(matchId), final.toBoolean()) } + } +} diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/components/queue/ButtonUpvote.kt b/src/main/kotlin/de/bigboot/ggtools/fang/components/queue/ButtonUpvote.kt new file mode 100644 index 0000000..620d298 --- /dev/null +++ b/src/main/kotlin/de/bigboot/ggtools/fang/components/queue/ButtonUpvote.kt @@ -0,0 +1,18 @@ +package de.bigboot.ggtools.fang.components.queue + +import discord4j.common.util.Snowflake +import discord4j.core.`object`.component.ActionComponent +import discord4j.core.`object`.component.Button +import java.util.* +import de.bigboot.ggtools.fang.utils.asReaction + +data class ButtonUpvote(val matchId: UUID, val suggester: Snowflake): QueueComponentSpec { + override fun id() = "$PREFIX:${matchId}:${suggester.asLong()}" + override fun component(): ActionComponent = Button.success(id(), "👍".asReaction(), "") + + companion object { + private val PREFIX = "${QueueComponentSpec.ID_PREFIX}:BUTTON:UPVOTE" + private val ID_REGEX = Regex("$PREFIX:([^:]+):([^:]+)") + fun parse(id: String) = ID_REGEX.find(id)?.destructured?.let { (matchId, suggester) -> ButtonUpvote(UUID.fromString(matchId), Snowflake.of(suggester) ) } + } +} diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/components/queue/SelectPickSwap.kt b/src/main/kotlin/de/bigboot/ggtools/fang/components/queue/SelectPickSwap.kt new file mode 100644 index 0000000..193841b --- /dev/null +++ b/src/main/kotlin/de/bigboot/ggtools/fang/components/queue/SelectPickSwap.kt @@ -0,0 +1,24 @@ +package de.bigboot.ggtools.fang.components.queue + +import discord4j.common.util.Snowflake +import discord4j.core.GatewayDiscordClient +import discord4j.core.`object`.entity.Member +import discord4j.core.`object`.component.ActionComponent +import discord4j.core.`object`.component.SelectMenu +import java.util.* +import de.bigboot.ggtools.fang.utils.awaitSingle + +class SelectPickSwap(val matchId: UUID, private val players: List>, val team: Boolean): + QueueComponentSpec { + override fun id() = "$PREFIX:${matchId}:${team.toString()}" + override fun component(): ActionComponent = SelectMenu.of( + id(), + players.map { SelectMenu.Option.of(it.first, it.second.toString()) } + ).withMinValues(1).withMaxValues(1).withPlaceholder("Pick a person") + + companion object { + private val PREFIX = "${QueueComponentSpec.ID_PREFIX}:SELECT:PICK_SWAP" + private val ID_REGEX = Regex("$PREFIX:([^:]+):([^:]+)") + fun parse(id: String) = ID_REGEX.find(id)?.destructured?.let { (matchId, team) -> SelectPickSwap(UUID.fromString(matchId), listOf(), team.toBoolean()) } + } +} diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt b/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt index 709d3ec..65453f3 100644 --- a/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt +++ b/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt @@ -13,6 +13,7 @@ import discord4j.core.GatewayDiscordClient import discord4j.core.event.domain.interaction.ComponentInteractionEvent import discord4j.core.event.domain.interaction.SelectMenuInteractionEvent import discord4j.core.`object`.component.ActionRow +import discord4j.core.`object`.entity.Member import discord4j.core.`object`.entity.Message import discord4j.core.`object`.entity.channel.MessageChannel import discord4j.core.spec.EmbedCreateSpec @@ -46,13 +47,29 @@ enum class DropState { DONE, } +enum class SwapState { + CREATATION, + VOTING, +} + data class FillRequest( val matchRequest: UUID, var message: Message? = null, - var state: DropState = DropState.WAITING, + var state: DropState = DropState.DONE, var filler: Snowflake? = null, ) +data class SwapRequest( + val matchRequest: UUID, + var message: Message? = null, + var state: SwapState = SwapState.CREATATION, + var teamOne: Snowflake? = null, + var teamTwo: Snowflake? = null, + var upVotes: HashSet = hashSetOf(), + var downVotes: HashSet = hashSetOf(), + var endTime: Instant? = null +) + data class MatchRequest( val queue: String, val popEndTime: Instant, @@ -74,6 +91,7 @@ data class MatchRequest( var teams: Pair, List>? = null, var ranked: Snowflake? = null, var drops: HashMap = hashMapOf(), + var swaps: HashMap = hashMapOf(), ) { fun getMapVoteResult() = mapVotes .values @@ -189,13 +207,12 @@ class QueueMessageService : AutostartService, KoinComponent { content(getPlayers(request).joinToString(" ") { "<@$it>" }) val components = mutableListOf(ButtonMatchFinished(matchId), ButtonMatchDrop(matchId)) + val componentsSecond = mutableListOf(ButtonRequestFill(matchId), ButtonSuggestSwap(matchId, false)) if (Config.bot.rating && request.ranked == null) { components.add(ButtonMatchUnranked(matchId, false)); } - components.add(ButtonRequestFill(matchId)); - title("Match ready!") description(""" |Everybody get ready, you've got a match. @@ -249,6 +266,7 @@ class QueueMessageService : AutostartService, KoinComponent { } addComponent(ActionRow.of(components.map { it.component() })) + addComponent(ActionRow.of(componentsSecond.map { it.component() })) } }.awaitSingle() } @@ -300,7 +318,7 @@ class QueueMessageService : AutostartService, KoinComponent { } request.state = MatchState.MAP_VOTE - request.mapVoteEnd = Instant.now().plusSeconds(Config.bot.mapvote_time.toLong()) + request.mapVoteEnd = Instant.now().plusSeconds(Config.bot.vote_time.toLong()) request.message.delete().await() matchReuests[matchId]!!.message = request.message.channel.awaitSingle().createMessageCompat { @@ -310,7 +328,7 @@ class QueueMessageService : AutostartService, KoinComponent { updateMapVoteMessage(matchId) CoroutineScope(Dispatchers.Default).launch { - delay(Config.bot.mapvote_time.seconds) + delay(Config.bot.vote_time.seconds) handleMapVoteFinished(matchId) } } @@ -579,6 +597,22 @@ class QueueMessageService : AutostartService, KoinComponent { request.drops[dropper] = fill } + private suspend fun updateSwapRequest(request: MatchRequest, suggester: Snowflake) { + val swap = request.swaps[suggester] ?: return + + swap.message!!.editCompat { + addEmbedCompat { + title("Swap Request") + description("<@${suggester.asLong()}> has requested a swap, <@${swap.teamOne!!.asLong()}> for <@${swap.teamTwo!!.asLong()}>") + addField("Up votes", swap.upVotes.size.toString(), true) + addField("Down votes", swap.downVotes.size.toString(), true) + + addField("Time remaining", "", false) + } + }.awaitSingle() + + } + private suspend fun handleInteraction(event: ComponentInteractionEvent, button: ButtonJoin) { event.deferEdit().awaitSafe() matchService.join(button.queue, event.interaction.user.id.asLong()) @@ -795,6 +829,184 @@ class QueueMessageService : AutostartService, KoinComponent { } } + private suspend fun handleInteraction(event: ComponentInteractionEvent, select: SelectPickSwap) { + val matchId = select.matchId; + val suggester = event.interaction.user.id + + val request = matchReuests[matchId] ?: return + val swap = request.swaps[suggester] + val value = Snowflake.of((event as SelectMenuInteractionEvent).values.first()) + + if (swap != null) { + if (select.team == false) { + swap.teamOne = value + } + else { + swap.teamTwo = value + } + } + event.deferEdit().awaitSafe() + } + + private suspend fun handleInteraction(event: ComponentInteractionEvent, button: ButtonSuggestSwap) { + val matchId = button.matchId; + val suggester = event.interaction.user.id + val guild = event.interaction.guild.awaitSingle().id + val channel = event.interaction.channel.awaitSingle() + + val request = matchReuests[matchId] ?: return + var swap = request.swaps[suggester] + + if (!button.final) { + if(getPlayers(request).contains(event.interaction.user.id.asLong()) && (swap == null || swap.state == SwapState.CREATATION)) { + request.swaps[suggester] = SwapRequest(matchId) + + val (teamOne, teamTwo) = request.teams!!.toList().map { + it.map { + Pair( + client.getMemberById( + guild, + Snowflake.of(it) + ).await()!!.displayName, + it + ) + } + } + + event + .deferReply(InteractionCallbackSpec.builder().ephemeral(true).build()) + .awaitSafe() + event.editReplyCompat { + addEmbedCompat { + description("Please select the swaps you would like.") + } + addAllComponents(listOf( + ActionRow.of( + SelectPickSwap( + matchId, + teamOne, + false, + ).component()), + ActionRow.of( + SelectPickSwap( + matchId, + teamTwo, + true, + ).component()), + ActionRow.of(ButtonSuggestSwap(matchId, true).component()), + )) + }.awaitSafe() + } + else if (swap != null) { + event + .deferReply(InteractionCallbackSpec.builder().ephemeral(true).build()) + .awaitSafe() + event.editReplyCompat { + addEmbedCompat { + description("Please wait for your other request to finish before making another") + } + }.awaitSafe() + } + else { + event.deferEdit().awaitSafe() + } + } + else { + swap = swap!! + + if (swap.teamTwo == null || swap.teamOne == null) { + return + } + + event.deferEdit().awaitSafe() + event.editReplyCompat { + addEmbedCompat { + description("You can dismiss this message") + } + + addAllComponents(emptyList()) + }.awaitSafe() + + swap.state = SwapState.VOTING + + swap.endTime = Instant.now().plusSeconds(Config.bot.vote_time.toLong()) + + swap.message = channel.createMessageCompat { + content(getPlayers(request).joinToString(" ") { "<@$it>" }) + + addComponent(ActionRow.of(ButtonUpvote(matchId, suggester).component(), ButtonDownvote(matchId, suggester).component())) + }.awaitSingle() + + updateSwapRequest(request, suggester) + + CoroutineScope(Dispatchers.Default).launch { + delay(Config.bot.vote_time.seconds) + if (swap.upVotes.count() > swap.downVotes.count()) { + swap.message!!.editCompat { + addEmbedCompat { + title("Swap Succsess") + description("<@${suggester.asLong()}> requested a swap, <@${swap.teamOne!!.asLong()}> for <@${swap.teamTwo!!.asLong()}>") + } + }.awaitSingle() + + val teamOne = request.teams!!.first.toMutableList() + val teamTwo = request.teams!!.second.toMutableList() + + teamOne.remove(swap.teamOne!!.asLong()) + teamOne.add(swap.teamTwo!!.asLong()) + + teamTwo.add(swap.teamOne!!.asLong()) + teamTwo.remove(swap.teamTwo!!.asLong()) + + request.teams = Pair(teamOne, teamTwo) + + updateMatchReadyMessage(matchId) + } + else { + swap.message!!.editCompat { + addEmbedCompat { + title("Swap Failed") + description("<@${suggester.asLong()}> requested a swap, <@${swap.teamOne!!.asLong()}> for <@${swap.teamTwo!!.asLong()}>") + } + }.awaitSingle() + + swap.message!!.deleteAfter(1.minutes) + } + request.swaps.remove(suggester) + } + } + } + + private suspend fun handleInteraction(event: ComponentInteractionEvent, button: ButtonUpvote) { + val request = matchReuests[button.matchId] ?: return + val voter = event.interaction.user.id.asLong() + + if (getPlayers(request).contains(voter)) { + val swap = request.swaps[button.suggester] ?: return + + swap.upVotes.add(voter) + swap.downVotes.remove(voter) + + updateSwapRequest(request, button.suggester) + } + event.deferEdit().awaitSafe() + } + + private suspend fun handleInteraction(event: ComponentInteractionEvent, button: ButtonDownvote) { + val request = matchReuests[button.matchId] ?: return + val voter = event.interaction.user.id.asLong() + + if (getPlayers(request).contains(voter)) { + val swap = request.swaps[button.suggester] ?: return + + swap.upVotes.remove(voter) + swap.downVotes.add(voter) + + updateSwapRequest(request, button.suggester) + } + event.deferEdit().awaitSafe() + } + private suspend fun handleInteraction(event: ComponentInteractionEvent, button: ButtonRequestFill) { val matchId = button.matchId; val dropper = event.interaction.user.id @@ -923,6 +1135,10 @@ class QueueMessageService : AutostartService, KoinComponent { SelectMatchSetupServer.parse(event.customId)?.also { handleInteraction(event, it); return } SelectMatchSetupCreatures.parse(event.customId)?.also { handleInteraction(event, it); return } ButtonMatchStartServer.parse(event.customId)?.also { handleInteraction(event, it); return } + ButtonSuggestSwap.parse(event.customId)?.also { handleInteraction(event, it); return } + ButtonUpvote.parse(event.customId)?.also { handleInteraction(event, it); return } + ButtonDownvote.parse(event.customId)?.also { handleInteraction(event, it); return } + SelectPickSwap.parse(event.customId)?.also { handleInteraction(event, it); return } } private suspend fun notifyPlayer(player: Snowflake, channel: Snowflake) { diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/service/RatingServiceImpl.kt b/src/main/kotlin/de/bigboot/ggtools/fang/service/RatingServiceImpl.kt index bc5c537..736fa6d 100644 --- a/src/main/kotlin/de/bigboot/ggtools/fang/service/RatingServiceImpl.kt +++ b/src/main/kotlin/de/bigboot/ggtools/fang/service/RatingServiceImpl.kt @@ -97,6 +97,14 @@ class RatingServiceImpl : RatingService, KoinComponent { } override fun teamDifferential(teams: Pair, List>): Double { - return teams.first.map{findUser(it).rating}.sum()/teams.second.map{findUser(it).rating}.sum() + val teamOne = teams.first.map{findUser(it).rating}.sum() + val teamTwo = teams.second.map{findUser(it).rating}.sum() + + if (teamOne > teamTwo) { + return teamOne/teamTwo + } + else { + return teamTwo/teamOne + } } } From 05cf3f29e889c769ac730f6dd2879ccd7032ba57 Mon Sep 17 00:00:00 2001 From: Arthur Melton Date: Sat, 10 Jun 2023 10:18:00 -0400 Subject: [PATCH 26/28] Delete drop messages when match is done --- .../ggtools/fang/service/QueueMessageService.kt | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt b/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt index 65453f3..61135e1 100644 --- a/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt +++ b/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt @@ -340,6 +340,12 @@ class QueueMessageService : AutostartService, KoinComponent { updateMatchReadyMessage(matchId) request.message.deleteAfter(90.minutes) { + for ((_, drop) in request.drops) { + if (drop.message != null) { + drop.message!!.delete().await() + } + } + for (player in getPlayers(request)) { matchService.leave(request.queue, player, true) } @@ -397,6 +403,12 @@ class QueueMessageService : AutostartService, KoinComponent { request.message.deleteAfter(60.seconds) + for ((_, drop) in request.drops) { + if (drop.message != null) { + drop.message!!.delete().await() + } + } + updateQueueMessage(request.queue) } From 7ac73c79d2a50056135cc697b249cba876082446 Mon Sep 17 00:00:00 2001 From: Arthur Melton Date: Sat, 10 Jun 2023 10:24:27 -0400 Subject: [PATCH 27/28] update change log --- .../de/bigboot/ggtools/fang/service/ChangelogServiceImpl.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/service/ChangelogServiceImpl.kt b/src/main/kotlin/de/bigboot/ggtools/fang/service/ChangelogServiceImpl.kt index 80e9c7d..0037f4c 100644 --- a/src/main/kotlin/de/bigboot/ggtools/fang/service/ChangelogServiceImpl.kt +++ b/src/main/kotlin/de/bigboot/ggtools/fang/service/ChangelogServiceImpl.kt @@ -86,7 +86,9 @@ private val CHANGELOG = listOf( ), listOf( "Added raking system", - "Added matchmaking" + "Added matchmaking", + "Add a request a drop button", + "Can make a swap through the bot" ) ) From 239ee09c4605a12ade50d9a9cb8b44013d288761 Mon Sep 17 00:00:00 2001 From: Arthur Melton Date: Sat, 10 Jun 2023 10:29:12 -0400 Subject: [PATCH 28/28] fix request fill --- .../de/bigboot/ggtools/fang/service/QueueMessageService.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt b/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt index 61135e1..80d12d8 100644 --- a/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt +++ b/src/main/kotlin/de/bigboot/ggtools/fang/service/QueueMessageService.kt @@ -55,7 +55,7 @@ enum class SwapState { data class FillRequest( val matchRequest: UUID, var message: Message? = null, - var state: DropState = DropState.DONE, + var state: DropState = DropState.WAITING, var filler: Snowflake? = null, ) @@ -545,7 +545,6 @@ class QueueMessageService : AutostartService, KoinComponent { if (fill.state == DropState.DONE || (fill.state == DropState.WAITING_PLAYER && !timeout)) { return } - var message = fill.message; if(matchService.getNumPlayers(request.queue, request.pop.server) >= 1) {