Skip to content
Open
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 @@ -13,6 +13,7 @@
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.player.ResourcePackInfo;
import java.util.UUID;
import net.kyori.adventure.resource.ResourcePackStatus;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.Nullable;

Expand Down Expand Up @@ -144,35 +145,50 @@ public enum Status {
/**
* The resource pack was applied successfully.
*/
SUCCESSFUL,
SUCCESSFUL(ResourcePackStatus.SUCCESSFULLY_LOADED),
/**
* The player declined to download the resource pack.
*/
DECLINED,
DECLINED(ResourcePackStatus.DECLINED),
/**
* The player could not download the resource pack.
*/
FAILED_DOWNLOAD,
FAILED_DOWNLOAD(ResourcePackStatus.FAILED_DOWNLOAD),
/**
* The player has accepted the resource pack and is now downloading it.
*/
ACCEPTED,
ACCEPTED(ResourcePackStatus.ACCEPTED),
/**
* The player has downloaded the resource pack.
*/
DOWNLOADED,
DOWNLOADED(ResourcePackStatus.DOWNLOADED),
/**
* The URL of the resource pack failed to load.
*/
INVALID_URL,
INVALID_URL(ResourcePackStatus.INVALID_URL),
/**
* The player failed to reload the resource pack.
*/
FAILED_RELOAD,
FAILED_RELOAD(ResourcePackStatus.FAILED_RELOAD),
/**
* The resource pack was discarded.
*/
DISCARDED;
DISCARDED(ResourcePackStatus.DISCARDED);

private final ResourcePackStatus adventureStatus;

Status(ResourcePackStatus adventureStatus) {
this.adventureStatus = adventureStatus;
}

/**
* Returns the Adventure {@link ResourcePackStatus} corresponding to this Velocity status.
*
* @return the matching Adventure status
*/
public ResourcePackStatus adventureStatus() {
return adventureStatus;
}

/**
* Returns true if the resource pack status is intermediate, indicating that the player has
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ public ResourcePackInfo getFirstPendingPack() {
}

@Override
public void clearAppliedResourcePacks() {
protected void doClearAppliedResourcePacks() {
// This is valid only for players with 1.20.2 versions
this.appliedResourcePack = null;
}
Expand Down Expand Up @@ -132,9 +132,9 @@ public boolean onResourcePackResponse(
final ResourcePackInfo queued = peek
? outstandingResourcePacks.peek() : outstandingResourcePacks.poll();

server.getEventManager()
.fire(new PlayerResourcePackStatusEvent(
this.player, bundle.uuid(), bundle.status(), queued))
dispatchPackCallback(bundle.uuid(), bundle.status())
.thenCompose(v -> server.getEventManager()
.fire(new PlayerResourcePackStatusEvent(this.player, bundle.uuid(), bundle.status(), queued)))
.thenAcceptAsync(event -> {
if (shouldDisconnectForForcePack(event)) {
event.getPlayer().disconnect(Component
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ public final class ModernResourcePackHandler extends ResourcePackHandler {
}

@Override
public void clearAppliedResourcePacks() {
protected void doClearAppliedResourcePacks() {
this.outstandingResourcePacks.clear();
this.pendingResourcePacks.clear();
this.appliedResourcePacks.clear();
Expand Down Expand Up @@ -126,8 +126,9 @@ public boolean onResourcePackResponse(
final ResourcePackInfo queued = outstandingResourcePacks.isEmpty() ? null :
peek ? outstandingResourcePacks.getFirst() : outstandingResourcePacks.removeFirst();

server.getEventManager()
.fire(new PlayerResourcePackStatusEvent(this.player, uuid, bundle.status(), queued))
dispatchPackCallback(uuid, bundle.status())
.thenCompose(v -> server.getEventManager()
.fire(new PlayerResourcePackStatusEvent(this.player, uuid, bundle.status(), queued)))
.thenAcceptAsync(event -> {
if (event.getStatus() == PlayerResourcePackStatusEvent.Status.DECLINED
&& event.getPackInfo() != null && event.getPackInfo().getShouldForce()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

package com.velocitypowered.proxy.connection.player.resourcepack.handler;

import com.velocitypowered.api.event.player.PlayerResourcePackStatusEvent;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.player.ResourcePackInfo;
import com.velocitypowered.proxy.VelocityServer;
Expand All @@ -29,8 +30,14 @@
import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder;
import io.netty.buffer.ByteBufUtil;
import java.util.Collection;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import net.kyori.adventure.resource.ResourcePackCallback;
import net.kyori.adventure.resource.ResourcePackRequest;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

Expand All @@ -39,9 +46,13 @@
*/
public abstract sealed class ResourcePackHandler
permits LegacyResourcePackHandler, ModernResourcePackHandler {
private static final Logger LOGGER = LogManager.getLogger(ResourcePackHandler.class);

protected final ConnectedPlayer player;
protected final VelocityServer server;

private final Map<UUID, ResourcePackCallback> packCallbacks = new ConcurrentHashMap<>();

protected ResourcePackHandler(final ConnectedPlayer player, final VelocityServer server) {
this.player = player;
this.server = server;
Expand Down Expand Up @@ -78,7 +89,15 @@ protected ResourcePackHandler(final ConnectedPlayer player, final VelocityServer
/**
* Clears the applied resource pack field.
*/
public abstract void clearAppliedResourcePacks();
public final void clearAppliedResourcePacks() {
packCallbacks.clear();
doClearAppliedResourcePacks();
}

/**
* Clears the applied resource pack field.
*/
protected abstract void doClearAppliedResourcePacks();

public abstract boolean remove(final UUID id);

Expand All @@ -93,9 +112,14 @@ protected ResourcePackHandler(final ConnectedPlayer player, final VelocityServer
* empty.
*/
public void queueResourcePack(final @NotNull ResourcePackRequest request) {
ResourcePackCallback callback = request.callback();
boolean trackCallback = callback != ResourcePackCallback.noOp();
for (final net.kyori.adventure.resource.ResourcePackInfo pack : request.packs()) {
final ResourcePackInfo resourcePackInfo = VelocityResourcePackInfo.fromAdventureRequest(request, pack);
this.checkAlreadyAppliedPack(resourcePackInfo.getHash());
if (trackCallback) {
packCallbacks.put(resourcePackInfo.getId(), callback);
}
queueResourcePack(resourcePackInfo);
}
}
Expand Down Expand Up @@ -164,6 +188,37 @@ protected boolean handleResponseResult(
return handled;
}

/**
* Invokes the Adventure {@link ResourcePackCallback} (if any) registered for the given pack
* UUID via {@code sendResourcePacks(ResourcePackRequest)}, then evicts the entry on a terminal
* status. Called by the per-version handlers when a {@code ResourcePackResponsePacket} arrives,
* before the {@link PlayerResourcePackStatusEvent} fire so the two cannot observe each other
* mid-flight. Callback execution is dispatched asynchronously off the player's connection event
* loop, since slow plugin callback handlers would otherwise stall the player's IO thread.
*
* @param uuid the pack UUID from the client response
* @param status the Velocity-side status reported by the client
* @return a future that completes once the registered callback returns, or an already-completed
* future when no callback was registered
*/
protected CompletableFuture<Void> dispatchPackCallback(@NotNull UUID uuid,
@NotNull PlayerResourcePackStatusEvent.Status status) {
ResourcePackCallback callback = status.isIntermediate()
? packCallbacks.get(uuid)
: packCallbacks.remove(uuid);
if (callback == null) {
return CompletableFuture.completedFuture(null);
}

return CompletableFuture.runAsync(() -> {
try {
callback.packEventReceived(uuid, status.adventureStatus(), player);
} catch (Throwable t) {
LOGGER.error("Couldn't pass resource pack callback for pack {} to {}", uuid, player, t);
}
});
}

/**
* Check if a pack has already been applied.
*
Expand Down