From 2bd9be826fe36b182b97f171075d27c36e3d347e Mon Sep 17 00:00:00 2001 From: FluxCapacitor2 <31071265+FluxCapacitor2@users.noreply.github.com> Date: Mon, 23 Dec 2024 18:01:57 -0500 Subject: [PATCH] Add translations, commands, and messages necessary for party Marathon system --- .../kotlin/com/bluedragonmc/server/Game.kt | 2 +- .../server/api/OutgoingRPCHandler.kt | 6 +++ .../server/api/OutgoingRPCHandlerStub.kt | 16 +++++++ .../server/module/database/AwardsModule.kt | 8 +++- gradle/libs.versions.toml | 3 +- .../bluedragonmc/server/bootstrap/Commands.kt | 2 +- .../server/command/PartyCommand.kt | 32 +++++++++++++ .../server/impl/OutgoingRPCHandlerImpl.kt | 46 +++++++++++++++++++ src/main/resources/lang_en.properties | 15 +++++- 9 files changed, 125 insertions(+), 5 deletions(-) diff --git a/common/src/main/kotlin/com/bluedragonmc/server/Game.kt b/common/src/main/kotlin/com/bluedragonmc/server/Game.kt index 42b6112b..c83078d9 100644 --- a/common/src/main/kotlin/com/bluedragonmc/server/Game.kt +++ b/common/src/main/kotlin/com/bluedragonmc/server/Game.kt @@ -102,7 +102,7 @@ abstract class Game(val name: String, val mapName: String, val mode: String? = n protected val eventNode = EventNode.event("$name-$mapName-$mode", EventFilter.ALL) { event -> try { when (event) { - is InstanceEvent -> ownsInstance(event.instance) + is InstanceEvent -> ownsInstance(event.instance ?: return@event false) is GameEvent -> event.game === this is PlayerEvent -> event.player.isActive && ownsInstance(event.player.instance ?: return@event false) is ServerTickMonitorEvent -> true diff --git a/common/src/main/kotlin/com/bluedragonmc/server/api/OutgoingRPCHandler.kt b/common/src/main/kotlin/com/bluedragonmc/server/api/OutgoingRPCHandler.kt index 5ebdb7da..767df978 100644 --- a/common/src/main/kotlin/com/bluedragonmc/server/api/OutgoingRPCHandler.kt +++ b/common/src/main/kotlin/com/bluedragonmc/server/api/OutgoingRPCHandler.kt @@ -52,6 +52,12 @@ interface OutgoingRPCHandler { suspend fun transferParty(partyOwner: Player, newOwner: UUID) suspend fun listPartyMembers(member: UUID): PartyListResponse + // Party Marathons + suspend fun startMarathon(player: UUID, durationMs: Int) + suspend fun endMarathon(player: UUID) + suspend fun getMarathonLeaderboard(players: Collection, silent: Boolean) + suspend fun recordCoinAward(player: UUID, coins: Int, gameId: String) + // Jukebox controls suspend fun getSongInfo(player: Player): PlayerSongQueue suspend fun playSong(player: Player, songName: String, queuePosition: Int, startTimeInTicks: Int, tags: List): Boolean diff --git a/common/src/main/kotlin/com/bluedragonmc/server/api/OutgoingRPCHandlerStub.kt b/common/src/main/kotlin/com/bluedragonmc/server/api/OutgoingRPCHandlerStub.kt index b02db746..8821b39a 100644 --- a/common/src/main/kotlin/com/bluedragonmc/server/api/OutgoingRPCHandlerStub.kt +++ b/common/src/main/kotlin/com/bluedragonmc/server/api/OutgoingRPCHandlerStub.kt @@ -102,6 +102,22 @@ class OutgoingRPCHandlerStub : OutgoingRPCHandler { return PartySvc.PartyListResponse.getDefaultInstance() } + override suspend fun startMarathon(player: UUID, durationMs: Int) { + + } + + override suspend fun endMarathon(player: UUID) { + + } + + override suspend fun getMarathonLeaderboard(players: Collection, silent: Boolean) { + + } + + override suspend fun recordCoinAward(player: UUID, coins: Int, gameId: String) { + + } + override suspend fun getSongInfo(player: Player): JukeboxOuterClass.PlayerSongQueue { return playerSongQueue { isPlaying = false diff --git a/common/src/main/kotlin/com/bluedragonmc/server/module/database/AwardsModule.kt b/common/src/main/kotlin/com/bluedragonmc/server/module/database/AwardsModule.kt index 35b56826..cc3bfa46 100644 --- a/common/src/main/kotlin/com/bluedragonmc/server/module/database/AwardsModule.kt +++ b/common/src/main/kotlin/com/bluedragonmc/server/module/database/AwardsModule.kt @@ -4,6 +4,7 @@ import com.bluedragonmc.server.* import com.bluedragonmc.server.model.PlayerDocument import com.bluedragonmc.server.service.Database import com.bluedragonmc.server.module.GameModule +import com.bluedragonmc.server.service.Messaging import com.bluedragonmc.server.utils.* import kotlinx.coroutines.launch import net.kyori.adventure.sound.Sound @@ -19,7 +20,11 @@ import java.time.Duration class AwardsModule : GameModule() { - override fun initialize(parent: Game, eventNode: EventNode) {} + private lateinit var parent: Game + + override fun initialize(parent: Game, eventNode: EventNode) { + this.parent = parent + } fun awardCoins(player: Player, amount: Int, reason: Component) { player as CustomPlayer @@ -28,6 +33,7 @@ class AwardsModule : GameModule() { Database.IO.launch { player.data.compute(PlayerDocument::coins) { it + amount } player.data.compute(PlayerDocument::experience) { it + amount } + Messaging.outgoing.recordCoinAward(player.uuid, amount, parent.id) val newLevel = CustomPlayer.getXpLevel(player.data.experience).toInt() if (newLevel > oldLevel) MinecraftServer.getSchedulerManager().buildTask { notifyLevelUp(player, oldLevel, newLevel) } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 69bdfa33..fa79ff9e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -11,7 +11,7 @@ okhttp = "4.10.0" serialization = "1.5.0-RC" tinylog = "2.6.2" # Auto-generated GRPC/Protobuf messaging code -rpc = "53da6cbfed" +rpc = "18b740c038" # Agones SDK and its necessary runtime dependencies agones-kt = "0.1.2" grpc = "1.50.2" @@ -32,6 +32,7 @@ serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serializ caffeine = { group = "com.github.ben-manes.caffeine", name = "caffeine", version.ref = "caffeine" } okhttp = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttp" } rpc = { group = "com.github.bluedragonmc", name = "rpc", version.ref = "rpc" } +#rpc = { group = "com.bluedragonmc", name = "rpc", version = "1.0" } grpc-protobuf = { group = "io.grpc", name = "grpc-protobuf", version.ref = "grpc" } grpc-netty = { group = "io.grpc", name = "grpc-netty", version.ref = "grpc" } grpc-kotlin-stub = { group = "io.grpc", name = "grpc-kotlin-stub", version.ref = "grpc-kotlin-stub" } diff --git a/src/main/kotlin/com/bluedragonmc/server/bootstrap/Commands.kt b/src/main/kotlin/com/bluedragonmc/server/bootstrap/Commands.kt index 106b4957..8c167d34 100644 --- a/src/main/kotlin/com/bluedragonmc/server/bootstrap/Commands.kt +++ b/src/main/kotlin/com/bluedragonmc/server/bootstrap/Commands.kt @@ -29,7 +29,7 @@ object Commands : Bootstrap() { MessageCommand("msg", "message", "w", "tell"), MindecraftesCommand("mindecraftes", "/mindecraftes"), PardonCommand("pardon", "/pardon ", "unban", "unmute"), - PartyCommand("party", "/party ...", "p"), + PartyCommand("party", "/party ...", "p"), PartyChatShorthandCommand("pchat", "/pc ", "pc", "partychat"), PingCommand("ping", "/ping", "latency"), PlaysoundCommand("playsound", "/playsound [position] [volume] [pitch]", "ps"), diff --git a/src/main/kotlin/com/bluedragonmc/server/command/PartyCommand.kt b/src/main/kotlin/com/bluedragonmc/server/command/PartyCommand.kt index 64681507..ea6ff4bb 100644 --- a/src/main/kotlin/com/bluedragonmc/server/command/PartyCommand.kt +++ b/src/main/kotlin/com/bluedragonmc/server/command/PartyCommand.kt @@ -22,6 +22,9 @@ class PartyCommand(name: String, usageString: String, vararg aliases: String) : warp chat list + marathon start + marathon end + marathon leaderboard */ subcommand("invite") { @@ -93,6 +96,35 @@ class PartyCommand(name: String, usageString: String, vararg aliases: String) : } } + subcommand("marathon") { + usage("/party marathon [...]") + + subcommand("start") { + usage("/party marathon start [minutes]") + val minutesArgument by IntArgument + suspendSyntax(minutesArgument) { + val minutes = get(minutesArgument) + if (minutes in 5..300) { + Messaging.outgoing.startMarathon(player.uuid, get(minutesArgument) * 60 * 1_000) + } else { + sender.sendMessage(Component.translatable("puffin.party.marathon.invalid_time", NamedTextColor.RED)) + } + } + } + + subcommand("end") { + suspendSyntax { + Messaging.outgoing.endMarathon(player.uuid) + } + } + + subcommand("leaderboard") { + suspendSyntax { + Messaging.outgoing.getMarathonLeaderboard(setOf(player.uuid), false) + } + } + } + // If the player adds a player as the first argument instead of typing `invite ` val playerArgument by OfflinePlayerArgument suspendSyntax(playerArgument) { diff --git a/src/main/kotlin/com/bluedragonmc/server/impl/OutgoingRPCHandlerImpl.kt b/src/main/kotlin/com/bluedragonmc/server/impl/OutgoingRPCHandlerImpl.kt index 0b9716e0..11dae00c 100644 --- a/src/main/kotlin/com/bluedragonmc/server/impl/OutgoingRPCHandlerImpl.kt +++ b/src/main/kotlin/com/bluedragonmc/server/impl/OutgoingRPCHandlerImpl.kt @@ -9,6 +9,7 @@ import com.bluedragonmc.server.module.DependsOn import com.bluedragonmc.server.module.GameModule import com.bluedragonmc.server.module.instance.InstanceModule import com.bluedragonmc.server.service.Messaging +import com.bluedragonmc.server.utils.GameState import com.bluedragonmc.server.utils.listen import com.bluedragonmc.server.utils.listenAsync import com.bluedragonmc.server.utils.miniMessage @@ -25,6 +26,7 @@ import net.minestom.server.event.instance.AddEntityToInstanceEvent import net.minestom.server.event.player.PlayerDisconnectEvent import net.minestom.server.event.player.PlayerSpawnEvent import net.minestom.server.instance.Instance +import java.time.Duration import java.util.* import java.util.concurrent.TimeUnit @@ -57,6 +59,14 @@ class OutgoingRPCHandlerImpl(serverAddress: String, serverPort: Int) : OutgoingR eventNode.listenAsync { event -> Messaging.outgoing.updateGameState(parent.id, event.game.rpcGameState) + + if (event.newState == GameState.ENDING) { + MinecraftServer.getSchedulerManager().buildTask { + Messaging.IO.launch { + Messaging.outgoing.getMarathonLeaderboard(event.game.players.map { it.uuid }, true) + } + }.delay(Duration.ofSeconds(2)).schedule() + } } eventNode.listenAsync { event -> @@ -297,6 +307,42 @@ class OutgoingRPCHandlerImpl(serverAddress: String, serverPort: Int) : OutgoingR ) } + override suspend fun startMarathon(player: UUID, durationMs: Int) { + partyStub.withDeadlineAfter(5, TimeUnit.SECONDS).startMarathon( + PartySvc.StartMarathonRequest.newBuilder() + .setPlayerUuid(player.toString()) + .setDurationMs(durationMs) + .build() + ) + } + + override suspend fun endMarathon(player: UUID) { + partyStub.withDeadlineAfter(5, TimeUnit.SECONDS).stopMarathon( + PartySvc.StopMarathonRequest.newBuilder() + .setPlayerUuid(player.toString()) + .build() + ) + } + + override suspend fun getMarathonLeaderboard(players: Collection, silent: Boolean) { + partyStub.withDeadlineAfter(5, TimeUnit.SECONDS).getMarathonLeaderboard( + PartySvc.MarathonLeaderboardRequest.newBuilder() + .addAllPlayerUuids(players.map { it.toString() }) + .setSilent(silent) + .build() + ) + } + + override suspend fun recordCoinAward(player: UUID, coins: Int, gameId: String) { + partyStub.withDeadlineAfter(5, TimeUnit.SECONDS).recordCoinAward( + PartySvc.RecordCoinAwardRequest.newBuilder() + .setPlayerUuid(player.toString()) + .setCoins(coins) + .setGameId(gameId) + .build() + ) + } + override suspend fun getSongInfo(player: Player): JukeboxOuterClass.PlayerSongQueue { return jukeboxStub.withDeadlineAfter(5, TimeUnit.SECONDS).getSongInfo(songInfoRequest { playerUuid = player.uuid.toString() diff --git a/src/main/resources/lang_en.properties b/src/main/resources/lang_en.properties index cd42404d..a01146b9 100644 --- a/src/main/resources/lang_en.properties +++ b/src/main/resources/lang_en.properties @@ -485,4 +485,17 @@ puffin.party.list.members=Members ({0}): {1} puffin.party.list.not_found=You are not in a party. puffin.queue.not_enough_space=You couldn't be sent to {0}: Server is full -puffin.party.game_join_disallowed.not_leader=You must be the leader of a party to do this. \ No newline at end of file +puffin.party.game_join_disallowed.not_leader=You must be the leader of a party to do this. + +puffin.party.marathon_already_started=Your party already has a marathon in progress! +puffin.party.marathon.not_leader=You must be the party leader to do this. +puffin.party.marathon.started={0} has started a Marathon!\nCoins earned by party members will be tallied for the next {1}.\nThe player with the most coins gained wins! +puffin.party.marathon.started.time_period=\u23F0 {0} minutes +puffin.party.marathon.ended=Time's Up! The Marathon has ended! +puffin.party.marathon.ended_by_player={0} has ended the Marathon! +puffin.party.marathon.not_found=Your party does not have an active Marathon! +puffin.party.marathon.current_leaderboard=Current Marathon leaderboard: +puffin.party.marathon.current_leaderboard.no_points=No one has earned any coins yet! Earn coins to earn a place on your party's Marathon leaderboard. +puffin.party.marathon.invalid_time=The Marathon duration must be between 5 and 300 minutes. +puffin.party.marathon.time_remaining=Time remaining: {0} hours, {1} minutes, {2} seconds +puffin.party.marathon.outside_points=Note: Coins earned in games without the rest of your party don't count towards your party's Marathon.