diff --git a/build.gradle b/build.gradle index 058187d92..879cfc783 100644 --- a/build.gradle +++ b/build.gradle @@ -52,8 +52,8 @@ dependencies { } java { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 withJavadocJar() withSourcesJar() diff --git a/src/main/java/dev/protocollib/Example.java b/src/main/java/dev/protocollib/Example.java new file mode 100644 index 000000000..94f8125b1 --- /dev/null +++ b/src/main/java/dev/protocollib/Example.java @@ -0,0 +1,157 @@ +package dev.protocollib; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; + +import dev.protocollib.api.ProtocolLib; +import dev.protocollib.api.listener.PacketListenerBundleBehavior; +import dev.protocollib.api.listener.PacketListenerPriority; +import dev.protocollib.api.packet.MutablePacketContainer; +import dev.protocollib.api.packet.PacketContainer; +import dev.protocollib.api.packet.PacketTypes; +import dev.protocollib.api.reflect.MutableGenericAccessor; + +public class Example { + + private static final ExecutorService EXECUTOR = Executors.newCachedThreadPool(); + private static ProtocolLib protocolLib; + private static Plugin plugin; + + public static class ParticleData { + public String s; + public int i; + } + + public static class ParticlePacket { + public int duration; + public String name; + public ParticleData data; + } + + static { + MutablePacketContainer packet = null; + Class clazz = MutableGenericAccessor.class; + + packet.accessor().update(a -> { + a.update(ParticleData.class, 0, b -> { + b.set(int.class, 0, -1); + }); + }); + + packet.accessor().update(accessor -> { + + accessor.setObject(clazz, 1, ""); + accessor.set(Integer.class, 1, 1235); + accessor.set(String.class, 1, "world"); + + accessor.update(Object.class, 1, a -> { + a.update(Object.class, 1, b -> { + b.set(int.class, 2, -1); + }); + }); + + accessor.update(Object.class, 1, mutableAccessor -> { + mutableAccessor.set(int.class, 1, -1); + }); + + accessor.update(ParticleData.class, 1, (pd) -> { + pd.i++; + return pd; + }); + + accessor.update(ParticleData.class, 1, (pd) -> { + pd.set(int.class, 1, pd.get(int.class, 1) + 1); + }); + }); + } + + // ======================== + // Packet Listeners + // ======================== + + static void registerListeners() { + // sync read-only listener + protocolLib + .createListener(plugin) + .types(PacketTypes.Game.LEVEL_CHUNK) + .priority(PacketListenerPriority.LOW) + .includeCanceledPackets() + .bundleBehavior(PacketListenerBundleBehavior.SKIP_OUTSIDE_BUNDLE) + .registerSync((packet, context) -> { + Chunk chunk = Chunk.from(packet); + // Sync processing + }); + + // async modify packet on netty thread + protocolLib + .createListener(plugin) + .types(PacketTypes.Game.LEVEL_CHUNK) + .mutable() + .registerAsync((packet, context) -> { + Chunk chunk = Chunk.from(packet); + + // do processing here ... + // write changes to packet ... + + context.addTransmissionListener(chunk::markSent); + context.resumeProcessing(); + }); + + // async modify packet on app thread-pool + protocolLib + .createListener(plugin) + .types(PacketTypes.Game.LEVEL_CHUNK) + .mutable() + .registerAsync((packet, context) -> { + Chunk chunk = Chunk.from(packet); + + EXECUTOR.execute(() -> { + + // do heavy processing here ... + // write changes to packet ... + + context.addTransmissionListener(chunk::markSent); + context.resumeProcessing(); + }); + }); + } + + // ======================== + // Packet Sending + // ======================== + + static void sendPackets(Player player, Chunk chunk) { + // full packet operation + protocolLib + .connection(player) + .packetOperation() + .skipProcessing() + .postTransmission(chunk::markSent) + .send(chunk.packet()); + + // connection shortcut + protocolLib + .connection(player) + .sendPacket(chunk.packet()); + + // direct API shortcut + protocolLib + .sendPacket(player, chunk.packet()); + } + + // ======================== + // Chunk Implementation + // ======================== + + static class Chunk { + static Chunk from(PacketContainer packet) { + return new Chunk(); + } + + PacketContainer packet() { return null; } + void markSent() {} + } +} \ No newline at end of file diff --git a/src/main/java/dev/protocollib/api/Connection.java b/src/main/java/dev/protocollib/api/Connection.java new file mode 100644 index 000000000..f789b1833 --- /dev/null +++ b/src/main/java/dev/protocollib/api/Connection.java @@ -0,0 +1,86 @@ +package dev.protocollib.api; + +import java.net.InetSocketAddress; +import java.util.Optional; + +import org.bukkit.entity.Player; + +import dev.protocollib.api.packet.PacketLike; +import dev.protocollib.api.packet.PacketOperationBuilder; + +/** + * Represents a connection associated with a player. + * + *

This interface provides methods to interact with the player's network connection, + * including retrieving the player's information, connection address, protocol version, + * and current connection state. It also allows for sending and receiving packets + * through the connection.

+ */ +public interface Connection { + + + /** + * Retrieves the player associated with the connection, if available. + * + * @return an {@link Optional} containing the player, or empty if the player is not present + */ + Optional player(); + + /** + * Retrieves the address of the connection. + * + * @return the remote {@link InetSocketAddress} of the connection + */ + InetSocketAddress address(); + + /** + * Retrieves the protocol version used by the connection. + * + * @return the protocol version + */ + int protocolVersion(); + + /** + * Retrieves the current protocol phase of the connection for a given direction. + * + * @param packetDirection the direction of the packet (clientbound or serverbound) + * @return the {@link ProtocolPhase} representing the current phase of the connection + */ + ProtocolPhase protocolPhase(ProtocolDirection packetDirection); + + /** + * Checks if the connection is currently open. + * + * @return {@code true} if the connection is open, {@code false} otherwise + */ + boolean isConnected(); + + /** + * Initiates a packet operation, which can involve sending or receiving a packet. + * + * @return a {@link PacketOperationBuilder} to configure the packet operation + */ + PacketOperationBuilder packetOperation(); + + /** + * Sends a packet to the client. + * + * @param packet the packet to send + */ + void sendPacket(PacketLike packet); + + /** + * Receives a packet as if the client had sent it. + * + * @param packet the received packet + */ + void receivePacket(PacketLike packet); + + /** + * Disconnects the connection with the specified reason. + * + * @param reason the reason for disconnecting + */ + void disconnect(String reason); + +} diff --git a/src/main/java/dev/protocollib/api/ProtocolDirection.java b/src/main/java/dev/protocollib/api/ProtocolDirection.java new file mode 100644 index 000000000..f9e8b055d --- /dev/null +++ b/src/main/java/dev/protocollib/api/ProtocolDirection.java @@ -0,0 +1,14 @@ +package dev.protocollib.api; + +/** + * Representing the direction of a packet, either sent to or from the server. + */ +public enum ProtocolDirection { + + /** Packet sent from the client to the server. */ + SERVERBOUND, + + /** Packet sent from the server to the client. */ + CLIENTBOUND; + +} diff --git a/src/main/java/dev/protocollib/api/ProtocolLib.java b/src/main/java/dev/protocollib/api/ProtocolLib.java new file mode 100644 index 000000000..2a86b11f6 --- /dev/null +++ b/src/main/java/dev/protocollib/api/ProtocolLib.java @@ -0,0 +1,67 @@ +package dev.protocollib.api; + +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; + +import dev.protocollib.api.listener.PacketListenerBuilder; +import dev.protocollib.api.packet.BinaryPacket; +import dev.protocollib.api.packet.MutablePacketContainer; +import dev.protocollib.api.packet.PacketLike; +import dev.protocollib.api.packet.PacketType; + +/** + * Representing the main entry point for the ProtocolLib API. + */ +public interface ProtocolLib { + + /** + * Creates a packet listener for the provided plugin. + * + * @param plugin the plugin registering the packet listener + * @return a builder to configure and register the packet listener + */ + PacketListenerBuilder createListener(Plugin plugin); + + /** + * Retrieves the connection associated with a specific player. + * + * @param player the player whose connection is being retrieved + * @return the connection for the specified player + */ + Connection connection(Player player); + + /** + * Sends a packet to the player. + * + * @param packet the packet to send + */ + default void sendPacket(Player player, PacketLike packet) { + connection(player).sendPacket(packet); + } + + /** + * Receives a packet as if the player had sent it. + * + * @param packet the received packet + */ + default void receivePacket(Player player, PacketLike packet) { + connection(player).receivePacket(packet); + } + + /** + * Creates a new binary packet with the given type and payload. + * + * @param packetType the type of the packet to create + * @param payload the binary payload to include in the packet + * @return a new {@link BinaryPacket} instance + */ + BinaryPacket createBinaryPacket(PacketType packetType, byte[] payload); + + /** + * Creates a new packet container for the given packet type. + * + * @param packetType the type of the packet to create + * @return a new {@link MutablePacketContainer} instance + */ + MutablePacketContainer createPacket(PacketType packetType); +} diff --git a/src/main/java/dev/protocollib/api/ProtocolPhase.java b/src/main/java/dev/protocollib/api/ProtocolPhase.java new file mode 100644 index 000000000..003596478 --- /dev/null +++ b/src/main/java/dev/protocollib/api/ProtocolPhase.java @@ -0,0 +1,10 @@ +package dev.protocollib.api; + +/** + * Representing the different protocol phases of a minecraft. + */ +public enum ProtocolPhase { + + HANDSHAKE, GAME, STATUS, LOGIN, CONFIGURATION, UNKNOWN; + +} diff --git a/src/main/java/dev/protocollib/api/listener/ImmutablePacketListener.java b/src/main/java/dev/protocollib/api/listener/ImmutablePacketListener.java new file mode 100644 index 000000000..5a142a3b9 --- /dev/null +++ b/src/main/java/dev/protocollib/api/listener/ImmutablePacketListener.java @@ -0,0 +1,24 @@ +package dev.protocollib.api.listener; + +import org.jetbrains.annotations.NotNull; + +import dev.protocollib.api.packet.PacketContainer; + +/** + * Functional interface for handling immutable packets. + * + *

An immutable packet listener can be executed either synchronously or asynchronously + * depending on how it was registered. If registered asynchronously, it will be executed + * in parallel with other listeners, ensuring non-blocking packet processing.

+ */ +@FunctionalInterface +public interface ImmutablePacketListener { + + /** + * Handles a packet that was sent or received. + * + * @param packet the immutable packet to handle + * @param context the context providing additional information about the packet + */ + void handlePacket(@NotNull PacketContainer packet, @NotNull ImmutablePacketListenerContext context); +} diff --git a/src/main/java/dev/protocollib/api/listener/ImmutablePacketListenerContext.java b/src/main/java/dev/protocollib/api/listener/ImmutablePacketListenerContext.java new file mode 100644 index 000000000..9a23c27d7 --- /dev/null +++ b/src/main/java/dev/protocollib/api/listener/ImmutablePacketListenerContext.java @@ -0,0 +1,35 @@ +package dev.protocollib.api.listener; + +import org.jetbrains.annotations.NotNull; + +import dev.protocollib.api.Connection; + +/** + * Context for immutable packet listeners. + */ +public interface ImmutablePacketListenerContext { + + /** + * Retrieves the connection associated with the packet. + * + * @return the connection handling the packet + */ + @NotNull + Connection connection(); + + /** + * Checks if the packet handling has been cancelled. + * + * @return true if the packet is cancelled, false otherwise + */ + boolean isCancelled(); + + /** + * Adds a listener to be invoked after the packet is fully sent or received. + * The transmission listener will get invoked on the underlying channel's + * event-loop. + * + * @param listener the transmission listener to invoke + */ + void addTransmissionListener(@NotNull PacketTransmissionListener listener); +} diff --git a/src/main/java/dev/protocollib/api/listener/MutableAsyncPacketListener.java b/src/main/java/dev/protocollib/api/listener/MutableAsyncPacketListener.java new file mode 100644 index 000000000..6c57e1983 --- /dev/null +++ b/src/main/java/dev/protocollib/api/listener/MutableAsyncPacketListener.java @@ -0,0 +1,34 @@ +package dev.protocollib.api.listener; + +import org.jetbrains.annotations.NotNull; + +import dev.protocollib.api.packet.MutablePacketContainer; + +/** + * Functional interface for handling and manipulating packets asynchronously. + * + *

Once a packet is processed by the listener, the context's + * {@code resumeProcessing()} or {@code resumeProcessingWithException(Throwable)} + * methods must be called to signal that the listener is done with the packet. + * Failing to call one of these methods will cause the packet to remain in + * a waiting state until it times out, preventing further listeners from + * receiving the packet. + *

+ */ +@FunctionalInterface +public interface MutableAsyncPacketListener { + + /** + * Handles a packet that was sent or received, asynchronously. + * + *

Once processing is complete, ensure that one of the {@code resumeProcessing} + * methods from the {@link MutableAsyncPacketListenerContext} is called. This allows the + * packet to continue to the next listener. If not called, the packet will remain + * in a waiting state and will eventually timeout.

+ * + * @param packet the packet to handle + * @param context the context providing additional information about the packet and connection + */ + void handlePacket(@NotNull MutablePacketContainer packet, @NotNull MutableAsyncPacketListenerContext context); + +} diff --git a/src/main/java/dev/protocollib/api/listener/MutableAsyncPacketListenerContext.java b/src/main/java/dev/protocollib/api/listener/MutableAsyncPacketListenerContext.java new file mode 100644 index 000000000..22008c1da --- /dev/null +++ b/src/main/java/dev/protocollib/api/listener/MutableAsyncPacketListenerContext.java @@ -0,0 +1,25 @@ +package dev.protocollib.api.listener; + +import org.jetbrains.annotations.NotNull; + +/** + * Context of a mutable asynchronous packet listener. + */ +public interface MutableAsyncPacketListenerContext extends MutableSyncPacketListenerContext { + + /** + * Singles the listener is done with processing the packet. Handing + * it over to the next asynchronous packet listener. + */ + void resumeProcessing(); + + /** + * Singles the listener is done with processing the packet and finished + * with an exception. Handing it over to the next asynchronous packet + * listener. + * + * @param throwable the processing exception + */ + void resumeProcessingWithException(@NotNull Throwable throwable); + +} diff --git a/src/main/java/dev/protocollib/api/listener/MutableSyncPacketListener.java b/src/main/java/dev/protocollib/api/listener/MutableSyncPacketListener.java new file mode 100644 index 000000000..d1a5880dd --- /dev/null +++ b/src/main/java/dev/protocollib/api/listener/MutableSyncPacketListener.java @@ -0,0 +1,21 @@ +package dev.protocollib.api.listener; + +import org.jetbrains.annotations.NotNull; + +import dev.protocollib.api.packet.MutablePacketContainer; + +/** + * Functional interface for handling and manipulating packets synchronously. + */ +@FunctionalInterface +public interface MutableSyncPacketListener { + + /** + * Handles a packet that was sent or received, synchronously. + * + * @param packet the packet to handle + * @param context the context providing additional information about the packet and functions + */ + void handlePacket(@NotNull MutablePacketContainer packet, @NotNull MutableSyncPacketListenerContext context); + +} diff --git a/src/main/java/dev/protocollib/api/listener/MutableSyncPacketListenerContext.java b/src/main/java/dev/protocollib/api/listener/MutableSyncPacketListenerContext.java new file mode 100644 index 000000000..ee9ba6dd1 --- /dev/null +++ b/src/main/java/dev/protocollib/api/listener/MutableSyncPacketListenerContext.java @@ -0,0 +1,18 @@ +package dev.protocollib.api.listener; + +/** + * Context of a mutable synchronous packet listener. + */ +public interface MutableSyncPacketListenerContext extends ImmutablePacketListenerContext { + + /** + * Sets whether the packet handling is cancelled. If cancelled, the packet will + * not be processed further unless a listener is using the + * {@link PacketListenerBuilder.WithType#includeCanceledPackets() includeCanceledPackets} flag. + * + * @param cancelled true to cancel the packet, false to allow processing + * + * @see PacketListenerBuilder + */ + void setCancelled(boolean cancelled); +} diff --git a/src/main/java/dev/protocollib/api/listener/PacketListenerBuilder.java b/src/main/java/dev/protocollib/api/listener/PacketListenerBuilder.java new file mode 100644 index 000000000..7c63924eb --- /dev/null +++ b/src/main/java/dev/protocollib/api/listener/PacketListenerBuilder.java @@ -0,0 +1,126 @@ +package dev.protocollib.api.listener; + +import java.util.Collection; + +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +import dev.protocollib.api.packet.PacketType; + +/** + * Builder for creating and registering packet listeners. + * + *

This builder allows configuring packet listeners with various settings, + * including the packet types they should handle, execution priority, and behavior + * regarding canceled packets and packet bundles.

+ */ +public interface PacketListenerBuilder { + + /** + * Specifies the types of packets to listen for. + * + * @param packetTypes the packet types to listen for + * @return the same builder for further configuration + */ + @NotNull + PacketListenerBuilder.WithType types(@NotNull PacketType... packetTypes); + + /** + * Specifies the types of packets to listen for. + * + * @param packetTypes the collection of packet types to listen for + * @return the same builder for further configuration + */ + @NotNull + PacketListenerBuilder.WithType types(@NotNull Collection packetTypes); + + /** + * Interface for building a packet listener with specific packet types. + */ + public interface WithType { + + /** + * Specifies the priority of the packet listener. + * + * @param priority the priority to assign to the listener + * @return the same builder for further configuration + */ + @Contract("_ -> this") + PacketListenerBuilder.WithType priority(@NotNull PacketListenerPriority priority); + + /** + * Configures the bundle behavior for the listener, determining how packets in bundles are handled. + * + * @param bundleBehavior the bundle behavior to apply + * @return the same builder for further configuration + * + * @see PacketListenerBundleBehavior + */ + @Contract("_ -> this") + PacketListenerBuilder.WithType bundleBehavior(@NotNull PacketListenerBundleBehavior bundleBehavior); + + /** + * Configures the listener to include packets that have been canceled. By default, + * canceled packets are skipped. + * + * @return the same builder for further configuration + */ + @Contract("_ -> this") + PacketListenerBuilder.WithType includeCanceledPackets(); + + /** + * Allows the listener to modify packets. By default, listeners are read-only and + * cannot modify packets. + * + * @return the mutable packet listener builder for further configuration + */ + @NotNull + PacketListenerBuilder.Mutable mutable(); + + /** + * Registers the packet listener to operate synchronously. The listener will be executed + * in order relative to other synchronous listeners. + * + * @param listener the immutable synchronous packet listener to register + * @return the packet listener registration instance + */ + @NotNull + PacketListenerRegistration registerSync(@NotNull ImmutablePacketListener listener); + + /** + * Registers the packet listener to operate asynchronously. The listener will be executed + * in parallel with other asynchronous listeners, ensuring non-blocking packet processing. + * + * @param listener the immutable asynchronous packet listener to register + * @return the packet listener registration instance + */ + @NotNull + PacketListenerRegistration registerAsync(@NotNull ImmutablePacketListener listener); + } + + /** + * Interface for building a mutable packet listener, allowing packet modifications. + */ + public interface Mutable { + + /** + * Registers the packet listener to operate synchronously. The listener will always + * be executed on the main game thread. + * + * @param listener the synchronous mutable packet listener to register + * @return the packet listener registration instance + */ + @NotNull + PacketListenerRegistration registerSync(@NotNull MutableSyncPacketListener listener); + + /** + * Registers the packet listener to operate asynchronously. The listener will be executed + * in parallel with other asynchronous listeners, ensuring non-blocking packet processing. + * + * @param listener the asynchronous mutable packet listener to register + * @return the packet listener registration instance + */ + @NotNull + PacketListenerRegistration registerAsync(@NotNull MutableAsyncPacketListener listener); + } +} diff --git a/src/main/java/dev/protocollib/api/listener/PacketListenerBundleBehavior.java b/src/main/java/dev/protocollib/api/listener/PacketListenerBundleBehavior.java new file mode 100644 index 000000000..e6eb9111e --- /dev/null +++ b/src/main/java/dev/protocollib/api/listener/PacketListenerBundleBehavior.java @@ -0,0 +1,31 @@ +package dev.protocollib.api.listener; + +/** + * Controls packet processing behavior relative to packet bundles (groups of + * packets processed atomically). + *

+ * Determines whether individual packets should be processed or ignored based on + * their membership in a packet bundle. Packet bundles typically represent + * groups of packets that are be processed together as an atomic unit. + *

+ */ +public enum PacketListenerBundleBehavior { + + /** + * Processes all packets unconditionally, regardless of whether they are part of + * a packet bundle. + */ + ALWAYS, + + /** + * Processes only packets that exist outside of packet bundles, ignoring those + * inside bundles. + */ + SKIP_INSIDE_BUNDLE, + + /** + * Processes only packets that are part of packet bundles, ignoring standalone + * packets. + */ + SKIP_OUTSIDE_BUNDLE; +} diff --git a/src/main/java/dev/protocollib/api/listener/PacketListenerPriority.java b/src/main/java/dev/protocollib/api/listener/PacketListenerPriority.java new file mode 100644 index 000000000..b7a912134 --- /dev/null +++ b/src/main/java/dev/protocollib/api/listener/PacketListenerPriority.java @@ -0,0 +1,22 @@ +package dev.protocollib.api.listener; + +/** + * Representing the priority levels for packet listeners. + */ +public enum PacketListenerPriority { + + /** Lowest priority, executed first. */ + LOWEST, + + /** Low priority, executed after lowest but before normal. */ + LOW, + + /** Normal priority, executed after low but before high. */ + NORMAL, + + /** High priority, executed after normal but before highest. */ + HIGH, + + /** Highest priority, executed last. */ + HIGHEST; +} diff --git a/src/main/java/dev/protocollib/api/listener/PacketListenerRegistration.java b/src/main/java/dev/protocollib/api/listener/PacketListenerRegistration.java new file mode 100644 index 000000000..cfcc71722 --- /dev/null +++ b/src/main/java/dev/protocollib/api/listener/PacketListenerRegistration.java @@ -0,0 +1,13 @@ +package dev.protocollib.api.listener; + +/** + * Representing the registration of a packet listener. + * Allows unregistering the listener when no longer needed. + */ +public interface PacketListenerRegistration { + + /** + * Unregisters the packet listener, stopping it from receiving further packets. + */ + void unregister(); +} diff --git a/src/main/java/dev/protocollib/api/listener/PacketTransmissionListener.java b/src/main/java/dev/protocollib/api/listener/PacketTransmissionListener.java new file mode 100644 index 000000000..0ea27c869 --- /dev/null +++ b/src/main/java/dev/protocollib/api/listener/PacketTransmissionListener.java @@ -0,0 +1,17 @@ +package dev.protocollib.api.listener; + +/** + * Functional interface for a listener that is invoked when a packet has been sent + * (according to the underlying write's ChannelFuture) or received (just before the + * packet gets processed by mojangs packet handlers). + * + * This method will get invoked on the underlying channel's event-loop. + */ +@FunctionalInterface +public interface PacketTransmissionListener { + + /** + * Invoked when a packet has been successfully sent. + */ + void invoke(); +} diff --git a/src/main/java/dev/protocollib/api/packet/BinaryPacket.java b/src/main/java/dev/protocollib/api/packet/BinaryPacket.java new file mode 100644 index 000000000..6acbde6a3 --- /dev/null +++ b/src/main/java/dev/protocollib/api/packet/BinaryPacket.java @@ -0,0 +1,24 @@ +package dev.protocollib.api.packet; + +import org.jetbrains.annotations.NotNull; + +/** + * Representing a raw binary packet with a packet id and payload. + */ +public non-sealed interface BinaryPacket extends PacketLike { + + /** + * Retrieves the packet id. + * + * @return the packet ID + */ + int id(); + + /** + * Retrieves the payload (data) of the packet. + * + * @return the packet payload as a byte array + */ + @NotNull + byte[] payload(); +} diff --git a/src/main/java/dev/protocollib/api/packet/MutablePacketContainer.java b/src/main/java/dev/protocollib/api/packet/MutablePacketContainer.java new file mode 100644 index 000000000..507fbfbb7 --- /dev/null +++ b/src/main/java/dev/protocollib/api/packet/MutablePacketContainer.java @@ -0,0 +1,49 @@ +package dev.protocollib.api.packet; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import dev.protocollib.api.reflect.MutableGenericAccessor; + +/** + * Represents a mutable packet container that allows modifications. + */ +public interface MutablePacketContainer extends PacketContainer { + + /** + * Retrieves the raw packet object. + * + *

This method provides access to the underlying packet instance, + * allowing direct interaction with its data.

+ * + * @return the raw packet object + */ + @NotNull + Object packet(); + + /** + * Provides a {@link MutableGenericAccessor} for modifying packet fields reflectively. + * + * @return the mutable generic accessor for packet data + */ + @NotNull + MutableGenericAccessor accessor(); + + /** + * Retrieves the packet bundle that this packet is part of, if any. + * + * @return the packet bundle containing this packet, or {@code null} if not part of a bundle + */ + @Nullable + MutablePacketContainer bundle(); + + /** + * Creates and returns a mutable copy of this packet. + * + *

The cloned packet retains mutability, allowing further modifications.

+ * + * @return a mutable clone of this packet container + */ + @Nullable + MutablePacketContainer clone(); +} diff --git a/src/main/java/dev/protocollib/api/packet/PacketContainer.java b/src/main/java/dev/protocollib/api/packet/PacketContainer.java new file mode 100644 index 000000000..45eb3fe58 --- /dev/null +++ b/src/main/java/dev/protocollib/api/packet/PacketContainer.java @@ -0,0 +1,47 @@ +package dev.protocollib.api.packet; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import dev.protocollib.api.reflect.GenericAccessor; + +/** + * Represents a container for a packet. + */ +public non-sealed interface PacketContainer extends PacketLike { + + /** + * Retrieves the type of the packet. + * + * @return the packet type + */ + @NotNull + PacketType packetType(); + + /** + * Provides a {@link GenericAccessor} for accessing packet fields reflectively. + * + * @return the generic accessor for packet data + */ + @NotNull + GenericAccessor accessor(); + + /** + * Retrieves the packet bundle that this packet is part of, if any. + * A packet bundle represents a collection of packets that are processed together. + * + * @return the packet bundle containing this packet, or {@code null} if not part of a bundle + */ + @Nullable + PacketContainer bundle(); + + /** + * Creates and returns a mutable copy of this packet. + * + *

The cloned packet allows modifications, unlike the immutable {@code PacketContainer}.

+ * + * @return a mutable clone of this packet container + */ + @Nullable + PacketContainer clone(); +} diff --git a/src/main/java/dev/protocollib/api/packet/PacketLike.java b/src/main/java/dev/protocollib/api/packet/PacketLike.java new file mode 100644 index 000000000..b9a2b4a66 --- /dev/null +++ b/src/main/java/dev/protocollib/api/packet/PacketLike.java @@ -0,0 +1,5 @@ +package dev.protocollib.api.packet; + +public sealed interface PacketLike permits BinaryPacket, PacketContainer { + +} diff --git a/src/main/java/dev/protocollib/api/packet/PacketOperationBuilder.java b/src/main/java/dev/protocollib/api/packet/PacketOperationBuilder.java new file mode 100644 index 000000000..1cb0f26e3 --- /dev/null +++ b/src/main/java/dev/protocollib/api/packet/PacketOperationBuilder.java @@ -0,0 +1,51 @@ +package dev.protocollib.api.packet; + +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +import dev.protocollib.api.listener.PacketTransmissionListener; + +/** + * Builder interface for constructing packet operations. + * + *

This interface provides methods to configure how packets are sent + * or received within ProtocolLib.

+ */ +public interface PacketOperationBuilder { + + /** + * Skips processing the packet in ProtocolLib's pipeline. + * + *

This method allows the caller to prevent further handling of the packet + * by other plugin listeners in the pipeline.

+ * + * @return the same builder for further configuration + */ + @Contract("_ -> this") + PacketOperationBuilder skipProcessing(); + + /** + * Registers a listener to be called once the packet has been sent or received. + * The transmission listener will get invoked on the underlying channel's + * event-loop. + * + * @param listener the listener to be notified upon packet transmission + * @return the same builder for further configuration + */ + @Contract("_ -> this") + PacketOperationBuilder postTransmission(@NotNull PacketTransmissionListener listener); + + /** + * Sends a packet to the client. + * + * @param packet the packet to send + */ + void send(@NotNull PacketLike packet); + + /** + * Receives a packet as if the client had sent it. + * + * @param packet the packet to receive + */ + void receive(@NotNull PacketLike packet); +} diff --git a/src/main/java/dev/protocollib/api/packet/PacketType.java b/src/main/java/dev/protocollib/api/packet/PacketType.java new file mode 100644 index 000000000..0e0ac8d34 --- /dev/null +++ b/src/main/java/dev/protocollib/api/packet/PacketType.java @@ -0,0 +1,67 @@ +package dev.protocollib.api.packet; + +import java.util.Optional; + +import org.jetbrains.annotations.NotNull; + +import dev.protocollib.api.ProtocolDirection; +import dev.protocollib.api.ProtocolPhase; +import net.kyori.adventure.key.Keyed; + +/** + * Representing the type of a network packet. + * + *

A {@code PacketType} identifies a specific type of packet in the protocol, + * including information about its direction, phase in the protocol, associated class (if any), + * whether the packet is currently supported and if it terminates the current phase.

+ */ +public interface PacketType extends Keyed { + + /** + * Retrieves the direction in which the packet is being sent. + * + * @return the {@link ProtocolDirection} of the packet, either clientbound or serverbound + */ + @NotNull + ProtocolDirection protocolDirection(); + + /** + * Retrieves the protocol phase during which this packet is used. + * This indicates which phase of the protocol (e.g., HANDSHAKE, LOGIN, GAME) + * the packet belongs to. + * + * @return the {@link ProtocolPhase} associated with this packet + */ + @NotNull + ProtocolPhase protocolPhase(); + + /** + * Retrieves the class associated with the packet type, if available. + * + *

If the packet is supported (i.e., {@link #isSupported()} returns {@code true}), + * this method will always return a non-empty {@link Optional} containing the packet class. + * Note that the same packet class may be reused across different protocol phases. + * The current protocol phase should be checked via {@link #protocolPhase()} to determine + * the correct context.

+ * + * @return an {@link Optional} containing the class of the packet, or empty if not applicable + */ + @NotNull + Optional> packetClass(); + + /** + * Checks whether the packet type is supported by the current protocol version. + * + * @return {@code true} if the packet type is supported, {@code false} otherwise + */ + boolean isSupported(); + + /** + * Determines if processing this packet terminates the current protocol phase, + * potentially transitioning to a new phase. For example, a login success packet + * might terminate the LOGIN phase and transition to the CONFIGURATION phase. + * + * @return {@code true} if this packet ends the current protocol phase, {@code false} otherwise + */ + boolean isTerminal(); +} diff --git a/src/main/java/dev/protocollib/api/packet/PacketTypes.java b/src/main/java/dev/protocollib/api/packet/PacketTypes.java new file mode 100644 index 000000000..703aaaec7 --- /dev/null +++ b/src/main/java/dev/protocollib/api/packet/PacketTypes.java @@ -0,0 +1,12 @@ +package dev.protocollib.api.packet; + +public class PacketTypes { + + // TODO should contain all packet types as public static final fields + + public static class Game { + + public static final PacketType LEVEL_CHUNK = null; + + } +} diff --git a/src/main/java/dev/protocollib/api/reflect/Converter.java b/src/main/java/dev/protocollib/api/reflect/Converter.java new file mode 100644 index 000000000..0a71ae0cb --- /dev/null +++ b/src/main/java/dev/protocollib/api/reflect/Converter.java @@ -0,0 +1,12 @@ +package dev.protocollib.api.reflect; + +public interface Converter { + + Object getGeneric(T specific); + + Class getGenericType(); + + T getSpecific(Object generic); + + Class getSpecificType(); +} diff --git a/src/main/java/dev/protocollib/api/reflect/GenericAccessor.java b/src/main/java/dev/protocollib/api/reflect/GenericAccessor.java new file mode 100644 index 000000000..b573bf261 --- /dev/null +++ b/src/main/java/dev/protocollib/api/reflect/GenericAccessor.java @@ -0,0 +1,31 @@ +package dev.protocollib.api.reflect; + +public interface GenericAccessor { + + // ==================================================== + // Value Retrieval Optional + // ==================================================== + + GenericAccessor getAccessor(Class type, int ordinal); + GenericAccessor getAccessorOrThrow(Class type, int ordinal); + + Object getObject(Class type, int ordinal); + Object getObjectOrThrow(Class type, int ordinal); + + T get(Class type, int ordinal); + T getOrThrow(Class type, int ordinal); + + T get(Converter converter, int ordinal); + T getOrThrow(Converter converter, int ordinal); + + // ==================================================== + // Metadata + // ==================================================== + + boolean isSupported(Class type, int ordinal); + boolean isSupported(Converter type, int ordinal); + + int count(Class type); + int count(Converter type); + +} diff --git a/src/main/java/dev/protocollib/api/reflect/GenericMutator.java b/src/main/java/dev/protocollib/api/reflect/GenericMutator.java new file mode 100644 index 000000000..f78d65dc2 --- /dev/null +++ b/src/main/java/dev/protocollib/api/reflect/GenericMutator.java @@ -0,0 +1,43 @@ +package dev.protocollib.api.reflect; + +import java.util.function.Consumer; +import java.util.function.UnaryOperator; + +public interface GenericMutator extends GenericAccessor { + + // ==================================================== + // Value Modification + // ==================================================== + + Object updateObject(Class type, int ordinal, UnaryOperator operator); + Object updateObjectOrThrow(Class type, int ordinal, UnaryOperator operator); + + T update(Class type, int ordinal, UnaryOperator operator); + T updateOrThrow(Class type, int ordinal, UnaryOperator operator); + + T update(Converter converter, int ordinal, UnaryOperator operator); + T updateOrThrow(Converter converter, int ordinal, UnaryOperator operator); + + Object update(Class type, int ordinal, Consumer operator); + Object updateOrThrow(Class type, int ordinal, Consumer operator); + + // ==================================================== + // Value Assignment + // ==================================================== + + void setObject(Class type, int ordinal, Object value); + void setObjectOrThrow(Class type, int ordinal, Object value); + + void setDefault(Class type, int ordinal); + void setDefaultOrThrow(Class type, int ordinal); + + void setDefault(Converter type, int ordinal); + void setDefaultOrThrow(Converter type, int ordinal); + + void set(Class type, int ordinal, T value); + void setOrThrow(Class type, int ordinal, T value); + + void set(Converter converter, int ordinal, T value); + void setOrThrow(Converter converter, int ordinal, T value); + +} diff --git a/src/main/java/dev/protocollib/api/reflect/MutableGenericAccessor.java b/src/main/java/dev/protocollib/api/reflect/MutableGenericAccessor.java new file mode 100644 index 000000000..7e17e7856 --- /dev/null +++ b/src/main/java/dev/protocollib/api/reflect/MutableGenericAccessor.java @@ -0,0 +1,9 @@ +package dev.protocollib.api.reflect; + +import java.util.function.Consumer; + +public interface MutableGenericAccessor extends GenericAccessor { + + void update(Consumer consumer); + +}