diff --git a/imperium-common/src/main/kotlin/com/xpdustry/imperium/common/CommonModule.kt b/imperium-common/src/main/kotlin/com/xpdustry/imperium/common/CommonModule.kt index f9f6b03e..183d9eda 100644 --- a/imperium-common/src/main/kotlin/com/xpdustry/imperium/common/CommonModule.kt +++ b/imperium-common/src/main/kotlin/com/xpdustry/imperium/common/CommonModule.kt @@ -21,7 +21,8 @@ import com.xpdustry.imperium.common.account.AccountManager import com.xpdustry.imperium.common.account.MongoAccountManager import com.xpdustry.imperium.common.account.MongoUserManager import com.xpdustry.imperium.common.account.UserManager -import com.xpdustry.imperium.common.application.ImperiumMetadata +import com.xpdustry.imperium.common.bridge.PlayerTracker +import com.xpdustry.imperium.common.bridge.RequestingPlayerTracker import com.xpdustry.imperium.common.config.ImageAnalysisConfig import com.xpdustry.imperium.common.config.ImperiumConfig import com.xpdustry.imperium.common.config.ImperiumConfigFactory @@ -54,6 +55,9 @@ import com.xpdustry.imperium.common.storage.MinioStorageBucket import com.xpdustry.imperium.common.storage.StorageBucket import com.xpdustry.imperium.common.translator.DeeplTranslator import com.xpdustry.imperium.common.translator.Translator +import com.xpdustry.imperium.common.version.ImperiumVersion +import java.util.function.Supplier +import kotlin.random.Random import kotlin.time.Duration.Companion.seconds import kotlin.time.toJavaDuration import okhttp3.OkHttpClient @@ -70,7 +74,7 @@ fun commonModule() = } } - single { SimpleDiscovery(get(), get(), get(), get()) } + single { SimpleDiscovery(get(), get("identifier"), get("discovery"), get()) } single { when (val config = get().network.vpnDetection) { @@ -81,7 +85,7 @@ fun commonModule() = single { when (val config = get().messenger) { - is MessengerConfig.RabbitMQ -> RabbitmqMessenger(config, get()) + is MessengerConfig.RabbitMQ -> RabbitmqMessenger(config, get("identifier")) } } @@ -91,8 +95,6 @@ fun commonModule() = } } - single { ImperiumMetadata() } - single { SimpleMongoProvider(get()) } single { MongoAccountManager(get()) } @@ -122,4 +124,14 @@ fun commonModule() = } single { SimpleSnowflakeGenerator(get()) } + + single>("discovery") { Supplier { Discovery.Data.Unknown } } + + single("identifier") { + get().server.name + "-" + Random.nextInt(0, 1000) + } + + single { ImperiumVersion(1, 1, 1) } + + single { RequestingPlayerTracker(get()) } } diff --git a/imperium-common/src/main/kotlin/com/xpdustry/imperium/common/application/ImperiumMetadata.kt b/imperium-common/src/main/kotlin/com/xpdustry/imperium/common/application/ImperiumMetadata.kt deleted file mode 100644 index 1067abca..00000000 --- a/imperium-common/src/main/kotlin/com/xpdustry/imperium/common/application/ImperiumMetadata.kt +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Imperium, the software collection powering the Xpdustry network. - * Copyright (C) 2023 Xpdustry - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.xpdustry.imperium.common.application - -import com.xpdustry.imperium.common.serialization.SerializableUUID -import com.xpdustry.imperium.common.version.ImperiumVersion -import java.util.UUID -import kotlinx.serialization.Serializable - -@Serializable -data class ImperiumMetadata( - val platform: ImperiumPlatform = ImperiumPlatform.UNKNOWN, - val version: ImperiumVersion = ImperiumVersion(1, 1, 1), - val identifier: SerializableUUID = UUID.randomUUID(), -) diff --git a/imperium-common/src/main/kotlin/com/xpdustry/imperium/common/application/ImperiumPlatform.kt b/imperium-common/src/main/kotlin/com/xpdustry/imperium/common/application/ImperiumPlatform.kt deleted file mode 100644 index 34040a19..00000000 --- a/imperium-common/src/main/kotlin/com/xpdustry/imperium/common/application/ImperiumPlatform.kt +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Imperium, the software collection powering the Xpdustry network. - * Copyright (C) 2023 Xpdustry - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.xpdustry.imperium.common.application - -enum class ImperiumPlatform { - MINDUSTRY, - DISCORD, - UNKNOWN, -} diff --git a/imperium-common/src/main/kotlin/com/xpdustry/imperium/common/bridge/PlayerTracker.kt b/imperium-common/src/main/kotlin/com/xpdustry/imperium/common/bridge/PlayerTracker.kt new file mode 100644 index 00000000..84c5e5c2 --- /dev/null +++ b/imperium-common/src/main/kotlin/com/xpdustry/imperium/common/bridge/PlayerTracker.kt @@ -0,0 +1,86 @@ +/* + * Imperium, the software collection powering the Xpdustry network. + * Copyright (C) 2023 Xpdustry + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.xpdustry.imperium.common.bridge + +import com.xpdustry.imperium.common.message.Message +import com.xpdustry.imperium.common.message.Messenger +import com.xpdustry.imperium.common.message.request +import com.xpdustry.imperium.common.security.Identity +import com.xpdustry.imperium.common.serialization.SerializableJInstant +import java.time.Instant +import kotlin.time.Duration.Companion.seconds +import kotlinx.serialization.Serializable + +interface PlayerTracker { + suspend fun getPlayerJoins(server: String): List? + + suspend fun getPlayerQuits(server: String): List? + + suspend fun getOnlinePlayers(server: String): List? + + suspend fun getPlayerEntry(tid: Long): Entry? + + @Serializable + data class Entry( + val player: Identity.Mindustry, + val tid: Long, + val timestamp: SerializableJInstant = Instant.now() + ) +} + +open class RequestingPlayerTracker(protected val messenger: Messenger) : PlayerTracker { + + override suspend fun getPlayerJoins(server: String): List? = + requestPlayerList(server, PlayerListRequest.Type.JOIN) + + override suspend fun getPlayerQuits(server: String): List? = + requestPlayerList(server, PlayerListRequest.Type.QUIT) + + override suspend fun getOnlinePlayers(server: String): List? = + requestPlayerList(server, PlayerListRequest.Type.ONLINE) + + private suspend fun requestPlayerList(server: String, type: PlayerListRequest.Type) = + messenger + .request(PlayerListRequest(server, type), timeout = 1.seconds) + ?.entries + + override suspend fun getPlayerEntry(tid: Long): PlayerTracker.Entry? = + messenger + .request(PlayerLookupRequest(tid), timeout = 1.seconds) + ?.entry + + @Serializable + protected data class PlayerListRequest( + val server: String, + val type: Type, + ) : Message { + enum class Type { + JOIN, + QUIT, + ONLINE + } + } + + @Serializable + protected data class PlayerListResponse(val entries: List) : Message + + @Serializable protected data class PlayerLookupRequest(val tid: Long) : Message + + @Serializable + protected data class PlayerLookupResponse(val entry: PlayerTracker.Entry) : Message +} diff --git a/imperium-common/src/main/kotlin/com/xpdustry/imperium/common/message/Messenger.kt b/imperium-common/src/main/kotlin/com/xpdustry/imperium/common/message/Messenger.kt index 2cbcca2a..9f82176c 100644 --- a/imperium-common/src/main/kotlin/com/xpdustry/imperium/common/message/Messenger.kt +++ b/imperium-common/src/main/kotlin/com/xpdustry/imperium/common/message/Messenger.kt @@ -40,7 +40,7 @@ interface Messenger { } fun interface FunctionListener { - suspend fun onMessage(message: M): R + suspend fun onMessage(message: M): R? } } diff --git a/imperium-common/src/main/kotlin/com/xpdustry/imperium/common/message/RabbitmqMessenger.kt b/imperium-common/src/main/kotlin/com/xpdustry/imperium/common/message/RabbitmqMessenger.kt index 6d2746f3..4025bc2a 100644 --- a/imperium-common/src/main/kotlin/com/xpdustry/imperium/common/message/RabbitmqMessenger.kt +++ b/imperium-common/src/main/kotlin/com/xpdustry/imperium/common/message/RabbitmqMessenger.kt @@ -26,7 +26,6 @@ import com.rabbitmq.client.Consumer import com.rabbitmq.client.Envelope import com.rabbitmq.client.ShutdownSignalException import com.xpdustry.imperium.common.application.ImperiumApplication -import com.xpdustry.imperium.common.application.ImperiumMetadata import com.xpdustry.imperium.common.async.ImperiumScope import com.xpdustry.imperium.common.config.MessengerConfig import com.xpdustry.imperium.common.misc.LoggerDelegate @@ -54,7 +53,7 @@ private typealias MessageOrRequest = Pair class RabbitmqMessenger( private val config: MessengerConfig.RabbitMQ, - private val metadata: ImperiumMetadata + private val identifier: String ) : Messenger, ImperiumApplication.Listener { internal val flows = ConcurrentHashMap, FlowWithCTag>() private lateinit var channel: Channel @@ -77,7 +76,7 @@ class RabbitmqMessenger( } } - connection = factory.newConnection(metadata.identifier.toString()) + connection = factory.newConnection(identifier) channel = connection.createChannel() channel.exchangeDeclare(IMPERIUM_EXCHANGE, BuiltinExchangeType.DIRECT, false, true, null) } @@ -132,7 +131,7 @@ class RabbitmqMessenger( val bytes = json.encodeToByteArray() val headers = mutableMapOf( - SENDER_HEADER to metadata.identifier.toString(), + SENDER_HEADER to identifier, JAVA_CLASS_HEADER to message::class.jvmName, ) if (request != null) { @@ -166,7 +165,7 @@ class RabbitmqMessenger( function: Messenger.FunctionListener ): Job { return handle(type) { (message, request) -> - val response = function.onMessage(message) + val response = function.onMessage(message) ?: return@handle publish0(response, false, request!!.copy(reply = true)) } } @@ -236,7 +235,7 @@ class RabbitmqMessenger( "Received ${type.simpleName ?: type.jvmName} message without sender header from $envelope") return@runBlocking } - if (sender == metadata.identifier.toString()) { + if (sender == identifier) { return@runBlocking } diff --git a/imperium-common/src/main/kotlin/com/xpdustry/imperium/common/network/Discovery.kt b/imperium-common/src/main/kotlin/com/xpdustry/imperium/common/network/Discovery.kt index 093b6cd9..6f8dfd6b 100644 --- a/imperium-common/src/main/kotlin/com/xpdustry/imperium/common/network/Discovery.kt +++ b/imperium-common/src/main/kotlin/com/xpdustry/imperium/common/network/Discovery.kt @@ -17,8 +17,52 @@ */ package com.xpdustry.imperium.common.network +import com.xpdustry.imperium.common.serialization.SerializableInetAddress +import com.xpdustry.imperium.common.version.MindustryVersion +import kotlinx.serialization.Serializable + interface Discovery { + val servers: Collection + fun heartbeat() - val servers: List + @Serializable data class Server(val name: String, val identifier: String, val data: Data) + + @Serializable + sealed interface Data { + @Serializable data object Unknown : Data + + @Serializable data object Discord : Data + + @Serializable + data class Mindustry( + val name: String, + val host: SerializableInetAddress, + val port: Int, + val mapName: String, + val description: String, + val wave: Int, + val playerCount: Int, + val playerLimit: Int, + val gameVersion: MindustryVersion, + val gameMode: GameMode, + val gameModeName: String?, + val state: State, + ) : Data { + + enum class State { + PLAYING, + PAUSED, + STOPPED + } + + enum class GameMode { + SURVIVAL, + SANDBOX, + ATTACK, + PVP, + EDITOR, + } + } + } } diff --git a/imperium-common/src/main/kotlin/com/xpdustry/imperium/common/network/DiscoveryMessage.kt b/imperium-common/src/main/kotlin/com/xpdustry/imperium/common/network/DiscoveryMessage.kt index 803da385..c7bca046 100644 --- a/imperium-common/src/main/kotlin/com/xpdustry/imperium/common/network/DiscoveryMessage.kt +++ b/imperium-common/src/main/kotlin/com/xpdustry/imperium/common/network/DiscoveryMessage.kt @@ -21,7 +21,7 @@ import com.xpdustry.imperium.common.message.Message import kotlinx.serialization.Serializable @Serializable -data class DiscoveryMessage(val info: ServerInfo, val type: Type) : Message { +data class DiscoveryMessage(val info: Discovery.Server, val type: Type) : Message { enum class Type { DISCOVER, UN_DISCOVER, diff --git a/imperium-common/src/main/kotlin/com/xpdustry/imperium/common/network/MindustryServerInfo.kt b/imperium-common/src/main/kotlin/com/xpdustry/imperium/common/network/MindustryServerInfo.kt deleted file mode 100644 index 01d79e83..00000000 --- a/imperium-common/src/main/kotlin/com/xpdustry/imperium/common/network/MindustryServerInfo.kt +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Imperium, the software collection powering the Xpdustry network. - * Copyright (C) 2023 Xpdustry - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.xpdustry.imperium.common.network - -import com.xpdustry.imperium.common.serialization.SerializableInetAddress -import com.xpdustry.imperium.common.version.MindustryVersion -import kotlinx.serialization.Serializable - -@Serializable -data class MindustryServerInfo( - val name: String, - val host: SerializableInetAddress, - val port: Int, - val mapName: String, - val description: String, - val wave: Int, - val playerCount: Int, - val playerLimit: Int, - val gameVersion: MindustryVersion, - val gameMode: GameMode, - val gameModeName: String?, -) { - enum class GameMode { - SURVIVAL, - SANDBOX, - ATTACK, - PVP, - EDITOR, - } -} diff --git a/imperium-common/src/main/kotlin/com/xpdustry/imperium/common/network/ServerInfo.kt b/imperium-common/src/main/kotlin/com/xpdustry/imperium/common/network/ServerInfo.kt deleted file mode 100644 index 2cfe0547..00000000 --- a/imperium-common/src/main/kotlin/com/xpdustry/imperium/common/network/ServerInfo.kt +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Imperium, the software collection powering the Xpdustry network. - * Copyright (C) 2023 Xpdustry - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.xpdustry.imperium.common.network - -import com.xpdustry.imperium.common.application.ImperiumMetadata -import kotlinx.serialization.Serializable - -@Serializable -data class ServerInfo( - val serverName: String, - val metadata: ImperiumMetadata, - val mindustry: MindustryServerInfo?, -) diff --git a/imperium-common/src/main/kotlin/com/xpdustry/imperium/common/network/SimpleDiscovery.kt b/imperium-common/src/main/kotlin/com/xpdustry/imperium/common/network/SimpleDiscovery.kt index c6ed5efe..ed7f93c4 100644 --- a/imperium-common/src/main/kotlin/com/xpdustry/imperium/common/network/SimpleDiscovery.kt +++ b/imperium-common/src/main/kotlin/com/xpdustry/imperium/common/network/SimpleDiscovery.kt @@ -23,7 +23,6 @@ import com.google.common.cache.RemovalCause import com.google.common.cache.RemovalListener import com.google.common.cache.RemovalNotification import com.xpdustry.imperium.common.application.ImperiumApplication -import com.xpdustry.imperium.common.application.ImperiumMetadata import com.xpdustry.imperium.common.async.ImperiumScope import com.xpdustry.imperium.common.config.ImperiumConfig import com.xpdustry.imperium.common.message.Messenger @@ -42,15 +41,15 @@ import kotlinx.coroutines.runBlocking class SimpleDiscovery( private val messenger: Messenger, - private val metadata: ImperiumMetadata, - private val mindustryServerProvider: Supplier, + private val identifier: String, + private val discoveryDataProvider: Supplier, private val config: ImperiumConfig, ) : Discovery, ImperiumApplication.Listener { - override val servers: List - get() = this._servers.asMap().values.toList() + override val servers: Collection + get() = this._servers.asMap().values - private val _servers: Cache = + private val _servers: Cache = CacheBuilder.newBuilder() .expireAfterWrite(45L, TimeUnit.SECONDS) .removalListener(DiscoveryRemovalListener()) @@ -59,17 +58,17 @@ class SimpleDiscovery( private lateinit var heartbeatJob: Job override fun onImperiumInit() { - logger.debug("Starting discovery as {}", metadata.identifier) + logger.debug("Starting discovery as {}", identifier) messenger.consumer { - if (it.info.serverName == config.server.name) { + if (it.info.name == config.server.name) { logger.warn("Received discovery message from another server with the same name.") } else if (it.type === DiscoveryMessage.Type.DISCOVER) { - logger.trace("Received discovery message from {}", it.info.metadata.identifier) - this._servers.put(it.info.metadata.identifier.toString(), it.info) + logger.trace("Received discovery message from {}", it.info.identifier) + this._servers.put(it.info.identifier, it.info) } else if (it.type === DiscoveryMessage.Type.UN_DISCOVER) { - this._servers.invalidate(it.info.metadata.identifier.toString()) - logger.debug("Undiscovered server {}", it.info.metadata.identifier) + this._servers.invalidate(it.info.identifier) + logger.debug("Undiscovered server {}", it.info.identifier) } } @@ -97,11 +96,12 @@ class SimpleDiscovery( logger.trace("Sending {} discovery message", type.name) messenger.publish( DiscoveryMessage( - ServerInfo(config.server.name, metadata, mindustryServerProvider.get()), type)) + Discovery.Server(config.server.name, identifier, discoveryDataProvider.get()), + type)) } - private inner class DiscoveryRemovalListener : RemovalListener { - override fun onRemoval(notification: RemovalNotification) { + private inner class DiscoveryRemovalListener : RemovalListener { + override fun onRemoval(notification: RemovalNotification) { if (notification.cause === RemovalCause.EXPIRED) { logger.debug("Server {} has timeout.", notification.key) } diff --git a/imperium-common/src/main/kotlin/com/xpdustry/imperium/common/translator/DeeplTranslator.kt b/imperium-common/src/main/kotlin/com/xpdustry/imperium/common/translator/DeeplTranslator.kt index 72192d82..aa7bf01b 100644 --- a/imperium-common/src/main/kotlin/com/xpdustry/imperium/common/translator/DeeplTranslator.kt +++ b/imperium-common/src/main/kotlin/com/xpdustry/imperium/common/translator/DeeplTranslator.kt @@ -24,16 +24,16 @@ import com.deepl.api.TranslatorOptions import com.google.common.cache.Cache import com.google.common.cache.CacheBuilder import com.xpdustry.imperium.common.application.ImperiumApplication -import com.xpdustry.imperium.common.application.ImperiumMetadata import com.xpdustry.imperium.common.async.ImperiumScope import com.xpdustry.imperium.common.config.ImperiumConfig import com.xpdustry.imperium.common.config.TranslatorConfig +import com.xpdustry.imperium.common.version.ImperiumVersion import java.time.Duration import java.util.Locale import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext -class DeeplTranslator(config: ImperiumConfig, metadata: ImperiumMetadata) : +class DeeplTranslator(config: ImperiumConfig, version: ImperiumVersion) : Translator, ImperiumApplication.Listener { private val translator: com.deepl.api.Translator private val cache: Cache @@ -49,7 +49,7 @@ class DeeplTranslator(config: ImperiumConfig, metadata: ImperiumMetadata) : config.translator.token.value, TranslatorOptions() .setTimeout(Duration.ofSeconds(3L)) - .setAppInfo("Imperium", metadata.version.toString()), + .setAppInfo("Imperium", version.toString()), ) cache = CacheBuilder.newBuilder() diff --git a/imperium-discord/build.gradle.kts b/imperium-discord/build.gradle.kts index 6d894a20..00baa7b0 100644 --- a/imperium-discord/build.gradle.kts +++ b/imperium-discord/build.gradle.kts @@ -33,11 +33,15 @@ tasks.shadowJar { } doFirst { - val file = temporaryDir.resolve("VERSION.txt") + val file = temporaryDir.resolve("imperium-version.txt") file.writeText(project.version.toString()) from(file) } + from(rootProject.fileTree("imperium-bundles")) { + into("com/xpdustry/imperium/bundles/") + } + mergeServiceFiles() minimize { exclude(dependency("org.jetbrains.kotlin:kotlin-.*:.*")) diff --git a/imperium-discord/src/main/kotlin/com/xpdustry/imperium/discord/DiscordModule.kt b/imperium-discord/src/main/kotlin/com/xpdustry/imperium/discord/DiscordModule.kt index e0779c1c..76c3ae0f 100644 --- a/imperium-discord/src/main/kotlin/com/xpdustry/imperium/discord/DiscordModule.kt +++ b/imperium-discord/src/main/kotlin/com/xpdustry/imperium/discord/DiscordModule.kt @@ -24,9 +24,8 @@ import com.xpdustry.imperium.common.config.ServerConfig import com.xpdustry.imperium.common.inject.get import com.xpdustry.imperium.common.inject.module import com.xpdustry.imperium.common.inject.single -import com.xpdustry.imperium.common.network.MindustryServerInfo -import com.xpdustry.imperium.discord.bridge.PlayerHistory -import com.xpdustry.imperium.discord.bridge.SimplePlayerHistory +import com.xpdustry.imperium.common.network.Discovery +import com.xpdustry.imperium.common.version.ImperiumVersion import com.xpdustry.imperium.discord.command.ButtonCommandRegistry import com.xpdustry.imperium.discord.command.SlashCommandRegistry import com.xpdustry.imperium.discord.content.AnukenMindustryContentHandler @@ -49,8 +48,6 @@ fun discordModule() = single("button") { ButtonCommandRegistry(get()) } - single> { Supplier { null } } - single { AnukenMindustryContentHandler(get("directory"), get()) } single { @@ -58,5 +55,10 @@ fun discordModule() = ?: error("The current server configuration is not Discord") } - single { SimplePlayerHistory(get()) } + single>("discovery") { Supplier { Discovery.Data.Discord } } + + single { + ImperiumVersion.parse( + this::class.java.getResourceAsStream("/imperium-version.txt")!!.reader().readText()) + } } diff --git a/imperium-discord/src/main/kotlin/com/xpdustry/imperium/discord/bridge/PlayerHistory.kt b/imperium-discord/src/main/kotlin/com/xpdustry/imperium/discord/bridge/PlayerHistory.kt deleted file mode 100644 index d93b8a47..00000000 --- a/imperium-discord/src/main/kotlin/com/xpdustry/imperium/discord/bridge/PlayerHistory.kt +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Imperium, the software collection powering the Xpdustry network. - * Copyright (C) 2023 Xpdustry - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.xpdustry.imperium.discord.bridge - -import com.xpdustry.imperium.common.application.ImperiumApplication -import com.xpdustry.imperium.common.bridge.MindustryPlayerMessage -import com.xpdustry.imperium.common.collection.LimitedList -import com.xpdustry.imperium.common.message.Messenger -import com.xpdustry.imperium.common.message.consumer -import com.xpdustry.imperium.common.security.Identity -import java.time.Instant -import java.util.concurrent.ConcurrentHashMap -import kotlin.random.Random -import kotlin.random.nextInt - -interface PlayerHistory { - fun getPlayerJoins(server: String): List? - - fun getPlayerQuits(server: String): List? - - fun getPlayerEntry(tid: Int): Entry? - - data class Entry( - val player: Identity.Mindustry, - val tid: Int, - val timestamp: Instant = Instant.now() - ) -} - -class SimplePlayerHistory(private val messenger: Messenger) : - PlayerHistory, ImperiumApplication.Listener { - - private val joins = ConcurrentHashMap>() - private val quits = ConcurrentHashMap>() - - override fun onImperiumInit() { - messenger.consumer { - val map = - when (it.action) { - MindustryPlayerMessage.Action.Join -> joins - MindustryPlayerMessage.Action.Quit -> quits - else -> return@consumer - } - val entry = PlayerHistory.Entry(it.player, Random.nextInt(100000..999999)) - map.computeIfAbsent(it.serverName) { LimitedList(30) }.add(entry) - } - } - - override fun getPlayerJoins(server: String): List? = joins[server] - - override fun getPlayerQuits(server: String): List? = quits[server] - - override fun getPlayerEntry(tid: Int): PlayerHistory.Entry? { - for (list in listOf(joins, quits)) { - for (entries in list.values) { - for (entry in entries) { - if (entry.tid == tid) { - return entry - } - } - } - } - return null - } -} diff --git a/imperium-discord/src/main/kotlin/com/xpdustry/imperium/discord/commands/PlayerCommand.kt b/imperium-discord/src/main/kotlin/com/xpdustry/imperium/discord/commands/PlayerCommand.kt index df3faf0b..28796407 100644 --- a/imperium-discord/src/main/kotlin/com/xpdustry/imperium/discord/commands/PlayerCommand.kt +++ b/imperium-discord/src/main/kotlin/com/xpdustry/imperium/discord/commands/PlayerCommand.kt @@ -20,10 +20,10 @@ package com.xpdustry.imperium.discord.commands import com.xpdustry.imperium.common.account.Role import com.xpdustry.imperium.common.account.UserManager import com.xpdustry.imperium.common.application.ImperiumApplication +import com.xpdustry.imperium.common.bridge.PlayerTracker import com.xpdustry.imperium.common.command.Command import com.xpdustry.imperium.common.inject.InstanceManager import com.xpdustry.imperium.common.inject.get -import com.xpdustry.imperium.discord.bridge.PlayerHistory import com.xpdustry.imperium.discord.command.InteractionSender import java.net.InetAddress import java.time.ZoneOffset @@ -31,7 +31,7 @@ import java.time.format.DateTimeFormatter import org.javacord.api.entity.message.embed.EmbedBuilder class PlayerCommand(instances: InstanceManager) : ImperiumApplication.Listener { - private val history = instances.get() + private val tracker = instances.get() private val users = instances.get() // TODO @@ -40,9 +40,10 @@ class PlayerCommand(instances: InstanceManager) : ImperiumApplication.Listener { suspend fun onPlayerInfo(actor: InteractionSender, query: String) { val user = users.findByUuid(query) - ?: query.toIntOrNull()?.let(history::getPlayerEntry)?.let { - users.findByUuid(it.player.uuid) - } + ?: query + .toLongOrNull() + ?.let { tracker.getPlayerEntry(it) } + ?.let { users.findByUuid(it.player.uuid) } if (user == null) { actor.respond("Player not found.") return diff --git a/imperium-discord/src/main/kotlin/com/xpdustry/imperium/discord/commands/ServerCommand.kt b/imperium-discord/src/main/kotlin/com/xpdustry/imperium/discord/commands/ServerCommand.kt index 359c57bf..85e05c1a 100644 --- a/imperium-discord/src/main/kotlin/com/xpdustry/imperium/discord/commands/ServerCommand.kt +++ b/imperium-discord/src/main/kotlin/com/xpdustry/imperium/discord/commands/ServerCommand.kt @@ -18,11 +18,11 @@ package com.xpdustry.imperium.discord.commands import com.xpdustry.imperium.common.application.ImperiumApplication +import com.xpdustry.imperium.common.bridge.PlayerTracker import com.xpdustry.imperium.common.command.Command import com.xpdustry.imperium.common.inject.InstanceManager import com.xpdustry.imperium.common.inject.get import com.xpdustry.imperium.common.network.Discovery -import com.xpdustry.imperium.discord.bridge.PlayerHistory import com.xpdustry.imperium.discord.command.InteractionSender import com.xpdustry.imperium.discord.command.annotation.NonEphemeral import java.time.ZoneOffset @@ -33,7 +33,7 @@ import org.javacord.api.entity.message.embed.EmbedBuilder class ServerCommand(instances: InstanceManager) : ImperiumApplication.Listener { private val discovery = instances.get() - private val history = instances.get() + private val tracker = instances.get() @Command(["server", "list"]) @NonEphemeral @@ -42,13 +42,13 @@ class ServerCommand(instances: InstanceManager) : ImperiumApplication.Listener { EmbedBuilder() .setTitle("Server List") .setDescription( - discovery.servers.joinToString(separator = "\n") { " - " + it.serverName }), + discovery.servers.joinToString(separator = "\n") { "- ${it.name}" }), ) @Command(["server", "player", "joins"]) @NonEphemeral suspend fun onServerPlayerJoin(actor: InteractionSender, server: String) { - val joins = history.getPlayerJoins(server) + val joins = tracker.getPlayerJoins(server) if (joins == null) { actor.respond("Server not found.") return @@ -59,7 +59,7 @@ class ServerCommand(instances: InstanceManager) : ImperiumApplication.Listener { @Command(["server", "player", "quits"]) @NonEphemeral suspend fun onServerPlayerQuit(actor: InteractionSender, server: String) { - val quits = history.getPlayerQuits(server) + val quits = tracker.getPlayerQuits(server) if (quits == null) { actor.respond("Server not found.") return @@ -67,9 +67,20 @@ class ServerCommand(instances: InstanceManager) : ImperiumApplication.Listener { onServerPlayerList(actor, quits, "Quit") } + @Command(["server", "player", "online"]) + @NonEphemeral + suspend fun onServerPlayerOnline(actor: InteractionSender, server: String) { + val online = tracker.getOnlinePlayers(server) + if (online == null) { + actor.respond("Server not found.") + return + } + onServerPlayerList(actor, online, "Online") + } + private suspend fun onServerPlayerList( actor: InteractionSender, - list: List, + list: List, name: String ) { val text = buildString { diff --git a/imperium-mindustry/src/main/kotlin/com/xpdustry/imperium/mindustry/MindustryModule.kt b/imperium-mindustry/src/main/kotlin/com/xpdustry/imperium/mindustry/MindustryModule.kt index 2dff8144..92a25d9c 100644 --- a/imperium-mindustry/src/main/kotlin/com/xpdustry/imperium/mindustry/MindustryModule.kt +++ b/imperium-mindustry/src/main/kotlin/com/xpdustry/imperium/mindustry/MindustryModule.kt @@ -17,17 +17,19 @@ */ package com.xpdustry.imperium.mindustry +import com.xpdustry.imperium.common.bridge.PlayerTracker import com.xpdustry.imperium.common.command.CommandRegistry import com.xpdustry.imperium.common.commonModule import com.xpdustry.imperium.common.config.ImperiumConfig import com.xpdustry.imperium.common.config.ServerConfig -import com.xpdustry.imperium.common.inject.factory import com.xpdustry.imperium.common.inject.get import com.xpdustry.imperium.common.inject.module import com.xpdustry.imperium.common.inject.single import com.xpdustry.imperium.common.misc.toInetAddress -import com.xpdustry.imperium.common.network.MindustryServerInfo +import com.xpdustry.imperium.common.network.Discovery +import com.xpdustry.imperium.common.version.ImperiumVersion import com.xpdustry.imperium.common.version.MindustryVersion +import com.xpdustry.imperium.mindustry.bridge.MindustryPlayerTracker import com.xpdustry.imperium.mindustry.chat.ChatMessagePipeline import com.xpdustry.imperium.mindustry.chat.SimpleChatMessagePipeline import com.xpdustry.imperium.mindustry.command.MindustryCommandRegistry @@ -43,6 +45,7 @@ import java.net.InetAddress import java.nio.file.Path import java.util.function.Supplier import mindustry.Vars +import mindustry.core.GameState import mindustry.core.Version import mindustry.game.Gamemode import mindustry.net.Administration @@ -51,11 +54,6 @@ fun mindustryModule(plugin: ImperiumPlugin) = module("mindustry") { include(commonModule()) - factory> { - val config = get() - Supplier { getMindustryServerInfo(config) } - } - single { SimpleBlockHistory(get()) } single { plugin } @@ -74,14 +72,17 @@ fun mindustryModule(plugin: ImperiumPlugin) = } single { MindustryCommandRegistry(get(), get(), get()) } - } -private fun getMindustryServerInfo(config: ImperiumConfig): MindustryServerInfo? { - if (Vars.state.isMenu) { - return null + single>("discovery") { Supplier { getMindustryServerInfo() } } + + single { ImperiumVersion.parse(get().descriptor.version) } + + single { MindustryPlayerTracker(get(), get(), get()) } } - return MindustryServerInfo( - config.server.name, + +private fun getMindustryServerInfo(): Discovery.Data = + Discovery.Data.Mindustry( + Administration.Config.serverName.string(), // Our servers run within a pterodactyl container, so we can use the SERVER_IP environment // variable System.getenv("SERVER_IP")?.toInetAddress() ?: InetAddress.getLocalHost(), @@ -94,16 +95,19 @@ private fun getMindustryServerInfo(config: ImperiumConfig): MindustryServerInfo? getVersion(), getGameMode(), Vars.state.rules.modeName, - ) -} + when (Vars.state.state!!) { + GameState.State.playing -> Discovery.Data.Mindustry.State.PLAYING + GameState.State.paused -> Discovery.Data.Mindustry.State.PAUSED + GameState.State.menu -> Discovery.Data.Mindustry.State.STOPPED + }) -private fun getGameMode(): MindustryServerInfo.GameMode = +private fun getGameMode(): Discovery.Data.Mindustry.GameMode = when (Vars.state.rules.mode()!!) { - Gamemode.attack -> MindustryServerInfo.GameMode.ATTACK - Gamemode.pvp -> MindustryServerInfo.GameMode.PVP - Gamemode.sandbox -> MindustryServerInfo.GameMode.SANDBOX - Gamemode.survival -> MindustryServerInfo.GameMode.SURVIVAL - Gamemode.editor -> MindustryServerInfo.GameMode.EDITOR + Gamemode.attack -> Discovery.Data.Mindustry.GameMode.ATTACK + Gamemode.pvp -> Discovery.Data.Mindustry.GameMode.PVP + Gamemode.sandbox -> Discovery.Data.Mindustry.GameMode.SANDBOX + Gamemode.survival -> Discovery.Data.Mindustry.GameMode.SURVIVAL + Gamemode.editor -> Discovery.Data.Mindustry.GameMode.EDITOR } private fun getVersion(): MindustryVersion = diff --git a/imperium-mindustry/src/main/kotlin/com/xpdustry/imperium/mindustry/bridge/MindustryPlayerTracker.kt b/imperium-mindustry/src/main/kotlin/com/xpdustry/imperium/mindustry/bridge/MindustryPlayerTracker.kt new file mode 100644 index 00000000..23e20fc1 --- /dev/null +++ b/imperium-mindustry/src/main/kotlin/com/xpdustry/imperium/mindustry/bridge/MindustryPlayerTracker.kt @@ -0,0 +1,83 @@ +/* + * Imperium, the software collection powering the Xpdustry network. + * Copyright (C) 2023 Xpdustry + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.xpdustry.imperium.mindustry.bridge + +import com.xpdustry.imperium.common.application.ImperiumApplication +import com.xpdustry.imperium.common.async.ImperiumScope +import com.xpdustry.imperium.common.bridge.PlayerTracker +import com.xpdustry.imperium.common.bridge.RequestingPlayerTracker +import com.xpdustry.imperium.common.collection.LimitedList +import com.xpdustry.imperium.common.config.ServerConfig +import com.xpdustry.imperium.common.database.snowflake.SnowflakeGenerator +import com.xpdustry.imperium.common.message.Messenger +import com.xpdustry.imperium.common.message.function +import com.xpdustry.imperium.mindustry.misc.identity +import fr.xpdustry.distributor.api.event.EventHandler +import kotlinx.coroutines.launch +import mindustry.game.EventType + +class MindustryPlayerTracker( + messenger: Messenger, + private val config: ServerConfig.Mindustry, + private val snowflake: SnowflakeGenerator +) : RequestingPlayerTracker(messenger), ImperiumApplication.Listener { + + private val joins = LimitedList(30) + private val quits = LimitedList(30) + private val online = mutableMapOf() + + override fun onImperiumInit() { + messenger.function { + if (it.server != config.name) { + return@function null + } + PlayerListResponse( + when (it.type) { + PlayerListRequest.Type.JOIN -> joins + PlayerListRequest.Type.QUIT -> quits + PlayerListRequest.Type.ONLINE -> online.values.toList() + }) + } + + messenger.function { + for (list in listOf(joins, quits)) { + for (entry in list) { + if (entry.tid == it.tid) { + return@function PlayerLookupResponse(entry) + } + } + } + null + } + } + + @EventHandler + fun onPlayerJoin(event: EventType.PlayerJoin) = + ImperiumScope.MAIN.launch { + val entry = PlayerTracker.Entry(event.player.identity, snowflake.generate()) + joins.add(entry) + online[event.player.id] = entry + } + + @EventHandler + fun onPlayerQuit(event: EventType.PlayerLeave) = + ImperiumScope.MAIN.launch { + val entry = online.remove(event.player.id)!! + quits.add(entry) + } +}