diff --git a/src/main/kotlin/network/warzone/mars/Mars.kt b/src/main/kotlin/network/warzone/mars/Mars.kt index a5b624b..d480d61 100644 --- a/src/main/kotlin/network/warzone/mars/Mars.kt +++ b/src/main/kotlin/network/warzone/mars/Mars.kt @@ -56,6 +56,17 @@ class Mars : JavaPlugin() { return future } + fun asyncAsFutureWithResult(block: suspend() -> T) : CompletableFuture { + val future = CompletableFuture() + Bukkit.getScheduler().runTaskAsynchronously(get()) { + runBlocking { + val data = block.invoke() + future.complete(data) + } + } + return future + } + fun sync(block: () -> Unit) = Bukkit.getScheduler().runTask(get(), block) // Run next tick fun get() = instance diff --git a/src/main/kotlin/network/warzone/mars/leaderboard/LeaderboardClient.kt b/src/main/kotlin/network/warzone/mars/leaderboard/LeaderboardClient.kt new file mode 100644 index 0000000..7ceb6a5 --- /dev/null +++ b/src/main/kotlin/network/warzone/mars/leaderboard/LeaderboardClient.kt @@ -0,0 +1,23 @@ +package network.warzone.mars.leaderboard + +import network.warzone.mars.api.ApiClient + +object LeaderboardClient { + suspend fun fetchLeaderboardEntries(scoreType: LeaderboardScoreType, period: LeaderboardPeriod) : List { + return ApiClient.get("/mc/leaderboards/${scoreType.name}/${period.name}") + } +} + +data class LeaderboardEntry( + val id: String, + val name: String, + val score: Int +) + +enum class LeaderboardScoreType { + KILLS, DEATHS, XP +} + +enum class LeaderboardPeriod { + ALL_TIME, DAILY +} \ No newline at end of file diff --git a/src/main/kotlin/network/warzone/mars/leaderboard/LeaderboardCommands.kt b/src/main/kotlin/network/warzone/mars/leaderboard/LeaderboardCommands.kt new file mode 100644 index 0000000..ddc2e0c --- /dev/null +++ b/src/main/kotlin/network/warzone/mars/leaderboard/LeaderboardCommands.kt @@ -0,0 +1,85 @@ +package network.warzone.mars.leaderboard + +import app.ashcon.intake.Command +import app.ashcon.intake.bukkit.parametric.annotation.Sender +import net.kyori.adventure.text.Component +import net.kyori.adventure.text.format.NamedTextColor +import network.warzone.mars.Mars +import network.warzone.mars.player.commands.ModCommands +import network.warzone.mars.player.models.PlayerStats +import network.warzone.mars.utils.enumify +import network.warzone.mars.utils.getUsername +import network.warzone.mars.utils.strategy.multiLine +import org.bukkit.ChatColor +import org.bukkit.command.CommandSender +import tc.oc.pgm.api.PGM +import java.util.* +import javax.annotation.Nullable + +class LeaderboardCommands { + @Command(aliases = ["leaderboard", "lb"], desc = "View leaderboards", usage = "[scoreType] [period]") + fun onLeaderboardList( + @Sender sender: CommandSender, + @Nullable scoreType: String? = null, + @Nullable period: String? = null + ) { + val match = PGM.get().matchManager.getMatch(sender)!! + val audience: tc.oc.pgm.util.Audience = tc.oc.pgm.util.Audience.get(sender) // PGM Audience required for name formatting + val scoreTypeAndPeriod = validateScoreTypeAndPeriod(scoreType, period) + if (scoreTypeAndPeriod == null) { + sender.sendMessage("${ChatColor.RED}/lb " + + "${formatVariantsToUsageString(LeaderboardScoreType::class.java)} " + + "${formatVariantsToUsageString(LeaderboardPeriod::class.java)}" + ) + return + } + Mars.asyncAsFutureWithResult { + LeaderboardClient.fetchLeaderboardEntries(scoreTypeAndPeriod.first, scoreTypeAndPeriod.second) + }.thenApply { lbEntries -> + val message = audience.multiLine() + lbEntries.mapIndexed { idx, entry -> + val username = getUsername(UUID.fromString(entry.id), entry.name, match, offlineNameProvider = ModCommands.offlineNameProvider) + Component.text("${idx + 1}. ", NamedTextColor.GOLD) + .append(username).append(Component.text(": ", NamedTextColor.GRAY)) + .append(formatScore(scoreTypeAndPeriod.first, entry.score)) + }.forEach { + message.appendMultiLineComponent(it) + } + audience.sendMessage( + Component.text( + "${scoreTypeAndPeriod.first.name} leaderboard (${scoreTypeAndPeriod.second.name})", + NamedTextColor.GREEN + ) + ) + message.deliver() + } + } + + private fun formatScore(scoreType: LeaderboardScoreType, score: Int) : Component = + when (scoreType) { + LeaderboardScoreType.XP -> { + Component.text(score, NamedTextColor.YELLOW).append( + Component.text(" (Level: ${PlayerStats.EXP_FORMULA.getLevelFromExp(score.toDouble())})", + NamedTextColor.GRAY) + ) + } + else -> Component.text(score, NamedTextColor.YELLOW) + } + + private fun validateScoreTypeAndPeriod(scoreType: String?, period: String?) : Pair? { + if (scoreType == null) return null + val scoreType = getEnumVariant(scoreType) + val period = if (period == null) LeaderboardPeriod.ALL_TIME else getEnumVariant(period) + if (scoreType == null || period == null) return null + return scoreType to period + } + + private fun > formatVariantsToUsageString(enumClazz: Class) : String { + return "(" + enumClazz.enumConstants.joinToString(separator = "|") { it.name.toLowerCase() } + ")" + } + + private inline fun > getEnumVariant(name: String): T? { + val enumified = name.enumify() + return enumValues().firstOrNull { it.name == enumified } + } +} \ No newline at end of file diff --git a/src/main/kotlin/network/warzone/mars/player/feature/PlayerFeature.kt b/src/main/kotlin/network/warzone/mars/player/feature/PlayerFeature.kt index f8595dc..e6e111e 100644 --- a/src/main/kotlin/network/warzone/mars/player/feature/PlayerFeature.kt +++ b/src/main/kotlin/network/warzone/mars/player/feature/PlayerFeature.kt @@ -7,6 +7,7 @@ import network.warzone.mars.admin.AdminService import network.warzone.mars.api.ApiClient import network.warzone.mars.api.socket.models.SimplePlayer import network.warzone.mars.feature.NamedCachedFeature +import network.warzone.mars.leaderboard.LeaderboardCommands import network.warzone.mars.player.PlayerManager import network.warzone.mars.player.commands.ChatCommands import network.warzone.mars.player.commands.MiscCommands @@ -250,7 +251,7 @@ object PlayerFeature : NamedCachedFeature(), Listener { } override fun getCommands(): List { - return listOf(ModCommands(), MiscCommands(), StatCommands(), PerkCommands(), AdminCommands()) + return listOf(ModCommands(), MiscCommands(), StatCommands(), PerkCommands(), AdminCommands(), LeaderboardCommands()) } override fun getSubcommands(): Map, Any> {