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 index 114a258..2a0d65e 100644 --- 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 @@ -4,8 +4,11 @@ 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 +import net.md_5.bungee.api.connection.ProxiedPlayer -class BungeeCordCommandSender(private val commandSender: net.md_5.bungee.api.CommandSender +class BungeeCordCommandSender( + private val commandSender: net.md_5.bungee.api.CommandSender, + private val plugin: ProxyBungeeCordPlugin ) : CommandSender { fun getCommandSender(): net.md_5.bungee.api.CommandSender { @@ -13,7 +16,9 @@ class BungeeCordCommandSender(private val commandSender: net.md_5.bungee.api.Com } override fun sendMessage(message: String) { - commandSender.sendMessage(message) + val player = commandSender as? ProxiedPlayer + val component = plugin.deserializeToComponent(message, player) + plugin.adventure().sender(commandSender).sendMessage(component) } } 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 52c3e90..92ee7ca 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 @@ -51,7 +51,7 @@ class ProxyBungeeCordPlugin: Plugin() { val executionCoordinator = ExecutionCoordinator.simpleCoordinator() val senderMapper = SenderMapper.create( - { commandSender -> BungeeCordCommandSender(commandSender) }, + { commandSender -> BungeeCordCommandSender(commandSender, this) }, { cloudSender -> (cloudSender as BungeeCordCommandSender).getCommandSender() } ) diff --git a/proxy-shared/build.gradle.kts b/proxy-shared/build.gradle.kts index 19c6aa3..485f46e 100644 --- a/proxy-shared/build.gradle.kts +++ b/proxy-shared/build.gradle.kts @@ -6,7 +6,7 @@ dependencies { implementation(rootProject.libs.configurate.yaml) implementation(rootProject.libs.configurate.kotlin) - compileOnly(rootProject.libs.simplecloud.controller) + implementation(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 index 45474dc..f5e962c 100644 --- 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 @@ -14,13 +14,24 @@ open class ProxyPlugin( ) { val config = YamlConfig(dirPath) - val tabListConfiguration = config.load("tablist")!! - val placeHolderConfiguration = config.load("placeholder")!! - val messagesConfiguration = config.load("messages")!! - val joinStateConfiguration = config.load("joinstate")!! + + val tabListConfiguration = loadOrCreate("tablist", TabListConfiguration()) + val placeHolderConfiguration = loadOrCreate("placeholder", PlaceHolderConfiguration()) + val messagesConfiguration = loadOrCreate("messages", MessageConfig()) + val joinStateConfiguration = loadOrCreate("joinstate", JoinStateConfiguration()) val motdLayoutHandler = MotdLayoutHandler(config, this) val joinStateHandler = JoinStateHandler(this) val cloudControllerHandler = CloudControllerHandler(joinStateHandler) + private inline fun loadOrCreate(path: String, defaultValue: T): T { + val loaded = config.load(path) + if (loaded != null) { + return loaded + } + + config.save(path, defaultValue) + return defaultValue + } + } \ No newline at end of file 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 index 7caa7be..b2005b5 100644 --- 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 @@ -8,17 +8,36 @@ class CloudControllerHandler( private val joinStateHandler: JoinStateHandler ) { - private val controllerApi = ControllerApi.createCoroutineApi() private val logger = Logger.getLogger(CloudControllerHandler::class.java.name) + private val controllerApi = createControllerApiOrNull() var groupName: String? = null var numericalId: Int? = null + fun isControllerAvailable(): Boolean { + return controllerApi != null + } + init { initializeGroupName() } + private fun createControllerApiOrNull(): ControllerApi.Coroutine? { + return try { + ControllerApi.createCoroutineApi() + } catch (e: Throwable) { + logger.warning("ControllerApi is not available: ${e.message}") + null + } + } + private fun initializeGroupName() { + val api = controllerApi + if (api == null) { + logger.warning("Skipping controller initialization because ControllerApi is unavailable.") + return + } + val serviceID = System.getenv("SIMPLECLOUD_UNIQUE_ID") if (serviceID == null) { @@ -28,7 +47,7 @@ class CloudControllerHandler( CoroutineScope(Dispatchers.IO).launch { try { - val service = controllerApi.getServers().getServerById(serviceID) + val service = api.getServers().getServerById(serviceID) groupName = service.group numericalId = service.numericalId logger.info("Group name initialized to: $groupName") @@ -53,15 +72,17 @@ class CloudControllerHandler( }*/ suspend fun getGroupProperties(groupName: String, key: String): String { + val api = controllerApi ?: return "" return groupName.let { retrievePropertyOrEmpty { - controllerApi.getGroups().getGroupByName(it).properties[key] + api.getGroups().getGroupByName(it).properties[key] } } } suspend fun getServiceProperties(groupName: String, numericalId: Long, key: String): String { - return controllerApi.getServers().getServerByNumerical(groupName, numericalId).let { server -> + val api = controllerApi ?: return "" + return api.getServers().getServerByNumerical(groupName, numericalId).let { server -> retrievePropertyOrEmpty { server.properties[key] } @@ -69,9 +90,10 @@ class CloudControllerHandler( } suspend fun setServiceProperties(groupName: String, numericalId: Long, key: String, value: String): Boolean { - controllerApi.getServers().getServerByNumerical(groupName, numericalId).let { server -> + val api = controllerApi ?: return false + api.getServers().getServerByNumerical(groupName, numericalId).let { server -> try { - controllerApi.getServers().updateServerProperty(server.uniqueId, key, value) + api.getServers().updateServerProperty(server.uniqueId, key, value) logger.info("Service property '$key' updated to '$value'") return true } catch (e: Exception) { @@ -82,11 +104,12 @@ class CloudControllerHandler( } suspend fun setServicePropertiesOnAllGroupServices(groupName: String, key: String, value: String): Boolean { + val api = controllerApi ?: return false groupName.let { name -> try { - controllerApi.getServers().getServersByGroup(name).forEach { server -> + api.getServers().getServersByGroup(name).forEach { server -> logger.info("Updating service property '$key' to '$value' on service ${server.group} ${server.numericalId} ${server.uniqueId}") - controllerApi.getServers().updateServerProperty(server.uniqueId, key, value) + api.getServers().updateServerProperty(server.uniqueId, key, value) } logger.info("Service property '$key' updated to '$value' on all services in group '$name'") return true @@ -98,11 +121,12 @@ class CloudControllerHandler( } suspend fun setGroupProperties(groupName: String, key: String, value: String): Boolean { + val api = controllerApi ?: return false groupName.let { name -> try { - val group = controllerApi.getGroups().getGroupByName(name) + val group = api.getGroups().getGroupByName(name) val updatedGroup = group.copy(properties = group.properties + (key to value)) - controllerApi.getGroups().updateGroup(updatedGroup) + api.getGroups().updateGroup(updatedGroup) logger.info("Group property '$key' updated to '$value'") return true } catch (e: Exception) { @@ -113,9 +137,10 @@ class CloudControllerHandler( } suspend fun getOnlinePlayersInGroup(groupName: String): Int { + val api = controllerApi ?: return 0 return groupName.let { name -> try { - controllerApi.getServers().getServersByGroup(name).sumOf { it.playerCount.toInt() } + api.getServers().getServersByGroup(name).sumOf { it.playerCount.toInt() } } catch (e: Exception) { logger.severe("Error retrieving online players in group: ${e.message}") 0 @@ -125,9 +150,10 @@ class CloudControllerHandler( } suspend fun getMaxPlayersInGroup(groupName: String): Int { + val api = controllerApi ?: return 0 return groupName.let { try { - controllerApi.getGroups().getGroupByName(it).maxPlayers.toInt() + api.getGroups().getGroupByName(it).maxPlayers.toInt() } catch (e: Exception) { logger.severe("Error retrieving max players in group: ${e.message}") 0 @@ -136,11 +162,13 @@ class CloudControllerHandler( } suspend fun getAllGroups(): List { - return controllerApi.getGroups().getAllGroups().map { it.name } + val api = controllerApi ?: return emptyList() + return api.getGroups().getAllGroups().map { it.name } } suspend fun getAllNumericalIdsFromGroup(groupName: String): List { - return controllerApi.getServers().getServersByGroup(groupName).map { it.numericalId } + val api = controllerApi ?: return emptyList() + return api.getServers().getServersByGroup(groupName).map { it.numericalId } } private suspend fun retrievePropertyOrEmpty(retrieve: suspend () -> String?): String { diff --git a/proxy-shared/src/main/kotlin/app/simplecloud/plugin/proxy/shared/handler/JoinStateHandler.kt b/proxy-shared/src/main/kotlin/app/simplecloud/plugin/proxy/shared/handler/JoinStateHandler.kt index 95337ab..fc6de04 100644 --- a/proxy-shared/src/main/kotlin/app/simplecloud/plugin/proxy/shared/handler/JoinStateHandler.kt +++ b/proxy-shared/src/main/kotlin/app/simplecloud/plugin/proxy/shared/handler/JoinStateHandler.kt @@ -23,6 +23,11 @@ class JoinStateHandler( * @param joinStateName The name of the join state. */ suspend fun setJoinStateAtGroup(groupName: String, joinStateName: String) { + if (!this.proxyPlugin.cloudControllerHandler.isControllerAvailable()) { + this.localState = joinStateName + return + } + this.proxyPlugin.cloudControllerHandler.setGroupProperties(groupName, JOINSTATE_KEY, joinStateName) } @@ -33,13 +38,16 @@ class JoinStateHandler( * @return The name of the join state. */ suspend fun getJoinStateAtGroup(groupName: String): String { + if (!this.proxyPlugin.cloudControllerHandler.isControllerAvailable()) { + return this.localState + } + val groupProperties = this.proxyPlugin.cloudControllerHandler.getGroupProperties(groupName, JOINSTATE_KEY) if (groupProperties.isEmpty()) { logger.warning("No join state found for group $groupName. Using default join state.") setJoinStateAtGroup(groupName, this.proxyPlugin.joinStateConfiguration.defaultState) - - return getJoinStateAtGroup(groupName) + return this.proxyPlugin.joinStateConfiguration.defaultState } return groupProperties @@ -55,6 +63,11 @@ class JoinStateHandler( * @return True if the join state was set successfully, false otherwise. */ suspend fun setJoinStateAtService(groupName: String, numericalId: Long, joinStateName: String): Boolean { + if (!this.proxyPlugin.cloudControllerHandler.isControllerAvailable()) { + this.localState = joinStateName + return true + } + return this.proxyPlugin.cloudControllerHandler.setServiceProperties(groupName, numericalId, JOINSTATE_KEY, joinStateName) } @@ -67,6 +80,12 @@ class JoinStateHandler( * @return True if the join state was set successfully, false otherwise. */ suspend fun setJoinStateAtGroupAndAllServicesInGroup(groupName: String, joinStateName: String): Boolean { + if (!this.proxyPlugin.cloudControllerHandler.isControllerAvailable()) { + this.localState = joinStateName + logger.warning("Controller API unavailable. Updated local join state only.") + return true + } + val groupProperties = this.proxyPlugin.cloudControllerHandler.setGroupProperties(groupName, JOINSTATE_KEY, joinStateName) val servicePropertiesOnAllGroupServices = @@ -92,13 +111,16 @@ class JoinStateHandler( * @return The name of the join state. */ suspend fun getJoinStateAtService(groupName: String, numericalId: Long): String { + if (!this.proxyPlugin.cloudControllerHandler.isControllerAvailable()) { + return this.localState + } + val serviceProperties = this.proxyPlugin.cloudControllerHandler.getServiceProperties(groupName, numericalId, JOINSTATE_KEY) if (serviceProperties.isEmpty()) { logger.warning("No join state found for service $numericalId in group $groupName. Using default join state.") setJoinStateAtService(groupName, numericalId, this.proxyPlugin.joinStateConfiguration.defaultState) - - return getJoinStateAtService(groupName, numericalId) + return this.proxyPlugin.joinStateConfiguration.defaultState } return serviceProperties diff --git a/proxy-velocity/build.gradle.kts b/proxy-velocity/build.gradle.kts index 7453e56..c40ff50 100644 --- a/proxy-velocity/build.gradle.kts +++ b/proxy-velocity/build.gradle.kts @@ -12,7 +12,7 @@ dependencies { compileOnly(rootProject.libs.velocity) annotationProcessor(rootProject.libs.velocity) - compileOnly(rootProject.libs.simplecloud.event.wrapper.velocity) + implementation(rootProject.libs.simplecloud.event.wrapper.velocity) compileOnly(rootProject.libs.simplecloud.controller) implementation(rootProject.libs.simplecloud.plugin.api) @@ -22,6 +22,9 @@ dependencies { } tasks.shadowJar { + from(sourceSets.main.get().resources) + configurations = listOf(project.configurations.runtimeClasspath.get()) + relocate("org.incendo", "app.simplecloud.relocate.incendo") relocate("org.spongepowered", "app.simplecloud.relocate.spongepowered") relocate("app.simplecloud.plugin.api", "app.simplecloud.relocate.plugin.api") 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 index 70f4f41..f700462 100644 --- 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 @@ -15,9 +15,16 @@ class ServerPreConnectListener( ) { private val logger = Logger.getLogger(ServerPreConnectListener::class.java.name) - private val identifier = ServerPatternIdentifier( - this.proxyPlugin.joinStateConfiguration.serverNamePattern - ) + private val identifier = createServerPatternIdentifier() + + private fun createServerPatternIdentifier(): ServerPatternIdentifier? { + return try { + ServerPatternIdentifier(this.proxyPlugin.joinStateConfiguration.serverNamePattern) + } catch (e: Throwable) { + logger.warning("ServerPatternIdentifier is unavailable. Server switch checks are disabled: ${e.message}") + null + } + } @Subscribe(order = PostOrder.EARLY) fun handle(event: ServerPreConnectEvent) { @@ -68,8 +75,13 @@ class ServerPreConnectListener( private fun checkAllowServerSwitch(player: Player, event: ServerPreConnectEvent, server: RegisteredServer) { val serverName = server.serverInfo.name + val parser = identifier + + if (parser == null) { + return + } - val (groupName, numericalId) = identifier.parse(serverName) + val (groupName, numericalId) = parser.parse(serverName) runBlocking { val joinStateName = @@ -99,8 +111,8 @@ class ServerPreConnectListener( val groupName = this.proxyPlugin.cloudControllerHandler.groupName if (groupName == null) { - logger.warning("No group name found for server.") - return true + logger.warning("No group name found for server. Skipping network-full check.") + return false } val maxPlayers = proxyPlugin.cloudControllerHandler.getMaxPlayersInGroup(groupName)