geforkt von Mirrors/Velocity
Refactor ResourcePacks handling (#1225)
* Initial ResourcePack refactor * Implement sendResourcePacks method * Initializes the ResourcePackHandler at player initialization * Move adventure to velocity resource pack conversion to the same class * Added some internal resource pack documentation * Refactored Modern ResourcePack handling * Handle RemoveResourcePackPacket from backend server * Fixed license * Use removeIf instead of manual iteration * Improve ModernResourcePackHandler * fix hash conversion * bundle resource packs * keep old constructors of PlayerResourcePackStatusEvent * add @Nullable to PlayerResourcePackStatusEvent#getPackId * Use a single instance of BundleDelimiterPacket * Throw UnSupportedOperationException on operations not supported by LegacyResourcePackHandler * Use a single instance on empty packets * Handle active packet bundle sending from backend server in case of sending a packet bundle of resource packs * Improve packet bundling * Fixed login for players with version 1.20.2 --------- Co-authored-by: Gero <gecam59@gmail.com>
Dieser Commit ist enthalten in:
Ursprung
825c3c68d1
Commit
cbd07b1434
@ -12,6 +12,7 @@ import com.velocitypowered.api.event.annotation.AwaitingEvent;
|
||||
import com.velocitypowered.api.network.ProtocolVersion;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import com.velocitypowered.api.proxy.player.ResourcePackInfo;
|
||||
import java.util.UUID;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
@ -24,6 +25,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
public class PlayerResourcePackStatusEvent {
|
||||
|
||||
private final Player player;
|
||||
private final @MonotonicNonNull UUID packId;
|
||||
private final Status status;
|
||||
private final @MonotonicNonNull ResourcePackInfo packInfo;
|
||||
private boolean overwriteKick;
|
||||
@ -32,20 +34,31 @@ public class PlayerResourcePackStatusEvent {
|
||||
* Instantiates this event.
|
||||
*
|
||||
* @deprecated Use {@link PlayerResourcePackStatusEvent#PlayerResourcePackStatusEvent
|
||||
* (Player, Status, ResourcePackInfo)} instead.
|
||||
* (Player, UUID, Status, ResourcePackInfo)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public PlayerResourcePackStatusEvent(Player player, Status status) {
|
||||
this.player = Preconditions.checkNotNull(player, "player");
|
||||
this.status = Preconditions.checkNotNull(status, "status");
|
||||
this.packInfo = null;
|
||||
this(player, null, status, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates this event.
|
||||
*
|
||||
* @deprecated Use {@link PlayerResourcePackStatusEvent#PlayerResourcePackStatusEvent
|
||||
* (Player, UUID, Status, ResourcePackInfo)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public PlayerResourcePackStatusEvent(Player player, Status status, ResourcePackInfo packInfo) {
|
||||
this(player, null, status, packInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates this event.
|
||||
*/
|
||||
public PlayerResourcePackStatusEvent(Player player, Status status, ResourcePackInfo packInfo) {
|
||||
public PlayerResourcePackStatusEvent(
|
||||
Player player, UUID packId, Status status, ResourcePackInfo packInfo) {
|
||||
this.player = Preconditions.checkNotNull(player, "player");
|
||||
this.packId = packId == null ? packInfo == null ? null : packInfo.getId() : packId;
|
||||
this.status = Preconditions.checkNotNull(status, "status");
|
||||
this.packInfo = packInfo;
|
||||
}
|
||||
@ -59,6 +72,16 @@ public class PlayerResourcePackStatusEvent {
|
||||
return player;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the id of the resource pack.
|
||||
*
|
||||
* @return the id
|
||||
*/
|
||||
@Nullable
|
||||
public UUID getPackId() {
|
||||
return packId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the new status for the resource pack.
|
||||
*
|
||||
|
@ -8,13 +8,14 @@
|
||||
package com.velocitypowered.api.proxy.player;
|
||||
|
||||
import java.util.UUID;
|
||||
import net.kyori.adventure.resource.ResourcePackRequestLike;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* Represents the information for a resource pack to apply that can be sent to the client.
|
||||
*/
|
||||
public interface ResourcePackInfo {
|
||||
public interface ResourcePackInfo extends ResourcePackRequestLike {
|
||||
|
||||
/**
|
||||
* Gets the id of this resource-pack.
|
||||
|
@ -20,6 +20,7 @@ package com.velocitypowered.proxy.connection;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.packet.AvailableCommandsPacket;
|
||||
import com.velocitypowered.proxy.protocol.packet.BossBarPacket;
|
||||
import com.velocitypowered.proxy.protocol.packet.BundleDelimiterPacket;
|
||||
import com.velocitypowered.proxy.protocol.packet.ClientSettingsPacket;
|
||||
import com.velocitypowered.proxy.protocol.packet.DisconnectPacket;
|
||||
import com.velocitypowered.proxy.protocol.packet.EncryptionRequestPacket;
|
||||
@ -324,4 +325,8 @@ public interface MinecraftSessionHandler {
|
||||
default boolean handle(ChatAcknowledgementPacket chatAcknowledgement) {
|
||||
return false;
|
||||
}
|
||||
|
||||
default boolean handle(BundleDelimiterPacket bundleDelimiterPacket) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -35,13 +35,16 @@ import com.velocitypowered.proxy.command.CommandGraphInjector;
|
||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||
import com.velocitypowered.proxy.connection.client.ClientPlaySessionHandler;
|
||||
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
|
||||
import com.velocitypowered.proxy.connection.player.VelocityResourcePackInfo;
|
||||
import com.velocitypowered.proxy.connection.player.resourcepack.ResourcePackHandler;
|
||||
import com.velocitypowered.proxy.connection.util.ConnectionMessages;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||
import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder;
|
||||
import com.velocitypowered.proxy.protocol.packet.AvailableCommandsPacket;
|
||||
import com.velocitypowered.proxy.protocol.packet.BossBarPacket;
|
||||
import com.velocitypowered.proxy.protocol.packet.BundleDelimiterPacket;
|
||||
import com.velocitypowered.proxy.protocol.packet.ClientSettingsPacket;
|
||||
import com.velocitypowered.proxy.protocol.packet.DisconnectPacket;
|
||||
import com.velocitypowered.proxy.protocol.packet.KeepAlivePacket;
|
||||
@ -125,6 +128,12 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(BundleDelimiterPacket bundleDelimiterPacket) {
|
||||
serverConn.getPlayer().getBundleHandler().toggleBundleSession();
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(StartUpdatePacket packet) {
|
||||
MinecraftConnection smc = serverConn.ensureConnected();
|
||||
@ -188,13 +197,13 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
|
||||
return;
|
||||
}
|
||||
if (serverResourcePackSendEvent.getResult().isAllowed()) {
|
||||
ResourcePackInfo toSend = serverResourcePackSendEvent.getProvidedResourcePack();
|
||||
final ResourcePackInfo toSend = serverResourcePackSendEvent.getProvidedResourcePack();
|
||||
if (toSend != serverResourcePackSendEvent.getReceivedResourcePack()) {
|
||||
((VelocityResourcePackInfo) toSend)
|
||||
.setOriginalOrigin(ResourcePackInfo.Origin.DOWNSTREAM_SERVER);
|
||||
}
|
||||
|
||||
serverConn.getPlayer().queueResourcePack(toSend);
|
||||
serverConn.getPlayer().resourcePackHandler().queueResourcePack(toSend);
|
||||
} else if (serverConn.getConnection() != null) {
|
||||
serverConn.getConnection().write(new ResourcePackResponsePacket(
|
||||
packet.getId(),
|
||||
@ -219,7 +228,15 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
|
||||
|
||||
@Override
|
||||
public boolean handle(RemoveResourcePackPacket packet) {
|
||||
return false; //TODO
|
||||
final ConnectedPlayer player = serverConn.getPlayer();
|
||||
final ResourcePackHandler handler = player.resourcePackHandler();
|
||||
if (packet.getId() != null) {
|
||||
handler.remove(packet.getId());
|
||||
} else {
|
||||
handler.clearAppliedResourcePacks();
|
||||
}
|
||||
playerConnection.write(packet);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -84,8 +84,8 @@ public class ConfigSessionHandler implements MinecraftSessionHandler {
|
||||
public void activated() {
|
||||
ConnectedPlayer player = serverConn.getPlayer();
|
||||
if (player.getProtocolVersion() == ProtocolVersion.MINECRAFT_1_20_2) {
|
||||
resourcePackToApply = player.getAppliedResourcePack();
|
||||
player.clearAppliedResourcePack();
|
||||
resourcePackToApply = player.resourcePackHandler().getFirstAppliedPack();
|
||||
player.resourcePackHandler().clearAppliedResourcePacks();
|
||||
}
|
||||
}
|
||||
|
||||
@ -136,7 +136,7 @@ public class ConfigSessionHandler implements MinecraftSessionHandler {
|
||||
}
|
||||
|
||||
resourcePackToApply = null;
|
||||
serverConn.getPlayer().queueResourcePack(toSend);
|
||||
serverConn.getPlayer().resourcePackHandler().queueResourcePack(toSend);
|
||||
} else if (serverConn.getConnection() != null) {
|
||||
serverConn.getConnection().write(new ResourcePackResponsePacket(
|
||||
packet.getId(), packet.getHash(), PlayerResourcePackStatusEvent.Status.DECLINED));
|
||||
@ -174,8 +174,9 @@ public class ConfigSessionHandler implements MinecraftSessionHandler {
|
||||
smc.setActiveSessionHandler(StateRegistry.PLAY,
|
||||
new TransitionSessionHandler(server, serverConn, resultFuture));
|
||||
}
|
||||
if (player.getAppliedResourcePack() == null && resourcePackToApply != null) {
|
||||
player.queueResourcePack(resourcePackToApply);
|
||||
if (player.resourcePackHandler().getFirstAppliedPack() == null
|
||||
&& resourcePackToApply != null) {
|
||||
player.resourcePackHandler().queueResourcePack(resourcePackToApply);
|
||||
}
|
||||
smc.setAutoReading(true);
|
||||
}, smc.eventLoop());
|
||||
@ -228,7 +229,7 @@ public class ConfigSessionHandler implements MinecraftSessionHandler {
|
||||
/**
|
||||
* Represents the state of the configuration stage.
|
||||
*/
|
||||
public static enum State {
|
||||
public enum State {
|
||||
START, NEGOTIATING, PLUGIN_MESSAGE_INTERRUPT, RESOURCE_PACK_INTERRUPT, COMPLETE
|
||||
}
|
||||
}
|
@ -142,10 +142,9 @@ public class AuthSessionHandler implements MinecraftSessionHandler {
|
||||
}
|
||||
|
||||
if (player.getIdentifiedKey() != null) {
|
||||
IdentifiedKey playerKey = player.getIdentifiedKey();
|
||||
final IdentifiedKey playerKey = player.getIdentifiedKey();
|
||||
if (playerKey.getSignatureHolder() == null) {
|
||||
if (playerKey instanceof IdentifiedKeyImpl) {
|
||||
IdentifiedKeyImpl unlinkedKey = (IdentifiedKeyImpl) playerKey;
|
||||
if (playerKey instanceof IdentifiedKeyImpl unlinkedKey) {
|
||||
// Failsafe
|
||||
if (!unlinkedKey.internalAddHolder(player.getUniqueId())) {
|
||||
if (onlineMode) {
|
||||
@ -153,7 +152,7 @@ public class AuthSessionHandler implements MinecraftSessionHandler {
|
||||
Component.translatable("multiplayer.disconnect.invalid_public_key"));
|
||||
return;
|
||||
} else {
|
||||
logger.warn("Key for player " + player.getUsername() + " could not be verified!");
|
||||
logger.warn("Key for player {} could not be verified!", player.getUsername());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -161,8 +160,9 @@ public class AuthSessionHandler implements MinecraftSessionHandler {
|
||||
}
|
||||
} else {
|
||||
if (!Objects.equals(playerKey.getSignatureHolder(), playerUniqueId)) {
|
||||
logger.warn("UUID for Player " + player.getUsername() + " mismatches! "
|
||||
+ "Chat/Commands signatures will not work correctly for this player!");
|
||||
logger.warn("UUID for Player {} mismatches! "
|
||||
+ "Chat/Commands signatures will not work correctly for this player!",
|
||||
player.getUsername());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -240,7 +240,7 @@ public class AuthSessionHandler implements MinecraftSessionHandler {
|
||||
|
||||
return server.getEventManager().fire(event).thenRunAsync(() -> {
|
||||
Optional<RegisteredServer> toTry = event.getInitialServer();
|
||||
if (!toTry.isPresent()) {
|
||||
if (toTry.isEmpty()) {
|
||||
player.disconnect0(
|
||||
Component.translatable("velocity.error.no-available-servers", NamedTextColor.RED),
|
||||
true);
|
||||
@ -263,7 +263,7 @@ public class AuthSessionHandler implements MinecraftSessionHandler {
|
||||
this.inbound.cleanup();
|
||||
}
|
||||
|
||||
static enum State {
|
||||
enum State {
|
||||
START, SUCCESS_SENT, ACKNOWLEDGED
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Velocity Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.velocitypowered.proxy.connection.client;
|
||||
|
||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
|
||||
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||
import com.velocitypowered.proxy.protocol.packet.BundleDelimiterPacket;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/**
|
||||
* BundleDelimiterHandler.
|
||||
*/
|
||||
public final class BundleDelimiterHandler {
|
||||
private final ConnectedPlayer player;
|
||||
private boolean inBundleSession = false;
|
||||
private CompletableFuture<Void> finishedBundleSessionFuture;
|
||||
|
||||
public BundleDelimiterHandler(ConnectedPlayer player) {
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
public boolean isInBundleSession() {
|
||||
return this.inBundleSession;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the player to be in the process of receiving multiple packets
|
||||
* from the backend server via a packet bundle.
|
||||
*/
|
||||
public void toggleBundleSession() {
|
||||
if (this.inBundleSession) {
|
||||
this.finishedBundleSessionFuture.complete(null);
|
||||
this.finishedBundleSessionFuture = null;
|
||||
} else {
|
||||
this.finishedBundleSessionFuture = new CompletableFuture<>();
|
||||
}
|
||||
this.inBundleSession = !this.inBundleSession;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bundles all packets sent in the given Runnable.
|
||||
*/
|
||||
public CompletableFuture<Void> bundlePackets(final Runnable sendPackets) {
|
||||
VelocityServerConnection connectedServer = player.getConnectedServer();
|
||||
MinecraftConnection connection = connectedServer == null
|
||||
? null : connectedServer.getConnection();
|
||||
if (connection == null) {
|
||||
sendPackets(sendPackets);
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
CompletableFuture<Void> future = new CompletableFuture<>();
|
||||
connection.eventLoop().execute(() -> {
|
||||
if (inBundleSession) {
|
||||
finishedBundleSessionFuture.thenRun(() -> {
|
||||
sendPackets(sendPackets);
|
||||
future.complete(null);
|
||||
});
|
||||
} else {
|
||||
if (connection.getState() == StateRegistry.PLAY) {
|
||||
sendPackets(sendPackets);
|
||||
} else {
|
||||
sendPackets.run();
|
||||
}
|
||||
future.complete(null);
|
||||
}
|
||||
});
|
||||
return future;
|
||||
}
|
||||
|
||||
private void sendPackets(Runnable sendPackets) {
|
||||
player.getConnection().write(BundleDelimiterPacket.INSTANCE);
|
||||
try {
|
||||
sendPackets.run();
|
||||
} finally {
|
||||
player.getConnection().write(BundleDelimiterPacket.INSTANCE);
|
||||
}
|
||||
}
|
||||
}
|
@ -22,6 +22,7 @@ import com.velocitypowered.proxy.VelocityServer;
|
||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
|
||||
import com.velocitypowered.proxy.connection.player.resourcepack.ResourcePackResponseBundle;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||
@ -70,17 +71,13 @@ public class ClientConfigSessionHandler implements MinecraftSessionHandler {
|
||||
configSwitchFuture = new CompletableFuture<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deactivated() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(KeepAlivePacket packet) {
|
||||
VelocityServerConnection serverConnection = player.getConnectedServer();
|
||||
final VelocityServerConnection serverConnection = player.getConnectedServer();
|
||||
if (serverConnection != null) {
|
||||
Long sentTime = serverConnection.getPendingPings().remove(packet.getRandomId());
|
||||
final Long sentTime = serverConnection.getPendingPings().remove(packet.getRandomId());
|
||||
if (sentTime != null) {
|
||||
MinecraftConnection smc = serverConnection.getConnection();
|
||||
final MinecraftConnection smc = serverConnection.getConnection();
|
||||
if (smc != null) {
|
||||
player.setPing(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - sentTime));
|
||||
smc.write(packet);
|
||||
@ -101,7 +98,9 @@ public class ClientConfigSessionHandler implements MinecraftSessionHandler {
|
||||
if (player.getConnectionInFlight() != null) {
|
||||
player.getConnectionInFlight().ensureConnected().write(packet);
|
||||
}
|
||||
return player.onResourcePackResponse(packet.getStatus());
|
||||
return player.resourcePackHandler().onResourcePackResponse(
|
||||
new ResourcePackResponseBundle(packet.getId(), packet.getStatus())
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -196,9 +195,9 @@ public class ClientConfigSessionHandler implements MinecraftSessionHandler {
|
||||
smc.write(brandPacket);
|
||||
}
|
||||
|
||||
player.getConnection().write(new FinishedUpdatePacket());
|
||||
player.getConnection().write(FinishedUpdatePacket.INSTANCE);
|
||||
|
||||
smc.write(new FinishedUpdatePacket());
|
||||
smc.write(FinishedUpdatePacket.INSTANCE);
|
||||
smc.getChannel().pipeline().get(MinecraftEncoder.class).setState(StateRegistry.PLAY);
|
||||
|
||||
return configSwitchFuture;
|
||||
|
@ -38,6 +38,7 @@ import com.velocitypowered.proxy.connection.backend.BackendConnectionPhases;
|
||||
import com.velocitypowered.proxy.connection.backend.BungeeCordMessageResponder;
|
||||
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
|
||||
import com.velocitypowered.proxy.connection.forge.legacy.LegacyForgeConstants;
|
||||
import com.velocitypowered.proxy.connection.player.resourcepack.ResourcePackResponseBundle;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||
import com.velocitypowered.proxy.protocol.packet.BossBarPacket;
|
||||
@ -172,11 +173,11 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
|
||||
@Override
|
||||
public boolean handle(KeepAlivePacket packet) {
|
||||
VelocityServerConnection serverConnection = player.getConnectedServer();
|
||||
final VelocityServerConnection serverConnection = player.getConnectedServer();
|
||||
if (serverConnection != null) {
|
||||
Long sentTime = serverConnection.getPendingPings().remove(packet.getRandomId());
|
||||
final Long sentTime = serverConnection.getPendingPings().remove(packet.getRandomId());
|
||||
if (sentTime != null) {
|
||||
MinecraftConnection smc = serverConnection.getConnection();
|
||||
final MinecraftConnection smc = serverConnection.getConnection();
|
||||
if (smc != null) {
|
||||
player.setPing(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - sentTime));
|
||||
smc.write(packet);
|
||||
@ -204,7 +205,6 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
if (!updateTimeKeeper(packet.getTimeStamp())) {
|
||||
return true;
|
||||
}
|
||||
@ -392,7 +392,8 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
|
||||
@Override
|
||||
public boolean handle(ResourcePackResponsePacket packet) {
|
||||
return player.onResourcePackResponse(packet.getStatus());
|
||||
return player.resourcePackHandler().onResourcePackResponse(
|
||||
new ResourcePackResponseBundle(packet.getId(), packet.getStatus()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -31,7 +31,6 @@ import com.velocitypowered.api.event.player.KickedFromServerEvent.Notify;
|
||||
import com.velocitypowered.api.event.player.KickedFromServerEvent.RedirectPlayer;
|
||||
import com.velocitypowered.api.event.player.KickedFromServerEvent.ServerKickResult;
|
||||
import com.velocitypowered.api.event.player.PlayerModInfoEvent;
|
||||
import com.velocitypowered.api.event.player.PlayerResourcePackStatusEvent;
|
||||
import com.velocitypowered.api.event.player.PlayerSettingsChangedEvent;
|
||||
import com.velocitypowered.api.event.player.ServerPreConnectEvent;
|
||||
import com.velocitypowered.api.network.ProtocolVersion;
|
||||
@ -55,6 +54,7 @@ import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||
import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation;
|
||||
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
|
||||
import com.velocitypowered.proxy.connection.player.VelocityResourcePackInfo;
|
||||
import com.velocitypowered.proxy.connection.player.resourcepack.ResourcePackHandler;
|
||||
import com.velocitypowered.proxy.connection.util.ConnectionMessages;
|
||||
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults.Impl;
|
||||
import com.velocitypowered.proxy.connection.util.VelocityInboundConnection;
|
||||
@ -66,7 +66,6 @@ import com.velocitypowered.proxy.protocol.packet.HeaderAndFooterPacket;
|
||||
import com.velocitypowered.proxy.protocol.packet.KeepAlivePacket;
|
||||
import com.velocitypowered.proxy.protocol.packet.PluginMessagePacket;
|
||||
import com.velocitypowered.proxy.protocol.packet.RemoveResourcePackPacket;
|
||||
import com.velocitypowered.proxy.protocol.packet.ResourcePackRequestPacket;
|
||||
import com.velocitypowered.proxy.protocol.packet.chat.ChatQueue;
|
||||
import com.velocitypowered.proxy.protocol.packet.chat.ChatType;
|
||||
import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder;
|
||||
@ -82,19 +81,14 @@ import com.velocitypowered.proxy.tablist.VelocityTabListLegacy;
|
||||
import com.velocitypowered.proxy.util.ClosestLocaleMatcher;
|
||||
import com.velocitypowered.proxy.util.DurationUtils;
|
||||
import com.velocitypowered.proxy.util.TranslatableMapper;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
import java.util.Queue;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
@ -129,7 +123,6 @@ import org.jetbrains.annotations.NotNull;
|
||||
public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, KeyIdentifiable,
|
||||
VelocityInboundConnection {
|
||||
|
||||
private static final int MAX_PLUGIN_CHANNELS = 1024;
|
||||
private static final PlainTextComponentSerializer PASS_THRU_TRANSLATE =
|
||||
PlainTextComponentSerializer.builder().flattener(TranslatableMapper.FLATTENER).build();
|
||||
static final PermissionProvider DEFAULT_PERMISSIONS = s -> PermissionFunction.ALWAYS_UNDEFINED;
|
||||
@ -159,14 +152,12 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
||||
private ClientConnectionPhase connectionPhase;
|
||||
private final CompletableFuture<Void> teardownFuture = new CompletableFuture<>();
|
||||
private @MonotonicNonNull List<String> serversToTry = null;
|
||||
private @MonotonicNonNull Boolean previousResourceResponse;
|
||||
private final Queue<ResourcePackInfo> outstandingResourcePacks = new ArrayDeque<>();
|
||||
private @Nullable ResourcePackInfo pendingResourcePack;
|
||||
private @Nullable ResourcePackInfo appliedResourcePack;
|
||||
private @NotNull List<ResourcePackInfo> pendingResourcePacks = new ArrayList<>();
|
||||
private @NotNull List<ResourcePackInfo> appliedResourcePacks = new ArrayList<>();
|
||||
private final ResourcePackHandler resourcePackHandler;
|
||||
private final BundleDelimiterHandler bundleHandler = new BundleDelimiterHandler(this);
|
||||
|
||||
private final @NotNull Pointers pointers =
|
||||
Player.super.pointers().toBuilder().withDynamic(Identity.UUID, this::getUniqueId)
|
||||
Player.super.pointers().toBuilder()
|
||||
.withDynamic(Identity.UUID, this::getUniqueId)
|
||||
.withDynamic(Identity.NAME, this::getUsername)
|
||||
.withDynamic(Identity.DISPLAY_NAME, () -> Component.text(this.getUsername()))
|
||||
.withDynamic(Identity.LOCALE, this::getEffectiveLocale)
|
||||
@ -174,7 +165,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
||||
.withStatic(FacetPointers.TYPE, Type.PLAYER).build();
|
||||
private @Nullable String clientBrand;
|
||||
private @Nullable Locale effectiveLocale;
|
||||
private @Nullable IdentifiedKey playerKey;
|
||||
private final @Nullable IdentifiedKey playerKey;
|
||||
private @Nullable ClientSettingsPacket clientSettingsPacket;
|
||||
private final ChatQueue chatQueue;
|
||||
private final ChatBuilderFactory chatBuilderFactory;
|
||||
@ -200,6 +191,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
||||
this.playerKey = playerKey;
|
||||
this.chatQueue = new ChatQueue(this);
|
||||
this.chatBuilderFactory = new ChatBuilderFactory(this.getProtocolVersion());
|
||||
this.resourcePackHandler = ResourcePackHandler.create(this, server);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -219,6 +211,10 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
||||
return chatQueue;
|
||||
}
|
||||
|
||||
public BundleDelimiterHandler getBundleHandler() {
|
||||
return this.bundleHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull Identity identity() {
|
||||
return this.identity;
|
||||
@ -238,7 +234,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEffectiveLocale(Locale locale) {
|
||||
public void setEffectiveLocale(final @Nullable Locale locale) {
|
||||
effectiveLocale = locale;
|
||||
}
|
||||
|
||||
@ -293,6 +289,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
||||
return settings == null ? ClientSettingsWrapper.DEFAULT : this.settings;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ClientSettingsPacket getClientSettingsPacket() {
|
||||
return clientSettingsPacket;
|
||||
}
|
||||
@ -367,7 +364,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
||||
|
||||
@Override
|
||||
public void sendMessage(@NonNull Identity identity, @NonNull Component message) {
|
||||
Component translated = translateMessage(message);
|
||||
final Component translated = translateMessage(message);
|
||||
|
||||
connection.write(getChatBuilderFactory().builder()
|
||||
.component(translated).forIdentity(identity).toClient());
|
||||
@ -432,7 +429,8 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPlayerListHeaderAndFooter(final Component header, final Component footer) {
|
||||
public void sendPlayerListHeaderAndFooter(final @NotNull Component header,
|
||||
final @NotNull Component footer) {
|
||||
Component translatedHeader = translateMessage(header);
|
||||
Component translatedFooter = translateMessage(footer);
|
||||
this.playerListHeader = translatedHeader;
|
||||
@ -472,6 +470,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantValue")
|
||||
@Override
|
||||
public <T> void sendTitlePart(@NotNull TitlePart<T> part, @NotNull T value) {
|
||||
if (part == null) {
|
||||
@ -744,11 +743,9 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.getResult() instanceof DisconnectPlayer) {
|
||||
DisconnectPlayer res = (DisconnectPlayer) event.getResult();
|
||||
if (event.getResult() instanceof final DisconnectPlayer res) {
|
||||
disconnect(res.getReasonComponent());
|
||||
} else if (event.getResult() instanceof RedirectPlayer) {
|
||||
RedirectPlayer res = (RedirectPlayer) event.getResult();
|
||||
} else if (event.getResult() instanceof final RedirectPlayer res) {
|
||||
createConnectionRequest(res.getServer(), previousConnection).connect()
|
||||
.whenCompleteAsync((status, throwable) -> {
|
||||
if (throwable != null) {
|
||||
@ -794,8 +791,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
||||
break;
|
||||
}
|
||||
}, connection.eventLoop());
|
||||
} else if (event.getResult() instanceof Notify) {
|
||||
Notify res = (Notify) event.getResult();
|
||||
} else if (event.getResult() instanceof final Notify res) {
|
||||
if (event.kickedDuringServerConnect() && previousConnection != null) {
|
||||
sendMessage(Identity.nil(), res.getMessageComponent());
|
||||
} else {
|
||||
@ -906,7 +902,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
||||
|
||||
DisconnectEvent.LoginStatus status;
|
||||
if (connectedPlayer.isPresent()) {
|
||||
if (!connectedPlayer.get().getCurrentServer().isPresent()) {
|
||||
if (connectedPlayer.get().getCurrentServer().isEmpty()) {
|
||||
status = LoginStatus.PRE_SERVER_JOIN;
|
||||
} else {
|
||||
status = connectedPlayer.get() == this ? LoginStatus.SUCCESSFUL_LOGIN
|
||||
@ -933,9 +929,9 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
boolean isPlayerAddressLoggingEnabled = server.getConfiguration()
|
||||
final boolean isPlayerAddressLoggingEnabled = server.getConfiguration()
|
||||
.isPlayerAddressLoggingEnabled();
|
||||
String playerIp =
|
||||
final String playerIp =
|
||||
isPlayerAddressLoggingEnabled ? getRemoteAddress().toString() : "<ip address withheld>";
|
||||
return "[connected player] " + profile.getName() + " (" + playerIp + ")";
|
||||
}
|
||||
@ -956,11 +952,12 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public String getClientBrand() {
|
||||
return clientBrand;
|
||||
}
|
||||
|
||||
void setClientBrand(String clientBrand) {
|
||||
void setClientBrand(final @Nullable String clientBrand) {
|
||||
this.clientBrand = clientBrand;
|
||||
}
|
||||
|
||||
@ -981,6 +978,15 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ResourcePackHandler corresponding to the player's version.
|
||||
*
|
||||
* @return the ResourcePackHandler of this player
|
||||
*/
|
||||
public ResourcePackHandler resourcePackHandler() {
|
||||
return this.resourcePackHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public void sendResourcePack(String url) {
|
||||
@ -997,7 +1003,15 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
||||
public void sendResourcePackOffer(ResourcePackInfo packInfo) {
|
||||
if (this.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_8)) {
|
||||
Preconditions.checkNotNull(packInfo, "packInfo");
|
||||
queueResourcePack(packInfo);
|
||||
this.resourcePackHandler.queueResourcePack(packInfo);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendResourcePacks(@NotNull ResourcePackRequest request) {
|
||||
if (this.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_8)) {
|
||||
Preconditions.checkNotNull(request, "packRequest");
|
||||
this.resourcePackHandler.queueResourcePack(request);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1005,15 +1019,21 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
||||
public void clearResourcePacks() {
|
||||
if (this.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_20_3)) {
|
||||
connection.write(new RemoveResourcePackPacket());
|
||||
this.resourcePackHandler.clearAppliedResourcePacks();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeResourcePacks(@NotNull UUID id, @NotNull UUID @NotNull ... others) {
|
||||
if (this.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_20_3)) {
|
||||
connection.write(new RemoveResourcePackPacket(id));
|
||||
Preconditions.checkNotNull(id, "packUUID");
|
||||
if (this.resourcePackHandler.remove(id)) {
|
||||
connection.write(new RemoveResourcePackPacket(id));
|
||||
}
|
||||
for (final UUID other : others) {
|
||||
connection.write(new RemoveResourcePackPacket(other));
|
||||
if (this.resourcePackHandler.remove(other)) {
|
||||
connection.write(new RemoveResourcePackPacket(other));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1039,174 +1059,26 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Queues a resource-pack for sending to the player and sends it immediately if the queue is
|
||||
* empty.
|
||||
*/
|
||||
public void queueResourcePack(ResourcePackInfo info) {
|
||||
outstandingResourcePacks.add(info);
|
||||
if (outstandingResourcePacks.size() == 1) {
|
||||
tickResourcePackQueue();
|
||||
}
|
||||
}
|
||||
|
||||
private void tickResourcePackQueue() {
|
||||
ResourcePackInfo queued = outstandingResourcePacks.peek();
|
||||
|
||||
if (queued != null) {
|
||||
// Check if the player declined a resource pack once already
|
||||
if (previousResourceResponse != null && !previousResourceResponse) {
|
||||
// If that happened we can flush the queue right away.
|
||||
// Unless its 1.17+ and forced it will come back denied anyway
|
||||
while (!outstandingResourcePacks.isEmpty()) {
|
||||
queued = outstandingResourcePacks.peek();
|
||||
if (queued.getShouldForce() && getProtocolVersion()
|
||||
.noLessThan(ProtocolVersion.MINECRAFT_1_17)) {
|
||||
break;
|
||||
}
|
||||
onResourcePackResponse(PlayerResourcePackStatusEvent.Status.DECLINED);
|
||||
queued = null;
|
||||
}
|
||||
if (queued == null) {
|
||||
// Exit as the queue was cleared
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ResourcePackRequestPacket request = new ResourcePackRequestPacket();
|
||||
request.setId(queued.getId());
|
||||
request.setUrl(queued.getUrl());
|
||||
if (queued.getHash() != null) {
|
||||
request.setHash(ByteBufUtil.hexDump(queued.getHash()));
|
||||
} else {
|
||||
request.setHash("");
|
||||
}
|
||||
request.setRequired(queued.getShouldForce());
|
||||
request.setPrompt(queued.getPrompt() == null ? null :
|
||||
new ComponentHolder(getProtocolVersion(), queued.getPrompt()));
|
||||
|
||||
connection.write(request);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public @Nullable ResourcePackInfo getAppliedResourcePack() {
|
||||
return appliedResourcePack;
|
||||
return this.resourcePackHandler.getFirstAppliedPack();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public @Nullable ResourcePackInfo getPendingResourcePack() {
|
||||
return pendingResourcePack;
|
||||
return this.resourcePackHandler.getFirstPendingPack();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ResourcePackInfo> getAppliedResourcePacks() {
|
||||
return new ArrayList<>(appliedResourcePacks);
|
||||
return this.resourcePackHandler.getAppliedResourcePacks();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ResourcePackInfo> getPendingResourcePacks() {
|
||||
return new ArrayList<>(pendingResourcePacks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the applied resource pack field.
|
||||
*/
|
||||
public void clearAppliedResourcePack() {
|
||||
appliedResourcePack = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a client response to a sent resource-pack.
|
||||
*/
|
||||
public boolean onResourcePackResponse(PlayerResourcePackStatusEvent.Status status) {
|
||||
final boolean peek = status.isIntermediate();
|
||||
final ResourcePackInfo queued = peek
|
||||
? outstandingResourcePacks.peek() : outstandingResourcePacks.poll();
|
||||
|
||||
server.getEventManager().fire(new PlayerResourcePackStatusEvent(this, status, queued))
|
||||
.thenAcceptAsync(event -> {
|
||||
if (event.getStatus() == PlayerResourcePackStatusEvent.Status.DECLINED
|
||||
&& event.getPackInfo() != null && event.getPackInfo().getShouldForce()
|
||||
&& (!event.isOverwriteKick() || event.getPlayer()
|
||||
.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_17))
|
||||
) {
|
||||
event.getPlayer().disconnect(Component
|
||||
.translatable("multiplayer.requiredTexturePrompt.disconnect"));
|
||||
}
|
||||
});
|
||||
|
||||
switch (status) {
|
||||
case ACCEPTED:
|
||||
previousResourceResponse = true;
|
||||
pendingResourcePack = queued;
|
||||
if (this.getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_20_3)) {
|
||||
pendingResourcePacks.clear();
|
||||
}
|
||||
pendingResourcePacks.add(queued);
|
||||
break;
|
||||
case DECLINED:
|
||||
previousResourceResponse = false;
|
||||
break;
|
||||
case SUCCESSFUL:
|
||||
appliedResourcePack = queued;
|
||||
pendingResourcePack = null;
|
||||
appliedResourcePacks.add(queued);
|
||||
if (this.getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_20_3)) {
|
||||
pendingResourcePacks.clear();
|
||||
}
|
||||
if (queued != null) {
|
||||
pendingResourcePacks.removeIf(resourcePackInfo -> {
|
||||
if (resourcePackInfo.getId() == null) {
|
||||
return resourcePackInfo.getUrl().equals(queued.getUrl())
|
||||
&& Arrays.equals(resourcePackInfo.getHash(), queued.getHash());
|
||||
}
|
||||
return resourcePackInfo.getId().equals(queued.getId());
|
||||
});
|
||||
}
|
||||
break;
|
||||
case FAILED_DOWNLOAD:
|
||||
pendingResourcePack = null;
|
||||
if (this.getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_20_3)) {
|
||||
pendingResourcePacks.clear();
|
||||
}
|
||||
if (queued != null) {
|
||||
pendingResourcePacks.removeIf(resourcePackInfo -> {
|
||||
if (resourcePackInfo.getId() == null) {
|
||||
return resourcePackInfo.getUrl().equals(queued.getUrl())
|
||||
&& Arrays.equals(resourcePackInfo.getHash(), queued.getHash());
|
||||
}
|
||||
return resourcePackInfo.getId().equals(queued.getId());
|
||||
});
|
||||
}
|
||||
break;
|
||||
case DISCARDED:
|
||||
if (queued != null && queued.getId() != null) {
|
||||
appliedResourcePacks.removeIf(resourcePackInfo -> {
|
||||
return queued.getId().equals(resourcePackInfo.getId());
|
||||
});
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (!peek) {
|
||||
connection.eventLoop().execute(this::tickResourcePackQueue);
|
||||
}
|
||||
|
||||
return queued != null
|
||||
&& queued.getOriginalOrigin() != ResourcePackInfo.Origin.DOWNSTREAM_SERVER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives an indication about the previous resource pack responses.
|
||||
*/
|
||||
public @Nullable Boolean getPreviousResourceResponse() {
|
||||
//TODO can probably be removed
|
||||
return previousResourceResponse;
|
||||
return this.resourcePackHandler.getPendingResourcePacks();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1228,7 +1100,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
||||
*/
|
||||
public void switchToConfigState() {
|
||||
CompletableFuture.runAsync(() -> {
|
||||
connection.write(new StartUpdatePacket());
|
||||
connection.write(StartUpdatePacket.INSTANCE);
|
||||
connection.getChannel().pipeline()
|
||||
.get(MinecraftEncoder.class).setState(StateRegistry.CONFIG);
|
||||
// Make sure we don't send any play packets to the player after update start
|
||||
@ -1271,7 +1143,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
||||
}
|
||||
}
|
||||
|
||||
private class ConnectionRequestBuilderImpl implements ConnectionRequestBuilder {
|
||||
private final class ConnectionRequestBuilderImpl implements ConnectionRequestBuilder {
|
||||
|
||||
private final RegisteredServer toConnect;
|
||||
private final @Nullable VelocityRegisteredServer previousServer;
|
||||
@ -1315,7 +1187,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
||||
new ServerPreConnectEvent(ConnectedPlayer.this, toConnect, previousServer);
|
||||
return server.getEventManager().fire(event).thenComposeAsync(newEvent -> {
|
||||
Optional<RegisteredServer> newDest = newEvent.getResult().getServer();
|
||||
if (!newDest.isPresent()) {
|
||||
if (newDest.isEmpty()) {
|
||||
return completedFuture(
|
||||
plainResult(ConnectionRequestBuilder.Status.CONNECTION_CANCELLED, toConnect));
|
||||
}
|
||||
|
@ -67,7 +67,7 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler {
|
||||
@Override
|
||||
public boolean handle(LegacyPingPacket packet) {
|
||||
connection.setProtocolVersion(ProtocolVersion.LEGACY);
|
||||
StatusSessionHandler handler =
|
||||
final StatusSessionHandler handler =
|
||||
new StatusSessionHandler(server, new LegacyInboundConnection(connection, packet));
|
||||
connection.setActiveSessionHandler(StateRegistry.STATUS, handler);
|
||||
handler.handle(packet);
|
||||
@ -85,9 +85,9 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler {
|
||||
|
||||
@Override
|
||||
public boolean handle(HandshakePacket handshake) {
|
||||
InitialInboundConnection ic = new InitialInboundConnection(connection,
|
||||
final InitialInboundConnection ic = new InitialInboundConnection(connection,
|
||||
cleanVhost(handshake.getServerAddress()), handshake);
|
||||
StateRegistry nextState = getStateForProtocol(handshake.getNextStatus());
|
||||
final StateRegistry nextState = getStateForProtocol(handshake.getNextStatus());
|
||||
if (nextState == null) {
|
||||
LOGGER.error("{} provided invalid protocol {}", ic, handshake.getNextStatus());
|
||||
connection.close(true);
|
||||
@ -96,14 +96,10 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler {
|
||||
connection.setAssociation(ic);
|
||||
|
||||
switch (nextState) {
|
||||
case STATUS:
|
||||
connection.setActiveSessionHandler(StateRegistry.STATUS,
|
||||
case STATUS -> connection.setActiveSessionHandler(StateRegistry.STATUS,
|
||||
new StatusSessionHandler(server, ic));
|
||||
break;
|
||||
case LOGIN:
|
||||
this.handleLogin(handshake, ic);
|
||||
break;
|
||||
default:
|
||||
case LOGIN -> this.handleLogin(handshake, ic);
|
||||
default ->
|
||||
// If you get this, it's a bug in Velocity.
|
||||
throw new AssertionError("getStateForProtocol provided invalid state!");
|
||||
}
|
||||
@ -113,24 +109,23 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler {
|
||||
}
|
||||
|
||||
private static @Nullable StateRegistry getStateForProtocol(int status) {
|
||||
switch (status) {
|
||||
case StateRegistry.STATUS_ID:
|
||||
return StateRegistry.STATUS;
|
||||
case StateRegistry.LOGIN_ID:
|
||||
return StateRegistry.LOGIN;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
return switch (status) {
|
||||
case StateRegistry.STATUS_ID -> StateRegistry.STATUS;
|
||||
case StateRegistry.LOGIN_ID -> StateRegistry.LOGIN;
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
|
||||
private void handleLogin(HandshakePacket handshake, InitialInboundConnection ic) {
|
||||
if (!ProtocolVersion.isSupported(handshake.getProtocolVersion())) {
|
||||
ic.disconnectQuietly(Component.translatable("multiplayer.disconnect.outdated_client")
|
||||
.args(Component.text(ProtocolVersion.SUPPORTED_VERSION_STRING)));
|
||||
ic.disconnectQuietly(Component.translatable()
|
||||
.key("multiplayer.disconnect.outdated_client")
|
||||
.arguments(Component.text(ProtocolVersion.SUPPORTED_VERSION_STRING))
|
||||
.build());
|
||||
return;
|
||||
}
|
||||
|
||||
InetAddress address = ((InetSocketAddress) connection.getRemoteAddress()).getAddress();
|
||||
final InetAddress address = ((InetSocketAddress) connection.getRemoteAddress()).getAddress();
|
||||
if (!server.getIpAttemptLimiter().attempt(address)) {
|
||||
// Bump connection into correct protocol state so that we can send the disconnect packet.
|
||||
connection.setState(StateRegistry.LOGIN);
|
||||
@ -151,7 +146,7 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
LoginInboundConnection lic = new LoginInboundConnection(ic);
|
||||
final LoginInboundConnection lic = new LoginInboundConnection(ic);
|
||||
server.getEventManager().fireAndForget(new ConnectionHandshakeEvent(lic));
|
||||
connection.setActiveSessionHandler(StateRegistry.LOGIN,
|
||||
new InitialLoginSessionHandler(server, connection, lic));
|
||||
@ -213,16 +208,10 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler {
|
||||
connection.close(true);
|
||||
}
|
||||
|
||||
private static class LegacyInboundConnection implements VelocityInboundConnection {
|
||||
|
||||
private final MinecraftConnection connection;
|
||||
private final LegacyPingPacket ping;
|
||||
|
||||
private LegacyInboundConnection(MinecraftConnection connection,
|
||||
LegacyPingPacket ping) {
|
||||
this.connection = connection;
|
||||
this.ping = ping;
|
||||
}
|
||||
private record LegacyInboundConnection(
|
||||
MinecraftConnection connection,
|
||||
LegacyPingPacket ping
|
||||
) implements VelocityInboundConnection {
|
||||
|
||||
@Override
|
||||
public InetSocketAddress getRemoteAddress() {
|
||||
|
@ -74,9 +74,9 @@ public final class InitialInboundConnection implements VelocityInboundConnection
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
boolean isPlayerAddressLoggingEnabled = connection.server.getConfiguration()
|
||||
final boolean isPlayerAddressLoggingEnabled = connection.server.getConfiguration()
|
||||
.isPlayerAddressLoggingEnabled();
|
||||
String playerIp =
|
||||
final String playerIp =
|
||||
isPlayerAddressLoggingEnabled
|
||||
? connection.getRemoteAddress().toString() : "<ip address withheld>";
|
||||
return "[initial connection] " + playerIp;
|
||||
|
@ -95,7 +95,7 @@ public class StatusSessionHandler implements MinecraftSessionHandler {
|
||||
.thenCompose(ping -> server.getEventManager().fire(new ProxyPingEvent(inbound, ping)))
|
||||
.thenAcceptAsync(
|
||||
(event) -> {
|
||||
StringBuilder json = new StringBuilder();
|
||||
final StringBuilder json = new StringBuilder();
|
||||
VelocityServer.getPingGsonInstance(connection.getProtocolVersion())
|
||||
.toJson(event.getPing(), json);
|
||||
connection.write(new StatusResponsePacket(json));
|
||||
|
@ -19,10 +19,14 @@ package com.velocitypowered.proxy.connection.player;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.velocitypowered.api.proxy.player.ResourcePackInfo;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.UUID;
|
||||
import net.kyori.adventure.resource.ResourcePackRequest;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Implements {@link ResourcePackInfo}.
|
||||
@ -31,13 +35,13 @@ public final class VelocityResourcePackInfo implements ResourcePackInfo {
|
||||
|
||||
private final UUID id;
|
||||
private final String url;
|
||||
private final @Nullable byte[] hash;
|
||||
private final byte @Nullable [] hash;
|
||||
private final boolean shouldForce;
|
||||
private final @Nullable Component prompt; // 1.17+ only
|
||||
private final Origin origin;
|
||||
private Origin originalOrigin;
|
||||
|
||||
private VelocityResourcePackInfo(UUID id, String url, @Nullable byte[] hash, boolean shouldForce,
|
||||
private VelocityResourcePackInfo(UUID id, String url, byte @Nullable [] hash, boolean shouldForce,
|
||||
@Nullable Component prompt, Origin origin) {
|
||||
this.id = id;
|
||||
this.url = url;
|
||||
@ -69,7 +73,7 @@ public final class VelocityResourcePackInfo implements ResourcePackInfo {
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable byte[] getHash() {
|
||||
public byte @Nullable[] getHash() {
|
||||
return hash == null ? null : hash.clone(); // Thanks spotbugs, very helpful.
|
||||
}
|
||||
|
||||
@ -105,6 +109,32 @@ public final class VelocityResourcePackInfo implements ResourcePackInfo {
|
||||
.setPrompt(prompt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ResourcePackRequest asResourcePackRequest() {
|
||||
return ResourcePackRequest.resourcePackRequest()
|
||||
.packs(net.kyori.adventure.resource.ResourcePackInfo.resourcePackInfo()
|
||||
.id(this.id)
|
||||
.uri(URI.create(this.url))
|
||||
.hash(this.hash == null ? "" : ByteBufUtil.hexDump(this.hash))
|
||||
.build())
|
||||
.required(this.shouldForce)
|
||||
.prompt(this.prompt)
|
||||
.build();
|
||||
}
|
||||
|
||||
@SuppressWarnings("checkstyle:MissingJavadocMethod")
|
||||
public static ResourcePackInfo fromAdventureRequest(
|
||||
final ResourcePackRequest request,
|
||||
final net.kyori.adventure.resource.ResourcePackInfo pack
|
||||
) {
|
||||
return new BuilderImpl(pack.uri().toString())
|
||||
.setHash(pack.hash().isEmpty() ? null : ByteBufUtil.decodeHexDump(pack.hash()))
|
||||
.setId(pack.id())
|
||||
.setShouldForce(request.required())
|
||||
.setPrompt(request.prompt())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements the builder for {@link ResourcePackInfo} instances.
|
||||
*/
|
||||
@ -113,7 +143,7 @@ public final class VelocityResourcePackInfo implements ResourcePackInfo {
|
||||
private UUID id;
|
||||
private final String url;
|
||||
private boolean shouldForce;
|
||||
private @Nullable byte[] hash;
|
||||
private byte @Nullable [] hash;
|
||||
private @Nullable Component prompt;
|
||||
private Origin origin = Origin.PLUGIN_ON_PROXY;
|
||||
|
||||
@ -135,7 +165,7 @@ public final class VelocityResourcePackInfo implements ResourcePackInfo {
|
||||
}
|
||||
|
||||
@Override
|
||||
public BuilderImpl setHash(@Nullable byte[] hash) {
|
||||
public BuilderImpl setHash(final byte @Nullable [] hash) {
|
||||
if (hash != null) {
|
||||
Preconditions.checkArgument(hash.length == 20, "Hash length is not 20");
|
||||
this.hash = hash.clone(); // Thanks spotbugs, very helpful.
|
||||
|
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Velocity Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.velocitypowered.proxy.connection.player.resourcepack;
|
||||
|
||||
import com.velocitypowered.api.event.player.PlayerResourcePackStatusEvent;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
|
||||
|
||||
/**
|
||||
* Legacy (Minecraft 1.17-1.20.2) ResourcePackHandler.
|
||||
*/
|
||||
public final class Legacy117ResourcePackHandler extends LegacyResourcePackHandler {
|
||||
Legacy117ResourcePackHandler(final ConnectedPlayer player, final VelocityServer server) {
|
||||
super(player, server);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean shouldDisconnectForForcePack(final PlayerResourcePackStatusEvent event) {
|
||||
return super.shouldDisconnectForForcePack(event) && !event.isOverwriteKick();
|
||||
}
|
||||
}
|
@ -0,0 +1,176 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Velocity Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.velocitypowered.proxy.connection.player.resourcepack;
|
||||
|
||||
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;
|
||||
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
import java.util.UUID;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Legacy (Minecraft <1.17) ResourcePackHandler.
|
||||
*/
|
||||
public sealed class LegacyResourcePackHandler extends ResourcePackHandler
|
||||
permits Legacy117ResourcePackHandler {
|
||||
protected @MonotonicNonNull Boolean previousResourceResponse;
|
||||
protected final Queue<ResourcePackInfo> outstandingResourcePacks = new ArrayDeque<>();
|
||||
private @Nullable ResourcePackInfo pendingResourcePack;
|
||||
private @Nullable ResourcePackInfo appliedResourcePack;
|
||||
|
||||
LegacyResourcePackHandler(final ConnectedPlayer player, final VelocityServer server) {
|
||||
super(player, server);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public ResourcePackInfo getFirstAppliedPack() {
|
||||
return appliedResourcePack;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public ResourcePackInfo getFirstPendingPack() {
|
||||
return pendingResourcePack;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Collection<ResourcePackInfo> getAppliedResourcePacks() {
|
||||
if (appliedResourcePack == null) {
|
||||
return List.of();
|
||||
}
|
||||
return List.of(appliedResourcePack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Collection<ResourcePackInfo> getPendingResourcePacks() {
|
||||
if (pendingResourcePack == null) {
|
||||
return List.of();
|
||||
}
|
||||
return List.of(pendingResourcePack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearAppliedResourcePacks() {
|
||||
// This is valid only for players with 1.20.2 versions
|
||||
this.appliedResourcePack = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(final @NotNull UUID id) throws UnsupportedOperationException {
|
||||
throw new UnsupportedOperationException("Cannot remove a ResourcePack from a legacy client");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void queueResourcePack(@NotNull ResourcePackInfo info) {
|
||||
outstandingResourcePacks.add(info);
|
||||
if (outstandingResourcePacks.size() == 1) {
|
||||
tickResourcePackQueue();
|
||||
}
|
||||
}
|
||||
|
||||
private void tickResourcePackQueue() {
|
||||
ResourcePackInfo queued = outstandingResourcePacks.peek();
|
||||
|
||||
if (queued != null) {
|
||||
// Check if the player declined a resource pack once already
|
||||
if (previousResourceResponse != null && !previousResourceResponse) {
|
||||
// If that happened we can flush the queue right away.
|
||||
// Unless its 1.17+ and forced it will come back denied anyway
|
||||
while (!outstandingResourcePacks.isEmpty()) {
|
||||
queued = outstandingResourcePacks.peek();
|
||||
if (queued.getShouldForce() && player.getProtocolVersion()
|
||||
.noLessThan(ProtocolVersion.MINECRAFT_1_17)) {
|
||||
break;
|
||||
}
|
||||
onResourcePackResponse(new ResourcePackResponseBundle(queued.getId(),
|
||||
PlayerResourcePackStatusEvent.Status.DECLINED));
|
||||
queued = null;
|
||||
}
|
||||
if (queued == null) {
|
||||
// Exit as the queue was cleared
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
sendResourcePackRequestPacket(queued);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onResourcePackResponse(
|
||||
final @NotNull ResourcePackResponseBundle bundle
|
||||
) {
|
||||
final boolean peek = bundle.status().isIntermediate();
|
||||
final ResourcePackInfo queued = peek
|
||||
? outstandingResourcePacks.peek() : outstandingResourcePacks.poll();
|
||||
|
||||
server.getEventManager()
|
||||
.fire(new PlayerResourcePackStatusEvent(
|
||||
this.player, bundle.uuid(), bundle.status(), queued))
|
||||
.thenAcceptAsync(event -> {
|
||||
if (shouldDisconnectForForcePack(event)) {
|
||||
event.getPlayer().disconnect(Component
|
||||
.translatable("multiplayer.requiredTexturePrompt.disconnect"));
|
||||
}
|
||||
});
|
||||
|
||||
switch (bundle.status()) {
|
||||
case ACCEPTED -> {
|
||||
previousResourceResponse = true;
|
||||
pendingResourcePack = queued;
|
||||
}
|
||||
case DECLINED -> previousResourceResponse = false;
|
||||
case SUCCESSFUL -> {
|
||||
appliedResourcePack = queued;
|
||||
pendingResourcePack = null;
|
||||
}
|
||||
case FAILED_DOWNLOAD -> pendingResourcePack = null;
|
||||
case DISCARDED -> {
|
||||
if (queued != null && queued.getId() != null
|
||||
&& appliedResourcePack != null
|
||||
&& appliedResourcePack.getId().equals(queued.getId())) {
|
||||
appliedResourcePack = null;
|
||||
}
|
||||
}
|
||||
default -> {
|
||||
}
|
||||
}
|
||||
|
||||
if (!peek) {
|
||||
player.getConnection().eventLoop().execute(this::tickResourcePackQueue);
|
||||
}
|
||||
|
||||
return queued != null
|
||||
&& queued.getOriginalOrigin() != ResourcePackInfo.Origin.DOWNSTREAM_SERVER;
|
||||
}
|
||||
|
||||
protected boolean shouldDisconnectForForcePack(final PlayerResourcePackStatusEvent event) {
|
||||
return event.getStatus() == PlayerResourcePackStatusEvent.Status.DECLINED
|
||||
&& event.getPackInfo() != null && event.getPackInfo().getShouldForce();
|
||||
}
|
||||
}
|
@ -0,0 +1,176 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Velocity Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.velocitypowered.proxy.connection.player.resourcepack;
|
||||
|
||||
import com.google.common.collect.ListMultimap;
|
||||
import com.google.common.collect.Multimaps;
|
||||
import com.velocitypowered.api.event.player.PlayerResourcePackStatusEvent;
|
||||
import com.velocitypowered.api.proxy.player.ResourcePackInfo;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import net.kyori.adventure.resource.ResourcePackRequest;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Modern (Minecraft 1.20.3+) ResourcePackHandler
|
||||
*/
|
||||
public final class ModernResourcePackHandler extends ResourcePackHandler {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ModernResourcePackHandler.class);
|
||||
private final ListMultimap<UUID, ResourcePackInfo> outstandingResourcePacks =
|
||||
Multimaps.newListMultimap(new ConcurrentHashMap<>(), LinkedList::new);
|
||||
private final Map<UUID, ResourcePackInfo> pendingResourcePacks = new ConcurrentHashMap<>();
|
||||
private final Map<UUID, ResourcePackInfo> appliedResourcePacks = new ConcurrentHashMap<>();
|
||||
|
||||
ModernResourcePackHandler(final ConnectedPlayer player, final VelocityServer server) {
|
||||
super(player, server);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ResourcePackInfo getFirstAppliedPack() {
|
||||
if (appliedResourcePacks.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return appliedResourcePacks.values().iterator().next();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ResourcePackInfo getFirstPendingPack() {
|
||||
if (pendingResourcePacks.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return pendingResourcePacks.values().iterator().next();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Collection<ResourcePackInfo> getAppliedResourcePacks() {
|
||||
return List.copyOf(appliedResourcePacks.values());
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Collection<ResourcePackInfo> getPendingResourcePacks() {
|
||||
return List.copyOf(pendingResourcePacks.values());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearAppliedResourcePacks() {
|
||||
this.outstandingResourcePacks.clear();
|
||||
this.pendingResourcePacks.clear();
|
||||
this.appliedResourcePacks.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(final @NotNull UUID uuid) {
|
||||
outstandingResourcePacks.removeAll(uuid);
|
||||
return appliedResourcePacks.remove(uuid) != null | pendingResourcePacks.remove(uuid) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void queueResourcePack(final @NotNull ResourcePackInfo info) {
|
||||
final List<ResourcePackInfo> outstandingResourcePacks =
|
||||
this.outstandingResourcePacks.get(info.getId());
|
||||
outstandingResourcePacks.add(info);
|
||||
if (outstandingResourcePacks.size() == 1) {
|
||||
tickResourcePackQueue(outstandingResourcePacks.get(0).getId());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void queueResourcePack(final @NotNull ResourcePackRequest request) {
|
||||
if (request.packs().size() > 1) {
|
||||
player.getBundleHandler().bundlePackets(() -> {
|
||||
super.queueResourcePack(request);
|
||||
});
|
||||
} else {
|
||||
super.queueResourcePack(request);
|
||||
}
|
||||
}
|
||||
|
||||
private void tickResourcePackQueue(final @NotNull UUID uuid) {
|
||||
final List<ResourcePackInfo> outstandingResourcePacks =
|
||||
this.outstandingResourcePacks.get(uuid);
|
||||
if (!outstandingResourcePacks.isEmpty()) {
|
||||
sendResourcePackRequestPacket(outstandingResourcePacks.get(0));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onResourcePackResponse(
|
||||
final @NotNull ResourcePackResponseBundle bundle
|
||||
) {
|
||||
final UUID uuid = bundle.uuid();
|
||||
final List<ResourcePackInfo> outstandingResourcePacks =
|
||||
this.outstandingResourcePacks.get(uuid);
|
||||
final boolean peek = bundle.status().isIntermediate();
|
||||
final ResourcePackInfo queued = outstandingResourcePacks.isEmpty() ? null :
|
||||
peek ? outstandingResourcePacks.get(0) : outstandingResourcePacks.remove(0);
|
||||
|
||||
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()
|
||||
&& !event.isOverwriteKick()
|
||||
) {
|
||||
player.disconnect(Component
|
||||
.translatable("multiplayer.requiredTexturePrompt.disconnect"));
|
||||
}
|
||||
});
|
||||
|
||||
switch (bundle.status()) {
|
||||
// The player has accepted the resource pack and will proceed to download it.
|
||||
case ACCEPTED -> {
|
||||
if (queued != null) {
|
||||
pendingResourcePacks.put(uuid, queued);
|
||||
}
|
||||
}
|
||||
// The resource pack has been applied correctly.
|
||||
case SUCCESSFUL -> {
|
||||
if (queued != null) {
|
||||
appliedResourcePacks.put(uuid, queued);
|
||||
}
|
||||
pendingResourcePacks.remove(uuid);
|
||||
}
|
||||
// An error occurred while trying to download the resource pack to the client,
|
||||
// so the resource pack cannot be applied.
|
||||
case DISCARDED, DECLINED, FAILED_RELOAD, FAILED_DOWNLOAD, INVALID_URL -> {
|
||||
pendingResourcePacks.remove(uuid);
|
||||
appliedResourcePacks.remove(uuid);
|
||||
}
|
||||
// The other cases in which no action is taken are documented in the javadocs.
|
||||
default -> {
|
||||
}
|
||||
}
|
||||
|
||||
if (!peek) {
|
||||
player.getConnection().eventLoop().execute(() -> tickResourcePackQueue(uuid));
|
||||
}
|
||||
|
||||
return queued != null
|
||||
&& queued.getOriginalOrigin() != ResourcePackInfo.Origin.DOWNSTREAM_SERVER;
|
||||
}
|
||||
}
|
@ -0,0 +1,142 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Velocity Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.velocitypowered.proxy.connection.player.resourcepack;
|
||||
|
||||
import com.velocitypowered.api.network.ProtocolVersion;
|
||||
import com.velocitypowered.api.proxy.player.ResourcePackInfo;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
|
||||
import com.velocitypowered.proxy.connection.player.VelocityResourcePackInfo;
|
||||
import com.velocitypowered.proxy.protocol.packet.ResourcePackRequestPacket;
|
||||
import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
import java.util.Collection;
|
||||
import java.util.UUID;
|
||||
import net.kyori.adventure.resource.ResourcePackRequest;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* ResourcePackHandler.
|
||||
*/
|
||||
public abstract sealed class ResourcePackHandler
|
||||
permits LegacyResourcePackHandler, ModernResourcePackHandler {
|
||||
protected final ConnectedPlayer player;
|
||||
protected final VelocityServer server;
|
||||
|
||||
protected ResourcePackHandler(final ConnectedPlayer player, final VelocityServer server) {
|
||||
this.player = player;
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new ResourcePackHandler.
|
||||
*
|
||||
* @param player the player.
|
||||
* @param server the velocity server
|
||||
*
|
||||
* @return a new ResourcePackHandler
|
||||
*/
|
||||
public static @NotNull ResourcePackHandler create(final ConnectedPlayer player,
|
||||
final VelocityServer server) {
|
||||
final ProtocolVersion protocolVersion = player.getProtocolVersion();
|
||||
if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_17)) {
|
||||
return new LegacyResourcePackHandler(player, server);
|
||||
}
|
||||
if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_20_3)) {
|
||||
return new Legacy117ResourcePackHandler(player, server);
|
||||
}
|
||||
return new ModernResourcePackHandler(player, server);
|
||||
}
|
||||
|
||||
public abstract @Nullable ResourcePackInfo getFirstAppliedPack();
|
||||
|
||||
public abstract @Nullable ResourcePackInfo getFirstPendingPack();
|
||||
|
||||
public abstract @NotNull Collection<ResourcePackInfo> getAppliedResourcePacks();
|
||||
|
||||
public abstract @NotNull Collection<ResourcePackInfo> getPendingResourcePacks();
|
||||
|
||||
/**
|
||||
* Clears the applied resource pack field.
|
||||
*/
|
||||
public abstract void clearAppliedResourcePacks();
|
||||
|
||||
public abstract boolean remove(final UUID id);
|
||||
|
||||
/**
|
||||
* Queues a resource-pack for sending to the player and sends it immediately if the queue is
|
||||
* empty.
|
||||
*/
|
||||
public abstract void queueResourcePack(final @NotNull ResourcePackInfo info);
|
||||
|
||||
/**
|
||||
* Queues a resource-request for sending to the player and sends it immediately if the queue is
|
||||
* empty.
|
||||
*/
|
||||
public void queueResourcePack(final @NotNull ResourcePackRequest request) {
|
||||
for (final net.kyori.adventure.resource.ResourcePackInfo pack : request.packs()) {
|
||||
queueResourcePack(VelocityResourcePackInfo.fromAdventureRequest(request, pack));
|
||||
}
|
||||
}
|
||||
|
||||
protected void sendResourcePackRequestPacket(final @NotNull ResourcePackInfo queued) {
|
||||
final ResourcePackRequestPacket request = new ResourcePackRequestPacket();
|
||||
request.setId(queued.getId());
|
||||
request.setUrl(queued.getUrl());
|
||||
if (queued.getHash() != null) {
|
||||
request.setHash(ByteBufUtil.hexDump(queued.getHash()));
|
||||
} else {
|
||||
request.setHash("");
|
||||
}
|
||||
request.setRequired(queued.getShouldForce());
|
||||
request.setPrompt(queued.getPrompt() == null ? null :
|
||||
new ComponentHolder(player.getProtocolVersion(), queued.getPrompt()));
|
||||
|
||||
player.getConnection().write(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a client response to a sent resource-pack.
|
||||
* <ul>
|
||||
* <p>Cases in which no action will be taken:</p>
|
||||
*
|
||||
* <br><li><b>DOWNLOADED</b>
|
||||
* <p>In this case the resource pack is downloaded and will be applied to the client,
|
||||
* no action is required in Velocity.</p>
|
||||
*
|
||||
* <br><li><b>INVALID_URL</b>
|
||||
* <p>In this case, the client has received a resource pack request
|
||||
* and the first check it performs is if the URL is valid, if not,
|
||||
* it will return this value</p>
|
||||
*
|
||||
* <br><li><b>FAILED_RELOAD</b>
|
||||
* <p>In this case, when trying to reload the client's resources,
|
||||
* an error occurred while reloading a resource pack</p>
|
||||
* </ul>
|
||||
*
|
||||
* <br><li><b>DECLINED</b>
|
||||
* <p>Only in modern versions, as the resource pack has already been rejected,
|
||||
* there is nothing to do, if the resource pack is required,
|
||||
* the client will be kicked out of the server.</p>
|
||||
*
|
||||
* @param bundle the resource pack response bundle
|
||||
*/
|
||||
public abstract boolean onResourcePackResponse(
|
||||
final @NotNull ResourcePackResponseBundle bundle);
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Velocity Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.velocitypowered.proxy.connection.player.resourcepack;
|
||||
|
||||
import com.velocitypowered.api.event.player.PlayerResourcePackStatusEvent;
|
||||
import java.util.UUID;
|
||||
|
||||
@SuppressWarnings("checkstyle:MissingJavadocType")
|
||||
public record ResourcePackResponseBundle(UUID uuid, PlayerResourcePackStatusEvent.Status status) {
|
||||
}
|
@ -119,7 +119,7 @@ public class IdentifiedKeyImpl implements IdentifiedKey {
|
||||
if (revision == Revision.GENERIC_V1) {
|
||||
String pemKey = EncryptionUtils.pemEncodeRsaKey(publicKey);
|
||||
long expires = expiryTemporal.toEpochMilli();
|
||||
byte[] toVerify = ("" + expires + pemKey).getBytes(StandardCharsets.US_ASCII);
|
||||
byte[] toVerify = (expires + pemKey).getBytes(StandardCharsets.US_ASCII);
|
||||
return EncryptionUtils.verifySignature(
|
||||
EncryptionUtils.SHA1_WITH_RSA, EncryptionUtils.getYggdrasilSessionKey(), signature,
|
||||
toVerify);
|
||||
@ -166,12 +166,10 @@ public class IdentifiedKeyImpl implements IdentifiedKey {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof IdentifiedKey)) {
|
||||
if (!(o instanceof final IdentifiedKey that)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
IdentifiedKey that = (IdentifiedKey) o;
|
||||
|
||||
return Objects.equal(this.getSignedPublicKey(), that.getSignedPublicKey())
|
||||
&& Objects.equal(this.getExpiryTemporal(), that.getExpiryTemporal())
|
||||
&& Arrays.equals(this.getSignature(), that.getSignature())
|
||||
|
@ -48,6 +48,7 @@ import static com.velocitypowered.proxy.protocol.ProtocolUtils.Direction.SERVERB
|
||||
import com.velocitypowered.api.network.ProtocolVersion;
|
||||
import com.velocitypowered.proxy.protocol.packet.AvailableCommandsPacket;
|
||||
import com.velocitypowered.proxy.protocol.packet.BossBarPacket;
|
||||
import com.velocitypowered.proxy.protocol.packet.BundleDelimiterPacket;
|
||||
import com.velocitypowered.proxy.protocol.packet.ClientSettingsPacket;
|
||||
import com.velocitypowered.proxy.protocol.packet.DisconnectPacket;
|
||||
import com.velocitypowered.proxy.protocol.packet.EncryptionRequestPacket;
|
||||
@ -144,7 +145,7 @@ public enum StateRegistry {
|
||||
PluginMessagePacket.class, PluginMessagePacket::new,
|
||||
map(0x01, MINECRAFT_1_20_2, false));
|
||||
serverbound.register(
|
||||
FinishedUpdatePacket.class, FinishedUpdatePacket::new,
|
||||
FinishedUpdatePacket.class, () -> FinishedUpdatePacket.INSTANCE,
|
||||
map(0x02, MINECRAFT_1_20_2, false));
|
||||
serverbound.register(KeepAlivePacket.class, KeepAlivePacket::new,
|
||||
map(0x03, MINECRAFT_1_20_2, false));
|
||||
@ -163,7 +164,7 @@ public enum StateRegistry {
|
||||
DisconnectPacket.class, () -> new DisconnectPacket(false),
|
||||
map(0x01, MINECRAFT_1_20_2, false));
|
||||
clientbound.register(
|
||||
FinishedUpdatePacket.class, FinishedUpdatePacket::new,
|
||||
FinishedUpdatePacket.class, () -> FinishedUpdatePacket.INSTANCE,
|
||||
map(0x02, MINECRAFT_1_20_2, false));
|
||||
clientbound.register(KeepAlivePacket.class, KeepAlivePacket::new,
|
||||
map(0x03, MINECRAFT_1_20_2, false));
|
||||
@ -289,7 +290,7 @@ public enum StateRegistry {
|
||||
map(0x27, MINECRAFT_1_20_2, false),
|
||||
map(0x28, MINECRAFT_1_20_3, false));
|
||||
serverbound.register(
|
||||
FinishedUpdatePacket.class, FinishedUpdatePacket::new,
|
||||
FinishedUpdatePacket.class, () -> FinishedUpdatePacket.INSTANCE,
|
||||
map(0x0B, MINECRAFT_1_20_2, false));
|
||||
|
||||
clientbound.register(
|
||||
@ -569,9 +570,13 @@ public enum StateRegistry {
|
||||
map(0x49, MINECRAFT_1_20_3, false));
|
||||
clientbound.register(
|
||||
StartUpdatePacket.class,
|
||||
StartUpdatePacket::new,
|
||||
() -> StartUpdatePacket.INSTANCE,
|
||||
map(0x65, MINECRAFT_1_20_2, false),
|
||||
map(0x67, MINECRAFT_1_20_3, false));
|
||||
clientbound.register(
|
||||
BundleDelimiterPacket.class,
|
||||
() -> BundleDelimiterPacket.INSTANCE,
|
||||
map(0x00, MINECRAFT_1_19_4, false));
|
||||
}
|
||||
},
|
||||
LOGIN {
|
||||
|
@ -39,7 +39,7 @@ public class LegacyPingEncoder extends MessageToByteEncoder<LegacyDisconnect> {
|
||||
protected void encode(ChannelHandlerContext ctx, LegacyDisconnect msg, ByteBuf out)
|
||||
throws Exception {
|
||||
out.writeByte(0xff);
|
||||
writeLegacyString(out, msg.getReason());
|
||||
writeLegacyString(out, msg.reason());
|
||||
}
|
||||
|
||||
private static void writeLegacyString(ByteBuf out, String string) {
|
||||
|
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright (C) 2018-2021 Velocity Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.velocitypowered.proxy.protocol.packet;
|
||||
|
||||
import com.velocitypowered.api.network.ProtocolVersion;
|
||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
public final class BundleDelimiterPacket implements MinecraftPacket {
|
||||
public static final BundleDelimiterPacket INSTANCE = new BundleDelimiterPacket();
|
||||
|
||||
private BundleDelimiterPacket() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(MinecraftSessionHandler handler) {
|
||||
return handler.handle(this);
|
||||
}
|
||||
}
|
@ -25,19 +25,14 @@ import net.kyori.adventure.text.TextComponent;
|
||||
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
||||
|
||||
public class LegacyDisconnect {
|
||||
@SuppressWarnings("checkstyle:MissingJavadocType")
|
||||
public record LegacyDisconnect(String reason) {
|
||||
|
||||
private static final ServerPing.Players FAKE_PLAYERS = new ServerPing.Players(0, 0,
|
||||
ImmutableList.of());
|
||||
private static final String LEGACY_COLOR_CODE = Character
|
||||
.toString(LegacyComponentSerializer.SECTION_CHAR);
|
||||
|
||||
private final String reason;
|
||||
|
||||
private LegacyDisconnect(String reason) {
|
||||
this.reason = reason;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a modern server list ping response into an legacy disconnect packet.
|
||||
*
|
||||
@ -47,22 +42,21 @@ public class LegacyDisconnect {
|
||||
*/
|
||||
public static LegacyDisconnect fromServerPing(ServerPing response,
|
||||
LegacyMinecraftPingVersion version) {
|
||||
Players players = response.getPlayers().orElse(FAKE_PLAYERS);
|
||||
final Players players = response.getPlayers().orElse(FAKE_PLAYERS);
|
||||
|
||||
switch (version) {
|
||||
case MINECRAFT_1_3:
|
||||
return switch (version) {
|
||||
case MINECRAFT_1_3 ->
|
||||
// Minecraft 1.3 and below use the section symbol as a delimiter. Accordingly, we must
|
||||
// remove all section symbols, along with fetching just the first line of an (unformatted)
|
||||
// MOTD.
|
||||
return new LegacyDisconnect(String.join(LEGACY_COLOR_CODE,
|
||||
new LegacyDisconnect(String.join(LEGACY_COLOR_CODE,
|
||||
cleanSectionSymbol(getFirstLine(PlainTextComponentSerializer.plainText().serialize(
|
||||
response.getDescriptionComponent()))),
|
||||
Integer.toString(players.getOnline()),
|
||||
Integer.toString(players.getMax())));
|
||||
case MINECRAFT_1_4:
|
||||
case MINECRAFT_1_6:
|
||||
case MINECRAFT_1_4, MINECRAFT_1_6 ->
|
||||
// Minecraft 1.4-1.6 provide support for more fields, and additionally support color codes.
|
||||
return new LegacyDisconnect(String.join("\0",
|
||||
new LegacyDisconnect(String.join("\0",
|
||||
LEGACY_COLOR_CODE + "1",
|
||||
Integer.toString(response.getVersion().getProtocol()),
|
||||
response.getVersion().getName(),
|
||||
@ -71,9 +65,8 @@ public class LegacyDisconnect {
|
||||
Integer.toString(players.getOnline()),
|
||||
Integer.toString(players.getMax())
|
||||
));
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown version " + version);
|
||||
}
|
||||
default -> throw new IllegalArgumentException("Unknown version " + version);
|
||||
};
|
||||
}
|
||||
|
||||
private static String cleanSectionSymbol(String string) {
|
||||
@ -81,7 +74,7 @@ public class LegacyDisconnect {
|
||||
}
|
||||
|
||||
private static String getFirstLine(String legacyMotd) {
|
||||
int newline = legacyMotd.indexOf('\n');
|
||||
final int newline = legacyMotd.indexOf('\n');
|
||||
return newline == -1 ? legacyMotd : legacyMotd.substring(0, newline);
|
||||
}
|
||||
|
||||
@ -93,11 +86,7 @@ public class LegacyDisconnect {
|
||||
*/
|
||||
public static LegacyDisconnect from(TextComponent component) {
|
||||
// We intentionally use the legacy serializers, because the old clients can't understand JSON.
|
||||
String serialized = LegacyComponentSerializer.legacySection().serialize(component);
|
||||
final String serialized = LegacyComponentSerializer.legacySection().serialize(component);
|
||||
return new LegacyDisconnect(serialized);
|
||||
}
|
||||
|
||||
public String getReason() {
|
||||
return reason;
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,6 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class RemoveResourcePackPacket implements MinecraftPacket {
|
||||
@ -37,6 +36,10 @@ public class RemoveResourcePackPacket implements MinecraftPacket {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public UUID getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) {
|
||||
if (buf.readBoolean()) {
|
||||
|
@ -28,11 +28,10 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
|
||||
import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.regex.Pattern;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
public class ResourcePackRequestPacket implements MinecraftPacket {
|
||||
|
||||
@ -80,7 +79,7 @@ public class ResourcePackRequestPacket implements MinecraftPacket {
|
||||
return prompt;
|
||||
}
|
||||
|
||||
public void setPrompt(ComponentHolder prompt) {
|
||||
public void setPrompt(@Nullable ComponentHolder prompt) {
|
||||
this.prompt = prompt;
|
||||
}
|
||||
|
||||
@ -126,7 +125,7 @@ public class ResourcePackRequestPacket implements MinecraftPacket {
|
||||
}
|
||||
|
||||
public VelocityResourcePackInfo toServerPromptedPack() {
|
||||
ResourcePackInfo.Builder builder =
|
||||
final ResourcePackInfo.Builder builder =
|
||||
new VelocityResourcePackInfo.BuilderImpl(Preconditions.checkNotNull(url))
|
||||
.setId(id).setPrompt(prompt == null ? null : prompt.getComponent())
|
||||
.setShouldForce(isRequired).setOrigin(ResourcePackInfo.Origin.DOWNSTREAM_SERVER);
|
||||
|
@ -54,6 +54,10 @@ public class ResourcePackResponsePacket implements MinecraftPacket {
|
||||
return hash;
|
||||
}
|
||||
|
||||
public UUID getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) {
|
||||
if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_20_3)) {
|
||||
|
@ -24,6 +24,10 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
public class FinishedUpdatePacket implements MinecraftPacket {
|
||||
public static final FinishedUpdatePacket INSTANCE = new FinishedUpdatePacket();
|
||||
|
||||
private FinishedUpdatePacket() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolUtils.Direction direction,
|
||||
|
@ -24,6 +24,10 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
public class StartUpdatePacket implements MinecraftPacket {
|
||||
public static final StartUpdatePacket INSTANCE = new StartUpdatePacket();
|
||||
|
||||
private StartUpdatePacket() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolUtils.Direction direction,
|
||||
|
@ -44,8 +44,8 @@ public enum TranslatableMapper implements BiConsumer<TranslatableComponent, Cons
|
||||
final Consumer<Component> componentConsumer
|
||||
) {
|
||||
for (final Translator source : GlobalTranslator.translator().sources()) {
|
||||
if (source instanceof TranslationRegistry
|
||||
&& ((TranslationRegistry) source).contains(translatableComponent.key())) {
|
||||
if (source instanceof TranslationRegistry registry
|
||||
&& registry.contains(translatableComponent.key())) {
|
||||
componentConsumer.accept(GlobalTranslator.render(translatableComponent,
|
||||
ClosestLocaleMatcher.INSTANCE.lookupClosest(Locale.getDefault())));
|
||||
return;
|
||||
@ -56,8 +56,7 @@ public enum TranslatableMapper implements BiConsumer<TranslatableComponent, Cons
|
||||
return;
|
||||
}
|
||||
for (final Translator source : GlobalTranslator.translator().sources()) {
|
||||
if (source instanceof TranslationRegistry
|
||||
&& ((TranslationRegistry) source).contains(fallback)) {
|
||||
if (source instanceof TranslationRegistry registry && registry.contains(fallback)) {
|
||||
componentConsumer.accept(
|
||||
GlobalTranslator.render(Component.translatable(fallback),
|
||||
ClosestLocaleMatcher.INSTANCE.lookupClosest(Locale.getDefault())));
|
||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren