Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package gg.grounds.resourcepacks.velocity
import com.google.inject.Inject
import com.velocitypowered.api.event.Subscribe
import com.velocitypowered.api.event.connection.DisconnectEvent
import com.velocitypowered.api.event.connection.PostLoginEvent
import com.velocitypowered.api.event.player.ServerPostConnectEvent
import com.velocitypowered.api.event.proxy.ProxyInitializeEvent
import com.velocitypowered.api.event.proxy.ProxyShutdownEvent
import com.velocitypowered.api.plugin.Dependency
Expand Down Expand Up @@ -115,7 +115,7 @@ internal constructor(
}

@Subscribe
fun onLogin(event: PostLoginEvent) {
fun onServerPostConnect(event: ServerPostConnectEvent) {
if (!stopped.get()) coordinator.onLogin(event.player)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Gate snapshot fanout until post-connect

When the resource-pack client publishes a READY snapshot after PostLoginEvent but before this ServerPostConnectEvent, ResourcePackCoordinator.onSnapshot still iterates every proxy-online player and sends the request during the unsafe transition. That send records the fingerprint, so this post-connect call is then suppressed as a duplicate and the discarded prompt is never retried. Track which players have completed their first backend connection (or otherwise prevent/clear pre-connect snapshot deliveries) before relying on this handler; the new test misses the race because its OnlinePlayerView is empty.

Useful? React with 👍 / 👎.

}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package gg.grounds.resourcepacks.velocity

import com.velocitypowered.api.event.Subscribe
import com.velocitypowered.api.event.connection.PostLoginEvent
import com.velocitypowered.api.event.player.PlayerResourcePackStatusEvent
import com.velocitypowered.api.event.player.ServerPostConnectEvent
import com.velocitypowered.api.event.proxy.ProxyInitializeEvent
import com.velocitypowered.api.event.proxy.ProxyShutdownEvent
import com.velocitypowered.api.plugin.Plugin
Expand Down Expand Up @@ -76,6 +78,26 @@ class GroundsResourcePacksPluginTest {
assertSame(request, received)
}

// Break caught: sending during PostLogin lets the initial backend transition discard the
// prompt before the player can answer it.
@Test
fun `resourcepacks wait for the initial backend connection before sending`() {
val initial = settings()
val gateway = FakeConfigGateway(ConfigRegistrationResult.ready(), initial)
val clients = FakeClientFactory()
val online = player("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa")
val sent = mutableListOf<ResourcePackRequest>()
val plugin = plugin(gateway, clients, sender = PackSender { _, request -> sent += request })
plugin.onInitialize(ProxyInitializeEvent())
clients.created.single().emit(readyState(initial, snapshot(initial)))

fireSubscribed(plugin, PostLoginEvent(online))
assertEquals(0, sent.size)

fireSubscribed(plugin, ServerPostConnectEvent(online, null))
assertEquals(1, sent.size)
}

// Break caught: degraded NOT_READY must not manufacture defaults or start CDN I/O.
@Test
fun `not ready registers exact scope without reading defaults or creating a client`() {
Expand Down Expand Up @@ -325,7 +347,7 @@ class GroundsResourcePacksPluginTest {
plugin.onInitialize(ProxyInitializeEvent())
val client = clients.created.single()
client.emit(readyState(initial, snapshot(initial)))
plugin.onLogin(PostLoginEvent(online))
plugin.onServerPostConnect(ServerPostConnectEvent(online, null))

gateway.emit(initial.copy(prompt = "second"))
gateway.emit(initial.copy(prompt = "second", required = false))
Expand Down Expand Up @@ -521,7 +543,7 @@ class GroundsResourcePacksPluginTest {
clients.created
.single()
.emit(degradedState(initial, fallback).copy(lastError = "token=do-not-log offline"))
plugin.onLogin(PostLoginEvent(online))
plugin.onServerPostConnect(ServerPostConnectEvent(online, null))
val statusListener = events.registered.single().second as ResourcePackStatusListener
statusListener.onStatus(
PlayerResourcePackStatusEvent(
Expand Down Expand Up @@ -700,6 +722,17 @@ class GroundsResourcePacksPluginTest {
eventRegistry = events,
log = log,
)

private fun fireSubscribed(plugin: GroundsResourcePacksPlugin, event: Any) {
GroundsResourcePacksPlugin::class
.java
.declaredMethods
.filter { method ->
method.getAnnotation(Subscribe::class.java) != null &&
method.parameterTypes.contentEquals(arrayOf(event.javaClass))
}
.forEach { method -> method.invoke(plugin, event) }
}
}

internal class FakeResourcePackConfigBackend(
Expand Down