Skip to content
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ dependencies {
implementation(libs.bundles.configurate) // Configurate for game configuration
implementation(libs.bundles.messaging) // Messaging
implementation(libs.okhttp)
implementation(libs.polar)

implementation(project(":common"))
}
Expand Down
64 changes: 20 additions & 44 deletions common/src/main/kotlin/com/bluedragonmc/server/Game.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package com.bluedragonmc.server

import com.bluedragonmc.api.grpc.CommonTypes
import com.bluedragonmc.api.grpc.CommonTypes.GameType.GameTypeFieldSelector
import com.bluedragonmc.api.grpc.gameState
import com.bluedragonmc.api.grpc.gameType
import com.bluedragonmc.server.api.Environment
import com.bluedragonmc.server.event.*
import com.bluedragonmc.server.game.GameData
import com.bluedragonmc.server.model.GameDocument
import com.bluedragonmc.server.model.InstanceRecord
import com.bluedragonmc.server.model.PlayerRecord
Expand All @@ -22,7 +22,6 @@ import com.bluedragonmc.server.utils.GameState
import com.bluedragonmc.server.utils.InstanceUtils
import com.bluedragonmc.server.utils.toPlainText
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import net.kyori.adventure.text.Component
import net.kyori.adventure.text.format.NamedTextColor
import net.minestom.server.MinecraftServer
Expand Down Expand Up @@ -51,18 +50,9 @@ import java.util.function.Predicate
import kotlin.random.Random
import kotlin.reflect.jvm.jvmName

