diff --git a/common/build.gradle.kts b/common/build.gradle.kts index 4ee5ea7..6f91d6e 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -11,7 +11,7 @@ repositories { } dependencies { - protobuf("gg.grounds:library-grpc-contracts-player:0.6.0") + protobuf("gg.grounds:library-grpc-contracts-player:0.7.0") testImplementation("org.junit.jupiter:junit-jupiter-api:5.13.4") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.13.4") diff --git a/common/src/main/kotlin/gg/grounds/player/presence/GrpcPlayerPresenceClient.kt b/common/src/main/kotlin/gg/grounds/player/presence/GrpcPlayerPresenceClient.kt index 423a996..7121a25 100644 --- a/common/src/main/kotlin/gg/grounds/player/presence/GrpcPlayerPresenceClient.kt +++ b/common/src/main/kotlin/gg/grounds/player/presence/GrpcPlayerPresenceClient.kt @@ -58,12 +58,20 @@ private constructor( } } - fun logout(playerId: UUID): PlayerLogoutReply { + /** + * [proxyId] scopes the delete to this proxy's own session: a logout that raced a proxy-to-proxy + * transfer must not remove the session the next proxy just created. Empty is legal (the service + * falls back to the old unconditional delete). + */ + fun logout(playerId: UUID, proxyId: String = ""): PlayerLogoutReply { return try { stub .withDeadlineAfter(DEFAULT_TIMEOUT_MS, TimeUnit.MILLISECONDS) .playerLogout( - PlayerLogoutRequest.newBuilder().setPlayerId(playerId.toString()).build() + PlayerLogoutRequest.newBuilder() + .setPlayerId(playerId.toString()) + .setProxyId(proxyId) + .build() ) } catch (e: StatusRuntimeException) { errorLogoutReply(e.status.toString()) diff --git a/velocity/src/main/kotlin/gg/grounds/listener/PlayerConnectionListener.kt b/velocity/src/main/kotlin/gg/grounds/listener/PlayerConnectionListener.kt index dc42694..b01c63e 100644 --- a/velocity/src/main/kotlin/gg/grounds/listener/PlayerConnectionListener.kt +++ b/velocity/src/main/kotlin/gg/grounds/listener/PlayerConnectionListener.kt @@ -89,7 +89,7 @@ class PlayerConnectionListener( val name = event.player.username return EventTask.async { - val result = playerPresenceService.logout(playerId) ?: return@async + val result = playerPresenceService.logout(playerId, proxyId()) ?: return@async if (result.removed) { logger.info( "Player session logout completed (playerId={}, username={}, message={})", @@ -181,7 +181,7 @@ class PlayerConnectionListener( val online = proxy.getPlayer(playerId).isPresent if (!shouldReleaseAbandonedLogin(pendingLogins, playerId, online)) return - val result = playerPresenceService.logout(playerId) + val result = playerPresenceService.logout(playerId, proxyId()) logger.info( "Player session released after abandoned login (playerId={}, username={}, removed={})", playerId, diff --git a/velocity/src/main/kotlin/gg/grounds/presence/PlayerPresenceService.kt b/velocity/src/main/kotlin/gg/grounds/presence/PlayerPresenceService.kt index 70e7e2d..550ca17 100644 --- a/velocity/src/main/kotlin/gg/grounds/presence/PlayerPresenceService.kt +++ b/velocity/src/main/kotlin/gg/grounds/presence/PlayerPresenceService.kt @@ -88,9 +88,9 @@ class PlayerPresenceService : AutoCloseable { } } - fun logout(playerId: UUID): PlayerLogoutReply? { + fun logout(playerId: UUID, proxyId: String = ""): PlayerLogoutReply? { return try { - client.logout(playerId) + client.logout(playerId, proxyId) } catch (e: RuntimeException) { null }