Skip to content

Commit

Permalink
Replace event logging system with post-game logs (#19)
Browse files Browse the repository at this point in the history
* Replace event logging system with post-game logs

* Only log games that have started
  • Loading branch information
FluxCapacitor2 authored Mar 10, 2024
1 parent 190709f commit 916a8bc
Show file tree
Hide file tree
Showing 11 changed files with 153 additions and 156 deletions.
93 changes: 72 additions & 21 deletions common/src/main/kotlin/com/bluedragonmc/server/Game.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,21 @@ import com.bluedragonmc.server.event.GameEvent
import com.bluedragonmc.server.event.GameStartEvent
import com.bluedragonmc.server.event.GameStateChangedEvent
import com.bluedragonmc.server.event.PlayerLeaveGameEvent
import com.bluedragonmc.server.model.EventLog
import com.bluedragonmc.server.model.Severity
import com.bluedragonmc.server.model.GameDocument
import com.bluedragonmc.server.model.InstanceRecord
import com.bluedragonmc.server.model.PlayerRecord
import com.bluedragonmc.server.model.TeamRecord
import com.bluedragonmc.server.module.GameModule
import com.bluedragonmc.server.module.database.StatisticsModule
import com.bluedragonmc.server.module.instance.InstanceModule
import com.bluedragonmc.server.module.minigame.SpawnpointModule
import com.bluedragonmc.server.module.minigame.TeamModule
import com.bluedragonmc.server.module.minigame.WinModule
import com.bluedragonmc.server.service.Database
import com.bluedragonmc.server.service.Messaging
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
Expand Down Expand Up @@ -80,6 +86,9 @@ abstract class Game(val name: String, val mapName: String, val mode: String? = n
'a' + Random.nextInt(0, 26)
}.joinToString("")

private lateinit var startTime: Date
private lateinit var winningTeam: TeamModule.Team

open val maxPlayers = 8

var state: GameState = GameState.SERVER_STARTING
Expand Down Expand Up @@ -124,14 +133,10 @@ abstract class Game(val name: String, val mapName: String, val mode: String? = n
}
}
onGameStart {
Database.IO.launch {
Database.connection.logEvent(
EventLog("game_started", Severity.DEBUG)
.withProperty("game_id", id)
.withProperty("players", players.map { it.uuid.toString() })
.withProperty("modules", modules.map { it.toString() })
)
}
startTime = Date()
}
handleEvent<WinModule.WinnerDeclaredEvent> { event ->
winningTeam = event.winningTeam
}
}

Expand Down Expand Up @@ -224,11 +229,65 @@ abstract class Game(val name: String, val mapName: String, val mode: String? = n
}

open fun endGame(queueAllPlayers: Boolean = true) {

// Log some information about the game in the database

val statHistory = getModuleOrNull<StatisticsModule>()?.getHistory()
val teams = getModuleOrNull<TeamModule>()?.teams?.map { team ->
TeamRecord(
name = team.name.toPlainText(),
players = team.players.map { player ->
PlayerRecord(
uuid = player.uuid,
username = player.username
)
}
)
}
val winningTeamRecord = if (::winningTeam.isInitialized) {
TeamRecord(
name = winningTeam.name.toPlainText(),
players = winningTeam.players.map { player ->
PlayerRecord(
uuid = player.uuid,
username = player.username
)
})
} else null

val instanceRecords = getOwnedInstances().map { instance ->
InstanceRecord(
type = instance::class.jvmName,
uuid = instance.uniqueId
)
}

Database.IO.launch {
if (::startTime.isInitialized) {
Database.connection.logGame(
GameDocument(
gameId = id,
serverId = Environment.getServerName(),
gameType = name,
mapName = mapName,
mode = mode,
statistics = statHistory,
teams = teams,
winningTeam = winningTeamRecord,
startTime = startTime,
endTime = Date(),
instances = instanceRecords
)
)
}
}

state = GameState.ENDING
games.remove(this)

// the NotifyInstanceRemovedMessage is published when the MessagingModule is unregistered
while (modules.isNotEmpty()) unregister(modules.first())
modules.forEach { it.deinitialize() }

if (queueAllPlayers) {
players.forEach {
it.sendMessage(Component.translatable("game.status.ending", NamedTextColor.GREEN))
Expand All @@ -242,12 +301,14 @@ abstract class Game(val name: String, val mapName: String, val mode: String? = n
})
}
}

MinecraftServer.getSchedulerManager().buildTask {
MinecraftServer.getInstanceManager().instances.filter { this.ownsInstance(it) }.forEach { instance ->
logger.info("Forcefully unregistering instance ${instance.uniqueId}...")
InstanceUtils.forceUnregisterInstance(instance).join()
}
}.executionType(ExecutionType.ASYNC).delay(Duration.ofSeconds(10))

players.clear()
}

Expand Down Expand Up @@ -287,16 +348,6 @@ abstract class Game(val name: String, val mapName: String, val mode: String? = n

// Allow the game to start receiving events
MinecraftServer.getGlobalEventHandler().addChild(eventNode)

Database.IO.launch {
Database.connection.logEvent(
EventLog("game_created", Severity.DEBUG)
.withProperty("game_id", id)
.withProperty("game_type", name)
.withProperty("map_name", mapName)
.withProperty("mode", mode)
)
}
}

protected abstract fun initialize()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.bluedragonmc.server.api

