diff --git a/examples/getting-started/proxy.yaml b/examples/getting-started/proxy.yaml index 0977dc92..5073e0a5 100644 --- a/examples/getting-started/proxy.yaml +++ b/examples/getting-started/proxy.yaml @@ -7,8 +7,8 @@ spec: name: getting-started replicas: 1 service: - type: ClusterIP - # externalTrafficPolicy: Local + type: LoadBalancer + externalTrafficPolicy: Local template: spec: version: diff --git a/packages/shulker-operator/assets/proxy-probe-readiness.sh b/packages/shulker-operator/assets/proxy-probe-readiness.sh index 02f5e6f6..0f48adc2 100644 --- a/packages/shulker-operator/assets/proxy-probe-readiness.sh +++ b/packages/shulker-operator/assets/proxy-probe-readiness.sh @@ -6,4 +6,4 @@ if [ -f "/tmp/drain-lock" ]; then echo "Drain lock found" && exit 1 fi -bash /health.sh +bash /usr/bin/health.sh diff --git a/packages/shulker-operator/src/constants.rs b/packages/shulker-operator/src/constants.rs index da29759b..6134583b 100644 --- a/packages/shulker-operator/src/constants.rs +++ b/packages/shulker-operator/src/constants.rs @@ -12,3 +12,6 @@ pub const SHULKER_PLUGIN_VERSION: &str = const_format::concatcp!(env!("CARGO_PKG_VERSION"), "-SNAPSHOT"); #[cfg(not(debug_assertions))] pub const SHULKER_PLUGIN_VERSION: &str = env!("CARGO_PKG_VERSION"); + +pub const PROXY_IMAGE: &str = "itzg/bungeecord:java17-2023.11.0"; +pub const MINECRAFT_SERVER_IMAGE: &str = "itzg/minecraft-server:2024.2.1-java17"; diff --git a/packages/shulker-operator/src/reconcilers/minecraft_server/gameserver.rs b/packages/shulker-operator/src/reconcilers/minecraft_server/gameserver.rs index cc0f9541..058b872b 100644 --- a/packages/shulker-operator/src/reconcilers/minecraft_server/gameserver.rs +++ b/packages/shulker-operator/src/reconcilers/minecraft_server/gameserver.rs @@ -24,6 +24,7 @@ use shulker_crds::v1alpha1::minecraft_server::MinecraftServerVersion; use url::Url; use crate::agent::AgentConfig; +use crate::constants; use crate::reconcilers::agent::get_agent_plugin_url; use crate::reconcilers::agent::AgentSide; use crate::resources::resourceref_resolver::ResourceRefResolver; @@ -37,7 +38,6 @@ use shulker_kube_utils::reconcilers::builder::ResourceBuilder; use super::config_map::ConfigMapBuilder; use super::MinecraftServerReconciler; -const MINECRAFT_SERVER_IMAGE: &str = "itzg/minecraft-server:2023.10.1-java17"; const MINECRAFT_SERVER_SHULKER_CONFIG_DIR: &str = "/mnt/shulker/config"; const MINECRAFT_SERVER_CONFIG_DIR: &str = "/config"; const MINECRAFT_SERVER_DATA_DIR: &str = "/data"; @@ -185,7 +185,7 @@ impl<'a> GameServerBuilder { ..Container::default() }]), containers: vec![Container { - image: Some(MINECRAFT_SERVER_IMAGE.to_string()), + image: Some(constants::MINECRAFT_SERVER_IMAGE.to_string()), name: "minecraft-server".to_string(), ports: Some(vec![ContainerPort { name: Some("minecraft".to_string()), diff --git a/packages/shulker-operator/src/reconcilers/minecraft_server/snapshots/shulker_operator__reconcilers__minecraft_server__gameserver__tests__build_snapshot.snap b/packages/shulker-operator/src/reconcilers/minecraft_server/snapshots/shulker_operator__reconcilers__minecraft_server__gameserver__tests__build_snapshot.snap index db811b22..7815de29 100644 --- a/packages/shulker-operator/src/reconcilers/minecraft_server/snapshots/shulker_operator__reconcilers__minecraft_server__gameserver__tests__build_snapshot.snap +++ b/packages/shulker-operator/src/reconcilers/minecraft_server/snapshots/shulker_operator__reconcilers__minecraft_server__gameserver__tests__build_snapshot.snap @@ -76,7 +76,7 @@ spec: value: 1.20.1 - name: EXTRA_ENV value: my_value - image: "itzg/minecraft-server:2023.10.1-java17" + image: "itzg/minecraft-server:2024.2.1-java17" imagePullPolicy: IfNotPresent name: minecraft-server ports: diff --git a/packages/shulker-operator/src/reconcilers/minecraft_server_fleet/snapshots/shulker_operator__reconcilers__minecraft_server_fleet__fleet__tests__build_snapshot.snap b/packages/shulker-operator/src/reconcilers/minecraft_server_fleet/snapshots/shulker_operator__reconcilers__minecraft_server_fleet__fleet__tests__build_snapshot.snap index 4dd28d15..6c34910f 100644 --- a/packages/shulker-operator/src/reconcilers/minecraft_server_fleet/snapshots/shulker_operator__reconcilers__minecraft_server_fleet__fleet__tests__build_snapshot.snap +++ b/packages/shulker-operator/src/reconcilers/minecraft_server_fleet/snapshots/shulker_operator__reconcilers__minecraft_server_fleet__fleet__tests__build_snapshot.snap @@ -98,7 +98,7 @@ spec: value: 1.20.1 - name: EXTRA_ENV value: my_value - image: "itzg/minecraft-server:2023.10.1-java17" + image: "itzg/minecraft-server:2024.2.1-java17" imagePullPolicy: IfNotPresent name: minecraft-server ports: diff --git a/packages/shulker-operator/src/reconcilers/proxy_fleet/fleet.rs b/packages/shulker-operator/src/reconcilers/proxy_fleet/fleet.rs index 165e203a..086ae18b 100644 --- a/packages/shulker-operator/src/reconcilers/proxy_fleet/fleet.rs +++ b/packages/shulker-operator/src/reconcilers/proxy_fleet/fleet.rs @@ -31,6 +31,7 @@ use shulker_crds::v1alpha1::proxy_fleet::ProxyFleetTemplateVersion; use url::Url; use crate::agent::AgentConfig; +use crate::constants; use crate::reconcilers::agent::get_agent_plugin_url; use crate::reconcilers::agent::AgentSide; use crate::reconcilers::redis_ref::RedisRef; @@ -47,7 +48,6 @@ use shulker_kube_utils::reconcilers::builder::ResourceBuilder; use super::config_map::ConfigMapBuilder; use super::ProxyFleetReconciler; -const PROXY_IMAGE: &str = "itzg/bungeecord:java17-2022.4.1"; const PROXY_SHULKER_CONFIG_DIR: &str = "/mnt/shulker/config"; const PROXY_SHULKER_FORWARDING_SECRET_DIR: &str = "/mnt/shulker/forwarding-secret"; const PROXY_DATA_DIR: &str = "/server"; @@ -205,7 +205,7 @@ impl<'a> FleetBuilder { ..Container::default() }]), containers: vec![Container { - image: Some(PROXY_IMAGE.to_string()), + image: Some(constants::PROXY_IMAGE.to_string()), name: "proxy".to_string(), ports: Some(vec![ContainerPort { name: Some("minecraft".to_string()), diff --git a/packages/shulker-operator/src/reconcilers/proxy_fleet/snapshots/shulker_operator__reconcilers__proxy_fleet__config_map__tests__build_snapshot.snap b/packages/shulker-operator/src/reconcilers/proxy_fleet/snapshots/shulker_operator__reconcilers__proxy_fleet__config_map__tests__build_snapshot.snap index 5a4052f1..6c325f3f 100644 --- a/packages/shulker-operator/src/reconcilers/proxy_fleet/snapshots/shulker_operator__reconcilers__proxy_fleet__config_map__tests__build_snapshot.snap +++ b/packages/shulker-operator/src/reconcilers/proxy_fleet/snapshots/shulker_operator__reconcilers__proxy_fleet__config_map__tests__build_snapshot.snap @@ -6,7 +6,7 @@ apiVersion: v1 kind: ConfigMap data: init-fs.sh: "#!/bin/sh\nset -euo pipefail\nset -o xtrace\n\ncp \"${SHULKER_CONFIG_DIR}/probe-readiness.sh\" \"${SHULKER_PROXY_DATA_DIR}/probe-readiness.sh\"\ncat \"${SHULKER_CONFIG_DIR}/server-icon.png\" | base64 -d > \"${SHULKER_PROXY_DATA_DIR}/server-icon.png\"\n\nif [ \"${SHULKER_VERSION_CHANNEL}\" == \"Velocity\" ]; then\n cp \"${SHULKER_CONFIG_DIR}/velocity-config.toml\" \"${SHULKER_PROXY_DATA_DIR}/velocity.toml\"\n echo \"dummy\" > \"${SHULKER_PROXY_DATA_DIR}/forwarding.secret\"\nelse\n cp \"${SHULKER_CONFIG_DIR}/bungeecord-config.yml\" \"${SHULKER_PROXY_DATA_DIR}/config.yml\"\nfi\n\nif [ ! -z \"${SHULKER_PROXY_PLUGIN_URLS+x}\" ]; then\n mkdir -p \"${SHULKER_PROXY_DATA_DIR}/plugins\"\n for plugin_url in ${SHULKER_PROXY_PLUGIN_URLS//;/ }; do\n (cd \"${SHULKER_PROXY_DATA_DIR}/plugins\" && wget \"${plugin_url}\")\n done\nfi\n\nif [ ! -z \"${SHULKER_PROXY_PATCH_URLS+x}\" ]; then\n for patch_url in ${SHULKER_PROXY_PATCH_URLS//;/ }; do\n (cd \"${SHULKER_PROXY_DATA_DIR}\" && wget \"${patch_url}\" -O - | tar -xzv)\n done\nfi\n" - probe-readiness.sh: "#!/bin/sh\nset -euo pipefail\nset -o xtrace\n\nif [ -f \"/tmp/drain-lock\" ]; then\n echo \"Drain lock found\" && exit 1\nfi\n\nbash /health.sh\n" + probe-readiness.sh: "#!/bin/sh\nset -euo pipefail\nset -o xtrace\n\nif [ -f \"/tmp/drain-lock\" ]; then\n echo \"Drain lock found\" && exit 1\nfi\n\nbash /usr/bin/health.sh\n" server-icon.png: abc== velocity-config.toml: "config-version = \"2.6\"\nbind = \"0.0.0.0:25577\"\nmotd = \"A Motd\"\nshow-max-players = 1000\nonline-mode = true\nforce-key-authentication = true\nprevent-client-proxy-connections = true\nforwarding-secret-file = \"/mnt/shulker/forwarding-secret/key\"\nplayer-info-forwarding-mode = \"modern\"\n\n[servers]\nlobby = \"localhost:30000\"\nlimbo = \"localhost:30001\"\ntry = [\"lobby\", \"limbo\"]\n\n[forced-hosts]\n\n[advanced]\nhaproxy-protocol = true\ntcp-fast-open = true\n\n" metadata: diff --git a/packages/shulker-operator/src/reconcilers/proxy_fleet/snapshots/shulker_operator__reconcilers__proxy_fleet__fleet__tests__build_snapshot.snap b/packages/shulker-operator/src/reconcilers/proxy_fleet/snapshots/shulker_operator__reconcilers__proxy_fleet__fleet__tests__build_snapshot.snap index 9a838bb9..6bdaedeb 100644 --- a/packages/shulker-operator/src/reconcilers/proxy_fleet/snapshots/shulker_operator__reconcilers__proxy_fleet__fleet__tests__build_snapshot.snap +++ b/packages/shulker-operator/src/reconcilers/proxy_fleet/snapshots/shulker_operator__reconcilers__proxy_fleet__fleet__tests__build_snapshot.snap @@ -87,7 +87,7 @@ spec: value: "true" - name: EXTRA_ENV value: my_value - image: "itzg/bungeecord:java17-2022.4.1" + image: "itzg/bungeecord:java17-2023.11.0" imagePullPolicy: IfNotPresent name: proxy ports: 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,