mirror of
https://github.com/PaperMC/Velocity.git
synced 2026-02-17 14:37:43 +01:00
Add various missing jd, bump remaining deps (#1718)
This commit is contained in:
@@ -14,10 +14,6 @@ application {
|
||||
}
|
||||
|
||||
tasks {
|
||||
withType<Checkstyle> {
|
||||
exclude("**/com/velocitypowered/proxy/protocol/packet/**")
|
||||
}
|
||||
|
||||
jar {
|
||||
manifest {
|
||||
attributes["Implementation-Title"] = "Velocity"
|
||||
@@ -33,7 +29,7 @@ tasks {
|
||||
|
||||
transform(Log4j2PluginsCacheFileTransformer::class.java)
|
||||
|
||||
// Exclude all the collection types we don"t intend to use
|
||||
// Exclude all the collection types we don't intend to use
|
||||
exclude("it/unimi/dsi/fastutil/booleans/**")
|
||||
exclude("it/unimi/dsi/fastutil/bytes/**")
|
||||
exclude("it/unimi/dsi/fastutil/chars/**")
|
||||
@@ -42,7 +38,7 @@ tasks {
|
||||
exclude("it/unimi/dsi/fastutil/longs/**")
|
||||
exclude("it/unimi/dsi/fastutil/shorts/**")
|
||||
|
||||
// Exclude the fastutil IO utilities - we don"t use them.
|
||||
// Exclude the fastutil IO utilities - we don't use them.
|
||||
exclude("it/unimi/dsi/fastutil/io/**")
|
||||
|
||||
// Exclude most of the int types - Object2IntMap have a values() method that returns an
|
||||
|
||||
@@ -650,7 +650,7 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link #shutdown(boolean, Component)} with the default reason "Proxy shutting down."
|
||||
* Calls {@link #shutdown(boolean, Component)} with the default reason "Proxy shutting down.".
|
||||
*
|
||||
* @param explicitExit whether the user explicitly shut down the proxy
|
||||
*/
|
||||
|
||||
@@ -37,7 +37,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Modern (Minecraft 1.20.3+) ResourcePackHandler
|
||||
* Modern (Minecraft 1.20.3+) ResourcePackHandler.
|
||||
*/
|
||||
public final class ModernResourcePackHandler extends ResourcePackHandler {
|
||||
private final ListMultimap<UUID, ResourcePackInfo> outstandingResourcePacks =
|
||||
|
||||
@@ -118,6 +118,7 @@ public abstract sealed class ResourcePackHandler
|
||||
|
||||
/**
|
||||
* Processes a client response to a sent resource-pack.
|
||||
*
|
||||
* <p>Cases in which no action will be taken:</p>
|
||||
* <ul>
|
||||
*
|
||||
|
||||
@@ -545,6 +545,36 @@ public class VelocityEventManager implements EventManager {
|
||||
}
|
||||
}
|
||||
|
||||
private <E> void fire(final @Nullable CompletableFuture<E> future, final E event,
|
||||
final int offset, final boolean currentlyAsync, final HandlerRegistration[] registrations) {
|
||||
for (int i = offset; i < registrations.length; i++) {
|
||||
final HandlerRegistration registration = registrations[i];
|
||||
try {
|
||||
final EventTask eventTask = registration.handler.executeAsync(event);
|
||||
if (eventTask == null) {
|
||||
continue;
|
||||
}
|
||||
final ContinuationTask<E> continuationTask = new ContinuationTask<>(eventTask,
|
||||
registrations, future, event, i, currentlyAsync);
|
||||
if (currentlyAsync || !eventTask.requiresAsync()) {
|
||||
if (continuationTask.execute()) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
registration.plugin.getExecutorService().execute(continuationTask);
|
||||
}
|
||||
// fire will continue in another thread once the async task is
|
||||
// executed and the continuation is resumed
|
||||
return;
|
||||
} catch (final Throwable t) {
|
||||
logHandlerException(registration, t);
|
||||
}
|
||||
}
|
||||
if (future != null) {
|
||||
future.complete(event);
|
||||
}
|
||||
}
|
||||
|
||||
private static final int TASK_STATE_DEFAULT = 0;
|
||||
private static final int TASK_STATE_EXECUTING = 1;
|
||||
private static final int TASK_STATE_CONTINUE_IMMEDIATELY = 2;
|
||||
@@ -669,40 +699,10 @@ public class VelocityEventManager implements EventManager {
|
||||
}
|
||||
}
|
||||
|
||||
private <E> void fire(final @Nullable CompletableFuture<E> future, final E event,
|
||||
final int offset, final boolean currentlyAsync, final HandlerRegistration[] registrations) {
|
||||
for (int i = offset; i < registrations.length; i++) {
|
||||
final HandlerRegistration registration = registrations[i];
|
||||
try {
|
||||
final EventTask eventTask = registration.handler.executeAsync(event);
|
||||
if (eventTask == null) {
|
||||
continue;
|
||||
}
|
||||
final ContinuationTask<E> continuationTask = new ContinuationTask<>(eventTask,
|
||||
registrations, future, event, i, currentlyAsync);
|
||||
if (currentlyAsync || !eventTask.requiresAsync()) {
|
||||
if (continuationTask.execute()) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
registration.plugin.getExecutorService().execute(continuationTask);
|
||||
}
|
||||
// fire will continue in another thread once the async task is
|
||||
// executed and the continuation is resumed
|
||||
return;
|
||||
} catch (final Throwable t) {
|
||||
logHandlerException(registration, t);
|
||||
}
|
||||
}
|
||||
if (future != null) {
|
||||
future.complete(event);
|
||||
}
|
||||
}
|
||||
|
||||
private static void logHandlerException(
|
||||
final HandlerRegistration registration, final Throwable t) {
|
||||
final PluginDescription pluginDescription = registration.plugin.getDescription();
|
||||
logger.error("Couldn't pass {} to {} {}", registration.eventType.getSimpleName(),
|
||||
pluginDescription.getId(), pluginDescription.getVersion().orElse(""), t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -633,7 +633,7 @@ public enum ProtocolUtils {
|
||||
private static final int FORGE_MAX_ARRAY_LENGTH = Integer.MAX_VALUE & 0x1FFF9A;
|
||||
|
||||
/**
|
||||
* Reads an byte array for legacy version 1.7 from the specified {@code buf}
|
||||
* Reads an byte array for legacy version 1.7 from the specified {@code buf}.
|
||||
*
|
||||
* @param buf the buffer to read from
|
||||
* @return the read byte array
|
||||
@@ -671,7 +671,7 @@ public enum ProtocolUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes an byte array for legacy version 1.7 to the specified {@code buf}
|
||||
* Writes an byte array for legacy version 1.7 to the specified {@code buf}.
|
||||
*
|
||||
* @param b array
|
||||
* @param buf buf
|
||||
@@ -695,7 +695,7 @@ public enum ProtocolUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes an {@link ByteBuf} for legacy version 1.7 to the specified {@code buf}
|
||||
* Writes an {@link ByteBuf} for legacy version 1.7 to the specified {@code buf}.
|
||||
*
|
||||
* @param b array
|
||||
* @param buf buf
|
||||
|
||||
@@ -53,6 +53,13 @@ import java.util.function.Predicate;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* Represents a packet that contains the list of available commands, implementing {@link MinecraftPacket}.
|
||||
*
|
||||
* <p>The {@code AvailableCommandsPacket} is responsible for transmitting the set of commands
|
||||
* that a player can execute. It provides the necessary information about available commands
|
||||
* within the current session or game state.</p>
|
||||
*/
|
||||
public class AvailableCommandsPacket implements MinecraftPacket {
|
||||
|
||||
private static final Command<CommandSource> PLACEHOLDER_COMMAND = source -> 0;
|
||||
|
||||
@@ -29,6 +29,10 @@ import java.util.UUID;
|
||||
import net.kyori.adventure.bossbar.BossBar;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* Represents a packet used to manage boss bars.
|
||||
* This packet can add, remove, or update a boss bar.
|
||||
*/
|
||||
public class BossBarPacket implements MinecraftPacket {
|
||||
|
||||
private static final Enum2IntMap<BossBar.Color> COLORS_TO_PROTOCOL =
|
||||
@@ -70,6 +74,14 @@ public class BossBarPacket implements MinecraftPacket {
|
||||
private int overlay;
|
||||
private short flags;
|
||||
|
||||
/**
|
||||
* Creates a packet to add a new boss bar.
|
||||
*
|
||||
* @param id the UUID of the boss bar
|
||||
* @param bar the {@link BossBar} instance
|
||||
* @param name the {@link ComponentHolder} containing the boss bar's name
|
||||
* @return a {@link BossBarPacket} to add a boss bar
|
||||
*/
|
||||
public static BossBarPacket createAddPacket(
|
||||
final UUID id,
|
||||
final BossBar bar,
|
||||
@@ -86,6 +98,13 @@ public class BossBarPacket implements MinecraftPacket {
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a packet to remove an existing boss bar.
|
||||
*
|
||||
* @param id the UUID of the boss bar to remove
|
||||
* @param bar the {@link BossBar} instance
|
||||
* @return a {@link BossBarPacket} to remove a boss bar
|
||||
*/
|
||||
public static BossBarPacket createRemovePacket(final UUID id, final BossBar bar) {
|
||||
final BossBarPacket packet = new BossBarPacket();
|
||||
packet.setUuid(id);
|
||||
@@ -93,6 +112,13 @@ public class BossBarPacket implements MinecraftPacket {
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a packet to update the progress (percentage) of the boss bar.
|
||||
*
|
||||
* @param id the UUID of the boss bar
|
||||
* @param bar the {@link BossBar} instance
|
||||
* @return a {@link BossBarPacket} to update the boss bar's progress
|
||||
*/
|
||||
public static BossBarPacket createUpdateProgressPacket(final UUID id, final BossBar bar) {
|
||||
final BossBarPacket packet = new BossBarPacket();
|
||||
packet.setUuid(id);
|
||||
@@ -101,6 +127,14 @@ public class BossBarPacket implements MinecraftPacket {
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a packet to update the name of the boss bar.
|
||||
*
|
||||
* @param id the UUID of the boss bar
|
||||
* @param bar the {@link BossBar} instance
|
||||
* @param name the {@link ComponentHolder} containing the boss bar's new name
|
||||
* @return a {@link BossBarPacket} to update the boss bar's name
|
||||
*/
|
||||
public static BossBarPacket createUpdateNamePacket(
|
||||
final UUID id,
|
||||
final BossBar bar,
|
||||
@@ -113,6 +147,13 @@ public class BossBarPacket implements MinecraftPacket {
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a packet to update the style (color and overlay) of the boss bar.
|
||||
*
|
||||
* @param id the UUID of the boss bar
|
||||
* @param bar the {@link BossBar} instance
|
||||
* @return a {@link BossBarPacket} to update the boss bar's style
|
||||
*/
|
||||
public static BossBarPacket createUpdateStylePacket(final UUID id, final BossBar bar) {
|
||||
final BossBarPacket packet = new BossBarPacket();
|
||||
packet.setUuid(id);
|
||||
@@ -122,6 +163,13 @@ public class BossBarPacket implements MinecraftPacket {
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a packet to update the properties of the boss bar.
|
||||
*
|
||||
* @param id the UUID of the boss bar
|
||||
* @param bar the {@link BossBar} instance
|
||||
* @return a {@link BossBarPacket} to update the boss bar's properties
|
||||
*/
|
||||
public static BossBarPacket createUpdatePropertiesPacket(final UUID id, final BossBar bar) {
|
||||
final BossBarPacket packet = new BossBarPacket();
|
||||
packet.setUuid(id);
|
||||
@@ -130,6 +178,12 @@ public class BossBarPacket implements MinecraftPacket {
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the UUID of the boss bar.
|
||||
*
|
||||
* @return the UUID of the boss bar
|
||||
* @throws IllegalStateException if the UUID has not been set
|
||||
*/
|
||||
public UUID getUuid() {
|
||||
if (uuid == null) {
|
||||
throw new IllegalStateException("No boss bar UUID specified");
|
||||
@@ -214,7 +268,8 @@ public class BossBarPacket implements MinecraftPacket {
|
||||
this.overlay = ProtocolUtils.readVarInt(buf);
|
||||
this.flags = buf.readUnsignedByte();
|
||||
}
|
||||
case REMOVE -> {}
|
||||
case REMOVE -> {
|
||||
}
|
||||
case UPDATE_PERCENT -> this.percent = buf.readFloat();
|
||||
case UPDATE_NAME -> this.name = ComponentHolder.read(buf, version);
|
||||
case UPDATE_STYLE -> {
|
||||
@@ -235,22 +290,23 @@ public class BossBarPacket implements MinecraftPacket {
|
||||
ProtocolUtils.writeVarInt(buf, action);
|
||||
switch (action) {
|
||||
case ADD -> {
|
||||
if (name == null) {
|
||||
throw new IllegalStateException("No name specified!");
|
||||
}
|
||||
name.write(buf);
|
||||
buf.writeFloat(percent);
|
||||
ProtocolUtils.writeVarInt(buf, color);
|
||||
ProtocolUtils.writeVarInt(buf, overlay);
|
||||
buf.writeByte(flags);
|
||||
if (name == null) {
|
||||
throw new IllegalStateException("No name specified!");
|
||||
}
|
||||
name.write(buf);
|
||||
buf.writeFloat(percent);
|
||||
ProtocolUtils.writeVarInt(buf, color);
|
||||
ProtocolUtils.writeVarInt(buf, overlay);
|
||||
buf.writeByte(flags);
|
||||
}
|
||||
case REMOVE -> {
|
||||
}
|
||||
case REMOVE -> {}
|
||||
case UPDATE_PERCENT -> buf.writeFloat(percent);
|
||||
case UPDATE_NAME -> {
|
||||
if (name == null) {
|
||||
throw new IllegalStateException("No name specified!");
|
||||
}
|
||||
name.write(buf);
|
||||
if (name == null) {
|
||||
throw new IllegalStateException("No name specified!");
|
||||
}
|
||||
name.write(buf);
|
||||
}
|
||||
case UPDATE_STYLE -> {
|
||||
ProtocolUtils.writeVarInt(buf, color);
|
||||
@@ -264,7 +320,7 @@ public class BossBarPacket implements MinecraftPacket {
|
||||
private static byte serializeFlags(Set<BossBar.Flag> flags) {
|
||||
byte val = 0x0;
|
||||
for (BossBar.Flag flag : flags) {
|
||||
val |= FLAG_BITS_TO_PROTOCOL.get(flag);
|
||||
val |= (byte) FLAG_BITS_TO_PROTOCOL.get(flag);
|
||||
}
|
||||
return val;
|
||||
}
|
||||
@@ -273,4 +329,4 @@ public class BossBarPacket implements MinecraftPacket {
|
||||
public boolean handle(MinecraftSessionHandler handler) {
|
||||
return handler.handle(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,14 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
/**
|
||||
* Represents a packet used as a delimiter for bundling multiple packets together.
|
||||
* The {@code BundleDelimiterPacket} marks the beginning or end of a packet bundle,
|
||||
* allowing the server and client to process groups of packets as a single logical unit.
|
||||
*
|
||||
* <p>This packet is typically used to signal the start or end of a packet sequence that
|
||||
* are sent together, enabling efficient transmission and processing of related data.</p>
|
||||
*/
|
||||
public final class BundleDelimiterPacket implements MinecraftPacket {
|
||||
public static final BundleDelimiterPacket INSTANCE = new BundleDelimiterPacket();
|
||||
|
||||
|
||||
@@ -23,9 +23,13 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* Represents the client settings packet in Minecraft, which is sent by the client
|
||||
* to the server to communicate its settings such as locale, view distance, chat preferences,
|
||||
* skin customization, and other client-side configurations.
|
||||
*/
|
||||
public class ClientSettingsPacket implements MinecraftPacket {
|
||||
private @Nullable String locale;
|
||||
private byte viewDistance;
|
||||
@@ -41,6 +45,19 @@ public class ClientSettingsPacket implements MinecraftPacket {
|
||||
public ClientSettingsPacket() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@code ClientSettingsPacket} with the specified settings.
|
||||
*
|
||||
* @param locale the client's locale setting
|
||||
* @param viewDistance the view distance
|
||||
* @param chatVisibility the client's chat visibility setting
|
||||
* @param chatColors whether chat colors are enabled
|
||||
* @param skinParts the customization for skin parts
|
||||
* @param mainHand the client's main hand preference
|
||||
* @param textFilteringEnabled whether text filtering is enabled
|
||||
* @param clientListingAllowed whether the client allows listing
|
||||
* @param particleStatus whether particles are enabled
|
||||
*/
|
||||
public ClientSettingsPacket(String locale, byte viewDistance, int chatVisibility, boolean chatColors,
|
||||
short skinParts, int mainHand, boolean textFilteringEnabled, boolean clientListingAllowed,
|
||||
int particleStatus) {
|
||||
@@ -55,6 +72,12 @@ public class ClientSettingsPacket implements MinecraftPacket {
|
||||
this.particleStatus = particleStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the client's locale.
|
||||
*
|
||||
* @return the locale
|
||||
* @throws IllegalStateException if no locale is specified
|
||||
*/
|
||||
public String getLocale() {
|
||||
if (locale == null) {
|
||||
throw new IllegalStateException("No locale specified");
|
||||
@@ -132,10 +155,10 @@ public class ClientSettingsPacket implements MinecraftPacket {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ClientSettings{" + "locale='" + locale + '\'' + ", viewDistance=" + viewDistance +
|
||||
", chatVisibility=" + chatVisibility + ", chatColors=" + chatColors + ", skinParts=" +
|
||||
skinParts + ", mainHand=" + mainHand + ", chatFilteringEnabled=" + textFilteringEnabled +
|
||||
", clientListingAllowed=" + clientListingAllowed + ", particleStatus=" + particleStatus + '}';
|
||||
return "ClientSettings{" + "locale='" + locale + '\'' + ", viewDistance=" + viewDistance
|
||||
+ ", chatVisibility=" + chatVisibility + ", chatColors=" + chatColors + ", skinParts="
|
||||
+ skinParts + ", mainHand=" + mainHand + ", chatFilteringEnabled=" + textFilteringEnabled
|
||||
+ ", clientListingAllowed=" + clientListingAllowed + ", particleStatus=" + particleStatus + '}';
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -25,6 +25,11 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.kyori.adventure.key.Key;
|
||||
|
||||
/**
|
||||
* Represents a packet sent from the server to the client to request cookies.
|
||||
* This packet can be used to initiate a request for cookie-related data from the client,
|
||||
* typically for authentication or tracking purposes.
|
||||
*/
|
||||
public class ClientboundCookieRequestPacket implements MinecraftPacket {
|
||||
|
||||
private Key key;
|
||||
|
||||
@@ -22,11 +22,16 @@ import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import java.util.Random;
|
||||
import net.kyori.adventure.sound.Sound;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* A clientbound packet that instructs the client to play a sound tied to an entity.
|
||||
*
|
||||
* <p>This is sent by the server when a sound should be played at the location of a
|
||||
* specific entity, with optional fixed range and seed.</p>
|
||||
*/
|
||||
public class ClientboundSoundEntityPacket implements MinecraftPacket {
|
||||
|
||||
private static final Random SEEDS_RANDOM = new Random();
|
||||
@@ -35,8 +40,16 @@ public class ClientboundSoundEntityPacket implements MinecraftPacket {
|
||||
private @Nullable Float fixedRange;
|
||||
private int emitterEntityId;
|
||||
|
||||
public ClientboundSoundEntityPacket() {}
|
||||
public ClientboundSoundEntityPacket() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new sound entity packet.
|
||||
*
|
||||
* @param sound the sound to play
|
||||
* @param fixedRange the fixed attenuation range, or {@code null} to use the default
|
||||
* @param emitterEntityId the entity ID of the sound emitter
|
||||
*/
|
||||
public ClientboundSoundEntityPacket(Sound sound, @Nullable Float fixedRange, int emitterEntityId) {
|
||||
this.sound = sound;
|
||||
this.fixedRange = fixedRange;
|
||||
@@ -55,8 +68,9 @@ public class ClientboundSoundEntityPacket implements MinecraftPacket {
|
||||
ProtocolUtils.writeMinimalKey(buf, sound.name());
|
||||
|
||||
buf.writeBoolean(fixedRange != null);
|
||||
if (fixedRange != null)
|
||||
if (fixedRange != null) {
|
||||
buf.writeFloat(fixedRange);
|
||||
}
|
||||
|
||||
ProtocolUtils.writeSoundSource(buf, protocolVersion, sound.source());
|
||||
|
||||
|
||||
@@ -22,18 +22,24 @@ import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import javax.annotation.Nullable;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import net.kyori.adventure.sound.Sound;
|
||||
import net.kyori.adventure.sound.SoundStop;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* A clientbound packet instructing the client to stop one or more sounds.
|
||||
*
|
||||
* <p>This packet supports specifying a {@link Sound.Source}, a {@link Key} sound identifier,
|
||||
* or both together. If neither is specified, the client will stop all currently playing sounds.</p>
|
||||
*/
|
||||
public class ClientboundStopSoundPacket implements MinecraftPacket {
|
||||
|
||||
private @Nullable Sound.Source source;
|
||||
private @Nullable Key soundName;
|
||||
|
||||
public ClientboundStopSoundPacket() {}
|
||||
public ClientboundStopSoundPacket() {
|
||||
}
|
||||
|
||||
public ClientboundStopSoundPacket(SoundStop soundStop) {
|
||||
this(soundStop.source(), soundStop.sound());
|
||||
|
||||
@@ -25,6 +25,11 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.kyori.adventure.key.Key;
|
||||
|
||||
/**
|
||||
* Represents a packet sent from the server to the client to store a cookie.
|
||||
* This packet can be used to send cookie-related data from the server to be stored or processed
|
||||
* by the client.
|
||||
*/
|
||||
public class ClientboundStoreCookiePacket implements MinecraftPacket {
|
||||
|
||||
private Key key;
|
||||
|
||||
@@ -23,6 +23,13 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
/**
|
||||
* Represents the packet sent by the server to the client to clear any
|
||||
* currently displayed configuration dialog.
|
||||
*
|
||||
* <p>This packet is used during the configuration phase (1.21.6+) to
|
||||
* instruct the client to dismiss an active dialog window.</p>
|
||||
*/
|
||||
public class DialogClearPacket implements MinecraftPacket {
|
||||
|
||||
public static final DialogClearPacket INSTANCE = new DialogClearPacket();
|
||||
|
||||
@@ -27,6 +27,13 @@ import io.netty.buffer.ByteBuf;
|
||||
import net.kyori.adventure.nbt.BinaryTag;
|
||||
import net.kyori.adventure.nbt.BinaryTagIO;
|
||||
|
||||
/**
|
||||
* Represents the packet sent by the server to the client to display a configuration dialog
|
||||
* during the configuration phase in Minecraft 1.21.6+.
|
||||
*
|
||||
* <p>This packet is only relevant in the CONFIG and PLAY states. If the ID is {@code 0},
|
||||
* a dialog is to be shown and the accompanying {@link BinaryTag} contains its data.</p>
|
||||
*/
|
||||
public class DialogShowPacket implements MinecraftPacket {
|
||||
|
||||
private final StateRegistry state;
|
||||
|
||||
@@ -28,6 +28,12 @@ import io.netty.buffer.ByteBuf;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* Represents a packet sent by the server to disconnect the client. This packet contains
|
||||
* a reason for the disconnection, which is sent to the client and displayed to the player.
|
||||
* The packet can be sent in different states (e.g., login, play), which affects how the
|
||||
* reason is processed.
|
||||
*/
|
||||
public class DisconnectPacket implements MinecraftPacket {
|
||||
|
||||
private @Nullable ComponentHolder reason;
|
||||
@@ -42,6 +48,12 @@ public class DisconnectPacket implements MinecraftPacket {
|
||||
this.reason = Preconditions.checkNotNull(reason, "reason");
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the reason for the disconnection, which will be sent to the client.
|
||||
*
|
||||
* @return the reason for the disconnection as a {@link ComponentHolder}
|
||||
* @throws IllegalStateException if no reason is specified
|
||||
*/
|
||||
public ComponentHolder getReason() {
|
||||
if (reason == null) {
|
||||
throw new IllegalStateException("No reason specified");
|
||||
@@ -62,8 +74,8 @@ public class DisconnectPacket implements MinecraftPacket {
|
||||
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
|
||||
reason = ComponentHolder.read(buf, state == StateRegistry.LOGIN
|
||||
? ProtocolVersion.MINECRAFT_1_20_2 : version);
|
||||
reason = ComponentHolder.read(buf, state == StateRegistry.LOGIN
|
||||
? ProtocolVersion.MINECRAFT_1_20_2 : version);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -76,9 +88,17 @@ public class DisconnectPacket implements MinecraftPacket {
|
||||
return handler.handle(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@code DisconnectPacket} with the specified reason and version.
|
||||
*
|
||||
* @param component the component explaining the disconnection reason
|
||||
* @param version the protocol version in use
|
||||
* @param state the state in which the disconnection occurs
|
||||
* @return the created {@code DisconnectPacket}
|
||||
*/
|
||||
public static DisconnectPacket create(Component component, ProtocolVersion version, StateRegistry state) {
|
||||
Preconditions.checkNotNull(component, "component");
|
||||
return new DisconnectPacket(state, new ComponentHolder(state == StateRegistry.LOGIN
|
||||
? ProtocolVersion.MINECRAFT_1_20_2 : version, component));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,12 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Represents the encryption request packet in Minecraft, which is sent by the server
|
||||
* during the encryption handshake process. This packet is used to initiate secure
|
||||
* communication by providing the client with the server's public key and a verified token.
|
||||
* The client must respond with the encrypted shared secret and verify token.
|
||||
*/
|
||||
public class EncryptionRequestPacket implements MinecraftPacket {
|
||||
|
||||
private String serverId = "";
|
||||
|
||||
@@ -26,10 +26,18 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
|
||||
import com.velocitypowered.proxy.util.except.QuietDecoderException;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import java.util.Arrays;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Represents the encryption response packet in Minecraft, which is sent by the client
|
||||
* during the encryption handshake process. This packet contains the shared secret
|
||||
* and verifies the token used to establish secure communication between the client
|
||||
* and the server.
|
||||
*
|
||||
* <p>The packet structure varies depending on the Minecraft protocol version, with additional
|
||||
* fields such as a salt being present in versions 1.19 and above.</p>
|
||||
*/
|
||||
public class EncryptionResponsePacket implements MinecraftPacket {
|
||||
|
||||
private static final QuietDecoderException NO_SALT = new QuietDecoderException(
|
||||
@@ -47,6 +55,13 @@ public class EncryptionResponsePacket implements MinecraftPacket {
|
||||
return verifyToken.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the salt used in the encryption response. The salt is introduced in
|
||||
* Minecraft version 1.19 and is optional in certain protocol versions.
|
||||
*
|
||||
* @return the salt used in the encryption response
|
||||
* @throws QuietDecoderException if the salt is not present
|
||||
*/
|
||||
public long getSalt() {
|
||||
if (salt == null) {
|
||||
throw NO_SALT;
|
||||
|
||||
@@ -27,6 +27,12 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
/**
|
||||
* Represents a handshake packet in Minecraft, which is used during the initial connection process.
|
||||
* This packet contains information such as the protocol version, server address, port, and the intent
|
||||
* of the handshake (e.g., login or status request). This packet is crucial for establishing a connection
|
||||
* between the client and the server.
|
||||
*/
|
||||
public class HandshakePacket implements MinecraftPacket {
|
||||
|
||||
// This size was chosen to ensure Forge clients can still connect even with very long hostnames.
|
||||
@@ -110,13 +116,13 @@ public class HandshakePacket implements MinecraftPacket {
|
||||
|
||||
@Override
|
||||
public int decodeExpectedMinLength(ByteBuf buf, ProtocolUtils.Direction direction,
|
||||
ProtocolVersion version) {
|
||||
ProtocolVersion version) {
|
||||
return 7;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int decodeExpectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction,
|
||||
ProtocolVersion version) {
|
||||
ProtocolVersion version) {
|
||||
return 9 + (MAXIMUM_HOSTNAME_LENGTH * 3);
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,10 @@ import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.kyori.adventure.text.Component;
|
||||
|
||||
/**
|
||||
* Represents a packet that contains both the header and footer for the player list screen (tab list) in Minecraft.
|
||||
* This packet allows the server to set or update the header and footer text that is displayed on the client's tab list.
|
||||
*/
|
||||
public class HeaderAndFooterPacket implements MinecraftPacket {
|
||||
|
||||
private final ComponentHolder header;
|
||||
@@ -67,11 +71,11 @@ public class HeaderAndFooterPacket implements MinecraftPacket {
|
||||
public static HeaderAndFooterPacket create(Component header,
|
||||
Component footer, ProtocolVersion protocolVersion) {
|
||||
return new HeaderAndFooterPacket(new ComponentHolder(protocolVersion, header),
|
||||
new ComponentHolder(protocolVersion, footer));
|
||||
new ComponentHolder(protocolVersion, footer));
|
||||
}
|
||||
|
||||
public static HeaderAndFooterPacket reset(ProtocolVersion version) {
|
||||
ComponentHolder empty = new ComponentHolder(version, Component.empty());
|
||||
return new HeaderAndFooterPacket(empty, empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,13 +21,19 @@ import com.google.common.collect.ImmutableSet;
|
||||
import com.velocitypowered.api.network.ProtocolVersion;
|
||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||
import com.velocitypowered.proxy.connection.registry.DimensionInfo;
|
||||
import com.velocitypowered.proxy.protocol.*;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import it.unimi.dsi.fastutil.Pair;
|
||||
import net.kyori.adventure.nbt.BinaryTagIO;
|
||||
import net.kyori.adventure.nbt.CompoundBinaryTag;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* Represents a packet sent to the client when they successfully join a game in Minecraft.
|
||||
* This packet contains all the necessary information to initialize the client state,
|
||||
* including the player's entity ID, game mode, dimension, world settings, and more.
|
||||
*/
|
||||
public class JoinGamePacket implements MinecraftPacket {
|
||||
|
||||
private static final BinaryTagIO.Reader JOINGAME_READER = BinaryTagIO.reader(4 * 1024 * 1024);
|
||||
@@ -204,17 +210,17 @@ public class JoinGamePacket implements MinecraftPacket {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "JoinGame{" + "entityId=" + entityId + ", gamemode=" + gamemode + ", dimension=" +
|
||||
dimension + ", partialHashedSeed=" + partialHashedSeed + ", difficulty=" + difficulty +
|
||||
", isHardcore=" + isHardcore + ", maxPlayers=" + maxPlayers + ", levelType='" + levelType +
|
||||
'\'' + ", viewDistance=" + viewDistance + ", reducedDebugInfo=" + reducedDebugInfo +
|
||||
", showRespawnScreen=" + showRespawnScreen + ", doLimitedCrafting=" + doLimitedCrafting +
|
||||
", levelNames=" + levelNames + ", registry='" + registry + '\'' + ", dimensionInfo='" +
|
||||
dimensionInfo + '\'' + ", currentDimensionData='" + currentDimensionData + '\'' +
|
||||
", previousGamemode=" + previousGamemode + ", simulationDistance=" + simulationDistance +
|
||||
", lastDeathPosition='" + lastDeathPosition + '\'' + ", portalCooldown=" + portalCooldown +
|
||||
", seaLevel=" + seaLevel +
|
||||
'}';
|
||||
return "JoinGame{" + "entityId=" + entityId + ", gamemode=" + gamemode + ", dimension="
|
||||
+ dimension + ", partialHashedSeed=" + partialHashedSeed + ", difficulty=" + difficulty
|
||||
+ ", isHardcore=" + isHardcore + ", maxPlayers=" + maxPlayers + ", levelType='" + levelType
|
||||
+ '\'' + ", viewDistance=" + viewDistance + ", reducedDebugInfo=" + reducedDebugInfo
|
||||
+ ", showRespawnScreen=" + showRespawnScreen + ", doLimitedCrafting=" + doLimitedCrafting
|
||||
+ ", levelNames=" + levelNames + ", registry='" + registry + '\'' + ", dimensionInfo='"
|
||||
+ dimensionInfo + '\'' + ", currentDimensionData='" + currentDimensionData + '\''
|
||||
+ ", previousGamemode=" + previousGamemode + ", simulationDistance=" + simulationDistance
|
||||
+ ", lastDeathPosition='" + lastDeathPosition + '\'' + ", portalCooldown=" + portalCooldown
|
||||
+ ", seaLevel=" + seaLevel
|
||||
+ '}';
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -23,6 +23,11 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
/**
|
||||
* Represents a KeepAlive packet in Minecraft. This packet is used to ensure that the connection
|
||||
* between the client and the server shall still be active by sending a randomly generated ID that
|
||||
* the client must respond to.
|
||||
*/
|
||||
public class KeepAlivePacket implements MinecraftPacket {
|
||||
|
||||
private long randomId;
|
||||
|
||||
@@ -25,7 +25,13 @@ import net.kyori.adventure.text.TextComponent;
|
||||
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
||||
|
||||
@SuppressWarnings("checkstyle:MissingJavadocType")
|
||||
/**
|
||||
* Represents a legacy disconnect packet that contains a reason for disconnection.
|
||||
* This class is used to convert modern server ping responses into the legacy format,
|
||||
* which is compatible with older Minecraft versions.
|
||||
*
|
||||
* @param reason the string reason for disconnection
|
||||
*/
|
||||
public record LegacyDisconnect(String reason) {
|
||||
|
||||
private static final ServerPing.Players FAKE_PLAYERS = new ServerPing.Players(0, 0,
|
||||
@@ -46,17 +52,17 @@ public record LegacyDisconnect(String reason) {
|
||||
|
||||
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.
|
||||
new LegacyDisconnect(String.join(LEGACY_COLOR_CODE,
|
||||
// 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.
|
||||
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, MINECRAFT_1_6 ->
|
||||
// Minecraft 1.4-1.6 provide support for more fields, and additionally support color codes.
|
||||
new LegacyDisconnect(String.join("\0",
|
||||
// Minecraft 1.4-1.6 provide support for more fields, and additionally support color codes.
|
||||
new LegacyDisconnect(String.join("\0",
|
||||
LEGACY_COLOR_CODE + "1",
|
||||
Integer.toString(response.getVersion().getProtocol()),
|
||||
response.getVersion().getName(),
|
||||
|
||||
@@ -23,6 +23,11 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
/**
|
||||
* Represents a legacy handshake packet in Minecraft, which is typically used
|
||||
* during the initial connection process for older versions of the Minecraft protocol.
|
||||
* This class currently does not support decoding of the handshake packet.
|
||||
*/
|
||||
public class LegacyHandshakePacket implements MinecraftPacket {
|
||||
|
||||
@Override
|
||||
|
||||
@@ -26,6 +26,11 @@ import io.netty.buffer.ByteBuf;
|
||||
import java.net.InetSocketAddress;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* Represents a legacy ping packet in Minecraft, commonly used in the server list ping process.
|
||||
* This packet handles compatibility with older Minecraft versions and contains information
|
||||
* such as the ping protocol version and optionally a virtual host address.
|
||||
*/
|
||||
public class LegacyPingPacket implements MinecraftPacket {
|
||||
|
||||
private final LegacyMinecraftPingVersion version;
|
||||
|
||||
@@ -33,6 +33,10 @@ import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* Represents a legacy player list item packet, which is used to modify the player list in a Minecraft client.
|
||||
* The packet can add, remove, or update player entries (e.g., updating gamemode, latency, or display names).
|
||||
*/
|
||||
public class LegacyPlayerListItemPacket implements MinecraftPacket {
|
||||
|
||||
public static final int ADD_PLAYER = 0;
|
||||
@@ -76,16 +80,16 @@ public class LegacyPlayerListItemPacket implements MinecraftPacket {
|
||||
item.setLatency(ProtocolUtils.readVarInt(buf));
|
||||
item.setDisplayName(readOptionalComponent(buf, version));
|
||||
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) {
|
||||
if (buf.readBoolean()) {
|
||||
item.setPlayerKey(ProtocolUtils.readPlayerKey(version, buf));
|
||||
}
|
||||
if (buf.readBoolean()) {
|
||||
item.setPlayerKey(ProtocolUtils.readPlayerKey(version, buf));
|
||||
}
|
||||
}
|
||||
}
|
||||
case UPDATE_GAMEMODE -> item.setGameMode(ProtocolUtils.readVarInt(buf));
|
||||
case UPDATE_LATENCY -> item.setLatency(ProtocolUtils.readVarInt(buf));
|
||||
case UPDATE_DISPLAY_NAME -> item.setDisplayName(readOptionalComponent(buf, version));
|
||||
case REMOVE_PLAYER -> {
|
||||
//Do nothing, all that is needed is the uuid
|
||||
// Do nothing, all that is needed is the uuid
|
||||
}
|
||||
default -> throw new UnsupportedOperationException("Unknown action " + action);
|
||||
}
|
||||
@@ -107,6 +111,17 @@ public class LegacyPlayerListItemPacket implements MinecraftPacket {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes this packet's contents into the given {@link ByteBuf}.
|
||||
*
|
||||
* <p>This method serializes the packet data based on the current protocol version.
|
||||
* Subclasses overriding this method should preserve compatibility with legacy
|
||||
* and modern formats as needed.</p>
|
||||
*
|
||||
* @param buf the buffer to write to
|
||||
* @param direction the direction of the packet (clientbound or serverbound)
|
||||
* @param version the Minecraft protocol version
|
||||
*/
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
|
||||
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_8)) {
|
||||
@@ -125,12 +140,12 @@ public class LegacyPlayerListItemPacket implements MinecraftPacket {
|
||||
ProtocolUtils.writeVarInt(buf, item.getLatency());
|
||||
writeDisplayName(buf, item.getDisplayName(), version);
|
||||
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) {
|
||||
if (item.getPlayerKey() != null) {
|
||||
buf.writeBoolean(true);
|
||||
ProtocolUtils.writePlayerKey(buf, item.getPlayerKey());
|
||||
} else {
|
||||
buf.writeBoolean(false);
|
||||
}
|
||||
if (item.getPlayerKey() != null) {
|
||||
buf.writeBoolean(true);
|
||||
ProtocolUtils.writePlayerKey(buf, item.getPlayerKey());
|
||||
} else {
|
||||
buf.writeBoolean(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
case UPDATE_GAMEMODE -> ProtocolUtils.writeVarInt(buf, item.getGameMode());
|
||||
@@ -172,6 +187,10 @@ public class LegacyPlayerListItemPacket implements MinecraftPacket {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an individual item in the player list, containing the player's details such as UUID, name,
|
||||
* game mode, latency, and optionally a display name and player key.
|
||||
*/
|
||||
public static class Item {
|
||||
|
||||
private final UUID uuid;
|
||||
@@ -190,6 +209,15 @@ public class LegacyPlayerListItemPacket implements MinecraftPacket {
|
||||
this.uuid = uuid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an {@link Item} instance from a {@link TabListEntry}.
|
||||
* This method extracts relevant data from the {@link TabListEntry} such as
|
||||
* the player's profile ID, name, properties, latency, game mode, player key,
|
||||
* and display name, and uses them to populate a new {@code Item}.
|
||||
*
|
||||
* @param entry the {@link TabListEntry} from which to extract data
|
||||
* @return an {@link Item} populated with data from the {@link TabListEntry}
|
||||
*/
|
||||
public static Item from(TabListEntry entry) {
|
||||
return new Item(entry.getProfile().getId())
|
||||
.setName(entry.getProfile().getName())
|
||||
|
||||
@@ -23,6 +23,13 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
/**
|
||||
* Represents a packet that acknowledges a successful login, implementing {@link MinecraftPacket}.
|
||||
*
|
||||
* <p>The {@code LoginAcknowledgedPacket} is sent by the server to confirm that the player's login
|
||||
* process has been successfully completed. It signals the transition from the login phase to the
|
||||
* game or session phase.</p>
|
||||
*/
|
||||
public class LoginAcknowledgedPacket implements MinecraftPacket {
|
||||
|
||||
@Override
|
||||
@@ -37,7 +44,7 @@ public class LoginAcknowledgedPacket implements MinecraftPacket {
|
||||
|
||||
@Override
|
||||
public int decodeExpectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction,
|
||||
ProtocolVersion version) {
|
||||
ProtocolVersion version) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,10 @@ import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* Represents a login plugin message packet sent during the login phase. This packet allows custom
|
||||
* plugin messages to be sent from the server to the client before login is complete.
|
||||
*/
|
||||
public class LoginPluginMessagePacket extends DeferredByteBufHolder implements MinecraftPacket {
|
||||
|
||||
private int id;
|
||||
@@ -36,6 +40,13 @@ public class LoginPluginMessagePacket extends DeferredByteBufHolder implements M
|
||||
super(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@code LoginPluginMessagePacket} with the specified ID, channel, and data buffer.
|
||||
*
|
||||
* @param id the plugin message ID
|
||||
* @param channel the channel name, or {@code null} if not specified
|
||||
* @param data the data buffer
|
||||
*/
|
||||
public LoginPluginMessagePacket(int id, @Nullable String channel, ByteBuf data) {
|
||||
super(data);
|
||||
this.id = id;
|
||||
@@ -46,6 +57,12 @@ public class LoginPluginMessagePacket extends DeferredByteBufHolder implements M
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the plugin message channel.
|
||||
*
|
||||
* @return the channel name
|
||||
* @throws IllegalStateException if the channel is not specified
|
||||
*/
|
||||
public String getChannel() {
|
||||
if (channel == null) {
|
||||
throw new IllegalStateException("Channel is not specified!");
|
||||
|
||||
@@ -27,6 +27,10 @@ import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
|
||||
/**
|
||||
* Represents the response packet to a plugin message sent during the login phase.
|
||||
* The packet contains the plugin message ID, a success flag, and any additional data.
|
||||
*/
|
||||
public class LoginPluginResponsePacket extends DeferredByteBufHolder implements MinecraftPacket {
|
||||
|
||||
private int id;
|
||||
@@ -36,6 +40,13 @@ public class LoginPluginResponsePacket extends DeferredByteBufHolder implements
|
||||
super(Unpooled.EMPTY_BUFFER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@code LoginPluginResponsePacket} with the specified ID, success status, and data buffer.
|
||||
*
|
||||
* @param id the plugin message ID
|
||||
* @param success {@code true} if the plugin message was successful, {@code false} otherwise
|
||||
* @param buf the data buffer
|
||||
*/
|
||||
public LoginPluginResponsePacket(int id, boolean success, @MonotonicNonNull ByteBuf buf) {
|
||||
super(buf);
|
||||
this.id = id;
|
||||
|
||||
@@ -23,6 +23,9 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
/**
|
||||
* Represents a packet used for ping identification with a unique ID.
|
||||
*/
|
||||
public class PingIdentifyPacket implements MinecraftPacket {
|
||||
|
||||
private int id;
|
||||
|
||||
@@ -29,6 +29,10 @@ import io.netty.buffer.ByteBuf;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* Represents a plugin message packet, which allows for custom communication between
|
||||
* a Minecraft server and a client via custom channels.
|
||||
*/
|
||||
public class PluginMessagePacket extends DeferredByteBufHolder implements MinecraftPacket {
|
||||
|
||||
private @Nullable String channel;
|
||||
@@ -43,6 +47,12 @@ public class PluginMessagePacket extends DeferredByteBufHolder implements Minecr
|
||||
this.channel = channel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the channel for this plugin message.
|
||||
*
|
||||
* @return the channel name
|
||||
* @throws IllegalStateException if the channel is not set
|
||||
*/
|
||||
public String getChannel() {
|
||||
if (channel == null) {
|
||||
throw new IllegalStateException("Channel is not specified.");
|
||||
@@ -73,7 +83,6 @@ public class PluginMessagePacket extends DeferredByteBufHolder implements Minecr
|
||||
} else {
|
||||
this.replace(ProtocolUtils.readRetainedByteBufSlice17(buf));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -97,7 +106,6 @@ public class PluginMessagePacket extends DeferredByteBufHolder implements Minecr
|
||||
} else {
|
||||
ProtocolUtils.writeByteBuf17(content(), buf, true); // True for Forge support
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -27,6 +27,10 @@ import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Represents a packet sent to remove player information from the player list.
|
||||
* The packet contains a collection of {@link UUID}s representing the profiles to be removed.
|
||||
*/
|
||||
public class RemovePlayerInfoPacket implements MinecraftPacket {
|
||||
|
||||
private Collection<UUID> profilesToRemove;
|
||||
|
||||
@@ -25,6 +25,10 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Represents a packet sent to remove a previously applied resource pack from the client.
|
||||
* The packet contains an optional UUID that identifies the resource pack to be removed.
|
||||
*/
|
||||
public class RemoveResourcePackPacket implements MinecraftPacket {
|
||||
|
||||
private UUID id;
|
||||
@@ -60,4 +64,4 @@ public class RemoveResourcePackPacket implements MinecraftPacket {
|
||||
public boolean handle(MinecraftSessionHandler handler) {
|
||||
return handler.handle(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,10 @@ import java.util.regex.Pattern;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* Represents a resource pack request packet sent by the server to prompt the client to download a resource pack.
|
||||
* The packet includes the resource pack URL, SHA1 hash, and optional prompt.
|
||||
*/
|
||||
public class ResourcePackRequestPacket implements MinecraftPacket {
|
||||
|
||||
private @MonotonicNonNull UUID id; // 1.20.3+
|
||||
@@ -124,6 +128,12 @@ public class ResourcePackRequestPacket implements MinecraftPacket {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts this packet into a {@link VelocityResourcePackInfo} object, which contains the information
|
||||
* about the resource pack being requested.
|
||||
*
|
||||
* @return a {@code VelocityResourcePackInfo} representing the resource pack information
|
||||
*/
|
||||
public VelocityResourcePackInfo toServerPromptedPack() {
|
||||
final ResourcePackInfo.Builder builder =
|
||||
new VelocityResourcePackInfo.BuilderImpl(Preconditions.checkNotNull(url))
|
||||
@@ -145,12 +155,12 @@ public class ResourcePackRequestPacket implements MinecraftPacket {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ResourcePackRequestPacket{" +
|
||||
"id=" + id +
|
||||
", url='" + url + '\'' +
|
||||
", hash='" + hash + '\'' +
|
||||
", isRequired=" + isRequired +
|
||||
", prompt=" + prompt +
|
||||
'}';
|
||||
return "ResourcePackRequestPacket{"
|
||||
+ "id=" + id
|
||||
+ ", url='" + url + '\''
|
||||
+ ", hash='" + hash + '\''
|
||||
+ ", isRequired=" + isRequired
|
||||
+ ", prompt=" + prompt
|
||||
+ '}';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,10 +24,13 @@ 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;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Represents the response packet sent by the client after receiving a resource pack request from the server.
|
||||
* The packet contains information about the client's response, including the resource pack status.
|
||||
*/
|
||||
public class ResourcePackResponsePacket implements MinecraftPacket {
|
||||
|
||||
private UUID id;
|
||||
@@ -37,12 +40,25 @@ public class ResourcePackResponsePacket implements MinecraftPacket {
|
||||
public ResourcePackResponsePacket() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@code ResourcePackResponsePacket} with the specified parameters.
|
||||
*
|
||||
* @param id the unique identifier for the response
|
||||
* @param hash the hash of the resource pack
|
||||
* @param status the status of the resource pack
|
||||
*/
|
||||
public ResourcePackResponsePacket(UUID id, String hash, @MonotonicNonNull Status status) {
|
||||
this.id = id;
|
||||
this.hash = hash;
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the status of the resource pack response.
|
||||
*
|
||||
* @return the status of the response
|
||||
* @throws IllegalStateException if the packet has not been deserialized yet
|
||||
*/
|
||||
public Status getStatus() {
|
||||
if (status == null) {
|
||||
throw new IllegalStateException("Packet not yet deserialized");
|
||||
@@ -87,10 +103,10 @@ public class ResourcePackResponsePacket implements MinecraftPacket {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ResourcePackResponsePacket{" +
|
||||
"id=" + id +
|
||||
", hash='" + hash + '\'' +
|
||||
", status=" + status +
|
||||
'}';
|
||||
return "ResourcePackResponsePacket{"
|
||||
+ "id=" + id
|
||||
+ ", hash='" + hash + '\''
|
||||
+ ", status=" + status
|
||||
+ '}';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,10 @@ import net.kyori.adventure.nbt.BinaryTagIO;
|
||||
import net.kyori.adventure.nbt.CompoundBinaryTag;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* Represents a respawn packet sent by the server when the player changes dimensions or respawns.
|
||||
* The packet contains information about the new dimension, difficulty, gamemode, and more.
|
||||
*/
|
||||
public class RespawnPacket implements MinecraftPacket {
|
||||
|
||||
private int dimension;
|
||||
@@ -46,6 +50,22 @@ public class RespawnPacket implements MinecraftPacket {
|
||||
public RespawnPacket() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@code RespawnPacket} with the specified parameters.
|
||||
*
|
||||
* @param dimension the dimension the player is respawning or teleporting to
|
||||
* @param partialHashedSeed the partial hashed seed
|
||||
* @param difficulty the difficulty of the server
|
||||
* @param gamemode the player's current gamemode
|
||||
* @param levelType the type of level (e.g., "default", "flat")
|
||||
* @param dataToKeep a byte flag indicating whether certain data should be kept
|
||||
* @param dimensionInfo additional information about the dimension (for 1.16-1.16.1)
|
||||
* @param previousGamemode the player's previous gamemode
|
||||
* @param currentDimensionData data about the current dimension (for 1.16.2+)
|
||||
* @param lastDeathPosition optional last death position (for 1.19+)
|
||||
* @param portalCooldown the cooldown for portal usage (for 1.20+)
|
||||
* @param seaLevel a determinable spawn point for a user (for 1.21.2+)
|
||||
*/
|
||||
public RespawnPacket(int dimension, long partialHashedSeed, short difficulty, short gamemode,
|
||||
String levelType, byte dataToKeep, DimensionInfo dimensionInfo,
|
||||
short previousGamemode, CompoundBinaryTag currentDimensionData,
|
||||
@@ -65,6 +85,12 @@ public class RespawnPacket implements MinecraftPacket {
|
||||
this.seaLevel = seaLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@code RespawnPacket} from a {@link JoinGamePacket}.
|
||||
*
|
||||
* @param joinGame the {@code JoinGamePacket} to use
|
||||
* @return a new {@code RespawnPacket} based on the provided {@code JoinGamePacket}
|
||||
*/
|
||||
public static RespawnPacket fromJoinGame(JoinGamePacket joinGame) {
|
||||
return new RespawnPacket(joinGame.getDimension(), joinGame.getPartialHashedSeed(),
|
||||
joinGame.getDifficulty(), joinGame.getGamemode(), joinGame.getLevelType(),
|
||||
|
||||
@@ -25,10 +25,14 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
|
||||
import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Represents the server data packet sent from the server to the client, which contains information
|
||||
* such as the server description, favicon, and secure chat enforcement status.
|
||||
*/
|
||||
public class ServerDataPacket implements MinecraftPacket {
|
||||
|
||||
private @Nullable ComponentHolder description;
|
||||
@@ -38,6 +42,13 @@ public class ServerDataPacket implements MinecraftPacket {
|
||||
public ServerDataPacket() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@code ServerDataPacket} with the given server description, favicon, and secure chat enforcement status.
|
||||
*
|
||||
* @param description the server description (maybe null)
|
||||
* @param favicon the server favicon (maybe null)
|
||||
* @param secureChatEnforced whether secure chat is enforced (for versions 1.19.1 to 1.20.5)
|
||||
*/
|
||||
public ServerDataPacket(@Nullable ComponentHolder description, @Nullable Favicon favicon,
|
||||
boolean secureChatEnforced) {
|
||||
this.description = description;
|
||||
@@ -127,4 +138,4 @@ public class ServerDataPacket implements MinecraftPacket {
|
||||
public int encodeSizeHint(Direction direction, ProtocolVersion version) {
|
||||
return 8 * 1024;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,11 @@ import io.netty.buffer.ByteBuf;
|
||||
import java.util.UUID;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* Represents the packet sent from the client to the server during the login phase.
|
||||
* This packet contains the player's username, optionally a cryptographic key for
|
||||
* authentication, and the holder UUID depending on the Minecraft protocol version.
|
||||
*/
|
||||
public class ServerLoginPacket implements MinecraftPacket {
|
||||
|
||||
private static final QuietDecoderException EMPTY_USERNAME = new QuietDecoderException(
|
||||
@@ -41,17 +46,35 @@ public class ServerLoginPacket implements MinecraftPacket {
|
||||
public ServerLoginPacket() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a {@code ServerLoginPacket} with a username and optional player key.
|
||||
*
|
||||
* @param username the player's username
|
||||
* @param playerKey the player's cryptographic key, or {@code null} if not present
|
||||
*/
|
||||
public ServerLoginPacket(String username, @Nullable IdentifiedKey playerKey) {
|
||||
this.username = Preconditions.checkNotNull(username, "username");
|
||||
this.playerKey = playerKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@code ServerLoginPacket} with the specified username and holder UUID.
|
||||
*
|
||||
* @param username the player's username
|
||||
* @param holderUuid the holder UUID (optional)
|
||||
*/
|
||||
public ServerLoginPacket(String username, @Nullable UUID holderUuid) {
|
||||
this.username = Preconditions.checkNotNull(username, "username");
|
||||
this.holderUuid = holderUuid;
|
||||
this.playerKey = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the player's username from the login packet.
|
||||
*
|
||||
* @return the player's username
|
||||
* @throws IllegalStateException if the username is not specified
|
||||
*/
|
||||
public String getUsername() {
|
||||
if (username == null) {
|
||||
throw new IllegalStateException("No username found!");
|
||||
@@ -74,10 +97,10 @@ public class ServerLoginPacket implements MinecraftPacket {
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ServerLogin{"
|
||||
+ "username='" + username + '\''
|
||||
+ "playerKey='" + playerKey + '\''
|
||||
+ "holderUUID='" + holderUuid + '\''
|
||||
+ '}';
|
||||
+ "username='" + username + '\''
|
||||
+ "playerKey='" + playerKey + '\''
|
||||
+ "holderUUID='" + holderUuid + '\''
|
||||
+ '}';
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -30,6 +30,10 @@ import java.util.List;
|
||||
import java.util.UUID;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* Represents the packet sent from the server to the client to indicate successful login.
|
||||
* This packet contains the player's UUID, username, and properties associated with their profile.
|
||||
*/
|
||||
public class ServerLoginSuccessPacket implements MinecraftPacket {
|
||||
|
||||
private @Nullable UUID uuid;
|
||||
@@ -38,6 +42,12 @@ public class ServerLoginSuccessPacket implements MinecraftPacket {
|
||||
private static final boolean strictErrorHandling = VelocityProperties
|
||||
.readBoolean("velocity.strictErrorHandling", true);
|
||||
|
||||
/**
|
||||
* Gets the player's UUID from the login success packet.
|
||||
*
|
||||
* @return the player's UUID
|
||||
* @throws IllegalStateException if the UUID is not specified
|
||||
*/
|
||||
public UUID getUuid() {
|
||||
if (uuid == null) {
|
||||
throw new IllegalStateException("No UUID specified!");
|
||||
@@ -49,6 +59,12 @@ public class ServerLoginSuccessPacket implements MinecraftPacket {
|
||||
this.uuid = uuid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the player's username from the login success packet.
|
||||
*
|
||||
* @return the player's username
|
||||
* @throws IllegalStateException if the username is not specified
|
||||
*/
|
||||
public String getUsername() {
|
||||
if (username == null) {
|
||||
throw new IllegalStateException("No username specified!");
|
||||
|
||||
@@ -26,6 +26,11 @@ import io.netty.buffer.ByteBuf;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* Represents a server-bound packet sent by the client containing a key and an optional payload.
|
||||
* This packet is typically used for exchanging metadata or other information between the client
|
||||
* and server.
|
||||
*/
|
||||
public class ServerboundCookieResponsePacket implements MinecraftPacket {
|
||||
|
||||
private Key key;
|
||||
|
||||
@@ -25,6 +25,12 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
|
||||
import com.velocitypowered.proxy.protocol.util.DeferredByteBufHolder;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
/**
|
||||
* Represents a serverbound packet carrying an opaque custom click action payload.
|
||||
*
|
||||
* <p>The payload is retained as-is and forwarded to the session handler without
|
||||
* interpretation by the proxy.</p>
|
||||
*/
|
||||
public class ServerboundCustomClickActionPacket extends DeferredByteBufHolder implements MinecraftPacket {
|
||||
|
||||
public ServerboundCustomClickActionPacket() {
|
||||
|
||||
@@ -23,6 +23,10 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
/**
|
||||
* Represents a packet that sets the compression threshold for network communication.
|
||||
* When the size of a packet exceeds the threshold, the packet will be compressed.
|
||||
*/
|
||||
public class SetCompressionPacket implements MinecraftPacket {
|
||||
|
||||
private int threshold;
|
||||
|
||||
@@ -24,6 +24,10 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
/**
|
||||
* Represents a status ping packet sent by the client to the server, which is used to measure the latency
|
||||
* between the client and server.
|
||||
*/
|
||||
public class StatusPingPacket implements MinecraftPacket {
|
||||
|
||||
private long randomId;
|
||||
|
||||
@@ -24,12 +24,14 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
/**
|
||||
* Represents a status request packet sent by the client to the server to request the server's status.
|
||||
*/
|
||||
public class StatusRequestPacket implements MinecraftPacket {
|
||||
|
||||
public static final StatusRequestPacket INSTANCE = new StatusRequestPacket();
|
||||
|
||||
private StatusRequestPacket() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -25,6 +25,9 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* Represents a status response packet sent from the server to the client.
|
||||
*/
|
||||
public class StatusResponsePacket implements MinecraftPacket {
|
||||
|
||||
private @Nullable CharSequence status;
|
||||
@@ -36,6 +39,12 @@ public class StatusResponsePacket implements MinecraftPacket {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the status message from the packet.
|
||||
*
|
||||
* @return the status message as a {@link String}
|
||||
* @throws IllegalStateException if the status is not specified
|
||||
*/
|
||||
public String getStatus() {
|
||||
if (status == null) {
|
||||
throw new IllegalStateException("Status is not specified");
|
||||
|
||||
@@ -29,6 +29,9 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* Represents a packet sent by the client when a tab-completion request is initiated.
|
||||
*/
|
||||
public class TabCompleteRequestPacket implements MinecraftPacket {
|
||||
|
||||
private static final int VANILLA_MAX_TAB_COMPLETE_LEN = 2048;
|
||||
@@ -39,6 +42,12 @@ public class TabCompleteRequestPacket implements MinecraftPacket {
|
||||
private boolean hasPosition;
|
||||
private long position;
|
||||
|
||||
/**
|
||||
* Gets the command string to be completed.
|
||||
*
|
||||
* @return the command string
|
||||
* @throws IllegalStateException if the command is not set
|
||||
*/
|
||||
public String getCommand() {
|
||||
if (command == null) {
|
||||
throw new IllegalStateException("Command is not specified");
|
||||
|
||||
@@ -30,6 +30,9 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* Represents the packet used to send tab-completion suggestions to the client.
|
||||
*/
|
||||
public class TabCompleteResponsePacket implements MinecraftPacket {
|
||||
|
||||
private int transactionId;
|
||||
@@ -122,6 +125,9 @@ public class TabCompleteResponsePacket implements MinecraftPacket {
|
||||
return handler.handle(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an individual tab-completion suggestion (offer) sent to the client.
|
||||
*/
|
||||
public static class Offer implements Comparable<Offer> {
|
||||
|
||||
private final String text;
|
||||
|
||||
@@ -25,6 +25,9 @@ import io.netty.buffer.ByteBuf;
|
||||
import java.net.InetSocketAddress;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Represents a packet used to transfer a player to another server.
|
||||
*/
|
||||
public class TransferPacket implements MinecraftPacket {
|
||||
private String host;
|
||||
private int port;
|
||||
@@ -32,11 +35,22 @@ public class TransferPacket implements MinecraftPacket {
|
||||
public TransferPacket() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a {@code TransferPacket} with the specified host and port.
|
||||
*
|
||||
* @param host the hostname of the destination server
|
||||
* @param port the port of the destination server
|
||||
*/
|
||||
public TransferPacket(final String host, final int port) {
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link InetSocketAddress} representing the transfer address.
|
||||
*
|
||||
* @return the {@code InetSocketAddress}, or {@code null} if the host is not set
|
||||
*/
|
||||
@Nullable
|
||||
public InetSocketAddress address() {
|
||||
if (host == null) {
|
||||
|
||||
@@ -34,6 +34,9 @@ import java.util.List;
|
||||
import java.util.UUID;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Represents the packet for updating or inserting player information.
|
||||
*/
|
||||
public class UpsertPlayerInfoPacket implements MinecraftPacket {
|
||||
|
||||
private static final Action[] ALL_ACTIONS = Action.class.getEnumConstants();
|
||||
@@ -133,6 +136,9 @@ public class UpsertPlayerInfoPacket implements MinecraftPacket {
|
||||
return handler.handle(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the possible actions in the player info packet.
|
||||
*/
|
||||
public enum Action {
|
||||
ADD_PLAYER((ignored, buf, info) -> { // read
|
||||
info.profile = new GameProfile(
|
||||
@@ -213,6 +219,9 @@ public class UpsertPlayerInfoPacket implements MinecraftPacket {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an entry in the player info packet.
|
||||
*/
|
||||
public static class Entry {
|
||||
|
||||
private final UUID profileId;
|
||||
@@ -303,16 +312,16 @@ public class UpsertPlayerInfoPacket implements MinecraftPacket {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Entry{" +
|
||||
"profileId=" + profileId +
|
||||
", profile=" + profile +
|
||||
", listed=" + listed +
|
||||
", latency=" + latency +
|
||||
", gameMode=" + gameMode +
|
||||
", displayName=" + displayName +
|
||||
", listOrder=" + listOrder +
|
||||
", chatSession=" + chatSession +
|
||||
'}';
|
||||
return "Entry{"
|
||||
+ "profileId=" + profileId
|
||||
+ ", profile=" + profile
|
||||
+ ", listed=" + listed
|
||||
+ ", latency=" + latency
|
||||
+ ", gameMode=" + gameMode
|
||||
+ ", displayName=" + displayName
|
||||
+ ", listOrder=" + listOrder
|
||||
+ ", chatSession=" + chatSession
|
||||
+ '}';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,14 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* Represents an identifier for a Brigadier command argument, mapping the argument to
|
||||
* different protocol versions.
|
||||
*
|
||||
* <p>The {@code ArgumentIdentifier} is responsible for holding an identifier string for
|
||||
* an argument and a map that associates protocol versions with their respective IDs.
|
||||
* It ensures that the protocol version is compatible with the Minecraft 1.19 protocol or later.</p>
|
||||
*/
|
||||
public class ArgumentIdentifier {
|
||||
|
||||
private final String identifier;
|
||||
@@ -37,8 +45,8 @@ public class ArgumentIdentifier {
|
||||
Map<ProtocolVersion, Integer> temp = new HashMap<>();
|
||||
|
||||
ProtocolVersion previous = null;
|
||||
for (int i = 0; i < versions.length; i++) {
|
||||
VersionSet current = Preconditions.checkNotNull(versions[i]);
|
||||
for (VersionSet version : versions) {
|
||||
VersionSet current = Preconditions.checkNotNull(version);
|
||||
|
||||
Preconditions.checkArgument(
|
||||
current.getVersion().noLessThan(ProtocolVersion.MINECRAFT_1_19),
|
||||
@@ -60,9 +68,9 @@ public class ArgumentIdentifier {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ArgumentIdentifier{" +
|
||||
"identifier='" + identifier + '\'' +
|
||||
'}';
|
||||
return "ArgumentIdentifier{"
|
||||
+ "identifier='" + identifier + '\''
|
||||
+ '}';
|
||||
}
|
||||
|
||||
public String getIdentifier() {
|
||||
@@ -101,7 +109,6 @@ public class ArgumentIdentifier {
|
||||
public ProtocolVersion getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -45,11 +45,18 @@ import com.mojang.brigadier.arguments.StringArgumentType;
|
||||
import com.velocitypowered.api.network.ProtocolVersion;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* The {@code ArgumentPropertyRegistry} is responsible for managing the registration and
|
||||
* retrieval of argument properties used in command parsing and execution.
|
||||
*
|
||||
* <p>This class functions as a registry, allowing different argument properties to be registered
|
||||
* and later retrieved or used when processing commands within the system. The properties
|
||||
* might be tied to argument types, validation rules, or transformations.</p>
|
||||
*/
|
||||
public class ArgumentPropertyRegistry {
|
||||
|
||||
private ArgumentPropertyRegistry() {
|
||||
@@ -145,7 +152,6 @@ public class ArgumentPropertyRegistry {
|
||||
} else {
|
||||
ProtocolUtils.writeString(buf, identifier.getIdentifier());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -272,7 +278,7 @@ public class ArgumentPropertyRegistry {
|
||||
empty(id("minecraft:heightmap", mapSet(MINECRAFT_1_21_6, 51), mapSet(MINECRAFT_1_21_5, 50), mapSet(MINECRAFT_1_20_3, 49),
|
||||
mapSet(MINECRAFT_1_19_4, 47))); // 1.19.4
|
||||
|
||||
empty(id("minecraft:uuid", mapSet(MINECRAFT_1_21_6, 56), mapSet(MINECRAFT_1_21_5, 54),mapSet(MINECRAFT_1_20_5, 53), mapSet(MINECRAFT_1_20_3, 48),
|
||||
empty(id("minecraft:uuid", mapSet(MINECRAFT_1_21_6, 56), mapSet(MINECRAFT_1_21_5, 54), mapSet(MINECRAFT_1_20_5, 53), mapSet(MINECRAFT_1_20_3, 48),
|
||||
mapSet(MINECRAFT_1_19_4, 48), mapSet(MINECRAFT_1_19, 47))); // added in 1.16
|
||||
|
||||
empty(id("minecraft:loot_table", mapSet(MINECRAFT_1_21_6, 52), mapSet(MINECRAFT_1_21_5, 51), mapSet(MINECRAFT_1_20_5, 50)));
|
||||
@@ -287,4 +293,4 @@ public class ArgumentPropertyRegistry {
|
||||
|
||||
empty(id("minecraft:nbt")); // No longer in 1.19+
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,16 @@ import com.velocitypowered.api.network.ProtocolVersion;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* The {@code ArgumentPropertySerializer} interface defines a contract for serializing and
|
||||
* deserializing argument properties to and from a specific format.
|
||||
*
|
||||
* <p>This interface allows implementations to convert argument properties into a serialized form,
|
||||
* which can later be deserialized and restored to their original form. This is particularly useful
|
||||
* for persisting command argument configurations or sending them across a network.</p>
|
||||
*
|
||||
* @param <T> the type of the argument property being serialized
|
||||
*/
|
||||
public interface ArgumentPropertySerializer<T> {
|
||||
|
||||
@Nullable T deserialize(ByteBuf buf, ProtocolVersion protocolVersion);
|
||||
|
||||
@@ -28,6 +28,17 @@ import io.netty.buffer.Unpooled;
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/**
|
||||
* Represents a mod-specific argument type with custom binary data attached.
|
||||
*
|
||||
* <p>This class allows external mods or extensions to define their own command argument
|
||||
* types, identified by a namespaced {@link ArgumentIdentifier} and accompanied by
|
||||
* serialized {@link ByteBuf} data.</p>
|
||||
*
|
||||
* <p>Note: This type is not parseable or suggestible through Brigadier and exists primarily
|
||||
* to preserve compatibility with extended command metadata during serialization and
|
||||
* deserialization.</p>
|
||||
*/
|
||||
public class ModArgumentProperty implements ArgumentType<ByteBuf> {
|
||||
|
||||
private final ArgumentIdentifier identifier;
|
||||
|
||||
@@ -21,6 +21,16 @@ import com.velocitypowered.api.network.ProtocolVersion;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
/**
|
||||
* The {@code RegistryIdArgumentSerializer} handles serialization and deserialization
|
||||
* of integer-based registry ID arguments.
|
||||
*
|
||||
* <p>This serializer is used for command arguments that refer to elements in Minecraft
|
||||
* registries (e.g., items, entities, dimensions) by their numerical registry ID.</p>
|
||||
*
|
||||
* <p>Values are encoded as variable-length integers using {@link ProtocolUtils}
|
||||
* for compact transmission.</p>
|
||||
*/
|
||||
public class RegistryIdArgumentSerializer implements ArgumentPropertySerializer<Integer> {
|
||||
|
||||
static final RegistryIdArgumentSerializer REGISTRY_ID = new RegistryIdArgumentSerializer();
|
||||
|
||||
@@ -28,6 +28,15 @@ import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/**
|
||||
* Represents a Brigadier {@link ArgumentType} for registry keys, which are typically
|
||||
* namespaced resource locations (e.g., {@code minecraft:diamond_sword}).
|
||||
*
|
||||
* <p>This argument type reads an unquoted string from input and treats it as a raw registry
|
||||
* key. It does not validate the format or resolve the key against a known registry.</p>
|
||||
*
|
||||
* <p>Examples include simple strings, namespaced keys, or numeric-like identifiers.</p>
|
||||
*/
|
||||
public class RegistryKeyArgument implements ArgumentType<String> {
|
||||
|
||||
private static final List<String> EXAMPLES = Arrays.asList("foo", "foo:bar", "012");
|
||||
|
||||
@@ -21,14 +21,25 @@ import com.velocitypowered.api.network.ProtocolVersion;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
/**
|
||||
* Represents a list of {@link RegistryKeyArgument} objects.
|
||||
*
|
||||
* <p>Used to manage and store multiple registry key arguments.</p>
|
||||
*/
|
||||
public final class RegistryKeyArgumentList {
|
||||
|
||||
/**
|
||||
* Represents a registry key argument that can either be a resource or a tag.
|
||||
*/
|
||||
public static class ResourceOrTag extends RegistryKeyArgument {
|
||||
|
||||
public ResourceOrTag(String identifier) {
|
||||
super(identifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializer for {@link ResourceOrTag}.
|
||||
*/
|
||||
public static class Serializer implements ArgumentPropertySerializer<ResourceOrTag> {
|
||||
|
||||
static final ResourceOrTag.Serializer REGISTRY = new ResourceOrTag.Serializer();
|
||||
@@ -45,12 +56,18 @@ public final class RegistryKeyArgumentList {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a registry key argument specifically for a resource or tag key.
|
||||
*/
|
||||
public static class ResourceOrTagKey extends RegistryKeyArgument {
|
||||
|
||||
public ResourceOrTagKey(String identifier) {
|
||||
super(identifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializer for {@link ResourceOrTagKey}.
|
||||
*/
|
||||
public static class Serializer implements ArgumentPropertySerializer<ResourceOrTagKey> {
|
||||
|
||||
static final ResourceOrTagKey.Serializer REGISTRY = new ResourceOrTagKey.Serializer();
|
||||
@@ -67,12 +84,18 @@ public final class RegistryKeyArgumentList {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a registry key argument for a resource.
|
||||
*/
|
||||
public static class ResourceSelector extends RegistryKeyArgument {
|
||||
|
||||
public ResourceSelector(String identifier) {
|
||||
super(identifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializer for {@link ResourceSelector}.
|
||||
*/
|
||||
public static class Serializer implements ArgumentPropertySerializer<ResourceSelector> {
|
||||
|
||||
static final ResourceSelector.Serializer REGISTRY = new ResourceSelector.Serializer();
|
||||
@@ -89,12 +112,18 @@ public final class RegistryKeyArgumentList {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a registry key argument for a resource key.
|
||||
*/
|
||||
public static class ResourceKey extends RegistryKeyArgument {
|
||||
|
||||
public ResourceKey(String identifier) {
|
||||
super(identifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializer for {@link ResourceKey}.
|
||||
*/
|
||||
public static class Serializer implements ArgumentPropertySerializer<ResourceKey> {
|
||||
|
||||
static final ResourceKey.Serializer REGISTRY = new ResourceKey.Serializer();
|
||||
|
||||
@@ -21,6 +21,12 @@ import com.velocitypowered.api.network.ProtocolVersion;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
/**
|
||||
* Serializer for {@link RegistryKeyArgument} objects.
|
||||
*
|
||||
* <p>This class handles the serialization and deserialization of {@code RegistryKeyArgument}
|
||||
* objects to and from a {@link ByteBuf} using the specified {@link ProtocolVersion}.</p>
|
||||
*/
|
||||
public class RegistryKeyArgumentSerializer implements
|
||||
ArgumentPropertySerializer<RegistryKeyArgument> {
|
||||
|
||||
|
||||
@@ -20,6 +20,12 @@ package com.velocitypowered.proxy.protocol.packet.brigadier;
|
||||
import com.velocitypowered.api.network.ProtocolVersion;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
/**
|
||||
* Serializer for time-based arguments represented as {@link Integer}.
|
||||
*
|
||||
* <p>This class handles the serialization and deserialization of time-related arguments,
|
||||
* converting them to and from an {@link Integer} format.</p>
|
||||
*/
|
||||
public class TimeArgumentSerializer implements ArgumentPropertySerializer<Integer> {
|
||||
|
||||
static final TimeArgumentSerializer TIME = new TimeArgumentSerializer();
|
||||
|
||||
@@ -23,39 +23,44 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
/**
|
||||
* Represents a packet sent to acknowledge the receipt of a chat message.
|
||||
* This packet is used to confirm that a player or client has received and processed
|
||||
* a chat message from the server.
|
||||
*/
|
||||
public class ChatAcknowledgementPacket implements MinecraftPacket {
|
||||
int offset;
|
||||
int offset;
|
||||
|
||||
public ChatAcknowledgementPacket(int offset) {
|
||||
this.offset = offset;
|
||||
}
|
||||
public ChatAcknowledgementPacket(int offset) {
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
public ChatAcknowledgementPacket() {
|
||||
}
|
||||
public ChatAcknowledgementPacket() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
|
||||
offset = ProtocolUtils.readVarInt(buf);
|
||||
}
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
|
||||
offset = ProtocolUtils.readVarInt(buf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
|
||||
ProtocolUtils.writeVarInt(buf, offset);
|
||||
}
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
|
||||
ProtocolUtils.writeVarInt(buf, offset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(MinecraftSessionHandler handler) {
|
||||
return handler.handle(this);
|
||||
}
|
||||
@Override
|
||||
public boolean handle(MinecraftSessionHandler handler) {
|
||||
return handler.handle(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ChatAcknowledgement{" +
|
||||
"offset=" + offset +
|
||||
'}';
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ChatAcknowledgement{"
|
||||
+ "offset=" + offset
|
||||
+ '}';
|
||||
}
|
||||
|
||||
public int offset() {
|
||||
return offset;
|
||||
}
|
||||
public int offset() {
|
||||
return offset;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,12 +19,28 @@ package com.velocitypowered.proxy.protocol.packet.chat;
|
||||
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
|
||||
/**
|
||||
* Represents a handler for processing chat-related packets in the game.
|
||||
* This interface is generic and can handle different types of Minecraft packets that
|
||||
* extend {@link MinecraftPacket}.
|
||||
*
|
||||
* @param <T> the type of packet that this chat handler processes, which must
|
||||
* extend {@link MinecraftPacket}
|
||||
*/
|
||||
public interface ChatHandler<T extends MinecraftPacket> {
|
||||
|
||||
Class<T> packetClass();
|
||||
|
||||
void handlePlayerChatInternal(T packet);
|
||||
|
||||
/**
|
||||
* Handles a player chat event represented by the given {@link MinecraftPacket}.
|
||||
* This default method provides a basic mechanism for processing chat-related packets that
|
||||
* involve player messages.
|
||||
*
|
||||
* @param packet the {@link MinecraftPacket} representing the player chat event to handle
|
||||
* @return {@code true} if the chat event was successfully handled, {@code false} otherwise
|
||||
*/
|
||||
default boolean handlePlayerChat(MinecraftPacket packet) {
|
||||
if (packetClass().isInstance(packet)) {
|
||||
handlePlayerChatInternal(packetClass().cast(packet));
|
||||
|
||||
@@ -21,12 +21,12 @@ import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import java.time.Instant;
|
||||
import java.util.BitSet;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Function;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* A precisely ordered queue which allows for outside entries into the ordered queue through
|
||||
@@ -79,7 +79,8 @@ public class ChatQueue implements AutoCloseable {
|
||||
* @param timestamp the new {@link Instant} timestamp of this packet to update the internal chat state.
|
||||
* @param lastSeenMessages the new {@link LastSeenMessages} last seen messages to update the internal chat state.
|
||||
*/
|
||||
public void queuePacket(Function<LastSeenMessages, CompletableFuture<MinecraftPacket>> nextPacket, @Nullable Instant timestamp, @Nullable LastSeenMessages lastSeenMessages) {
|
||||
public void queuePacket(Function<LastSeenMessages, CompletableFuture<MinecraftPacket>> nextPacket, @Nullable Instant timestamp,
|
||||
@Nullable LastSeenMessages lastSeenMessages) {
|
||||
queueTask((chatState, smc) -> {
|
||||
LastSeenMessages newLastSeenMessages = chatState.updateFromMessage(timestamp, lastSeenMessages);
|
||||
return nextPacket.apply(newLastSeenMessages).thenCompose(packet -> writePacket(packet, smc));
|
||||
@@ -100,6 +101,12 @@ public class ChatQueue implements AutoCloseable {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the acknowledgement of a chat message or event by processing the given offset.
|
||||
* This method is typically called when a chat message or command is acknowledged by the client or server.
|
||||
*
|
||||
* @param offset the offset representing the specific message or event being acknowledged
|
||||
*/
|
||||
public void handleAcknowledgement(int offset) {
|
||||
queueTask((chatState, smc) -> {
|
||||
int ackCountToForward = chatState.accumulateAckCount(offset);
|
||||
@@ -138,16 +145,16 @@ public class ChatQueue implements AutoCloseable {
|
||||
* <li>If we last forwarded a chat or command packet from the client, we have a known 'last seen' that we can
|
||||
* reuse.</li>
|
||||
* <li>If we last forwarded a {@link ChatAcknowledgementPacket}, the previous 'last seen' cannot be reused. We
|
||||
* cannot predict an up-to-date 'last seen', as we do not know which messages the client actually saw.</li>
|
||||
* cannot predict an up to date 'last seen', as we do not know which messages the client actually saw.</li>
|
||||
* <li>Therefore, we need to hold back any acknowledgement packets so that we can continue to reuse the last valid
|
||||
* 'last seen' state.</li>
|
||||
* <li>However, there is a limit to the number of messages that can remain unacknowledged on the server.</li>
|
||||
* <li>To address this, we know that if the client has moved its 'last seen' window far enough, we can fill in the
|
||||
* gap with dummy 'last seen', and it will never be checked.</li>
|
||||
* gap with stub 'last seen', and it will never be checked.</li>
|
||||
* </ul>
|
||||
*
|
||||
* Note that this is effectively unused for 1.20.5+ clients, as commands without any signature do not send 'last seen'
|
||||
* updates.
|
||||
* <p>Note that this is effectively unused for 1.20.5+ clients, as commands without any signature do not send 'last seen'
|
||||
* updates.</p>
|
||||
*/
|
||||
public static class ChatState {
|
||||
private static final int MINIMUM_DELAYED_ACK_COUNT = LastSeenMessages.WINDOW_SIZE;
|
||||
@@ -160,6 +167,17 @@ public class ChatQueue implements AutoCloseable {
|
||||
private ChatState() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the state of the {@link LastSeenMessages} and the timestamp based on a new message or event.
|
||||
* This method processes the given timestamp and last seen messages to ensure the internal state is up to date.
|
||||
* - If the provided {@link Instant} is not null, it updates the last known timestamp.
|
||||
* - If the provided {@link LastSeenMessages} is not null, it flushes any delayed acknowledgements and updates the
|
||||
* internal acknowledged messages, returning an adjusted {@link LastSeenMessages} with the offset applied.
|
||||
*
|
||||
* @param timestamp the optional {@link Instant} representing the new timestamp for the message or event
|
||||
* @param lastSeenMessages the optional {@link LastSeenMessages} representing the last seen messages by the player
|
||||
* @return the updated {@link LastSeenMessages} with the applied offset, or {@code null} if no updates were made
|
||||
*/
|
||||
@Nullable
|
||||
public LastSeenMessages updateFromMessage(@Nullable Instant timestamp, @Nullable LastSeenMessages lastSeenMessages) {
|
||||
if (timestamp != null) {
|
||||
@@ -174,6 +192,16 @@ public class ChatQueue implements AutoCloseable {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Accumulates the given acknowledgement count and determines if enough acknowledgements have been gathered to forward.
|
||||
* - Adds the provided `ackCount` to the current delayed acknowledgement count.
|
||||
* - If the accumulated acknowledgements exceed the {@link LastSeenMessages#WINDOW_SIZE}, the method resets the delayed
|
||||
* acknowledgement count and returns the number of acknowledgements that should be forwarded.
|
||||
* - If the threshold is not met, the method returns 0, indicating that no acknowledgements need to be forwarded yet.
|
||||
*
|
||||
* @param ackCount the number of acknowledgements to add to the accumulated count
|
||||
* @return the number of acknowledgements that should be forwarded, or 0 if the threshold has not been reached
|
||||
*/
|
||||
public int accumulateAckCount(int ackCount) {
|
||||
int delayedAckCount = this.delayedAckCount.addAndGet(ackCount);
|
||||
int ackCountToForward = delayedAckCount - MINIMUM_DELAYED_ACK_COUNT;
|
||||
@@ -186,6 +214,11 @@ public class ChatQueue implements AutoCloseable {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a snapshot of the current {@link LastSeenMessages} state.
|
||||
*
|
||||
* @return a new {@link LastSeenMessages} representing the current view
|
||||
*/
|
||||
public LastSeenMessages createLastSeen() {
|
||||
return new LastSeenMessages(0, lastSeenMessages, (byte) 0);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,11 @@ package com.velocitypowered.proxy.protocol.packet.chat;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
/**
|
||||
* Manages the timing and duration of chat messages within the game.
|
||||
* The {@code ChatTimeKeeper} class tracks when chat messages are sent and provides mechanisms
|
||||
* to determine how long a message has been displayed or to manage message expiration.
|
||||
*/
|
||||
public class ChatTimeKeeper {
|
||||
|
||||
private Instant lastTimestamp;
|
||||
@@ -27,6 +32,18 @@ public class ChatTimeKeeper {
|
||||
this.lastTimestamp = Instant.MIN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the internal timestamp of the chat message or session.
|
||||
* This method checks if the provided {@link Instant} is before the current stored timestamp.
|
||||
* If it is, the internal timestamp is updated, and the method returns {@code false} to
|
||||
* indicate that the update was not successful.
|
||||
* If the provided {@link Instant} is valid, the timestamp is updated,
|
||||
* and the method may return {@code true}.
|
||||
*
|
||||
* @param instant the {@link Instant} representing the new timestamp to update
|
||||
* @return {@code true} if the timestamp was successfully updated, {@code false}
|
||||
* if the provided instant is before the current timestamp
|
||||
*/
|
||||
public boolean update(Instant instant) {
|
||||
if (instant.isBefore(this.lastTimestamp)) {
|
||||
this.lastTimestamp = instant;
|
||||
|
||||
@@ -17,6 +17,12 @@
|
||||
|
||||
package com.velocitypowered.proxy.protocol.packet.chat;
|
||||
|
||||
/**
|
||||
* Represents different types of chat messages in the game, defining how a message should be
|
||||
* handled and displayed.
|
||||
* This enum categorizes various chat message types such as system messages, player chat,
|
||||
* or game info.
|
||||
*/
|
||||
public enum ChatType {
|
||||
CHAT((byte) 0),
|
||||
SYSTEM((byte) 1),
|
||||
@@ -31,4 +37,4 @@ public enum ChatType {
|
||||
public byte getId() {
|
||||
return raw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,14 @@ import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* Represents a handler for processing commands associated with specific types of Minecraft packets.
|
||||
* This interface is generic and allows for handling different packet types that extend
|
||||
* {@link MinecraftPacket}.
|
||||
*
|
||||
* @param <T> the type of packet that this handler is responsible for, which
|
||||
* must extend {@link MinecraftPacket}
|
||||
*/
|
||||
public interface CommandHandler<T extends MinecraftPacket> {
|
||||
|
||||
Logger logger = LogManager.getLogger(CommandHandler.class);
|
||||
@@ -39,6 +47,14 @@ public interface CommandHandler<T extends MinecraftPacket> {
|
||||
|
||||
void handlePlayerCommandInternal(T packet);
|
||||
|
||||
/**
|
||||
* Handles a player command associated with a given {@link MinecraftPacket}.
|
||||
* This default method provides a mechanism for processing commands related to player
|
||||
* actions within the game.
|
||||
*
|
||||
* @param packet the {@link MinecraftPacket} representing the player command to be handled
|
||||
* @return {@code true} if the command was successfully handled, {@code false} otherwise
|
||||
*/
|
||||
default boolean handlePlayerCommand(MinecraftPacket packet) {
|
||||
if (packetClass().isInstance(packet)) {
|
||||
handlePlayerCommandInternal(packetClass().cast(packet));
|
||||
@@ -54,25 +70,45 @@ public interface CommandHandler<T extends MinecraftPacket> {
|
||||
.thenApply(hasRunPacketFunction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Queues the result of a player command for execution, managing the future result of the command.
|
||||
* This method is designed to interact with the {@link VelocityServer}
|
||||
* and {@link ConnectedPlayer} to
|
||||
* handle the process of command execution, queuing, and response handling.
|
||||
*
|
||||
* @param server the {@link VelocityServer} instance responsible for managing the
|
||||
* command execution
|
||||
* @param player the {@link ConnectedPlayer} who initiated the command
|
||||
* @param futurePacketCreator a {@link BiFunction} that creates a future packet based on
|
||||
* the {@link CommandExecuteEvent}
|
||||
* and {@link LastSeenMessages}, which will be used for sending
|
||||
* a command result
|
||||
* @param message the command message that the player sent
|
||||
* @param timestamp the {@link Instant} when the command was executed
|
||||
* @param lastSeenMessages the {@link LastSeenMessages} object containing the messages last
|
||||
* seen by the player,
|
||||
* or {@code null} if not applicable
|
||||
* @param invocationInfo signing metadata for the event dispatch
|
||||
*/
|
||||
default void queueCommandResult(VelocityServer server, ConnectedPlayer player,
|
||||
BiFunction<CommandExecuteEvent, LastSeenMessages, CompletableFuture<MinecraftPacket>> futurePacketCreator,
|
||||
String message, Instant timestamp, @Nullable LastSeenMessages lastSeenMessages,
|
||||
CommandExecuteEvent.InvocationInfo invocationInfo) {
|
||||
CompletableFuture<CommandExecuteEvent> eventFuture = server.getCommandManager().callCommandEvent(player, message,
|
||||
invocationInfo);
|
||||
player.getChatQueue().queuePacket(
|
||||
CompletableFuture<CommandExecuteEvent> eventFuture = server.getCommandManager().callCommandEvent(player, message,
|
||||
invocationInfo);
|
||||
player.getChatQueue().queuePacket(
|
||||
newLastSeenMessages -> eventFuture
|
||||
.thenComposeAsync(event -> futurePacketCreator.apply(event, newLastSeenMessages))
|
||||
.thenApply(pkt -> {
|
||||
if (server.getConfiguration().isLogCommandExecutions()) {
|
||||
logger.info("{} -> executed command /{}", player, message);
|
||||
}
|
||||
return pkt;
|
||||
}).exceptionally(e -> {
|
||||
logger.info("Exception occurred while running command for {}", player.getUsername(), e);
|
||||
player.sendMessage(
|
||||
Component.translatable("velocity.command.generic-error", NamedTextColor.RED));
|
||||
return null;
|
||||
}), timestamp, lastSeenMessages);
|
||||
.thenComposeAsync(event -> futurePacketCreator.apply(event, newLastSeenMessages))
|
||||
.thenApply(pkt -> {
|
||||
if (server.getConfiguration().isLogCommandExecutions()) {
|
||||
logger.info("{} -> executed command /{}", player, message);
|
||||
}
|
||||
return pkt;
|
||||
}).exceptionally(e -> {
|
||||
logger.info("Exception occurred while running command for {}", player.getUsername(), e);
|
||||
player.sendMessage(
|
||||
Component.translatable("velocity.command.generic-error", NamedTextColor.RED));
|
||||
return null;
|
||||
}), timestamp, lastSeenMessages);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,9 @@ import com.google.gson.internal.LazilyParsedNumber;
|
||||
import com.velocitypowered.api.network.ProtocolVersion;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import net.kyori.adventure.nbt.BinaryTag;
|
||||
import net.kyori.adventure.nbt.BinaryTagIO;
|
||||
import net.kyori.adventure.nbt.BinaryTagType;
|
||||
@@ -47,10 +50,12 @@ import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Represents a holder for components used in chat or other text-based data in the Minecraft
|
||||
* protocol.
|
||||
* This class supports various formats including JSON and NBT (Named Binary Tag) for storing and
|
||||
* transmitting text components.
|
||||
*/
|
||||
public class ComponentHolder {
|
||||
private static final Logger logger = LogManager.getLogger(ComponentHolder.class);
|
||||
public static final int DEFAULT_MAX_STRING_SIZE = 262143;
|
||||
@@ -75,6 +80,14 @@ public class ComponentHolder {
|
||||
this.binaryTag = binaryTag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the {@link Component} stored in this {@link ComponentHolder}.
|
||||
* If the component is not yet initialized, it will attempt to deserialize it from either
|
||||
* the JSON or NBT representation, depending on which is available.
|
||||
*
|
||||
* @return the {@link Component} stored in this holder
|
||||
* @throws IllegalStateException if both the JSON and binary representations fail to deserialize
|
||||
*/
|
||||
public Component getComponent() {
|
||||
if (component == null) {
|
||||
if (json != null) {
|
||||
@@ -95,6 +108,13 @@ public class ComponentHolder {
|
||||
return component;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the JSON representation of the {@link Component} stored in this
|
||||
* {@link ComponentHolder}.
|
||||
* If the JSON string is not yet initialized, it will serialize the component into a JSON string.
|
||||
*
|
||||
* @return the JSON string representing the {@link Component}
|
||||
*/
|
||||
public String getJson() {
|
||||
if (json == null) {
|
||||
json = ProtocolUtils.getJsonChatSerializer(version).serialize(getComponent());
|
||||
@@ -102,6 +122,14 @@ public class ComponentHolder {
|
||||
return json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the NBT (Named Binary Tag) representation of the {@link Component} stored in this
|
||||
* {@link ComponentHolder}.
|
||||
* If the NBT tag is not yet initialized, it will serialize the component into an NBT
|
||||
* representation.
|
||||
*
|
||||
* @return the {@link BinaryTag} representing the {@link Component}
|
||||
*/
|
||||
public BinaryTag getBinaryTag() {
|
||||
if (binaryTag == null) {
|
||||
// TODO: replace this with adventure-text-serializer-nbt
|
||||
@@ -110,6 +138,16 @@ public class ComponentHolder {
|
||||
return binaryTag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes a {@link JsonElement} into a {@link BinaryTag} format.
|
||||
* This method converts JSON primitives (numbers, strings, booleans) and complex structures
|
||||
* (arrays, objects)
|
||||
* into their corresponding NBT representations.
|
||||
*
|
||||
* @param json the {@link JsonElement} to be serialized into a {@link BinaryTag}
|
||||
* @return the {@link BinaryTag} representing the serialized JSON element
|
||||
* @throws IllegalArgumentException if the JSON element is of an unsupported or unknown type
|
||||
*/
|
||||
public static BinaryTag serialize(JsonElement json) {
|
||||
if (json instanceof JsonPrimitive jsonPrimitive) {
|
||||
if (jsonPrimitive.isNumber()) {
|
||||
@@ -162,36 +200,40 @@ public class ComponentHolder {
|
||||
}
|
||||
|
||||
switch (listType.id()) {
|
||||
case 1://BinaryTagTypes.BYTE:
|
||||
case 1 -> { // BinaryTagTypes.BYTE:
|
||||
byte[] bytes = new byte[jsonArray.size()];
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
bytes[i] = jsonArray.get(i).getAsNumber().byteValue();
|
||||
}
|
||||
|
||||
return ByteArrayBinaryTag.byteArrayBinaryTag(bytes);
|
||||
case 3://BinaryTagTypes.INT:
|
||||
}
|
||||
case 3 -> { // BinaryTagTypes.INT:
|
||||
int[] ints = new int[jsonArray.size()];
|
||||
for (int i = 0; i < ints.length; i++) {
|
||||
ints[i] = jsonArray.get(i).getAsNumber().intValue();
|
||||
}
|
||||
|
||||
return IntArrayBinaryTag.intArrayBinaryTag(ints);
|
||||
case 4://BinaryTagTypes.LONG:
|
||||
}
|
||||
case 4 -> { // BinaryTagTypes.LONG:
|
||||
long[] longs = new long[jsonArray.size()];
|
||||
for (int i = 0; i < longs.length; i++) {
|
||||
longs[i] = jsonArray.get(i).getAsNumber().longValue();
|
||||
}
|
||||
|
||||
return LongArrayBinaryTag.longArrayBinaryTag(longs);
|
||||
case 10://BinaryTagTypes.COMPOUND:
|
||||
tagItems.replaceAll(tag -> {
|
||||
if (tag.type() == BinaryTagTypes.COMPOUND) {
|
||||
return tag;
|
||||
} else {
|
||||
return CompoundBinaryTag.builder().put("", tag).build();
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 10 -> // BinaryTagTypes.COMPOUND:
|
||||
tagItems.replaceAll(tag -> {
|
||||
if (tag.type() == BinaryTagTypes.COMPOUND) {
|
||||
return tag;
|
||||
} else {
|
||||
return CompoundBinaryTag.builder().put("", tag).build();
|
||||
}
|
||||
});
|
||||
default -> {
|
||||
}
|
||||
}
|
||||
|
||||
return ListBinaryTag.listBinaryTag(listType, tagItems);
|
||||
@@ -200,21 +242,30 @@ public class ComponentHolder {
|
||||
return EndBinaryTag.endBinaryTag();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes a {@link BinaryTag} into a {@link JsonElement}.
|
||||
* This method converts NBT (Named Binary Tag) data into its corresponding JSON representation,
|
||||
* including handling of primitive types, arrays, and compound structures.
|
||||
*
|
||||
* @param tag the {@link BinaryTag} to be deserialized into a {@link JsonElement}
|
||||
* @return the {@link JsonElement} representing the deserialized NBT data
|
||||
* @throws IllegalArgumentException if the NBT tag type is unsupported or unknown
|
||||
*/
|
||||
public static JsonElement deserialize(BinaryTag tag) {
|
||||
return switch (tag.type().id()) {
|
||||
//BinaryTagTypes.BYTE
|
||||
// BinaryTagTypes.BYTE
|
||||
case 1 -> new JsonPrimitive(((ByteBinaryTag) tag).value());
|
||||
//BinaryTagTypes.SHORT
|
||||
// BinaryTagTypes.SHORT
|
||||
case 2 -> new JsonPrimitive(((ShortBinaryTag) tag).value());
|
||||
//BinaryTagTypes.INT:
|
||||
// BinaryTagTypes.INT:
|
||||
case 3 -> new JsonPrimitive(((IntBinaryTag) tag).value());
|
||||
//BinaryTagTypes.LONG:
|
||||
// BinaryTagTypes.LONG:
|
||||
case 4 -> new JsonPrimitive(((LongBinaryTag) tag).value());
|
||||
//BinaryTagTypes.FLOAT:
|
||||
// BinaryTagTypes.FLOAT:
|
||||
case 5 -> new JsonPrimitive(((FloatBinaryTag) tag).value());
|
||||
//BinaryTagTypes.DOUBLE:
|
||||
// BinaryTagTypes.DOUBLE:
|
||||
case 6 -> new JsonPrimitive(((DoubleBinaryTag) tag).value());
|
||||
//BinaryTagTypes.BYTE_ARRAY:
|
||||
// BinaryTagTypes.BYTE_ARRAY:
|
||||
case 7 -> {
|
||||
byte[] byteArray = ((ByteArrayBinaryTag) tag).value();
|
||||
|
||||
@@ -225,9 +276,9 @@ public class ComponentHolder {
|
||||
|
||||
yield jsonByteArray;
|
||||
}
|
||||
//BinaryTagTypes.STRING:
|
||||
// BinaryTagTypes.STRING:
|
||||
case 8 -> new JsonPrimitive(((StringBinaryTag) tag).value());
|
||||
//BinaryTagTypes.LIST:
|
||||
// BinaryTagTypes.LIST:
|
||||
case 9 -> {
|
||||
ListBinaryTag items = (ListBinaryTag) tag;
|
||||
JsonArray jsonList = new JsonArray(items.size());
|
||||
@@ -238,7 +289,7 @@ public class ComponentHolder {
|
||||
|
||||
yield jsonList;
|
||||
}
|
||||
//BinaryTagTypes.COMPOUND:
|
||||
// BinaryTagTypes.COMPOUND:
|
||||
case 10 -> {
|
||||
CompoundBinaryTag compound = (CompoundBinaryTag) tag;
|
||||
JsonObject jsonObject = new JsonObject();
|
||||
@@ -254,7 +305,7 @@ public class ComponentHolder {
|
||||
|
||||
yield jsonObject;
|
||||
}
|
||||
//BinaryTagTypes.INT_ARRAY:
|
||||
// BinaryTagTypes.INT_ARRAY:
|
||||
case 11 -> {
|
||||
int[] intArray = ((IntArrayBinaryTag) tag).value();
|
||||
|
||||
@@ -265,7 +316,7 @@ public class ComponentHolder {
|
||||
|
||||
yield jsonIntArray;
|
||||
}
|
||||
//BinaryTagTypes.LONG_ARRAY:
|
||||
// BinaryTagTypes.LONG_ARRAY:
|
||||
case 12 -> {
|
||||
long[] longArray = ((LongArrayBinaryTag) tag).value();
|
||||
|
||||
@@ -280,6 +331,19 @@ public class ComponentHolder {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a {@link ComponentHolder} from the provided {@link ByteBuf} using the specified
|
||||
* {@link ProtocolVersion}.
|
||||
* This method deserializes a component from either its binary (NBT) or JSON representation,
|
||||
* depending on the protocol version.
|
||||
* - For Minecraft versions 1.20.3 and later, it reads a binary tag.
|
||||
* - For Minecraft versions 1.13 and later, it reads a JSON string with a size limit.
|
||||
* - For earlier versions, it reads a standard JSON string.
|
||||
*
|
||||
* @param buf the {@link ByteBuf} containing the serialized component data
|
||||
* @param version the {@link ProtocolVersion} indicating how the component should be deserialized
|
||||
* @return a {@link ComponentHolder} containing the deserialized component
|
||||
*/
|
||||
public static ComponentHolder read(ByteBuf buf, ProtocolVersion version) {
|
||||
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_3)) {
|
||||
return new ComponentHolder(version,
|
||||
@@ -291,6 +355,15 @@ public class ComponentHolder {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the {@link ComponentHolder}'s data to the provided {@link ByteBuf}.
|
||||
* This method serializes the component into either its binary (NBT) or JSON representation
|
||||
* based on the protocol version.
|
||||
* - For Minecraft versions 1.20.3 and later, it writes the component as a binary tag (NBT).
|
||||
* - For earlier versions, it writes the component as a JSON string.
|
||||
*
|
||||
* @param buf the {@link ByteBuf} where the component data will be written
|
||||
*/
|
||||
public void write(ByteBuf buf) {
|
||||
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_3)) {
|
||||
ProtocolUtils.writeBinaryTag(buf, version, getBinaryTag());
|
||||
@@ -298,4 +371,4 @@ public class ComponentHolder {
|
||||
ProtocolUtils.writeString(buf, getJson());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,10 @@ import io.netty.buffer.ByteBuf;
|
||||
import java.util.Arrays;
|
||||
import java.util.BitSet;
|
||||
|
||||
/**
|
||||
* Represents a collection of the last seen messages by a player or client.
|
||||
* This class tracks the recent chat messages that the player has viewed.
|
||||
*/
|
||||
public class LastSeenMessages {
|
||||
|
||||
public static final int WINDOW_SIZE = 20;
|
||||
@@ -35,12 +39,26 @@ public class LastSeenMessages {
|
||||
this(0, new BitSet(), (byte) 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link LastSeenMessages} instance with the specified offset, acknowledged messages, and checksum.
|
||||
*
|
||||
* @param offset the starting index of the message window
|
||||
* @param acknowledged a BitSet representing which messages have been acknowledged
|
||||
* @param checksum the checksum for the message window data
|
||||
*/
|
||||
public LastSeenMessages(int offset, BitSet acknowledged, byte checksum) {
|
||||
this.offset = offset;
|
||||
this.acknowledged = acknowledged;
|
||||
this.checksum = checksum;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@link LastSeenMessages} instance by decoding data from the provided
|
||||
* {@link ByteBuf}.
|
||||
*
|
||||
* @param buf the buffer containing the serialized last seen messages data
|
||||
* @param protocolVersion the protocol version (determines if checksum is written)
|
||||
*/
|
||||
public LastSeenMessages(ByteBuf buf, ProtocolVersion protocolVersion) {
|
||||
this.offset = ProtocolUtils.readVarInt(buf);
|
||||
|
||||
@@ -53,6 +71,12 @@ public class LastSeenMessages {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes this {@link LastSeenMessages} instance into the provided {@link ByteBuf}.
|
||||
*
|
||||
* @param buf the buffer to write the data to
|
||||
* @param protocolVersion the protocol version used for encoding
|
||||
*/
|
||||
public void encode(ByteBuf buf, ProtocolVersion protocolVersion) {
|
||||
ProtocolUtils.writeVarInt(buf, offset);
|
||||
buf.writeBytes(Arrays.copyOf(acknowledged.toByteArray(), DIV_FLOOR));
|
||||
@@ -75,10 +99,10 @@ public class LastSeenMessages {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "LastSeenMessages{" +
|
||||
"offset=" + offset +
|
||||
", acknowledged=" + acknowledged +
|
||||
", checksum=" + checksum +
|
||||
'}';
|
||||
return "LastSeenMessages{"
|
||||
+ "offset=" + offset
|
||||
+ ", acknowledged=" + acknowledged
|
||||
+ ", checksum=" + checksum
|
||||
+ '}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,11 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
/**
|
||||
* Represents a packet sent between the server and client to handle chat completion suggestions.
|
||||
* This packet allows the server to send chat message completions or suggestions to the client,
|
||||
* helping users complete commands or chat messages.
|
||||
*/
|
||||
public class PlayerChatCompletionPacket implements MinecraftPacket {
|
||||
|
||||
private String[] completions;
|
||||
@@ -71,6 +76,9 @@ public class PlayerChatCompletionPacket implements MinecraftPacket {
|
||||
return handler.handle(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the different actions that can be taken with chat completions.
|
||||
*/
|
||||
public enum Action {
|
||||
ADD,
|
||||
REMOVE,
|
||||
|
||||
@@ -22,37 +22,46 @@ import com.velocitypowered.proxy.VelocityServer;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import net.kyori.adventure.text.Component;
|
||||
|
||||
/**
|
||||
* Abstract base class for handling rate-limited player command packets.
|
||||
*
|
||||
* <p>Subclasses should implement {@link #handlePlayerCommandInternal(MinecraftPacket)} to define
|
||||
* how individual command packets are processed. Rate limiting is enforced to prevent abuse.</p>
|
||||
*
|
||||
* @param <T> the type of {@link MinecraftPacket} this handler processes
|
||||
*/
|
||||
public abstract class RateLimitedCommandHandler<T extends MinecraftPacket> implements CommandHandler<T> {
|
||||
|
||||
private final Player player;
|
||||
private final VelocityServer velocityServer;
|
||||
private final Player player;
|
||||
private final VelocityServer velocityServer;
|
||||
|
||||
private int failedAttempts;
|
||||
private int failedAttempts;
|
||||
|
||||
protected RateLimitedCommandHandler(Player player, VelocityServer velocityServer) {
|
||||
this.player = player;
|
||||
this.velocityServer = velocityServer;
|
||||
}
|
||||
protected RateLimitedCommandHandler(Player player, VelocityServer velocityServer) {
|
||||
this.player = player;
|
||||
this.velocityServer = velocityServer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handlePlayerCommand(MinecraftPacket packet) {
|
||||
if (packetClass().isInstance(packet)) {
|
||||
if (!velocityServer.getCommandRateLimiter().attempt(player.getUniqueId())) {
|
||||
if (velocityServer.getConfiguration().isKickOnCommandRateLimit() && failedAttempts++ >= velocityServer.getConfiguration().getKickAfterRateLimitedCommands()) {
|
||||
player.disconnect(Component.translatable("velocity.kick.command-rate-limit"));
|
||||
}
|
||||
|
||||
if (velocityServer.getConfiguration().isForwardCommandsIfRateLimited()) {
|
||||
return false; // Send the packet to the server
|
||||
}
|
||||
} else {
|
||||
failedAttempts = 0;
|
||||
}
|
||||
|
||||
handlePlayerCommandInternal(packetClass().cast(packet));
|
||||
return true;
|
||||
@Override
|
||||
public boolean handlePlayerCommand(MinecraftPacket packet) {
|
||||
if (packetClass().isInstance(packet)) {
|
||||
if (!velocityServer.getCommandRateLimiter().attempt(player.getUniqueId())) {
|
||||
if (velocityServer.getConfiguration().isKickOnCommandRateLimit()
|
||||
&& failedAttempts++ >= velocityServer.getConfiguration().getKickAfterRateLimitedCommands()) {
|
||||
player.disconnect(Component.translatable("velocity.kick.command-rate-limit"));
|
||||
}
|
||||
|
||||
return false;
|
||||
if (velocityServer.getConfiguration().isForwardCommandsIfRateLimited()) {
|
||||
return false; // Send the packet to the server
|
||||
}
|
||||
} else {
|
||||
failedAttempts = 0;
|
||||
}
|
||||
|
||||
handlePlayerCommandInternal(packetClass().cast(packet));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,11 @@ import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* Represents a remote chat session that implements the {@link ChatSession} interface.
|
||||
* This session is used for handling chat interactions that occur remotely, typically between
|
||||
* a client and server, allowing for communication and session tracking.
|
||||
*/
|
||||
public class RemoteChatSession implements ChatSession {
|
||||
|
||||
private final @Nullable UUID sessionId;
|
||||
|
||||
@@ -23,6 +23,11 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
/**
|
||||
* Represents a packet sent from the server to the client to display system chat messages.
|
||||
* This packet handles the communication of messages that are not player-generated, but instead
|
||||
* come from the system or server itself.
|
||||
*/
|
||||
public class SystemChatPacket implements MinecraftPacket {
|
||||
|
||||
public SystemChatPacket() {
|
||||
@@ -47,7 +52,7 @@ public class SystemChatPacket implements MinecraftPacket {
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
|
||||
component = ComponentHolder.read(buf, version);
|
||||
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19_1)){
|
||||
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19_1)) {
|
||||
type = buf.readBoolean() ? ChatType.GAME_INFO : ChatType.SYSTEM;
|
||||
} else {
|
||||
type = ChatType.values()[ProtocolUtils.readVarInt(buf)];
|
||||
|
||||
@@ -23,11 +23,22 @@ import com.velocitypowered.proxy.protocol.packet.chat.legacy.LegacyChatBuilder;
|
||||
import com.velocitypowered.proxy.protocol.packet.chat.session.SessionChatBuilder;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Factory class for creating instances of chat builders.
|
||||
*
|
||||
* <p>The {@code ChatBuilderFactory} is responsible for providing various builder instances
|
||||
* used to construct chat-related components, such as messages, chat formats, or text components.</p>
|
||||
*/
|
||||
public class ChatBuilderFactory {
|
||||
|
||||
private final ProtocolVersion version;
|
||||
private final Function<ProtocolVersion, ChatBuilderV2> builderFunction;
|
||||
|
||||
/**
|
||||
* Creates a new {@code ChatBuilderFactory} for the specified protocol version.
|
||||
*
|
||||
* @param version the protocol version to be used by the chat builder factory
|
||||
*/
|
||||
public ChatBuilderFactory(ProtocolVersion version) {
|
||||
this.version = version;
|
||||
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19_3)) {
|
||||
|
||||
@@ -28,6 +28,13 @@ import net.kyori.adventure.text.Component;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* An abstract class for building chat components in version 2 of the chat system.
|
||||
*
|
||||
* <p>The {@code ChatBuilderV2} class provides the foundation for creating and formatting
|
||||
* chat components, allowing subclasses to implement specific behaviors for constructing
|
||||
* chat messages or text components.</p>
|
||||
*/
|
||||
public abstract class ChatBuilderV2 {
|
||||
|
||||
protected final ProtocolVersion version;
|
||||
|
||||
@@ -26,6 +26,12 @@ import com.velocitypowered.proxy.protocol.packet.chat.SystemChatPacket;
|
||||
import com.velocitypowered.proxy.protocol.packet.chat.builder.ChatBuilderV2;
|
||||
import net.kyori.adventure.text.Component;
|
||||
|
||||
/**
|
||||
* A concrete implementation of {@link ChatBuilderV2} that uses keys to build chat components.
|
||||
*
|
||||
* <p>The {@code KeyedChatBuilder} class extends the functionality of {@link ChatBuilderV2} by allowing
|
||||
* chat components to be built using specific keys, enabling dynamic message construction.</p>
|
||||
*/
|
||||
public class KeyedChatBuilder extends ChatBuilderV2 {
|
||||
|
||||
public KeyedChatBuilder(ProtocolVersion version) {
|
||||
|
||||
@@ -30,6 +30,13 @@ import net.kyori.adventure.text.Component;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
/**
|
||||
* A handler for processing chat components based on specific keys.
|
||||
*
|
||||
* <p>The {@code KeyedChatHandler} class is responsible for managing chat interactions or
|
||||
* messages that keys identify. It implements the required interface or class
|
||||
* to handle key-based chat processing.</p>
|
||||
*/
|
||||
public class KeyedChatHandler implements
|
||||
com.velocitypowered.proxy.protocol.packet.chat.ChatHandler<KeyedPlayerChatPacket> {
|
||||
|
||||
@@ -48,6 +55,15 @@ public class KeyedChatHandler implements
|
||||
return KeyedPlayerChatPacket.class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs an error and disconnects the player when a plugin attempts to cancel a signed chat message.
|
||||
*
|
||||
* <p>This method handles the invalid behavior of canceling signed chat messages, which is no longer allowed
|
||||
* starting from Minecraft version 1.19.1.</p>
|
||||
*
|
||||
* @param logger the logger used to log the error
|
||||
* @param player the player to disconnect due to the illegal action
|
||||
*/
|
||||
public static void invalidCancel(Logger logger, ConnectedPlayer player) {
|
||||
logger.fatal("A plugin tried to cancel a signed chat message."
|
||||
+ " This is no longer possible in 1.19.1 and newer. "
|
||||
@@ -56,6 +72,15 @@ public class KeyedChatHandler implements
|
||||
+ "Contact your network administrator."));
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs an error and disconnects the player when a plugin attempts to modify a signed chat message.
|
||||
*
|
||||
* <p>This method handles the invalid behavior of modifying signed chat messages, which is no longer allowed
|
||||
* starting from Minecraft version 1.19.1.</p>
|
||||
*
|
||||
* @param logger the logger used to log the error
|
||||
* @param player the player to disconnect due to the illegal action
|
||||
*/
|
||||
public static void invalidChange(Logger logger, ConnectedPlayer player) {
|
||||
logger.fatal("A plugin tried to change a signed chat message. "
|
||||
+ "This is no longer possible in 1.19.1 and newer. "
|
||||
|
||||
@@ -26,11 +26,24 @@ import com.velocitypowered.proxy.protocol.packet.chat.builder.ChatBuilderV2;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import net.kyori.adventure.text.Component;
|
||||
|
||||
/**
|
||||
* Handles keyed player commands by implementing {@link RateLimitedCommandHandler}.
|
||||
*
|
||||
* <p>The {@code KeyedCommandHandler} processes commands that are sent using
|
||||
* {@link KeyedPlayerCommandPacket}. It provides the necessary logic for handling
|
||||
* and executing commands associated with specific keys.</p>
|
||||
*/
|
||||
public class KeyedCommandHandler extends RateLimitedCommandHandler<KeyedPlayerCommandPacket> {
|
||||
|
||||
private final ConnectedPlayer player;
|
||||
private final VelocityServer server;
|
||||
|
||||
/**
|
||||
* Constructs a new {@code KeyedCommandHandler}.
|
||||
*
|
||||
* @param player the player sending the command
|
||||
* @param server the proxy server instance
|
||||
*/
|
||||
public KeyedCommandHandler(ConnectedPlayer player, VelocityServer server) {
|
||||
super(player, server);
|
||||
this.player = player;
|
||||
@@ -112,6 +125,7 @@ public class KeyedCommandHandler extends RateLimitedCommandHandler<KeyedPlayerCo
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}, packet.getCommand(), packet.getTimestamp(), null, new CommandExecuteEvent.InvocationInfo(CommandExecuteEvent.SignedState.UNSUPPORTED, CommandExecuteEvent.Source.PLAYER));
|
||||
}, packet.getCommand(), packet.getTimestamp(), null, new CommandExecuteEvent.InvocationInfo(CommandExecuteEvent.SignedState.UNSUPPORTED,
|
||||
CommandExecuteEvent.Source.PLAYER));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,13 @@ import io.netty.buffer.ByteBuf;
|
||||
import java.time.Instant;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* Represents a player chat packet with support for message signing and preview.
|
||||
*
|
||||
* <p>The {@code KeyedPlayerChatPacket} handles player chat messages, supporting signed previews,
|
||||
* message signatures, and previous message validation. It includes fields for tracking message
|
||||
* signatures and handling expired messages.</p>
|
||||
*/
|
||||
public class KeyedPlayerChatPacket implements MinecraftPacket {
|
||||
|
||||
private String message;
|
||||
|
||||
@@ -35,6 +35,13 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* Represents a player command packet with support for keyed commands.
|
||||
*
|
||||
* <p>The {@code KeyedPlayerCommandPacket} handles player commands sent to the server,
|
||||
* allowing for command execution based on specific keys. This packet can include additional
|
||||
* information such as arguments and key-based identifiers for the command.</p>
|
||||
*/
|
||||
public class KeyedPlayerCommandPacket implements MinecraftPacket {
|
||||
|
||||
private static final int MAX_NUM_ARGUMENTS = 8;
|
||||
|
||||
@@ -25,6 +25,13 @@ import java.util.UUID;
|
||||
import net.kyori.adventure.identity.Identity;
|
||||
import net.kyori.adventure.text.Component;
|
||||
|
||||
/**
|
||||
* A concrete implementation of {@link ChatBuilderV2} for handling legacy chat formats.
|
||||
*
|
||||
* <p>The {@code LegacyChatBuilder} is designed to support and build chat components
|
||||
* using legacy chat formatting, such as the formats used in earlier versions of Minecraft.
|
||||
* It extends the functionality of {@link ChatBuilderV2} to cater to older chat systems.</p>
|
||||
*/
|
||||
public class LegacyChatBuilder extends ChatBuilderV2 {
|
||||
|
||||
public LegacyChatBuilder(ProtocolVersion version) {
|
||||
|
||||
@@ -23,6 +23,13 @@ import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
|
||||
import com.velocitypowered.proxy.protocol.packet.chat.ChatHandler;
|
||||
|
||||
/**
|
||||
* A handler for processing legacy chat packets, implementing {@link ChatHandler}.
|
||||
*
|
||||
* <p>The {@code LegacyChatHandler} is responsible for handling and processing chat messages
|
||||
* sent using {@link LegacyChatPacket}. This class provides the necessary logic for
|
||||
* processing chat data using legacy Minecraft chat formats.</p>
|
||||
*/
|
||||
public class LegacyChatHandler implements ChatHandler<LegacyChatPacket> {
|
||||
|
||||
private final VelocityServer server;
|
||||
|
||||
@@ -25,6 +25,13 @@ import io.netty.buffer.ByteBuf;
|
||||
import java.util.UUID;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* Represents a legacy chat packet used in older versions of Minecraft.
|
||||
*
|
||||
* <p>The {@code LegacyChatPacket} is responsible for holding and transmitting chat messages
|
||||
* in the format used by legacy versions of Minecraft. It implements {@link MinecraftPacket}
|
||||
* to ensure compatibility with the packet-handling system.</p>
|
||||
*/
|
||||
public class LegacyChatPacket implements MinecraftPacket {
|
||||
|
||||
public static final byte CHAT_TYPE = (byte) 0;
|
||||
@@ -45,6 +52,7 @@ public class LegacyChatPacket implements MinecraftPacket {
|
||||
try {
|
||||
return Integer.parseInt(value.trim());
|
||||
} catch (final NumberFormatException e) {
|
||||
// Exception has been handled
|
||||
}
|
||||
}
|
||||
return 100;
|
||||
|
||||
@@ -21,15 +21,27 @@ import com.velocitypowered.api.event.command.CommandExecuteEvent;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
|
||||
import com.velocitypowered.proxy.protocol.packet.chat.RateLimitedCommandHandler;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/**
|
||||
* A handler for processing legacy commands, implementing {@link RateLimitedCommandHandler}.
|
||||
*
|
||||
* <p>The {@code LegacyCommandHandler} processes and handles command packets that are sent
|
||||
* using {@link LegacyChatPacket}. It provides the necessary logic to support legacy
|
||||
* command formats and ensure compatibility with older Minecraft versions.</p>
|
||||
*/
|
||||
public class LegacyCommandHandler extends RateLimitedCommandHandler<LegacyChatPacket> {
|
||||
|
||||
private final ConnectedPlayer player;
|
||||
private final VelocityServer server;
|
||||
|
||||
/**
|
||||
* Constructs a new {@code LegacyCommandHandler} for handling legacy command packets.
|
||||
*
|
||||
* @param player the connected player issuing the command
|
||||
* @param server the Velocity server instance
|
||||
*/
|
||||
public LegacyCommandHandler(ConnectedPlayer player, VelocityServer server) {
|
||||
super(player, server);
|
||||
this.player = player;
|
||||
@@ -64,6 +76,7 @@ public class LegacyCommandHandler extends RateLimitedCommandHandler<LegacyChatPa
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}, command, Instant.now(), null, new CommandExecuteEvent.InvocationInfo(CommandExecuteEvent.SignedState.UNSUPPORTED, CommandExecuteEvent.Source.PLAYER));
|
||||
}, command, Instant.now(), null, new CommandExecuteEvent.InvocationInfo(CommandExecuteEvent.SignedState.UNSUPPORTED,
|
||||
CommandExecuteEvent.Source.PLAYER));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,13 @@ import com.velocitypowered.proxy.protocol.packet.chat.SystemChatPacket;
|
||||
import com.velocitypowered.proxy.protocol.packet.chat.builder.ChatBuilderV2;
|
||||
import net.kyori.adventure.text.Component;
|
||||
|
||||
/**
|
||||
* A concrete implementation of {@link ChatBuilderV2} for handling session-based chat messages.
|
||||
*
|
||||
* <p>The {@code SessionChatBuilder} is designed to build chat components that are specific to
|
||||
* a player's session, allowing customization and context-specific formatting of chat messages
|
||||
* within the current session.</p>
|
||||
*/
|
||||
public class SessionChatBuilder extends ChatBuilderV2 {
|
||||
|
||||
public SessionChatBuilder(ProtocolVersion version) {
|
||||
|
||||
@@ -26,11 +26,17 @@ import com.velocitypowered.proxy.VelocityServer;
|
||||
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
|
||||
import com.velocitypowered.proxy.protocol.packet.chat.ChatHandler;
|
||||
import com.velocitypowered.proxy.protocol.packet.chat.ChatQueue;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/**
|
||||
* A handler for processing session-based chat packets, implementing {@link ChatHandler}.
|
||||
*
|
||||
* <p>The {@code SessionChatHandler} processes and handles chat messages sent during a player's
|
||||
* session using {@link SessionPlayerChatPacket}. It provides the logic for handling session-specific
|
||||
* chat messages, ensuring the correct context and formatting within the session.</p>
|
||||
*/
|
||||
public class SessionChatHandler implements ChatHandler<SessionPlayerChatPacket> {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(SessionChatHandler.class);
|
||||
|
||||
@@ -22,17 +22,29 @@ import com.velocitypowered.proxy.VelocityServer;
|
||||
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.packet.chat.ChatAcknowledgementPacket;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import com.velocitypowered.proxy.protocol.packet.chat.RateLimitedCommandHandler;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* A handler for processing session-based commands, implementing {@link RateLimitedCommandHandler}.
|
||||
*
|
||||
* <p>The {@code SessionCommandHandler} is responsible for handling commands that are specific
|
||||
* to a player's session, using {@link SessionPlayerCommandPacket}. It provides logic to
|
||||
* process commands that are tied to the context of the current session.</p>
|
||||
*/
|
||||
public class SessionCommandHandler extends RateLimitedCommandHandler<SessionPlayerCommandPacket> {
|
||||
|
||||
private final ConnectedPlayer player;
|
||||
private final VelocityServer server;
|
||||
|
||||
/**
|
||||
* Constructs a new {@link SessionCommandHandler} for the specified player and server.
|
||||
*
|
||||
* @param player the connected player associated with this handler
|
||||
* @param server the Velocity server instance
|
||||
*/
|
||||
public SessionCommandHandler(ConnectedPlayer player, VelocityServer server) {
|
||||
super(player, server);
|
||||
this.player = player;
|
||||
|
||||
@@ -25,6 +25,13 @@ import com.velocitypowered.proxy.protocol.packet.chat.LastSeenMessages;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import java.time.Instant;
|
||||
|
||||
/**
|
||||
* Represents a player chat packet specific to a session, implementing {@link MinecraftPacket}.
|
||||
*
|
||||
* <p>The {@code SessionPlayerChatPacket} handles chat messages sent by a player during a session,
|
||||
* and may include session-specific context, such as timestamps, message formatting, or other
|
||||
* relevant session data.</p>
|
||||
*/
|
||||
public class SessionPlayerChatPacket implements MinecraftPacket {
|
||||
|
||||
protected String message;
|
||||
@@ -100,6 +107,15 @@ public class SessionPlayerChatPacket implements MinecraftPacket {
|
||||
return signature;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@code SessionPlayerChatPacket} with the specified last-seen messages.
|
||||
*
|
||||
* <p>This method constructs a new {@code SessionPlayerChatPacket} instance that retains the
|
||||
* current packet's properties, while updating the last seen messages.</p>
|
||||
*
|
||||
* @param lastSeenMessages the last seen messages to associate with the new packet
|
||||
* @return a new {@code SessionPlayerChatPacket} with the updated last seen messages
|
||||
*/
|
||||
public SessionPlayerChatPacket withLastSeenMessages(LastSeenMessages lastSeenMessages) {
|
||||
SessionPlayerChatPacket packet = new SessionPlayerChatPacket();
|
||||
packet.message = message;
|
||||
|
||||
@@ -26,11 +26,17 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import com.velocitypowered.proxy.protocol.packet.chat.LastSeenMessages;
|
||||
import com.velocitypowered.proxy.util.except.QuietDecoderException;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* Represents a packet that handles player commands in a Minecraft session.
|
||||
*
|
||||
* <p>This packet contains information about the player's command, timestamp,
|
||||
* salt, argument signatures, and last seen messages for verification and
|
||||
* processing.</p>
|
||||
*/
|
||||
public class SessionPlayerCommandPacket implements MinecraftPacket {
|
||||
|
||||
protected String command;
|
||||
@@ -71,7 +77,8 @@ public class SessionPlayerCommandPacket implements MinecraftPacket {
|
||||
}
|
||||
|
||||
public CommandExecuteEvent.SignedState getEventSignedState() {
|
||||
return !this.argumentSignatures.isEmpty() ? CommandExecuteEvent.SignedState.SIGNED_WITH_ARGS : CommandExecuteEvent.SignedState.SIGNED_WITHOUT_ARGS;
|
||||
return !this.argumentSignatures.isEmpty() ? CommandExecuteEvent.SignedState.SIGNED_WITH_ARGS
|
||||
: CommandExecuteEvent.SignedState.SIGNED_WITHOUT_ARGS;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -81,15 +88,26 @@ public class SessionPlayerCommandPacket implements MinecraftPacket {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SessionPlayerCommand{" +
|
||||
"command='" + command + '\'' +
|
||||
", timeStamp=" + timeStamp +
|
||||
", salt=" + salt +
|
||||
", argumentSignatures=" + argumentSignatures +
|
||||
", lastSeenMessages=" + lastSeenMessages +
|
||||
'}';
|
||||
return "SessionPlayerCommand{"
|
||||
+ "command='" + command + '\''
|
||||
+ ", timeStamp=" + timeStamp
|
||||
+ ", salt=" + salt
|
||||
+ ", argumentSignatures=" + argumentSignatures
|
||||
+ ", lastSeenMessages=" + lastSeenMessages
|
||||
+ '}';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new instance of {@code SessionPlayerCommandPacket} with the specified
|
||||
* {@code LastSeenMessages}.
|
||||
*
|
||||
* <p>If {@code lastSeenMessages} is null, it creates an {@code UnsignedPlayerCommandPacket}
|
||||
* instead. Otherwise, it creates a new {@code SessionPlayerCommandPacket} with the
|
||||
* provided {@code lastSeenMessages}.</p>
|
||||
*
|
||||
* @param lastSeenMessages the last seen messages to include in the packet, may be {@code null}
|
||||
* @return a new instance of {@code SessionPlayerCommandPacket} or {@code UnsignedPlayerCommandPacket}
|
||||
*/
|
||||
public SessionPlayerCommandPacket withLastSeenMessages(@Nullable LastSeenMessages lastSeenMessages) {
|
||||
if (lastSeenMessages == null) {
|
||||
UnsignedPlayerCommandPacket packet = new UnsignedPlayerCommandPacket();
|
||||
@@ -105,6 +123,12 @@ public class SessionPlayerCommandPacket implements MinecraftPacket {
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a collection of argument signatures for commands.
|
||||
*
|
||||
* <p>This class is responsible for handling the encoding and decoding of
|
||||
* argument signatures associated with a player command in a Minecraft session.</p>
|
||||
*/
|
||||
public static class ArgumentSignatures {
|
||||
|
||||
private final List<ArgumentSignature> entries;
|
||||
@@ -113,6 +137,16 @@ public class SessionPlayerCommandPacket implements MinecraftPacket {
|
||||
this.entries = List.of();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an {@code ArgumentSignatures} instance by decoding the signatures
|
||||
* from the provided {@code ByteBuf}.
|
||||
*
|
||||
* <p>This constructor reads the argument signatures from the buffer and ensures
|
||||
* that the number of signatures does not exceed the allowed limit.</p>
|
||||
*
|
||||
* @param buf the {@code ByteBuf} to decode the argument signatures from
|
||||
* @throws QuietDecoderException if the number of argument signatures exceeds the allowed limit
|
||||
*/
|
||||
public ArgumentSignatures(ByteBuf buf) {
|
||||
int size = ProtocolUtils.readVarInt(buf);
|
||||
if (size > 8) {
|
||||
@@ -130,20 +164,44 @@ public class SessionPlayerCommandPacket implements MinecraftPacket {
|
||||
return this.entries.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the argument signatures into the provided {@code ByteBuf}.
|
||||
*
|
||||
* <p>This method writes the number of argument signatures and each signature's
|
||||
* details into the buffer for transmission.</p>
|
||||
*
|
||||
* @param buf the {@code ByteBuf} to encode the argument signatures into
|
||||
*/
|
||||
public void encode(ByteBuf buf) {
|
||||
ProtocolUtils.writeVarInt(buf, entries.size());
|
||||
for (ArgumentSignature entry : entries) {
|
||||
entry.encode(buf);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation of this {@code ArgumentSignatures} instance.
|
||||
*
|
||||
* <p>The output includes a list of individual {@link ArgumentSignature} entries
|
||||
* attached to this command packet.</p>
|
||||
*
|
||||
* @return a human-readable string describing the argument signatures
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ArgumentSignatures{" +
|
||||
"entries=" + entries +
|
||||
'}';
|
||||
return "ArgumentSignatures{"
|
||||
+ "entries=" + entries
|
||||
+ '}';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a single argument signature associated with a command.
|
||||
*
|
||||
* <p>This class is responsible for handling the encoding and decoding of
|
||||
* individual argument signatures, which consist of a name and a signature
|
||||
* (byte array).</p>
|
||||
*/
|
||||
public static class ArgumentSignature {
|
||||
|
||||
private final String name;
|
||||
@@ -161,9 +219,9 @@ public class SessionPlayerCommandPacket implements MinecraftPacket {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ArgumentSignature{" +
|
||||
"name='" + name + '\'' +
|
||||
'}';
|
||||
return "ArgumentSignature{"
|
||||
+ "name='" + name + '\''
|
||||
+ '}';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,13 @@ import com.velocitypowered.proxy.protocol.packet.chat.LastSeenMessages;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* Represents an unsigned player command packet, extending {@link SessionPlayerCommandPacket}.
|
||||
*
|
||||
* <p>The {@code UnsignedPlayerCommandPacket} is used to handle player commands that are not
|
||||
* signed. It inherits session-specific behavior from {@link SessionPlayerCommandPacket}
|
||||
* while indicating that the command is unsigned.</p>
|
||||
*/
|
||||
public class UnsignedPlayerCommandPacket extends SessionPlayerCommandPacket {
|
||||
|
||||
@Override
|
||||
@@ -52,8 +59,8 @@ public class UnsignedPlayerCommandPacket extends SessionPlayerCommandPacket {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "UnsignedPlayerCommandPacket{" +
|
||||
"command='" + command + '\'' +
|
||||
'}';
|
||||
return "UnsignedPlayerCommandPacket{"
|
||||
+ "command='" + command + '\''
|
||||
+ '}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,13 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.kyori.adventure.key.Key;
|
||||
|
||||
/**
|
||||
* The {@code ActiveFeaturesPacket} class represents a packet that communicates the currently
|
||||
* active features between the client and server in the Minecraft protocol.
|
||||
*
|
||||
* <p>This packet is used to inform the client about which features are enabled or active,
|
||||
* potentially based on server configurations or gameplay states.</p>
|
||||
*/
|
||||
public class ActiveFeaturesPacket implements MinecraftPacket {
|
||||
|
||||
private Key[] activeFeatures;
|
||||
|
||||
@@ -25,43 +25,47 @@ import io.netty.buffer.ByteBuf;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Represents a packet sent from the server to the client, containing custom report details.
|
||||
* This packet carries a map of key-value pairs, where each key and value are strings.
|
||||
*/
|
||||
public class ClientboundCustomReportDetailsPacket implements MinecraftPacket {
|
||||
|
||||
private Map<String, String> details;
|
||||
private Map<String, String> details;
|
||||
|
||||
public ClientboundCustomReportDetailsPacket() {
|
||||
public ClientboundCustomReportDetailsPacket() {
|
||||
}
|
||||
|
||||
public ClientboundCustomReportDetailsPacket(Map<String, String> details) {
|
||||
this.details = details;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
|
||||
int detailsCount = ProtocolUtils.readVarInt(buf);
|
||||
|
||||
this.details = new HashMap<>(detailsCount);
|
||||
for (int i = 0; i < detailsCount; i++) {
|
||||
details.put(ProtocolUtils.readString(buf), ProtocolUtils.readString(buf));
|
||||
}
|
||||
}
|
||||
|
||||
public ClientboundCustomReportDetailsPacket(Map<String, String> details) {
|
||||
this.details = details;
|
||||
}
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
|
||||
ProtocolUtils.writeVarInt(buf, details.size());
|
||||
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
|
||||
int detailsCount = ProtocolUtils.readVarInt(buf);
|
||||
details.forEach((key, detail) -> {
|
||||
ProtocolUtils.writeString(buf, key);
|
||||
ProtocolUtils.writeString(buf, detail);
|
||||
});
|
||||
}
|
||||
|
||||
this.details = new HashMap<>(detailsCount);
|
||||
for (int i = 0; i < detailsCount; i++) {
|
||||
details.put(ProtocolUtils.readString(buf), ProtocolUtils.readString(buf));
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public boolean handle(MinecraftSessionHandler handler) {
|
||||
return handler.handle(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
|
||||
ProtocolUtils.writeVarInt(buf, details.size());
|
||||
|
||||
details.forEach((key, detail) -> {
|
||||
ProtocolUtils.writeString(buf, key);
|
||||
ProtocolUtils.writeString(buf, detail);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(MinecraftSessionHandler handler) {
|
||||
return handler.handle(this);
|
||||
}
|
||||
|
||||
public Map<String, String> getDetails() {
|
||||
return details;
|
||||
}
|
||||
public Map<String, String> getDetails() {
|
||||
return details;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
package com.velocitypowered.proxy.protocol.packet.config;
|
||||
|
||||
import com.velocitypowered.api.network.ProtocolVersion;
|
||||
import com.velocitypowered.api.util.ServerLink;
|
||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
@@ -27,64 +26,78 @@ import io.netty.buffer.ByteBuf;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Represents a packet sent from the server to the client, containing server-related links.
|
||||
* This packet carries a list of links (e.g., URLs or other resources) associated with the server.
|
||||
*/
|
||||
public class ClientboundServerLinksPacket implements MinecraftPacket {
|
||||
|
||||
private List<ServerLink> serverLinks;
|
||||
private List<ServerLink> serverLinks;
|
||||
|
||||
public ClientboundServerLinksPacket() {
|
||||
public ClientboundServerLinksPacket() {
|
||||
}
|
||||
|
||||
public ClientboundServerLinksPacket(List<ServerLink> serverLinks) {
|
||||
this.serverLinks = serverLinks;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
|
||||
int linksCount = ProtocolUtils.readVarInt(buf);
|
||||
|
||||
this.serverLinks = new ArrayList<>(linksCount);
|
||||
for (int i = 0; i < linksCount; i++) {
|
||||
serverLinks.add(ServerLink.read(buf, version));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
|
||||
ProtocolUtils.writeVarInt(buf, serverLinks.size());
|
||||
|
||||
for (ServerLink serverLink : serverLinks) {
|
||||
serverLink.write(buf);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(MinecraftSessionHandler handler) {
|
||||
return handler.handle(this);
|
||||
}
|
||||
|
||||
public List<ServerLink> getServerLinks() {
|
||||
return serverLinks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a link to a server with an ID, display name, and URL.
|
||||
*
|
||||
* <p>This record holds the server's identification number, a display name
|
||||
* encapsulated in a {@code ComponentHolder}, and the server's URL as a string.</p>
|
||||
*
|
||||
* @param id the unique identifier for the server
|
||||
* @param displayName the display name of the server, represented by a {@code ComponentHolder}
|
||||
* @param url the URL of the server
|
||||
*/
|
||||
public record ServerLink(int id, ComponentHolder displayName, String url) {
|
||||
|
||||
private static ServerLink read(ByteBuf buf, ProtocolVersion version) {
|
||||
if (buf.readBoolean()) {
|
||||
return new ServerLink(ProtocolUtils.readVarInt(buf), null, ProtocolUtils.readString(buf));
|
||||
} else {
|
||||
return new ServerLink(-1, ComponentHolder.read(buf, version), ProtocolUtils.readString(buf));
|
||||
}
|
||||
}
|
||||
|
||||
public ClientboundServerLinksPacket(List<ServerLink> serverLinks) {
|
||||
this.serverLinks = serverLinks;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
|
||||
int linksCount = ProtocolUtils.readVarInt(buf);
|
||||
|
||||
this.serverLinks = new ArrayList<>(linksCount);
|
||||
for (int i = 0; i < linksCount; i++) {
|
||||
serverLinks.add(ServerLink.read(buf, version));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
|
||||
ProtocolUtils.writeVarInt(buf, serverLinks.size());
|
||||
|
||||
for (ServerLink serverLink : serverLinks) {
|
||||
serverLink.write(buf);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(MinecraftSessionHandler handler) {
|
||||
return handler.handle(this);
|
||||
}
|
||||
|
||||
public List<ServerLink> getServerLinks() {
|
||||
return serverLinks;
|
||||
}
|
||||
|
||||
public record ServerLink(int id, ComponentHolder displayName, String url) {
|
||||
|
||||
private static ServerLink read(ByteBuf buf, ProtocolVersion version) {
|
||||
if (buf.readBoolean()) {
|
||||
return new ServerLink(ProtocolUtils.readVarInt(buf), null, ProtocolUtils.readString(buf));
|
||||
} else {
|
||||
return new ServerLink(-1, ComponentHolder.read(buf, version), ProtocolUtils.readString(buf));
|
||||
}
|
||||
}
|
||||
|
||||
private void write(ByteBuf buf) {
|
||||
if (id >= 0) {
|
||||
buf.writeBoolean(true);
|
||||
ProtocolUtils.writeVarInt(buf, id);
|
||||
} else {
|
||||
buf.writeBoolean(false);
|
||||
displayName.write(buf);
|
||||
}
|
||||
ProtocolUtils.writeString(buf, url);
|
||||
}
|
||||
private void write(ByteBuf buf) {
|
||||
if (id >= 0) {
|
||||
buf.writeBoolean(true);
|
||||
ProtocolUtils.writeVarInt(buf, id);
|
||||
} else {
|
||||
buf.writeBoolean(false);
|
||||
displayName.write(buf);
|
||||
}
|
||||
ProtocolUtils.writeString(buf, url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,12 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
/**
|
||||
* A client-to-server packet indicating the player has accepted the server's
|
||||
* code of conduct during the configuration stage.
|
||||
*
|
||||
* <p>This packet has no payload and is represented as a singleton.</p>
|
||||
*/
|
||||
public class CodeOfConductAcceptPacket implements MinecraftPacket {
|
||||
|
||||
public static final CodeOfConductAcceptPacket INSTANCE = new CodeOfConductAcceptPacket();
|
||||
|
||||
@@ -24,6 +24,14 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
|
||||
import com.velocitypowered.proxy.protocol.util.DeferredByteBufHolder;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
/**
|
||||
* A server-to-client packet containing the server's code of conduct.
|
||||
*
|
||||
* <p>This packet is sent during the configuration stage to present the
|
||||
* server-defined conduct rules to the client. The client may later
|
||||
* respond with a {@link CodeOfConductAcceptPacket} to indicate
|
||||
* acceptance.</p>
|
||||
*/
|
||||
public class CodeOfConductPacket extends DeferredByteBufHolder implements MinecraftPacket {
|
||||
|
||||
public CodeOfConductPacket() {
|
||||
|
||||
@@ -23,6 +23,13 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
/**
|
||||
* The {@code FinishedUpdatePacket} class represents a packet that signals the completion
|
||||
* of an update process between the client and server in the Minecraft protocol.
|
||||
*
|
||||
* <p>This packet is used to indicate that the client has finished receiving and processing
|
||||
* an update, ensuring that further operations can proceed.</p>
|
||||
*/
|
||||
public class FinishedUpdatePacket implements MinecraftPacket {
|
||||
public static final FinishedUpdatePacket INSTANCE = new FinishedUpdatePacket();
|
||||
|
||||
@@ -41,7 +48,7 @@ public class FinishedUpdatePacket implements MinecraftPacket {
|
||||
|
||||
@Override
|
||||
public int decodeExpectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction,
|
||||
ProtocolVersion version) {
|
||||
ProtocolVersion version) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -24,55 +24,74 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import com.velocitypowered.proxy.util.except.QuietDecoderException;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
/**
|
||||
* The {@code KnownPacksPacket} class represents a packet that handles the synchronization
|
||||
* of known resource packs between the client and server in the Minecraft protocol.
|
||||
*
|
||||
* <p>This packet contains a list of {@link KnownPack} instances, each representing a resource
|
||||
* pack with a namespace, identifier, and version. It allows the server to inform the client
|
||||
* about available resource packs.</p>
|
||||
*/
|
||||
public class KnownPacksPacket implements MinecraftPacket {
|
||||
|
||||
private static final int MAX_LENGTH_PACKS = Integer.getInteger("velocity.max-known-packs", 64);
|
||||
private static final QuietDecoderException TOO_MANY_PACKS =
|
||||
new QuietDecoderException("too many known packs");
|
||||
private static final int MAX_LENGTH_PACKS = Integer.getInteger("velocity.max-known-packs", 64);
|
||||
private static final QuietDecoderException TOO_MANY_PACKS =
|
||||
new QuietDecoderException("too many known packs");
|
||||
|
||||
private KnownPack[] packs;
|
||||
private KnownPack[] packs;
|
||||
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolUtils.Direction direction,
|
||||
ProtocolVersion protocolVersion) {
|
||||
final int packCount = ProtocolUtils.readVarInt(buf);
|
||||
if (direction == ProtocolUtils.Direction.SERVERBOUND && packCount > MAX_LENGTH_PACKS) {
|
||||
throw TOO_MANY_PACKS;
|
||||
}
|
||||
|
||||
final KnownPack[] packs = new KnownPack[packCount];
|
||||
|
||||
for (int i = 0; i < packCount; i++) {
|
||||
packs[i] = KnownPack.read(buf);
|
||||
}
|
||||
|
||||
this.packs = packs;
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolUtils.Direction direction,
|
||||
ProtocolVersion protocolVersion) {
|
||||
final int packCount = ProtocolUtils.readVarInt(buf);
|
||||
if (direction == ProtocolUtils.Direction.SERVERBOUND && packCount > MAX_LENGTH_PACKS) {
|
||||
throw TOO_MANY_PACKS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolUtils.Direction direction,
|
||||
ProtocolVersion protocolVersion) {
|
||||
ProtocolUtils.writeVarInt(buf, packs.length);
|
||||
final KnownPack[] packs = new KnownPack[packCount];
|
||||
|
||||
for (KnownPack pack : packs) {
|
||||
pack.write(buf);
|
||||
}
|
||||
for (int i = 0; i < packCount; i++) {
|
||||
packs[i] = KnownPack.read(buf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(MinecraftSessionHandler handler) {
|
||||
return handler.handle(this);
|
||||
this.packs = packs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolUtils.Direction direction,
|
||||
ProtocolVersion protocolVersion) {
|
||||
ProtocolUtils.writeVarInt(buf, packs.length);
|
||||
|
||||
for (KnownPack pack : packs) {
|
||||
pack.write(buf);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(MinecraftSessionHandler handler) {
|
||||
return handler.handle(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@code KnownPack} record represents a known resource pack with a namespace,
|
||||
* identifier, and version in the Minecraft protocol.
|
||||
*
|
||||
* <p>It encapsulates the information needed to identify a resource pack, typically used
|
||||
* for managing or synchronizing resource packs between the client and server.</p>
|
||||
*
|
||||
* @param namespace the namespace of the resource pack (e.g., "minecraft" or a mod name)
|
||||
* @param id the unique identifier of the resource pack within the namespace
|
||||
* @param version the version of the resource pack
|
||||
*/
|
||||
public record KnownPack(String namespace, String id, String version) {
|
||||
private static KnownPack read(ByteBuf buf) {
|
||||
return new KnownPack(ProtocolUtils.readString(buf), ProtocolUtils.readString(buf), ProtocolUtils.readString(buf));
|
||||
}
|
||||
|
||||
public record KnownPack(String namespace, String id, String version) {
|
||||
private static KnownPack read(ByteBuf buf) {
|
||||
return new KnownPack(ProtocolUtils.readString(buf), ProtocolUtils.readString(buf), ProtocolUtils.readString(buf));
|
||||
}
|
||||
|
||||
private void write(ByteBuf buf) {
|
||||
ProtocolUtils.writeString(buf, namespace);
|
||||
ProtocolUtils.writeString(buf, id);
|
||||
ProtocolUtils.writeString(buf, version);
|
||||
}
|
||||
private void write(ByteBuf buf) {
|
||||
ProtocolUtils.writeString(buf, namespace);
|
||||
ProtocolUtils.writeString(buf, id);
|
||||
ProtocolUtils.writeString(buf, version);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,18 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
|
||||
import com.velocitypowered.proxy.protocol.util.DeferredByteBufHolder;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
/**
|
||||
* The {@code RegistrySyncPacket} class is responsible for synchronizing registry data
|
||||
* between the server and client in Minecraft.
|
||||
*
|
||||
* <p>This packet is used to ensure that the client has the same registry information as
|
||||
* the server, covering aspects like blocks, items, entities, and other game elements
|
||||
* that are part of Minecraft's internal registries.</p>
|
||||
*
|
||||
* <p>It extends the {@link DeferredByteBufHolder} class to handle deferred buffering
|
||||
* operations for potentially large sets of registry data, which may include
|
||||
* complex serialization processes.</p>
|
||||
*/
|
||||
public class RegistrySyncPacket extends DeferredByteBufHolder implements MinecraftPacket {
|
||||
|
||||
public RegistrySyncPacket() {
|
||||
|
||||
@@ -23,6 +23,16 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
/**
|
||||
* The {@code StartUpdatePacket} class represents a packet that signals the
|
||||
* start of an update process in the Minecraft protocol.
|
||||
*
|
||||
* <p>This packet may be used to notify the client or server that a certain update
|
||||
* process, such as data synchronization or gameplay changes, is about to begin.</p>
|
||||
*
|
||||
* <p>Its specific use depends on the version and context of the update,
|
||||
* typically handled in the Minecraft networking layer.</p>
|
||||
*/
|
||||
public class StartUpdatePacket implements MinecraftPacket {
|
||||
public static final StartUpdatePacket INSTANCE = new StartUpdatePacket();
|
||||
|
||||
@@ -41,7 +51,7 @@ public class StartUpdatePacket implements MinecraftPacket {
|
||||
|
||||
@Override
|
||||
public int decodeExpectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction,
|
||||
ProtocolVersion version) {
|
||||
ProtocolVersion version) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -24,9 +24,17 @@ 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.Map;
|
||||
|
||||
/**
|
||||
* The {@code TagsUpdatePacket} class represents a packet sent to update the tags
|
||||
* used by the Minecraft client. Tags are used in various parts of the game to group
|
||||
* blocks, items, entities, and other objects under common categories.
|
||||
*
|
||||
* <p>This packet is typically sent to clients when they join a server or when
|
||||
* the server needs to update the list of tags for the client, ensuring that
|
||||
* the client has the most up-to-date tag information.</p>
|
||||
*/
|
||||
public class TagsUpdatePacket implements MinecraftPacket {
|
||||
|
||||
private Map<String, Map<String, int[]>> tags;
|
||||
|
||||
@@ -17,6 +17,16 @@
|
||||
|
||||
package com.velocitypowered.proxy.protocol.packet.legacyping;
|
||||
|
||||
/**
|
||||
* The {@code LegacyMinecraftPingVersion} enum represents the various protocol versions
|
||||
* used by older Minecraft clients during the server ping process.
|
||||
*
|
||||
* <p>This enum is used to distinguish between the different legacy versions of Minecraft
|
||||
* that have unique ping formats, ensuring compatibility with those older clients.</p>
|
||||
*
|
||||
* <p>Each constant in this enum corresponds to a specific version of Minecraft that
|
||||
* requires a legacy server ping format.</p>
|
||||
*/
|
||||
public enum LegacyMinecraftPingVersion {
|
||||
MINECRAFT_1_3,
|
||||
MINECRAFT_1_4,
|
||||
|
||||
@@ -23,8 +23,21 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
/**
|
||||
* The {@code GenericTitlePacket} class serves as the base class for all title-related packets
|
||||
* in Minecraft. This class provides common functionality and properties for handling title, subtitle,
|
||||
* action bar, and timing-related packets.
|
||||
*
|
||||
* <p>Subclasses of {@code GenericTitlePacket} implement specific behavior for different types of title
|
||||
* packets, such as titles, subtitles, and action bars.</p>
|
||||
*/
|
||||
public abstract class GenericTitlePacket implements MinecraftPacket {
|
||||
|
||||
/**
|
||||
* The {@code ActionType} enum represents the different actions that can be performed with a title packet.
|
||||
* Each action corresponds to a specific type of title operation, such as setting a title or subtitle,
|
||||
* updating timing information, or resetting and hiding titles.
|
||||
*/
|
||||
public enum ActionType {
|
||||
SET_TITLE(0),
|
||||
SET_SUBTITLE(1),
|
||||
@@ -45,7 +58,6 @@ public abstract class GenericTitlePacket implements MinecraftPacket {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private ActionType action;
|
||||
|
||||
protected void setAction(ActionType action) {
|
||||
@@ -88,10 +100,9 @@ public abstract class GenericTitlePacket implements MinecraftPacket {
|
||||
throw new UnsupportedOperationException("Invalid function for this TitlePacket ActionType");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public final void decode(ByteBuf buf, ProtocolUtils.Direction direction,
|
||||
ProtocolVersion version) {
|
||||
ProtocolVersion version) {
|
||||
throw new UnsupportedOperationException(); // encode only
|
||||
}
|
||||
|
||||
@@ -103,7 +114,7 @@ public abstract class GenericTitlePacket implements MinecraftPacket {
|
||||
* @return GenericTitlePacket instance that follows the invoker type/version
|
||||
*/
|
||||
public static GenericTitlePacket constructTitlePacket(ActionType type, ProtocolVersion version) {
|
||||
GenericTitlePacket packet = null;
|
||||
GenericTitlePacket packet;
|
||||
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_17)) {
|
||||
packet = switch (type) {
|
||||
case SET_ACTION_BAR -> new TitleActionbarPacket();
|
||||
@@ -111,7 +122,6 @@ public abstract class GenericTitlePacket implements MinecraftPacket {
|
||||
case SET_TIMES -> new TitleTimesPacket();
|
||||
case SET_TITLE -> new TitleTextPacket();
|
||||
case HIDE, RESET -> new TitleClearPacket();
|
||||
default -> throw new IllegalArgumentException("Invalid ActionType");
|
||||
};
|
||||
} else {
|
||||
packet = new LegacyTitlePacket();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user