From 776ad37446bdec286fd2ac489fb058ea12c4fc3c Mon Sep 17 00:00:00 2001 From: xXJanisXx Date: Sun, 15 Feb 2026 19:56:33 +0100 Subject: [PATCH 01/19] refactor: cleanup project structure --- .gitignore | 33 +--- .idea/codeStyles/Project.xml | 10 -- .idea/codeStyles/codeStyleConfig.xml | 5 - build.gradle.kts | 35 ++-- connection-bungeecord/build.gradle.kts | 14 +- .../bungeecord/BungeeCordCommand.kt | 54 ------- .../BungeeCordServerConnectionPlugin.kt | 79 --------- .../bungeecord/ConnectionReconnectHandler.kt | 31 ---- .../src/main/resources/bungee.yml | 7 - connection-shared/build.gradle.kts | 24 +-- .../shared/ConnectionAndTargetConfig.kt | 9 -- .../connection/shared/PermissionChecker.kt | 7 - .../shared/ServerConnectionPlugin.kt | 83 ---------- .../connection/shared/config/CommandConfig.kt | 13 -- .../plugin/connection/shared/config/Config.kt | 117 -------------- .../connection/shared/config/ConfigFactory.kt | 48 ------ .../shared/config/ConnectionConfig.kt | 11 -- .../connection/shared/config/RulesConfig.kt | 39 ----- .../shared/config/TargetConnectionConfig.kt | 11 -- .../connection/shared/config/TargetsConfig.kt | 10 -- .../shared/server/ServerConnectionInfo.kt | 6 - .../server/ServerConnectionInfoGetter.kt | 7 - connection-velocity/build.gradle.kts | 13 +- .../VelocityServerConnectionPlugin.kt | 150 ------------------ connection-waterdog/build.gradle.kts | 5 + gradle/libs.versions.toml | 56 +++++-- gradle/wrapper/gradle-wrapper.properties | 2 +- settings.gradle.kts | 4 +- 28 files changed, 84 insertions(+), 799 deletions(-) delete mode 100644 .idea/codeStyles/Project.xml delete mode 100644 .idea/codeStyles/codeStyleConfig.xml delete mode 100644 connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/BungeeCordCommand.kt delete mode 100644 connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/BungeeCordServerConnectionPlugin.kt delete mode 100644 connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/ConnectionReconnectHandler.kt delete mode 100644 connection-bungeecord/src/main/resources/bungee.yml delete mode 100644 connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/ConnectionAndTargetConfig.kt delete mode 100644 connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/PermissionChecker.kt delete mode 100644 connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/ServerConnectionPlugin.kt delete mode 100644 connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/CommandConfig.kt delete mode 100644 connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/Config.kt delete mode 100644 connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/ConfigFactory.kt delete mode 100644 connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/ConnectionConfig.kt delete mode 100644 connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/RulesConfig.kt delete mode 100644 connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/TargetConnectionConfig.kt delete mode 100644 connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/TargetsConfig.kt delete mode 100644 connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/server/ServerConnectionInfo.kt delete mode 100644 connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/server/ServerConnectionInfoGetter.kt delete mode 100644 connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/VelocityServerConnectionPlugin.kt create mode 100644 connection-waterdog/build.gradle.kts diff --git a/.gitignore b/.gitignore index b63da45..27b634f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,39 +1,8 @@ .gradle build/ -!gradle/wrapper/gradle-wrapper.jar -!**/src/main/**/build/ -!**/src/test/**/build/ ### IntelliJ IDEA ### -.idea/modules.xml -.idea/jarRepositories.xml -.idea/compiler.xml -.idea/libraries/ -*.iws -*.iml -*.ipr -out/ -!**/src/main/**/out/ -!**/src/test/**/out/ - -### Eclipse ### -.apt_generated -.classpath -.factorypath -.project -.settings -.springBeans -.sts4-cache -bin/ -!**/src/main/**/bin/ -!**/src/test/**/bin/ - -### NetBeans ### -/nbproject/private/ -/nbbuild/ -/dist/ -/nbdist/ -/.nb-gradle/ +.idea/ ### VS Code ### .vscode/ diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml deleted file mode 100644 index 51024d5..0000000 --- a/.idea/codeStyles/Project.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml deleted file mode 100644 index dcc2d5f..0000000 --- a/.idea/codeStyles/codeStyleConfig.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 398a12d..87a374a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,15 +1,16 @@ +import org.jetbrains.kotlin.gradle.dsl.KotlinVersion +import org.jetbrains.kotlin.gradle.dsl.JvmTarget import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar plugins { alias(libs.plugins.kotlin) alias(libs.plugins.shadow) - alias(libs.plugins.sonatype.central.portal.publisher) - `maven-publish` + id("maven-publish") } val baseVersion = "0.0.1" val commitHash = System.getenv("COMMIT_HASH") -val snapshotversion = "${baseVersion}-dev.$commitHash" +val snapshotversion = "${baseVersion}-platform.$commitHash" allprojects { group = "app.simplecloud.plugin" @@ -22,25 +23,29 @@ allprojects { maven("https://libraries.minecraft.net") maven("https://repo.papermc.io/repository/maven-public") maven("https://repo.simplecloud.app/snapshots") + maven("https://repo.waterdog.dev/releases/") + maven("https://repo.waterdog.dev/snapshots/") + maven("https://repo.opencollab.dev/maven-releases/") + maven("https://repo.opencollab.dev/maven-snapshots/") } } subprojects { apply(plugin = "org.jetbrains.kotlin.jvm") apply(plugin = "com.gradleup.shadow") - apply(plugin = "net.thebugmc.gradle.sonatype-central-portal-publisher") apply(plugin = "maven-publish") dependencies { testImplementation(rootProject.libs.kotlin.test) - compileOnly(rootProject.libs.kotlin.jvm) + implementation(rootProject.libs.kotlin.jvm) + implementation(rootProject.libs.kotlin.coroutines.core) } kotlin { jvmToolchain(21) compilerOptions { - apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0) - jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_21) + apiVersion.set(KotlinVersion.KOTLIN_2_0) + jvmTarget.set(JvmTarget.JVM_21) } } @@ -72,23 +77,11 @@ subprojects { } } - signing { - if (commitHash != null) { - return@signing - } - - sign(publishing.publications) - useGpgCmd() - } - tasks.named("shadowJar", ShadowJar::class) { mergeServiceFiles() + relocate("org.spongepowered", "app.simplecloud.plugin.relocate.spongepowered") archiveFileName.set("${project.name}.jar") - - val externalRelocatePath = "app.simplecloud.external" - relocate("kotlinx", "${externalRelocatePath}.kotlinx") - relocate("io", "${externalRelocatePath}.io") - relocate("org", "${externalRelocatePath}.org") + archiveClassifier.set("") } tasks.test { diff --git a/connection-bungeecord/build.gradle.kts b/connection-bungeecord/build.gradle.kts index d54bccb..54c4b30 100644 --- a/connection-bungeecord/build.gradle.kts +++ b/connection-bungeecord/build.gradle.kts @@ -4,9 +4,9 @@ plugins { dependencies { api(project(":connection-shared")) - api("net.kyori:adventure-text-minimessage:4.16.0") - api("net.kyori:adventure-platform-bungeecord:4.3.2") - compileOnly("net.md-5:bungeecord-api:1.20-R0.2") + compileOnly(libs.simplecloud.api) + compileOnly(libs.bungeecord.api) + implementation(libs.adventure.platform.bungeecord) } modrinth { @@ -16,9 +16,6 @@ modrinth { versionType.set("beta") uploadFile.set(tasks.shadowJar) gameVersions.addAll( - - - "1.20", "1.20.1", "1.20.2", @@ -38,10 +35,7 @@ modrinth { "1.21.9", "1.21.10", "1.21.11", - - - - ) + ) loaders.add("bungeecord") loaders.add("waterfall") changelog.set("https://docs.simplecloud.app/changelog") diff --git a/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/BungeeCordCommand.kt b/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/BungeeCordCommand.kt deleted file mode 100644 index 3e89b1e..0000000 --- a/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/BungeeCordCommand.kt +++ /dev/null @@ -1,54 +0,0 @@ -package app.simplecloud.plugin.connection.bungeecord - -import app.simplecloud.plugin.connection.shared.ServerConnectionPlugin -import app.simplecloud.plugin.connection.shared.config.CommandConfig -import net.kyori.adventure.text.minimessage.MiniMessage -import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer -import net.md_5.bungee.api.CommandSender -import net.md_5.bungee.api.ProxyServer -import net.md_5.bungee.api.connection.ProxiedPlayer -import net.md_5.bungee.api.plugin.Command - -/** - * @author Niklas Nieberler - */ - -class BungeeCordCommand( - private val serverConnection: ServerConnectionPlugin, - private val commandConfig: CommandConfig, - private val proxyServer: ProxyServer, - private val miniMessage: MiniMessage, -) : Command( - commandConfig.name, - commandConfig.permission, - *commandConfig.aliases.toTypedArray() -) { - - override fun execute(sender: CommandSender, args: Array) { - val player = sender as ProxiedPlayer? ?: return - - val currentServerName = player.server.info.name - val connectionToServerName = - this.serverConnection.getConnectionAndNameForCommand(player, this.commandConfig) - - if (connectionToServerName == null) { - player.sendMessage(*BungeeComponentSerializer.get().serialize(miniMessage.deserialize(commandConfig.noTargetConnectionFound))) - return - } - - if (currentServerName != null - && connectionToServerName.first.connectionConfig.serverNameMatcher.matches(currentServerName) - ) { - val miniMessageComponent = this.miniMessage.deserialize(this.commandConfig.alreadyConnectedMessage) - val component = BungeeComponentSerializer.get().serialize(miniMessageComponent) - player.sendMessage(*component) - return - } - - val serverInfo = this.proxyServer.getServerInfo(connectionToServerName.second) - if (serverInfo != null) { - player.connect(serverInfo) - } - } - -} \ No newline at end of file diff --git a/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/BungeeCordServerConnectionPlugin.kt b/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/BungeeCordServerConnectionPlugin.kt deleted file mode 100644 index feef894..0000000 --- a/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/BungeeCordServerConnectionPlugin.kt +++ /dev/null @@ -1,79 +0,0 @@ -package app.simplecloud.plugin.connection.bungeecord - -import app.simplecloud.plugin.connection.shared.PermissionChecker -import app.simplecloud.plugin.connection.shared.ServerConnectionPlugin -import app.simplecloud.plugin.connection.shared.server.ServerConnectionInfo -import app.simplecloud.plugin.connection.shared.server.ServerConnectionInfoGetter -import net.kyori.adventure.text.minimessage.MiniMessage -import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer -import net.md_5.bungee.api.chat.TextComponent -import net.md_5.bungee.api.connection.ProxiedPlayer -import net.md_5.bungee.api.event.ServerKickEvent -import net.md_5.bungee.api.plugin.Listener -import net.md_5.bungee.api.plugin.Plugin -import net.md_5.bungee.event.EventHandler - -/** - * @author Niklas Nieberler - */ - -class BungeeCordServerConnectionPlugin : Plugin(), Listener { - - private val serverConnection = ServerConnectionPlugin( - dataFolder.toPath(), - ServerConnectionInfoGetter { - proxy.servers.map { - ServerConnectionInfo( - it.key, - it.value.players.size - ) - } - }, - PermissionChecker { player, permission -> player.hasPermission(permission) } - ) - - private val miniMessage = MiniMessage.miniMessage() - - override fun onLoad() { - proxy.reconnectHandler = ConnectionReconnectHandler(this.serverConnection, proxy) - } - - override fun onEnable() { - val pluginManager = proxy.pluginManager - pluginManager.registerListener(this, this) - - this.serverConnection.getCommandConfigs().forEach { - val bungeeCommand = BungeeCordCommand( - this.serverConnection, - it, - proxy, - miniMessage - ) - pluginManager.registerCommand(this, bungeeCommand) - } - } - - @EventHandler - fun onServerKick(event: ServerKickEvent) { - if (event.isCancelled) { - return - } - - val connectionAndTargetConfigToServerName = serverConnection.getConnectionAndNameForFallback(event.player, event.kickedFrom.name) - if (connectionAndTargetConfigToServerName == null) { - event.reason = TextComponent.fromArray(*BungeeComponentSerializer.get().serialize( - miniMessage.deserialize( - serverConnection.config.fallbackConnectionsConfig.noTargetConnectionFoundMessage - ) - )) - event.cancelServer = null - event.isCancelled = true - return - } - - val serverInfo = proxy.getServerInfo(connectionAndTargetConfigToServerName.second) ?: return - - event.isCancelled = true - event.cancelServer = serverInfo - } -} \ No newline at end of file diff --git a/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/ConnectionReconnectHandler.kt b/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/ConnectionReconnectHandler.kt deleted file mode 100644 index 3cb04b1..0000000 --- a/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/ConnectionReconnectHandler.kt +++ /dev/null @@ -1,31 +0,0 @@ -package app.simplecloud.plugin.connection.bungeecord - -import app.simplecloud.plugin.connection.shared.ServerConnectionPlugin -import net.md_5.bungee.api.ProxyServer -import net.md_5.bungee.api.ReconnectHandler -import net.md_5.bungee.api.config.ServerInfo -import net.md_5.bungee.api.connection.ProxiedPlayer - -/** - * @author Niklas Nieberler - */ - -class ConnectionReconnectHandler( - private val serverConnection: ServerConnectionPlugin, - private val proxyServer: ProxyServer, -) : ReconnectHandler { - - override fun getServer(player: ProxiedPlayer?): ServerInfo { - if (player == null) - throw NullPointerException("failed to find player") - val serverName = this.serverConnection.getServerNameForLogin(player) - ?: throw NullPointerException("failed to find connected server") - return this.proxyServer.getServerInfo(serverName) - } - - override fun setServer(player: ProxiedPlayer?) {} - - override fun save() {} - - override fun close() {} -} \ No newline at end of file diff --git a/connection-bungeecord/src/main/resources/bungee.yml b/connection-bungeecord/src/main/resources/bungee.yml deleted file mode 100644 index 4e938e7..0000000 --- a/connection-bungeecord/src/main/resources/bungee.yml +++ /dev/null @@ -1,7 +0,0 @@ -name: simplecloud-connection -version: 0.0.1 -author: MrManHD - -main: app.simplecloud.plugin.connection.bungeecord.BungeeCordServerConnectionPlugin - -depends: [simplecloud-api] \ No newline at end of file diff --git a/connection-shared/build.gradle.kts b/connection-shared/build.gradle.kts index 43ee7a2..c6a7767 100644 --- a/connection-shared/build.gradle.kts +++ b/connection-shared/build.gradle.kts @@ -1,20 +1,8 @@ -import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar -import org.gradle.kotlin.dsl.named - dependencies { - compileOnly("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.2") - api("org.spongepowered:configurate-yaml:4.0.0") - api("org.spongepowered:configurate-extra-kotlin:4.1.2") { - exclude(group = "org.jetbrains.kotlin") - exclude(group = "org.jetbrains.kotlinx") - } - api("commons-io:commons-io:2.15.1") - api(rootProject.libs.simplecloud.plugin.api) -} - -tasks.named("shadowJar", ShadowJar::class) { - val externalRelocatePath = "app.simplecloud.external" - relocate("app.simplecloud.plugin.api", "${externalRelocatePath}.plugin.api") { - include("app.simplecloud.plugin.api.**") - } + compileOnly(libs.simplecloud.api) + implementation(libs.simplecloud.plugin.api) + implementation(libs.commons.io) + implementation(libs.bundles.adventure) + implementation(libs.bundles.logging) + implementation(libs.bundles.configurate) } \ No newline at end of file diff --git a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/ConnectionAndTargetConfig.kt b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/ConnectionAndTargetConfig.kt deleted file mode 100644 index 3334686..0000000 --- a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/ConnectionAndTargetConfig.kt +++ /dev/null @@ -1,9 +0,0 @@ -package app.simplecloud.plugin.connection.shared - -import app.simplecloud.plugin.connection.shared.config.ConnectionConfig -import app.simplecloud.plugin.connection.shared.config.TargetConnectionConfig - -data class ConnectionAndTargetConfig( - val connectionConfig: ConnectionConfig, - val targetConfig: TargetConnectionConfig, -) diff --git a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/PermissionChecker.kt b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/PermissionChecker.kt deleted file mode 100644 index 77827e1..0000000 --- a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/PermissionChecker.kt +++ /dev/null @@ -1,7 +0,0 @@ -package app.simplecloud.plugin.connection.shared - -fun interface PermissionChecker

{ - - fun checkPermission(player: P, permission: String): Boolean - -} \ No newline at end of file diff --git a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/ServerConnectionPlugin.kt b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/ServerConnectionPlugin.kt deleted file mode 100644 index 5d87c1c..0000000 --- a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/ServerConnectionPlugin.kt +++ /dev/null @@ -1,83 +0,0 @@ -package app.simplecloud.plugin.connection.shared - -import app.simplecloud.plugin.connection.shared.config.CommandConfig -import app.simplecloud.plugin.connection.shared.config.ConfigFactory -import app.simplecloud.plugin.connection.shared.config.ConnectionConfig -import app.simplecloud.plugin.connection.shared.config.TargetConnectionConfig -import app.simplecloud.plugin.connection.shared.server.ServerConnectionInfoGetter -import java.nio.file.Path - -class ServerConnectionPlugin

