diff --git a/.github/workflows/release-dev.yml b/.github/workflows/release-dev.yml new file mode 100644 index 0000000..e96e3b9 --- /dev/null +++ b/.github/workflows/release-dev.yml @@ -0,0 +1,81 @@ +name: Create Release with ShadowJars + +on: + workflow_dispatch: + +jobs: + build: + name: Build ShadowJars and Create Release + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + with: + ref: develop # Ensure it works from the develop branch + + - name: Set up JDK 21 + uses: actions/setup-java@v3 + with: + distribution: 'adopt' + java-version: '21' + + - name: Cache Gradle packages + uses: actions/cache@v3 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + gradle-${{ runner.os }} + + - name: Make gradlew executable + run: chmod +x ./gradlew + + - name: Build ShadowJars + run: ./gradlew clean build shadowJar + + - name: Get Gradle Version + id: gradle_version + run: echo "GRADLE_VERSION=$(./gradlew properties -q | grep "version:" | awk '{print $2}')" >> $GITHUB_ENV + + - name: Get Commit Hash + id: commit_hash + run: echo "COMMIT_HASH=$(git rev-parse --short HEAD)" >> $GITHUB_ENV + +# - name: Publish to SimpleCloud Repository +# run: ./gradlew publishMavenJavaPublicationToSimplecloudRepository +# env: +# COMMIT_HASH: ${{ env.COMMIT_HASH }} +# SIMPLECLOUD_USERNAME: ${{ secrets.SIMPLECLOUD_USERNAME }} +# SIMPLECLOUD_PASSWORD: ${{ secrets.SIMPLECLOUD_PASSWORD }} + + - name: Create Release + id: create_release + uses: actions/create-release@v1 + with: + tag_name: v${{ env.GRADLE_VERSION }}-dev.${{ env.COMMIT_HASH }} + release_name: v${{ env.GRADLE_VERSION }}-dev.${{ env.COMMIT_HASH }} + draft: false + prerelease: true + commitish: develop + body: | + This release contains dev builds for all Gradle modules. + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Upload ShadowJars to Release + run: | + # Find JAR files in any submodule's build/libs directory + for jar in $(find . -type f -name "*.jar" -path "*/build/libs/*.jar" -not -path "./build/libs/*"); do + # Check if the filename contains a version number (e.g., a dash followed by numbers) + if [[ $(basename "$jar") =~ -[0-9]+\.[0-9]+ ]]; then + echo "Skipping $jar due to version number" + else + echo "Uploading $jar" + gh release upload v${{ env.GRADLE_VERSION }}-dev.${{ env.COMMIT_HASH }} "$jar" + fi + done + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml index 2b8a50f..d4b7acc 100644 --- a/.idea/kotlinc.xml +++ b/.idea/kotlinc.xml @@ -1,6 +1,6 @@ - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index c28d5d0..69eb25a 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -7,7 +7,7 @@ - + \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 7af9308..8b4b07f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,10 +6,13 @@ plugins { alias(libs.plugins.shadow) } -allprojects { +val baseVersion = "0.0.1" +val commitHash = System.getenv("COMMIT_HASH") +val snapshotversion = "${baseVersion}-dev.$commitHash" +allprojects { group = "app.simplecloud.plugin.proxy" - version = "1.0.0" + version = if (commitHash != null) snapshotversion else baseVersion repositories { mavenCentral() @@ -18,10 +21,12 @@ allprojects { subprojects { apply(plugin = "org.jetbrains.kotlin.jvm") - apply(plugin = "com.github.johnrengelman.shadow") + apply(plugin = "com.gradleup.shadow") repositories { mavenCentral() + maven("https://buf.build/gen/maven") + maven { name = "papermc" url = uri("https://repo.papermc.io/repository/maven-public/") @@ -29,19 +34,27 @@ subprojects { maven { url = uri("https://oss.sonatype.org/content/repositories/snapshots") } + maven { + name = "simplecloudRepositorySnapshots" + url = uri("https://repo.simplecloud.app/snapshots") + } } dependencies { - testImplementation(rootProject.libs.kotlinTest) - implementation(rootProject.libs.kotlinJvm) + testImplementation(rootProject.libs.kotlin.test) + implementation(rootProject.libs.kotlin.jvm) + implementation(rootProject.libs.kotlin.coroutines) } - kotlin { - jvmToolchain(17) + java { + toolchain.languageVersion.set(JavaLanguageVersion.of(21)) } - tasks.withType { - kotlinOptions.jvmTarget = "17" + kotlin { + jvmToolchain(21) + compilerOptions { + apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0) + } } tasks.named("shadowJar", ShadowJar::class) { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e5d143d..be52b1f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,26 +1,42 @@ [versions] -kotlin = "1.8.0" -shadow = "8.1.1" -velocity = "3.1.1" -bungeecord = "1.19-R0.1-SNAPSHOT" +kotlin = "2.0.20" +kotlin-coroutines = "1.9.0" +shadow = "8.3.3" +velocity = "3.4.0-SNAPSHOT" +bungeecord = "1.20-R0.1-SNAPSHOT" adventure = "4.16.0" -adventurelatform = "4.3.2" +adventure-platform = "4.3.2" gson = "2.10.1" -configurateYaml = "4.0.0" -configurateKotlin = "4.1.2" +configurate-yaml = "4.0.0" +configurate-kotlin = "4.1.2" +simplecloud-event-wrapper = "0.0.1-dev.950792a" +simplecloud-controller = "0.0.30-dev.bf5da83" +command-cloud-core = "2.0.0" +command-cloud-velocity = "2.0.0-beta.10" +command-cloud-bungeecord = "2.0.0-beta.10" [libraries] -kotlinJvm = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.ref = "kotlin" } -kotlinTest = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } -velocityApi = { module = "com.velocitypowered:velocity-api", version.ref = "velocity" } -bungeecordApi = { module = "net.md-5:bungeecord-api", version.ref = "bungeecord" } -adventureLegacySerializer = { module = "net.kyori:adventure-text-serializer-legacy", version.ref = "adventure" } -adventureMinimessage = { module = "net.kyori:adventure-text-minimessage", version.ref = "adventure" } -adventureBungeecordPlatform = { module = "net.kyori:adventure-platform-bungeecord", version.ref = "adventurelatform" } +kotlin-jvm = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.ref = "kotlin" } +kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } +kotlin-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlin-coroutines" } + +command-cloud-core = { module = "org.incendo:cloud-core", version.ref = "command-cloud-core" } +command-cloud-velocity = { module = "org.incendo:cloud-velocity", version.ref = "command-cloud-velocity" } +command-cloud-bungeecord = { module = "org.incendo:cloud-bungee", version.ref = "command-cloud-bungeecord" } + +velocity = { module = "com.velocitypowered:velocity-api", version.ref = "velocity" } +bungeecord = { module = "net.md-5:bungeecord-api", version.ref = "bungeecord" } +adventure-legacy-serializer = { module = "net.kyori:adventure-text-serializer-legacy", version.ref = "adventure" } +adventure-minimessage = { module = "net.kyori:adventure-text-minimessage", version.ref = "adventure" } +adventure-bungeecord-platform = { module = "net.kyori:adventure-platform-bungeecord", version.ref = "adventure-platform" } gson = { module = "com.google.code.gson:gson", version.ref = "gson" } -configurateYaml = { module = "org.spongepowered:configurate-yaml", version.ref = "configurateYaml" } -configurateKotlin = { module = "org.spongepowered:configurate-extra-kotlin", version.ref = "configurateKotlin" } +configurate-yaml = { module = "org.spongepowered:configurate-yaml", version.ref = "configurate-yaml" } +configurate-kotlin = { module = "org.spongepowered:configurate-extra-kotlin", version.ref = "configurate-kotlin" } + +simplecloud-event-wrapper-velocity = { module = "app.simplecloud.event:event-wrapper-velocity", version.ref = "simplecloud-event-wrapper" } +simplecloud-event-wrapper-bungeecord = { module = "app.simplecloud.event:event-wrapper-bungeecord", version.ref = "simplecloud-event-wrapper" } +simplecloud-controller = { module = "app.simplecloud.controller:controller-api", version.ref = "simplecloud-controller" } [plugins] kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } -shadow = { id = "com.github.johnrengelman.shadow", version.ref = "shadow" } \ No newline at end of file +shadow = { id = "com.gradleup.shadow", version.ref = "shadow" } \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 30609b7..34ace61 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Thu Mar 21 21:19:37 CET 2024 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/proxy-bungeecord/build.gradle.kts b/proxy-bungeecord/build.gradle.kts index be86456..0ba193c 100644 --- a/proxy-bungeecord/build.gradle.kts +++ b/proxy-bungeecord/build.gradle.kts @@ -1,9 +1,14 @@ dependencies { api(project(":proxy-shared")) - compileOnly(rootProject.libs.bungeecordApi) + compileOnly(rootProject.libs.bungeecord) - implementation(rootProject.libs.adventureLegacySerializer) - implementation(rootProject.libs.adventureMinimessage) - implementation(rootProject.libs.adventureBungeecordPlatform) + implementation(rootProject.libs.adventure.legacy.serializer) + implementation(rootProject.libs.adventure.minimessage) + implementation(rootProject.libs.adventure.bungeecord.platform) + + compileOnly(rootProject.libs.simplecloud.event.wrapper.bungeecord) + + implementation(rootProject.libs.command.cloud.core) + implementation(rootProject.libs.command.cloud.bungeecord) } \ No newline at end of file diff --git a/proxy-bungeecord/src/main/kotlin/app/simplecloud/plugin/proxy/bungeecord/BungeeCordCommandSender.kt b/proxy-bungeecord/src/main/kotlin/app/simplecloud/plugin/proxy/bungeecord/BungeeCordCommandSender.kt new file mode 100644 index 0000000..114a258 --- /dev/null +++ b/proxy-bungeecord/src/main/kotlin/app/simplecloud/plugin/proxy/bungeecord/BungeeCordCommandSender.kt @@ -0,0 +1,22 @@ +package app.simplecloud.plugin.proxy.bungeecord + +import app.simplecloud.plugin.proxy.shared.handler.command.CommandSender +import net.kyori.adventure.text.Component +import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer +import net.md_5.bungee.api.chat.BaseComponent + +class BungeeCordCommandSender(private val commandSender: net.md_5.bungee.api.CommandSender +) : CommandSender { + + fun getCommandSender(): net.md_5.bungee.api.CommandSender { + return commandSender + } + + override fun sendMessage(message: String) { + commandSender.sendMessage(message) + } +} + +fun Component.toBaseComponent(): BaseComponent { + return BungeeComponentSerializer.get().serialize(this)[0] +} \ No newline at end of file diff --git a/proxy-bungeecord/src/main/kotlin/app/simplecloud/plugin/proxy/bungeecord/ProxyBungeeCordPlugin.kt b/proxy-bungeecord/src/main/kotlin/app/simplecloud/plugin/proxy/bungeecord/ProxyBungeeCordPlugin.kt index 904ac30..29abcb1 100644 --- a/proxy-bungeecord/src/main/kotlin/app/simplecloud/plugin/proxy/bungeecord/ProxyBungeeCordPlugin.kt +++ b/proxy-bungeecord/src/main/kotlin/app/simplecloud/plugin/proxy/bungeecord/ProxyBungeeCordPlugin.kt @@ -2,59 +2,74 @@ package app.simplecloud.plugin.proxy.bungeecord import app.simplecloud.plugin.proxy.bungeecord.event.ConfigureTagResolversEvent import app.simplecloud.plugin.proxy.bungeecord.handler.TabListHandler -import app.simplecloud.plugin.proxy.bungeecord.listener.ConfigureTagResolversListener -import app.simplecloud.plugin.proxy.bungeecord.listener.ProxyPingListener -import app.simplecloud.plugin.proxy.bungeecord.listener.TabListListener -import app.simplecloud.plugin.proxy.shared.config.GeneralConfig +import app.simplecloud.plugin.proxy.bungeecord.listener.* +import app.simplecloud.plugin.proxy.shared.ProxyPlugin import app.simplecloud.plugin.proxy.shared.config.YamlConfig import app.simplecloud.plugin.proxy.shared.config.placeholder.PlaceHolderConfiguration import app.simplecloud.plugin.proxy.shared.config.tablis.TabListConfiguration import app.simplecloud.plugin.proxy.shared.handler.MotdLayoutHandler +import app.simplecloud.plugin.proxy.shared.handler.command.CommandSender +import app.simplecloud.plugin.proxy.shared.handler.command.ProxyCommandHandler import net.kyori.adventure.platform.bungeecord.BungeeAudiences import net.kyori.adventure.text.Component import net.kyori.adventure.text.minimessage.MiniMessage import net.md_5.bungee.api.connection.ProxiedPlayer import net.md_5.bungee.api.plugin.Plugin +import org.incendo.cloud.SenderMapper +import org.incendo.cloud.bungee.BungeeCommandManager +import org.incendo.cloud.execution.ExecutionCoordinator class ProxyBungeeCordPlugin: Plugin() { - lateinit var generalConfiguration: GeneralConfig - lateinit var tabListConfiguration: TabListConfiguration - lateinit var placeHolderConfiguration: PlaceHolderConfiguration + val proxyPlugin = ProxyPlugin(this.dataFolder.path) + - val config = YamlConfig(this.dataFolder.path) val tabListHandler = TabListHandler(this) - val motdLayoutHandler = MotdLayoutHandler(config, generalConfiguration) + + private lateinit var commandManager: BungeeCommandManager private var adventure: BungeeAudiences? = null private val miniMessage = MiniMessage.miniMessage() override fun onEnable() { - val config = YamlConfig(this.dataFolder.path) - - this.generalConfiguration = config.load("general")!! - config.save("general", this.generalConfiguration) - - this.tabListConfiguration = config.load("tablist")!! - config.save("tablist", this.tabListConfiguration) + this.proxyPlugin.config.save("tablist", this.proxyPlugin.tabListConfiguration) + this.proxyPlugin.config.save("placeholder", this.proxyPlugin.placeHolderConfiguration) + this.proxyPlugin.config.save("messages", this.proxyPlugin.messagesConfiguration) - this.placeHolderConfiguration = config.load("placeholder")!! - config.save("placeholder", this.placeHolderConfiguration) - - this.motdLayoutHandler.loadMotdLayouts() + this.proxyPlugin.motdLayoutHandler.loadMotdLayouts() this.adventure = BungeeAudiences.create(this); + this.proxy.pluginManager.registerListener(this, ProxyPingListener(this)) + this.proxy.pluginManager.registerListener(this, ConfigureTagResolversListener(this)) + this.proxy.pluginManager.registerListener(this, CloudListener(this)) + this.proxy.pluginManager.registerListener(this, ServerPreConnectListener(this)) - if (this.tabListConfiguration.tabListUpdateTime > 0) + if (this.proxyPlugin.tabListConfiguration.tabListUpdateTime > 0) this.tabListHandler.startTabListTask() else this.logger.info("Tablist update time is set to 0, tablist will not be updated automatically") - this.proxy.pluginManager.registerListener(this, TabListListener(this)) - this.proxy.pluginManager.registerListener(this, ProxyPingListener(this)) - this.proxy.pluginManager.registerListener(this, ConfigureTagResolversListener(this)) + val executionCoordinator = ExecutionCoordinator.simpleCoordinator() + + val senderMapper = SenderMapper.create( + { commandSender -> BungeeCordCommandSender(commandSender) }, + { cloudSender -> (cloudSender as BungeeCordCommandSender).getCommandSender() } + ) + + commandManager = BungeeCommandManager( + this, + executionCoordinator, + senderMapper + ) + + val proxyCommandHandler = ProxyCommandHandler(commandManager, this.proxyPlugin) + proxyCommandHandler.loadCommands() + + System.getenv("SIMPLECLOUD_MAINTENANCE")?.let { + this.proxyPlugin.maintenance = it == "true" + } } override fun onDisable() { diff --git a/proxy-bungeecord/src/main/kotlin/app/simplecloud/plugin/proxy/bungeecord/handler/TabListHandler.kt b/proxy-bungeecord/src/main/kotlin/app/simplecloud/plugin/proxy/bungeecord/handler/TabListHandler.kt index 9632995..750d0ea 100644 --- a/proxy-bungeecord/src/main/kotlin/app/simplecloud/plugin/proxy/bungeecord/handler/TabListHandler.kt +++ b/proxy-bungeecord/src/main/kotlin/app/simplecloud/plugin/proxy/bungeecord/handler/TabListHandler.kt @@ -7,7 +7,6 @@ import net.md_5.bungee.api.connection.ProxiedPlayer import net.md_5.bungee.api.scheduler.ScheduledTask import java.util.concurrent.TimeUnit - class TabListHandler( private val plugin: ProxyBungeeCordPlugin ) { @@ -24,7 +23,7 @@ class TabListHandler( this.tabListIndex.forEach { (key, value) -> this.tabListIndex[key] = value + 1 } - }, 1, this.plugin.tabListConfiguration.tabListUpdateTime, TimeUnit.MILLISECONDS) + }, 1, this.plugin.proxyPlugin.tabListConfiguration.tabListUpdateTime, TimeUnit.MILLISECONDS) } fun stopTabListTask() { @@ -37,7 +36,7 @@ class TabListHandler( } fun updateTabListForPlayer(player: ProxiedPlayer) { - val configuration = plugin.tabListConfiguration + val configuration = plugin.proxyPlugin.tabListConfiguration val serviceName = player.server.info.name diff --git a/proxy-bungeecord/src/main/kotlin/app/simplecloud/plugin/proxy/bungeecord/listener/CloudListener.kt b/proxy-bungeecord/src/main/kotlin/app/simplecloud/plugin/proxy/bungeecord/listener/CloudListener.kt new file mode 100644 index 0000000..40448e4 --- /dev/null +++ b/proxy-bungeecord/src/main/kotlin/app/simplecloud/plugin/proxy/bungeecord/listener/CloudListener.kt @@ -0,0 +1,67 @@ +package app.simplecloud.plugin.proxy.bungeecord.listener + +import app.simplecloud.event.bungeecord.mapping.CloudServerUpdateEvent +import app.simplecloud.plugin.proxy.bungeecord.ProxyBungeeCordPlugin +import app.simplecloud.plugin.proxy.shared.handler.MotdLayoutHandler +import net.md_5.bungee.api.plugin.Listener +import net.md_5.bungee.event.EventHandler +import java.util.logging.Logger + +class CloudListener( + private val plugin: ProxyBungeeCordPlugin +) : Listener { + + private val logger = Logger.getLogger(CloudListener::class.java.name) + + @EventHandler + fun test(event: CloudServerUpdateEvent) { + + if (event.getTo().uniqueId != System.getenv("SIMPLECLOUD_UNIQUE_ID")) return + + checkMaintenanceChance(event) + checkLayoutMaintenanceChance(event) + checkLayoutChance(event) + } + + private fun checkMaintenanceChance(event: CloudServerUpdateEvent) { + val isMaintenance = event.getTo().properties["maintenance"] + + if (isMaintenance == event.getFrom().properties["maintenance"]) return + + val newMaintenanceState = isMaintenance == "true" + + if (this.plugin.proxyPlugin.maintenance == newMaintenanceState) return + + this.plugin.proxyPlugin.maintenance = newMaintenanceState + + this.logger.info("Maintenance mode has been toggled to $newMaintenanceState") + } + + private fun checkLayoutMaintenanceChance(event: CloudServerUpdateEvent) { + val layout = event.getTo().properties[MotdLayoutHandler.CURRENT_MAINTENANCE_LAYOUT_KEY] + + if (layout == event.getFrom().properties[MotdLayoutHandler.CURRENT_MAINTENANCE_LAYOUT_KEY]) return + + val newLayout = layout ?: MotdLayoutHandler.DEFAULT_MAINTENANCE_LAYOUT_NAME + + if (MotdLayoutHandler.CURRENT_MAINTENANCE_LAYOUT_KEY == newLayout) return + + MotdLayoutHandler.CURRENT_MAINTENANCE_LAYOUT_KEY = newLayout + + this.logger.info("Layout has been changed to $newLayout") + } + + private fun checkLayoutChance(event: CloudServerUpdateEvent) { + val layout = event.getTo().properties[MotdLayoutHandler.CURRENT_LAYOUT_KEY] + + if (layout == event.getFrom().properties[MotdLayoutHandler.CURRENT_LAYOUT_KEY]) return + + val newLayout = layout ?: MotdLayoutHandler.DEFAULT_LAYOUT_NAME + + if (MotdLayoutHandler.CURRENT_LAYOUT_KEY == newLayout) return + + MotdLayoutHandler.CURRENT_LAYOUT_KEY = newLayout + + this.logger.info("Layout has been changed to $newLayout") + } +} \ No newline at end of file diff --git a/proxy-bungeecord/src/main/kotlin/app/simplecloud/plugin/proxy/bungeecord/listener/ConfigureTagResolversListener.kt b/proxy-bungeecord/src/main/kotlin/app/simplecloud/plugin/proxy/bungeecord/listener/ConfigureTagResolversListener.kt index cabdb40..b134f1e 100644 --- a/proxy-bungeecord/src/main/kotlin/app/simplecloud/plugin/proxy/bungeecord/listener/ConfigureTagResolversListener.kt +++ b/proxy-bungeecord/src/main/kotlin/app/simplecloud/plugin/proxy/bungeecord/listener/ConfigureTagResolversListener.kt @@ -3,9 +3,11 @@ package app.simplecloud.plugin.proxy.bungeecord.listener import app.simplecloud.plugin.proxy.bungeecord.ProxyBungeeCordPlugin import app.simplecloud.plugin.proxy.bungeecord.event.ConfigureTagResolversEvent import app.simplecloud.plugin.proxy.shared.resolver.TagResolverHelper +import kotlinx.coroutines.runBlocking import net.md_5.bungee.api.plugin.Listener import net.md_5.bungee.event.EventHandler import net.md_5.bungee.event.EventPriority +import kotlin.jvm.optionals.getOrNull class ConfigureTagResolversListener( private val plugin: ProxyBungeeCordPlugin @@ -13,25 +15,28 @@ class ConfigureTagResolversListener( @EventHandler(priority = EventPriority.LOWEST) fun onConfigureTagResolvers(event: ConfigureTagResolversEvent) { - val player = event.player - val serverName = player?.server?.info?.name ?: "unknown" - - val ping = player?.ping?.toLong() ?: -1 - val pingColors = plugin.placeHolderConfiguration.pingColors - - val onlinePlayers = this.plugin.proxy.players.size - val realMaxPlayers = this.plugin.proxy.config.listeners.sumOf { it.maxPlayers } - - event.withTagResolvers( - TagResolverHelper.getDefaultTagResolvers( - serverName, - ping, - pingColors, - onlinePlayers, - realMaxPlayers, - this.plugin.motdLayoutHandler.getCurrentMotdLayout() + + runBlocking { + val player = event.player + val serverName = player?.server?.info?.name ?: "unknown" + + val ping = player?.ping ?: -1 + val pingColors = plugin.proxyPlugin.placeHolderConfiguration.pingColors + + val onlinePlayers = plugin.proxy.players.size + val realMaxPlayers = plugin.proxy.config.playerLimit + + event.withTagResolvers( + TagResolverHelper.getDefaultTagResolvers( + serverName, + ping.toLong(), + pingColors, + onlinePlayers, + realMaxPlayers, + plugin.proxyPlugin.motdLayoutHandler.getCurrentMotdLayout() + ) ) - ) + } } } diff --git a/proxy-bungeecord/src/main/kotlin/app/simplecloud/plugin/proxy/bungeecord/listener/ProxyPingListener.kt b/proxy-bungeecord/src/main/kotlin/app/simplecloud/plugin/proxy/bungeecord/listener/ProxyPingListener.kt index 5756358..37463eb 100644 --- a/proxy-bungeecord/src/main/kotlin/app/simplecloud/plugin/proxy/bungeecord/listener/ProxyPingListener.kt +++ b/proxy-bungeecord/src/main/kotlin/app/simplecloud/plugin/proxy/bungeecord/listener/ProxyPingListener.kt @@ -2,6 +2,7 @@ package app.simplecloud.plugin.proxy.bungeecord.listener import app.simplecloud.plugin.proxy.bungeecord.ProxyBungeeCordPlugin import app.simplecloud.plugin.proxy.shared.config.motd.MaxPlayerDisplayType +import kotlinx.coroutines.runBlocking import net.kyori.adventure.text.Component import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer import net.md_5.bungee.api.Favicon @@ -11,10 +12,12 @@ import net.md_5.bungee.api.plugin.Listener import net.md_5.bungee.event.EventHandler import java.awt.image.BufferedImage import java.io.File +import java.io.IOException import java.net.InetAddress import java.net.InetSocketAddress import java.util.* import javax.imageio.ImageIO +import kotlin.jvm.optionals.getOrNull class ProxyPingListener( private val plugin: ProxyBungeeCordPlugin @@ -22,62 +25,64 @@ class ProxyPingListener( @EventHandler fun onPing(event: ProxyPingEvent) { - val socketAddress = event.connection.socketAddress as? InetSocketAddress - val hostStringFromConnection = socketAddress?.address?.hostName?: "" - val hostStringFromServer = InetAddress.getLocalHost().hostAddress - - if (hostStringFromConnection == hostStringFromServer) { - return + runBlocking { + val socketAddress = event.connection.socketAddress as? InetSocketAddress + val hostStringFromConnection = socketAddress?.address?.hostName ?: "" + val hostStringFromServer = InetAddress.getLocalHost().hostAddress + + if (hostStringFromConnection == hostStringFromServer) { + return@runBlocking + } + + val response = event.response + + val motdConfiguration = plugin.proxyPlugin.motdLayoutHandler.getCurrentMotdLayout() + + val firstLine = motdConfiguration.firstLines.random() + val secondLine = motdConfiguration.secondLines.random() + + val messageOfTheDay = plugin.deserializeToComponent("$firstLine\n$secondLine") + + response.descriptionComponent = BungeeComponentSerializer.get().serialize(messageOfTheDay)[0] + + val playerList = motdConfiguration.playerInfo.map { PlayerInfo(it, UUID.randomUUID()) } + response.players = when (motdConfiguration.maxPlayerDisplayType) { + null -> Players( + response.players.max, + response.players.online, + playerList.toTypedArray().ifEmpty { response.players.sample } + ) + + MaxPlayerDisplayType.REAL -> Players( + response.players.max, + response.players.online, + playerList.toTypedArray().ifEmpty { response.players.sample } + ) + + else -> Players( + response.players.online + motdConfiguration.dynamicPlayerRange, + response.players.online, + playerList.toTypedArray().ifEmpty { response.players.sample } + ) + } + + response.version = when (motdConfiguration.versionName) { + "" -> response.version + else -> Protocol( + motdConfiguration.versionName, + -1 + ) + } + + val favicon = if (motdConfiguration.serverIcon == "") { + response.faviconObject + } else { + val serverIcon: BufferedImage = ImageIO.read(File(motdConfiguration.serverIcon)) + Favicon.create(serverIcon) + } + + response.setFavicon(favicon) } - - val response = event.response - - val motdConfiguration = this.plugin.motdLayoutHandler.getCurrentMotdLayout() - - val firstLine = motdConfiguration.firstLines.random() - val secondLine = motdConfiguration.secondLines.random() - - val messageOfTheDay: Component = this.plugin.deserializeToComponent(firstLine + "\n" + secondLine) - - response.descriptionComponent = BungeeComponentSerializer.get().serialize(messageOfTheDay)[0] - - val playerList = motdConfiguration.playerInfo.map { PlayerInfo(it, UUID.randomUUID()) } - response.players = when (motdConfiguration.maxPlayerDisplayType) { - null -> Players( - response.players.max, - response.players.online, - playerList.toTypedArray().ifEmpty { response.players.sample } - ) - - MaxPlayerDisplayType.REAL -> Players( - response.players.max, - response.players.online, - playerList.toTypedArray().ifEmpty { response.players.sample } - ) - - else -> Players( - response.players.online + motdConfiguration.dynamicPlayerRange, - response.players.online, - playerList.toTypedArray().ifEmpty { response.players.sample } - ) - } - - response.version = when (motdConfiguration.versionName) { - "" -> response.version - else -> Protocol( - motdConfiguration.versionName, - -1 - ) - } - - val favicon = if (motdConfiguration.serverIcon == "") { - response.faviconObject - } else { - val serverIcon: BufferedImage = ImageIO.read(File(motdConfiguration.serverIcon)) - Favicon.create(serverIcon) - } - - response.setFavicon(favicon) } } \ No newline at end of file diff --git a/proxy-bungeecord/src/main/kotlin/app/simplecloud/plugin/proxy/bungeecord/listener/ServerPreConnectListener.kt b/proxy-bungeecord/src/main/kotlin/app/simplecloud/plugin/proxy/bungeecord/listener/ServerPreConnectListener.kt new file mode 100644 index 0000000..8458a33 --- /dev/null +++ b/proxy-bungeecord/src/main/kotlin/app/simplecloud/plugin/proxy/bungeecord/listener/ServerPreConnectListener.kt @@ -0,0 +1,55 @@ +package app.simplecloud.plugin.proxy.bungeecord.listener + +import app.simplecloud.plugin.proxy.bungeecord.ProxyBungeeCordPlugin +import app.simplecloud.plugin.proxy.bungeecord.toBaseComponent +import app.simplecloud.plugin.proxy.shared.ProxyPlugin +import kotlinx.coroutines.runBlocking +import net.md_5.bungee.api.connection.ProxiedPlayer +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 +import java.util.logging.Logger + +class ServerPreConnectListener( + private val proxyPlugin: ProxyBungeeCordPlugin, +): Listener { + private val logger = Logger.getLogger(ServerPreConnectListener::class.java.name) + + @EventHandler(priority = EventPriority.HIGH) + fun handle(event: ServerConnectEvent) { + val player = event.player + + if (proxyPlugin.proxyPlugin.maintenance && !player.hasPermission(ProxyPlugin.JOIN_MAINTENANCE_PERMISSION)) { + denyAccess( + player, + this.proxyPlugin.proxyPlugin.messagesConfiguration.kickMessage.networkMaintenance, + event + ) + return + } + + runBlocking { + try { + if (!isServerFull(player)) { + return@runBlocking + } + denyAccess(player, proxyPlugin.proxyPlugin.messagesConfiguration.kickMessage.networkFull, event) + } catch (e: Exception) { + logger.severe("Error checking player limits: ${e.message}") + } + } + } + + private suspend fun isServerFull(player: ProxiedPlayer): Boolean { + val maxPlayers = proxyPlugin.proxyPlugin.cloudControllerHandler.getMaxPlayersInGroup() + val onlinePlayers = proxyPlugin.proxyPlugin.cloudControllerHandler.getOnlinePlayersInGroup() + + return onlinePlayers >= maxPlayers && !player.hasPermission(ProxyPlugin.JOIN_FULL_PERMISSION) + } + + private fun denyAccess(player: ProxiedPlayer, message: String, event: ServerConnectEvent) { + player.disconnect(proxyPlugin.deserializeToComponent(message, player).toBaseComponent()) + event.isCancelled = true + } +} diff --git a/proxy-shared/build.gradle.kts b/proxy-shared/build.gradle.kts index 1ba8b0a..19c6aa3 100644 --- a/proxy-shared/build.gradle.kts +++ b/proxy-shared/build.gradle.kts @@ -1,8 +1,12 @@ dependencies { compileOnly(rootProject.libs.gson) - compileOnly(rootProject.libs.adventureMinimessage) + compileOnly(rootProject.libs.adventure.minimessage) - implementation(rootProject.libs.configurateYaml) - implementation(rootProject.libs.configurateKotlin) + implementation(rootProject.libs.configurate.yaml) + implementation(rootProject.libs.configurate.kotlin) + + compileOnly(rootProject.libs.simplecloud.controller) + + compileOnly(rootProject.libs.command.cloud.core) } \ No newline at end of file diff --git a/proxy-shared/src/main/kotlin/app/simplecloud/plugin/proxy/shared/ProxyPlugin.kt b/proxy-shared/src/main/kotlin/app/simplecloud/plugin/proxy/shared/ProxyPlugin.kt new file mode 100644 index 0000000..5d9137c --- /dev/null +++ b/proxy-shared/src/main/kotlin/app/simplecloud/plugin/proxy/shared/ProxyPlugin.kt @@ -0,0 +1,27 @@ +package app.simplecloud.plugin.proxy.shared + +import app.simplecloud.plugin.proxy.shared.config.YamlConfig +import app.simplecloud.plugin.proxy.shared.config.message.MessageConfig +import app.simplecloud.plugin.proxy.shared.config.placeholder.PlaceHolderConfiguration +import app.simplecloud.plugin.proxy.shared.config.tablis.TabListConfiguration +import app.simplecloud.plugin.proxy.shared.handler.CloudControllerHandler +import app.simplecloud.plugin.proxy.shared.handler.MotdLayoutHandler + +open class ProxyPlugin( + dirPath: String +) { + + val config = YamlConfig(dirPath) + val tabListConfiguration = config.load("tablist")!! + val placeHolderConfiguration = config.load("placeholder")!! + val messagesConfiguration = config.load("messages")!! + val cloudControllerHandler = CloudControllerHandler() + val motdLayoutHandler = MotdLayoutHandler(config, this) + + var maintenance = true + + companion object { + val JOIN_MAINTENANCE_PERMISSION = "simplecloud.proxy.join.maintenance" + val JOIN_FULL_PERMISSION = "simplecloud.proxy.join.full" + } +} \ No newline at end of file diff --git a/proxy-shared/src/main/kotlin/app/simplecloud/plugin/proxy/shared/config/GeneralConfig.kt b/proxy-shared/src/main/kotlin/app/simplecloud/plugin/proxy/shared/config/GeneralConfig.kt deleted file mode 100644 index 59f879e..0000000 --- a/proxy-shared/src/main/kotlin/app/simplecloud/plugin/proxy/shared/config/GeneralConfig.kt +++ /dev/null @@ -1,8 +0,0 @@ -package app.simplecloud.plugin.proxy.shared.config - -import org.spongepowered.configurate.objectmapping.ConfigSerializable - -@ConfigSerializable -data class GeneralConfig( - var currentLayout: String = "default-motd" -) \ No newline at end of file diff --git a/proxy-shared/src/main/kotlin/app/simplecloud/plugin/proxy/shared/config/message/CommandMessageConfig.kt b/proxy-shared/src/main/kotlin/app/simplecloud/plugin/proxy/shared/config/message/CommandMessageConfig.kt new file mode 100644 index 0000000..8d76efe --- /dev/null +++ b/proxy-shared/src/main/kotlin/app/simplecloud/plugin/proxy/shared/config/message/CommandMessageConfig.kt @@ -0,0 +1,17 @@ +package app.simplecloud.plugin.proxy.shared.config.message + +import org.spongepowered.configurate.objectmapping.ConfigSerializable + +@ConfigSerializable +data class CommandMessageConfig( + val toggleMaintenance: String = "You have toggled the maintenance mode.", + val activateMaintenance: String = "You have activated the maintenance mode.", + val maintenanceAlreadyActivated: String = "The maintenance mode is already activated.", + val deactivateMaintenance: String = "You have deactivated the maintenance mode.", + val maintenanceAlreadyDeactivated: String = "The maintenance mode is already deactivated.", + val layoutNotFound: String = "The layout could not be found.", + val layoutMaintenanceAlreadySet: String = "The layout is already set for maintenance.", + val layoutMaintenanceSet: String = "The layout has been set for maintenance.", + val layoutNonMaintenanceAlreadySet: String = "The layout is already set for non-maintenance.", + val layoutNonMaintenanceSet: String = "The layout has been set for non-maintenance." +) \ No newline at end of file diff --git a/proxy-shared/src/main/kotlin/app/simplecloud/plugin/proxy/shared/config/message/KickMessageConfig.kt b/proxy-shared/src/main/kotlin/app/simplecloud/plugin/proxy/shared/config/message/KickMessageConfig.kt new file mode 100644 index 0000000..4e57cfc --- /dev/null +++ b/proxy-shared/src/main/kotlin/app/simplecloud/plugin/proxy/shared/config/message/KickMessageConfig.kt @@ -0,0 +1,9 @@ +package app.simplecloud.plugin.proxy.shared.config.message + +import org.spongepowered.configurate.objectmapping.ConfigSerializable + +@ConfigSerializable +data class KickMessageConfig( + val networkMaintenance: String = "The network is currently in maintenance mode. Please try again later.", + val networkFull: String = "The network is currently full. Please try again later.", +) \ No newline at end of file diff --git a/proxy-shared/src/main/kotlin/app/simplecloud/plugin/proxy/shared/config/message/MessageConfig.kt b/proxy-shared/src/main/kotlin/app/simplecloud/plugin/proxy/shared/config/message/MessageConfig.kt new file mode 100644 index 0000000..ef46d5f --- /dev/null +++ b/proxy-shared/src/main/kotlin/app/simplecloud/plugin/proxy/shared/config/message/MessageConfig.kt @@ -0,0 +1,9 @@ +package app.simplecloud.plugin.proxy.shared.config.message + +import org.spongepowered.configurate.objectmapping.ConfigSerializable + +@ConfigSerializable +data class MessageConfig( + var kickMessage: KickMessageConfig = KickMessageConfig(), + var commandMessage: CommandMessageConfig = CommandMessageConfig() +) \ No newline at end of file diff --git a/proxy-shared/src/main/kotlin/app/simplecloud/plugin/proxy/shared/config/motd/MotdLayoutConfiguration.kt b/proxy-shared/src/main/kotlin/app/simplecloud/plugin/proxy/shared/config/motd/MotdLayoutConfiguration.kt index 22e7f45..5a02064 100644 --- a/proxy-shared/src/main/kotlin/app/simplecloud/plugin/proxy/shared/config/motd/MotdLayoutConfiguration.kt +++ b/proxy-shared/src/main/kotlin/app/simplecloud/plugin/proxy/shared/config/motd/MotdLayoutConfiguration.kt @@ -5,8 +5,8 @@ import java.awt.image.BufferedImage @ConfigSerializable data class MotdLayoutConfiguration( - val firstLines: List = listOf("SimpleCloud » Simplify your network |<#4595ff> 1.12<#545454> - <#4595ff>1.20"), - val secondLines: List = listOf("× <#178fff>Status: <#22cc22>Online - <#ffffff>%PROXY%"), + val firstLines: List = listOf("A simplecloud.app network"), + val secondLines: List = listOf("ʀᴇᴀᴅ ᴅᴏᴄs.sɪᴍᴘʟᴇᴄʟᴏᴜᴅ.ᴀᴘᴘ ᴛᴏ ᴄᴏɴғɪɢᴜʀᴇ"), val playerInfo: List = listOf(), val versionName: String = "", var serverIcon: String = "server-icon.png", diff --git a/proxy-shared/src/main/kotlin/app/simplecloud/plugin/proxy/shared/config/tablis/TabList.kt b/proxy-shared/src/main/kotlin/app/simplecloud/plugin/proxy/shared/config/tablis/TabList.kt index 87dd905..5a73412 100644 --- a/proxy-shared/src/main/kotlin/app/simplecloud/plugin/proxy/shared/config/tablis/TabList.kt +++ b/proxy-shared/src/main/kotlin/app/simplecloud/plugin/proxy/shared/config/tablis/TabList.kt @@ -5,15 +5,15 @@ import org.spongepowered.configurate.objectmapping.ConfigSerializable @ConfigSerializable data class TabList( val header: List = listOf( - " ", - "SimpleCloud » Simplify your network", - "Online » / Server » ", - "" + "", + "SimpleCloud v3", + " Customize header and footer in the config! ", + "" ), val footer: List = listOf( - "", - "Twitter » @theSimpleCloud", - "Discord » discord.simplecloud.app", - "" + "", + " players are playing on your network ", + " sɪᴍᴘʟᴇᴄʟᴏᴜᴅ.ᴀᴘᴘ", + "" ), ) diff --git a/proxy-shared/src/main/kotlin/app/simplecloud/plugin/proxy/shared/handler/CloudControllerHandler.kt b/proxy-shared/src/main/kotlin/app/simplecloud/plugin/proxy/shared/handler/CloudControllerHandler.kt new file mode 100644 index 0000000..1fd07c7 --- /dev/null +++ b/proxy-shared/src/main/kotlin/app/simplecloud/plugin/proxy/shared/handler/CloudControllerHandler.kt @@ -0,0 +1,127 @@ +package app.simplecloud.plugin.proxy.shared.handler + +import app.simplecloud.controller.api.ControllerApi +import kotlinx.coroutines.runBlocking +import java.util.logging.Logger + +class CloudControllerHandler { + + private val controllerApi = ControllerApi.createCoroutineApi() + private var groupName: String? = null + private val logger = Logger.getLogger(CloudControllerHandler::class.java.name) + + init { + initializeGroupName() + } + + private fun initializeGroupName() { + val uniqueId = System.getenv("SIMPLECLOUD_UNIQUE_ID") + + if (uniqueId == null) { + logger.warning("Environment variable SIMPLECLOUD_UNIQUE_ID is not set.") + return + } + + runBlocking { + try { + val service = controllerApi.getServers().getServerById(uniqueId) + groupName = service.group + logger.info("Group name initialized to: $groupName") + } catch (e: Exception) { + logger.severe("Error retrieving server by ID: ${e.message}") + } + } + } + + suspend fun getServiceProperties(key: String): String { + return retrievePropertyOrEmpty { + val uniqueId = System.getenv("SIMPLECLOUD_UNIQUE_ID") + controllerApi.getServers().getServerById(uniqueId).properties[key] + } + } + + suspend fun getGroupProperties(key: String): String { + return groupName?.let { + retrievePropertyOrEmpty { + controllerApi.getGroups().getGroupByName(it).properties[key] + } + } ?: run { + logger.warning("Group name is not initialized.") + "" + } + } + + suspend fun setServiceProperties(key: String, value: String) { + val uniqueId = System.getenv("SIMPLECLOUD_UNIQUE_ID") + + try { + controllerApi.getServers().updateServerProperty(uniqueId, key, value) + logger.info("Service property '$key' updated to '$value'") + } catch (e: Exception) { + logger.severe("Error updating service properties: ${e.message}") + } + } + + suspend fun setServicePropertiesOnAllGroupServices(key: String, value: String) { + groupName?.let { name -> + try { + controllerApi.getServers().getServersByGroup(name).forEach { server -> + controllerApi.getServers().updateServerProperty(server.uniqueId, key, value) + } + logger.info("Service property '$key' updated to '$value' on all services in group '$name'") + } catch (e: Exception) { + logger.severe("Error updating service properties on all group services: ${e.message}") + } + } ?: logger.warning("Group name is not initialized.") + } + + suspend fun setGroupProperties(key: String, value: String) { + groupName?.let { name -> + try { + val group = controllerApi.getGroups().getGroupByName(name) + val updatedGroup = group.copy(properties = group.properties + (key to value)) + controllerApi.getGroups().updateGroup(updatedGroup) + logger.info("Group property '$key' updated to '$value'") + } catch (e: Exception) { + logger.severe("Error updating group properties: ${e.message}") + } + } ?: logger.warning("Group name is not initialized.") + } + + suspend fun getOnlinePlayersInGroup(): Int { + return groupName?.let { + try { + controllerApi.getServers().getServersByGroup(it).sumBy { it.playerCount.toInt() } + } catch (e: Exception) { + logger.severe("Error retrieving online players in group: ${e.message}") + 0 + } + } ?: run { + logger.warning("Group name is not initialized.") + 0 + } + } + + suspend fun getMaxPlayersInGroup(): Int { + return groupName?.let { + try { + controllerApi.getGroups().getGroupByName(it).maxPlayers.toInt() + } catch (e: Exception) { + logger.severe("Error retrieving max players in group: ${e.message}") + 0 + } + } ?: run { + logger.warning("Group name is not initialized.") + 0 + } + } + + private suspend fun retrievePropertyOrEmpty(retrieve: suspend () -> String?): String { + return try { + retrieve() ?: "" + } catch (e: Exception) { + logger.severe("Error retrieving property: ${e.message}") + "" + } + } +} diff --git a/proxy-shared/src/main/kotlin/app/simplecloud/plugin/proxy/shared/handler/MotdLayoutHandler.kt b/proxy-shared/src/main/kotlin/app/simplecloud/plugin/proxy/shared/handler/MotdLayoutHandler.kt index 0139b4a..ceee4a1 100644 --- a/proxy-shared/src/main/kotlin/app/simplecloud/plugin/proxy/shared/handler/MotdLayoutHandler.kt +++ b/proxy-shared/src/main/kotlin/app/simplecloud/plugin/proxy/shared/handler/MotdLayoutHandler.kt @@ -1,77 +1,123 @@ package app.simplecloud.plugin.proxy.shared.handler -import app.simplecloud.plugin.proxy.shared.config.GeneralConfig +import app.simplecloud.plugin.proxy.shared.ProxyPlugin import app.simplecloud.plugin.proxy.shared.config.YamlConfig import app.simplecloud.plugin.proxy.shared.config.motd.MotdLayoutConfiguration -import java.awt.image.BufferedImage -import java.io.ByteArrayOutputStream +import kotlinx.coroutines.runBlocking import java.io.File -import java.util.* -import javax.imageio.ImageIO - +import java.util.logging.Logger class MotdLayoutHandler( - val yamlConfig: YamlConfig, - val generalConfig: GeneralConfig + private val yamlConfig: YamlConfig, + private val proxyPlugin: ProxyPlugin ) { + private val cloudControllerHandler = proxyPlugin.cloudControllerHandler private val loadedMotdLayouts: MutableMap = mutableMapOf() + private val logger = Logger.getLogger(MotdLayoutHandler::class.java.name) + + companion object { + private const val RANDOM_MOTD_KEY = "random-motd-layouts" + var CURRENT_LAYOUT_KEY = "current-motd-layout" + var CURRENT_MAINTENANCE_LAYOUT_KEY = "current-maintenance-layout" + const val DEFAULT_LAYOUT_NAME = "default-motd" + const val DEFAULT_MAINTENANCE_LAYOUT_NAME = "default-maintenance-motd" + } fun loadMotdLayouts() { loadedMotdLayouts.clear() - val directory = File(yamlConfig.dirPath + "/layout") + initializeLayoutDirectory() + loadLayoutsFromDirectory() + createDefaultLayoutsIfEmpty() + initializeCloudProperties() + } + private fun initializeLayoutDirectory() { + val directory = File(yamlConfig.dirPath + "/layout") if (!directory.exists()) { directory.mkdirs() } + } - directory.listFiles()?.forEach { - val name = it.nameWithoutExtension - val motdLayout = yamlConfig.load("layout/$name") - if (motdLayout != null) { - //motdLayout.serverIcon = convirtImageFileToBase64Url(File(motdLayout.serverIcon)) - loadedMotdLayouts[name] = motdLayout - println("Loaded motd layout: $name") + private fun loadLayoutsFromDirectory() { + File(yamlConfig.dirPath + "/layout").listFiles()?.forEach { file -> + yamlConfig.load("layout/${file.nameWithoutExtension}")?.let { layout -> + loadedMotdLayouts[file.nameWithoutExtension] = layout + logger.info("Loaded MOTD layout: ${file.nameWithoutExtension}") } } + } - if (loadedMotdLayouts.isEmpty()) { - println("No motd layouts found, creating default motd layout") - val defaultMotdLayout = MotdLayoutConfiguration() - yamlConfig.save("layout/default-motd", defaultMotdLayout) - loadedMotdLayouts["default-motd"] = defaultMotdLayout + private fun createDefaultLayoutsIfEmpty() { + if (getAllNoneMaintenanceLayouts().isEmpty()) { + createAndSaveDefaultLayout(DEFAULT_LAYOUT_NAME) + } + if (getAllMaintenanceLayouts().isEmpty()) { + createAndSaveDefaultLayout(DEFAULT_MAINTENANCE_LAYOUT_NAME) } } - fun getMotdLayout(name: String): MotdLayoutConfiguration { - return loadedMotdLayouts[name] ?: MotdLayoutConfiguration() + private fun createAndSaveDefaultLayout(layoutName: String) { + val defaultLayout = MotdLayoutConfiguration() + yamlConfig.save("layout/$layoutName", defaultLayout) + loadedMotdLayouts[layoutName] = defaultLayout + logger.info("Created and saved default layout: $layoutName") } - fun getCurrentMotdLayout(): MotdLayoutConfiguration { - return getMotdLayout(generalConfig.currentLayout) + private fun initializeCloudProperties() = runBlocking { + with(cloudControllerHandler) { + if (getServiceProperties(RANDOM_MOTD_KEY).isEmpty()) { + setServiceProperties(RANDOM_MOTD_KEY, "false") + setGroupProperties(RANDOM_MOTD_KEY, "false") + logger.info("No random MOTD layout key found in service properties, setting to false.") + } + + if (getServiceProperties(CURRENT_LAYOUT_KEY).isEmpty()) { + setServiceProperties(CURRENT_LAYOUT_KEY, getAllNoneMaintenanceLayouts().first()) + setGroupProperties(CURRENT_LAYOUT_KEY, getAllNoneMaintenanceLayouts().first()) + logger.info("No current MOTD layout key found in service properties, setting to first layout.") + } + + if (getServiceProperties(CURRENT_MAINTENANCE_LAYOUT_KEY).isEmpty()) { + setServiceProperties(CURRENT_MAINTENANCE_LAYOUT_KEY, getAllMaintenanceLayouts().first()) + setGroupProperties(CURRENT_MAINTENANCE_LAYOUT_KEY, getAllMaintenanceLayouts().first()) + logger.info("No current maintenance MOTD layout key found in service properties, setting to first layout.") + } + } } - fun getLoadedMotdLayouts(): List { - return loadedMotdLayouts.keys.toList() + fun getMotdLayout(name: String): MotdLayoutConfiguration = + loadedMotdLayouts[name] ?: MotdLayoutConfiguration() + + suspend fun getMaintenanceLayout(): MotdLayoutConfiguration { + val maintenanceLayoutName = cloudControllerHandler.getServiceProperties(CURRENT_MAINTENANCE_LAYOUT_KEY).ifEmpty { + logger.warning("No current maintenance layout found, using default maintenance layout as fallback.") + DEFAULT_MAINTENANCE_LAYOUT_NAME + } + return getMotdLayout(maintenanceLayoutName) } - fun setMotdLayout(name: String) { - generalConfig.currentLayout = name - yamlConfig.save("general", generalConfig) + suspend fun getNonMaintenanceLayout(): MotdLayoutConfiguration { + val nonMaintenanceLayoutName = cloudControllerHandler.getServiceProperties(CURRENT_LAYOUT_KEY).ifEmpty { + logger.warning("No current non-maintenance layout found, using default layout as fallback.") + DEFAULT_LAYOUT_NAME + } + return getMotdLayout(nonMaintenanceLayoutName) } - /*fun convirtImageFileToBase64Url(imageFile: File): String { - /*if (!imageFile.exists()) { - println("Image file not found: ${imageFile.absolutePath}") - return "" + suspend fun getCurrentMotdLayout(): MotdLayoutConfiguration { + val layouts = if (proxyPlugin.maintenance) getAllMaintenanceLayouts() else getAllNoneMaintenanceLayouts() + val useRandomLayouts = cloudControllerHandler.getServiceProperties(RANDOM_MOTD_KEY).toBoolean() + val currentLayoutKey = if (proxyPlugin.maintenance) CURRENT_MAINTENANCE_LAYOUT_KEY else CURRENT_LAYOUT_KEY + val selectedLayout = cloudControllerHandler.getServiceProperties(currentLayoutKey).ifEmpty { + logger.warning("No current layout found, using random layout as fallback.") + layouts.firstOrNull() ?: DEFAULT_LAYOUT_NAME } - val serverIcon: BufferedImage = ImageIO.read(imageFile) - val outputStream = ByteArrayOutputStream() - ImageIO.write(serverIcon, "png", outputStream) - val iconBytes = outputStream.toByteArray() - return Base64.getEncoder().encodeToString(iconBytes) - */ - return imageFile.name - }*/ -} \ No newline at end of file + return getMotdLayout(if (useRandomLayouts) layouts.random() else selectedLayout) + } + + fun getAllMaintenanceLayouts(): List = loadedMotdLayouts.keys.filter { it.contains("maintenance") } + + fun getAllNoneMaintenanceLayouts(): List = loadedMotdLayouts.keys.filter { !it.contains("maintenance") } +} diff --git a/proxy-shared/src/main/kotlin/app/simplecloud/plugin/proxy/shared/handler/command/CommandSender.kt b/proxy-shared/src/main/kotlin/app/simplecloud/plugin/proxy/shared/handler/command/CommandSender.kt new file mode 100644 index 0000000..32dd79f --- /dev/null +++ b/proxy-shared/src/main/kotlin/app/simplecloud/plugin/proxy/shared/handler/command/CommandSender.kt @@ -0,0 +1,6 @@ +package app.simplecloud.plugin.proxy.shared.handler.command + +interface CommandSender { + + fun sendMessage(message: String) +} \ No newline at end of file diff --git a/proxy-shared/src/main/kotlin/app/simplecloud/plugin/proxy/shared/handler/command/ProxyCommandHandler.kt b/proxy-shared/src/main/kotlin/app/simplecloud/plugin/proxy/shared/handler/command/ProxyCommandHandler.kt new file mode 100644 index 0000000..185eb2b --- /dev/null +++ b/proxy-shared/src/main/kotlin/app/simplecloud/plugin/proxy/shared/handler/command/ProxyCommandHandler.kt @@ -0,0 +1,164 @@ +package app.simplecloud.plugin.proxy.shared.handler.command + +import app.simplecloud.plugin.proxy.shared.ProxyPlugin +import app.simplecloud.plugin.proxy.shared.handler.MotdLayoutHandler +import kotlinx.coroutines.runBlocking +import org.incendo.cloud.CommandManager +import org.incendo.cloud.context.CommandContext +import org.incendo.cloud.parser.standard.StringParser.stringParser +import org.incendo.cloud.suggestion.Suggestion +import org.incendo.cloud.suggestion.SuggestionProvider +import java.util.concurrent.CompletableFuture + +class ProxyCommandHandler( + val commandManager: CommandManager, + val proxyPlugin: ProxyPlugin +) { + + fun loadCommands() { + loadToggleMaintenanceCommand() + loadActivateMaintenanceCommand() + loadDeactivateMaintenanceCommand() + + loadLayoutMaintenanceSetCommand() + loadLayoutNonMaintenanceSetCommand() + } + + private fun loadToggleMaintenanceCommand() { + commandManager.command( + commandManager.commandBuilder("proxy") + .literal("maintenance") + .literal("toggle") + .permission("simplecloud.command.proxy.maintenance.toggle") + .handler { context: CommandContext -> + runBlocking { + val mode = !proxyPlugin.cloudControllerHandler.getGroupProperties("maintenance").toBoolean() + setProxyMaintenanceMode(mode) + context.sender().sendMessage(proxyPlugin.messagesConfiguration.commandMessage.toggleMaintenance) + } + } + .build() + ) + } + + private fun loadActivateMaintenanceCommand() { + commandManager.command( + commandManager.commandBuilder("proxy") + .literal("maintenance") + .literal("activate") + .permission("simplecloud.command.proxy.maintenance.activate") + .handler { context: CommandContext -> + runBlocking { + if (!proxyPlugin.cloudControllerHandler.getGroupProperties("maintenance").toBoolean()) { + setProxyMaintenanceMode(true) + context.sender().sendMessage(proxyPlugin.messagesConfiguration.commandMessage.activateMaintenance) + } else { + context.sender().sendMessage(proxyPlugin.messagesConfiguration.commandMessage.maintenanceAlreadyActivated) + } + } + } + .build() + ) + } + + private fun loadDeactivateMaintenanceCommand() { + commandManager.command( + commandManager.commandBuilder("proxy") + .literal("maintenance") + .literal("deactivate") + .permission("simplecloud.command.proxy.maintenance.deactivate") + .handler { context: CommandContext -> + runBlocking { + if (proxyPlugin.cloudControllerHandler.getGroupProperties("maintenance").toBoolean()) { + setProxyMaintenanceMode(false) + context.sender().sendMessage(proxyPlugin.messagesConfiguration.commandMessage.deactivateMaintenance) + } else { + context.sender().sendMessage(proxyPlugin.messagesConfiguration.commandMessage.maintenanceAlreadyDeactivated) + } + } + } + .build() + ) + } + + private fun loadLayoutMaintenanceSetCommand() { + commandManager.command( + commandManager.commandBuilder("proxy") + .literal("layout") + .literal("maintenance") + .literal("set") + .required( + "layout", + stringParser() + ) { _, _ -> + val suggestionList = proxyPlugin.motdLayoutHandler.getAllMaintenanceLayouts().map { Suggestion.suggestion(it) } + CompletableFuture.completedFuture(suggestionList) + } + .permission("simplecloud.command.proxy.layout.maintenance.set") + .handler { context: CommandContext -> + val layout = context.get("layout") + + if (!proxyPlugin.motdLayoutHandler.getAllMaintenanceLayouts().contains(layout)) { + context.sender().sendMessage(proxyPlugin.messagesConfiguration.commandMessage.layoutNotFound) + return@handler + } + + if (MotdLayoutHandler.CURRENT_MAINTENANCE_LAYOUT_KEY == layout) { + context.sender().sendMessage(proxyPlugin.messagesConfiguration.commandMessage.layoutMaintenanceAlreadySet) + return@handler + } + + runBlocking { + proxyPlugin.cloudControllerHandler.setServicePropertiesOnAllGroupServices(MotdLayoutHandler.CURRENT_MAINTENANCE_LAYOUT_KEY, layout) + proxyPlugin.cloudControllerHandler.setGroupProperties(MotdLayoutHandler.CURRENT_MAINTENANCE_LAYOUT_KEY, layout) + + context.sender().sendMessage(proxyPlugin.messagesConfiguration.commandMessage.layoutMaintenanceSet) + } + } + .build() + ) + } + + private fun loadLayoutNonMaintenanceSetCommand() { + commandManager.command( + commandManager.commandBuilder("proxy") + .literal("layout") + .literal("nonmaintenance") + .literal("set") + .required( + "layout", + stringParser() + ) { _, _ -> + val suggestionList = proxyPlugin.motdLayoutHandler.getAllNoneMaintenanceLayouts().map { Suggestion.suggestion(it) } + CompletableFuture.completedFuture(suggestionList) + } + .permission("simplecloud.command.proxy.layout.nonmaintenance.set") + .handler { context: CommandContext -> + val layout = context.get("layout") + + if (!proxyPlugin.motdLayoutHandler.getAllNoneMaintenanceLayouts().contains(layout)) { + context.sender().sendMessage(proxyPlugin.messagesConfiguration.commandMessage.layoutNotFound) + return@handler + } + + if (MotdLayoutHandler.CURRENT_LAYOUT_KEY == layout) { + context.sender().sendMessage(proxyPlugin.messagesConfiguration.commandMessage.layoutNonMaintenanceAlreadySet) + return@handler + } + + runBlocking { + proxyPlugin.cloudControllerHandler.setServicePropertiesOnAllGroupServices(MotdLayoutHandler.CURRENT_LAYOUT_KEY, layout) + proxyPlugin.cloudControllerHandler.setGroupProperties(MotdLayoutHandler.CURRENT_LAYOUT_KEY, layout) + + context.sender().sendMessage(proxyPlugin.messagesConfiguration.commandMessage.layoutNonMaintenanceSet) + } + } + .build() + ) + } + + private suspend fun setProxyMaintenanceMode(mode: Boolean) { + this.proxyPlugin.cloudControllerHandler.setGroupProperties("maintenance", mode.toString()) + this.proxyPlugin.cloudControllerHandler.setServicePropertiesOnAllGroupServices("maintenance", mode.toString()) + } +} \ No newline at end of file diff --git a/proxy-velocity/build.gradle.kts b/proxy-velocity/build.gradle.kts index ec2315d..a01c7f5 100644 --- a/proxy-velocity/build.gradle.kts +++ b/proxy-velocity/build.gradle.kts @@ -3,12 +3,14 @@ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar dependencies { api(project(":proxy-shared")) - compileOnly(rootProject.libs.adventureMinimessage) + compileOnly(rootProject.libs.adventure.minimessage) - compileOnly(rootProject.libs.velocityApi) - annotationProcessor(rootProject.libs.velocityApi) -} + compileOnly(rootProject.libs.velocity) + annotationProcessor(rootProject.libs.velocity) -tasks.named("shadowJar", ShadowJar::class) { - //relocate("kotlin.", "app.simplecloud.plugin.libs.kotlin.") + compileOnly(rootProject.libs.simplecloud.event.wrapper.velocity) + compileOnly(rootProject.libs.simplecloud.controller) + + implementation(rootProject.libs.command.cloud.core) + implementation(rootProject.libs.command.cloud.velocity) } \ No newline at end of file diff --git a/proxy-velocity/src/main/kotlin/app/simplecloud/plugin/proxy/velocity/ProxyVelocityPlugin.kt b/proxy-velocity/src/main/kotlin/app/simplecloud/plugin/proxy/velocity/ProxyVelocityPlugin.kt index 817c63c..1db1fce 100644 --- a/proxy-velocity/src/main/kotlin/app/simplecloud/plugin/proxy/velocity/ProxyVelocityPlugin.kt +++ b/proxy-velocity/src/main/kotlin/app/simplecloud/plugin/proxy/velocity/ProxyVelocityPlugin.kt @@ -1,23 +1,37 @@ package app.simplecloud.plugin.proxy.velocity -import app.simplecloud.plugin.proxy.shared.config.GeneralConfig +import app.simplecloud.controller.api.ControllerApi +import app.simplecloud.plugin.proxy.shared.ProxyPlugin import app.simplecloud.plugin.proxy.shared.config.YamlConfig import app.simplecloud.plugin.proxy.shared.config.placeholder.PlaceHolderConfiguration import app.simplecloud.plugin.proxy.shared.config.tablis.TabListConfiguration import app.simplecloud.plugin.proxy.shared.handler.MotdLayoutHandler +import app.simplecloud.plugin.proxy.shared.handler.command.CommandSender +import app.simplecloud.plugin.proxy.shared.handler.command.ProxyCommandHandler import app.simplecloud.plugin.proxy.velocity.event.ConfigureTagResolversEvent import app.simplecloud.plugin.proxy.velocity.handler.TabListHandler +import app.simplecloud.plugin.proxy.velocity.listener.CloudListener import app.simplecloud.plugin.proxy.velocity.listener.ConfigureTagResolversListener import app.simplecloud.plugin.proxy.velocity.listener.ProxyPingListener +import app.simplecloud.plugin.proxy.velocity.listener.ServerPreConnectListener import com.google.inject.Inject +import com.velocitypowered.api.command.CommandSource +import com.velocitypowered.api.command.RawCommand +import com.velocitypowered.api.command.SimpleCommand +import com.velocitypowered.api.command.SimpleCommand.Invocation 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.PluginContainer import com.velocitypowered.api.plugin.annotation.DataDirectory import com.velocitypowered.api.proxy.Player import com.velocitypowered.api.proxy.ProxyServer +import kotlinx.coroutines.runBlocking import net.kyori.adventure.text.Component import net.kyori.adventure.text.minimessage.MiniMessage +import org.incendo.cloud.SenderMapper +import org.incendo.cloud.execution.ExecutionCoordinator +import org.incendo.cloud.velocity.VelocityCommandManager import org.slf4j.Logger import java.nio.file.Path import kotlin.io.path.pathString @@ -25,33 +39,53 @@ import kotlin.io.path.pathString class ProxyVelocityPlugin @Inject constructor( val proxyServer: ProxyServer, @DataDirectory val dataDirectory: Path, - val logger: Logger -) { + val logger: Logger, + val pluginContainer: PluginContainer +): ProxyPlugin(dataDirectory.pathString) { val tabListHandler = TabListHandler(this) - private val config = YamlConfig(this.dataDirectory.pathString) - val tabListConfiguration = config.load("tablist")!! - val placeHolderConfiguration = config.load("placeholder")!! - val generalConfig = config.load("general")!! - val motdLayoutHandler = MotdLayoutHandler(config, generalConfig) - + private lateinit var commandManager: VelocityCommandManager private val miniMessage = MiniMessage.miniMessage() @Subscribe fun onProxyInitialize(event: ProxyInitializeEvent) { config.save("tablist", this.tabListConfiguration) config.save("placeholder", this.placeHolderConfiguration) - config.save("general", this.generalConfig) + config.save("messages", this.messagesConfiguration) this.motdLayoutHandler.loadMotdLayouts() this.proxyServer.eventManager.register(this, ProxyPingListener(this)) this.proxyServer.eventManager.register(this, ConfigureTagResolversListener(this)) + this.proxyServer.eventManager.register(this, CloudListener(this)) + this.proxyServer.eventManager.register(this, ServerPreConnectListener(this)) + if (this.tabListConfiguration.tabListUpdateTime > 0) this.tabListHandler.startTabListTask() else this.logger.info("Tablist update time is set to 0, tablist will not be updated automatically") + + val executionCoordinator = ExecutionCoordinator.simpleCoordinator() + + val senderMapper = SenderMapper.create( + { commandSender -> VelocityCommandSender(commandSender, this) }, + { commandSender -> (commandSender as VelocityCommandSender).getCommandSource() } + ) + + commandManager = VelocityCommandManager( + pluginContainer, + proxyServer, + executionCoordinator, + senderMapper + ) + + val proxyCommandHandler = ProxyCommandHandler(commandManager, this) + proxyCommandHandler.loadCommands() + + System.getenv("SIMPLECLOUD_MAINTENANCE")?.let { + this.maintenance = it == "true" + } } @Subscribe diff --git a/proxy-velocity/src/main/kotlin/app/simplecloud/plugin/proxy/velocity/VelocityCommandSender.kt b/proxy-velocity/src/main/kotlin/app/simplecloud/plugin/proxy/velocity/VelocityCommandSender.kt new file mode 100644 index 0000000..a594946 --- /dev/null +++ b/proxy-velocity/src/main/kotlin/app/simplecloud/plugin/proxy/velocity/VelocityCommandSender.kt @@ -0,0 +1,18 @@ +package app.simplecloud.plugin.proxy.velocity + +import app.simplecloud.plugin.proxy.shared.handler.command.CommandSender +import com.velocitypowered.api.command.CommandSource + +class VelocityCommandSender( + private val commandSource: CommandSource, + val proxyVelocityPlugin: ProxyVelocityPlugin +) : CommandSender { + + fun getCommandSource(): CommandSource { + return commandSource + } + + override fun sendMessage(message: String) { + commandSource.sendMessage(this.proxyVelocityPlugin.deserializeToComponent(message)) + } +} \ No newline at end of file diff --git a/proxy-velocity/src/main/kotlin/app/simplecloud/plugin/proxy/velocity/handler/TabListHandler.kt b/proxy-velocity/src/main/kotlin/app/simplecloud/plugin/proxy/velocity/handler/TabListHandler.kt index aeafce1..36a1d4e 100644 --- a/proxy-velocity/src/main/kotlin/app/simplecloud/plugin/proxy/velocity/handler/TabListHandler.kt +++ b/proxy-velocity/src/main/kotlin/app/simplecloud/plugin/proxy/velocity/handler/TabListHandler.kt @@ -15,14 +15,14 @@ class TabListHandler( private lateinit var task: ScheduledTask fun startTabListTask() { - this.task = this.plugin.proxyServer.scheduler.buildTask(plugin) { + this.task = this.plugin.proxyServer.scheduler.buildTask(plugin, Runnable { this.plugin.proxyServer.allPlayers.forEach { this.updateTabListForPlayer(it) } this.tabListIndex.forEach { (key, value) -> this.tabListIndex[key] = value + 1 } - }.repeat(this.plugin.tabListConfiguration.tabListUpdateTime, java.util.concurrent.TimeUnit.MILLISECONDS).schedule() + }).repeat(this.plugin.tabListConfiguration.tabListUpdateTime, java.util.concurrent.TimeUnit.MILLISECONDS).schedule() } fun stopTabListTask() { diff --git a/proxy-velocity/src/main/kotlin/app/simplecloud/plugin/proxy/velocity/listener/CloudListener.kt b/proxy-velocity/src/main/kotlin/app/simplecloud/plugin/proxy/velocity/listener/CloudListener.kt new file mode 100644 index 0000000..c554fd2 --- /dev/null +++ b/proxy-velocity/src/main/kotlin/app/simplecloud/plugin/proxy/velocity/listener/CloudListener.kt @@ -0,0 +1,66 @@ +package app.simplecloud.plugin.proxy.velocity.listener + +import app.simplecloud.event.velocity.mapping.CloudServerUpdateEvent +import app.simplecloud.plugin.proxy.shared.handler.MotdLayoutHandler +import app.simplecloud.plugin.proxy.velocity.ProxyVelocityPlugin +import com.velocitypowered.api.event.Subscribe +import java.util.logging.Logger + +class CloudListener( + private val plugin: ProxyVelocityPlugin +) { + + private val logger = Logger.getLogger(CloudListener::class.java.name) + + @Subscribe + fun test(event: CloudServerUpdateEvent) { + + if (event.getTo().uniqueId != System.getenv("SIMPLECLOUD_UNIQUE_ID")) return + + checkMaintenanceChance(event) + checkLayoutMaintenanceChance(event) + checkLayoutChance(event) + } + + private fun checkMaintenanceChance(event: CloudServerUpdateEvent) { + val isMaintenance = event.getTo().properties["maintenance"] + + if (isMaintenance == event.getFrom().properties["maintenance"]) return + + val newMaintenanceState = isMaintenance == "true" + + if (this.plugin.maintenance == newMaintenanceState) return + + this.plugin.maintenance = newMaintenanceState + + this.logger.info("Maintenance mode has been toggled to $newMaintenanceState") + } + + private fun checkLayoutMaintenanceChance(event: CloudServerUpdateEvent) { + val layout = event.getTo().properties[MotdLayoutHandler.CURRENT_MAINTENANCE_LAYOUT_KEY] + + if (layout == event.getFrom().properties[MotdLayoutHandler.CURRENT_MAINTENANCE_LAYOUT_KEY]) return + + val newLayout = layout ?: MotdLayoutHandler.DEFAULT_MAINTENANCE_LAYOUT_NAME + + if (MotdLayoutHandler.CURRENT_MAINTENANCE_LAYOUT_KEY == newLayout) return + + MotdLayoutHandler.CURRENT_MAINTENANCE_LAYOUT_KEY = newLayout + + this.logger.info("Layout has been changed to $newLayout") + } + + private fun checkLayoutChance(event: CloudServerUpdateEvent) { + val layout = event.getTo().properties[MotdLayoutHandler.CURRENT_LAYOUT_KEY] + + if (layout == event.getFrom().properties[MotdLayoutHandler.CURRENT_LAYOUT_KEY]) return + + val newLayout = layout ?: MotdLayoutHandler.DEFAULT_LAYOUT_NAME + + if (MotdLayoutHandler.CURRENT_LAYOUT_KEY == newLayout) return + + MotdLayoutHandler.CURRENT_LAYOUT_KEY = newLayout + + this.logger.info("Layout has been changed to $newLayout") + } +} \ No newline at end of file diff --git a/proxy-velocity/src/main/kotlin/app/simplecloud/plugin/proxy/velocity/listener/ConfigureTagResolversListener.kt b/proxy-velocity/src/main/kotlin/app/simplecloud/plugin/proxy/velocity/listener/ConfigureTagResolversListener.kt index 3fa2d3d..ede5a77 100644 --- a/proxy-velocity/src/main/kotlin/app/simplecloud/plugin/proxy/velocity/listener/ConfigureTagResolversListener.kt +++ b/proxy-velocity/src/main/kotlin/app/simplecloud/plugin/proxy/velocity/listener/ConfigureTagResolversListener.kt @@ -5,6 +5,7 @@ import app.simplecloud.plugin.proxy.velocity.ProxyVelocityPlugin import app.simplecloud.plugin.proxy.velocity.event.ConfigureTagResolversEvent import com.velocitypowered.api.event.PostOrder import com.velocitypowered.api.event.Subscribe +import kotlinx.coroutines.runBlocking import kotlin.jvm.optionals.getOrNull class ConfigureTagResolversListener( @@ -13,25 +14,27 @@ class ConfigureTagResolversListener( @Subscribe(order = PostOrder.FIRST) fun onConfigureTagResolvers(event: ConfigureTagResolversEvent) { - val player = event.player - val serverName = player?.currentServer?.getOrNull()?.serverInfo?.name ?: "unknown" + runBlocking { + val player = event.player + val serverName = player?.currentServer?.getOrNull()?.serverInfo?.name ?: "unknown" - val ping = player?.ping ?: -1 - val pingColors = plugin.placeHolderConfiguration.pingColors + val ping = player?.ping ?: -1 + val pingColors = plugin.placeHolderConfiguration.pingColors - val onlinePlayers = this.plugin.proxyServer.allPlayers.size - val realMaxPlayers = this.plugin.proxyServer.configuration.showMaxPlayers + val onlinePlayers = plugin.proxyServer.allPlayers.size + val realMaxPlayers = plugin.proxyServer.configuration.showMaxPlayers - event.withTagResolvers( - TagResolverHelper.getDefaultTagResolvers( - serverName, - ping, - pingColors, - onlinePlayers, - realMaxPlayers, - this.plugin.motdLayoutHandler.getCurrentMotdLayout() + event.withTagResolvers( + TagResolverHelper.getDefaultTagResolvers( + serverName, + ping, + pingColors, + onlinePlayers, + realMaxPlayers, + plugin.motdLayoutHandler.getCurrentMotdLayout() + ) ) - ) + } } } diff --git a/proxy-velocity/src/main/kotlin/app/simplecloud/plugin/proxy/velocity/listener/ProxyPingListener.kt b/proxy-velocity/src/main/kotlin/app/simplecloud/plugin/proxy/velocity/listener/ProxyPingListener.kt index 7d1c7d8..4268045 100644 --- a/proxy-velocity/src/main/kotlin/app/simplecloud/plugin/proxy/velocity/listener/ProxyPingListener.kt +++ b/proxy-velocity/src/main/kotlin/app/simplecloud/plugin/proxy/velocity/listener/ProxyPingListener.kt @@ -7,9 +7,11 @@ import com.velocitypowered.api.event.proxy.ProxyPingEvent import com.velocitypowered.api.proxy.server.ServerPing import com.velocitypowered.api.proxy.server.ServerPing.SamplePlayer import com.velocitypowered.api.util.Favicon +import kotlinx.coroutines.runBlocking import java.awt.image.BufferedImage import java.io.ByteArrayOutputStream import java.io.File +import java.io.IOException import java.net.InetAddress import java.util.* import javax.imageio.ImageIO @@ -22,58 +24,66 @@ class ProxyPingListener( @Subscribe fun onProxyPing(event: ProxyPingEvent) { - val hostStringFromConnection = event.connection.remoteAddress.address.hostAddress - val hostStringFromServer = InetAddress.getLocalHost().hostAddress - - if (hostStringFromConnection == hostStringFromServer) { - return - } - - val serverPing = event.ping - - val motdConfiguration = this.plugin.motdLayoutHandler.getCurrentMotdLayout() - - val firstLine = motdConfiguration.firstLines.random() - val secondLine = motdConfiguration.secondLines.random() - - val messageOfTheDay = this.plugin.deserializeToComponent("$firstLine\n$secondLine") - - val playerList = motdConfiguration.playerInfo.map { SamplePlayer(it, UUID.randomUUID()) } - - val players = serverPing.players.getOrNull() - val onlinePlayers = players?.online ?: 0 - val realMaxPlayers = players?.max ?: 0 - val maxPlayers = when (motdConfiguration.maxPlayerDisplayType) { - MaxPlayerDisplayType.REAL -> realMaxPlayers - MaxPlayerDisplayType.DYNAMIC -> onlinePlayers + motdConfiguration.dynamicPlayerRange - null -> realMaxPlayers - } - - val samplePlayers = playerList.ifEmpty { players?.sample ?: emptyList() } - - val versions: ServerPing.Version = when (motdConfiguration.versionName) { - "" -> serverPing.version - else -> ServerPing.Version( - -1, - motdConfiguration.versionName - ) + runBlocking { + val hostStringFromConnection = event.connection.remoteAddress.address.hostAddress + val hostStringFromServer = InetAddress.getLocalHost().hostAddress + + if (hostStringFromConnection == hostStringFromServer) { + return@runBlocking + } + + val serverPing = event.ping + + val motdConfiguration = plugin.motdLayoutHandler.getCurrentMotdLayout() + + val firstLine = motdConfiguration.firstLines.random() + val secondLine = motdConfiguration.secondLines.random() + + val messageOfTheDay = plugin.deserializeToComponent("$firstLine\n$secondLine") + + val playerList = motdConfiguration.playerInfo.map { SamplePlayer(it, UUID.randomUUID()) } + + val players = serverPing.players.getOrNull() + val onlinePlayers = players?.online ?: 0 + val realMaxPlayers = players?.max ?: 0 + val maxPlayers = when (motdConfiguration.maxPlayerDisplayType) { + MaxPlayerDisplayType.REAL -> realMaxPlayers + MaxPlayerDisplayType.DYNAMIC -> onlinePlayers + motdConfiguration.dynamicPlayerRange + null -> realMaxPlayers + } + + val samplePlayers = playerList.ifEmpty { players?.sample ?: emptyList() } + + val versions: ServerPing.Version = when (motdConfiguration.versionName) { + "" -> serverPing.version + else -> ServerPing.Version( + -1, + motdConfiguration.versionName + ) + } + + val favicon = if (motdConfiguration.serverIcon == "") { + serverPing.favicon.orElse(null) + } else { + try { + val serverIcon: BufferedImage = ImageIO.read(File(motdConfiguration.serverIcon)) + Favicon.create(serverIcon) + } catch (e: IOException) { + null + } + } + + val builder = event.ping.asBuilder() + .version(versions) + .onlinePlayers(onlinePlayers) + .maximumPlayers(maxPlayers) + .samplePlayers(*samplePlayers.toTypedArray()) + .description(messageOfTheDay) + + favicon?.let { builder.favicon(it) } + + event.ping = builder.build() } - - val favicon = if (motdConfiguration.serverIcon == "") { - serverPing.favicon.orElse(null) - } else { - val serverIcon: BufferedImage = ImageIO.read(File(motdConfiguration.serverIcon)) - Favicon.create(serverIcon) - } - - event.ping = event.ping.asBuilder() - .version(versions) - .onlinePlayers(onlinePlayers) - .maximumPlayers(maxPlayers) - .samplePlayers(*samplePlayers.toTypedArray()) - .description(messageOfTheDay) - .favicon(favicon) - .build() } } \ No newline at end of file diff --git a/proxy-velocity/src/main/kotlin/app/simplecloud/plugin/proxy/velocity/listener/ServerPreConnectListener.kt b/proxy-velocity/src/main/kotlin/app/simplecloud/plugin/proxy/velocity/listener/ServerPreConnectListener.kt new file mode 100644 index 0000000..d8c8ed0 --- /dev/null +++ b/proxy-velocity/src/main/kotlin/app/simplecloud/plugin/proxy/velocity/listener/ServerPreConnectListener.kt @@ -0,0 +1,53 @@ +package app.simplecloud.plugin.proxy.velocity.listener + +import app.simplecloud.plugin.proxy.shared.ProxyPlugin +import app.simplecloud.plugin.proxy.velocity.ProxyVelocityPlugin +import com.velocitypowered.api.event.PostOrder +import com.velocitypowered.api.event.Subscribe +import com.velocitypowered.api.event.player.ServerPreConnectEvent +import com.velocitypowered.api.proxy.Player +import kotlinx.coroutines.runBlocking +import java.util.logging.Logger + +class ServerPreConnectListener( + private val proxyPlugin: ProxyVelocityPlugin, +) { + private val logger = Logger.getLogger(ServerPreConnectListener::class.java.name) + + @Subscribe(order = PostOrder.EARLY) + fun handle(event: ServerPreConnectEvent) { + val player = event.player + + if (proxyPlugin.maintenance && !player.hasPermission(ProxyPlugin.JOIN_MAINTENANCE_PERMISSION)) { + denyAccess( + player, + this.proxyPlugin.messagesConfiguration.kickMessage.networkMaintenance, + event + ) + return + } + + runBlocking { + try { + if (!isServerFull(player)) { + return@runBlocking + } + denyAccess(player, proxyPlugin.messagesConfiguration.kickMessage.networkFull, event) + } catch (e: Exception) { + logger.severe("Error checking player limits: ${e.message}") + } + } + } + + private suspend fun isServerFull(player: Player): Boolean { + val maxPlayers = proxyPlugin.cloudControllerHandler.getMaxPlayersInGroup() + val onlinePlayers = proxyPlugin.cloudControllerHandler.getOnlinePlayersInGroup() + + return onlinePlayers >= maxPlayers && !player.hasPermission(ProxyPlugin.JOIN_FULL_PERMISSION) + } + + private fun denyAccess(player: Player, message: String, event: ServerPreConnectEvent) { + player.disconnect(proxyPlugin.deserializeToComponent(message, player)) + event.result = ServerPreConnectEvent.ServerResult.denied() + } +}