From 31cd569f38a3d0d0b896d873c8b475f18d0d4cec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Levilain?= Date: Tue, 6 Feb 2024 11:31:33 +0100 Subject: [PATCH] feat(shulker-proxy-agent): use proxy capacities as max slots in ping requests --- .../bungeecord/ProxyInterfaceBungeeCord.kt | 13 +- .../io/shulkermc/proxyagent/ProxyInterface.kt | 1 + .../proxyagent/ShulkerProxyAgentCommon.kt | 2 +- .../proxyagent/adapters/cache/CacheAdapter.kt | 5 +- .../adapters/cache/RedisCacheAdapter.kt | 123 ++++++++++++------ .../proxyagent/platform/ProxyPingHook.kt | 2 +- .../services/PlayerMovementService.kt | 9 +- .../velocity/ProxyInterfaceVelocity.kt | 9 +- 8 files changed, 113 insertions(+), 51 deletions(-) diff --git a/packages/shulker-proxy-agent/src/bungeecord/kotlin/io/shulkermc/proxyagent/bungeecord/ProxyInterfaceBungeeCord.kt b/packages/shulker-proxy-agent/src/bungeecord/kotlin/io/shulkermc/proxyagent/bungeecord/ProxyInterfaceBungeeCord.kt index b9b7403e..06d898c4 100644 --- a/packages/shulker-proxy-agent/src/bungeecord/kotlin/io/shulkermc/proxyagent/bungeecord/ProxyInterfaceBungeeCord.kt +++ b/packages/shulker-proxy-agent/src/bungeecord/kotlin/io/shulkermc/proxyagent/bungeecord/ProxyInterfaceBungeeCord.kt @@ -14,6 +14,7 @@ import io.shulkermc.proxyagent.platform.ServerPreConnectHook import net.kyori.adventure.text.Component import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer import net.md_5.bungee.api.ProxyServer +import net.md_5.bungee.api.chat.TextComponent import net.md_5.bungee.api.connection.ProxiedPlayer import net.md_5.bungee.api.event.PermissionCheckEvent import net.md_5.bungee.api.event.PlayerDisconnectEvent @@ -56,7 +57,8 @@ class ProxyInterfaceBungeeCord( @EventHandler(priority = EventPriority.LOWEST) fun onPreLogin(event: ProxyPingEvent) { val result = hook() - event.response.players.online = result.playerCount + event.response.players.online = result.onlinePlayerCount + event.response.players.max = result.maxPlayerCount } } ) @@ -73,8 +75,9 @@ class ProxyInterfaceBungeeCord( val result = hook() if (!result.allowed) { - @Suppress("UnsafeCallOnNullableType") - event.setCancelReason(*BungeeComponentSerializer.get().serialize(result.rejectComponent!!)) + event.reason = TextComponent.fromArray( + *BungeeComponentSerializer.get().serialize(result.rejectComponent!!) + ) } } } @@ -166,6 +169,10 @@ class ProxyInterfaceBungeeCord( return this.proxy.players.size } + override fun getPlayerCapacity(): Int { + return this.proxy.config.playerLimit + } + override fun scheduleDelayedTask( delay: Long, timeUnit: TimeUnit, diff --git a/packages/shulker-proxy-agent/src/common/kotlin/io/shulkermc/proxyagent/ProxyInterface.kt b/packages/shulker-proxy-agent/src/common/kotlin/io/shulkermc/proxyagent/ProxyInterface.kt index 736c78ba..1ae9e899 100644 --- a/packages/shulker-proxy-agent/src/common/kotlin/io/shulkermc/proxyagent/ProxyInterface.kt +++ b/packages/shulker-proxy-agent/src/common/kotlin/io/shulkermc/proxyagent/ProxyInterface.kt @@ -27,6 +27,7 @@ interface ProxyInterface { fun prepareNetworkAdminsPermissions(playerIds: List) fun teleportPlayerOnServer(playerName: String, serverName: String) fun getPlayerCount(): Int + fun getPlayerCapacity(): Int fun scheduleDelayedTask(delay: Long, timeUnit: TimeUnit, runnable: Runnable): ScheduledTask fun scheduleRepeatingTask(delay: Long, interval: Long, timeUnit: TimeUnit, runnable: Runnable): ScheduledTask diff --git a/packages/shulker-proxy-agent/src/common/kotlin/io/shulkermc/proxyagent/ShulkerProxyAgentCommon.kt b/packages/shulker-proxy-agent/src/common/kotlin/io/shulkermc/proxyagent/ShulkerProxyAgentCommon.kt index 8794427d..f7c0ca2e 100644 --- a/packages/shulker-proxy-agent/src/common/kotlin/io/shulkermc/proxyagent/ShulkerProxyAgentCommon.kt +++ b/packages/shulker-proxy-agent/src/common/kotlin/io/shulkermc/proxyagent/ShulkerProxyAgentCommon.kt @@ -86,7 +86,7 @@ class ShulkerProxyAgentCommon(val proxyInterface: ProxyInterface, val logger: Lo ) } - this.cache.registerProxy(Configuration.PROXY_NAME) + this.cache.registerProxy(Configuration.PROXY_NAME, this.proxyInterface.getPlayerCapacity()) this.agonesGateway.setAllocated() } catch (@Suppress("TooGenericExceptionCaught") e: Exception) { this.logger.log(Level.SEVERE, "Shulker Agent crashed, stopping proxy", e) diff --git a/packages/shulker-proxy-agent/src/common/kotlin/io/shulkermc/proxyagent/adapters/cache/CacheAdapter.kt b/packages/shulker-proxy-agent/src/common/kotlin/io/shulkermc/proxyagent/adapters/cache/CacheAdapter.kt index 02741461..7a08ab88 100644 --- a/packages/shulker-proxy-agent/src/common/kotlin/io/shulkermc/proxyagent/adapters/cache/CacheAdapter.kt +++ b/packages/shulker-proxy-agent/src/common/kotlin/io/shulkermc/proxyagent/adapters/cache/CacheAdapter.kt @@ -5,7 +5,7 @@ import java.util.Optional import java.util.UUID interface CacheAdapter { - fun registerProxy(proxyName: String) + fun registerProxy(proxyName: String, proxyCapacity: Int) fun unregisterProxy(proxyName: String) fun updateProxyLastSeen(proxyName: String) fun listRegisteredProxies(): List @@ -25,8 +25,9 @@ interface CacheAdapter { fun getPlayerNamesFromIds(playerIds: List): Map fun countOnlinePlayers(): Int + fun countPlayerCapacity(): Int - data class RegisteredProxy(val proxyName: String, val lastSeenMillis: Long) + data class RegisteredProxy(val proxyName: String, val proxyCapacity: Int, val lastSeenMillis: Long) interface Lock : AutoCloseable } diff --git a/packages/shulker-proxy-agent/src/common/kotlin/io/shulkermc/proxyagent/adapters/cache/RedisCacheAdapter.kt b/packages/shulker-proxy-agent/src/common/kotlin/io/shulkermc/proxyagent/adapters/cache/RedisCacheAdapter.kt index e309c770..83d971c7 100644 --- a/packages/shulker-proxy-agent/src/common/kotlin/io/shulkermc/proxyagent/adapters/cache/RedisCacheAdapter.kt +++ b/packages/shulker-proxy-agent/src/common/kotlin/io/shulkermc/proxyagent/adapters/cache/RedisCacheAdapter.kt @@ -10,32 +10,63 @@ class RedisCacheAdapter(private val jedisPool: JedisPool) : CacheAdapter { companion object { private const val PROXY_LOST_PURGE_LOCK_SECONDS = 15L private const val PLAYER_ID_CACHE_TTL_SECONDS = 60L * 60 * 24 * 14 + + private const val KEY_PREFIX = "shulker" + + // Proxies keys + private const val PROXIES_KEY_PREFIX = "$KEY_PREFIX:proxies" + private const val PROXIES_SET_KEY = PROXIES_KEY_PREFIX + private const val PROXIES_CAPACITY_HASH_KEY = "$PROXIES_KEY_PREFIX:capacity" + private const val PROXIES_LAST_SEEN_HASH_KEY = "$PROXIES_KEY_PREFIX:last-seen" + private val PROXIES_PLAYERS_SET_KEY = { proxyName: String -> "$PROXIES_KEY_PREFIX:$proxyName:players" } + + // Servers keys + private const val SERVERS_KEY_PREFIX = "$KEY_PREFIX:servers" + private val SERVERS_PLAYERS_SET_KEY = { serverName: String -> "$SERVERS_KEY_PREFIX:$serverName:players" } + + // Players keys + private const val PLAYERS_KEY_PREFIX = "$KEY_PREFIX:players" + private const val PLAYERS_ONLINE_SET_KEY = "$PLAYERS_KEY_PREFIX:online" + private const val PLAYERS_CURRENT_PROXY_HASH_KEY = "$PLAYERS_KEY_PREFIX:current-proxy" + private const val PLAYERS_CURRENT_SERVER_HASH_KEY = "$PLAYERS_KEY_PREFIX:current-server" + + // UUID cache keys + private const val UUID_CACHE_KEY_PREFIX = "$KEY_PREFIX:uuid-cache" + private val UUID_CACHE_NAME_TO_ID_KEY = { name: String -> "$UUID_CACHE_KEY_PREFIX:name-to-id:$name" } + private val UUID_CACHE_ID_TO_NAME_KEY = { id: String -> "$UUID_CACHE_KEY_PREFIX:id-to-name:$id" } + + // Locks keys + private const val LOCKS_KEY_PREFIX = "$KEY_PREFIX:locks" + private const val LOCKS_LOST_PROXIES_PURGE_KEY = "$LOCKS_KEY_PREFIX:lost-proxies-purge" } - override fun registerProxy(proxyName: String) { + override fun registerProxy(proxyName: String, proxyCapacity: Int) { this.jedisPool.resource.use { jedis -> val pipeline = jedis.pipelined() - pipeline.sadd("shulker:proxies", proxyName) - pipeline.hset("shulker:proxies:last-seen", proxyName, System.currentTimeMillis().toString()) + pipeline.sadd(PROXIES_SET_KEY, proxyName) + pipeline.hset(PROXIES_CAPACITY_HASH_KEY, proxyName, proxyCapacity.toString()) + pipeline.hset(PROXIES_LAST_SEEN_HASH_KEY, proxyName, System.currentTimeMillis().toString()) pipeline.sync() } } override fun unregisterProxy(proxyName: String) { this.jedisPool.resource.use { jedis -> - val playerIds = jedis.smembers("shulker:proxies:$proxyName:players") + val playersRedisKey = PROXIES_PLAYERS_SET_KEY(proxyName) + val playerIds = jedis.smembers(playersRedisKey) val pipeline = jedis.pipelined() - pipeline.srem("shulker:proxies", proxyName) - pipeline.hdel("shulker:proxies:last-seen", proxyName) - pipeline.del("shulker:proxies:$proxyName:players") + pipeline.srem(PROXIES_SET_KEY, proxyName) + pipeline.hdel(PROXIES_CAPACITY_HASH_KEY, proxyName) + pipeline.hdel(PROXIES_LAST_SEEN_HASH_KEY, proxyName) + pipeline.del(playersRedisKey) pipeline.sync() val playerPipeline = jedis.pipelined() playerIds.forEach { playerId -> - playerPipeline.srem("shulker:players:online", playerId) - playerPipeline.hdel("shulker:players:current-proxy", playerId) - playerPipeline.hdel("shulker:players:current-server", playerId) + playerPipeline.srem(PLAYERS_ONLINE_SET_KEY, playerId) + playerPipeline.hdel(PLAYERS_CURRENT_PROXY_HASH_KEY, playerId) + playerPipeline.hdel(PLAYERS_CURRENT_SERVER_HASH_KEY, playerId) } playerPipeline.sync() } @@ -43,34 +74,36 @@ class RedisCacheAdapter(private val jedisPool: JedisPool) : CacheAdapter { override fun updateProxyLastSeen(proxyName: String) { this.jedisPool.resource.use { jedis -> - jedis.hset("shulker:proxies:last-seen", proxyName, System.currentTimeMillis().toString()) + jedis.hset(PROXIES_LAST_SEEN_HASH_KEY, proxyName, System.currentTimeMillis().toString()) } } override fun listRegisteredProxies(): List { this.jedisPool.resource.use { jedis -> - val registeredProxies = jedis.smembers("shulker:proxies") - val lastSeenMillis = jedis.hgetAll("shulker:proxies:last-seen") + val registeredProxies = jedis.smembers(PROXIES_SET_KEY) + val capacities = jedis.hgetAll(PROXIES_CAPACITY_HASH_KEY) + val lastSeenMillis = jedis.hgetAll(PROXIES_LAST_SEEN_HASH_KEY) return registeredProxies.map { proxyName -> + val capacity = capacities[proxyName]?.toInt() ?: 0 val lastSeen = lastSeenMillis[proxyName]?.toLong() ?: 0L - CacheAdapter.RegisteredProxy(proxyName, lastSeen) + CacheAdapter.RegisteredProxy(proxyName, capacity, lastSeen) } } } override fun tryLockLostProxiesPurgeTask(ownerProxyName: String): Optional = - this.tryLock(ownerProxyName, "shulker:lock:lost-proxies-purge", PROXY_LOST_PURGE_LOCK_SECONDS) + this.tryLock(ownerProxyName, LOCKS_LOST_PROXIES_PURGE_KEY, PROXY_LOST_PURGE_LOCK_SECONDS) override fun unregisterServer(serverName: String) { this.jedisPool.resource.use { jedis -> - jedis.del("shulker:servers:$serverName:players") + jedis.del(SERVERS_PLAYERS_SET_KEY(serverName)) } } override fun listPlayersInServer(serverName: String): List { this.jedisPool.resource.use { jedis -> - val playerIds = jedis.smembers("shulker:servers:$serverName:players") + val playerIds = jedis.smembers(SERVERS_PLAYERS_SET_KEY(serverName)) return playerIds.map(UUID::fromString) } } @@ -78,23 +111,23 @@ class RedisCacheAdapter(private val jedisPool: JedisPool) : CacheAdapter { override fun setPlayerPosition(playerId: UUID, proxyName: String, serverName: String) { this.jedisPool.resource.use { jedis -> val playerIdString = playerId.toString() - val oldProxyName = jedis.hget("shulker:players:current-proxy", playerIdString) - val oldServerName = jedis.hget("shulker:players:current-server", playerIdString) + val oldProxyName = jedis.hget(PLAYERS_CURRENT_PROXY_HASH_KEY, playerIdString) + val oldServerName = jedis.hget(PLAYERS_CURRENT_SERVER_HASH_KEY, playerIdString) val pipeline = jedis.pipelined() - pipeline.sadd("shulker:players:online", playerIdString) - pipeline.hset("shulker:players:current-proxy", playerIdString, proxyName) - pipeline.hset("shulker:players:current-server", playerIdString, serverName) + pipeline.sadd(PLAYERS_ONLINE_SET_KEY, playerIdString) + pipeline.hset(PLAYERS_CURRENT_PROXY_HASH_KEY, playerIdString, proxyName) + pipeline.hset(PLAYERS_CURRENT_SERVER_HASH_KEY, playerIdString, serverName) if (oldProxyName != null) { - pipeline.srem("shulker:proxies:$oldProxyName:players", playerIdString) + pipeline.srem(PROXIES_PLAYERS_SET_KEY(oldProxyName), playerIdString) } - pipeline.sadd("shulker:proxies:$proxyName:players", playerIdString) + pipeline.sadd(PROXIES_PLAYERS_SET_KEY(proxyName), playerIdString) if (oldServerName != null) { - pipeline.srem("shulker:servers:$oldServerName:players", playerIdString) + pipeline.srem(SERVERS_PLAYERS_SET_KEY(oldServerName), playerIdString) } - pipeline.sadd("shulker:servers:$serverName:players", playerIdString) + pipeline.sadd(SERVERS_PLAYERS_SET_KEY(serverName), playerIdString) pipeline.sync() } @@ -103,15 +136,15 @@ class RedisCacheAdapter(private val jedisPool: JedisPool) : CacheAdapter { override fun unsetPlayerPosition(playerId: UUID) { this.jedisPool.resource.use { jedis -> val playerIdString = playerId.toString() - val currentProxyName = jedis.hget("shulker:players:current-proxy", playerIdString) - val currentServerName = jedis.hget("shulker:players:current-server", playerIdString) + val currentProxyName = jedis.hget(PLAYERS_CURRENT_PROXY_HASH_KEY, playerIdString) + val currentServerName = jedis.hget(PLAYERS_CURRENT_SERVER_HASH_KEY, playerIdString) val pipeline = jedis.pipelined() - pipeline.srem("shulker:players:online", playerIdString) - pipeline.hdel("shulker:players:current-proxy", playerIdString) - pipeline.hdel("shulker:players:current-server", playerIdString) - pipeline.srem("shulker:proxies:$currentProxyName:players", playerIdString) - pipeline.srem("shulker:servers:$currentServerName:players", playerIdString) + pipeline.srem(PLAYERS_ONLINE_SET_KEY, playerIdString) + pipeline.hdel(PLAYERS_CURRENT_PROXY_HASH_KEY, playerIdString) + pipeline.hdel(PLAYERS_CURRENT_SERVER_HASH_KEY, playerIdString) + pipeline.srem(PROXIES_PLAYERS_SET_KEY(currentProxyName), playerIdString) + pipeline.srem(SERVERS_PLAYERS_SET_KEY(currentServerName), playerIdString) pipeline.sync() } } @@ -121,8 +154,8 @@ class RedisCacheAdapter(private val jedisPool: JedisPool) : CacheAdapter { val playerIdString = playerId.toString() val pipeline = jedis.pipelined() - val proxyNameResponse = pipeline.hget("shulker:players:current-proxy", playerIdString) - val serverNameResponse = pipeline.hget("shulker:players:current-server", playerIdString) + val proxyNameResponse = pipeline.hget(PLAYERS_CURRENT_PROXY_HASH_KEY, playerIdString) + val serverNameResponse = pipeline.hget(PLAYERS_CURRENT_SERVER_HASH_KEY, playerIdString) pipeline.sync() if (proxyNameResponse != null && serverNameResponse != null) { @@ -139,7 +172,7 @@ class RedisCacheAdapter(private val jedisPool: JedisPool) : CacheAdapter { override fun isPlayerConnected(playerId: UUID): Boolean { this.jedisPool.resource.use { jedis -> - return jedis.sismember("shulker:players:online", playerId.toString()) + return jedis.sismember(PLAYERS_ONLINE_SET_KEY, playerId.toString()) } } @@ -149,28 +182,28 @@ class RedisCacheAdapter(private val jedisPool: JedisPool) : CacheAdapter { val params = SetParams().ex(PLAYER_ID_CACHE_TTL_SECONDS) val pipeline = jedis.pipelined() - pipeline.set("shulker:uuid-cache:id-to-name:$playerIdString", playerName, params) - pipeline.set("shulker:uuid-cache:name-to-id:$playerName", playerIdString, params) + pipeline.set(UUID_CACHE_ID_TO_NAME_KEY(playerIdString), playerName, params) + pipeline.set(UUID_CACHE_NAME_TO_ID_KEY(playerName), playerIdString, params) pipeline.sync() } } override fun getPlayerNameFromId(playerId: UUID): Optional { this.jedisPool.resource.use { jedis -> - return Optional.ofNullable(jedis.get("shulker:uuid-cache:id-to-name:$playerId")) + return Optional.ofNullable(jedis.get(UUID_CACHE_ID_TO_NAME_KEY(playerId.toString()))) } } override fun getPlayerIdFromName(playerName: String): Optional { this.jedisPool.resource.use { jedis -> - return Optional.ofNullable(jedis.get("shulker:uuid-cache:name-to-id:$playerName")).map(UUID::fromString) + return Optional.ofNullable(jedis.get(UUID_CACHE_NAME_TO_ID_KEY(playerName))).map(UUID::fromString) } } override fun getPlayerNamesFromIds(playerIds: List): Map { this.jedisPool.resource.use { jedis -> val pipeline = jedis.pipelined() - val responses = playerIds.associateWith { uuid -> pipeline.get("shulker:uuid-cache:id-to-name:$uuid") } + val responses = playerIds.associateWith { uuid -> pipeline.get(UUID_CACHE_ID_TO_NAME_KEY(uuid.toString())) } pipeline.sync() return responses.mapValues { (_, response) -> response.get() } @@ -179,7 +212,13 @@ class RedisCacheAdapter(private val jedisPool: JedisPool) : CacheAdapter { override fun countOnlinePlayers(): Int { this.jedisPool.resource.use { jedis -> - return jedis.scard("shulker:players:online").toInt() + return jedis.scard(PLAYERS_ONLINE_SET_KEY).toInt() + } + } + + override fun countPlayerCapacity(): Int { + this.jedisPool.resource.use { jedis -> + return jedis.hgetAll(PROXIES_CAPACITY_HASH_KEY).values.sumOf { it.toInt() } } } diff --git a/packages/shulker-proxy-agent/src/common/kotlin/io/shulkermc/proxyagent/platform/ProxyPingHook.kt b/packages/shulker-proxy-agent/src/common/kotlin/io/shulkermc/proxyagent/platform/ProxyPingHook.kt index 7216ae5c..86be4f2f 100644 --- a/packages/shulker-proxy-agent/src/common/kotlin/io/shulkermc/proxyagent/platform/ProxyPingHook.kt +++ b/packages/shulker-proxy-agent/src/common/kotlin/io/shulkermc/proxyagent/platform/ProxyPingHook.kt @@ -1,5 +1,5 @@ package io.shulkermc.proxyagent.platform -data class ProxyPingHookResult(val playerCount: Int) +data class ProxyPingHookResult(val onlinePlayerCount: Int, val maxPlayerCount: Int) typealias ProxyPingHook = () -> ProxyPingHookResult diff --git a/packages/shulker-proxy-agent/src/common/kotlin/io/shulkermc/proxyagent/services/PlayerMovementService.kt b/packages/shulker-proxy-agent/src/common/kotlin/io/shulkermc/proxyagent/services/PlayerMovementService.kt index 2466d7f5..d420a95d 100644 --- a/packages/shulker-proxy-agent/src/common/kotlin/io/shulkermc/proxyagent/services/PlayerMovementService.kt +++ b/packages/shulker-proxy-agent/src/common/kotlin/io/shulkermc/proxyagent/services/PlayerMovementService.kt @@ -18,6 +18,7 @@ class PlayerMovementService(private val agent: ShulkerProxyAgentCommon) { private const val LIMBO_TAG = "limbo" private const val ONLINE_PLAYERS_COUNT_MEMOIZE_SECONDS = 10L + private const val PLAYER_CAPACITY_COUNT_MEMOIZE_SECONDS = 60L private val MSG_NOT_ACCEPTING_PLAYERS = createDisconnectMessage( "Proxy is not accepting players, try reconnect.", @@ -35,6 +36,12 @@ class PlayerMovementService(private val agent: ShulkerProxyAgentCommon) { ONLINE_PLAYERS_COUNT_MEMOIZE_SECONDS, java.util.concurrent.TimeUnit.SECONDS ) + private val playerCapacityCountSupplier = Suppliers.memoizeWithExpiration( + { this.agent.cache.countPlayerCapacity() }, + PLAYER_CAPACITY_COUNT_MEMOIZE_SECONDS, + java.util.concurrent.TimeUnit.SECONDS + ) + private var acceptingPlayers = true init { @@ -57,7 +64,7 @@ class PlayerMovementService(private val agent: ShulkerProxyAgentCommon) { } private fun onProxyPing(): ProxyPingHookResult { - return ProxyPingHookResult(this.onlinePlayerCountSupplier.get()) + return ProxyPingHookResult(this.onlinePlayerCountSupplier.get(), this.playerCapacityCountSupplier.get()) } private fun onPlayerPreLogin(): PlayerPreLoginHookResult { diff --git a/packages/shulker-proxy-agent/src/velocity/kotlin/io/shulkermc/proxyagent/velocity/ProxyInterfaceVelocity.kt b/packages/shulker-proxy-agent/src/velocity/kotlin/io/shulkermc/proxyagent/velocity/ProxyInterfaceVelocity.kt index decda2dd..248c2fb4 100644 --- a/packages/shulker-proxy-agent/src/velocity/kotlin/io/shulkermc/proxyagent/velocity/ProxyInterfaceVelocity.kt +++ b/packages/shulker-proxy-agent/src/velocity/kotlin/io/shulkermc/proxyagent/velocity/ProxyInterfaceVelocity.kt @@ -53,7 +53,10 @@ class ProxyInterfaceVelocity( this.mapPostOrder(postOrder) ) { event -> val result = hook() - event.ping = event.ping.asBuilder().onlinePlayers(result.playerCount).build() + event.ping = event.ping.asBuilder() + .onlinePlayers(result.onlinePlayerCount) + .maximumPlayers(result.maxPlayerCount) + .build() } } @@ -137,6 +140,10 @@ class ProxyInterfaceVelocity( return this.proxy.playerCount } + override fun getPlayerCapacity(): Int { + return this.proxy.configuration.showMaxPlayers + } + override fun scheduleDelayedTask( delay: Long, timeUnit: TimeUnit,