( - private val dataDirectory: Path, - private val serverConnectionInfoGetter: ServerConnectionInfoGetter, - private val permissionChecker: PermissionChecker

-) { - - val config = ConfigFactory.loadOrCreate(dataDirectory) - - fun getCommandConfigs(): List { - return config.commands - } - - fun getServerNameForLogin(player: P): String? { - return getConnectionAndNameForLogin(player)?.second - } - - fun getConnectionAndNameForLogin(player: P): Pair? { - return getConnectionAndName(player, config.networkJoinTargets.targetConnections) - } - - fun getConnectionAndNameForFallback(player: P, fromServerName: String): Pair? { - return getConnectionAndName(player, config.fallbackConnectionsConfig.targetConnections, fromServerName) - } - - fun getConnectionAndNameForCommand(player: P, commandConfig: CommandConfig): Pair? { - return getConnectionAndName(player, commandConfig.targetConnections) - } - - private fun getConnectionAndName(player: P, targetConnections: List, fromServerName: String = ""): Pair? { - val possibleConnections = getPossibleServerConnections(player) - val filteredTargetConnections = targetConnections.asSequence() - .filter { fromServerName.isBlank() || matchesTargetConnection(it, fromServerName) } - val possibleConnectionsWithTarget = possibleConnections.asSequence().mapNotNull { possibleConnection -> - val targetConfig = filteredTargetConnections - .firstOrNull { possibleConnection.name == it.name } ?: return@mapNotNull null - ConnectionAndTargetConfig(possibleConnection, targetConfig) - } - - val connectionAndTargetConfig = possibleConnectionsWithTarget.maxByOrNull { it.targetConfig.priority }?: return null - val bestServerToConnect = getBestServerToConnect(fromServerName, connectionAndTargetConfig.connectionConfig)?: return null - return Pair(connectionAndTargetConfig, bestServerToConnect) - } - - private fun matchesTargetConnection( - targetConnectionConfig: TargetConnectionConfig, - fromServerName: String - ): Boolean { - if (targetConnectionConfig.from.isEmpty()) - return true - return targetConnectionConfig.from.any { it.matches(fromServerName) } - } - - private fun getPossibleServerConnections( - player: P - ): List { - val serverConnectionInfos = serverConnectionInfoGetter.get() - val serverNames = serverConnectionInfos.map { it.name } - - return config.connections.filter { connection -> - connection.serverNameMatcher.anyMatches(serverNames) - && connection.rules.all { it.isAllowed(player, permissionChecker) } - } - } - - private fun getBestServerToConnect(fromServerName: String, bestConnection: ConnectionConfig): String? { - val serverConnectionInfos = serverConnectionInfoGetter.get() - val bestServer = serverConnectionInfos - .filter { it.name != fromServerName } - .sortedBy { it.onlinePlayers } - .firstOrNull { bestConnection.serverNameMatcher.matches(it.name) } - return bestServer?.name - } - -} \ No newline at end of file diff --git a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/CommandConfig.kt b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/CommandConfig.kt deleted file mode 100644 index ad980b6..0000000 --- a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/CommandConfig.kt +++ /dev/null @@ -1,13 +0,0 @@ -package app.simplecloud.plugin.connection.shared.config - -import org.spongepowered.configurate.objectmapping.ConfigSerializable - -@ConfigSerializable -data class CommandConfig( - val name: String = "", - val aliases: List = emptyList(), - val targetConnections: List = emptyList(), - val alreadyConnectedMessage: String = "You are already connected to this group!", - val noTargetConnectionFound: String = "Couldn't find a target connection!", - val permission: String = "", -) diff --git a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/Config.kt b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/Config.kt deleted file mode 100644 index 8e325eb..0000000 --- a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/Config.kt +++ /dev/null @@ -1,117 +0,0 @@ -package app.simplecloud.plugin.connection.shared.config - -import app.simplecloud.plugin.api.shared.matcher.MatcherType -import app.simplecloud.plugin.api.shared.matcher.ServerMatcherConfiguration -import org.spongepowered.configurate.objectmapping.ConfigSerializable -@ConfigSerializable -data class Config( - val version: String = "1", - val connections: List = emptyList(), - val networkJoinTargets: TargetsConfig = TargetsConfig( - noTargetConnectionFoundMessage = "Couldn't connect you to the network because no target servers are available." - ), - val fallbackConnectionsConfig: TargetsConfig = TargetsConfig( - noTargetConnectionFoundMessage = "You have been disconnected from the network since you have been kicked and no fallback server are available." - ), - val commands: List = emptyList(), -) { - companion object { - fun createDefaultConfig(): Config { - val defaultConnections = listOf( - ConnectionConfig( - name = "lobby", - serverNameMatcher = ServerMatcherConfiguration( - operation = MatcherType.STARTS_WITH, - value = "lobby" - ) - ), - ConnectionConfig( - name = "hub", - serverNameMatcher = ServerMatcherConfiguration( - operation = MatcherType.STARTS_WITH, - value = "hub" - ) - ), - ConnectionConfig( - name = "premium-lobby", - serverNameMatcher = ServerMatcherConfiguration( - operation = MatcherType.STARTS_WITH, - value = "premium" - ), - rules = listOf( - RulesConfig( - type = RulesConfig.Type.PERMISSION, - name = "simplecloud.connection.premium", - value = "true", - ) - ) - ), - ConnectionConfig( - name = "vip-lobby", - serverNameMatcher = ServerMatcherConfiguration( - operation = MatcherType.STARTS_WITH, - value = "vip" - ), - rules = listOf( - RulesConfig( - type = RulesConfig.Type.PERMISSION, - name = "simplecloud.connection.vip", - value = "true", - ) - ) - ), - ConnectionConfig( - name = "silent-lobby", - serverNameMatcher = ServerMatcherConfiguration( - operation = MatcherType.STARTS_WITH, - value = "silent" - ), - rules = listOf( - RulesConfig( - type = RulesConfig.Type.PERMISSION, - name = "simplecloud.connection.silent", - value = "true", - ) - ) - ) - ) - - val defaultTargetConnections = listOf( - TargetConnectionConfig("lobby", 0), - TargetConnectionConfig("hub", 0), - TargetConnectionConfig("premium-lobby", 10), - TargetConnectionConfig("vip-lobby", 20), - TargetConnectionConfig("silent-lobby", 20) - ) - - val networkJoinTargets = TargetsConfig( - enabled = true, - noTargetConnectionFoundMessage = "Couldn't connect you to the network because\nno target servers are available.", - targetConnections = defaultTargetConnections - ) - - val fallbackConnectionsConfig = TargetsConfig( - enabled = true, - noTargetConnectionFoundMessage = "You have been disconnected from the network\nbecause there are no fallback servers available.", - targetConnections = defaultTargetConnections - ) - - val defaultCommands = listOf( - CommandConfig( - name = "lobby", - alreadyConnectedMessage = "You are already connected to this lobby!", - noTargetConnectionFound = "Couldn't find a target server!", - targetConnections = defaultTargetConnections, - aliases = listOf("l", "hub", "quit", "leave") - ) - ) - - return Config( - connections = defaultConnections, - networkJoinTargets = networkJoinTargets, - fallbackConnectionsConfig = fallbackConnectionsConfig, - commands = defaultCommands - ) - } - } -} diff --git a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/ConfigFactory.kt b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/ConfigFactory.kt deleted file mode 100644 index 1fe06af..0000000 --- a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/ConfigFactory.kt +++ /dev/null @@ -1,48 +0,0 @@ -package app.simplecloud.plugin.connection.shared.config - -import org.spongepowered.configurate.kotlin.extensions.get -import org.spongepowered.configurate.kotlin.objectMapperFactory -import org.spongepowered.configurate.kotlin.toNode -import org.spongepowered.configurate.yaml.NodeStyle -import org.spongepowered.configurate.yaml.YamlConfigurationLoader -import java.nio.file.Files -import java.nio.file.Path - -object ConfigFactory { - - fun loadOrCreate(dataDirectory: Path): Config { - val path = dataDirectory.resolve("config.yml") - val loader = YamlConfigurationLoader.builder() - .path(path) - .nodeStyle(NodeStyle.BLOCK) - .defaultOptions { options -> - options.serializers { - it.registerAnnotatedObjects(objectMapperFactory()).build() - } - } - .build() - - if (!Files.exists(path)) { - return create(path, loader) - } - - val configurationNode = loader.load() - return configurationNode.get() ?: throw IllegalStateException("Config could not be loaded") - } - - - private fun create(path: Path, loader: YamlConfigurationLoader): Config { - val config = Config.createDefaultConfig() - if (!Files.exists(path)) { - path.parent?.let { Files.createDirectories(it) } - Files.createFile(path) - - val configurationNode = loader.load() - config.toNode(configurationNode) - loader.save(configurationNode) - } - - return config - } - -} \ No newline at end of file diff --git a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/ConnectionConfig.kt b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/ConnectionConfig.kt deleted file mode 100644 index 5559623..0000000 --- a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/ConnectionConfig.kt +++ /dev/null @@ -1,11 +0,0 @@ -package app.simplecloud.plugin.connection.shared.config - -import app.simplecloud.plugin.api.shared.matcher.ServerMatcherConfiguration -import org.spongepowered.configurate.objectmapping.ConfigSerializable - -@ConfigSerializable -data class ConnectionConfig( - val name: String = "", - val serverNameMatcher: ServerMatcherConfiguration = ServerMatcherConfiguration(), - val rules: List = emptyList() -) diff --git a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/RulesConfig.kt b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/RulesConfig.kt deleted file mode 100644 index acc10ff..0000000 --- a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/RulesConfig.kt +++ /dev/null @@ -1,39 +0,0 @@ -package app.simplecloud.plugin.connection.shared.config - -import app.simplecloud.plugin.api.shared.matcher.MatcherType -import app.simplecloud.plugin.connection.shared.PermissionChecker -import org.spongepowered.configurate.objectmapping.ConfigSerializable - -@ConfigSerializable -data class RulesConfig( - val type: Type = Type.ENV, - val operation: MatcherType = MatcherType.STARTS_WITH, - val name: String = "", - val value: String = "", - val negate: Boolean = false, - val bypassPermission: String = "" -) { - - enum class Type { - ENV, - PERMISSION - } - - fun

isAllowed(player: P, permissionChecker: PermissionChecker

): Boolean { - if (bypassPermission.isNotEmpty() && permissionChecker.checkPermission(player, bypassPermission)) { - return true - } - - when (type) { - Type.ENV -> { - val env = System.getenv(name) - return operation.matches(env, value, negate) - } - - Type.PERMISSION -> { - return permissionChecker.checkPermission(player, name).toString().equals(value, true) - } - } - } - -} \ No newline at end of file diff --git a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/TargetConnectionConfig.kt b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/TargetConnectionConfig.kt deleted file mode 100644 index 9f20ebc..0000000 --- a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/TargetConnectionConfig.kt +++ /dev/null @@ -1,11 +0,0 @@ -package app.simplecloud.plugin.connection.shared.config - -import app.simplecloud.plugin.api.shared.matcher.ServerMatcherConfiguration -import org.spongepowered.configurate.objectmapping.ConfigSerializable - -@ConfigSerializable -data class TargetConnectionConfig ( - val name: String = "", - val priority: Int = 0, - val from: List = emptyList() -) diff --git a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/TargetsConfig.kt b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/TargetsConfig.kt deleted file mode 100644 index e6a904e..0000000 --- a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/TargetsConfig.kt +++ /dev/null @@ -1,10 +0,0 @@ -package app.simplecloud.plugin.connection.shared.config - -import org.spongepowered.configurate.objectmapping.ConfigSerializable - -@ConfigSerializable -data class TargetsConfig( - val enabled: Boolean = false, - val noTargetConnectionFoundMessage: String = "", - val targetConnections: List = emptyList() -) \ No newline at end of file diff --git a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/server/ServerConnectionInfo.kt b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/server/ServerConnectionInfo.kt deleted file mode 100644 index af2f15b..0000000 --- a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/server/ServerConnectionInfo.kt +++ /dev/null @@ -1,6 +0,0 @@ -package app.simplecloud.plugin.connection.shared.server - -data class ServerConnectionInfo( - val name: String, - val onlinePlayers: Int -) \ No newline at end of file diff --git a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/server/ServerConnectionInfoGetter.kt b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/server/ServerConnectionInfoGetter.kt deleted file mode 100644 index ef97f6d..0000000 --- a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/server/ServerConnectionInfoGetter.kt +++ /dev/null @@ -1,7 +0,0 @@ -package app.simplecloud.plugin.connection.shared.server - -fun interface ServerConnectionInfoGetter { - - fun get(): List - -} \ No newline at end of file diff --git a/connection-velocity/build.gradle.kts b/connection-velocity/build.gradle.kts index 3afe125..585b85d 100644 --- a/connection-velocity/build.gradle.kts +++ b/connection-velocity/build.gradle.kts @@ -5,8 +5,9 @@ plugins { dependencies { api(project(":connection-shared")) - compileOnly("com.velocitypowered:velocity-api:3.3.0-SNAPSHOT") - kapt("com.velocitypowered:velocity-api:3.3.0-SNAPSHOT") + compileOnly(libs.simplecloud.api) + compileOnly(libs.velocity.api) + kapt(libs.velocity.api) } modrinth { @@ -16,9 +17,6 @@ modrinth { versionType.set("beta") uploadFile.set(tasks.shadowJar) gameVersions.addAll( - - - "1.20", "1.20.1", "1.20.2", @@ -38,10 +36,7 @@ modrinth { "1.21.9", "1.21.10", "1.21.11", - - - - ) + ) loaders.add("velocity") changelog.set("https://docs.simplecloud.app/changelog") syncBodyFrom.set(rootProject.file("README.md").readText()) diff --git a/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/VelocityServerConnectionPlugin.kt b/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/VelocityServerConnectionPlugin.kt deleted file mode 100644 index ad6267b..0000000 --- a/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/VelocityServerConnectionPlugin.kt +++ /dev/null @@ -1,150 +0,0 @@ -package app.simplecloud.plugin.connection.velocity - -import app.simplecloud.plugin.connection.shared.PermissionChecker -import app.simplecloud.plugin.connection.shared.ServerConnectionPlugin -import app.simplecloud.plugin.connection.shared.config.CommandConfig -import app.simplecloud.plugin.connection.shared.server.ServerConnectionInfo -import app.simplecloud.plugin.connection.shared.server.ServerConnectionInfoGetter -import com.google.inject.Inject -import com.velocitypowered.api.command.BrigadierCommand -import com.velocitypowered.api.event.Subscribe -import com.velocitypowered.api.event.player.KickedFromServerEvent -import com.velocitypowered.api.event.player.PlayerChooseInitialServerEvent -import com.velocitypowered.api.event.proxy.ProxyInitializeEvent -import com.velocitypowered.api.plugin.Dependency -import com.velocitypowered.api.plugin.Plugin -import com.velocitypowered.api.plugin.annotation.DataDirectory -import com.velocitypowered.api.proxy.Player -import com.velocitypowered.api.proxy.ProxyServer -import net.kyori.adventure.text.minimessage.MiniMessage -import java.nio.file.Path -import java.util.logging.Logger -import kotlin.jvm.optionals.getOrNull - -@Plugin( - id = "simplecloud-connection", - name = "simplecloud-connection", - version = "0.0.1", - authors = ["Fllip", "hmtill"], - dependencies = [ - Dependency( - id = "simplecloud-api" - ) - ], - url = "https://github.com/theSimpleCloud/server-connection-plugin" -) -class VelocityServerConnectionPlugin @Inject constructor( - @DataDirectory val dataDirectory: Path, - private val server: ProxyServer, - private val logger: Logger -) { - - private val serverConnection = ServerConnectionPlugin( - dataDirectory, - ServerConnectionInfoGetter { - server.allServers.map { - ServerConnectionInfo( - it.serverInfo.name, - it.playersConnected.size - ) - } - }, - PermissionChecker { player, permission -> player.hasPermission(permission) } - ) - - private val miniMessage = MiniMessage.miniMessage() - - @Subscribe - fun onProxyInitialize(event: ProxyInitializeEvent) { - registerCommands() - } - - @Subscribe - fun onPlayerChooseInitialServer(event: PlayerChooseInitialServerEvent) { - val serverConnectionInfoName = serverConnection.getServerNameForLogin(event.player) - if (serverConnectionInfoName == null) { - event.player.disconnect(miniMessage.deserialize( - serverConnection.config.networkJoinTargets.noTargetConnectionFoundMessage - )) - return - } - - val serverInfo = server.getServer(serverConnectionInfoName) - serverInfo.ifPresent { - event.setInitialServer(it) - } - } - - @Subscribe - fun onKickedFromServer(event: KickedFromServerEvent) { - val connectionAndTargetConfigToServerName = serverConnection.getConnectionAndNameForFallback(event.player, event.server.serverInfo.name) - if (connectionAndTargetConfigToServerName == null) { - event.result = KickedFromServerEvent.DisconnectPlayer.create(miniMessage.deserialize( - serverConnection.config.fallbackConnectionsConfig.noTargetConnectionFoundMessage - )) - return - } - - val (_, serverName) = connectionAndTargetConfigToServerName - if (event.server.serverInfo.name == serverName) { - return - } - - if (event.player.currentServer.isEmpty) { - return - } - - server.getServer(serverName).ifPresent { - event.result = KickedFromServerEvent.RedirectPlayer.create(it) - } - } - - private fun registerCommands() { - val commandManager = server.commandManager - serverConnection.getCommandConfigs().forEach { - val commandMeta = commandManager.metaBuilder(it.name) - .aliases(*it.aliases.toTypedArray()) - .plugin(this) - .build() - - val commandToRegister = createCommand(it) - commandManager.register(commandMeta, commandToRegister) - } - } - - private fun createCommand(commandConfig: CommandConfig): BrigadierCommand { - val commandNode = BrigadierCommand.literalArgumentBuilder(commandConfig.name) - .requires { commandConfig.permission.isEmpty() || it.hasPermission(commandConfig.permission) } - .executes { - val player = it.source as? Player ?: return@executes 0 - val currentServerName = player.currentServer.getOrNull()?.serverInfo?.name - val connectionToServerName = serverConnection.getConnectionAndNameForCommand( - player, - commandConfig, - ) - - if (connectionToServerName == null) { - player.sendMessage(miniMessage.deserialize(commandConfig.noTargetConnectionFound)) - return@executes 1 - } - - if (currentServerName != null - && connectionToServerName.first.connectionConfig.serverNameMatcher.matches(currentServerName) - ) { - player.sendMessage(miniMessage.deserialize(commandConfig.alreadyConnectedMessage)) - return@executes 1 - } - - val registeredServer = server.getServer(connectionToServerName.second) - registeredServer.ifPresent { - player.createConnectionRequest(it).fireAndForget() - } - - return@executes 1 - } - .build() - - return BrigadierCommand(commandNode) - } - -} \ No newline at end of file diff --git a/connection-waterdog/build.gradle.kts b/connection-waterdog/build.gradle.kts new file mode 100644 index 0000000..97860a9 --- /dev/null +++ b/connection-waterdog/build.gradle.kts @@ -0,0 +1,5 @@ +dependencies { + api(project(":connection-shared")) + compileOnly(libs.simplecloud.api) + compileOnly(libs.waterdog.api) +} \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f47f030..6e2641f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,17 +1,55 @@ [versions] -kotlin = "2.0.20" -shadow = "8.3.3" -sonatype-central-portal-publisher = "1.2.3" -simplecloud-plugin = "0.0.1-dev.feb0927" -minotaur = "2.8.7" +kotlin = "2.2.20" +kotlin-coroutines = "1.10.2" + +shadow = "9.3.1" +minotaur = "2.8.10" + +simplecloud-api = "0.1.0-platform.19-dev.1770725947326-23f3f70" +simplecloud-plugin-api = "0.0.1-platform.1766000824440-e57ed95" + +velocity = "3.5.0-SNAPSHOT" +bungeecord = "1.21-R0.4" +waterdog = "2.0.3" + +adventure-api = "4.26.1" +adventure-platform-bungeecord = "4.4.1" + +configurate = "4.2.0" +commons-io = "2.21.0" +log4j = "2.25.3" [libraries] -kotlin-jvm = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.ref = "kotlin" } +kotlin-jvm = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" } kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } -simplecloud-plugin-api = { module = "app.simplecloud.plugin:plugin-shared", version.ref = "simplecloud-plugin" } +kotlin-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlin-coroutines" } + +simplecloud-api = { module = "app.simplecloud.api:api", version.ref = "simplecloud-api" } +simplecloud-plugin-api = { module = "app.simplecloud.plugin:plugin-shared", version.ref = "simplecloud-plugin-api" } + +velocity-api = { module = "com.velocitypowered:velocity-api", version.ref = "velocity" } +bungeecord-api = { module = "net.md-5:bungeecord-api", version.ref = "bungeecord" } +waterdog-api = { module = "dev.waterdog.waterdogpe:waterdog", version.ref = "waterdog" } + +adventure-api = { module = "net.kyori:adventure-api", version.ref = "adventure-api" } +adventure-platform-bungeecord = { module = "net.kyori:adventure-platform-bungeecord", version.ref = "adventure-platform-bungeecord" } +adventure-text-minimessage = { module = "net.kyori:adventure-text-minimessage", version.ref = "adventure-api" } + +commons-io = { module = "commons-io:commons-io", version.ref = "commons-io" } + +configurate-yaml = { module = "org.spongepowered:configurate-yaml", version.ref = "configurate" } +configurate-kotlin = { module = "org.spongepowered:configurate-extra-kotlin", version.ref = "configurate" } + +log4j-core = { module = "org.apache.logging.log4j:log4j-core", version.ref = "log4j" } +log4j-api = { module = "org.apache.logging.log4j:log4j-api", version.ref = "log4j" } +log4j-slf4j-impl = { module = "org.apache.logging.log4j:log4j-slf4j-impl", version.ref = "log4j" } + +[bundles] +logging = ["log4j-core", "log4j-api", "log4j-slf4j-impl"] +configurate = ["configurate-kotlin", "configurate-yaml"] +adventure = ["adventure-api", "adventure-text-minimessage"] [plugins] kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } shadow = { id = "com.gradleup.shadow", version.ref = "shadow" } -sonatype-central-portal-publisher = { id = "net.thebugmc.gradle.sonatype-central-portal-publisher", version.ref = "sonatype-central-portal-publisher" } -minotaur = { id = "com.modrinth.minotaur", version.ref = "minotaur" } +minotaur = { id = "com.modrinth.minotaur", version.ref = "minotaur" } \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0aaefbc..37f78a6 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/settings.gradle.kts b/settings.gradle.kts index 26bd757..e9ce7bb 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -8,9 +8,9 @@ pluginManagement { } plugins { - id("org.gradle.toolchains.foojay-resolver-convention") version "0.5.0" + id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" } rootProject.name = "server-connection-plugin" -include("connection-shared", "connection-velocity", "connection-bungeecord") +include("connection-shared", "connection-velocity", "connection-bungeecord", "connection-waterdog") From 4933b82b5fd1ca21a7e1a1cbe65aad5d51534caf Mon Sep 17 00:00:00 2001 From: xXJanisXx Date: Sun, 15 Feb 2026 20:22:59 +0100 Subject: [PATCH 02/19] feat: basic structure --- build.gradle.kts | 1 + .../bungeecord/BungeeCordConnectionPlugin.kt | 13 ++++++++ .../src/main/resources/bungee.yml | 5 +++ connection-shared/build.gradle.kts | 1 - .../connection/shared/ConnectionPlugin.kt | 11 +++++++ .../velocity/VelocityConnectionPlugin.kt | 32 +++++++++++++++++++ .../waterdog/WaterDogConnectionPlugin.kt | 13 ++++++++ .../src/main/resources/plugin.yml | 5 +++ 8 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/BungeeCordConnectionPlugin.kt create mode 100644 connection-bungeecord/src/main/resources/bungee.yml create mode 100644 connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/ConnectionPlugin.kt create mode 100644 connection-velocity/src/main/kotlin/app/simplecloud/connection/plugin/velocity/VelocityConnectionPlugin.kt create mode 100644 connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/WaterDogConnectionPlugin.kt create mode 100644 connection-waterdog/src/main/resources/plugin.yml diff --git a/build.gradle.kts b/build.gradle.kts index 87a374a..96c1b85 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -39,6 +39,7 @@ subprojects { testImplementation(rootProject.libs.kotlin.test) implementation(rootProject.libs.kotlin.jvm) implementation(rootProject.libs.kotlin.coroutines.core) + implementation(rootProject.libs.bundles.logging) } kotlin { diff --git a/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/BungeeCordConnectionPlugin.kt b/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/BungeeCordConnectionPlugin.kt new file mode 100644 index 0000000..ef51e3d --- /dev/null +++ b/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/BungeeCordConnectionPlugin.kt @@ -0,0 +1,13 @@ +package app.simplecloud.plugin.connection.bungeecord + +import net.md_5.bungee.api.plugin.Plugin +import org.apache.logging.log4j.LogManager + +class BungeeCordConnectionPlugin : Plugin() { + + private val logger = LogManager.getLogger(BungeeCordConnectionPlugin::class.java) + + override fun onEnable() { + logger.info("Initialize bungeecord connection plugin...") + } +} \ No newline at end of file diff --git a/connection-bungeecord/src/main/resources/bungee.yml b/connection-bungeecord/src/main/resources/bungee.yml new file mode 100644 index 0000000..184ba4e --- /dev/null +++ b/connection-bungeecord/src/main/resources/bungee.yml @@ -0,0 +1,5 @@ +name: connection-bungeecord +version: 1.0.0 +author: MrManHD + +main: app.simplecloud.plugin.connection.bungeecord.BungeeCordConnectionPlugin \ No newline at end of file diff --git a/connection-shared/build.gradle.kts b/connection-shared/build.gradle.kts index c6a7767..af325f3 100644 --- a/connection-shared/build.gradle.kts +++ b/connection-shared/build.gradle.kts @@ -3,6 +3,5 @@ dependencies { implementation(libs.simplecloud.plugin.api) implementation(libs.commons.io) implementation(libs.bundles.adventure) - implementation(libs.bundles.logging) implementation(libs.bundles.configurate) } \ No newline at end of file diff --git a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/ConnectionPlugin.kt b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/ConnectionPlugin.kt new file mode 100644 index 0000000..23b4346 --- /dev/null +++ b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/ConnectionPlugin.kt @@ -0,0 +1,11 @@ +package app.simplecloud.plugin.connection.shared + +import org.apache.logging.log4j.LogManager +import java.nio.file.Path + +class ConnectionPlugin

( + private val dir: Path +) { + + private val logger = LogManager.getLogger(ConnectionPlugin::class.java) +} \ No newline at end of file diff --git a/connection-velocity/src/main/kotlin/app/simplecloud/connection/plugin/velocity/VelocityConnectionPlugin.kt b/connection-velocity/src/main/kotlin/app/simplecloud/connection/plugin/velocity/VelocityConnectionPlugin.kt new file mode 100644 index 0000000..acea5b4 --- /dev/null +++ b/connection-velocity/src/main/kotlin/app/simplecloud/connection/plugin/velocity/VelocityConnectionPlugin.kt @@ -0,0 +1,32 @@ +package app.simplecloud.connection.plugin.velocity + +import com.google.inject.Inject +import com.velocitypowered.api.event.Subscribe +import com.velocitypowered.api.event.proxy.ProxyInitializeEvent +import com.velocitypowered.api.plugin.Plugin +import com.velocitypowered.api.plugin.annotation.DataDirectory +import com.velocitypowered.api.proxy.ProxyServer +import org.apache.logging.log4j.LogManager +import org.slf4j.Logger +import java.nio.file.Path + +@Plugin( + id = "connection-velocity", + name = "connection-velocity", + version = "1.0-SNAPSHOT", + authors = ["Fllip", "hmtill"], + url = "https://github.com/simplecloudapp/server-connection-plugin" +) +class VelocityConnectionPlugin @Inject constructor( + @DataDirectory val dataDirectory: Path, + private val server: ProxyServer, +) { + + private val logger = LogManager.getLogger(VelocityConnectionPlugin::class.java) + + @Subscribe + fun onProxyInitialize(event: ProxyInitializeEvent) { + logger.info("Initialize velocity connection plugin...") + } + +} \ No newline at end of file diff --git a/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/WaterDogConnectionPlugin.kt b/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/WaterDogConnectionPlugin.kt new file mode 100644 index 0000000..314c29d --- /dev/null +++ b/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/WaterDogConnectionPlugin.kt @@ -0,0 +1,13 @@ +package app.simplecloud.plugin.connection.waterdog + +import dev.waterdog.waterdogpe.plugin.Plugin +import org.apache.logging.log4j.LogManager + +class WaterDogConnectionPlugin : Plugin() { + + private val logger = LogManager.getLogger(WaterDogConnectionPlugin::class.java) + + override fun onEnable() { + logger.info("Initialize waterdog connection plugin...") + } +} \ No newline at end of file diff --git a/connection-waterdog/src/main/resources/plugin.yml b/connection-waterdog/src/main/resources/plugin.yml new file mode 100644 index 0000000..bedadc4 --- /dev/null +++ b/connection-waterdog/src/main/resources/plugin.yml @@ -0,0 +1,5 @@ +name: connection-waterdog +version: 1.0.0 +author: InvalidJoker + +main: app.simplecloud.plugin.connection.waterdog.WaterDogConnectionPlugin \ No newline at end of file From 9b116a670af3ad1ba4be694e13838f8997b12f1a Mon Sep 17 00:00:00 2001 From: xXJanisXx Date: Mon, 16 Feb 2026 20:30:57 +0100 Subject: [PATCH 03/19] feat: add server registration --- .../connection/shared/ConnectionPlugin.kt | 41 +++- .../shared/config/ConnectionConfig.kt | 39 ++++ .../connection/shared/config/YamlConfig.kt | 175 ++++++++++++++++++ .../shared/listener/ServerEventListener.kt | 80 ++++++++ .../shared/registration/RegisteredServer.kt | 12 ++ .../shared/registration/ServerRegistry.kt | 11 ++ .../resolver/RegisteredServerResolver.kt | 27 +++ .../shared/utilities/ConfigVersion.kt | 7 + .../velocity/VelocityConnectionPlugin.kt | 32 ---- .../velocity/VelocityConnectionPlugin.kt | 75 ++++++++ .../registration/VelocityServerRegistry.kt | 39 ++++ 11 files changed, 503 insertions(+), 35 deletions(-) create mode 100644 connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/ConnectionConfig.kt create mode 100644 connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/YamlConfig.kt create mode 100644 connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/listener/ServerEventListener.kt create mode 100644 connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/registration/RegisteredServer.kt create mode 100644 connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/registration/ServerRegistry.kt create mode 100644 connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/resolver/RegisteredServerResolver.kt create mode 100644 connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/utilities/ConfigVersion.kt delete mode 100644 connection-velocity/src/main/kotlin/app/simplecloud/connection/plugin/velocity/VelocityConnectionPlugin.kt create mode 100644 connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/VelocityConnectionPlugin.kt create mode 100644 connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/registration/VelocityServerRegistry.kt diff --git a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/ConnectionPlugin.kt b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/ConnectionPlugin.kt index 23b4346..b88fecf 100644 --- a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/ConnectionPlugin.kt +++ b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/ConnectionPlugin.kt @@ -1,11 +1,46 @@ package app.simplecloud.plugin.connection.shared +import app.simplecloud.api.CloudApi +import app.simplecloud.api.group.GroupServerType +import app.simplecloud.api.server.ServerQuery +import app.simplecloud.api.server.ServerState +import app.simplecloud.plugin.connection.shared.config.ConnectionConfig +import app.simplecloud.plugin.connection.shared.config.YamlConfig +import app.simplecloud.plugin.connection.shared.listener.ServerEventListener +import app.simplecloud.plugin.connection.shared.registration.ServerRegistry import org.apache.logging.log4j.LogManager -import java.nio.file.Path -class ConnectionPlugin

( - private val dir: Path +class ConnectionPlugin( + private val dir: String, + private val api: CloudApi, + private val registry: ServerRegistry, ) { private val logger = LogManager.getLogger(ConnectionPlugin::class.java) + private val listener = ServerEventListener(api, registry) + + val config = YamlConfig(dir) + val connectionConfig: ConnectionConfig + get() = config.load("config") ?: ConnectionConfig() + + suspend fun start() { + loadExistingServers() + listener.start() + } + + suspend fun shutdown() { + config.close() + listener.stop() + } + + private fun loadExistingServers() { + api.server().getAllServers( + ServerQuery.create() + .filterByState(ServerState.AVAILABLE) + .filterByServerGroupType(GroupServerType.SERVER) + ).thenAccept { servers -> + logger.info("Found ${servers.size} servers") + servers.forEach { listener.register(it) } + } + } } \ No newline at end of file diff --git a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/ConnectionConfig.kt b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/ConnectionConfig.kt new file mode 100644 index 0000000..06d8fbc --- /dev/null +++ b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/ConnectionConfig.kt @@ -0,0 +1,39 @@ +package app.simplecloud.plugin.connection.shared.config + +import app.simplecloud.plugin.connection.shared.utilities.ConfigVersion +import org.spongepowered.configurate.objectmapping.ConfigSerializable +import org.spongepowered.configurate.objectmapping.meta.Comment + +@ConfigSerializable +data class ConnectionConfig( + val version: Char = ConfigVersion.VERSION, + + @Comment(""" +─────────────────────────────────────────────────────────────────────────────── +Server Registration +This keeps your proxy's registered child servers in sync with SimpleCloud's +server registry. + +Read more @ https://docs.simplecloud.app/manual/plugins/server-connection +─────────────────────────────────────────────────────────────────────────────── + +Configure how servers are automatically registered with the proxy""") + val registration: RegistrationConfig = RegistrationConfig(), +) + +@ConfigSerializable +data class RegistrationConfig( + @Comment("Attention: If this is set to false, no child servers will be registered.") + val enabled: Boolean = true, + val serverNamePattern: String = "-", + val persistentServerNamePattern: String = "", + val ignoreServerGroups: List = listOf(), + val additionalServers: List = listOf() +) + +@ConfigSerializable +data class RegistrationServer( + val name: String = "", + val address: String = "", + val port: Long = 0L +) \ No newline at end of file diff --git a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/YamlConfig.kt b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/YamlConfig.kt new file mode 100644 index 0000000..e19aba6 --- /dev/null +++ b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/YamlConfig.kt @@ -0,0 +1,175 @@ +package app.simplecloud.plugin.connection.shared.config + +import kotlinx.coroutines.* +import org.apache.logging.log4j.LogManager +import org.apache.logging.log4j.Logger +import org.spongepowered.configurate.kotlin.objectMapperFactory +import org.spongepowered.configurate.loader.ParsingException +import org.spongepowered.configurate.yaml.NodeStyle +import org.spongepowered.configurate.yaml.YamlConfigurationLoader +import java.io.File +import java.nio.file.FileSystems +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.StandardWatchEventKinds +import java.util.concurrent.ConcurrentHashMap + +open class YamlConfig(private val dirPath: String) { + + companion object { + protected val logger: Logger = LogManager.getLogger(YamlConfig::class.java) + } + + private val watchService = FileSystems.getDefault().newWatchService() + private val reloadListeners = ConcurrentHashMap Unit>>() + private val lastReload = ConcurrentHashMap() + + private var watcherJob: Job? = null + + init { + startWatcher() + } + + private fun buildNode(path: String?): Pair { + val file = File(if (path != null) "$dirPath/${path.lowercase()}.yml" else dirPath) + + if (!file.exists()) { + file.parentFile.mkdirs() + file.createNewFile() + } + + val loader = YamlConfigurationLoader.builder() + .path(file.toPath()) + .nodeStyle(NodeStyle.BLOCK) + .defaultOptions { options -> + options.serializers { builder -> + builder.registerAnnotatedObjects(objectMapperFactory()) + } + }.build() + return Pair(loader.load(), loader) + } + + inline fun load(path: String?): T? { + return load(path, T::class.java) + } + + @PublishedApi + internal fun load(path: String?, clazz: Class): T? { + return try { + val node = buildNode(path).first + val config = node.get(clazz) + + config + } catch (e: ParsingException) { + logger.error("Failed to parse config file: ${path ?: "default"} ${e.message}") + null + } catch (e: Exception) { + logger.error("Failed to load config: ${path ?: "default"} ${e.message}", e) + null + } + } + + fun save(path: String?, obj: T) { + try { + val pair = buildNode(path) + pair.first.set(obj) + pair.second.save(pair.first) + } catch (e: Exception) { + logger.error("Failed to save config: $path ${e.message}", e) + } + } + + private fun startWatcher() { + val directory = File(dirPath).toPath() + + if (!directory.toFile().exists()) { + directory.toFile().mkdirs() + } + + try { + directory.register( + watchService, + StandardWatchEventKinds.ENTRY_MODIFY, + StandardWatchEventKinds.ENTRY_CREATE + ) + + watcherJob = CoroutineScope(Dispatchers.IO).launch { + while (isActive) { + try { + val key = watchService.take() + delay(150) + + for (event in key.pollEvents()) { + val path = event.context() as? Path ?: continue + val resolvedPath = directory.resolve(path) + + if (Files.isDirectory(resolvedPath) || !resolvedPath.toString().endsWith(".yml")) { + continue + } + + handleFileChange(resolvedPath.toFile()) + } + + key.reset() + + } catch (e: InterruptedException) { + logger.info("Config watcher interrupted") + break + } catch (e: Exception) { + logger.warn("Error in config watcher: ${e.message}") + } + } + } + + } catch (e: Exception) { + logger.error("Could not start config file watcher: ${e.message}", e) + } + } + + private fun handleFileChange(file: File) { + val cacheKey = file.nameWithoutExtension + val now = System.currentTimeMillis() + + val last = lastReload[cacheKey] + if (last != null && now - last < 300) return + lastReload[cacheKey] = now + + val listeners = reloadListeners[cacheKey] + if (listeners.isNullOrEmpty()) return + + try { + val loader = YamlConfigurationLoader.builder() + .path(file.toPath()) + .nodeStyle(NodeStyle.BLOCK) + .defaultOptions { options -> + options.serializers { builder -> + builder.registerAnnotatedObjects(objectMapperFactory()) + } + }.build() + + val node = loader.load() + + listeners.forEach { listener -> + try { + listener(node) + } catch (e: Exception) { + logger.error("Error in reload listener for $cacheKey: ${e.message}", e) + } + } + } catch (e: ParsingException) { + logger.error("Failed to parse changed config file: $cacheKey ${e.message}") + } catch (e: Exception) { + logger.error("Failed to reload config: $cacheKey ${e.message}", e) + } + } + + fun close() { + watcherJob?.cancel() + try { + watchService.close() + logger.info("Config watcher closed") + } catch (e: Exception) { + logger.warn("Error closing watch service: ${e.message}") + } + } +} \ No newline at end of file diff --git a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/listener/ServerEventListener.kt b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/listener/ServerEventListener.kt new file mode 100644 index 0000000..f586833 --- /dev/null +++ b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/listener/ServerEventListener.kt @@ -0,0 +1,80 @@ +package app.simplecloud.plugin.connection.shared.listener + +import app.simplecloud.api.CloudApi +import app.simplecloud.api.event.Subscription +import app.simplecloud.api.group.GroupServerType +import app.simplecloud.api.server.Server +import app.simplecloud.api.server.ServerState +import app.simplecloud.plugin.connection.shared.registration.RegisteredServer +import app.simplecloud.plugin.connection.shared.registration.ServerRegistry +import kotlinx.coroutines.* +import org.apache.logging.log4j.LogManager + +class ServerEventListener( + private val api: CloudApi, + private val registry: ServerRegistry, +) { + + private val logger = LogManager.getLogger(ServerEventListener::class.java) + private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob()) + private var subscriptions: MutableList = mutableListOf() + + fun start() { + subscriptions.add( + api.event().server().onStateChanged { event -> + val server = event.server ?: return@onStateChanged + if (server.serverBase?.type != GroupServerType.SERVER) return@onStateChanged + if (event.newState == ServerState.AVAILABLE && event.oldState != ServerState.AVAILABLE) { + scope.launch { + register(convertToRegisteredServer(server)) + } + } + } + ) + + subscriptions.add( + api.event().server().onStopped { event -> + val server = event.server ?: return@onStopped + scope.launch { + unregister(convertToRegisteredServer(server)) + } + } + ) + } + + fun stop() { + subscriptions.forEach { it.close() } + subscriptions.clear() + scope.cancel() + } + + fun register(server: Server) { + register(convertToRegisteredServer(server)) + } + + private fun register(server: RegisteredServer) { + if (server.blueprintConfigurator == "standalone") return + logger.info("Registering server ${server.serverId}...") + registry.register(server) + } + + private fun unregister(server: RegisteredServer) { + if (registry.getRegistered().containsKey(server.serverId)) { + logger.info("Unregistering server ${server.serverId}...") + registry.unregister(server) + } + } + + private fun convertToRegisteredServer(server: Server): RegisteredServer { + return RegisteredServer( + serverId = server.serverId, + numericalId = server.numericalId, + ip = server.ip!!, + port = server.port!!, + serverBaseName = server.serverBase!!.name!!, + properties = server.properties ?: emptyMap(), + blueprintConfigurator = server.blueprint?.configurator, + persistent = server.isFromPersistentServer + ) + } +} \ No newline at end of file diff --git a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/registration/RegisteredServer.kt b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/registration/RegisteredServer.kt new file mode 100644 index 0000000..13ff937 --- /dev/null +++ b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/registration/RegisteredServer.kt @@ -0,0 +1,12 @@ +package app.simplecloud.plugin.connection.shared.registration + +data class RegisteredServer( + val serverId: String, + val numericalId: Int, + val ip: String, + val port: Int, + val serverBaseName: String, + val properties: Map, + val blueprintConfigurator: String?, + val persistent: Boolean, +) \ No newline at end of file diff --git a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/registration/ServerRegistry.kt b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/registration/ServerRegistry.kt new file mode 100644 index 0000000..af64afa --- /dev/null +++ b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/registration/ServerRegistry.kt @@ -0,0 +1,11 @@ +package app.simplecloud.plugin.connection.shared.registration + +interface ServerRegistry { + + fun getRegistered(): Map + + fun register(server: RegisteredServer) + + fun unregister(server: RegisteredServer) + +} \ No newline at end of file diff --git a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/resolver/RegisteredServerResolver.kt b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/resolver/RegisteredServerResolver.kt new file mode 100644 index 0000000..1a3bdd4 --- /dev/null +++ b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/resolver/RegisteredServerResolver.kt @@ -0,0 +1,27 @@ +package app.simplecloud.plugin.connection.shared.resolver + +import app.simplecloud.plugin.connection.shared.config.RegistrationConfig +import app.simplecloud.plugin.connection.shared.registration.RegisteredServer +import net.kyori.adventure.text.minimessage.MiniMessage +import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder +import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver + +object RegisteredServerResolver { + fun resolve(server: RegisteredServer, config: RegistrationConfig): String { + val pattern = if (!server.persistent) config.serverNamePattern else config.persistentServerNamePattern + + val resolver = TagResolver.resolver( + Placeholder.unparsed("group", server.serverBaseName), + Placeholder.unparsed("name", server.serverBaseName), + Placeholder.unparsed("numerical_id", server.numericalId.toString()), + Placeholder.unparsed("id", server.serverId), + *server.properties.map { (key, value) -> + Placeholder.unparsed(key.lowercase(), value.toString()) + }.toTypedArray() + ) + + return MiniMessage.miniMessage().stripTags( + MiniMessage.miniMessage().deserialize(pattern, resolver).toString() + ) + } +} \ No newline at end of file diff --git a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/utilities/ConfigVersion.kt b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/utilities/ConfigVersion.kt new file mode 100644 index 0000000..2b33539 --- /dev/null +++ b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/utilities/ConfigVersion.kt @@ -0,0 +1,7 @@ +package app.simplecloud.plugin.connection.shared.utilities + +object ConfigVersion { + + const val VERSION = '1' + +} \ No newline at end of file diff --git a/connection-velocity/src/main/kotlin/app/simplecloud/connection/plugin/velocity/VelocityConnectionPlugin.kt b/connection-velocity/src/main/kotlin/app/simplecloud/connection/plugin/velocity/VelocityConnectionPlugin.kt deleted file mode 100644 index acea5b4..0000000 --- a/connection-velocity/src/main/kotlin/app/simplecloud/connection/plugin/velocity/VelocityConnectionPlugin.kt +++ /dev/null @@ -1,32 +0,0 @@ -package app.simplecloud.connection.plugin.velocity - -import com.google.inject.Inject -import com.velocitypowered.api.event.Subscribe -import com.velocitypowered.api.event.proxy.ProxyInitializeEvent -import com.velocitypowered.api.plugin.Plugin -import com.velocitypowered.api.plugin.annotation.DataDirectory -import com.velocitypowered.api.proxy.ProxyServer -import org.apache.logging.log4j.LogManager -import org.slf4j.Logger -import java.nio.file.Path - -@Plugin( - id = "connection-velocity", - name = "connection-velocity", - version = "1.0-SNAPSHOT", - authors = ["Fllip", "hmtill"], - url = "https://github.com/simplecloudapp/server-connection-plugin" -) -class VelocityConnectionPlugin @Inject constructor( - @DataDirectory val dataDirectory: Path, - private val server: ProxyServer, -) { - - private val logger = LogManager.getLogger(VelocityConnectionPlugin::class.java) - - @Subscribe - fun onProxyInitialize(event: ProxyInitializeEvent) { - logger.info("Initialize velocity connection plugin...") - } - -} \ No newline at end of file diff --git a/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/VelocityConnectionPlugin.kt b/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/VelocityConnectionPlugin.kt new file mode 100644 index 0000000..3b0fdeb --- /dev/null +++ b/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/VelocityConnectionPlugin.kt @@ -0,0 +1,75 @@ +package app.simplecloud.plugin.connection.velocity + +import app.simplecloud.api.CloudApi +import app.simplecloud.plugin.connection.shared.ConnectionPlugin +import app.simplecloud.plugin.connection.velocity.registration.VelocityServerRegistry +import com.google.inject.Inject +import com.velocitypowered.api.event.Subscribe +import com.velocitypowered.api.event.proxy.ProxyInitializeEvent +import com.velocitypowered.api.event.proxy.ProxyShutdownEvent +import com.velocitypowered.api.plugin.Plugin +import com.velocitypowered.api.plugin.annotation.DataDirectory +import com.velocitypowered.api.proxy.ProxyServer +import com.velocitypowered.api.proxy.server.ServerInfo +import kotlinx.coroutines.* +import org.apache.logging.log4j.LogManager +import java.net.InetSocketAddress +import java.nio.file.Path + +@Plugin( + id = "connection-velocity", + name = "connection-velocity", + version = "1.0.0", + authors = ["Fllip", "hmtill"], + url = "https://github.com/simplecloudapp/server-connection-plugin" +) +class VelocityConnectionPlugin @Inject constructor( + private val server: ProxyServer, + @DataDirectory val dataDirectory: Path +) { + + private val api = CloudApi.create() + private val logger = LogManager.getLogger(VelocityConnectionPlugin::class.java) + private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob()) + + val connectionPlugin = ConnectionPlugin( + dataDirectory.toString(), + api, + VelocityServerRegistry(this, server) + ) + + @Subscribe + fun onProxyInitialize(event: ProxyInitializeEvent) { + logger.info("Initialize velocity-connection plugin...") + connectionPlugin.config.save("config", connectionPlugin.connectionConfig) + cleanupServers() + registerAdditionalServers() + scope.launch { + connectionPlugin.start() + } + } + + @Subscribe + fun onProxyShutdown(event: ProxyShutdownEvent) { + logger.info("Shutting down velocity-connection plugin...") + scope.launch { + connectionPlugin.shutdown() + } + scope.cancel() + } + + private fun cleanupServers() { + server.allServers.forEach { + server.unregisterServer(it.serverInfo) + } + } + + private fun registerAdditionalServers() { + val additionalServers = connectionPlugin.connectionConfig.registration.additionalServers + additionalServers.forEach { + val info = ServerInfo(it.name, InetSocketAddress.createUnresolved(it.address, it.port.toInt())) + server.registerServer(info) + logger.info("Additional server ${info.name} has been registered!") + } + } +} \ No newline at end of file diff --git a/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/registration/VelocityServerRegistry.kt b/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/registration/VelocityServerRegistry.kt new file mode 100644 index 0000000..d6e52ff --- /dev/null +++ b/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/registration/VelocityServerRegistry.kt @@ -0,0 +1,39 @@ +package app.simplecloud.plugin.connection.velocity.registration + +import app.simplecloud.plugin.connection.shared.registration.RegisteredServer +import app.simplecloud.plugin.connection.shared.registration.ServerRegistry +import app.simplecloud.plugin.connection.shared.resolver.RegisteredServerResolver +import app.simplecloud.plugin.connection.velocity.VelocityConnectionPlugin +import com.velocitypowered.api.proxy.ProxyServer +import com.velocitypowered.api.proxy.server.ServerInfo +import java.net.InetSocketAddress +import kotlin.jvm.optionals.getOrNull + +class VelocityServerRegistry( + private val plugin: VelocityConnectionPlugin, + private val proxy: ProxyServer +) : ServerRegistry { + + private val servers = mutableMapOf() + + override fun getRegistered(): Map { + return servers + } + + override fun register(server: RegisteredServer) { + val info = ServerInfo( + RegisteredServerResolver.resolve(server, plugin.connectionPlugin.connectionConfig.registration), + InetSocketAddress.createUnresolved(server.ip, server.port) + ) + proxy.registerServer(info) + servers[server.serverId] = server + } + + override fun unregister(server: RegisteredServer) { + val registered = proxy.getServer( + RegisteredServerResolver.resolve(server, plugin.connectionPlugin.connectionConfig.registration) + ).getOrNull() ?: return + proxy.unregisterServer(registered.serverInfo) + servers.remove(server.serverId) + } +} \ No newline at end of file From cf534b47e982fccb59298d7fdb31eac0fb33baac Mon Sep 17 00:00:00 2001 From: xXJanisXx Date: Mon, 16 Feb 2026 20:51:47 +0100 Subject: [PATCH 04/19] feat: add bungeecord and waterdog support for server registration --- .../bungeecord/BungeeCordConnectionPlugin.kt | 51 ++++++++++++++++++- .../registration/BungeeCordServerRegistry.kt | 46 +++++++++++++++++ .../waterdog/WaterDogConnectionPlugin.kt | 46 ++++++++++++++++- .../registration/WaterDogServerRegistry.kt | 41 +++++++++++++++ 4 files changed, 182 insertions(+), 2 deletions(-) create mode 100644 connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/registration/BungeeCordServerRegistry.kt create mode 100644 connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/registration/WaterDogServerRegistry.kt diff --git a/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/BungeeCordConnectionPlugin.kt b/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/BungeeCordConnectionPlugin.kt index ef51e3d..38e6e61 100644 --- a/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/BungeeCordConnectionPlugin.kt +++ b/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/BungeeCordConnectionPlugin.kt @@ -1,13 +1,62 @@ package app.simplecloud.plugin.connection.bungeecord +import app.simplecloud.api.CloudApi +import app.simplecloud.plugin.connection.bungeecord.registration.BungeeCordServerRegistry +import app.simplecloud.plugin.connection.shared.ConnectionPlugin +import kotlinx.coroutines.* import net.md_5.bungee.api.plugin.Plugin import org.apache.logging.log4j.LogManager +import java.net.InetSocketAddress class BungeeCordConnectionPlugin : Plugin() { + private val api = CloudApi.create() private val logger = LogManager.getLogger(BungeeCordConnectionPlugin::class.java) + private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob()) + + val connectionPlugin = ConnectionPlugin( + dataFolder.toString(), + api, + BungeeCordServerRegistry(this, proxy) + ) override fun onEnable() { - logger.info("Initialize bungeecord connection plugin...") + logger.info("Initialize bungeecord-connection plugin...") + connectionPlugin.config.save("config", connectionPlugin.connectionConfig) + cleanupServers() + registerAdditionalServers() + scope.launch { + connectionPlugin.start() + } + } + + override fun onDisable() { + logger.info("Shutting down bungeecord-connection plugin...") + scope.launch { + connectionPlugin.shutdown() + } + scope.cancel() + } + + private fun cleanupServers() { + proxy.servers.clear() + proxy.configurationAdapter.servers.clear() + proxy.configurationAdapter.listeners.forEach { + it.serverPriority.clear() + } + } + + private fun registerAdditionalServers() { + val additionalServers = connectionPlugin.connectionConfig.registration.additionalServers + additionalServers.forEach { + val info = proxy.constructServerInfo( + it.name, + InetSocketAddress.createUnresolved(it.address, it.port.toInt()), + it.name, + false + ) + proxy.servers[it.name] = info + logger.info("Additional server ${info.name} has been registered!") + } } } \ No newline at end of file diff --git a/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/registration/BungeeCordServerRegistry.kt b/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/registration/BungeeCordServerRegistry.kt new file mode 100644 index 0000000..540f8f0 --- /dev/null +++ b/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/registration/BungeeCordServerRegistry.kt @@ -0,0 +1,46 @@ +package app.simplecloud.plugin.connection.bungeecord.registration + +import app.simplecloud.plugin.connection.bungeecord.BungeeCordConnectionPlugin +import app.simplecloud.plugin.connection.shared.registration.RegisteredServer +import app.simplecloud.plugin.connection.shared.registration.ServerRegistry +import app.simplecloud.plugin.connection.shared.resolver.RegisteredServerResolver +import net.md_5.bungee.api.ProxyServer +import net.md_5.bungee.api.config.ServerInfo +import java.net.InetSocketAddress + +class BungeeCordServerRegistry( + private val plugin: BungeeCordConnectionPlugin, + private val proxy: ProxyServer +) : ServerRegistry { + + private val servers = mutableMapOf() + + override fun getRegistered(): Map { + return servers + } + + override fun register(server: RegisteredServer) { + val address = InetSocketAddress.createUnresolved(server.ip, server.port) + val name = RegisteredServerResolver.resolve( + server, + plugin.connectionPlugin.connectionConfig.registration + ) + val info: ServerInfo = ProxyServer.getInstance().constructServerInfo( + name, + address, + server.serverId, + server.properties.getOrDefault("proxy-restricted", "false").toString().toBoolean() + ) + proxy.servers[name] = info + servers[server.serverId] = server + } + + override fun unregister(server: RegisteredServer) { + val name = RegisteredServerResolver.resolve( + server, + plugin.connectionPlugin.connectionConfig.registration + ) + proxy.servers.remove(name) + servers.remove(server.serverId) + } +} \ No newline at end of file diff --git a/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/WaterDogConnectionPlugin.kt b/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/WaterDogConnectionPlugin.kt index 314c29d..9316fb2 100644 --- a/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/WaterDogConnectionPlugin.kt +++ b/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/WaterDogConnectionPlugin.kt @@ -1,13 +1,57 @@ package app.simplecloud.plugin.connection.waterdog +import app.simplecloud.api.CloudApi +import app.simplecloud.plugin.connection.shared.ConnectionPlugin +import app.simplecloud.plugin.connection.waterdog.registration.WaterDogServerRegistry +import dev.waterdog.waterdogpe.network.serverinfo.BedrockServerInfo import dev.waterdog.waterdogpe.plugin.Plugin +import kotlinx.coroutines.* import org.apache.logging.log4j.LogManager +import java.net.InetSocketAddress class WaterDogConnectionPlugin : Plugin() { + private val api = CloudApi.create() private val logger = LogManager.getLogger(WaterDogConnectionPlugin::class.java) + private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob()) + + val connectionPlugin = ConnectionPlugin( + dataFolder.toString(), + api, + WaterDogServerRegistry(this, proxy) + ) override fun onEnable() { - logger.info("Initialize waterdog connection plugin...") + logger.info("Initialize waterdog-connection plugin...") + connectionPlugin.config.save("config", connectionPlugin.connectionConfig) + cleanupServers() + registerAdditionalServers() + scope.launch { + connectionPlugin.start() + } + } + + override fun onDisable() { + logger.info("Shutting down waterdog-connection plugin...") + scope.launch { + connectionPlugin.shutdown() + } + scope.cancel() + } + + private fun cleanupServers() { + proxy.servers.forEach { + proxy.removeServerInfo(it.serverName) + } + } + + private fun registerAdditionalServers() { + val additionalServers = connectionPlugin.connectionConfig.registration.additionalServers + additionalServers.forEach { + val address = InetSocketAddress.createUnresolved(it.address, it.port.toInt()) + val info = BedrockServerInfo(it.name, address, address) + proxy.registerServerInfo(info) + logger.info("Additional server ${info.serverName} has been registered!") + } } } \ No newline at end of file diff --git a/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/registration/WaterDogServerRegistry.kt b/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/registration/WaterDogServerRegistry.kt new file mode 100644 index 0000000..9310364 --- /dev/null +++ b/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/registration/WaterDogServerRegistry.kt @@ -0,0 +1,41 @@ +package app.simplecloud.plugin.connection.waterdog.registration + +import app.simplecloud.plugin.connection.shared.registration.RegisteredServer +import app.simplecloud.plugin.connection.shared.registration.ServerRegistry +import app.simplecloud.plugin.connection.shared.resolver.RegisteredServerResolver +import app.simplecloud.plugin.connection.waterdog.WaterDogConnectionPlugin +import dev.waterdog.waterdogpe.ProxyServer +import dev.waterdog.waterdogpe.network.serverinfo.BedrockServerInfo +import java.net.InetSocketAddress + +class WaterDogServerRegistry( + private val plugin: WaterDogConnectionPlugin, + private val proxy: ProxyServer +) : ServerRegistry { + + private val servers = mutableMapOf() + + override fun getRegistered(): Map { + return servers + } + + override fun register(server: RegisteredServer) { + val address = InetSocketAddress.createUnresolved(server.ip, server.port) + val info = BedrockServerInfo( + RegisteredServerResolver.resolve(server, plugin.connectionPlugin.connectionConfig.registration), + address, + address + ) + proxy.registerServerInfo(info) + servers[server.serverId] = server + } + + override fun unregister(server: RegisteredServer) { + val name = RegisteredServerResolver.resolve( + server, + plugin.connectionPlugin.connectionConfig.registration + ) + proxy.removeServerInfo(name) ?: return + servers.remove(server.serverId) + } +} \ No newline at end of file From 96b28b651016e1c54654ff4182ecf6b01def23e4 Mon Sep 17 00:00:00 2001 From: xXJanisXx Date: Mon, 16 Feb 2026 22:38:58 +0100 Subject: [PATCH 05/19] chore: some improvements --- .gitignore | 3 +++ build.gradle.kts | 2 ++ .../shared/registration/RegisteredServer.kt | 3 +++ .../shared/registration/ServerRegistry.kt | 19 ++++++++++++++++++- 4 files changed, 26 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 27b634f..5f3a971 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,9 @@ build/ ### IntelliJ IDEA ### .idea/ +### Kotlin ### +.kotlin/ + ### VS Code ### .vscode/ diff --git a/build.gradle.kts b/build.gradle.kts index 96c1b85..fa79115 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -47,6 +47,7 @@ subprojects { compilerOptions { apiVersion.set(KotlinVersion.KOTLIN_2_0) jvmTarget.set(JvmTarget.JVM_21) + freeCompilerArgs.add("-Xannotation-default-target=param-property") } } @@ -81,6 +82,7 @@ subprojects { tasks.named("shadowJar", ShadowJar::class) { mergeServiceFiles() relocate("org.spongepowered", "app.simplecloud.plugin.relocate.spongepowered") + relocate("app.simplecloud.plugin.api", "app.simplecloud.plugin.relocate.plugin.api") archiveFileName.set("${project.name}.jar") archiveClassifier.set("") } diff --git a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/registration/RegisteredServer.kt b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/registration/RegisteredServer.kt index 13ff937..e0a9231 100644 --- a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/registration/RegisteredServer.kt +++ b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/registration/RegisteredServer.kt @@ -1,5 +1,8 @@ package app.simplecloud.plugin.connection.shared.registration +/** + * Represents a registered server. + */ data class RegisteredServer( val serverId: String, val numericalId: Int, diff --git a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/registration/ServerRegistry.kt b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/registration/ServerRegistry.kt index af64afa..da53368 100644 --- a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/registration/ServerRegistry.kt +++ b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/registration/ServerRegistry.kt @@ -1,11 +1,28 @@ package app.simplecloud.plugin.connection.shared.registration +/** + * Manages server registration and lifecycle within the proxy network. + */ interface ServerRegistry { + /** + * Retrieves all currently registered servers. + * + * @return map of server IDs to their registered server instances + */ fun getRegistered(): Map + /** + * Registers a new server. + * + * @param server The server to register + */ fun register(server: RegisteredServer) + /** + * Unregisters a server. + * + * @param server The server to unregister + */ fun unregister(server: RegisteredServer) - } \ No newline at end of file From 5cc252e5aaac035a553f7181b0aadccd6d80b318 Mon Sep 17 00:00:00 2001 From: xXJanisXx Date: Thu, 19 Feb 2026 14:17:42 +0100 Subject: [PATCH 06/19] feat: add connection commands --- connection-bungeecord/build.gradle.kts | 1 + .../bungeecord/BungeeCordConnectionPlugin.kt | 12 +++ .../command/BungeeCordCommandManager.kt | 99 +++++++++++++++++ .../connection/shared/ConnectionPlugin.kt | 12 ++- .../connection/shared/config/CommandConfig.kt | 33 ++++++ .../shared/config/ConnectionConfig.kt | 12 --- .../shared/utilities/DefaultCommands.kt | 29 +++++ .../velocity/VelocityConnectionPlugin.kt | 12 +++ .../command/VelocityCommandManager.kt | 93 ++++++++++++++++ connection-waterdog/build.gradle.kts | 1 + .../waterdog/WaterDogConnectionPlugin.kt | 11 ++ .../command/WaterDogCommandManager.kt | 100 ++++++++++++++++++ gradle/libs.versions.toml | 3 +- 13 files changed, 401 insertions(+), 17 deletions(-) create mode 100644 connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/command/BungeeCordCommandManager.kt create mode 100644 connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/CommandConfig.kt create mode 100644 connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/utilities/DefaultCommands.kt create mode 100644 connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/command/VelocityCommandManager.kt create mode 100644 connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/command/WaterDogCommandManager.kt diff --git a/connection-bungeecord/build.gradle.kts b/connection-bungeecord/build.gradle.kts index 54c4b30..52fbb0e 100644 --- a/connection-bungeecord/build.gradle.kts +++ b/connection-bungeecord/build.gradle.kts @@ -7,6 +7,7 @@ dependencies { compileOnly(libs.simplecloud.api) compileOnly(libs.bungeecord.api) implementation(libs.adventure.platform.bungeecord) + implementation(libs.bundles.adventure) } modrinth { diff --git a/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/BungeeCordConnectionPlugin.kt b/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/BungeeCordConnectionPlugin.kt index 38e6e61..58cb5d1 100644 --- a/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/BungeeCordConnectionPlugin.kt +++ b/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/BungeeCordConnectionPlugin.kt @@ -1,6 +1,7 @@ package app.simplecloud.plugin.connection.bungeecord import app.simplecloud.api.CloudApi +import app.simplecloud.plugin.connection.bungeecord.command.BungeeCordCommandManager import app.simplecloud.plugin.connection.bungeecord.registration.BungeeCordServerRegistry import app.simplecloud.plugin.connection.shared.ConnectionPlugin import kotlinx.coroutines.* @@ -20,11 +21,21 @@ class BungeeCordConnectionPlugin : Plugin() { BungeeCordServerRegistry(this, proxy) ) + private val commandManager = BungeeCordCommandManager( + proxy, + this, + scope + ) + override fun onEnable() { logger.info("Initialize bungeecord-connection plugin...") connectionPlugin.config.save("config", connectionPlugin.connectionConfig) + connectionPlugin.config.save("commands", connectionPlugin.commandConfig) + cleanupServers() registerAdditionalServers() + commandManager.registerAll(connectionPlugin.commandConfig) + scope.launch { connectionPlugin.start() } @@ -32,6 +43,7 @@ class BungeeCordConnectionPlugin : Plugin() { override fun onDisable() { logger.info("Shutting down bungeecord-connection plugin...") + commandManager.unregisterAll() scope.launch { connectionPlugin.shutdown() } diff --git a/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/command/BungeeCordCommandManager.kt b/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/command/BungeeCordCommandManager.kt new file mode 100644 index 0000000..f599d9e --- /dev/null +++ b/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/command/BungeeCordCommandManager.kt @@ -0,0 +1,99 @@ +package app.simplecloud.plugin.connection.bungeecord.command + +import app.simplecloud.plugin.connection.bungeecord.BungeeCordConnectionPlugin +import app.simplecloud.plugin.connection.shared.config.CommandConfig +import app.simplecloud.plugin.connection.shared.config.CommandEntry +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import net.kyori.adventure.text.Component +import net.kyori.adventure.text.minimessage.MiniMessage +import net.md_5.bungee.api.ChatMessageType +import net.md_5.bungee.api.CommandSender +import net.md_5.bungee.api.ProxyServer +import net.md_5.bungee.api.connection.ProxiedPlayer +import net.md_5.bungee.api.plugin.Command +import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer +import org.apache.logging.log4j.LogManager + +class BungeeCordCommandManager( + private val proxy: ProxyServer, + private val plugin: BungeeCordConnectionPlugin, + private val scope: CoroutineScope +) { + + private val logger = LogManager.getLogger(BungeeCordCommandManager::class.java) + private val miniMessage = MiniMessage.miniMessage() + private val serializer = BungeeComponentSerializer.get() + private val registeredCommands = mutableListOf() + + fun registerAll(commandConfig: CommandConfig) { + commandConfig.commands.forEach { entry -> + registerCommand(entry) + } + logger.info("Registered ${registeredCommands.size} command(s): ${registeredCommands.joinToString(", ")}") + } + + fun unregisterAll() { + registeredCommands.forEach { _ -> + proxy.pluginManager.unregisterCommands(plugin) + } + logger.info("Unregistered ${registeredCommands.size} command(s)") + registeredCommands.clear() + } + + private fun registerCommand(entry: CommandEntry) { + val command = buildCommand(entry) + proxy.pluginManager.registerCommand(plugin, command) + registeredCommands.add(entry.name) + } + + private fun buildCommand(entry: CommandEntry): Command { + val permission = entry.permission.ifEmpty { null } + + return object : Command( + entry.name, + permission, + *entry.aliases.toTypedArray() + ) { + override fun execute(sender: CommandSender, args: Array) { + val player = sender as? ProxiedPlayer ?: return + scope.launch { handleCommand(player, entry) } + } + } + } + + private fun handleCommand(player: ProxiedPlayer, entry: CommandEntry) { + val currentServerName = player.server?.info?.name + val groupedByPriority = entry.targetConnections + .groupBy { it.priority } + .entries + .sortedByDescending { it.key } + + for ((_, targets) in groupedByPriority) { + for (target in targets.shuffled()) { + if (target.from.isNotEmpty()) { + val matchesFrom = currentServerName != null && + target.from.any { it.equals(currentServerName, ignoreCase = true) } + if (!matchesFrom) continue + } + + val serverInfo = proxy.getServerInfo(target.name) ?: continue + + if (currentServerName.equals(serverInfo.name, ignoreCase = true)) { + player.sendMessage(miniMessage.deserialize(entry.messages.alreadyConnected)) + return + } + + player.connect(serverInfo) + return + } + } + + player.sendMessage(miniMessage.deserialize(entry.messages.noTargetConnectionFound)) + } + + private fun ProxiedPlayer.sendMessage(component: Component) { + val bungeeComponents = serializer.serialize(component) + sendMessage(ChatMessageType.SYSTEM, *bungeeComponents) + } +} \ No newline at end of file diff --git a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/ConnectionPlugin.kt b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/ConnectionPlugin.kt index b88fecf..9e5eb30 100644 --- a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/ConnectionPlugin.kt +++ b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/ConnectionPlugin.kt @@ -4,6 +4,7 @@ import app.simplecloud.api.CloudApi import app.simplecloud.api.group.GroupServerType import app.simplecloud.api.server.ServerQuery import app.simplecloud.api.server.ServerState +import app.simplecloud.plugin.connection.shared.config.CommandConfig import app.simplecloud.plugin.connection.shared.config.ConnectionConfig import app.simplecloud.plugin.connection.shared.config.YamlConfig import app.simplecloud.plugin.connection.shared.listener.ServerEventListener @@ -11,24 +12,27 @@ import app.simplecloud.plugin.connection.shared.registration.ServerRegistry import org.apache.logging.log4j.LogManager class ConnectionPlugin( - private val dir: String, + dir: String, private val api: CloudApi, - private val registry: ServerRegistry, + registry: ServerRegistry, ) { private val logger = LogManager.getLogger(ConnectionPlugin::class.java) private val listener = ServerEventListener(api, registry) val config = YamlConfig(dir) + val connectionConfig: ConnectionConfig get() = config.load("config") ?: ConnectionConfig() + val commandConfig: CommandConfig + get() = config.load("commands") ?: CommandConfig() - suspend fun start() { + fun start() { loadExistingServers() listener.start() } - suspend fun shutdown() { + fun shutdown() { config.close() listener.stop() } diff --git a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/CommandConfig.kt b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/CommandConfig.kt new file mode 100644 index 0000000..4e5c1e5 --- /dev/null +++ b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/CommandConfig.kt @@ -0,0 +1,33 @@ +package app.simplecloud.plugin.connection.shared.config + +import app.simplecloud.plugin.connection.shared.utilities.ConfigVersion +import app.simplecloud.plugin.connection.shared.utilities.DefaultCommands +import org.spongepowered.configurate.objectmapping.ConfigSerializable + +@ConfigSerializable +data class CommandConfig( + val version: Char = ConfigVersion.VERSION, + val commands: List = DefaultCommands.DEFAULT +) + +@ConfigSerializable +data class CommandEntry( + val name: String, + val aliases: List = emptyList(), + val targetConnections: List = emptyList(), + val messages: CommandMessages = CommandMessages(), + val permission: String = "" +) + +@ConfigSerializable +data class TargetConnection( + val name: String, + val priority: Int = 0, + val from: List = emptyList() +) + +@ConfigSerializable +data class CommandMessages( + val alreadyConnected: String = "You are already connected to this lobby!", + val noTargetConnectionFound: String = "Couldn't find a target server!" +) \ No newline at end of file diff --git a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/ConnectionConfig.kt b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/ConnectionConfig.kt index 06d8fbc..f2f2762 100644 --- a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/ConnectionConfig.kt +++ b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/ConnectionConfig.kt @@ -7,23 +7,11 @@ import org.spongepowered.configurate.objectmapping.meta.Comment @ConfigSerializable data class ConnectionConfig( val version: Char = ConfigVersion.VERSION, - - @Comment(""" -─────────────────────────────────────────────────────────────────────────────── -Server Registration -This keeps your proxy's registered child servers in sync with SimpleCloud's -server registry. - -Read more @ https://docs.simplecloud.app/manual/plugins/server-connection -─────────────────────────────────────────────────────────────────────────────── - -Configure how servers are automatically registered with the proxy""") val registration: RegistrationConfig = RegistrationConfig(), ) @ConfigSerializable data class RegistrationConfig( - @Comment("Attention: If this is set to false, no child servers will be registered.") val enabled: Boolean = true, val serverNamePattern: String = "-", val persistentServerNamePattern: String = "", diff --git a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/utilities/DefaultCommands.kt b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/utilities/DefaultCommands.kt new file mode 100644 index 0000000..fae357d --- /dev/null +++ b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/utilities/DefaultCommands.kt @@ -0,0 +1,29 @@ +package app.simplecloud.plugin.connection.shared.utilities + +import app.simplecloud.plugin.connection.shared.config.CommandEntry +import app.simplecloud.plugin.connection.shared.config.CommandMessages +import app.simplecloud.plugin.connection.shared.config.TargetConnection + +object DefaultCommands { + + val DEFAULT: List = listOf( + lobbyCommand() + ) + + private fun lobbyCommand() = CommandEntry( + name = "lobby", + aliases = listOf("l", "hub", "quit", "leave"), + targetConnections = listOf( + TargetConnection( + name = "lobby", + priority = 0, + from = emptyList() + ) + ), + messages = CommandMessages( + alreadyConnected = "You are already connected to this lobby!", + noTargetConnectionFound = "Couldn't find a target server!" + ), + permission = "" + ) +} \ No newline at end of file diff --git a/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/VelocityConnectionPlugin.kt b/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/VelocityConnectionPlugin.kt index 3b0fdeb..55bc82e 100644 --- a/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/VelocityConnectionPlugin.kt +++ b/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/VelocityConnectionPlugin.kt @@ -2,6 +2,7 @@ package app.simplecloud.plugin.connection.velocity import app.simplecloud.api.CloudApi import app.simplecloud.plugin.connection.shared.ConnectionPlugin +import app.simplecloud.plugin.connection.velocity.command.VelocityCommandManager import app.simplecloud.plugin.connection.velocity.registration.VelocityServerRegistry import com.google.inject.Inject import com.velocitypowered.api.event.Subscribe @@ -38,12 +39,22 @@ class VelocityConnectionPlugin @Inject constructor( VelocityServerRegistry(this, server) ) + private val commandManager = VelocityCommandManager( + server, + this, + scope + ) + @Subscribe fun onProxyInitialize(event: ProxyInitializeEvent) { logger.info("Initialize velocity-connection plugin...") connectionPlugin.config.save("config", connectionPlugin.connectionConfig) + connectionPlugin.config.save("commands", connectionPlugin.commandConfig) + cleanupServers() registerAdditionalServers() + commandManager.registerAll(connectionPlugin.commandConfig) + scope.launch { connectionPlugin.start() } @@ -52,6 +63,7 @@ class VelocityConnectionPlugin @Inject constructor( @Subscribe fun onProxyShutdown(event: ProxyShutdownEvent) { logger.info("Shutting down velocity-connection plugin...") + commandManager.unregisterAll() scope.launch { connectionPlugin.shutdown() } diff --git a/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/command/VelocityCommandManager.kt b/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/command/VelocityCommandManager.kt new file mode 100644 index 0000000..77b6de9 --- /dev/null +++ b/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/command/VelocityCommandManager.kt @@ -0,0 +1,93 @@ +package app.simplecloud.plugin.connection.velocity.command + +import app.simplecloud.plugin.connection.shared.config.CommandConfig +import app.simplecloud.plugin.connection.shared.config.CommandEntry +import app.simplecloud.plugin.connection.velocity.VelocityConnectionPlugin +import com.velocitypowered.api.command.BrigadierCommand +import com.velocitypowered.api.proxy.Player +import com.velocitypowered.api.proxy.ProxyServer +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import net.kyori.adventure.text.minimessage.MiniMessage +import org.apache.logging.log4j.LogManager + +class VelocityCommandManager( + private val server: ProxyServer, + private val plugin: VelocityConnectionPlugin, + private val scope: CoroutineScope +) { + + private val logger = LogManager.getLogger(VelocityCommandManager::class.java) + private val miniMessage = MiniMessage.miniMessage() + private val registeredCommands = mutableListOf() + + fun registerAll(commandConfig: CommandConfig) { + commandConfig.commands.forEach { entry -> + registerCommand(entry) + } + logger.info("Registered ${registeredCommands.size} command(s): ${registeredCommands.joinToString(", ")}") + } + + fun unregisterAll() { + val commandManager = server.commandManager + registeredCommands.forEach { name -> commandManager.unregister(name) } + logger.info("Unregistered ${registeredCommands.size} command(s)") + registeredCommands.clear() + } + + private fun registerCommand(entry: CommandEntry) { + val commandManager = server.commandManager + val meta = commandManager.metaBuilder(entry.name) + .aliases(*entry.aliases.toTypedArray()) + .plugin(plugin) + .build() + + commandManager.register(meta, buildBrigadierCommand(entry)) + registeredCommands.add(entry.name) + } + + private fun buildBrigadierCommand(entry: CommandEntry): BrigadierCommand { + val node = BrigadierCommand.literalArgumentBuilder(entry.name) + .requires { + entry.permission.isEmpty() || it.hasPermission(entry.permission) + } + .executes { context -> + val player = context.source as? Player ?: return@executes 0 + scope.launch { handleCommand(player, entry) } + 1 + } + .build() + + return BrigadierCommand(node) + } + + private fun handleCommand(player: Player, entry: CommandEntry) { + val currentServerName = player.currentServer.orElse(null)?.serverInfo?.name + val groupedByPriority = entry.targetConnections + .groupBy { it.priority } + .entries + .sortedByDescending { it.key } + + for ((_, targets) in groupedByPriority) { + for (target in targets.shuffled()) { + if (target.from.isNotEmpty()) { + val matchesFrom = currentServerName != null && + target.from.any { it.equals(currentServerName, ignoreCase = true) } + if (!matchesFrom) continue + } + + val registeredServer = server.getServer(target.name).orElse(null) ?: continue + + if (currentServerName.equals(registeredServer.serverInfo.name, ignoreCase = true)) { + player.sendMessage(miniMessage.deserialize(entry.messages.alreadyConnected)) + return + } + + player.createConnectionRequest(registeredServer).fireAndForget() + return + } + } + + player.sendMessage(miniMessage.deserialize(entry.messages.noTargetConnectionFound)) + } +} \ No newline at end of file diff --git a/connection-waterdog/build.gradle.kts b/connection-waterdog/build.gradle.kts index 97860a9..6b2fc74 100644 --- a/connection-waterdog/build.gradle.kts +++ b/connection-waterdog/build.gradle.kts @@ -1,5 +1,6 @@ dependencies { api(project(":connection-shared")) + implementation(libs.bundles.adventure) compileOnly(libs.simplecloud.api) compileOnly(libs.waterdog.api) } \ No newline at end of file diff --git a/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/WaterDogConnectionPlugin.kt b/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/WaterDogConnectionPlugin.kt index 9316fb2..81326ac 100644 --- a/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/WaterDogConnectionPlugin.kt +++ b/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/WaterDogConnectionPlugin.kt @@ -2,6 +2,7 @@ package app.simplecloud.plugin.connection.waterdog import app.simplecloud.api.CloudApi import app.simplecloud.plugin.connection.shared.ConnectionPlugin +import app.simplecloud.plugin.connection.waterdog.command.WaterDogCommandManager import app.simplecloud.plugin.connection.waterdog.registration.WaterDogServerRegistry import dev.waterdog.waterdogpe.network.serverinfo.BedrockServerInfo import dev.waterdog.waterdogpe.plugin.Plugin @@ -21,11 +22,20 @@ class WaterDogConnectionPlugin : Plugin() { WaterDogServerRegistry(this, proxy) ) + private val commandManager = WaterDogCommandManager( + proxy, + scope + ) + override fun onEnable() { logger.info("Initialize waterdog-connection plugin...") connectionPlugin.config.save("config", connectionPlugin.connectionConfig) + connectionPlugin.config.save("commands", connectionPlugin.commandConfig) + cleanupServers() registerAdditionalServers() + commandManager.registerAll(connectionPlugin.commandConfig) + scope.launch { connectionPlugin.start() } @@ -33,6 +43,7 @@ class WaterDogConnectionPlugin : Plugin() { override fun onDisable() { logger.info("Shutting down waterdog-connection plugin...") + commandManager.unregisterAll() scope.launch { connectionPlugin.shutdown() } diff --git a/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/command/WaterDogCommandManager.kt b/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/command/WaterDogCommandManager.kt new file mode 100644 index 0000000..ee799b9 --- /dev/null +++ b/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/command/WaterDogCommandManager.kt @@ -0,0 +1,100 @@ +package app.simplecloud.plugin.connection.waterdog.command + +import app.simplecloud.plugin.connection.shared.config.CommandConfig +import app.simplecloud.plugin.connection.shared.config.CommandEntry +import dev.waterdog.waterdogpe.command.Command +import dev.waterdog.waterdogpe.command.CommandSender +import dev.waterdog.waterdogpe.command.CommandSettings +import dev.waterdog.waterdogpe.player.ProxiedPlayer +import dev.waterdog.waterdogpe.ProxyServer +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import net.kyori.adventure.text.minimessage.MiniMessage +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer +import org.apache.logging.log4j.LogManager + +class WaterDogCommandManager( + private val proxy: ProxyServer, + private val scope: CoroutineScope +) { + + private val logger = LogManager.getLogger(WaterDogCommandManager::class.java) + private val miniMessage = MiniMessage.miniMessage() + private val registeredCommands = mutableListOf() + + fun registerAll(commandConfig: CommandConfig) { + commandConfig.commands.forEach { entry -> + registerCommand(entry) + } + logger.info("Registered ${registeredCommands.size} command(s): ${registeredCommands.joinToString(", ")}") + } + + fun unregisterAll() { + registeredCommands.forEach { name -> + proxy.commandMap.unregisterCommand(name) + } + logger.info("Unregistered ${registeredCommands.size} command(s)") + registeredCommands.clear() + } + + private fun registerCommand(entry: CommandEntry) { + val command = buildCommand(entry) + proxy.commandMap.registerCommand(command) + registeredCommands.add(entry.name) + } + + private fun buildCommand(entry: CommandEntry): Command { + val settings = CommandSettings.builder() + .setAliases(entry.aliases.toTypedArray().toString()) + .apply { + if (entry.permission.isNotEmpty()) { + permission = entry.permission + } + } + .build() + + return object : Command(entry.name, settings) { + override fun onExecute(sender: CommandSender, alias: String?, args: Array): Boolean { + val player = sender as? ProxiedPlayer ?: return false + scope.launch { handleCommand(player, entry) } + return true + } + } + } + + private fun handleCommand(player: ProxiedPlayer, entry: CommandEntry) { + val currentServerName = player.serverInfo?.serverName + val groupedByPriority = entry.targetConnections + .groupBy { it.priority } + .entries + .sortedByDescending { it.key } + + for ((_, targets) in groupedByPriority) { + for (target in targets.shuffled()) { + if (target.from.isNotEmpty()) { + val matchesFrom = currentServerName != null && + target.from.any { it.equals(currentServerName, ignoreCase = true) } + if (!matchesFrom) continue + } + + val serverInfo = proxy.getServerInfo(target.name) ?: continue + + if (currentServerName.equals(serverInfo.serverName, ignoreCase = true)) { + sendMessage(player, entry.messages.alreadyConnected) + return + } + + player.connect(serverInfo) + return + } + } + + sendMessage(player, entry.messages.noTargetConnectionFound) + } + + private fun sendMessage(player: ProxiedPlayer, rawMessage: String) { + val component = miniMessage.deserialize(rawMessage) + val plain = PlainTextComponentSerializer.plainText().serialize(component) + player.sendMessage(plain) + } +} \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6e2641f..6c9d247 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -34,6 +34,7 @@ waterdog-api = { module = "dev.waterdog.waterdogpe:waterdog", version.ref = "wat adventure-api = { module = "net.kyori:adventure-api", version.ref = "adventure-api" } adventure-platform-bungeecord = { module = "net.kyori:adventure-platform-bungeecord", version.ref = "adventure-platform-bungeecord" } adventure-text-minimessage = { module = "net.kyori:adventure-text-minimessage", version.ref = "adventure-api" } +adventure-text-serializer-plain = { module = "net.kyori:adventure-text-serializer-plain", version.ref = "adventure-api" } commons-io = { module = "commons-io:commons-io", version.ref = "commons-io" } @@ -47,7 +48,7 @@ log4j-slf4j-impl = { module = "org.apache.logging.log4j:log4j-slf4j-impl", versi [bundles] logging = ["log4j-core", "log4j-api", "log4j-slf4j-impl"] configurate = ["configurate-kotlin", "configurate-yaml"] -adventure = ["adventure-api", "adventure-text-minimessage"] +adventure = ["adventure-api", "adventure-text-minimessage", "adventure-text-serializer-plain"] [plugins] kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } From 1e2aef193f7575fe222805cf7f014ac3152ea019 Mon Sep 17 00:00:00 2001 From: xXJanisXx Date: Thu, 19 Feb 2026 14:38:44 +0100 Subject: [PATCH 07/19] feat: improve server registration --- .../shared/listener/ServerEventListener.kt | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/listener/ServerEventListener.kt b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/listener/ServerEventListener.kt index f586833..5fe7c28 100644 --- a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/listener/ServerEventListener.kt +++ b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/listener/ServerEventListener.kt @@ -54,13 +54,24 @@ class ServerEventListener( private fun register(server: RegisteredServer) { if (server.blueprintConfigurator == "standalone") return - logger.info("Registering server ${server.serverId}...") + + if (server.persistent) { + logger.info("Registering server ${server.serverId} (${server.serverBaseName})...") + } else { + logger.info("Registering server ${server.serverId} (${server.serverBaseName}-${server.numericalId})...") + } + registry.register(server) } private fun unregister(server: RegisteredServer) { if (registry.getRegistered().containsKey(server.serverId)) { - logger.info("Unregistering server ${server.serverId}...") + if (server.persistent) { + logger.info("Unregistering server ${server.serverId} (${server.serverBaseName})...") + } else { + logger.info("Unregistering server ${server.serverId} (${server.serverBaseName}-${server.numericalId})...") + } + registry.unregister(server) } } From b56ce8097d32b39239d912643533829a3427562c Mon Sep 17 00:00:00 2001 From: xXJanisXx Date: Thu, 19 Feb 2026 21:51:02 +0100 Subject: [PATCH 08/19] feat: server connection --- .../bungeecord/BungeeCordConnectionPlugin.kt | 16 +++-- .../listener/ServerConnectListener.kt | 35 +++++++++++ .../bungeecord/listener/ServerKickListener.kt | 61 +++++++++++++++++++ connection-shared/build.gradle.kts | 2 +- .../connection/shared/ConnectionPlugin.kt | 9 +-- .../connection/shared/config/CommandConfig.kt | 7 --- .../shared/config/ConnectionConfig.kt | 35 ++++++++++- .../connection/shared/config/MessageConfig.kt | 33 ++++++++++ .../shared/connection/ConnectionResolver.kt | 48 +++++++++++++++ .../resolver/RegisteredServerResolver.kt | 9 ++- .../velocity/VelocityConnectionPlugin.kt | 18 +++--- .../command/VelocityCommandManager.kt | 9 +-- .../listener/KickedFromServerListener.kt | 60 ++++++++++++++++++ .../PlayerChooseInitialServerListener.kt | 46 ++++++++++++++ .../waterdog/WaterDogConnectionPlugin.kt | 6 ++ .../connection/WaterdogJoinHandler.kt | 27 ++++++++ .../connection/WaterdogReconnectHandler.kt | 58 ++++++++++++++++++ 17 files changed, 442 insertions(+), 37 deletions(-) create mode 100644 connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/listener/ServerConnectListener.kt create mode 100644 connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/listener/ServerKickListener.kt create mode 100644 connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/MessageConfig.kt create mode 100644 connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/connection/ConnectionResolver.kt create mode 100644 connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/listener/KickedFromServerListener.kt create mode 100644 connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/listener/PlayerChooseInitialServerListener.kt create mode 100644 connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/connection/WaterdogJoinHandler.kt create mode 100644 connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/connection/WaterdogReconnectHandler.kt diff --git a/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/BungeeCordConnectionPlugin.kt b/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/BungeeCordConnectionPlugin.kt index 58cb5d1..3728d8d 100644 --- a/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/BungeeCordConnectionPlugin.kt +++ b/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/BungeeCordConnectionPlugin.kt @@ -2,6 +2,8 @@ package app.simplecloud.plugin.connection.bungeecord import app.simplecloud.api.CloudApi import app.simplecloud.plugin.connection.bungeecord.command.BungeeCordCommandManager +import app.simplecloud.plugin.connection.bungeecord.listener.ServerConnectListener +import app.simplecloud.plugin.connection.bungeecord.listener.ServerKickListener import app.simplecloud.plugin.connection.bungeecord.registration.BungeeCordServerRegistry import app.simplecloud.plugin.connection.shared.ConnectionPlugin import kotlinx.coroutines.* @@ -14,6 +16,7 @@ class BungeeCordConnectionPlugin : Plugin() { private val api = CloudApi.create() private val logger = LogManager.getLogger(BungeeCordConnectionPlugin::class.java) private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob()) + private val commandManager = BungeeCordCommandManager(proxy, this, scope) val connectionPlugin = ConnectionPlugin( dataFolder.toString(), @@ -21,12 +24,6 @@ class BungeeCordConnectionPlugin : Plugin() { BungeeCordServerRegistry(this, proxy) ) - private val commandManager = BungeeCordCommandManager( - proxy, - this, - scope - ) - override fun onEnable() { logger.info("Initialize bungeecord-connection plugin...") connectionPlugin.config.save("config", connectionPlugin.connectionConfig) @@ -36,6 +33,8 @@ class BungeeCordConnectionPlugin : Plugin() { registerAdditionalServers() commandManager.registerAll(connectionPlugin.commandConfig) + registerListener() + scope.launch { connectionPlugin.start() } @@ -71,4 +70,9 @@ class BungeeCordConnectionPlugin : Plugin() { logger.info("Additional server ${info.name} has been registered!") } } + + private fun registerListener() { + proxy.pluginManager.registerListener(this, ServerConnectListener(proxy) { connectionPlugin.connectionConfig }) + proxy.pluginManager.registerListener(this, ServerKickListener(proxy, { connectionPlugin.connectionConfig }, { connectionPlugin.messageConfig })) + } } \ No newline at end of file diff --git a/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/listener/ServerConnectListener.kt b/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/listener/ServerConnectListener.kt new file mode 100644 index 0000000..c0b9fb6 --- /dev/null +++ b/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/listener/ServerConnectListener.kt @@ -0,0 +1,35 @@ +package app.simplecloud.plugin.connection.bungeecord.listener + +import app.simplecloud.plugin.connection.shared.config.ConnectionConfig +import app.simplecloud.plugin.connection.shared.connection.ConnectionResolver +import net.md_5.bungee.api.ProxyServer +import net.md_5.bungee.api.event.ServerConnectEvent +import net.md_5.bungee.api.plugin.Listener +import net.md_5.bungee.event.EventHandler +import net.md_5.bungee.event.EventPriority + +class ServerConnectListener( + private val proxy: ProxyServer, + private val config: () -> ConnectionConfig +) : Listener { + + @EventHandler(priority = EventPriority.NORMAL) + fun onServerConnect(event: ServerConnectEvent) { + if (event.reason != ServerConnectEvent.Reason.JOIN_PROXY) return + + val currentConfig = config() + if (!currentConfig.networkJoinTargets.enabled) return + + val resolver = ConnectionResolver(currentConfig) + val entry = resolver.resolve( + targetConnections = currentConfig.networkJoinTargets.targetConnections, + currentServerName = null + ) ?: return + + val targetServer = proxy.servers.values + .filter { entry.serverNameMatcher.matches(it.name) } + .randomOrNull() ?: return + + event.target = targetServer + } +} \ No newline at end of file diff --git a/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/listener/ServerKickListener.kt b/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/listener/ServerKickListener.kt new file mode 100644 index 0000000..2974058 --- /dev/null +++ b/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/listener/ServerKickListener.kt @@ -0,0 +1,61 @@ +package app.simplecloud.plugin.connection.bungeecord.listener + +import app.simplecloud.plugin.connection.shared.config.ConnectionConfig +import app.simplecloud.plugin.connection.shared.config.MessageConfig +import app.simplecloud.plugin.connection.shared.connection.ConnectionResolver +import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer +import net.md_5.bungee.api.ProxyServer +import net.md_5.bungee.api.event.ServerKickEvent +import net.md_5.bungee.api.plugin.Listener +import net.md_5.bungee.event.EventHandler +import net.md_5.bungee.event.EventPriority + +class ServerKickListener( + private val proxy: ProxyServer, + private val config: () -> ConnectionConfig, + private val messageConfig: () -> MessageConfig +) : Listener { + + private val serializer = BungeeComponentSerializer.get() + + @EventHandler(priority = EventPriority.NORMAL) + fun onServerKick(event: ServerKickEvent) { + val currentConfig = config() + val messages = messageConfig() + + if (!currentConfig.fallback.enabled) return + + val kickedFromServerName = event.kickedFrom.name + val resolver = ConnectionResolver(currentConfig) + + val entry = resolver.resolve( + targetConnections = currentConfig.fallback.targetConnections, + currentServerName = kickedFromServerName + ) + + if (entry == null) { + event.isCancelled = true + event.player.disconnect( + *serializer.serialize(messages.deserialize(messages.kick.noTargetConnection)) + ) + return + } + + val candidates = proxy.servers.values + .filter { entry.serverNameMatcher.matches(it.name) } + .filter { !it.name.equals(kickedFromServerName, ignoreCase = true) } + + val targetServer = candidates.randomOrNull() + + if (targetServer == null) { + event.isCancelled = true + event.player.disconnect( + *serializer.serialize(messages.deserialize(messages.kick.noFallbackServers)) + ) + return + } + + event.cancelServer = targetServer + event.isCancelled = true + } +} \ No newline at end of file diff --git a/connection-shared/build.gradle.kts b/connection-shared/build.gradle.kts index af325f3..2bf1ad7 100644 --- a/connection-shared/build.gradle.kts +++ b/connection-shared/build.gradle.kts @@ -1,6 +1,6 @@ dependencies { compileOnly(libs.simplecloud.api) - implementation(libs.simplecloud.plugin.api) + api(libs.simplecloud.plugin.api) implementation(libs.commons.io) implementation(libs.bundles.adventure) implementation(libs.bundles.configurate) diff --git a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/ConnectionPlugin.kt b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/ConnectionPlugin.kt index 9e5eb30..3aa071b 100644 --- a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/ConnectionPlugin.kt +++ b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/ConnectionPlugin.kt @@ -6,6 +6,7 @@ import app.simplecloud.api.server.ServerQuery import app.simplecloud.api.server.ServerState import app.simplecloud.plugin.connection.shared.config.CommandConfig import app.simplecloud.plugin.connection.shared.config.ConnectionConfig +import app.simplecloud.plugin.connection.shared.config.MessageConfig import app.simplecloud.plugin.connection.shared.config.YamlConfig import app.simplecloud.plugin.connection.shared.listener.ServerEventListener import app.simplecloud.plugin.connection.shared.registration.ServerRegistry @@ -22,10 +23,9 @@ class ConnectionPlugin( val config = YamlConfig(dir) - val connectionConfig: ConnectionConfig - get() = config.load("config") ?: ConnectionConfig() - val commandConfig: CommandConfig - get() = config.load("commands") ?: CommandConfig() + val connectionConfig: ConnectionConfig get() = config.load("config") ?: ConnectionConfig() + val commandConfig: CommandConfig get() = config.load("commands") ?: CommandConfig() + val messageConfig: MessageConfig get() = config.load("messages") ?: MessageConfig() fun start() { loadExistingServers() @@ -46,5 +46,6 @@ class ConnectionPlugin( logger.info("Found ${servers.size} servers") servers.forEach { listener.register(it) } } + } } \ No newline at end of file diff --git a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/CommandConfig.kt b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/CommandConfig.kt index 4e5c1e5..53e70bd 100644 --- a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/CommandConfig.kt +++ b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/CommandConfig.kt @@ -19,13 +19,6 @@ data class CommandEntry( val permission: String = "" ) -@ConfigSerializable -data class TargetConnection( - val name: String, - val priority: Int = 0, - val from: List = emptyList() -) - @ConfigSerializable data class CommandMessages( val alreadyConnected: String = "You are already connected to this lobby!", diff --git a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/ConnectionConfig.kt b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/ConnectionConfig.kt index f2f2762..ae6bf07 100644 --- a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/ConnectionConfig.kt +++ b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/ConnectionConfig.kt @@ -1,13 +1,16 @@ package app.simplecloud.plugin.connection.shared.config +import app.simplecloud.plugin.api.shared.matcher.ServerMatcherConfiguration import app.simplecloud.plugin.connection.shared.utilities.ConfigVersion import org.spongepowered.configurate.objectmapping.ConfigSerializable -import org.spongepowered.configurate.objectmapping.meta.Comment @ConfigSerializable data class ConnectionConfig( val version: Char = ConfigVersion.VERSION, val registration: RegistrationConfig = RegistrationConfig(), + val connections: List = listOf(ConnectionEntry()), + val networkJoinTargets: NetworkJoinTargets = NetworkJoinTargets(), + val fallback: FallbackConfig = FallbackConfig(), ) @ConfigSerializable @@ -24,4 +27,34 @@ data class RegistrationServer( val name: String = "", val address: String = "", val port: Long = 0L +) + +@ConfigSerializable +data class ConnectionEntry( + val name: String = "lobby", + val serverNameMatcher: ServerMatcherConfiguration = ServerMatcherConfiguration(), + val rules: List = listOf() +) + +@ConfigSerializable +data class NetworkJoinTargets( + val enabled: Boolean = true, + val targetConnections: List = listOf( + TargetConnection(name = "lobby", priority = 0) + ) +) + +@ConfigSerializable +data class FallbackConfig( + val enabled: Boolean = true, + val targetConnections: List = listOf( + TargetConnection(name = "lobby", priority = 0) + ) +) + +@ConfigSerializable +data class TargetConnection( + val name: String = "", + val priority: Int = 0, + val from: List = listOf() ) \ No newline at end of file diff --git a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/MessageConfig.kt b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/MessageConfig.kt new file mode 100644 index 0000000..082e5cd --- /dev/null +++ b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/MessageConfig.kt @@ -0,0 +1,33 @@ +package app.simplecloud.plugin.connection.shared.config + +import app.simplecloud.plugin.connection.shared.utilities.ConfigVersion +import net.kyori.adventure.text.Component +import net.kyori.adventure.text.minimessage.MiniMessage +import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder +import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver +import org.spongepowered.configurate.objectmapping.ConfigSerializable + +@ConfigSerializable +data class MessageConfig( + val version: Char = ConfigVersion.VERSION, + val variables: Map = mapOf(), + val kick: KickMessages = KickMessages() +) { + private fun buildVariableResolver(): TagResolver { + val placeholders = variables.map { (key, value) -> + Placeholder.parsed(key, value) + } + return TagResolver.resolver(placeholders) + } + + fun deserialize(raw: String, vararg extra: TagResolver): Component { + val resolver = TagResolver.resolver(buildVariableResolver(), *extra) + return MiniMessage.miniMessage().deserialize(raw, resolver) + } +} + +@ConfigSerializable +data class KickMessages( + val noFallbackServers: String = "There is no fallback server available.", + val noTargetConnection: String = "You have been disconnected from the network
because there are no fallback servers available." +) \ No newline at end of file diff --git a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/connection/ConnectionResolver.kt b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/connection/ConnectionResolver.kt new file mode 100644 index 0000000..94f24aa --- /dev/null +++ b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/connection/ConnectionResolver.kt @@ -0,0 +1,48 @@ +package app.simplecloud.plugin.connection.shared.connection + +import app.simplecloud.plugin.connection.shared.config.ConnectionConfig +import app.simplecloud.plugin.connection.shared.config.ConnectionEntry +import app.simplecloud.plugin.connection.shared.config.TargetConnection + +/** + * Resolves a list of [TargetConnection]s to the best matching [ConnectionEntry] + */ +class ConnectionResolver( + private val config: ConnectionConfig +) { + + /** + * Resolves the best [ConnectionEntry] for the given [targetConnections]. + * + * @param targetConnections the list of target connections to try + * @param currentServerName the name of the server the player is currently on + * @return the resolved [ConnectionEntry] + */ + fun resolve( + targetConnections: List, + currentServerName: String? + ): ConnectionEntry? { + val groupedByPriority = targetConnections + .groupBy { it.priority } + .entries + .sortedByDescending { it.key } + + for ((_, targets) in groupedByPriority) { + for (target in targets.shuffled()) { + if (target.from.isNotEmpty()) { + val matchesFrom = currentServerName != null && + target.from.any { it.equals(currentServerName, ignoreCase = true) } + if (!matchesFrom) continue + } + + val entry = config.connections.find { it.name == target.name } + ?: continue + + return entry + } + } + + return null + } + +} \ No newline at end of file diff --git a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/resolver/RegisteredServerResolver.kt b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/resolver/RegisteredServerResolver.kt index 1a3bdd4..981c84c 100644 --- a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/resolver/RegisteredServerResolver.kt +++ b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/resolver/RegisteredServerResolver.kt @@ -5,8 +5,12 @@ import app.simplecloud.plugin.connection.shared.registration.RegisteredServer import net.kyori.adventure.text.minimessage.MiniMessage import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer object RegisteredServerResolver { + + private val serializer = PlainTextComponentSerializer.plainText() + fun resolve(server: RegisteredServer, config: RegistrationConfig): String { val pattern = if (!server.persistent) config.serverNamePattern else config.persistentServerNamePattern @@ -20,8 +24,7 @@ object RegisteredServerResolver { }.toTypedArray() ) - return MiniMessage.miniMessage().stripTags( - MiniMessage.miniMessage().deserialize(pattern, resolver).toString() - ) + val component = MiniMessage.miniMessage().deserialize(pattern, resolver) + return serializer.serialize(component) } } \ No newline at end of file diff --git a/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/VelocityConnectionPlugin.kt b/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/VelocityConnectionPlugin.kt index 55bc82e..74abe68 100644 --- a/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/VelocityConnectionPlugin.kt +++ b/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/VelocityConnectionPlugin.kt @@ -3,6 +3,8 @@ package app.simplecloud.plugin.connection.velocity import app.simplecloud.api.CloudApi import app.simplecloud.plugin.connection.shared.ConnectionPlugin import app.simplecloud.plugin.connection.velocity.command.VelocityCommandManager +import app.simplecloud.plugin.connection.velocity.listener.KickedFromServerListener +import app.simplecloud.plugin.connection.velocity.listener.PlayerChooseInitialServerListener import app.simplecloud.plugin.connection.velocity.registration.VelocityServerRegistry import com.google.inject.Inject import com.velocitypowered.api.event.Subscribe @@ -32,6 +34,7 @@ class VelocityConnectionPlugin @Inject constructor( private val api = CloudApi.create() private val logger = LogManager.getLogger(VelocityConnectionPlugin::class.java) private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob()) + private val commandManager = VelocityCommandManager(server, this) val connectionPlugin = ConnectionPlugin( dataDirectory.toString(), @@ -39,21 +42,15 @@ class VelocityConnectionPlugin @Inject constructor( VelocityServerRegistry(this, server) ) - private val commandManager = VelocityCommandManager( - server, - this, - scope - ) - @Subscribe fun onProxyInitialize(event: ProxyInitializeEvent) { logger.info("Initialize velocity-connection plugin...") connectionPlugin.config.save("config", connectionPlugin.connectionConfig) - connectionPlugin.config.save("commands", connectionPlugin.commandConfig) cleanupServers() registerAdditionalServers() commandManager.registerAll(connectionPlugin.commandConfig) + registerListeners() scope.launch { connectionPlugin.start() @@ -70,6 +67,13 @@ class VelocityConnectionPlugin @Inject constructor( scope.cancel() } + private fun registerListeners() { + val eventManager = server.eventManager + + eventManager.register(this, PlayerChooseInitialServerListener(server) { connectionPlugin.connectionConfig }) + eventManager.register(this, KickedFromServerListener(server, scope, { connectionPlugin.connectionConfig }, { connectionPlugin.messageConfig })) + } + private fun cleanupServers() { server.allServers.forEach { server.unregisterServer(it.serverInfo) diff --git a/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/command/VelocityCommandManager.kt b/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/command/VelocityCommandManager.kt index 77b6de9..005d41d 100644 --- a/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/command/VelocityCommandManager.kt +++ b/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/command/VelocityCommandManager.kt @@ -6,18 +6,13 @@ import app.simplecloud.plugin.connection.velocity.VelocityConnectionPlugin import com.velocitypowered.api.command.BrigadierCommand import com.velocitypowered.api.proxy.Player import com.velocitypowered.api.proxy.ProxyServer -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch import net.kyori.adventure.text.minimessage.MiniMessage -import org.apache.logging.log4j.LogManager class VelocityCommandManager( private val server: ProxyServer, private val plugin: VelocityConnectionPlugin, - private val scope: CoroutineScope ) { - private val logger = LogManager.getLogger(VelocityCommandManager::class.java) private val miniMessage = MiniMessage.miniMessage() private val registeredCommands = mutableListOf() @@ -25,13 +20,11 @@ class VelocityCommandManager( commandConfig.commands.forEach { entry -> registerCommand(entry) } - logger.info("Registered ${registeredCommands.size} command(s): ${registeredCommands.joinToString(", ")}") } fun unregisterAll() { val commandManager = server.commandManager registeredCommands.forEach { name -> commandManager.unregister(name) } - logger.info("Unregistered ${registeredCommands.size} command(s)") registeredCommands.clear() } @@ -53,7 +46,7 @@ class VelocityCommandManager( } .executes { context -> val player = context.source as? Player ?: return@executes 0 - scope.launch { handleCommand(player, entry) } + handleCommand(player, entry) 1 } .build() diff --git a/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/listener/KickedFromServerListener.kt b/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/listener/KickedFromServerListener.kt new file mode 100644 index 0000000..21fa623 --- /dev/null +++ b/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/listener/KickedFromServerListener.kt @@ -0,0 +1,60 @@ +package app.simplecloud.plugin.connection.velocity.listener + +import app.simplecloud.plugin.connection.shared.config.ConnectionConfig +import app.simplecloud.plugin.connection.shared.config.MessageConfig +import app.simplecloud.plugin.connection.shared.connection.ConnectionResolver +import com.velocitypowered.api.event.EventTask +import com.velocitypowered.api.event.PostOrder +import com.velocitypowered.api.event.Subscribe +import com.velocitypowered.api.event.player.KickedFromServerEvent +import com.velocitypowered.api.proxy.ProxyServer +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.future.future + +class KickedFromServerListener( + private val proxyServer: ProxyServer, + private val scope: CoroutineScope, + private val config: () -> ConnectionConfig, + private val messageConfig: () -> MessageConfig +) { + + @Subscribe(order = PostOrder.NORMAL) + fun onKickedFromServer(event: KickedFromServerEvent): EventTask { + return EventTask.resumeWhenComplete( + scope.future { handleFallback(event) } + ) + } + + private fun handleFallback(event: KickedFromServerEvent) { + val currentConfig = config() + val messages = messageConfig() + + if (!currentConfig.fallback.enabled) return + + val kickedFromServerName = event.server.serverInfo.name + val resolver = ConnectionResolver(currentConfig) + + val entry = resolver.resolve( + targetConnections = currentConfig.fallback.targetConnections, + currentServerName = kickedFromServerName + ) + + if (entry == null) { + event.result = KickedFromServerEvent.DisconnectPlayer.create(messages.deserialize(messages.kick.noTargetConnection)) + return + } + + val candidates = proxyServer.allServers + .filter { entry.serverNameMatcher.matches(it.serverInfo.name) } + .filter { !it.serverInfo.name.equals(kickedFromServerName, ignoreCase = true) } + + val targetServer = candidates.randomOrNull() + + if (targetServer == null) { + event.result = KickedFromServerEvent.DisconnectPlayer.create(messages.deserialize(messages.kick.noFallbackServers)) + return + } + + event.result = KickedFromServerEvent.RedirectPlayer.create(targetServer, event.serverKickReason.orElse(null)) + } +} \ No newline at end of file diff --git a/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/listener/PlayerChooseInitialServerListener.kt b/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/listener/PlayerChooseInitialServerListener.kt new file mode 100644 index 0000000..af7d220 --- /dev/null +++ b/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/listener/PlayerChooseInitialServerListener.kt @@ -0,0 +1,46 @@ +package app.simplecloud.plugin.connection.velocity.listener + +import app.simplecloud.plugin.connection.shared.config.ConnectionConfig +import app.simplecloud.plugin.connection.shared.connection.ConnectionResolver +import com.velocitypowered.api.event.PostOrder +import com.velocitypowered.api.event.Subscribe +import com.velocitypowered.api.event.player.PlayerChooseInitialServerEvent +import com.velocitypowered.api.proxy.ProxyServer + +class PlayerChooseInitialServerListener( + private val proxyServer: ProxyServer, + private val config: () -> ConnectionConfig +) { + + @Subscribe(order = PostOrder.NORMAL) + fun onPlayerChooseInitialServer(event: PlayerChooseInitialServerEvent) { + handleJoin(event) + } + + private fun handleJoin(event: PlayerChooseInitialServerEvent) { + val currentConfig = config() + + if (!currentConfig.networkJoinTargets.enabled) return + + val resolver = ConnectionResolver(currentConfig) + + val entry = resolver.resolve( + targetConnections = currentConfig.networkJoinTargets.targetConnections, + currentServerName = null + ) + + if (entry == null) { + return + } + + val targetServer = proxyServer.allServers + .filter { entry.serverNameMatcher.matches(it.serverInfo.name) } + .randomOrNull() + + if (targetServer == null) { + return + } + + event.setInitialServer(targetServer) + } +} \ No newline at end of file diff --git a/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/WaterDogConnectionPlugin.kt b/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/WaterDogConnectionPlugin.kt index 81326ac..2e3db53 100644 --- a/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/WaterDogConnectionPlugin.kt +++ b/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/WaterDogConnectionPlugin.kt @@ -3,6 +3,8 @@ package app.simplecloud.plugin.connection.waterdog import app.simplecloud.api.CloudApi import app.simplecloud.plugin.connection.shared.ConnectionPlugin import app.simplecloud.plugin.connection.waterdog.command.WaterDogCommandManager +import app.simplecloud.plugin.connection.waterdog.connection.WaterdogJoinHandler +import app.simplecloud.plugin.connection.waterdog.connection.WaterdogReconnectHandler import app.simplecloud.plugin.connection.waterdog.registration.WaterDogServerRegistry import dev.waterdog.waterdogpe.network.serverinfo.BedrockServerInfo import dev.waterdog.waterdogpe.plugin.Plugin @@ -31,11 +33,15 @@ class WaterDogConnectionPlugin : Plugin() { logger.info("Initialize waterdog-connection plugin...") connectionPlugin.config.save("config", connectionPlugin.connectionConfig) connectionPlugin.config.save("commands", connectionPlugin.commandConfig) + connectionPlugin.config.save("messages", connectionPlugin.messageConfig) cleanupServers() registerAdditionalServers() commandManager.registerAll(connectionPlugin.commandConfig) + proxy.joinHandler = WaterdogJoinHandler { connectionPlugin.connectionConfig } + proxy.reconnectHandler = WaterdogReconnectHandler({ connectionPlugin.connectionConfig }, { connectionPlugin.messageConfig }) + scope.launch { connectionPlugin.start() } diff --git a/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/connection/WaterdogJoinHandler.kt b/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/connection/WaterdogJoinHandler.kt new file mode 100644 index 0000000..4982b31 --- /dev/null +++ b/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/connection/WaterdogJoinHandler.kt @@ -0,0 +1,27 @@ +package app.simplecloud.plugin.connection.waterdog.connection + +import app.simplecloud.plugin.connection.shared.config.ConnectionConfig +import app.simplecloud.plugin.connection.shared.connection.ConnectionResolver +import dev.waterdog.waterdogpe.network.connection.handler.IJoinHandler +import dev.waterdog.waterdogpe.network.serverinfo.ServerInfo +import dev.waterdog.waterdogpe.player.ProxiedPlayer + +class WaterdogJoinHandler( + private val config: () -> ConnectionConfig +) : IJoinHandler { + + override fun determineServer(player: ProxiedPlayer): ServerInfo? { + val currentConfig = config() + if (!currentConfig.networkJoinTargets.enabled) return null + + val resolver = ConnectionResolver(currentConfig) + val entry = resolver.resolve( + targetConnections = currentConfig.networkJoinTargets.targetConnections, + currentServerName = null + ) ?: return null + + return player.proxy.servers + .filter { entry.serverNameMatcher.matches(it.serverName) } + .randomOrNull() + } +} \ No newline at end of file diff --git a/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/connection/WaterdogReconnectHandler.kt b/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/connection/WaterdogReconnectHandler.kt new file mode 100644 index 0000000..7fe1165 --- /dev/null +++ b/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/connection/WaterdogReconnectHandler.kt @@ -0,0 +1,58 @@ +package app.simplecloud.plugin.connection.waterdog.connection + +import app.simplecloud.plugin.connection.shared.config.ConnectionConfig +import app.simplecloud.plugin.connection.shared.config.MessageConfig +import app.simplecloud.plugin.connection.shared.connection.ConnectionResolver +import dev.waterdog.waterdogpe.network.connection.handler.IReconnectHandler +import dev.waterdog.waterdogpe.network.serverinfo.ServerInfo +import dev.waterdog.waterdogpe.player.ProxiedPlayer +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer + +class WaterdogReconnectHandler( + private val config: () -> ConnectionConfig, + private val messageConfig: () -> MessageConfig +) : IReconnectHandler { + + private val plainSerializer = PlainTextComponentSerializer.plainText() + + override fun getFallbackServer( + player: ProxiedPlayer, + kickedFrom: ServerInfo, + reason: String + ): ServerInfo? { + val currentConfig = config() + val messages = messageConfig() + + if (!currentConfig.fallback.enabled) return null + + val kickedFromServerName = kickedFrom.serverName + val resolver = ConnectionResolver(currentConfig) + + val entry = resolver.resolve( + targetConnections = currentConfig.fallback.targetConnections, + currentServerName = kickedFromServerName + ) + + if (entry == null) { + player.sendMessage( + plainSerializer.serialize(messages.deserialize(messages.kick.noTargetConnection)) + ) + return null + } + + val candidates = player.proxy.servers + .filter { entry.serverNameMatcher.matches(it.serverName) } + .filter { !it.serverName.equals(kickedFromServerName, ignoreCase = true) } + + val targetServer = candidates.randomOrNull() + + if (targetServer == null) { + player.sendMessage( + plainSerializer.serialize(messages.deserialize(messages.kick.noFallbackServers)) + ) + return null + } + + return targetServer + } +} \ No newline at end of file From d012d5e891bc5d86ec46d87a32a2fd4f8981976f Mon Sep 17 00:00:00 2001 From: xXJanisXx Date: Thu, 19 Feb 2026 23:42:58 +0100 Subject: [PATCH 09/19] chore: small improvements --- .../bungeecord/BungeeCordConnectionPlugin.kt | 6 +---- .../command/BungeeCordCommandManager.kt | 2 -- .../bungeecord/listener/ServerKickListener.kt | 4 +-- .../connection/shared/ConnectionPlugin.kt | 4 +++ .../connection/shared/config/CommandConfig.kt | 2 +- .../connection/shared/config/MessageConfig.kt | 8 +++--- .../velocity/VelocityConnectionPlugin.kt | 9 +++---- .../command/VelocityCommandManager.kt | 4 +-- .../listener/KickedFromServerListener.kt | 4 +-- .../waterdog/WaterDogConnectionPlugin.kt | 13 ++------- .../command/WaterDogCommandManager.kt | 4 --- .../connection/WaterdogReconnectHandler.kt | 27 ++++++++++--------- 12 files changed, 34 insertions(+), 53 deletions(-) diff --git a/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/BungeeCordConnectionPlugin.kt b/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/BungeeCordConnectionPlugin.kt index 3728d8d..1c7b6f4 100644 --- a/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/BungeeCordConnectionPlugin.kt +++ b/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/BungeeCordConnectionPlugin.kt @@ -25,12 +25,9 @@ class BungeeCordConnectionPlugin : Plugin() { ) override fun onEnable() { - logger.info("Initialize bungeecord-connection plugin...") - connectionPlugin.config.save("config", connectionPlugin.connectionConfig) - connectionPlugin.config.save("commands", connectionPlugin.commandConfig) - cleanupServers() registerAdditionalServers() + commandManager.registerAll(connectionPlugin.commandConfig) registerListener() @@ -41,7 +38,6 @@ class BungeeCordConnectionPlugin : Plugin() { } override fun onDisable() { - logger.info("Shutting down bungeecord-connection plugin...") commandManager.unregisterAll() scope.launch { connectionPlugin.shutdown() diff --git a/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/command/BungeeCordCommandManager.kt b/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/command/BungeeCordCommandManager.kt index f599d9e..637a1d0 100644 --- a/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/command/BungeeCordCommandManager.kt +++ b/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/command/BungeeCordCommandManager.kt @@ -30,14 +30,12 @@ class BungeeCordCommandManager( commandConfig.commands.forEach { entry -> registerCommand(entry) } - logger.info("Registered ${registeredCommands.size} command(s): ${registeredCommands.joinToString(", ")}") } fun unregisterAll() { registeredCommands.forEach { _ -> proxy.pluginManager.unregisterCommands(plugin) } - logger.info("Unregistered ${registeredCommands.size} command(s)") registeredCommands.clear() } diff --git a/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/listener/ServerKickListener.kt b/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/listener/ServerKickListener.kt index 2974058..b911f7e 100644 --- a/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/listener/ServerKickListener.kt +++ b/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/listener/ServerKickListener.kt @@ -41,11 +41,11 @@ class ServerKickListener( return } - val candidates = proxy.servers.values + val servers = proxy.servers.values .filter { entry.serverNameMatcher.matches(it.name) } .filter { !it.name.equals(kickedFromServerName, ignoreCase = true) } - val targetServer = candidates.randomOrNull() + val targetServer = servers.randomOrNull() if (targetServer == null) { event.isCancelled = true diff --git a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/ConnectionPlugin.kt b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/ConnectionPlugin.kt index 3aa071b..6f31882 100644 --- a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/ConnectionPlugin.kt +++ b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/ConnectionPlugin.kt @@ -28,6 +28,10 @@ class ConnectionPlugin( val messageConfig: MessageConfig get() = config.load("messages") ?: MessageConfig() fun start() { + config.save("config", connectionConfig) + config.save("commands", commandConfig) + config.save("messages", messageConfig) + loadExistingServers() listener.start() } diff --git a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/CommandConfig.kt b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/CommandConfig.kt index 53e70bd..65e15db 100644 --- a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/CommandConfig.kt +++ b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/CommandConfig.kt @@ -21,6 +21,6 @@ data class CommandEntry( @ConfigSerializable data class CommandMessages( - val alreadyConnected: String = "You are already connected to this lobby!", + val alreadyConnected: String = "You are already connected to this server!", val noTargetConnectionFound: String = "Couldn't find a target server!" ) \ No newline at end of file diff --git a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/MessageConfig.kt b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/MessageConfig.kt index 082e5cd..1608e21 100644 --- a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/MessageConfig.kt +++ b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/MessageConfig.kt @@ -13,7 +13,7 @@ data class MessageConfig( val variables: Map = mapOf(), val kick: KickMessages = KickMessages() ) { - private fun buildVariableResolver(): TagResolver { + private fun buildResolver(): TagResolver { val placeholders = variables.map { (key, value) -> Placeholder.parsed(key, value) } @@ -21,13 +21,13 @@ data class MessageConfig( } fun deserialize(raw: String, vararg extra: TagResolver): Component { - val resolver = TagResolver.resolver(buildVariableResolver(), *extra) + val resolver = TagResolver.resolver(buildResolver(), *extra) return MiniMessage.miniMessage().deserialize(raw, resolver) } } @ConfigSerializable data class KickMessages( - val noFallbackServers: String = "There is no fallback server available.", - val noTargetConnection: String = "You have been disconnected from the network
because there are no fallback servers available." + val noFallbackServers: String = "There is no fallback server available.", + val noTargetConnection: String = "You have been disconnected from the network
because there are no fallback servers available." ) \ No newline at end of file diff --git a/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/VelocityConnectionPlugin.kt b/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/VelocityConnectionPlugin.kt index 74abe68..200383a 100644 --- a/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/VelocityConnectionPlugin.kt +++ b/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/VelocityConnectionPlugin.kt @@ -27,8 +27,8 @@ import java.nio.file.Path url = "https://github.com/simplecloudapp/server-connection-plugin" ) class VelocityConnectionPlugin @Inject constructor( + @DataDirectory val dataDirectory: Path, private val server: ProxyServer, - @DataDirectory val dataDirectory: Path ) { private val api = CloudApi.create() @@ -44,12 +44,11 @@ class VelocityConnectionPlugin @Inject constructor( @Subscribe fun onProxyInitialize(event: ProxyInitializeEvent) { - logger.info("Initialize velocity-connection plugin...") - connectionPlugin.config.save("config", connectionPlugin.connectionConfig) - cleanupServers() registerAdditionalServers() + commandManager.registerAll(connectionPlugin.commandConfig) + registerListeners() scope.launch { @@ -59,7 +58,6 @@ class VelocityConnectionPlugin @Inject constructor( @Subscribe fun onProxyShutdown(event: ProxyShutdownEvent) { - logger.info("Shutting down velocity-connection plugin...") commandManager.unregisterAll() scope.launch { connectionPlugin.shutdown() @@ -69,7 +67,6 @@ class VelocityConnectionPlugin @Inject constructor( private fun registerListeners() { val eventManager = server.eventManager - eventManager.register(this, PlayerChooseInitialServerListener(server) { connectionPlugin.connectionConfig }) eventManager.register(this, KickedFromServerListener(server, scope, { connectionPlugin.connectionConfig }, { connectionPlugin.messageConfig })) } diff --git a/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/command/VelocityCommandManager.kt b/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/command/VelocityCommandManager.kt index 005d41d..d55ada4 100644 --- a/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/command/VelocityCommandManager.kt +++ b/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/command/VelocityCommandManager.kt @@ -41,9 +41,7 @@ class VelocityCommandManager( private fun buildBrigadierCommand(entry: CommandEntry): BrigadierCommand { val node = BrigadierCommand.literalArgumentBuilder(entry.name) - .requires { - entry.permission.isEmpty() || it.hasPermission(entry.permission) - } + .requires { entry.permission.isEmpty() || it.hasPermission(entry.permission) } .executes { context -> val player = context.source as? Player ?: return@executes 0 handleCommand(player, entry) diff --git a/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/listener/KickedFromServerListener.kt b/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/listener/KickedFromServerListener.kt index 21fa623..107f103 100644 --- a/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/listener/KickedFromServerListener.kt +++ b/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/listener/KickedFromServerListener.kt @@ -44,11 +44,11 @@ class KickedFromServerListener( return } - val candidates = proxyServer.allServers + val servers = proxyServer.allServers .filter { entry.serverNameMatcher.matches(it.serverInfo.name) } .filter { !it.serverInfo.name.equals(kickedFromServerName, ignoreCase = true) } - val targetServer = candidates.randomOrNull() + val targetServer = servers.randomOrNull() if (targetServer == null) { event.result = KickedFromServerEvent.DisconnectPlayer.create(messages.deserialize(messages.kick.noFallbackServers)) diff --git a/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/WaterDogConnectionPlugin.kt b/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/WaterDogConnectionPlugin.kt index 2e3db53..a5ee8f1 100644 --- a/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/WaterDogConnectionPlugin.kt +++ b/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/WaterDogConnectionPlugin.kt @@ -17,6 +17,7 @@ class WaterDogConnectionPlugin : Plugin() { private val api = CloudApi.create() private val logger = LogManager.getLogger(WaterDogConnectionPlugin::class.java) private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob()) + private val commandManager = WaterDogCommandManager(proxy, scope) val connectionPlugin = ConnectionPlugin( dataFolder.toString(), @@ -24,19 +25,10 @@ class WaterDogConnectionPlugin : Plugin() { WaterDogServerRegistry(this, proxy) ) - private val commandManager = WaterDogCommandManager( - proxy, - scope - ) - override fun onEnable() { - logger.info("Initialize waterdog-connection plugin...") - connectionPlugin.config.save("config", connectionPlugin.connectionConfig) - connectionPlugin.config.save("commands", connectionPlugin.commandConfig) - connectionPlugin.config.save("messages", connectionPlugin.messageConfig) - cleanupServers() registerAdditionalServers() + commandManager.registerAll(connectionPlugin.commandConfig) proxy.joinHandler = WaterdogJoinHandler { connectionPlugin.connectionConfig } @@ -48,7 +40,6 @@ class WaterDogConnectionPlugin : Plugin() { } override fun onDisable() { - logger.info("Shutting down waterdog-connection plugin...") commandManager.unregisterAll() scope.launch { connectionPlugin.shutdown() diff --git a/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/command/WaterDogCommandManager.kt b/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/command/WaterDogCommandManager.kt index ee799b9..5049491 100644 --- a/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/command/WaterDogCommandManager.kt +++ b/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/command/WaterDogCommandManager.kt @@ -11,14 +11,12 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import net.kyori.adventure.text.minimessage.MiniMessage import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer -import org.apache.logging.log4j.LogManager class WaterDogCommandManager( private val proxy: ProxyServer, private val scope: CoroutineScope ) { - private val logger = LogManager.getLogger(WaterDogCommandManager::class.java) private val miniMessage = MiniMessage.miniMessage() private val registeredCommands = mutableListOf() @@ -26,14 +24,12 @@ class WaterDogCommandManager( commandConfig.commands.forEach { entry -> registerCommand(entry) } - logger.info("Registered ${registeredCommands.size} command(s): ${registeredCommands.joinToString(", ")}") } fun unregisterAll() { registeredCommands.forEach { name -> proxy.commandMap.unregisterCommand(name) } - logger.info("Unregistered ${registeredCommands.size} command(s)") registeredCommands.clear() } diff --git a/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/connection/WaterdogReconnectHandler.kt b/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/connection/WaterdogReconnectHandler.kt index 7fe1165..ffd79c9 100644 --- a/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/connection/WaterdogReconnectHandler.kt +++ b/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/connection/WaterdogReconnectHandler.kt @@ -4,6 +4,7 @@ import app.simplecloud.plugin.connection.shared.config.ConnectionConfig import app.simplecloud.plugin.connection.shared.config.MessageConfig import app.simplecloud.plugin.connection.shared.connection.ConnectionResolver import dev.waterdog.waterdogpe.network.connection.handler.IReconnectHandler +import dev.waterdog.waterdogpe.network.connection.handler.ReconnectReason import dev.waterdog.waterdogpe.network.serverinfo.ServerInfo import dev.waterdog.waterdogpe.player.ProxiedPlayer import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer @@ -13,19 +14,23 @@ class WaterdogReconnectHandler( private val messageConfig: () -> MessageConfig ) : IReconnectHandler { - private val plainSerializer = PlainTextComponentSerializer.plainText() + private val serializer = PlainTextComponentSerializer.plainText() override fun getFallbackServer( - player: ProxiedPlayer, - kickedFrom: ServerInfo, - reason: String + player: ProxiedPlayer?, + oldServer: ServerInfo?, + reason: ReconnectReason?, + kickMessage: String? ): ServerInfo? { + player ?: return null + oldServer ?: return null + val currentConfig = config() val messages = messageConfig() if (!currentConfig.fallback.enabled) return null - val kickedFromServerName = kickedFrom.serverName + val kickedFromServerName = oldServer.serverName val resolver = ConnectionResolver(currentConfig) val entry = resolver.resolve( @@ -34,22 +39,18 @@ class WaterdogReconnectHandler( ) if (entry == null) { - player.sendMessage( - plainSerializer.serialize(messages.deserialize(messages.kick.noTargetConnection)) - ) + player.sendMessage(serializer.serialize(messages.deserialize(messages.kick.noTargetConnection))) return null } - val candidates = player.proxy.servers + val servers = player.proxy.servers .filter { entry.serverNameMatcher.matches(it.serverName) } .filter { !it.serverName.equals(kickedFromServerName, ignoreCase = true) } - val targetServer = candidates.randomOrNull() + val targetServer = servers.randomOrNull() if (targetServer == null) { - player.sendMessage( - plainSerializer.serialize(messages.deserialize(messages.kick.noFallbackServers)) - ) + player.sendMessage(serializer.serialize(messages.deserialize(messages.kick.noFallbackServers))) return null } From 5d5dfcad8619a3baaff3f9b7ce62d00ddc2e21e8 Mon Sep 17 00:00:00 2001 From: xXJanisXx Date: Fri, 20 Feb 2026 11:54:49 +0100 Subject: [PATCH 10/19] chore: update readme --- README.md | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index ff85edb..9a6da76 100644 --- a/README.md +++ b/README.md @@ -24,15 +24,19 @@ > All information about this project can be found in our detailed [documentation][docs-thisproject]. -The Server Connection Plugin provides comprehensive player connection management for your network, including network join handling, fallback servers, and navigation commands. +The Server Connection Plugin provides comprehensive player connection management for your network. It automatically registers SimpleCloud v3 servers on your proxy. ## Features - [x] **Velocity** - [x] **BungeeCord** +- [x] **Waterdog PE** - [ ] **Gate** -- [x] **Connection targets**: Connection targets define server groups that players can connect to -- [x] **Matcher Operations**: Match Server names by Operations +- [x] **Server registration**: Automatically registers SimpleCloud servers on your proxy +- [x] **Additional servers**: Add non network servers manually +- [x] **Connection targets**: Define server groups and persistent servers that players can connect to +- [x] **Fallback servers**: Automatically redirect players when a server becomes unavailable +- [x] **Matcher Operations**: Match server names by operations ## Contributing @@ -50,11 +54,11 @@ This repository is licensed under [Apache 2.0][license]. [banner]: https://github.com/simplecloudapp/branding/blob/main/readme/banner/plugin/server-connection.png?raw=true -[issue-bug-report]: https://github.com/theSimpleCloud/server-connection-plugin/issues/new?labels=bug&projects=template=01_BUG-REPORT.yml&title=%5BBUG%5D+%3Ctitle%3E +[issue-bug-report]: https://github.com/simplecloudapp/server-connection-plugin/issues/new?labels=bug&projects=template=01_BUG-REPORT.yml&title=%5BBUG%5D+%3Ctitle%3E -[issue-feature-request]: https://github.com/theSimpleCloud/server-connection-plugin/discussions/new?category=ideas +[issue-feature-request]: https://github.com/simplecloudapp/server-connection-plugin/discussions/new?category=ideas -[docs-thisproject]: https://docs.simplecloud.app/plugin/server-connection +[docs-thisproject]: https://docs.simplecloud.app/en/manual/plugin/server-connection [docs-contribute]: https://docs.simplecloud.app/contribute @@ -84,4 +88,4 @@ This repository is licensed under [Apache 2.0][license]. [badge-bluesky]: https://img.shields.io/badge/Follow_@simplecloud.app-d95652.svg?style=flat-square&logo=bluesky&color=27272a -[badge-youtube]: https://img.shields.io/badge/youtube-d95652.svg?style=flat-square&logo=youtube&color=27272a +[badge-youtube]: https://img.shields.io/badge/youtube-d95652.svg?style=flat-square&logo=youtube&color=27272a \ No newline at end of file From 6e07a5b12bf5adbac81153c2ad3cc569a6cc7da6 Mon Sep 17 00:00:00 2001 From: xXJanisXx Date: Sat, 7 Mar 2026 11:19:04 +0100 Subject: [PATCH 11/19] chore: bump dependencies --- build.gradle.kts | 2 +- gradle/libs.versions.toml | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index fa79115..9b58aff 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -39,7 +39,7 @@ subprojects { testImplementation(rootProject.libs.kotlin.test) implementation(rootProject.libs.kotlin.jvm) implementation(rootProject.libs.kotlin.coroutines.core) - implementation(rootProject.libs.bundles.logging) + implementation(rootProject.libs.log4j.api) } kotlin { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6c9d247..7abf4c9 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,10 +2,10 @@ kotlin = "2.2.20" kotlin-coroutines = "1.10.2" -shadow = "9.3.1" +shadow = "9.3.2" minotaur = "2.8.10" -simplecloud-api = "0.1.0-platform.19-dev.1770725947326-23f3f70" +simplecloud-api = "0.1.0-platform.20-dev.1771710355025-becd984" simplecloud-plugin-api = "0.0.1-platform.1766000824440-e57ed95" velocity = "3.5.0-SNAPSHOT" @@ -41,12 +41,9 @@ commons-io = { module = "commons-io:commons-io", version.ref = "commons-io" } configurate-yaml = { module = "org.spongepowered:configurate-yaml", version.ref = "configurate" } configurate-kotlin = { module = "org.spongepowered:configurate-extra-kotlin", version.ref = "configurate" } -log4j-core = { module = "org.apache.logging.log4j:log4j-core", version.ref = "log4j" } log4j-api = { module = "org.apache.logging.log4j:log4j-api", version.ref = "log4j" } -log4j-slf4j-impl = { module = "org.apache.logging.log4j:log4j-slf4j-impl", version.ref = "log4j" } [bundles] -logging = ["log4j-core", "log4j-api", "log4j-slf4j-impl"] configurate = ["configurate-kotlin", "configurate-yaml"] adventure = ["adventure-api", "adventure-text-minimessage", "adventure-text-serializer-plain"] From 785b39212d11b7ffb30b8b2cecf140ba1db3bf43 Mon Sep 17 00:00:00 2001 From: xXJanisXx Date: Sat, 7 Mar 2026 22:00:47 +0100 Subject: [PATCH 12/19] refactor: connection logic --- connection-bungeecord/build.gradle.kts | 6 +- .../bungeecord/BungeeCordConnectionPlugin.kt | 62 +++++---- .../command/BungeeCordCommandManager.kt | 125 ++++++++++-------- .../listener/ServerConnectListener.kt | 54 +++++--- .../bungeecord/listener/ServerKickListener.kt | 90 +++++++------ .../src/main/resources/bungee.yml | 2 +- .../connection/shared/ConnectionPlugin.kt | 35 +++-- .../connection/shared/config/CommandConfig.kt | 16 +-- .../shared/config/ConnectionConfig.kt | 51 ++++--- .../connection/shared/config/MessageConfig.kt | 28 ++-- .../connection/shared/config/YamlConfig.kt | 1 + .../shared/connection/ConnectionResolver.kt | 86 ++++++------ .../shared/utilities/DefaultCommands.kt | 29 ---- .../shared/utilities/DefaultConfigs.kt | 63 +++++++++ connection-velocity/build.gradle.kts | 2 +- .../velocity/VelocityConnectionPlugin.kt | 54 ++++---- .../command/VelocityCommandManager.kt | 122 ++++++++++------- .../listener/KickedFromServerListener.kt | 76 +++++------ .../PlayerChooseInitialServerListener.kt | 50 +++---- connection-waterdog/build.gradle.kts | 2 +- .../waterdog/WaterDogConnectionPlugin.kt | 65 --------- .../waterdog/WaterdogConnectionPlugin.kt | 74 +++++++++++ .../command/WaterDogCommandManager.kt | 96 -------------- .../command/WaterdogCommandManager.kt | 114 ++++++++++++++++ .../connection/WaterdogJoinHandler.kt | 27 ---- .../connection/WaterdogReconnectHandler.kt | 59 --------- .../waterdog/handler/WaterdogJoinHandler.kt | 42 ++++++ .../handler/WaterdogReconnectHandler.kt | 61 +++++++++ ...rRegistry.kt => WaterdogServerRegistry.kt} | 6 +- .../src/main/resources/plugin.yml | 2 +- 30 files changed, 844 insertions(+), 656 deletions(-) delete mode 100644 connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/utilities/DefaultCommands.kt create mode 100644 connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/utilities/DefaultConfigs.kt delete mode 100644 connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/WaterDogConnectionPlugin.kt create mode 100644 connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/WaterdogConnectionPlugin.kt delete mode 100644 connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/command/WaterDogCommandManager.kt create mode 100644 connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/command/WaterdogCommandManager.kt delete mode 100644 connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/connection/WaterdogJoinHandler.kt delete mode 100644 connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/connection/WaterdogReconnectHandler.kt create mode 100644 connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/handler/WaterdogJoinHandler.kt create mode 100644 connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/handler/WaterdogReconnectHandler.kt rename connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/registration/{WaterDogServerRegistry.kt => WaterdogServerRegistry.kt} (90%) diff --git a/connection-bungeecord/build.gradle.kts b/connection-bungeecord/build.gradle.kts index 52fbb0e..46520d2 100644 --- a/connection-bungeecord/build.gradle.kts +++ b/connection-bungeecord/build.gradle.kts @@ -3,11 +3,11 @@ plugins { } dependencies { - api(project(":connection-shared")) - compileOnly(libs.simplecloud.api) - compileOnly(libs.bungeecord.api) + implementation(project(":connection-shared")) implementation(libs.adventure.platform.bungeecord) implementation(libs.bundles.adventure) + compileOnly(libs.simplecloud.api) + compileOnly(libs.bungeecord.api) } modrinth { diff --git a/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/BungeeCordConnectionPlugin.kt b/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/BungeeCordConnectionPlugin.kt index 1c7b6f4..86ec24b 100644 --- a/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/BungeeCordConnectionPlugin.kt +++ b/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/BungeeCordConnectionPlugin.kt @@ -7,6 +7,7 @@ import app.simplecloud.plugin.connection.bungeecord.listener.ServerKickListener import app.simplecloud.plugin.connection.bungeecord.registration.BungeeCordServerRegistry import app.simplecloud.plugin.connection.shared.ConnectionPlugin import kotlinx.coroutines.* +import net.kyori.adventure.platform.bungeecord.BungeeAudiences import net.md_5.bungee.api.plugin.Plugin import org.apache.logging.log4j.LogManager import java.net.InetSocketAddress @@ -16,7 +17,8 @@ class BungeeCordConnectionPlugin : Plugin() { private val api = CloudApi.create() private val logger = LogManager.getLogger(BungeeCordConnectionPlugin::class.java) private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob()) - private val commandManager = BungeeCordCommandManager(proxy, this, scope) + private val audiences = BungeeAudiences.create(this) + private val commandManager = BungeeCordCommandManager(this, audiences) val connectionPlugin = ConnectionPlugin( dataFolder.toString(), @@ -27,10 +29,8 @@ class BungeeCordConnectionPlugin : Plugin() { override fun onEnable() { cleanupServers() registerAdditionalServers() - - commandManager.registerAll(connectionPlugin.commandConfig) - - registerListener() + registerListeners() + registerCommands() scope.launch { connectionPlugin.start() @@ -38,37 +38,45 @@ class BungeeCordConnectionPlugin : Plugin() { } override fun onDisable() { - commandManager.unregisterAll() - scope.launch { - connectionPlugin.shutdown() - } + commandManager.unregisterCommands() + audiences.close() + scope.launch { connectionPlugin.shutdown() } scope.cancel() } private fun cleanupServers() { - proxy.servers.clear() - proxy.configurationAdapter.servers.clear() - proxy.configurationAdapter.listeners.forEach { - it.serverPriority.clear() + if (connectionPlugin.connectionConfig.registration.enabled) { + proxy.servers.clear() + proxy.configurationAdapter.servers.clear() + proxy.configurationAdapter.listeners.forEach { + it.serverPriority.clear() + } } } private fun registerAdditionalServers() { - val additionalServers = connectionPlugin.connectionConfig.registration.additionalServers - additionalServers.forEach { - val info = proxy.constructServerInfo( - it.name, - InetSocketAddress.createUnresolved(it.address, it.port.toInt()), - it.name, - false - ) - proxy.servers[it.name] = info - logger.info("Additional server ${info.name} has been registered!") + if (connectionPlugin.connectionConfig.registration.enabled) { + val additionalServers = connectionPlugin.connectionConfig.registration.additionalServers + additionalServers.forEach { + val info = proxy.constructServerInfo( + it.name, + InetSocketAddress.createUnresolved(it.address, it.port.toInt()), + it.name, + false + ) + proxy.servers[it.name] = info + logger.info("Additional server ${info.name} has been registered!") + } } } - private fun registerListener() { - proxy.pluginManager.registerListener(this, ServerConnectListener(proxy) { connectionPlugin.connectionConfig }) - proxy.pluginManager.registerListener(this, ServerKickListener(proxy, { connectionPlugin.connectionConfig }, { connectionPlugin.messageConfig })) + private fun registerListeners() { + proxy.pluginManager.registerListener(this, ServerConnectListener(this, audiences)) + proxy.pluginManager.registerListener(this, ServerKickListener(this, audiences)) } -} \ No newline at end of file + + private fun registerCommands() { + commandManager.registerCommands() + } + +} diff --git a/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/command/BungeeCordCommandManager.kt b/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/command/BungeeCordCommandManager.kt index 637a1d0..8b4838f 100644 --- a/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/command/BungeeCordCommandManager.kt +++ b/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/command/BungeeCordCommandManager.kt @@ -1,97 +1,108 @@ package app.simplecloud.plugin.connection.bungeecord.command import app.simplecloud.plugin.connection.bungeecord.BungeeCordConnectionPlugin -import app.simplecloud.plugin.connection.shared.config.CommandConfig import app.simplecloud.plugin.connection.shared.config.CommandEntry -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch -import net.kyori.adventure.text.Component -import net.kyori.adventure.text.minimessage.MiniMessage -import net.md_5.bungee.api.ChatMessageType +import app.simplecloud.plugin.connection.shared.connection.ConnectionResolver +import net.kyori.adventure.platform.bungeecord.BungeeAudiences import net.md_5.bungee.api.CommandSender -import net.md_5.bungee.api.ProxyServer import net.md_5.bungee.api.connection.ProxiedPlayer import net.md_5.bungee.api.plugin.Command -import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer -import org.apache.logging.log4j.LogManager class BungeeCordCommandManager( - private val proxy: ProxyServer, private val plugin: BungeeCordConnectionPlugin, - private val scope: CoroutineScope + private val audiences: BungeeAudiences, ) { - private val logger = LogManager.getLogger(BungeeCordCommandManager::class.java) - private val miniMessage = MiniMessage.miniMessage() - private val serializer = BungeeComponentSerializer.get() - private val registeredCommands = mutableListOf() + private val commands = mutableListOf() - fun registerAll(commandConfig: CommandConfig) { - commandConfig.commands.forEach { entry -> - registerCommand(entry) + fun registerCommands() { + val commands = plugin.connectionPlugin.commandConfig.commands + for (command in commands) { + registerCommand(command) } } - fun unregisterAll() { - registeredCommands.forEach { _ -> - proxy.pluginManager.unregisterCommands(plugin) - } - registeredCommands.clear() + fun unregisterCommands() { + commands.forEach { plugin.proxy.pluginManager.unregisterCommand(findCommand(it)) } + commands.clear() } - private fun registerCommand(entry: CommandEntry) { - val command = buildCommand(entry) - proxy.pluginManager.registerCommand(plugin, command) - registeredCommands.add(entry.name) + private fun findCommand(name: String): Command? { + return plugin.proxy.pluginManager.commands.firstOrNull { + it.value.name.equals(name, ignoreCase = true) + }?.value } - private fun buildCommand(entry: CommandEntry): Command { - val permission = entry.permission.ifEmpty { null } + private fun registerCommand(command: CommandEntry) { + val permission = command.permission.ifEmpty { null } - return object : Command( - entry.name, + val connectionCommand = object : Command( + command.name, permission, - *entry.aliases.toTypedArray() + *command.aliases.toTypedArray() ) { override fun execute(sender: CommandSender, args: Array) { val player = sender as? ProxiedPlayer ?: return - scope.launch { handleCommand(player, entry) } + handleCommand(player, command) } } + + plugin.proxy.pluginManager.registerCommand(plugin, connectionCommand) + commands.add(command.name) } - private fun handleCommand(player: ProxiedPlayer, entry: CommandEntry) { + private fun handleCommand(player: ProxiedPlayer, command: CommandEntry) { + val config = plugin.connectionPlugin.connectionConfig + val messages = plugin.connectionPlugin.messageConfig + val audience = audiences.player(player) + + if (command.permission.isNotEmpty() && !player.hasPermission(command.permission)) { + audience.sendMessage(messages.send(messages.kick.permissionDenied)) + return + } + val currentServerName = player.server?.info?.name - val groupedByPriority = entry.targetConnections - .groupBy { it.priority } - .entries - .sortedByDescending { it.key } - - for ((_, targets) in groupedByPriority) { - for (target in targets.shuffled()) { - if (target.from.isNotEmpty()) { - val matchesFrom = currentServerName != null && - target.from.any { it.equals(currentServerName, ignoreCase = true) } - if (!matchesFrom) continue + val serverNames = plugin.proxy.servers.keys.toList() + val sortedTargets = command.targetConnections.sortedByDescending { it.priority } + + for (target in sortedTargets) { + if (target.from.isNotEmpty() && currentServerName != null) { + val isFromAllowed = target.from.any { connectionName -> + ConnectionResolver.isServerInConnection( + currentServerName, connectionName, config.connections, serverNames + ) } + if (!isFromAllowed) continue + } - val serverInfo = proxy.getServerInfo(target.name) ?: continue + val connection = ConnectionResolver.findConnection(target.name, config.connections) ?: continue - if (currentServerName.equals(serverInfo.name, ignoreCase = true)) { - player.sendMessage(miniMessage.deserialize(entry.messages.alreadyConnected)) - return - } + val failedRule = ConnectionResolver.checkRules(connection) { permission -> + player.hasPermission(permission) + } + if (failedRule != null) { + audience.sendMessage(messages.send(messages.kick.permissionDenied)) + return + } + + val matchingNames = ConnectionResolver.findMatchingServerNames(connection, serverNames) + if (matchingNames.isEmpty()) continue - player.connect(serverInfo) + val targetServer = matchingNames + .mapNotNull { plugin.proxy.servers[it] } + .minByOrNull { it.players.size } + ?: continue + + if (currentServerName != null && targetServer.name.equals(currentServerName, ignoreCase = true)) { + audience.sendMessage(messages.send(command.messages.alreadyConnected)) return } + + player.connect(targetServer) + return } - player.sendMessage(miniMessage.deserialize(entry.messages.noTargetConnectionFound)) + audience.sendMessage(messages.send(command.messages.noTargetConnectionFound)) } - private fun ProxiedPlayer.sendMessage(component: Component) { - val bungeeComponents = serializer.serialize(component) - sendMessage(ChatMessageType.SYSTEM, *bungeeComponents) - } -} \ No newline at end of file +} diff --git a/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/listener/ServerConnectListener.kt b/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/listener/ServerConnectListener.kt index c0b9fb6..5433609 100644 --- a/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/listener/ServerConnectListener.kt +++ b/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/listener/ServerConnectListener.kt @@ -1,35 +1,53 @@ package app.simplecloud.plugin.connection.bungeecord.listener -import app.simplecloud.plugin.connection.shared.config.ConnectionConfig +import app.simplecloud.plugin.connection.bungeecord.BungeeCordConnectionPlugin import app.simplecloud.plugin.connection.shared.connection.ConnectionResolver -import net.md_5.bungee.api.ProxyServer +import net.kyori.adventure.platform.bungeecord.BungeeAudiences import net.md_5.bungee.api.event.ServerConnectEvent import net.md_5.bungee.api.plugin.Listener import net.md_5.bungee.event.EventHandler -import net.md_5.bungee.event.EventPriority class ServerConnectListener( - private val proxy: ProxyServer, - private val config: () -> ConnectionConfig + private val plugin: BungeeCordConnectionPlugin, + private val audiences: BungeeAudiences, ) : Listener { - @EventHandler(priority = EventPriority.NORMAL) + @EventHandler fun onServerConnect(event: ServerConnectEvent) { if (event.reason != ServerConnectEvent.Reason.JOIN_PROXY) return - val currentConfig = config() - if (!currentConfig.networkJoinTargets.enabled) return + val config = plugin.connectionPlugin.connectionConfig + val messages = plugin.connectionPlugin.messageConfig - val resolver = ConnectionResolver(currentConfig) - val entry = resolver.resolve( - targetConnections = currentConfig.networkJoinTargets.targetConnections, - currentServerName = null - ) ?: return + if (!config.networkJoinTargets.enabled) return - val targetServer = proxy.servers.values - .filter { entry.serverNameMatcher.matches(it.name) } - .randomOrNull() ?: return + val serverNames = plugin.proxy.servers.keys.toList() + val sortedTargets = config.networkJoinTargets.targetConnections.sortedByDescending { it.priority } - event.target = targetServer + for (target in sortedTargets) { + val connection = ConnectionResolver.findConnection(target.name, config.connections) ?: continue + + val failedRule = ConnectionResolver.checkRules(connection) { permission -> + event.player.hasPermission(permission) + } + if (failedRule != null) continue + + val matchingNames = ConnectionResolver.findMatchingServerNames(connection, serverNames) + if (matchingNames.isEmpty()) continue + + val targetServer = matchingNames + .mapNotNull { plugin.proxy.servers[it] } + .minByOrNull { it.players.size } + ?: continue + + event.target = targetServer + return + } + + event.isCancelled = true + val audience = audiences.player(event.player) + audience.sendMessage(messages.send(messages.kick.noTargetConnection)) + event.player.disconnect() } -} \ No newline at end of file + +} diff --git a/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/listener/ServerKickListener.kt b/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/listener/ServerKickListener.kt index b911f7e..4efacaa 100644 --- a/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/listener/ServerKickListener.kt +++ b/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/listener/ServerKickListener.kt @@ -1,61 +1,63 @@ package app.simplecloud.plugin.connection.bungeecord.listener -import app.simplecloud.plugin.connection.shared.config.ConnectionConfig -import app.simplecloud.plugin.connection.shared.config.MessageConfig +import app.simplecloud.plugin.connection.bungeecord.BungeeCordConnectionPlugin import app.simplecloud.plugin.connection.shared.connection.ConnectionResolver -import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer -import net.md_5.bungee.api.ProxyServer +import net.kyori.adventure.platform.bungeecord.BungeeAudiences import net.md_5.bungee.api.event.ServerKickEvent import net.md_5.bungee.api.plugin.Listener import net.md_5.bungee.event.EventHandler -import net.md_5.bungee.event.EventPriority class ServerKickListener( - private val proxy: ProxyServer, - private val config: () -> ConnectionConfig, - private val messageConfig: () -> MessageConfig + private val plugin: BungeeCordConnectionPlugin, + private val audiences: BungeeAudiences, ) : Listener { - private val serializer = BungeeComponentSerializer.get() - - @EventHandler(priority = EventPriority.NORMAL) + @EventHandler fun onServerKick(event: ServerKickEvent) { - val currentConfig = config() - val messages = messageConfig() - - if (!currentConfig.fallback.enabled) return - - val kickedFromServerName = event.kickedFrom.name - val resolver = ConnectionResolver(currentConfig) - - val entry = resolver.resolve( - targetConnections = currentConfig.fallback.targetConnections, - currentServerName = kickedFromServerName - ) - - if (entry == null) { - event.isCancelled = true - event.player.disconnect( - *serializer.serialize(messages.deserialize(messages.kick.noTargetConnection)) - ) - return - } - - val servers = proxy.servers.values - .filter { entry.serverNameMatcher.matches(it.name) } - .filter { !it.name.equals(kickedFromServerName, ignoreCase = true) } - - val targetServer = servers.randomOrNull() - - if (targetServer == null) { + val config = plugin.connectionPlugin.connectionConfig + val messageConfig = plugin.connectionPlugin.messageConfig + + if (!config.fallback.enabled) return + + val kickedServerName = event.kickedFrom.name + val serverNames = plugin.proxy.servers.keys.toList() + val sortedTargets = config.fallback.targetConnections.sortedByDescending { it.priority } + + for (target in sortedTargets) { + if (target.from.isNotEmpty()) { + val isFromAllowed = target.from.any { connectionName -> + ConnectionResolver.isServerInConnection( + kickedServerName, connectionName, config.connections, serverNames + ) + } + if (!isFromAllowed) continue + } + + val connection = ConnectionResolver.findConnection(target.name, config.connections) ?: continue + + val failedRule = ConnectionResolver.checkRules(connection) { permission -> + event.player.hasPermission(permission) + } + if (failedRule != null) continue + + val matchingNames = ConnectionResolver.findMatchingServerNames(connection, serverNames) + if (matchingNames.isEmpty()) continue + + val targetServer = matchingNames + .mapNotNull { plugin.proxy.servers[it] } + .filter { it.name != kickedServerName } + .minByOrNull { it.players.size } + ?: continue + + event.cancelServer = targetServer event.isCancelled = true - event.player.disconnect( - *serializer.serialize(messages.deserialize(messages.kick.noFallbackServers)) - ) return } - event.cancelServer = targetServer event.isCancelled = true + val audience = audiences.player(event.player) + audience.sendMessage(messageConfig.send(messageConfig.kick.noFallbackServers)) + event.player.disconnect() } -} \ No newline at end of file + +} diff --git a/connection-bungeecord/src/main/resources/bungee.yml b/connection-bungeecord/src/main/resources/bungee.yml index 184ba4e..534d484 100644 --- a/connection-bungeecord/src/main/resources/bungee.yml +++ b/connection-bungeecord/src/main/resources/bungee.yml @@ -1,4 +1,4 @@ -name: connection-bungeecord +name: simplecloud-connection version: 1.0.0 author: MrManHD diff --git a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/ConnectionPlugin.kt b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/ConnectionPlugin.kt index 6f31882..102bfbf 100644 --- a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/ConnectionPlugin.kt +++ b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/ConnectionPlugin.kt @@ -10,6 +10,7 @@ import app.simplecloud.plugin.connection.shared.config.MessageConfig import app.simplecloud.plugin.connection.shared.config.YamlConfig import app.simplecloud.plugin.connection.shared.listener.ServerEventListener import app.simplecloud.plugin.connection.shared.registration.ServerRegistry +import kotlinx.coroutines.future.await import org.apache.logging.log4j.LogManager class ConnectionPlugin( @@ -24,32 +25,40 @@ class ConnectionPlugin( val config = YamlConfig(dir) val connectionConfig: ConnectionConfig get() = config.load("config") ?: ConnectionConfig() - val commandConfig: CommandConfig get() = config.load("commands") ?: CommandConfig() val messageConfig: MessageConfig get() = config.load("messages") ?: MessageConfig() + val commandConfig: CommandConfig get() = config.load("commands") ?: CommandConfig() - fun start() { + suspend fun start() { + logger.info("SimpleCloud v3 connection plugin initialized!") config.save("config", connectionConfig) - config.save("commands", commandConfig) config.save("messages", messageConfig) - - loadExistingServers() - listener.start() + config.save("commands", commandConfig) + startRegistration() } fun shutdown() { + logger.info("SimpleCloud v3 connection plugin uninitialized!") config.close() - listener.stop() + if (connectionConfig.registration.enabled) { + listener.stop() + } } - private fun loadExistingServers() { - api.server().getAllServers( + private suspend fun startRegistration() { + if (connectionConfig.registration.enabled) { + loadExistingServers() + listener.start() + } + } + + private suspend fun loadExistingServers() { + val servers = api.server().getAllServers( ServerQuery.create() .filterByState(ServerState.AVAILABLE) .filterByServerGroupType(GroupServerType.SERVER) - ).thenAccept { servers -> - logger.info("Found ${servers.size} servers") - servers.forEach { listener.register(it) } - } + ).await() + logger.info("Found ${servers.size} servers") + servers.forEach { listener.register(it) } } } \ No newline at end of file diff --git a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/CommandConfig.kt b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/CommandConfig.kt index 65e15db..c721cef 100644 --- a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/CommandConfig.kt +++ b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/CommandConfig.kt @@ -1,26 +1,26 @@ package app.simplecloud.plugin.connection.shared.config import app.simplecloud.plugin.connection.shared.utilities.ConfigVersion -import app.simplecloud.plugin.connection.shared.utilities.DefaultCommands +import app.simplecloud.plugin.connection.shared.utilities.DefaultConfigs import org.spongepowered.configurate.objectmapping.ConfigSerializable @ConfigSerializable data class CommandConfig( val version: Char = ConfigVersion.VERSION, - val commands: List = DefaultCommands.DEFAULT + val commands: List = DefaultConfigs.COMMANDS, ) @ConfigSerializable data class CommandEntry( - val name: String, - val aliases: List = emptyList(), - val targetConnections: List = emptyList(), + val name: String = "", + val aliases: List = listOf(), + val targetConnections: List = listOf(), val messages: CommandMessages = CommandMessages(), - val permission: String = "" + val permission: String = "", ) @ConfigSerializable data class CommandMessages( val alreadyConnected: String = "You are already connected to this server!", - val noTargetConnectionFound: String = "Couldn't find a target server!" -) \ No newline at end of file + val noTargetConnectionFound: String = "Couldn't find a target server!", +) diff --git a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/ConnectionConfig.kt b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/ConnectionConfig.kt index ae6bf07..6092424 100644 --- a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/ConnectionConfig.kt +++ b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/ConnectionConfig.kt @@ -1,16 +1,18 @@ package app.simplecloud.plugin.connection.shared.config +import app.simplecloud.plugin.api.shared.matcher.OperationType import app.simplecloud.plugin.api.shared.matcher.ServerMatcherConfiguration import app.simplecloud.plugin.connection.shared.utilities.ConfigVersion +import app.simplecloud.plugin.connection.shared.utilities.DefaultConfigs import org.spongepowered.configurate.objectmapping.ConfigSerializable @ConfigSerializable data class ConnectionConfig( val version: Char = ConfigVersion.VERSION, val registration: RegistrationConfig = RegistrationConfig(), - val connections: List = listOf(ConnectionEntry()), - val networkJoinTargets: NetworkJoinTargets = NetworkJoinTargets(), - val fallback: FallbackConfig = FallbackConfig(), + val connections: List = DefaultConfigs.CONNECTIONS, + val networkJoinTargets: NetworkJoinTargetsConfig = DefaultConfigs.NETWORK_JOIN_TARGETS, + val fallback: FallbackConfig = DefaultConfigs.FALLBACK, ) @ConfigSerializable @@ -18,7 +20,7 @@ data class RegistrationConfig( val enabled: Boolean = true, val serverNamePattern: String = "-", val persistentServerNamePattern: String = "", - val ignoreServerGroups: List = listOf(), + val ignoreServerGroupsAndPersistentServers: List = listOf(), val additionalServers: List = listOf() ) @@ -31,30 +33,47 @@ data class RegistrationServer( @ConfigSerializable data class ConnectionEntry( - val name: String = "lobby", + val name: String = "", val serverNameMatcher: ServerMatcherConfiguration = ServerMatcherConfiguration(), - val rules: List = listOf() + val rules: List = listOf(), ) @ConfigSerializable -data class NetworkJoinTargets( +data class ConnectionRule( + val type: RuleType = RuleType.PERMISSION, + val name: String = "", + val value: String = "", + val operation: OperationType = OperationType.EQUALS, + val negate: Boolean = false, + val bypassPermission: String = "", +) + +enum class RuleType { + PERMISSION, + ENV +} + +@ConfigSerializable +data class NetworkJoinTargetsConfig( val enabled: Boolean = true, - val targetConnections: List = listOf( - TargetConnection(name = "lobby", priority = 0) - ) + val targetConnections: List = listOf(), +) + +@ConfigSerializable +data class TargetConnection( + val name: String = "", + val priority: Int = 0, ) @ConfigSerializable data class FallbackConfig( val enabled: Boolean = true, - val targetConnections: List = listOf( - TargetConnection(name = "lobby", priority = 0) - ) + val targetConnections: List = listOf(), ) @ConfigSerializable -data class TargetConnection( +data class FallbackTargetConnection( val name: String = "", val priority: Int = 0, - val from: List = listOf() -) \ No newline at end of file + val from: List = listOf(), +) diff --git a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/MessageConfig.kt b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/MessageConfig.kt index 1608e21..bee74ad 100644 --- a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/MessageConfig.kt +++ b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/MessageConfig.kt @@ -1,33 +1,35 @@ package app.simplecloud.plugin.connection.shared.config import app.simplecloud.plugin.connection.shared.utilities.ConfigVersion +import app.simplecloud.plugin.connection.shared.utilities.DefaultConfigs import net.kyori.adventure.text.Component import net.kyori.adventure.text.minimessage.MiniMessage -import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder +import net.kyori.adventure.text.minimessage.tag.Tag import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver import org.spongepowered.configurate.objectmapping.ConfigSerializable @ConfigSerializable data class MessageConfig( val version: Char = ConfigVersion.VERSION, - val variables: Map = mapOf(), + val variables: Map = DefaultConfigs.VARIABLES, val kick: KickMessages = KickMessages() ) { - private fun buildResolver(): TagResolver { - val placeholders = variables.map { (key, value) -> - Placeholder.parsed(key, value) + private val miniMessage = MiniMessage.miniMessage() + + private fun tagResolver(): TagResolver { + val resolvers = variables.map { (key, value) -> + TagResolver.resolver(key, Tag.selfClosingInserting(miniMessage.deserialize(value))) } - return TagResolver.resolver(placeholders) + return TagResolver.resolver(*resolvers.toTypedArray()) } - fun deserialize(raw: String, vararg extra: TagResolver): Component { - val resolver = TagResolver.resolver(buildResolver(), *extra) - return MiniMessage.miniMessage().deserialize(raw, resolver) - } + fun send(message: String, vararg tagResolver: TagResolver): Component = + miniMessage.deserialize(message, TagResolver.resolver(tagResolver(), *tagResolver)) } @ConfigSerializable data class KickMessages( - val noFallbackServers: String = "There is no fallback server available.", - val noTargetConnection: String = "You have been disconnected from the network
because there are no fallback servers available." -) \ No newline at end of file + val noFallbackServers: String = " There is no fallback server available.", + val noTargetConnection: String = " You have been disconnected from the network
because there are no fallback servers available.", + val permissionDenied: String = " You don't have permission to join this server.", +) diff --git a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/YamlConfig.kt b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/YamlConfig.kt index e19aba6..761813c 100644 --- a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/YamlConfig.kt +++ b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/YamlConfig.kt @@ -20,6 +20,7 @@ open class YamlConfig(private val dirPath: String) { protected val logger: Logger = LogManager.getLogger(YamlConfig::class.java) } + private val watchService = FileSystems.getDefault().newWatchService() private val reloadListeners = ConcurrentHashMap Unit>>() private val lastReload = ConcurrentHashMap() diff --git a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/connection/ConnectionResolver.kt b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/connection/ConnectionResolver.kt index 94f24aa..4d5a643 100644 --- a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/connection/ConnectionResolver.kt +++ b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/connection/ConnectionResolver.kt @@ -1,48 +1,58 @@ package app.simplecloud.plugin.connection.shared.connection -import app.simplecloud.plugin.connection.shared.config.ConnectionConfig import app.simplecloud.plugin.connection.shared.config.ConnectionEntry -import app.simplecloud.plugin.connection.shared.config.TargetConnection - -/** - * Resolves a list of [TargetConnection]s to the best matching [ConnectionEntry] - */ -class ConnectionResolver( - private val config: ConnectionConfig -) { - - /** - * Resolves the best [ConnectionEntry] for the given [targetConnections]. - * - * @param targetConnections the list of target connections to try - * @param currentServerName the name of the server the player is currently on - * @return the resolved [ConnectionEntry] - */ - fun resolve( - targetConnections: List, - currentServerName: String? - ): ConnectionEntry? { - val groupedByPriority = targetConnections - .groupBy { it.priority } - .entries - .sortedByDescending { it.key } - - for ((_, targets) in groupedByPriority) { - for (target in targets.shuffled()) { - if (target.from.isNotEmpty()) { - val matchesFrom = currentServerName != null && - target.from.any { it.equals(currentServerName, ignoreCase = true) } - if (!matchesFrom) continue - } +import app.simplecloud.plugin.connection.shared.config.ConnectionRule +import app.simplecloud.plugin.connection.shared.config.RuleType + +object ConnectionResolver { - val entry = config.connections.find { it.name == target.name } - ?: continue + fun findConnection(name: String, connections: List): ConnectionEntry? { + return connections.find { it.name.equals(name, ignoreCase = true) } + } - return entry + fun findMatchingServerNames( + connection: ConnectionEntry, + servers: List, + ): List { + return servers.filter { connection.serverNameMatcher.matches(it) } + } + + fun checkRules( + connection: ConnectionEntry, + permissionChecker: (String) -> Boolean, + ): ConnectionRule? { + for (rule in connection.rules) { + if (rule.bypassPermission.isNotEmpty() && permissionChecker(rule.bypassPermission)) { + continue + } + + val failed = when (rule.type) { + RuleType.PERMISSION -> { + val hasPermission = permissionChecker(rule.name) + hasPermission != rule.value.toBoolean() + } + + RuleType.ENV -> { + val envValue = System.getenv(rule.name) ?: "" + val matches = rule.operation.matches(envValue, rule.value, rule.negate) + !matches + } } - } + if (failed) return rule + } return null } -} \ No newline at end of file + fun isServerInConnection( + serverName: String, + connectionName: String, + connections: List, + servers: List, + ): Boolean { + val connection = findConnection(connectionName, connections) ?: return false + val matchingNames = findMatchingServerNames(connection, servers) + return matchingNames.any { it.equals(serverName, ignoreCase = true) } + } + +} diff --git a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/utilities/DefaultCommands.kt b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/utilities/DefaultCommands.kt deleted file mode 100644 index fae357d..0000000 --- a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/utilities/DefaultCommands.kt +++ /dev/null @@ -1,29 +0,0 @@ -package app.simplecloud.plugin.connection.shared.utilities - -import app.simplecloud.plugin.connection.shared.config.CommandEntry -import app.simplecloud.plugin.connection.shared.config.CommandMessages -import app.simplecloud.plugin.connection.shared.config.TargetConnection - -object DefaultCommands { - - val DEFAULT: List = listOf( - lobbyCommand() - ) - - private fun lobbyCommand() = CommandEntry( - name = "lobby", - aliases = listOf("l", "hub", "quit", "leave"), - targetConnections = listOf( - TargetConnection( - name = "lobby", - priority = 0, - from = emptyList() - ) - ), - messages = CommandMessages( - alreadyConnected = "You are already connected to this lobby!", - noTargetConnectionFound = "Couldn't find a target server!" - ), - permission = "" - ) -} \ No newline at end of file diff --git a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/utilities/DefaultConfigs.kt b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/utilities/DefaultConfigs.kt new file mode 100644 index 0000000..c60c68a --- /dev/null +++ b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/utilities/DefaultConfigs.kt @@ -0,0 +1,63 @@ +package app.simplecloud.plugin.connection.shared.utilities + +import app.simplecloud.plugin.api.shared.matcher.OperationType +import app.simplecloud.plugin.api.shared.matcher.ServerMatcherConfiguration +import app.simplecloud.plugin.connection.shared.config.* + +object DefaultConfigs { + + val VARIABLES: Map = mapOf("prefix" to "") + + val CONNECTIONS: List = listOf( + ConnectionEntry( + name = "lobby", + serverNameMatcher = ServerMatcherConfiguration( + operation = OperationType.STARTS_WITH, + value = "lobby", + negate = false, + ), + rules = listOf(), + ), + ) + + val NETWORK_JOIN_TARGETS: NetworkJoinTargetsConfig = NetworkJoinTargetsConfig( + enabled = true, + targetConnections = listOf( + TargetConnection( + name = "lobby", + priority = 0, + ), + ), + ) + + val FALLBACK: FallbackConfig = FallbackConfig( + enabled = true, + targetConnections = listOf( + FallbackTargetConnection( + name = "lobby", + priority = 0, + from = listOf(), + ), + ), + ) + + val COMMANDS: List = listOf( + CommandEntry( + name = "lobby", + aliases = listOf("l", "hub", "quit", "leave"), + targetConnections = listOf( + FallbackTargetConnection( + name = "lobby", + priority = 0, + from = listOf(), + ), + ), + messages = CommandMessages( + alreadyConnected = "You are already connected to this lobby!", + noTargetConnectionFound = "Couldn't find a target server!", + ), + permission = "", + ), + ) + +} \ No newline at end of file diff --git a/connection-velocity/build.gradle.kts b/connection-velocity/build.gradle.kts index 585b85d..fede50a 100644 --- a/connection-velocity/build.gradle.kts +++ b/connection-velocity/build.gradle.kts @@ -4,7 +4,7 @@ plugins { } dependencies { - api(project(":connection-shared")) + implementation(project(":connection-shared")) compileOnly(libs.simplecloud.api) compileOnly(libs.velocity.api) kapt(libs.velocity.api) diff --git a/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/VelocityConnectionPlugin.kt b/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/VelocityConnectionPlugin.kt index 200383a..e855e29 100644 --- a/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/VelocityConnectionPlugin.kt +++ b/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/VelocityConnectionPlugin.kt @@ -20,8 +20,8 @@ import java.net.InetSocketAddress import java.nio.file.Path @Plugin( - id = "connection-velocity", - name = "connection-velocity", + id = "simplecloud-connection", + name = "simplecloud-connection", version = "1.0.0", authors = ["Fllip", "hmtill"], url = "https://github.com/simplecloudapp/server-connection-plugin" @@ -34,22 +34,21 @@ class VelocityConnectionPlugin @Inject constructor( private val api = CloudApi.create() private val logger = LogManager.getLogger(VelocityConnectionPlugin::class.java) private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob()) - private val commandManager = VelocityCommandManager(server, this) + private val registry = VelocityServerRegistry(this, server) + private val commandManager = VelocityCommandManager(this, server) val connectionPlugin = ConnectionPlugin( dataDirectory.toString(), api, - VelocityServerRegistry(this, server) + registry ) @Subscribe fun onProxyInitialize(event: ProxyInitializeEvent) { cleanupServers() registerAdditionalServers() - - commandManager.registerAll(connectionPlugin.commandConfig) - registerListeners() + registerCommands() scope.launch { connectionPlugin.start() @@ -58,31 +57,36 @@ class VelocityConnectionPlugin @Inject constructor( @Subscribe fun onProxyShutdown(event: ProxyShutdownEvent) { - commandManager.unregisterAll() - scope.launch { - connectionPlugin.shutdown() - } + commandManager.unregisterCommands() + scope.launch { connectionPlugin.shutdown() } scope.cancel() } - private fun registerListeners() { - val eventManager = server.eventManager - eventManager.register(this, PlayerChooseInitialServerListener(server) { connectionPlugin.connectionConfig }) - eventManager.register(this, KickedFromServerListener(server, scope, { connectionPlugin.connectionConfig }, { connectionPlugin.messageConfig })) - } - private fun cleanupServers() { - server.allServers.forEach { - server.unregisterServer(it.serverInfo) + if (connectionPlugin.connectionConfig.registration.enabled) { + server.allServers.forEach { + server.unregisterServer(it.serverInfo) + } } } private fun registerAdditionalServers() { - val additionalServers = connectionPlugin.connectionConfig.registration.additionalServers - additionalServers.forEach { - val info = ServerInfo(it.name, InetSocketAddress.createUnresolved(it.address, it.port.toInt())) - server.registerServer(info) - logger.info("Additional server ${info.name} has been registered!") + if (connectionPlugin.connectionConfig.registration.enabled) { + val additionalServers = connectionPlugin.connectionConfig.registration.additionalServers + additionalServers.forEach { + val info = ServerInfo(it.name, InetSocketAddress.createUnresolved(it.address, it.port.toInt())) + server.registerServer(info) + logger.info("Additional server ${info.name} has been registered!") + } } } -} \ No newline at end of file + + private fun registerListeners() { + server.eventManager.register(this, PlayerChooseInitialServerListener(this, server)) + server.eventManager.register(this, KickedFromServerListener(this, server)) + } + + private fun registerCommands() { + commandManager.registerCommands() + } +} diff --git a/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/command/VelocityCommandManager.kt b/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/command/VelocityCommandManager.kt index d55ada4..1f62b98 100644 --- a/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/command/VelocityCommandManager.kt +++ b/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/command/VelocityCommandManager.kt @@ -1,84 +1,108 @@ package app.simplecloud.plugin.connection.velocity.command -import app.simplecloud.plugin.connection.shared.config.CommandConfig import app.simplecloud.plugin.connection.shared.config.CommandEntry +import app.simplecloud.plugin.connection.shared.connection.ConnectionResolver import app.simplecloud.plugin.connection.velocity.VelocityConnectionPlugin -import com.velocitypowered.api.command.BrigadierCommand +import com.velocitypowered.api.command.SimpleCommand import com.velocitypowered.api.proxy.Player import com.velocitypowered.api.proxy.ProxyServer -import net.kyori.adventure.text.minimessage.MiniMessage class VelocityCommandManager( - private val server: ProxyServer, private val plugin: VelocityConnectionPlugin, + private val proxy: ProxyServer, ) { + private val commands = mutableListOf() - private val miniMessage = MiniMessage.miniMessage() - private val registeredCommands = mutableListOf() - - fun registerAll(commandConfig: CommandConfig) { - commandConfig.commands.forEach { entry -> - registerCommand(entry) + fun registerCommands() { + val commands = plugin.connectionPlugin.commandConfig.commands + for (command in commands) { + registerCommand(command) } } - fun unregisterAll() { - val commandManager = server.commandManager - registeredCommands.forEach { name -> commandManager.unregister(name) } - registeredCommands.clear() + fun unregisterCommands() { + commands.forEach { proxy.commandManager.unregister(it) } + commands.clear() } - private fun registerCommand(entry: CommandEntry) { - val commandManager = server.commandManager - val meta = commandManager.metaBuilder(entry.name) - .aliases(*entry.aliases.toTypedArray()) + private fun registerCommand(command: CommandEntry) { + val connectionCommand = ConnectionCommand(plugin, proxy, command) + val meta = proxy.commandManager.metaBuilder(command.name) + .aliases(*command.aliases.toTypedArray()) .plugin(plugin) .build() - commandManager.register(meta, buildBrigadierCommand(entry)) - registeredCommands.add(entry.name) + proxy.commandManager.register(meta, connectionCommand) + commands.add(command.name) } - private fun buildBrigadierCommand(entry: CommandEntry): BrigadierCommand { - val node = BrigadierCommand.literalArgumentBuilder(entry.name) - .requires { entry.permission.isEmpty() || it.hasPermission(entry.permission) } - .executes { context -> - val player = context.source as? Player ?: return@executes 0 - handleCommand(player, entry) - 1 + private class ConnectionCommand( + private val plugin: VelocityConnectionPlugin, + private val proxy: ProxyServer, + private val command: CommandEntry, + ) : SimpleCommand { + + override fun execute(invocation: SimpleCommand.Invocation) { + val source = invocation.source() + if (source !is Player) return + + val config = plugin.connectionPlugin.connectionConfig + val messages = plugin.connectionPlugin.messageConfig + + if (command.permission.isNotEmpty() && !source.hasPermission(command.permission)) { + source.sendMessage(messages.send(messages.kick.permissionDenied)) + return } - .build() - return BrigadierCommand(node) - } + val currentServerName = source.currentServer.orElse(null)?.serverInfo?.name + val serverNames = proxy.allServers.map { it.serverInfo.name } + val sortedTargets = command.targetConnections.sortedByDescending { it.priority } - private fun handleCommand(player: Player, entry: CommandEntry) { - val currentServerName = player.currentServer.orElse(null)?.serverInfo?.name - val groupedByPriority = entry.targetConnections - .groupBy { it.priority } - .entries - .sortedByDescending { it.key } - - for ((_, targets) in groupedByPriority) { - for (target in targets.shuffled()) { - if (target.from.isNotEmpty()) { - val matchesFrom = currentServerName != null && - target.from.any { it.equals(currentServerName, ignoreCase = true) } - if (!matchesFrom) continue + for (target in sortedTargets) { + if (target.from.isNotEmpty() && currentServerName != null) { + val isFromAllowed = target.from.any { connectionName -> + ConnectionResolver.isServerInConnection( + currentServerName, connectionName, config.connections, serverNames + ) + } + if (!isFromAllowed) continue } - val registeredServer = server.getServer(target.name).orElse(null) ?: continue + val connection = ConnectionResolver.findConnection(target.name, config.connections) ?: continue - if (currentServerName.equals(registeredServer.serverInfo.name, ignoreCase = true)) { - player.sendMessage(miniMessage.deserialize(entry.messages.alreadyConnected)) + val failedRule = ConnectionResolver.checkRules(connection) { permission -> + source.hasPermission(permission) + } + if (failedRule != null) { + source.sendMessage(messages.send(messages.kick.permissionDenied)) return } - player.createConnectionRequest(registeredServer).fireAndForget() + val matchingNames = ConnectionResolver.findMatchingServerNames(connection, serverNames) + if (matchingNames.isEmpty()) continue + + val server = matchingNames + .mapNotNull { proxy.getServer(it).orElse(null) } + .minByOrNull { it.playersConnected.size } + ?: continue + + if (currentServerName != null && server.serverInfo.name == currentServerName) { + source.sendMessage(messages.send(command.messages.alreadyConnected)) + return + } + + source.createConnectionRequest(server).fireAndForget() return } + + source.sendMessage(messages.send(command.messages.noTargetConnectionFound)) + } + + override fun hasPermission(invocation: SimpleCommand.Invocation): Boolean { + if (command.permission.isEmpty()) return true + return invocation.source().hasPermission(command.permission) } - player.sendMessage(miniMessage.deserialize(entry.messages.noTargetConnectionFound)) } -} \ No newline at end of file + +} diff --git a/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/listener/KickedFromServerListener.kt b/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/listener/KickedFromServerListener.kt index 107f103..5739885 100644 --- a/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/listener/KickedFromServerListener.kt +++ b/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/listener/KickedFromServerListener.kt @@ -1,60 +1,60 @@ package app.simplecloud.plugin.connection.velocity.listener -import app.simplecloud.plugin.connection.shared.config.ConnectionConfig -import app.simplecloud.plugin.connection.shared.config.MessageConfig import app.simplecloud.plugin.connection.shared.connection.ConnectionResolver -import com.velocitypowered.api.event.EventTask -import com.velocitypowered.api.event.PostOrder +import app.simplecloud.plugin.connection.velocity.VelocityConnectionPlugin import com.velocitypowered.api.event.Subscribe import com.velocitypowered.api.event.player.KickedFromServerEvent import com.velocitypowered.api.proxy.ProxyServer -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.future.future class KickedFromServerListener( - private val proxyServer: ProxyServer, - private val scope: CoroutineScope, - private val config: () -> ConnectionConfig, - private val messageConfig: () -> MessageConfig + private val plugin: VelocityConnectionPlugin, + private val proxy: ProxyServer, ) { - @Subscribe(order = PostOrder.NORMAL) - fun onKickedFromServer(event: KickedFromServerEvent): EventTask { - return EventTask.resumeWhenComplete( - scope.future { handleFallback(event) } - ) - } + @Subscribe + fun onKickedFromServer(event: KickedFromServerEvent) { + val config = plugin.connectionPlugin.connectionConfig + val messages = plugin.connectionPlugin.messageConfig - private fun handleFallback(event: KickedFromServerEvent) { - val currentConfig = config() - val messages = messageConfig() + if (!config.fallback.enabled) return - if (!currentConfig.fallback.enabled) return + val kickedServerName = event.server.serverInfo.name + val serverNames = proxy.allServers.map { it.serverInfo.name } + val sortedTargets = config.fallback.targetConnections.sortedByDescending { it.priority } - val kickedFromServerName = event.server.serverInfo.name - val resolver = ConnectionResolver(currentConfig) + for (target in sortedTargets) { + if (target.from.isNotEmpty()) { + val isFromAllowed = target.from.any { connectionName -> + ConnectionResolver.isServerInConnection( + kickedServerName, connectionName, config.connections, serverNames + ) + } + if (!isFromAllowed) continue + } - val entry = resolver.resolve( - targetConnections = currentConfig.fallback.targetConnections, - currentServerName = kickedFromServerName - ) + val connection = ConnectionResolver.findConnection(target.name, config.connections) ?: continue - if (entry == null) { - event.result = KickedFromServerEvent.DisconnectPlayer.create(messages.deserialize(messages.kick.noTargetConnection)) - return - } + val failedRule = ConnectionResolver.checkRules(connection) { permission -> + event.player.hasPermission(permission) + } + if (failedRule != null) continue - val servers = proxyServer.allServers - .filter { entry.serverNameMatcher.matches(it.serverInfo.name) } - .filter { !it.serverInfo.name.equals(kickedFromServerName, ignoreCase = true) } + val matchingNames = ConnectionResolver.findMatchingServerNames(connection, serverNames) + if (matchingNames.isEmpty()) continue - val targetServer = servers.randomOrNull() + val server = matchingNames + .mapNotNull { proxy.getServer(it).orElse(null) } + .filter { it.serverInfo.name != kickedServerName } + .minByOrNull { it.playersConnected.size } + ?: continue - if (targetServer == null) { - event.result = KickedFromServerEvent.DisconnectPlayer.create(messages.deserialize(messages.kick.noFallbackServers)) + event.result = KickedFromServerEvent.RedirectPlayer.create(server) return } - event.result = KickedFromServerEvent.RedirectPlayer.create(targetServer, event.serverKickReason.orElse(null)) + event.result = KickedFromServerEvent.DisconnectPlayer.create( + messages.send(messages.kick.noFallbackServers) + ) } -} \ No newline at end of file + +} diff --git a/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/listener/PlayerChooseInitialServerListener.kt b/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/listener/PlayerChooseInitialServerListener.kt index af7d220..4cbdac1 100644 --- a/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/listener/PlayerChooseInitialServerListener.kt +++ b/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/listener/PlayerChooseInitialServerListener.kt @@ -1,46 +1,48 @@ package app.simplecloud.plugin.connection.velocity.listener -import app.simplecloud.plugin.connection.shared.config.ConnectionConfig import app.simplecloud.plugin.connection.shared.connection.ConnectionResolver -import com.velocitypowered.api.event.PostOrder +import app.simplecloud.plugin.connection.velocity.VelocityConnectionPlugin import com.velocitypowered.api.event.Subscribe import com.velocitypowered.api.event.player.PlayerChooseInitialServerEvent import com.velocitypowered.api.proxy.ProxyServer class PlayerChooseInitialServerListener( - private val proxyServer: ProxyServer, - private val config: () -> ConnectionConfig + private val plugin: VelocityConnectionPlugin, + private val proxy: ProxyServer, ) { - @Subscribe(order = PostOrder.NORMAL) + @Subscribe fun onPlayerChooseInitialServer(event: PlayerChooseInitialServerEvent) { - handleJoin(event) - } + val config = plugin.connectionPlugin.connectionConfig + val messages = plugin.connectionPlugin.messageConfig - private fun handleJoin(event: PlayerChooseInitialServerEvent) { - val currentConfig = config() + if (!config.networkJoinTargets.enabled) return - if (!currentConfig.networkJoinTargets.enabled) return + val serverNames = proxy.allServers.map { it.serverInfo.name } + val sortedTargets = config.networkJoinTargets.targetConnections.sortedByDescending { it.priority } - val resolver = ConnectionResolver(currentConfig) + for (target in sortedTargets) { + val connection = ConnectionResolver.findConnection(target.name, config.connections) ?: continue - val entry = resolver.resolve( - targetConnections = currentConfig.networkJoinTargets.targetConnections, - currentServerName = null - ) + val failedRule = ConnectionResolver.checkRules(connection) { permission -> + event.player.hasPermission(permission) + } + if (failedRule != null) continue - if (entry == null) { - return - } + val matchingNames = ConnectionResolver.findMatchingServerNames(connection, serverNames) + if (matchingNames.isEmpty()) continue - val targetServer = proxyServer.allServers - .filter { entry.serverNameMatcher.matches(it.serverInfo.name) } - .randomOrNull() + val server = matchingNames + .mapNotNull { proxy.getServer(it).orElse(null) } + .minByOrNull { it.playersConnected.size } + ?: continue - if (targetServer == null) { + event.setInitialServer(server) return } - event.setInitialServer(targetServer) + event.setInitialServer(null) + event.player.disconnect(messages.send(messages.kick.noTargetConnection)) } -} \ No newline at end of file + +} diff --git a/connection-waterdog/build.gradle.kts b/connection-waterdog/build.gradle.kts index 6b2fc74..e670452 100644 --- a/connection-waterdog/build.gradle.kts +++ b/connection-waterdog/build.gradle.kts @@ -1,5 +1,5 @@ dependencies { - api(project(":connection-shared")) + implementation(project(":connection-shared")) implementation(libs.bundles.adventure) compileOnly(libs.simplecloud.api) compileOnly(libs.waterdog.api) diff --git a/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/WaterDogConnectionPlugin.kt b/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/WaterDogConnectionPlugin.kt deleted file mode 100644 index a5ee8f1..0000000 --- a/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/WaterDogConnectionPlugin.kt +++ /dev/null @@ -1,65 +0,0 @@ -package app.simplecloud.plugin.connection.waterdog - -import app.simplecloud.api.CloudApi -import app.simplecloud.plugin.connection.shared.ConnectionPlugin -import app.simplecloud.plugin.connection.waterdog.command.WaterDogCommandManager -import app.simplecloud.plugin.connection.waterdog.connection.WaterdogJoinHandler -import app.simplecloud.plugin.connection.waterdog.connection.WaterdogReconnectHandler -import app.simplecloud.plugin.connection.waterdog.registration.WaterDogServerRegistry -import dev.waterdog.waterdogpe.network.serverinfo.BedrockServerInfo -import dev.waterdog.waterdogpe.plugin.Plugin -import kotlinx.coroutines.* -import org.apache.logging.log4j.LogManager -import java.net.InetSocketAddress - -class WaterDogConnectionPlugin : Plugin() { - - private val api = CloudApi.create() - private val logger = LogManager.getLogger(WaterDogConnectionPlugin::class.java) - private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob()) - private val commandManager = WaterDogCommandManager(proxy, scope) - - val connectionPlugin = ConnectionPlugin( - dataFolder.toString(), - api, - WaterDogServerRegistry(this, proxy) - ) - - override fun onEnable() { - cleanupServers() - registerAdditionalServers() - - commandManager.registerAll(connectionPlugin.commandConfig) - - proxy.joinHandler = WaterdogJoinHandler { connectionPlugin.connectionConfig } - proxy.reconnectHandler = WaterdogReconnectHandler({ connectionPlugin.connectionConfig }, { connectionPlugin.messageConfig }) - - scope.launch { - connectionPlugin.start() - } - } - - override fun onDisable() { - commandManager.unregisterAll() - scope.launch { - connectionPlugin.shutdown() - } - scope.cancel() - } - - private fun cleanupServers() { - proxy.servers.forEach { - proxy.removeServerInfo(it.serverName) - } - } - - private fun registerAdditionalServers() { - val additionalServers = connectionPlugin.connectionConfig.registration.additionalServers - additionalServers.forEach { - val address = InetSocketAddress.createUnresolved(it.address, it.port.toInt()) - val info = BedrockServerInfo(it.name, address, address) - proxy.registerServerInfo(info) - logger.info("Additional server ${info.serverName} has been registered!") - } - } -} \ No newline at end of file diff --git a/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/WaterdogConnectionPlugin.kt b/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/WaterdogConnectionPlugin.kt new file mode 100644 index 0000000..f026579 --- /dev/null +++ b/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/WaterdogConnectionPlugin.kt @@ -0,0 +1,74 @@ +package app.simplecloud.plugin.connection.waterdog + +import app.simplecloud.api.CloudApi +import app.simplecloud.plugin.connection.shared.ConnectionPlugin +import app.simplecloud.plugin.connection.waterdog.command.WaterdogCommandManager +import app.simplecloud.plugin.connection.waterdog.handler.WaterdogJoinHandler +import app.simplecloud.plugin.connection.waterdog.handler.WaterdogReconnectHandler +import app.simplecloud.plugin.connection.waterdog.registration.WaterdogServerRegistry +import dev.waterdog.waterdogpe.network.serverinfo.BedrockServerInfo +import dev.waterdog.waterdogpe.plugin.Plugin +import kotlinx.coroutines.* +import org.apache.logging.log4j.LogManager +import java.net.InetSocketAddress + +class WaterdogConnectionPlugin : Plugin() { + + private val api = CloudApi.create() + private val logger = LogManager.getLogger(WaterdogConnectionPlugin::class.java) + private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob()) + private val commandManager = WaterdogCommandManager(this) + + val connectionPlugin = ConnectionPlugin( + dataFolder.toString(), + api, + WaterdogServerRegistry(this, proxy) + ) + + override fun onEnable() { + cleanupServers() + registerAdditionalServers() + registerHandlers() + registerCommands() + + scope.launch { + connectionPlugin.start() + } + } + + override fun onDisable() { + commandManager.unregisterCommands() + scope.launch { connectionPlugin.shutdown() } + scope.cancel() + } + + private fun cleanupServers() { + if (connectionPlugin.connectionConfig.registration.enabled) { + proxy.servers.forEach { + proxy.removeServerInfo(it.serverName) + } + } + } + + private fun registerAdditionalServers() { + if (connectionPlugin.connectionConfig.registration.enabled) { + val additionalServers = connectionPlugin.connectionConfig.registration.additionalServers + additionalServers.forEach { + val address = InetSocketAddress.createUnresolved(it.address, it.port.toInt()) + val info = BedrockServerInfo(it.name, address, address) + proxy.registerServerInfo(info) + logger.info("Additional server ${info.serverName} has been registered!") + } + } + } + + private fun registerHandlers() { + proxy.joinHandler = WaterdogJoinHandler(this) + proxy.reconnectHandler = WaterdogReconnectHandler(this) + } + + private fun registerCommands() { + commandManager.registerCommands() + } + +} diff --git a/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/command/WaterDogCommandManager.kt b/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/command/WaterDogCommandManager.kt deleted file mode 100644 index 5049491..0000000 --- a/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/command/WaterDogCommandManager.kt +++ /dev/null @@ -1,96 +0,0 @@ -package app.simplecloud.plugin.connection.waterdog.command - -import app.simplecloud.plugin.connection.shared.config.CommandConfig -import app.simplecloud.plugin.connection.shared.config.CommandEntry -import dev.waterdog.waterdogpe.command.Command -import dev.waterdog.waterdogpe.command.CommandSender -import dev.waterdog.waterdogpe.command.CommandSettings -import dev.waterdog.waterdogpe.player.ProxiedPlayer -import dev.waterdog.waterdogpe.ProxyServer -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch -import net.kyori.adventure.text.minimessage.MiniMessage -import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer - -class WaterDogCommandManager( - private val proxy: ProxyServer, - private val scope: CoroutineScope -) { - - private val miniMessage = MiniMessage.miniMessage() - private val registeredCommands = mutableListOf() - - fun registerAll(commandConfig: CommandConfig) { - commandConfig.commands.forEach { entry -> - registerCommand(entry) - } - } - - fun unregisterAll() { - registeredCommands.forEach { name -> - proxy.commandMap.unregisterCommand(name) - } - registeredCommands.clear() - } - - private fun registerCommand(entry: CommandEntry) { - val command = buildCommand(entry) - proxy.commandMap.registerCommand(command) - registeredCommands.add(entry.name) - } - - private fun buildCommand(entry: CommandEntry): Command { - val settings = CommandSettings.builder() - .setAliases(entry.aliases.toTypedArray().toString()) - .apply { - if (entry.permission.isNotEmpty()) { - permission = entry.permission - } - } - .build() - - return object : Command(entry.name, settings) { - override fun onExecute(sender: CommandSender, alias: String?, args: Array): Boolean { - val player = sender as? ProxiedPlayer ?: return false - scope.launch { handleCommand(player, entry) } - return true - } - } - } - - private fun handleCommand(player: ProxiedPlayer, entry: CommandEntry) { - val currentServerName = player.serverInfo?.serverName - val groupedByPriority = entry.targetConnections - .groupBy { it.priority } - .entries - .sortedByDescending { it.key } - - for ((_, targets) in groupedByPriority) { - for (target in targets.shuffled()) { - if (target.from.isNotEmpty()) { - val matchesFrom = currentServerName != null && - target.from.any { it.equals(currentServerName, ignoreCase = true) } - if (!matchesFrom) continue - } - - val serverInfo = proxy.getServerInfo(target.name) ?: continue - - if (currentServerName.equals(serverInfo.serverName, ignoreCase = true)) { - sendMessage(player, entry.messages.alreadyConnected) - return - } - - player.connect(serverInfo) - return - } - } - - sendMessage(player, entry.messages.noTargetConnectionFound) - } - - private fun sendMessage(player: ProxiedPlayer, rawMessage: String) { - val component = miniMessage.deserialize(rawMessage) - val plain = PlainTextComponentSerializer.plainText().serialize(component) - player.sendMessage(plain) - } -} \ No newline at end of file diff --git a/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/command/WaterdogCommandManager.kt b/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/command/WaterdogCommandManager.kt new file mode 100644 index 0000000..8758f52 --- /dev/null +++ b/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/command/WaterdogCommandManager.kt @@ -0,0 +1,114 @@ +package app.simplecloud.plugin.connection.waterdog.command + +import app.simplecloud.plugin.connection.shared.config.CommandEntry +import app.simplecloud.plugin.connection.shared.connection.ConnectionResolver +import app.simplecloud.plugin.connection.waterdog.WaterdogConnectionPlugin +import dev.waterdog.waterdogpe.command.Command +import dev.waterdog.waterdogpe.command.CommandSender +import dev.waterdog.waterdogpe.command.CommandSettings +import dev.waterdog.waterdogpe.player.ProxiedPlayer +import net.kyori.adventure.text.minimessage.MiniMessage +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer + +class WaterdogCommandManager( + private val plugin: WaterdogConnectionPlugin, +) { + + private val commands = mutableListOf() + private val miniMessage = MiniMessage.miniMessage() + private val serializer = PlainTextComponentSerializer.plainText() + + fun registerCommands() { + val commands = plugin.connectionPlugin.commandConfig.commands + for (command in commands) { + registerCommand(command) + } + } + + fun unregisterCommands() { + commands.forEach { plugin.proxy.commandMap.unregisterCommand(it) } + commands.clear() + } + + private fun registerCommand(command: CommandEntry) { + val settings = CommandSettings.builder() + .setAliases(*command.aliases.toTypedArray()) + .apply { + if (command.permission.isNotEmpty()) { + permission = command.permission + } + } + .build() + + val connectionCommand = object : Command(command.name, settings) { + override fun onExecute(sender: CommandSender, alias: String?, args: Array): Boolean { + val player = sender as? ProxiedPlayer ?: return false + handleCommand(player, command) + return true + } + } + + plugin.proxy.commandMap.registerCommand(connectionCommand) + commands.add(command.name) + } + + private fun handleCommand(player: ProxiedPlayer, command: CommandEntry) { + val config = plugin.connectionPlugin.connectionConfig + val messages = plugin.connectionPlugin.messageConfig + + if (command.permission.isNotEmpty() && !player.hasPermission(command.permission)) { + sendMessage(player, messages.kick.permissionDenied) + return + } + + val currentServerName = player.serverInfo?.serverName + val serverNames = plugin.proxy.servers.map { it.serverName } + val sortedTargets = command.targetConnections.sortedByDescending { it.priority } + + for (target in sortedTargets) { + if (target.from.isNotEmpty() && currentServerName != null) { + val isFromAllowed = target.from.any { connectionName -> + ConnectionResolver.isServerInConnection( + currentServerName, connectionName, config.connections, serverNames + ) + } + if (!isFromAllowed) continue + } + + val connection = ConnectionResolver.findConnection(target.name, config.connections) ?: continue + + val failedRule = ConnectionResolver.checkRules(connection) { permission -> + player.hasPermission(permission) + } + if (failedRule != null) { + sendMessage(player, messages.kick.permissionDenied) + return + } + + val matchingNames = ConnectionResolver.findMatchingServerNames(connection, serverNames) + if (matchingNames.isEmpty()) continue + + val serverInfo = matchingNames + .mapNotNull { name -> plugin.proxy.getServerInfo(name) } + .minByOrNull { it.players.size } + ?: continue + + if (currentServerName != null && serverInfo.serverName.equals(currentServerName, ignoreCase = true)) { + sendMessage(player, command.messages.alreadyConnected) + return + } + + player.connect(serverInfo) + return + } + + sendMessage(player, command.messages.noTargetConnectionFound) + } + + private fun sendMessage(player: ProxiedPlayer, rawMessage: String) { + val component = miniMessage.deserialize(rawMessage) + val plain = serializer.serialize(component) + player.sendMessage(plain) + } + +} diff --git a/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/connection/WaterdogJoinHandler.kt b/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/connection/WaterdogJoinHandler.kt deleted file mode 100644 index 4982b31..0000000 --- a/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/connection/WaterdogJoinHandler.kt +++ /dev/null @@ -1,27 +0,0 @@ -package app.simplecloud.plugin.connection.waterdog.connection - -import app.simplecloud.plugin.connection.shared.config.ConnectionConfig -import app.simplecloud.plugin.connection.shared.connection.ConnectionResolver -import dev.waterdog.waterdogpe.network.connection.handler.IJoinHandler -import dev.waterdog.waterdogpe.network.serverinfo.ServerInfo -import dev.waterdog.waterdogpe.player.ProxiedPlayer - -class WaterdogJoinHandler( - private val config: () -> ConnectionConfig -) : IJoinHandler { - - override fun determineServer(player: ProxiedPlayer): ServerInfo? { - val currentConfig = config() - if (!currentConfig.networkJoinTargets.enabled) return null - - val resolver = ConnectionResolver(currentConfig) - val entry = resolver.resolve( - targetConnections = currentConfig.networkJoinTargets.targetConnections, - currentServerName = null - ) ?: return null - - return player.proxy.servers - .filter { entry.serverNameMatcher.matches(it.serverName) } - .randomOrNull() - } -} \ No newline at end of file diff --git a/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/connection/WaterdogReconnectHandler.kt b/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/connection/WaterdogReconnectHandler.kt deleted file mode 100644 index ffd79c9..0000000 --- a/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/connection/WaterdogReconnectHandler.kt +++ /dev/null @@ -1,59 +0,0 @@ -package app.simplecloud.plugin.connection.waterdog.connection - -import app.simplecloud.plugin.connection.shared.config.ConnectionConfig -import app.simplecloud.plugin.connection.shared.config.MessageConfig -import app.simplecloud.plugin.connection.shared.connection.ConnectionResolver -import dev.waterdog.waterdogpe.network.connection.handler.IReconnectHandler -import dev.waterdog.waterdogpe.network.connection.handler.ReconnectReason -import dev.waterdog.waterdogpe.network.serverinfo.ServerInfo -import dev.waterdog.waterdogpe.player.ProxiedPlayer -import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer - -class WaterdogReconnectHandler( - private val config: () -> ConnectionConfig, - private val messageConfig: () -> MessageConfig -) : IReconnectHandler { - - private val serializer = PlainTextComponentSerializer.plainText() - - override fun getFallbackServer( - player: ProxiedPlayer?, - oldServer: ServerInfo?, - reason: ReconnectReason?, - kickMessage: String? - ): ServerInfo? { - player ?: return null - oldServer ?: return null - - val currentConfig = config() - val messages = messageConfig() - - if (!currentConfig.fallback.enabled) return null - - val kickedFromServerName = oldServer.serverName - val resolver = ConnectionResolver(currentConfig) - - val entry = resolver.resolve( - targetConnections = currentConfig.fallback.targetConnections, - currentServerName = kickedFromServerName - ) - - if (entry == null) { - player.sendMessage(serializer.serialize(messages.deserialize(messages.kick.noTargetConnection))) - return null - } - - val servers = player.proxy.servers - .filter { entry.serverNameMatcher.matches(it.serverName) } - .filter { !it.serverName.equals(kickedFromServerName, ignoreCase = true) } - - val targetServer = servers.randomOrNull() - - if (targetServer == null) { - player.sendMessage(serializer.serialize(messages.deserialize(messages.kick.noFallbackServers))) - return null - } - - return targetServer - } -} \ No newline at end of file diff --git a/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/handler/WaterdogJoinHandler.kt b/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/handler/WaterdogJoinHandler.kt new file mode 100644 index 0000000..a666b83 --- /dev/null +++ b/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/handler/WaterdogJoinHandler.kt @@ -0,0 +1,42 @@ +package app.simplecloud.plugin.connection.waterdog.handler + +import app.simplecloud.plugin.connection.shared.connection.ConnectionResolver +import app.simplecloud.plugin.connection.waterdog.WaterdogConnectionPlugin +import dev.waterdog.waterdogpe.network.connection.handler.IJoinHandler +import dev.waterdog.waterdogpe.network.serverinfo.ServerInfo +import dev.waterdog.waterdogpe.player.ProxiedPlayer + +class WaterdogJoinHandler( + private val plugin: WaterdogConnectionPlugin, +) : IJoinHandler { + + override fun determineServer(player: ProxiedPlayer): ServerInfo? { + val config = plugin.connectionPlugin.connectionConfig + + if (!config.networkJoinTargets.enabled) return null + + val serverNames = plugin.proxy.servers.map { it.serverName } + val sortedTargets = config.networkJoinTargets.targetConnections.sortedByDescending { it.priority } + + for (target in sortedTargets) { + val connection = ConnectionResolver.findConnection(target.name, config.connections) ?: continue + + val failedRule = ConnectionResolver.checkRules(connection) { permission -> + player.hasPermission(permission) + } + if (failedRule != null) continue + + val matchingNames = ConnectionResolver.findMatchingServerNames(connection, serverNames) + if (matchingNames.isEmpty()) continue + + val serverInfo = matchingNames + .mapNotNull { name -> plugin.proxy.getServerInfo(name) } + .minByOrNull { it.players.size } + + if (serverInfo != null) return serverInfo + } + + return null + } + +} diff --git a/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/handler/WaterdogReconnectHandler.kt b/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/handler/WaterdogReconnectHandler.kt new file mode 100644 index 0000000..943135e --- /dev/null +++ b/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/handler/WaterdogReconnectHandler.kt @@ -0,0 +1,61 @@ +package app.simplecloud.plugin.connection.waterdog.handler + +import app.simplecloud.plugin.connection.shared.connection.ConnectionResolver +import app.simplecloud.plugin.connection.waterdog.WaterdogConnectionPlugin +import dev.waterdog.waterdogpe.network.connection.handler.IReconnectHandler +import dev.waterdog.waterdogpe.network.connection.handler.ReconnectReason +import dev.waterdog.waterdogpe.network.serverinfo.ServerInfo +import dev.waterdog.waterdogpe.player.ProxiedPlayer + +class WaterdogReconnectHandler( + private val plugin: WaterdogConnectionPlugin, +) : IReconnectHandler { + + override fun getFallbackServer( + player: ProxiedPlayer?, + oldServer: ServerInfo?, + reason: ReconnectReason?, + kickMessage: String? + ): ServerInfo? { + if (player == null) return null + + val config = plugin.connectionPlugin.connectionConfig + + if (!config.fallback.enabled) return null + + val kickedServerName = oldServer?.serverName + val serverNames = plugin.proxy.servers.map { it.serverName } + val sortedTargets = config.fallback.targetConnections.sortedByDescending { it.priority } + + for (target in sortedTargets) { + if (target.from.isNotEmpty() && kickedServerName != null) { + val isFromAllowed = target.from.any { connectionName -> + ConnectionResolver.isServerInConnection( + kickedServerName, connectionName, config.connections, serverNames + ) + } + if (!isFromAllowed) continue + } + + val connection = ConnectionResolver.findConnection(target.name, config.connections) ?: continue + + val failedRule = ConnectionResolver.checkRules(connection) { permission -> + player.hasPermission(permission) + } + if (failedRule != null) continue + + val matchingNames = ConnectionResolver.findMatchingServerNames(connection, serverNames) + if (matchingNames.isEmpty()) continue + + val serverInfo = matchingNames + .mapNotNull { name -> plugin.proxy.getServerInfo(name) } + .filter { it.serverName != kickedServerName } + .minByOrNull { it.players.size } + + if (serverInfo != null) return serverInfo + } + + return null + } + +} diff --git a/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/registration/WaterDogServerRegistry.kt b/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/registration/WaterdogServerRegistry.kt similarity index 90% rename from connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/registration/WaterDogServerRegistry.kt rename to connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/registration/WaterdogServerRegistry.kt index 9310364..0d23f65 100644 --- a/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/registration/WaterDogServerRegistry.kt +++ b/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/registration/WaterdogServerRegistry.kt @@ -3,13 +3,13 @@ package app.simplecloud.plugin.connection.waterdog.registration import app.simplecloud.plugin.connection.shared.registration.RegisteredServer import app.simplecloud.plugin.connection.shared.registration.ServerRegistry import app.simplecloud.plugin.connection.shared.resolver.RegisteredServerResolver -import app.simplecloud.plugin.connection.waterdog.WaterDogConnectionPlugin +import app.simplecloud.plugin.connection.waterdog.WaterdogConnectionPlugin import dev.waterdog.waterdogpe.ProxyServer import dev.waterdog.waterdogpe.network.serverinfo.BedrockServerInfo import java.net.InetSocketAddress -class WaterDogServerRegistry( - private val plugin: WaterDogConnectionPlugin, +class WaterdogServerRegistry( + private val plugin: WaterdogConnectionPlugin, private val proxy: ProxyServer ) : ServerRegistry { diff --git a/connection-waterdog/src/main/resources/plugin.yml b/connection-waterdog/src/main/resources/plugin.yml index bedadc4..ac5a7ef 100644 --- a/connection-waterdog/src/main/resources/plugin.yml +++ b/connection-waterdog/src/main/resources/plugin.yml @@ -1,4 +1,4 @@ -name: connection-waterdog +name: simplecloud-connection version: 1.0.0 author: InvalidJoker From ce0172c75ccf6afe35b6c763cdddbc370d3ab3d8 Mon Sep 17 00:00:00 2001 From: xXJanisXx Date: Sat, 7 Mar 2026 22:39:26 +0100 Subject: [PATCH 13/19] feat: improve YamlConfig --- .../bungeecord/BungeeCordConnectionPlugin.kt | 6 +- .../command/BungeeCordCommandManager.kt | 6 +- .../listener/ServerConnectListener.kt | 4 +- .../bungeecord/listener/ServerKickListener.kt | 4 +- .../registration/BungeeCordServerRegistry.kt | 4 +- .../connection/shared/ConnectionPlugin.kt | 10 +- .../connection/shared/config/YamlConfig.kt | 154 +++++++++--------- .../shared/config/reactive/ReactiveConfig.kt | 29 ++++ .../config/reactive/ReactiveConfigInfo.kt | 6 + .../velocity/VelocityConnectionPlugin.kt | 6 +- .../command/VelocityCommandManager.kt | 6 +- .../listener/KickedFromServerListener.kt | 4 +- .../PlayerChooseInitialServerListener.kt | 4 +- .../registration/VelocityServerRegistry.kt | 4 +- .../waterdog/WaterdogConnectionPlugin.kt | 6 +- .../command/WaterdogCommandManager.kt | 6 +- .../waterdog/handler/WaterdogJoinHandler.kt | 2 +- .../handler/WaterdogReconnectHandler.kt | 2 +- .../registration/WaterdogServerRegistry.kt | 4 +- 19 files changed, 151 insertions(+), 116 deletions(-) create mode 100644 connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/reactive/ReactiveConfig.kt create mode 100644 connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/reactive/ReactiveConfigInfo.kt diff --git a/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/BungeeCordConnectionPlugin.kt b/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/BungeeCordConnectionPlugin.kt index 86ec24b..4f5ab49 100644 --- a/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/BungeeCordConnectionPlugin.kt +++ b/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/BungeeCordConnectionPlugin.kt @@ -45,7 +45,7 @@ class BungeeCordConnectionPlugin : Plugin() { } private fun cleanupServers() { - if (connectionPlugin.connectionConfig.registration.enabled) { + if (connectionPlugin.connectionConfig.get().registration.enabled) { proxy.servers.clear() proxy.configurationAdapter.servers.clear() proxy.configurationAdapter.listeners.forEach { @@ -55,8 +55,8 @@ class BungeeCordConnectionPlugin : Plugin() { } private fun registerAdditionalServers() { - if (connectionPlugin.connectionConfig.registration.enabled) { - val additionalServers = connectionPlugin.connectionConfig.registration.additionalServers + if (connectionPlugin.connectionConfig.get().registration.enabled) { + val additionalServers = connectionPlugin.connectionConfig.get().registration.additionalServers additionalServers.forEach { val info = proxy.constructServerInfo( it.name, diff --git a/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/command/BungeeCordCommandManager.kt b/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/command/BungeeCordCommandManager.kt index 8b4838f..e8cca0b 100644 --- a/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/command/BungeeCordCommandManager.kt +++ b/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/command/BungeeCordCommandManager.kt @@ -16,7 +16,7 @@ class BungeeCordCommandManager( private val commands = mutableListOf() fun registerCommands() { - val commands = plugin.connectionPlugin.commandConfig.commands + val commands = plugin.connectionPlugin.commandConfig.get().commands for (command in commands) { registerCommand(command) } @@ -52,8 +52,8 @@ class BungeeCordCommandManager( } private fun handleCommand(player: ProxiedPlayer, command: CommandEntry) { - val config = plugin.connectionPlugin.connectionConfig - val messages = plugin.connectionPlugin.messageConfig + val config = plugin.connectionPlugin.connectionConfig.get() + val messages = plugin.connectionPlugin.messageConfig.get() val audience = audiences.player(player) if (command.permission.isNotEmpty() && !player.hasPermission(command.permission)) { diff --git a/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/listener/ServerConnectListener.kt b/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/listener/ServerConnectListener.kt index 5433609..7fe10ff 100644 --- a/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/listener/ServerConnectListener.kt +++ b/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/listener/ServerConnectListener.kt @@ -16,8 +16,8 @@ class ServerConnectListener( fun onServerConnect(event: ServerConnectEvent) { if (event.reason != ServerConnectEvent.Reason.JOIN_PROXY) return - val config = plugin.connectionPlugin.connectionConfig - val messages = plugin.connectionPlugin.messageConfig + val config = plugin.connectionPlugin.connectionConfig.get() + val messages = plugin.connectionPlugin.messageConfig.get() if (!config.networkJoinTargets.enabled) return diff --git a/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/listener/ServerKickListener.kt b/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/listener/ServerKickListener.kt index 4efacaa..4fe2ccf 100644 --- a/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/listener/ServerKickListener.kt +++ b/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/listener/ServerKickListener.kt @@ -14,8 +14,8 @@ class ServerKickListener( @EventHandler fun onServerKick(event: ServerKickEvent) { - val config = plugin.connectionPlugin.connectionConfig - val messageConfig = plugin.connectionPlugin.messageConfig + val config = plugin.connectionPlugin.connectionConfig.get() + val messageConfig = plugin.connectionPlugin.messageConfig.get() if (!config.fallback.enabled) return diff --git a/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/registration/BungeeCordServerRegistry.kt b/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/registration/BungeeCordServerRegistry.kt index 540f8f0..be964c5 100644 --- a/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/registration/BungeeCordServerRegistry.kt +++ b/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/registration/BungeeCordServerRegistry.kt @@ -23,7 +23,7 @@ class BungeeCordServerRegistry( val address = InetSocketAddress.createUnresolved(server.ip, server.port) val name = RegisteredServerResolver.resolve( server, - plugin.connectionPlugin.connectionConfig.registration + plugin.connectionPlugin.connectionConfig.get().registration ) val info: ServerInfo = ProxyServer.getInstance().constructServerInfo( name, @@ -38,7 +38,7 @@ class BungeeCordServerRegistry( override fun unregister(server: RegisteredServer) { val name = RegisteredServerResolver.resolve( server, - plugin.connectionPlugin.connectionConfig.registration + plugin.connectionPlugin.connectionConfig.get().registration ) proxy.servers.remove(name) servers.remove(server.serverId) diff --git a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/ConnectionPlugin.kt b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/ConnectionPlugin.kt index 102bfbf..785bc09 100644 --- a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/ConnectionPlugin.kt +++ b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/ConnectionPlugin.kt @@ -24,9 +24,9 @@ class ConnectionPlugin( val config = YamlConfig(dir) - val connectionConfig: ConnectionConfig get() = config.load("config") ?: ConnectionConfig() - val messageConfig: MessageConfig get() = config.load("messages") ?: MessageConfig() - val commandConfig: CommandConfig get() = config.load("commands") ?: CommandConfig() + val connectionConfig = config.load("config") + val messageConfig = config.load("messages") + val commandConfig = config.load("commands") suspend fun start() { logger.info("SimpleCloud v3 connection plugin initialized!") @@ -39,13 +39,13 @@ class ConnectionPlugin( fun shutdown() { logger.info("SimpleCloud v3 connection plugin uninitialized!") config.close() - if (connectionConfig.registration.enabled) { + if (connectionConfig.get().registration.enabled) { listener.stop() } } private suspend fun startRegistration() { - if (connectionConfig.registration.enabled) { + if (connectionConfig.get().registration.enabled) { loadExistingServers() listener.start() } diff --git a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/YamlConfig.kt b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/YamlConfig.kt index 761813c..62cd9d1 100644 --- a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/YamlConfig.kt +++ b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/YamlConfig.kt @@ -1,8 +1,10 @@ package app.simplecloud.plugin.connection.shared.config +import app.simplecloud.plugin.connection.shared.config.reactive.ReactiveConfig +import app.simplecloud.plugin.connection.shared.config.reactive.ReactiveConfigInfo import kotlinx.coroutines.* import org.apache.logging.log4j.LogManager -import org.apache.logging.log4j.Logger +import org.spongepowered.configurate.CommentedConfigurationNode import org.spongepowered.configurate.kotlin.objectMapperFactory import org.spongepowered.configurate.loader.ParsingException import org.spongepowered.configurate.yaml.NodeStyle @@ -14,31 +16,58 @@ import java.nio.file.Path import java.nio.file.StandardWatchEventKinds import java.util.concurrent.ConcurrentHashMap -open class YamlConfig(private val dirPath: String) { - - companion object { - protected val logger: Logger = LogManager.getLogger(YamlConfig::class.java) - } - +open class YamlConfig(val dirPath: String) { + private val logger = LogManager.getLogger(YamlConfig::class.java) private val watchService = FileSystems.getDefault().newWatchService() - private val reloadListeners = ConcurrentHashMap Unit>>() - private val lastReload = ConcurrentHashMap() - + private val configCache = ConcurrentHashMap() + private val reactiveConfigs = ConcurrentHashMap>>() private var watcherJob: Job? = null init { startWatcher() } - private fun buildNode(path: String?): Pair { - val file = File(if (path != null) "$dirPath/${path.lowercase()}.yml" else dirPath) + inline fun load(): ReactiveConfig { + return load(null) + } + + inline fun load(path: String?): ReactiveConfig { + return ReactiveConfig(this, path, T::class.java) + } + + internal fun loadDirect(path: String?, clazz: Class): T? { + val cacheKey = path ?: "default" + + try { + val node = buildNode(path).first + val config = node.get(clazz) + + if (config != null) { + configCache[cacheKey] = config + } + + return config + } catch (ex: ParsingException) { + val file = File(if (path != null) "${dirPath}/${path.lowercase()}.yml" else dirPath) + logger.warn("Could not load config file ${file.name}. Using cached version if available.") + @Suppress("UNCHECKED_CAST") + return configCache[cacheKey] as? T + } + } + + internal fun registerReactiveConfig(path: String?, clazz: Class, reactiveConfig: ReactiveConfig) { + val cacheKey = path ?: "default" + val configs = reactiveConfigs.getOrPut(cacheKey) { mutableListOf() } + configs.add(ReactiveConfigInfo(clazz, reactiveConfig)) + } + fun buildNode(path: String?): Pair { + val file = File(if (path != null) "${dirPath}/${path.lowercase()}.yml" else dirPath) if (!file.exists()) { file.parentFile.mkdirs() file.createNewFile() } - val loader = YamlConfigurationLoader.builder() .path(file.toPath()) .nodeStyle(NodeStyle.BLOCK) @@ -50,39 +79,28 @@ open class YamlConfig(private val dirPath: String) { return Pair(loader.load(), loader) } - inline fun load(path: String?): T? { - return load(path, T::class.java) + fun save(obj: T) { + save(null, obj) } - @PublishedApi - internal fun load(path: String?, clazz: Class): T? { - return try { - val node = buildNode(path).first - val config = node.get(clazz) - - config - } catch (e: ParsingException) { - logger.error("Failed to parse config file: ${path ?: "default"} ${e.message}") - null - } catch (e: Exception) { - logger.error("Failed to load config: ${path ?: "default"} ${e.message}", e) - null + fun save(path: String?, obj: T) { + val pair = buildNode(path) + pair.first.set(obj) + pair.second.save(pair.first) + + // Update cache after successful save + val cacheKey = path ?: "default" + if (obj != null) { + configCache[cacheKey] = obj } } - fun save(path: String?, obj: T) { - try { - val pair = buildNode(path) - pair.first.set(obj) - pair.second.save(pair.first) - } catch (e: Exception) { - logger.error("Failed to save config: $path ${e.message}", e) - } + fun save(path: String?, reactiveConfig: ReactiveConfig) { + return save(path, reactiveConfig.get()) } private fun startWatcher() { val directory = File(dirPath).toPath() - if (!directory.toFile().exists()) { directory.toFile().mkdirs() } @@ -90,16 +108,13 @@ open class YamlConfig(private val dirPath: String) { try { directory.register( watchService, - StandardWatchEventKinds.ENTRY_MODIFY, - StandardWatchEventKinds.ENTRY_CREATE + StandardWatchEventKinds.ENTRY_MODIFY ) watcherJob = CoroutineScope(Dispatchers.IO).launch { while (isActive) { try { val key = watchService.take() - delay(150) - for (event in key.pollEvents()) { val path = event.context() as? Path ?: continue val resolvedPath = directory.resolve(path) @@ -108,59 +123,44 @@ open class YamlConfig(private val dirPath: String) { continue } - handleFileChange(resolvedPath.toFile()) + val kind = event.kind() + if (kind == StandardWatchEventKinds.ENTRY_MODIFY) { + handleFileChange(resolvedPath.toFile()) + } } - key.reset() - - } catch (e: InterruptedException) { - logger.info("Config watcher interrupted") - break } catch (e: Exception) { logger.warn("Error in config watcher: ${e.message}") } } } - } catch (e: Exception) { - logger.error("Could not start config file watcher: ${e.message}", e) + logger.warn("Could not start config file watcher: ${e.message}") } } private fun handleFileChange(file: File) { - val cacheKey = file.nameWithoutExtension - val now = System.currentTimeMillis() + val fileName = file.nameWithoutExtension + val cacheKey = if (file.name == File(dirPath).name) "default" else fileName - val last = lastReload[cacheKey] - if (last != null && now - last < 300) return - lastReload[cacheKey] = now + val reactiveConfigList = reactiveConfigs[cacheKey] ?: return - val listeners = reloadListeners[cacheKey] - if (listeners.isNullOrEmpty()) return + reactiveConfigList.forEach { configInfo -> + try { + val configPath = if (cacheKey == "default") null else fileName - try { - val loader = YamlConfigurationLoader.builder() - .path(file.toPath()) - .nodeStyle(NodeStyle.BLOCK) - .defaultOptions { options -> - options.serializers { builder -> - builder.registerAnnotatedObjects(objectMapperFactory()) - } - }.build() + @Suppress("UNCHECKED_CAST") + val typedConfigInfo = configInfo as ReactiveConfigInfo + val newValue = loadDirect(configPath, typedConfigInfo.clazz) - val node = loader.load() + typedConfigInfo.reactiveConfig.update(newValue) - listeners.forEach { listener -> - try { - listener(node) - } catch (e: Exception) { - logger.error("Error in reload listener for $cacheKey: ${e.message}", e) - } + } catch (ex: ParsingException) { + logger.warn("Config file ${file.name} has parsing errors. Keeping old version.") + // Don't update the reactive config, keep the old cached version + } catch (e: Exception) { + logger.warn("Error updating reactive config: ${e.message}") } - } catch (e: ParsingException) { - logger.error("Failed to parse changed config file: $cacheKey ${e.message}") - } catch (e: Exception) { - logger.error("Failed to reload config: $cacheKey ${e.message}", e) } } @@ -168,9 +168,9 @@ open class YamlConfig(private val dirPath: String) { watcherJob?.cancel() try { watchService.close() - logger.info("Config watcher closed") } catch (e: Exception) { logger.warn("Error closing watch service: ${e.message}") } } + } \ No newline at end of file diff --git a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/reactive/ReactiveConfig.kt b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/reactive/ReactiveConfig.kt new file mode 100644 index 0000000..90076b8 --- /dev/null +++ b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/reactive/ReactiveConfig.kt @@ -0,0 +1,29 @@ +package app.simplecloud.plugin.connection.shared.config.reactive + +import app.simplecloud.plugin.connection.shared.config.YamlConfig + +class ReactiveConfig( + val config: YamlConfig, + val path: String?, + val clazz: Class +) { + + @Volatile + private var currentValue: T? = config.loadDirect(path, clazz) + + init { + config.registerReactiveConfig(path, clazz, this) + } + + fun get(): T = currentValue?: throw NullPointerException("Reactive config is not initialized") + + internal fun update(newValue: T?) { + currentValue = newValue + } + + fun reload() { + val newValue = config.loadDirect(path, clazz) + update(newValue) + } + +} \ No newline at end of file diff --git a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/reactive/ReactiveConfigInfo.kt b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/reactive/ReactiveConfigInfo.kt new file mode 100644 index 0000000..1509cfa --- /dev/null +++ b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/reactive/ReactiveConfigInfo.kt @@ -0,0 +1,6 @@ +package app.simplecloud.plugin.connection.shared.config.reactive + +data class ReactiveConfigInfo( + val clazz: Class, + val reactiveConfig: ReactiveConfig +) \ No newline at end of file diff --git a/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/VelocityConnectionPlugin.kt b/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/VelocityConnectionPlugin.kt index e855e29..5cb067a 100644 --- a/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/VelocityConnectionPlugin.kt +++ b/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/VelocityConnectionPlugin.kt @@ -63,7 +63,7 @@ class VelocityConnectionPlugin @Inject constructor( } private fun cleanupServers() { - if (connectionPlugin.connectionConfig.registration.enabled) { + if (connectionPlugin.connectionConfig.get().registration.enabled) { server.allServers.forEach { server.unregisterServer(it.serverInfo) } @@ -71,8 +71,8 @@ class VelocityConnectionPlugin @Inject constructor( } private fun registerAdditionalServers() { - if (connectionPlugin.connectionConfig.registration.enabled) { - val additionalServers = connectionPlugin.connectionConfig.registration.additionalServers + if (connectionPlugin.connectionConfig.get().registration.enabled) { + val additionalServers = connectionPlugin.connectionConfig.get().registration.additionalServers additionalServers.forEach { val info = ServerInfo(it.name, InetSocketAddress.createUnresolved(it.address, it.port.toInt())) server.registerServer(info) diff --git a/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/command/VelocityCommandManager.kt b/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/command/VelocityCommandManager.kt index 1f62b98..228e0c4 100644 --- a/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/command/VelocityCommandManager.kt +++ b/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/command/VelocityCommandManager.kt @@ -14,7 +14,7 @@ class VelocityCommandManager( private val commands = mutableListOf() fun registerCommands() { - val commands = plugin.connectionPlugin.commandConfig.commands + val commands = plugin.connectionPlugin.commandConfig.get().commands for (command in commands) { registerCommand(command) } @@ -46,8 +46,8 @@ class VelocityCommandManager( val source = invocation.source() if (source !is Player) return - val config = plugin.connectionPlugin.connectionConfig - val messages = plugin.connectionPlugin.messageConfig + val config = plugin.connectionPlugin.connectionConfig.get() + val messages = plugin.connectionPlugin.messageConfig.get() if (command.permission.isNotEmpty() && !source.hasPermission(command.permission)) { source.sendMessage(messages.send(messages.kick.permissionDenied)) diff --git a/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/listener/KickedFromServerListener.kt b/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/listener/KickedFromServerListener.kt index 5739885..fdb1db4 100644 --- a/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/listener/KickedFromServerListener.kt +++ b/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/listener/KickedFromServerListener.kt @@ -13,8 +13,8 @@ class KickedFromServerListener( @Subscribe fun onKickedFromServer(event: KickedFromServerEvent) { - val config = plugin.connectionPlugin.connectionConfig - val messages = plugin.connectionPlugin.messageConfig + val config = plugin.connectionPlugin.connectionConfig.get() + val messages = plugin.connectionPlugin.messageConfig.get() if (!config.fallback.enabled) return diff --git a/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/listener/PlayerChooseInitialServerListener.kt b/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/listener/PlayerChooseInitialServerListener.kt index 4cbdac1..8f5b188 100644 --- a/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/listener/PlayerChooseInitialServerListener.kt +++ b/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/listener/PlayerChooseInitialServerListener.kt @@ -13,8 +13,8 @@ class PlayerChooseInitialServerListener( @Subscribe fun onPlayerChooseInitialServer(event: PlayerChooseInitialServerEvent) { - val config = plugin.connectionPlugin.connectionConfig - val messages = plugin.connectionPlugin.messageConfig + val config = plugin.connectionPlugin.connectionConfig.get() + val messages = plugin.connectionPlugin.messageConfig.get() if (!config.networkJoinTargets.enabled) return diff --git a/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/registration/VelocityServerRegistry.kt b/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/registration/VelocityServerRegistry.kt index d6e52ff..5c2170b 100644 --- a/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/registration/VelocityServerRegistry.kt +++ b/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/registration/VelocityServerRegistry.kt @@ -22,7 +22,7 @@ class VelocityServerRegistry( override fun register(server: RegisteredServer) { val info = ServerInfo( - RegisteredServerResolver.resolve(server, plugin.connectionPlugin.connectionConfig.registration), + RegisteredServerResolver.resolve(server, plugin.connectionPlugin.connectionConfig.get().registration), InetSocketAddress.createUnresolved(server.ip, server.port) ) proxy.registerServer(info) @@ -31,7 +31,7 @@ class VelocityServerRegistry( override fun unregister(server: RegisteredServer) { val registered = proxy.getServer( - RegisteredServerResolver.resolve(server, plugin.connectionPlugin.connectionConfig.registration) + RegisteredServerResolver.resolve(server, plugin.connectionPlugin.connectionConfig.get().registration) ).getOrNull() ?: return proxy.unregisterServer(registered.serverInfo) servers.remove(server.serverId) diff --git a/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/WaterdogConnectionPlugin.kt b/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/WaterdogConnectionPlugin.kt index f026579..9aea606 100644 --- a/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/WaterdogConnectionPlugin.kt +++ b/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/WaterdogConnectionPlugin.kt @@ -43,7 +43,7 @@ class WaterdogConnectionPlugin : Plugin() { } private fun cleanupServers() { - if (connectionPlugin.connectionConfig.registration.enabled) { + if (connectionPlugin.connectionConfig.get().registration.enabled) { proxy.servers.forEach { proxy.removeServerInfo(it.serverName) } @@ -51,8 +51,8 @@ class WaterdogConnectionPlugin : Plugin() { } private fun registerAdditionalServers() { - if (connectionPlugin.connectionConfig.registration.enabled) { - val additionalServers = connectionPlugin.connectionConfig.registration.additionalServers + if (connectionPlugin.connectionConfig.get().registration.enabled) { + val additionalServers = connectionPlugin.connectionConfig.get().registration.additionalServers additionalServers.forEach { val address = InetSocketAddress.createUnresolved(it.address, it.port.toInt()) val info = BedrockServerInfo(it.name, address, address) diff --git a/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/command/WaterdogCommandManager.kt b/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/command/WaterdogCommandManager.kt index 8758f52..276bb30 100644 --- a/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/command/WaterdogCommandManager.kt +++ b/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/command/WaterdogCommandManager.kt @@ -19,7 +19,7 @@ class WaterdogCommandManager( private val serializer = PlainTextComponentSerializer.plainText() fun registerCommands() { - val commands = plugin.connectionPlugin.commandConfig.commands + val commands = plugin.connectionPlugin.commandConfig.get().commands for (command in commands) { registerCommand(command) } @@ -53,8 +53,8 @@ class WaterdogCommandManager( } private fun handleCommand(player: ProxiedPlayer, command: CommandEntry) { - val config = plugin.connectionPlugin.connectionConfig - val messages = plugin.connectionPlugin.messageConfig + val config = plugin.connectionPlugin.connectionConfig.get() + val messages = plugin.connectionPlugin.messageConfig.get() if (command.permission.isNotEmpty() && !player.hasPermission(command.permission)) { sendMessage(player, messages.kick.permissionDenied) diff --git a/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/handler/WaterdogJoinHandler.kt b/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/handler/WaterdogJoinHandler.kt index a666b83..206f78b 100644 --- a/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/handler/WaterdogJoinHandler.kt +++ b/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/handler/WaterdogJoinHandler.kt @@ -11,7 +11,7 @@ class WaterdogJoinHandler( ) : IJoinHandler { override fun determineServer(player: ProxiedPlayer): ServerInfo? { - val config = plugin.connectionPlugin.connectionConfig + val config = plugin.connectionPlugin.connectionConfig.get() if (!config.networkJoinTargets.enabled) return null diff --git a/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/handler/WaterdogReconnectHandler.kt b/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/handler/WaterdogReconnectHandler.kt index 943135e..7a2cb47 100644 --- a/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/handler/WaterdogReconnectHandler.kt +++ b/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/handler/WaterdogReconnectHandler.kt @@ -19,7 +19,7 @@ class WaterdogReconnectHandler( ): ServerInfo? { if (player == null) return null - val config = plugin.connectionPlugin.connectionConfig + val config = plugin.connectionPlugin.connectionConfig.get() if (!config.fallback.enabled) return null diff --git a/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/registration/WaterdogServerRegistry.kt b/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/registration/WaterdogServerRegistry.kt index 0d23f65..08be8d3 100644 --- a/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/registration/WaterdogServerRegistry.kt +++ b/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/registration/WaterdogServerRegistry.kt @@ -22,7 +22,7 @@ class WaterdogServerRegistry( override fun register(server: RegisteredServer) { val address = InetSocketAddress.createUnresolved(server.ip, server.port) val info = BedrockServerInfo( - RegisteredServerResolver.resolve(server, plugin.connectionPlugin.connectionConfig.registration), + RegisteredServerResolver.resolve(server, plugin.connectionPlugin.connectionConfig.get().registration), address, address ) @@ -33,7 +33,7 @@ class WaterdogServerRegistry( override fun unregister(server: RegisteredServer) { val name = RegisteredServerResolver.resolve( server, - plugin.connectionPlugin.connectionConfig.registration + plugin.connectionPlugin.connectionConfig.get().registration ) proxy.removeServerInfo(name) ?: return servers.remove(server.serverId) From afb5bac866d9fe6e9643aab2716547421cf2bd36 Mon Sep 17 00:00:00 2001 From: xXJanisXx Date: Sat, 7 Mar 2026 23:29:29 +0100 Subject: [PATCH 14/19] feat: add connection reload command --- .../bungeecord/BungeeCordConnectionPlugin.kt | 2 + .../bungeecord/command/ConnectionCommand.kt | 43 ++++++++++++++ .../connection/shared/config/MessageConfig.kt | 13 ++++- .../connection/shared/config/YamlConfig.kt | 6 +- .../velocity/VelocityConnectionPlugin.kt | 6 ++ .../velocity/command/ConnectionCommand.kt | 46 +++++++++++++++ .../waterdog/WaterdogConnectionPlugin.kt | 2 + .../waterdog/command/ConnectionCommand.kt | 57 +++++++++++++++++++ 8 files changed, 170 insertions(+), 5 deletions(-) create mode 100644 connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/command/ConnectionCommand.kt create mode 100644 connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/command/ConnectionCommand.kt create mode 100644 connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/command/ConnectionCommand.kt diff --git a/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/BungeeCordConnectionPlugin.kt b/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/BungeeCordConnectionPlugin.kt index 4f5ab49..13f9f39 100644 --- a/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/BungeeCordConnectionPlugin.kt +++ b/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/BungeeCordConnectionPlugin.kt @@ -2,6 +2,7 @@ package app.simplecloud.plugin.connection.bungeecord import app.simplecloud.api.CloudApi import app.simplecloud.plugin.connection.bungeecord.command.BungeeCordCommandManager +import app.simplecloud.plugin.connection.bungeecord.command.ConnectionCommand import app.simplecloud.plugin.connection.bungeecord.listener.ServerConnectListener import app.simplecloud.plugin.connection.bungeecord.listener.ServerKickListener import app.simplecloud.plugin.connection.bungeecord.registration.BungeeCordServerRegistry @@ -77,6 +78,7 @@ class BungeeCordConnectionPlugin : Plugin() { private fun registerCommands() { commandManager.registerCommands() + proxy.pluginManager.registerCommand(this, ConnectionCommand(this, audiences)) } } diff --git a/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/command/ConnectionCommand.kt b/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/command/ConnectionCommand.kt new file mode 100644 index 0000000..d7649dd --- /dev/null +++ b/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/command/ConnectionCommand.kt @@ -0,0 +1,43 @@ +package app.simplecloud.plugin.connection.bungeecord.command + +import app.simplecloud.plugin.connection.bungeecord.BungeeCordConnectionPlugin +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import net.kyori.adventure.platform.bungeecord.BungeeAudiences +import net.md_5.bungee.api.CommandSender +import net.md_5.bungee.api.plugin.Command +import org.apache.logging.log4j.LogManager + +class ConnectionCommand( + private val plugin: BungeeCordConnectionPlugin, + private val audiences: BungeeAudiences, +) : Command("connection", "simplecloud.connection.reload") { + + private val logger = LogManager.getLogger(ConnectionCommand::class.java) + + override fun execute(sender: CommandSender, args: Array) { + val messages = plugin.connectionPlugin.messageConfig.get() + val audience = audiences.sender(sender) + + if (args.firstOrNull()?.equals("reload", ignoreCase = true) != true) { + audience.sendMessage(messages.send(messages.command.commandUsage)) + return + } + + audience.sendMessage(messages.send(messages.command.configReloading)) + try { + CoroutineScope(Dispatchers.IO).launch { + plugin.connectionPlugin.connectionConfig.reload() + plugin.connectionPlugin.commandConfig.reload() + plugin.connectionPlugin.messageConfig.reload() + + audience.sendMessage(messages.send(messages.command.configReloadedSuccess)) + } + } catch (e: Exception) { + audience.sendMessage(messages.send(messages.command.configReloadedFailed)) + logger.error("Failed to reload config", e) + } + } + +} diff --git a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/MessageConfig.kt b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/MessageConfig.kt index bee74ad..1c4976d 100644 --- a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/MessageConfig.kt +++ b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/MessageConfig.kt @@ -12,7 +12,8 @@ import org.spongepowered.configurate.objectmapping.ConfigSerializable data class MessageConfig( val version: Char = ConfigVersion.VERSION, val variables: Map = DefaultConfigs.VARIABLES, - val kick: KickMessages = KickMessages() + val kick: KickMessages = KickMessages(), + val command: ConnectionCommandMessages = ConnectionCommandMessages() ) { private val miniMessage = MiniMessage.miniMessage() @@ -31,5 +32,13 @@ data class MessageConfig( data class KickMessages( val noFallbackServers: String = " There is no fallback server available.", val noTargetConnection: String = " You have been disconnected from the network
because there are no fallback servers available.", - val permissionDenied: String = " You don't have permission to join this server.", + val permissionDenied: String = " You don't have permission to join this server." ) + +@ConfigSerializable +data class ConnectionCommandMessages( + val commandUsage: String = " Usage: /connection reload", + val configReloading: String = " Reloading Connection configurations...", + val configReloadedSuccess: String = " Successfully reloaded all Connection configurations.", + val configReloadedFailed: String = " Failed to reload Connection configurations." +) \ No newline at end of file diff --git a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/YamlConfig.kt b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/YamlConfig.kt index 62cd9d1..25875aa 100644 --- a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/YamlConfig.kt +++ b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/YamlConfig.kt @@ -16,7 +16,7 @@ import java.nio.file.Path import java.nio.file.StandardWatchEventKinds import java.util.concurrent.ConcurrentHashMap -open class YamlConfig(val dirPath: String) { +open class YamlConfig(private val dirPath: String) { private val logger = LogManager.getLogger(YamlConfig::class.java) private val watchService = FileSystems.getDefault().newWatchService() @@ -62,7 +62,7 @@ open class YamlConfig(val dirPath: String) { configs.add(ReactiveConfigInfo(clazz, reactiveConfig)) } - fun buildNode(path: String?): Pair { + private fun buildNode(path: String?): Pair { val file = File(if (path != null) "${dirPath}/${path.lowercase()}.yml" else dirPath) if (!file.exists()) { file.parentFile.mkdirs() @@ -83,7 +83,7 @@ open class YamlConfig(val dirPath: String) { save(null, obj) } - fun save(path: String?, obj: T) { + private fun save(path: String?, obj: T) { val pair = buildNode(path) pair.first.set(obj) pair.second.save(pair.first) diff --git a/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/VelocityConnectionPlugin.kt b/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/VelocityConnectionPlugin.kt index 5cb067a..25b358e 100644 --- a/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/VelocityConnectionPlugin.kt +++ b/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/VelocityConnectionPlugin.kt @@ -2,6 +2,7 @@ package app.simplecloud.plugin.connection.velocity import app.simplecloud.api.CloudApi import app.simplecloud.plugin.connection.shared.ConnectionPlugin +import app.simplecloud.plugin.connection.velocity.command.ConnectionCommand import app.simplecloud.plugin.connection.velocity.command.VelocityCommandManager import app.simplecloud.plugin.connection.velocity.listener.KickedFromServerListener import app.simplecloud.plugin.connection.velocity.listener.PlayerChooseInitialServerListener @@ -88,5 +89,10 @@ class VelocityConnectionPlugin @Inject constructor( private fun registerCommands() { commandManager.registerCommands() + + val meta = server.commandManager.metaBuilder("connection") + .plugin(this) + .build() + server.commandManager.register(meta, ConnectionCommand(this)) } } diff --git a/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/command/ConnectionCommand.kt b/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/command/ConnectionCommand.kt new file mode 100644 index 0000000..3944f8a --- /dev/null +++ b/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/command/ConnectionCommand.kt @@ -0,0 +1,46 @@ +package app.simplecloud.plugin.connection.velocity.command + +import app.simplecloud.plugin.connection.velocity.VelocityConnectionPlugin +import com.velocitypowered.api.command.SimpleCommand +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import org.apache.logging.log4j.LogManager + +class ConnectionCommand( + private val plugin: VelocityConnectionPlugin, +) : SimpleCommand { + + private val logger = LogManager.getLogger(ConnectionCommand::class.java) + + override fun execute(invocation: SimpleCommand.Invocation) { + val args = invocation.arguments() + val source = invocation.source() + + val messages = plugin.connectionPlugin.messageConfig.get() + + if (args.isEmpty() || !args[0].equals("reload", ignoreCase = true)) { + source.sendMessage(messages.send(messages.command.commandUsage)) + return + } + + source.sendMessage(messages.send(messages.command.configReloading)) + try { + CoroutineScope(Dispatchers.IO).launch { + plugin.connectionPlugin.connectionConfig.reload() + plugin.connectionPlugin.commandConfig.reload() + plugin.connectionPlugin.messageConfig.reload() + + source.sendMessage(messages.send(messages.command.configReloadedSuccess)) + } + } catch (e: Exception) { + source.sendMessage(messages.send(messages.command.configReloadedFailed)) + logger.error("Failed to reload config", e) + } + } + + override fun hasPermission(invocation: SimpleCommand.Invocation): Boolean { + return invocation.source().hasPermission("simplecloud.connection.reload") + } + +} diff --git a/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/WaterdogConnectionPlugin.kt b/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/WaterdogConnectionPlugin.kt index 9aea606..058f59a 100644 --- a/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/WaterdogConnectionPlugin.kt +++ b/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/WaterdogConnectionPlugin.kt @@ -2,6 +2,7 @@ package app.simplecloud.plugin.connection.waterdog import app.simplecloud.api.CloudApi import app.simplecloud.plugin.connection.shared.ConnectionPlugin +import app.simplecloud.plugin.connection.waterdog.command.ConnectionCommand import app.simplecloud.plugin.connection.waterdog.command.WaterdogCommandManager import app.simplecloud.plugin.connection.waterdog.handler.WaterdogJoinHandler import app.simplecloud.plugin.connection.waterdog.handler.WaterdogReconnectHandler @@ -69,6 +70,7 @@ class WaterdogConnectionPlugin : Plugin() { private fun registerCommands() { commandManager.registerCommands() + proxy.commandMap.registerCommand(ConnectionCommand(this)) } } diff --git a/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/command/ConnectionCommand.kt b/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/command/ConnectionCommand.kt new file mode 100644 index 0000000..03bfced --- /dev/null +++ b/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/command/ConnectionCommand.kt @@ -0,0 +1,57 @@ +package app.simplecloud.plugin.connection.waterdog.command + +import app.simplecloud.plugin.connection.waterdog.WaterdogConnectionPlugin +import dev.waterdog.waterdogpe.command.Command +import dev.waterdog.waterdogpe.command.CommandSender +import dev.waterdog.waterdogpe.command.CommandSettings +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer +import org.apache.logging.log4j.LogManager + +class ConnectionCommand( + private val plugin: WaterdogConnectionPlugin, +) : Command( + "connection", + CommandSettings.builder() + .setPermission("simplecloud.connection.reload") + .build() +) { + + private val logger = LogManager.getLogger(ConnectionCommand::class.java) + private val serializer = PlainTextComponentSerializer.plainText() + + override fun onExecute(sender: CommandSender, alias: String?, args: Array): Boolean { + val messages = plugin.connectionPlugin.messageConfig.get() + + if (args.firstOrNull()?.equals("reload", ignoreCase = true) != true) { + sendMessage(sender, messages.command.commandUsage) + return true + } + + sendMessage(sender, messages.command.configReloading) + try { + CoroutineScope(Dispatchers.IO).launch { + plugin.connectionPlugin.connectionConfig.reload() + plugin.connectionPlugin.commandConfig.reload() + plugin.connectionPlugin.messageConfig.reload() + + sendMessage(sender, messages.command.configReloadedSuccess) + } + } catch (e: Exception) { + sendMessage(sender, messages.command.configReloadedFailed) + logger.error("Failed to reload config", e) + } + + return true + } + + private fun sendMessage(sender: CommandSender, rawMessage: String) { + val messages = plugin.connectionPlugin.messageConfig.get() + val component = messages.send(rawMessage) + val plain = serializer.serialize(component) + sender.sendMessage(plain) + } + +} From 069e2557bbd5121bcdb116bbd7608562796b1e0e Mon Sep 17 00:00:00 2001 From: xXJanisXx Date: Sat, 7 Mar 2026 23:45:34 +0100 Subject: [PATCH 15/19] chore: remove the prefix from some messages --- .../plugin/connection/shared/config/MessageConfig.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/MessageConfig.kt b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/MessageConfig.kt index 1c4976d..e231674 100644 --- a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/MessageConfig.kt +++ b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/MessageConfig.kt @@ -30,9 +30,9 @@ data class MessageConfig( @ConfigSerializable data class KickMessages( - val noFallbackServers: String = " There is no fallback server available.", - val noTargetConnection: String = " You have been disconnected from the network
because there are no fallback servers available.", - val permissionDenied: String = " You don't have permission to join this server." + val noFallbackServers: String = "There is no fallback server available.", + val noTargetConnection: String = "You have been disconnected from the network
because there are no fallback servers available.", + val permissionDenied: String = "You don't have permission to join this server." ) @ConfigSerializable From 34fd52c823c86194d8959c10a9d96dd2f8024624 Mon Sep 17 00:00:00 2001 From: xXJanisXx Date: Sat, 7 Mar 2026 23:54:23 +0100 Subject: [PATCH 16/19] chore: remove unused repositories --- build.gradle.kts | 2 -- 1 file changed, 2 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 9b58aff..06b97b6 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -19,8 +19,6 @@ allprojects { repositories { mavenCentral() maven("https://buf.build/gen/maven") - maven("https://oss.sonatype.org/content/repositories/snapshots") - maven("https://libraries.minecraft.net") maven("https://repo.papermc.io/repository/maven-public") maven("https://repo.simplecloud.app/snapshots") maven("https://repo.waterdog.dev/releases/") From ddd7906bbf52e080b6eca1aca58e8f2e0c1b40b7 Mon Sep 17 00:00:00 2001 From: xXJanisXx Date: Sun, 8 Mar 2026 12:05:03 +0100 Subject: [PATCH 17/19] chore: update copyright year --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 94395cc..b085927 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright [2024] [SimpleCloud] + Copyright [2024-2026] [SimpleCloud] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. From e9a0e444e3d6bc0ab2b761c8a7f3a3d565873a38 Mon Sep 17 00:00:00 2001 From: xXJanisXx Date: Sun, 8 Mar 2026 14:47:14 +0100 Subject: [PATCH 18/19] fix: remove unused permission message --- .../connection/bungeecord/command/BungeeCordCommandManager.kt | 2 -- .../plugin/connection/shared/config/MessageConfig.kt | 1 - .../connection/velocity/command/VelocityCommandManager.kt | 2 -- .../connection/waterdog/command/WaterdogCommandManager.kt | 3 --- 4 files changed, 8 deletions(-) diff --git a/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/command/BungeeCordCommandManager.kt b/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/command/BungeeCordCommandManager.kt index e8cca0b..7dd77bb 100644 --- a/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/command/BungeeCordCommandManager.kt +++ b/connection-bungeecord/src/main/kotlin/app/simplecloud/plugin/connection/bungeecord/command/BungeeCordCommandManager.kt @@ -57,7 +57,6 @@ class BungeeCordCommandManager( val audience = audiences.player(player) if (command.permission.isNotEmpty() && !player.hasPermission(command.permission)) { - audience.sendMessage(messages.send(messages.kick.permissionDenied)) return } @@ -81,7 +80,6 @@ class BungeeCordCommandManager( player.hasPermission(permission) } if (failedRule != null) { - audience.sendMessage(messages.send(messages.kick.permissionDenied)) return } diff --git a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/MessageConfig.kt b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/MessageConfig.kt index e231674..1c65fb7 100644 --- a/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/MessageConfig.kt +++ b/connection-shared/src/main/kotlin/app/simplecloud/plugin/connection/shared/config/MessageConfig.kt @@ -32,7 +32,6 @@ data class MessageConfig( data class KickMessages( val noFallbackServers: String = "There is no fallback server available.", val noTargetConnection: String = "You have been disconnected from the network
because there are no fallback servers available.", - val permissionDenied: String = "You don't have permission to join this server." ) @ConfigSerializable diff --git a/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/command/VelocityCommandManager.kt b/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/command/VelocityCommandManager.kt index 228e0c4..7ea1754 100644 --- a/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/command/VelocityCommandManager.kt +++ b/connection-velocity/src/main/kotlin/app/simplecloud/plugin/connection/velocity/command/VelocityCommandManager.kt @@ -50,7 +50,6 @@ class VelocityCommandManager( val messages = plugin.connectionPlugin.messageConfig.get() if (command.permission.isNotEmpty() && !source.hasPermission(command.permission)) { - source.sendMessage(messages.send(messages.kick.permissionDenied)) return } @@ -74,7 +73,6 @@ class VelocityCommandManager( source.hasPermission(permission) } if (failedRule != null) { - source.sendMessage(messages.send(messages.kick.permissionDenied)) return } diff --git a/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/command/WaterdogCommandManager.kt b/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/command/WaterdogCommandManager.kt index 276bb30..4228c2e 100644 --- a/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/command/WaterdogCommandManager.kt +++ b/connection-waterdog/src/main/kotlin/app/simplecloud/plugin/connection/waterdog/command/WaterdogCommandManager.kt @@ -54,10 +54,8 @@ class WaterdogCommandManager( private fun handleCommand(player: ProxiedPlayer, command: CommandEntry) { val config = plugin.connectionPlugin.connectionConfig.get() - val messages = plugin.connectionPlugin.messageConfig.get() if (command.permission.isNotEmpty() && !player.hasPermission(command.permission)) { - sendMessage(player, messages.kick.permissionDenied) return } @@ -81,7 +79,6 @@ class WaterdogCommandManager( player.hasPermission(permission) } if (failedRule != null) { - sendMessage(player, messages.kick.permissionDenied) return } From c1de1e0dbe79b957d2c1e63412207cfe9b64603c Mon Sep 17 00:00:00 2001 From: xXJanisXx Date: Sun, 8 Mar 2026 15:21:46 +0100 Subject: [PATCH 19/19] chore: bump gradle to 9.4.0 --- gradle/wrapper/gradle-wrapper.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 37f78a6..dbc3ce4 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.0-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME