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
4 changes: 2 additions & 2 deletions velocity/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ configure<BuildConfigExtension> { packageName("gg.grounds.generated") }

dependencies {
compileOnly("gg.grounds:plugin-config-common:1.0.0")
compileOnly("gg.grounds:plugin-proxy-api:2.6.0")
compileOnly("gg.grounds:plugin-proxy-api:2.11.2-announcements-938c2d7")
implementation("gg.grounds:library-i18n:0.4.4")
compileOnly("gg.grounds:plugin-config-velocity:1.0.0") {
attributes {
Expand All @@ -19,7 +19,7 @@ dependencies {
implementation("gg.grounds:resourcepacks-client:1.0.0")

testImplementation("gg.grounds:plugin-config-common:1.0.0")
testImplementation("gg.grounds:plugin-proxy-api:2.6.0")
testImplementation("gg.grounds:plugin-proxy-api:2.11.2-announcements-938c2d7")
testImplementation("gg.grounds:plugin-config-velocity:1.0.0") {
attributes {
attribute(
Expand Down
2 changes: 1 addition & 1 deletion velocity/gradle.lockfile
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ com.velocitypowered:velocity-brigadier:1.0.0-SNAPSHOT=compileClasspath,kapt,kapt
gg.grounds:library-i18n:0.4.4=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
gg.grounds:plugin-config-common:1.0.0=compileClasspath,testCompileClasspath,testRuntimeClasspath
gg.grounds:plugin-config-velocity:1.0.0=compileClasspath,testCompileClasspath,testRuntimeClasspath
gg.grounds:plugin-proxy-api:2.6.0=compileClasspath,testCompileClasspath,testRuntimeClasspath
gg.grounds:plugin-proxy-api:2.11.2-announcements-938c2d7=compileClasspath,testCompileClasspath,testRuntimeClasspath
gg.grounds:resourcepacks-client:1.0.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
gg.grounds:resourcepacks-contract:1.0.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.github.java-diff-utils:java-diff-utils:4.12=kotlinInternalAbiValidation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ internal constructor(
private val chatStyleBridge: PlayerChatStyleBridge = NoopPlayerChatStyleBridge,
private val sidebarStyleBridge: PlayerChatStyleBridge = NoopPlayerChatStyleBridge,
private val sidebarProtocol: (java.util.UUID) -> Int? = { appearanceProtocol(it)?.protocol },
private val hudRegistry: PlayerHudAppearanceRegistry = NoopPlayerHudAppearanceRegistry,
private val appliedHashes: (java.util.UUID) -> Set<String> = { emptySet() },
) {
@Inject
constructor(
Expand All @@ -82,6 +84,17 @@ internal constructor(
},
platformLookup = FloodgateChatPlatformLookup.create(logger),
appearanceRegistry = ProxyPlayerChatAppearanceRegistry,
hudRegistry = ProxyPlayerHudAppearanceRegistry,
appliedHashes = { id ->
proxy
.getPlayer(id)
.orElse(null)
?.appliedResourcePacks
?.mapNotNull { pack ->
pack.hash?.joinToString("") { "%02x".format(it.toInt() and 255) }
}
?.toSet() ?: emptySet()
},
chatStyleBridge =
VelocityPlayerChatStyleBridge(
registerChannel = { proxy.channelRegistrar.register(it) },
Expand All @@ -105,6 +118,7 @@ internal constructor(
private val configurationWaiter = ResourcePackConfigurationWaiter()
private val appearanceTracker = AtomicReference<PlayerChatAppearanceTracker?>(null)
private val sidebarTracker = AtomicReference<PlayerChatAppearanceTracker?>(null)
private val hudTracker = AtomicReference<PlayerChatAppearanceTracker?>(null)
private val messages =
Translations.forBundle("gg.grounds.resourcepacks.messages", javaClass.classLoader)
private val deadlines = Any()
Expand All @@ -123,6 +137,7 @@ internal constructor(
ResourcePackDeliveryObserver { player, prepared ->
appearanceTracker.get()?.offered(player.uniqueId, prepared.packHashes)
sidebarTracker.get()?.offered(player.uniqueId, prepared.packHashes)
hudTracker.get()?.offered(player.uniqueId, prepared.packHashes)
publishChatStyle(player)
log.info(
"Resource-pack request sent (playerId=${player.uniqueId}, " +
Expand Down Expand Up @@ -155,6 +170,18 @@ internal constructor(
appearanceRegistry.register(
ResourcePackPlayerChatAppearanceQuery(appearanceProtocol, platformLookup, tracker)
)
val hudHashes = HudAppearanceEnvironment.from(deployment)
val hud = PlayerChatAppearanceTracker(hudHashes)
hudTracker.set(hud)
hudRegistry.register(
ResourcePackPlayerHudAppearanceQuery(
hud,
hudHashes,
sidebarProtocol,
platformLookup,
appliedHashes,
)
)
chatStyleBridge.register()
sidebarStyleBridge.register()
val definition = resourcePackSettingsDefinition(bootstrapPackSetChannel(deployment))
Expand All @@ -179,6 +206,7 @@ internal constructor(
if (packId != null) {
tracker.loaded(playerId, packId)
sidebarTracker.get()?.loaded(playerId, packId)
hudTracker.get()?.loaded(playerId, packId)
}
true
}
Expand All @@ -188,6 +216,7 @@ internal constructor(
if (packId != null) {
tracker.failed(playerId, packId)
sidebarTracker.get()?.failed(playerId, packId)
hudTracker.get()?.failed(playerId, packId)
}
true
}
Expand Down Expand Up @@ -249,6 +278,7 @@ internal constructor(
if (initialDeadlines[playerId]?.player !== event.player()) {
appearanceTracker.get()?.reset(playerId)
sidebarTracker.get()?.reset(playerId)
hudTracker.get()?.reset(playerId)
}
initialDeadlines.put(playerId, initial)?.handle
}
Expand Down Expand Up @@ -284,6 +314,7 @@ internal constructor(
initialDeadlines[event.player.uniqueId]?.takeIf { it.player === event.player }
?: return@synchronized null
initialDeadlines.remove(event.player.uniqueId)
hudTracker.get()?.forget(event.player.uniqueId)
DisconnectedPlayer(
initial,
appearanceTracker.get()?.disconnectSnapshot(event.player.uniqueId),
Expand Down Expand Up @@ -331,6 +362,8 @@ internal constructor(
coordinator.clear()
appearanceTracker.getAndSet(null)?.clear()
sidebarTracker.getAndSet(null)?.clear()
hudTracker.getAndSet(null)?.clear()
hudRegistry.unregister()
appearanceRegistry.unregister()
chatStyleBridge.unregister()
sidebarStyleBridge.unregister()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package gg.grounds.resourcepacks.velocity

import gg.grounds.proxy.api.PlayerHudAppearance
import gg.grounds.proxy.api.PlayerHudAppearanceQuery
import gg.grounds.proxy.api.ProxyServiceRegistry
import java.util.UUID

internal object HudAppearanceEnvironment {
fun from(environment: Map<String, String>): Set<String> {
val hashes =
environment["HUD_FONT_PACK_SHA1S"]
.orEmpty()
.split(',')
.map(String::trim)
.filter(String::isNotEmpty)
require(hashes.all { it.matches(Regex("[0-9a-f]{40}")) }) {
"HUD_FONT_PACK_SHA1S must contain lowercase SHA-1 hashes"
}
return hashes.toSet()
}

fun eligible(protocol: Int?, bedrock: Boolean?): Boolean = protocol == 777 && bedrock == false
}

internal class ResourcePackPlayerHudAppearanceQuery(
private val tracker: PlayerChatAppearanceTracker,
private val hashes: Set<String>,
private val protocol: (UUID) -> Int?,
private val platform: ChatPlatformLookup,
private val appliedHashes: (UUID) -> Set<String>,
) : PlayerHudAppearanceQuery {
override fun appearanceOf(playerId: UUID): PlayerHudAppearance =
PlayerHudAppearance(
HudAppearanceEnvironment.eligible(protocol(playerId), platform.isBedrock(playerId)) &&
tracker.chatFontReady(playerId) &&
appliedHashes(playerId).any { it in hashes }
)
}

internal interface PlayerHudAppearanceRegistry {
fun register(query: PlayerHudAppearanceQuery)

fun unregister()
}

internal object ProxyPlayerHudAppearanceRegistry : PlayerHudAppearanceRegistry {
override fun register(query: PlayerHudAppearanceQuery) {
ProxyServiceRegistry.register(PlayerHudAppearanceQuery::class.java, query)
}

override fun unregister() {
ProxyServiceRegistry.unregister(PlayerHudAppearanceQuery::class.java)
}
}

internal object NoopPlayerHudAppearanceRegistry : PlayerHudAppearanceRegistry {
override fun register(query: PlayerHudAppearanceQuery) = Unit

override fun unregister() = Unit
}
Original file line number Diff line number Diff line change
Expand Up @@ -1684,6 +1684,70 @@ class GroundsResourcePacksPluginTest {
}
}

@Test
fun `reentrant reconnect during disconnect cannot clear the new HUD session`() {
val initial = settings()
val clients = FakeClientFactory()
val events = FakeEventRegistry()
val old = player("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa")
val replacement = player("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa")
lateinit var instance: GroundsResourcePacksPlugin
var query: gg.grounds.proxy.api.PlayerHudAppearanceQuery? = null
var reconnect = false
val registry =
object : PlayerHudAppearanceRegistry {
override fun register(value: gg.grounds.proxy.api.PlayerHudAppearanceQuery) {
query = value
}

override fun unregister() {
query = null
}
}
val bridge =
object : PlayerChatStyleBridge by NoopPlayerChatStyleBridge {
override fun send(player: Player, snapshot: PlayerChatStyleSnapshot) {
if (reconnect && player === old && !snapshot.ready) {
reconnect = false
instance.onPlayerConfiguration(PlayerConfigurationEvent(replacement, null))
terminalStatus(events, replacement)
}
}
}
instance =
plugin(
FakeConfigGateway(ConfigRegistrationResult.ready(), initial),
clients,
events = events,
environment =
mapOf(
"GROUNDS_ENVIRONMENT" to "stage",
"HUD_FONT_PACK_SHA1S" to "1".repeat(40),
),
platformLookup = ChatPlatformLookup { false },
sidebarProtocol = { 777 },
hudRegistry = registry,
appliedHashes = { setOf("1".repeat(40)) },
chatStyleBridge = bridge,
)
instance.onInitialize(ProxyInitializeEvent())
clients.created
.single()
.emit(
readyState(
initial,
snapshot(initial, packs = listOf(resolvedPack(role = "platform"))),
)
)
instance.onPlayerConfiguration(PlayerConfigurationEvent(old, null))
terminalStatus(events, old)
assertTrue(query!!.appearanceOf(old.uniqueId).ready)
reconnect = true
instance.onDisconnect(DisconnectEvent(old, DisconnectEvent.LoginStatus.SUCCESSFUL_LOGIN))
assertTrue(query!!.appearanceOf(replacement.uniqueId).ready)
instance.onShutdown(ProxyShutdownEvent())
}

private fun plugin(
gateway: ResourcePackConfigGateway,
clients: FakeClientFactory,
Expand All @@ -1700,6 +1764,8 @@ class GroundsResourcePacksPluginTest {
chatStyleBridge: PlayerChatStyleBridge = NoopPlayerChatStyleBridge,
sidebarStyleBridge: PlayerChatStyleBridge = NoopPlayerChatStyleBridge,
sidebarProtocol: (UUID) -> Int? = { appearanceProtocol(it)?.protocol },
hudRegistry: PlayerHudAppearanceRegistry = NoopPlayerHudAppearanceRegistry,
appliedHashes: (UUID) -> Set<String> = { emptySet() },
) =
GroundsResourcePacksPlugin(
dataDirectory = directory,
Expand All @@ -1716,6 +1782,8 @@ class GroundsResourcePacksPluginTest {
chatStyleBridge = chatStyleBridge,
sidebarStyleBridge = sidebarStyleBridge,
sidebarProtocol = sidebarProtocol,
hudRegistry = hudRegistry,
appliedHashes = appliedHashes,
)

private fun fireSubscribed(plugin: GroundsResourcePacksPlugin, event: Any) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package gg.grounds.resourcepacks.velocity

import java.util.UUID
import kotlin.test.Test
import kotlin.test.assertFalse
import kotlin.test.assertTrue

class PlayerHudAppearanceTest {
@Test
fun `only explicit HUD hashes enable a loaded pack`() {
val id = UUID.randomUUID()
val pack = UUID.randomUUID()
val hash = "a".repeat(40)
val tracker =
PlayerChatAppearanceTracker(
HudAppearanceEnvironment.from(mapOf("HUD_FONT_PACK_SHA1S" to hash))
)
tracker.offered(id, mapOf(pack to hash))
assertFalse(tracker.chatFontReady(id))
tracker.loaded(id, pack)
assertTrue(tracker.chatFontReady(id))
assertTrue(HudAppearanceEnvironment.eligible(777, false))
assertFalse(HudAppearanceEnvironment.eligible(777, true))
assertFalse(HudAppearanceEnvironment.eligible(776, false))
tracker.failed(id, pack)
assertFalse(tracker.chatFontReady(id))
tracker.offered(id, mapOf(pack to "b".repeat(40)))
tracker.loaded(id, pack)
assertFalse(tracker.chatFontReady(id))
tracker.forget(id)
assertFalse(tracker.chatFontReady(id))
}

@Test
fun `chat hash configuration never enables HUD`() {
assertTrue(
HudAppearanceEnvironment.from(mapOf("CHAT_FONT_PACK_SHA1S" to "a".repeat(40))).isEmpty()
)
}

@Test
fun `removal from currently applied packs revokes readiness`() {
val id = UUID.randomUUID()
val pack = UUID.randomUUID()
val hash = "a".repeat(40)
val tracker = PlayerChatAppearanceTracker(setOf(hash))
tracker.offered(id, mapOf(pack to hash))
tracker.loaded(id, pack)
var applied = setOf(hash)
val query =
ResourcePackPlayerHudAppearanceQuery(
tracker,
setOf(hash),
{ 777 },
ChatPlatformLookup { false },
{ applied },
)
assertTrue(query.appearanceOf(id).ready)
applied = emptySet()
assertFalse(query.appearanceOf(id).ready)
applied = setOf(hash)
tracker.reset(id)
assertFalse(query.appearanceOf(id).ready)
}
}
Loading