import com.bluedragonmc.server.CustomPlayer
import com.bluedragonmc.server.model.EventLog
import com.bluedragonmc.server.model.GameDocument
import com.bluedragonmc.server.model.PlayerDocument
import net.minestom.server.entity.Player
import java.util.*
Expand All @@ -23,5 +23,5 @@ interface DatabaseConnection {

suspend fun <T> updatePlayer(playerUuid: String, field: KMutableProperty<T>, value: T)

suspend fun logEvent(event: EventLog)
suspend fun logGame(game: GameDocument)
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@

package com.bluedragonmc.server.model

import com.bluedragonmc.server.api.Environment
import com.bluedragonmc.server.service.Database
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.*
import kotlinx.serialization.EncodeDefault
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import java.time.Duration
import java.time.Instant
import java.util.*
Expand Down Expand Up @@ -79,23 +80,43 @@ data class Achievement(
@Serializable(with = DateSerializer::class) val earnedAt: Date,
)

enum class Severity {
TRACE, DEBUG, INFO, WARN, ERROR, FATAL
}
@Serializable
data class PlayerRecord(
@SerialName("_id") @Serializable(with = UUIDSerializer::class) val uuid: UUID,
val username: String,
)

data class EventLog(
val type: String,
val severity: Severity,
) {
companion object {
private val serverName = runBlocking { Environment.getServerName() }
}
@Serializable
data class TeamRecord(
@SerialName("_id") val name: String,
val players: List<PlayerRecord>,
)

val date: Long = System.currentTimeMillis()
val node: String = serverName
val properties = mutableMapOf<String, @Contextual Any?>()
@Serializable
data class StatisticRecord(
val key: String,
val player: PlayerRecord,
val oldValue: Double?,
val newValue: Double,
)

fun withProperty(name: String, value: Any?) = apply {
properties[name] = value
}
}
@Serializable
data class InstanceRecord(
@SerialName("_id") @Serializable(with = UUIDSerializer::class) val uuid: UUID,
val type: String,
)

@Serializable
data class GameDocument(
val gameId: String,
val serverId: String,
val gameType: String,
val mode: String?,
val mapName: String,
@EncodeDefault val teams: List<TeamRecord>? = listOf(),
val winningTeam: TeamRecord?,
@EncodeDefault val statistics: List<StatisticRecord>? = listOf(),
@Serializable(with = DateSerializer::class) val startTime: Date?,
@Serializable(with = DateSerializer::class) val endTime: Date,
val instances: List<InstanceRecord>,
)
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import com.bluedragonmc.server.Game
import com.bluedragonmc.server.event.DataLoadedEvent
import com.bluedragonmc.server.event.PlayerLeaveGameEvent
import com.bluedragonmc.server.model.PlayerDocument
import com.bluedragonmc.server.model.PlayerRecord
import com.bluedragonmc.server.model.StatisticRecord
import com.bluedragonmc.server.module.GameModule
import com.bluedragonmc.server.module.database.StatisticsModule.StatisticRecorder
import com.bluedragonmc.server.module.minigame.WinModule
Expand Down Expand Up @@ -32,7 +34,7 @@ import java.util.function.Predicate
* It is recommended, however not required, to use a
* [StatisticRecorder] to record statistics
*/
class StatisticsModule(private vararg val recorders : StatisticRecorder) : GameModule() {
class StatisticsModule(private vararg val recorders: StatisticRecorder) : GameModule() {

companion object {
// Caches should be static to reduce the number of DB queries
Expand Down Expand Up @@ -103,15 +105,42 @@ class StatisticsModule(private vararg val recorders : StatisticRecorder) : GameM
}
}

override fun deinitialize() {
history.clear()
}

private val history = mutableMapOf<Pair<Player, String>, Pair<Double?, Double>>()

fun getHistory(): List<StatisticRecord> {
return history.map { (playerAndKey, values) ->
val (player, key) = playerAndKey
val (oldValue, newValue) = values
StatisticRecord(
key = key,
player = PlayerRecord(uuid = player.uuid, username = player.username),
oldValue = oldValue,
newValue = newValue
)
}
}

/**
* Records a statistic for the [player] using the [key] and provided [value].
* The operation may be delayed as statistic updates are batched.
*/
fun recordStatistic(player: Player, key: String, value: Double) {
player as CustomPlayer

// Record the change in the game's statistic history for logging purposes
if (history.containsKey(player to key)) {
history[player to key] = history[player to key]?.first to value
} else {
history[player to key] = player.data.statistics[key] to value
}

// Update the local player data to reflect the change
player.data.statistics[key] = value

// Queue a database update operation
necessaryUpdates.getOrPut(player) { mutableSetOf() }.add(key)
}
Expand Down Expand Up @@ -208,7 +237,10 @@ class StatisticsModule(private vararg val recorders : StatisticRecorder) : GameM
return documents.associateWith { it.statistics[key]!! }
}

class EventStatisticRecorder<T : Event>(private val eventType: Class<T>, val handler: suspend StatisticsModule.(Game, T) -> Unit) : StatisticRecorder() {
class EventStatisticRecorder<T : Event>(
private val eventType: Class<T>,
val handler: suspend StatisticsModule.(Game, T) -> Unit,
) : StatisticRecorder() {
override fun subscribe(module: StatisticsModule, game: Game, eventNode: EventNode<Event>) {
eventNode.addListener(eventType) { event ->
Database.IO.launch { handler(module, game, event) }
Expand Down
1 change: 0 additions & 1 deletion src/main/kotlin/com/bluedragonmc/server/Server.kt
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ fun start() {
Commands,
CustomPlayerProvider,
DevInstanceRouter,
ExceptionHandler,
GlobalBlockHandlers,
GlobalChatFormat,
GlobalPlayerNameFormat,
Expand Down

This file was deleted.

Loading

0 comments on commit 916a8bc

Please sign in to comment.