abstract class Game(val name: String, val mapName: String, val mode: String? = null) : ModuleHolder(),
abstract class Game(val data: GameData) : ModuleHolder(),
PacketGroupingAudience {

val gameType: CommonTypes.GameType
get() = gameType {
name = this@Game.name
mapName = this@Game.mapName
if (this@Game.mode != null) {
mode = this@Game.mode
}
}

val rpcGameState: CommonTypes.GameState
get() = gameState {
gameState = state.mapToRpcState()
Expand All @@ -80,7 +70,7 @@ abstract class Game(val name: String, val mapName: String, val mode: String? = n
* A random, 4-character identifier unique to this game.
*/
val id = (0 until 4).map {
'a' + Random.nextInt(0, 26)
'a' + Random.Default.nextInt(0, 26)
}.joinToString("")

private lateinit var startTime: Date
Expand All @@ -95,7 +85,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 ->
protected val eventNode = EventNode.event("$id-$data", EventFilter.ALL) { event ->
try {
return@event when (event) {
is InstanceEvent -> ownsInstance(event.instance ?: return@event false)
Expand Down Expand Up @@ -135,7 +125,9 @@ abstract class Game(val name: String, val mapName: String, val mode: String? = n
startTime = Date()
}
handleEvent<WinModule.WinnerDeclaredEvent> { event ->
winningTeam = TeamRecord(event.winningTeamName.toPlainText(), event.winningTeamPlayers.map { PlayerRecord(it.uuid, it.username) })
winningTeam = TeamRecord(
event.winningTeamName.toPlainText(),
event.winningTeamPlayers.map { PlayerRecord(it.uuid, it.username) })
}
}

Expand Down Expand Up @@ -206,7 +198,6 @@ abstract class Game(val name: String, val mapName: String, val mode: String? = n
)
Environment.queue.queue(player, gameType {
name = Environment.defaultGameName
selectors += GameTypeFieldSelector.GAME_NAME
})
return AsyncUtils.empty()
}
Expand Down Expand Up @@ -263,9 +254,9 @@ abstract class Game(val name: String, val mapName: String, val mode: String? = n
GameDocument(
gameId = id,
serverId = Environment.getServerName(),
gameType = name,
mapName = mapName,
mode = mode,
gameType = data.name,
mapName = data.mapSource.id,
mode = data.mode,
statistics = statHistory,
teams = teams,
winningTeam = winningTeamRecord,
Expand All @@ -290,17 +281,13 @@ abstract class Game(val name: String, val mapName: String, val mode: String? = n
while (modules.isNotEmpty()) unregister(modules.first())

if (queueAllPlayers) {
players.forEach {
it.sendMessage(Component.translatable("game.status.ending", NamedTextColor.GREEN))
Environment.queue.queue(it, gameType {
name = this@Game.name
if (this@Game.mode != null) {
mode = this@Game.mode
selectors += GameTypeFieldSelector.GAME_MODE
}
selectors += GameTypeFieldSelector.GAME_NAME
})
val gameType = gameType {
name = data.name
if (data.mode != null) {
mode = data.mode
}
}
Environment.queue.bulkEnqueue(players.map { it to gameType })
}

MinecraftServer.getSchedulerManager().buildTask {
Expand All @@ -318,19 +305,8 @@ abstract class Game(val name: String, val mapName: String, val mode: String? = n
open fun isInactive(): Boolean {
// Games with players are always considered active
if (players.isNotEmpty()) return false
// Games without players are always inactive after 4 hours
if (System.currentTimeMillis() - creationTime >= 1_000 * 60 * 60 * 4) return true
// Games without players are always active in the first 30 minutes after being created
if (System.currentTimeMillis() - creationTime <= 1_000 * 60 * 30 && !playerHasJoined) return false

try {
return runBlocking {
Messaging.outgoing.checkRemoveInstance(id)
}
} catch (e: Throwable) {
e.printStackTrace()
return true
}
// Games without players are always inactive after 5 minutes
return System.currentTimeMillis() - creationTime >= 1_000 * 60 * 5
}

fun init() {
Expand Down Expand Up @@ -358,7 +334,7 @@ abstract class Game(val name: String, val mapName: String, val mode: String? = n
override fun toString(): String {
val modules = modules.joinToString { it::class.simpleName ?: it::class.jvmName }
val players = players.joinToString { it.username }
return "Game(id='$id', name='$name', mapName='$mapName', mode='$mode', modules=$modules, players=$players, maxPlayers=$maxPlayers, isJoinable=$isJoinable, state=$state)"
return "Game(id='$id', data=$data, modules=$modules, players=$players, maxPlayers=$maxPlayers, isJoinable=$isJoinable, state=$state)"
}

companion object {
Expand Down Expand Up @@ -402,7 +378,7 @@ abstract class Game(val name: String, val mapName: String, val mode: String? = n

games.forEach { game ->
if (game.isInactive()) {
logger.info("Ending inactive game ${game.id} (${game.name}/${game.mapName}/${game.mode})")
logger.info("Ending inactive game ${game.id} (${game.data})")
game.endGame(false)
}
game._players.removeIf { player -> !player.isOnline }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,19 @@ interface OutgoingRPCHandler {
suspend fun initGame(id: String, gameType: GameType, gameState: GameState)
suspend fun updateGameState(id: String, gameState: GameState)
suspend fun notifyInstanceRemoved(gameId: String)
suspend fun checkRemoveInstance(gameId: String): Boolean

// Player tracking
suspend fun recordInstanceChange(player: Player, newGame: String)
suspend fun playerTransfer(player: Player, newGame: String?)
suspend fun queryPlayer(username: String? = null, uuid: UUID? = null): QueryPlayerResponse

// Maps
suspend fun getAvailableMaps(gameName: String?, gameMode: String?, whitelist: List<UUID>?): com.bluedragonmc.api.grpc.Map.MapList
suspend fun updateMapConfig(mapId: String, configJson: String)

// Queue
suspend fun addToQueue(player: Player, gameType: GameType)
suspend fun bulkAddToQueue(messages: List<Pair<Player, GameType>>)
suspend fun removeFromQueue(player: Player)
suspend fun getDestination(player: UUID): String?

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package com.bluedragonmc.server.api

import com.bluedragonmc.api.grpc.*
import com.bluedragonmc.api.grpc.Map
import com.bluedragonmc.server.Game
import net.kyori.adventure.text.Component
import net.minestom.server.command.CommandSender
import net.minestom.server.entity.Player
import java.io.File
import java.nio.file.Paths
import java.util.*
import kotlin.io.path.name

/**
* Stub - no functionality. Used in development and testing environments.
Expand Down Expand Up @@ -37,10 +41,6 @@ class OutgoingRPCHandlerStub : OutgoingRPCHandler {

}

override suspend fun checkRemoveInstance(gameId: String): Boolean {
return true
}

override suspend fun recordInstanceChange(player: Player, newGame: String) {

}
Expand All @@ -53,10 +53,32 @@ class OutgoingRPCHandlerStub : OutgoingRPCHandler {
return PlayerTrackerOuterClass.QueryPlayerResponse.getDefaultInstance()
}

override suspend fun getAvailableMaps(gameName: String?, gameMode: String?, whitelist: List<UUID>?): Map.MapList {
val mapDefs = File("worlds").listFiles()
.flatMap { it.listFiles().map { file -> file.absolutePath } }
.map { mapFolderPath ->
CommonTypes.MapSource.newBuilder()
.setMapId(Paths.get(mapFolderPath).name)
.setMapConfig(File(mapFolderPath, "config.yml").readText())
.setMapFormat(CommonTypes.MapFormat.ANVIL)
.setMapUrl("file://$mapFolderPath")
.build()
}
return com.bluedragonmc.api.grpc.Map.MapList.newBuilder().addAllMaps(mapDefs).build()
}

override suspend fun updateMapConfig(mapId: String, configJson: String) {

}

override suspend fun addToQueue(player: Player, gameType: CommonTypes.GameType) {

}

override suspend fun bulkAddToQueue(messages: List<Pair<Player, CommonTypes.GameType>>) {

}

override suspend fun removeFromQueue(player: Player) {

}
Expand Down
5 changes: 1 addition & 4 deletions common/src/main/kotlin/com/bluedragonmc/server/api/Queue.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,12 @@ import com.bluedragonmc.api.grpc.GsClient
import com.bluedragonmc.api.grpc.PlayerHolderOuterClass.SendPlayerRequest
import com.bluedragonmc.server.Game
import net.minestom.server.entity.Player
import java.io.File

abstract class Queue {

abstract fun start()
abstract fun queue(player: Player, gameType: CommonTypes.GameType)

abstract fun getMaps(gameType: String): Array<File>?
abstract fun randomMap(gameType: String): String?
abstract fun bulkEnqueue(requests: List<Pair<Player, CommonTypes.GameType>>)

open fun createInstance(request: GsClient.CreateInstanceRequest): Game? {
throw NotImplementedError("Creating instances not implemented")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import net.minestom.server.command.builder.arguments.Argument
import net.minestom.server.coordinate.Point
import net.minestom.server.entity.Player
import net.minestom.server.utils.entity.EntityFinder
import java.util.function.Predicate

/**
* A basic command class that is extended by BlueDragon commands.
Expand Down Expand Up @@ -144,21 +143,6 @@ open class BlueDragonCommand(
private fun constructSubcommand(name: String, block: BlueDragonCommand.() -> Unit) =
BlueDragonCommand(name, emptyArray(), permission, block)

/**
* Only allow senders which pass the [scopePredicate]
* to execute the command. Primarily used to limit commands
* on a per-game basis.
*/
fun scopeTo(scopePredicate: Predicate<CommandSender>) {
conditions.add {
if (!scopePredicate.test(sender)) {
sender.sendMessage(Component.text("You can't use that command here!", NamedTextColor.RED))
return@add false
}
return@add true
}
}

fun syntax(vararg args: Argument<*>, block: CommandCtx.() -> Unit) = Syntax(this, args.toList(), block)
fun suspendSyntax(vararg args: Argument<*>, block: suspend CommandCtx.() -> Unit) =
BlockingSyntax(this, args.toList(), block)
Expand Down
20 changes: 20 additions & 0 deletions common/src/main/kotlin/com/bluedragonmc/server/game/GameData.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.bluedragonmc.server.game

import com.bluedragonmc.api.grpc.CommonTypes
import com.bluedragonmc.api.grpc.gameType
import com.bluedragonmc.server.service.Maps

data class GameData(
val name: String,
val mapSource: Maps.MapSource,
val mode: String? = null,
) {
val gameType: CommonTypes.GameType
get() = gameType {
name = this@GameData.name
mapId = mapSource.id
if (this@GameData.mode != null) {
mode = this@GameData.mode
}
}
}
Loading
Loading