diff --git a/api/src/main/java/org/geysermc/geyser/api/event/bedrock/SessionDefineNetworkChannelsEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/bedrock/SessionDefineNetworkChannelsEvent.java index 29b917025..85107e26c 100644 --- a/api/src/main/java/org/geysermc/geyser/api/event/bedrock/SessionDefineNetworkChannelsEvent.java +++ b/api/src/main/java/org/geysermc/geyser/api/event/bedrock/SessionDefineNetworkChannelsEvent.java @@ -29,13 +29,18 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.api.connection.GeyserConnection; import org.geysermc.geyser.api.event.connection.ConnectionEvent; import org.geysermc.geyser.api.network.NetworkChannel; +import org.geysermc.geyser.api.network.message.Message; import org.geysermc.geyser.api.network.message.MessageBuffer; import org.geysermc.geyser.api.network.message.MessageCodec; import org.geysermc.geyser.api.network.message.MessageFactory; +import org.geysermc.geyser.api.network.message.MessageHandler; +import org.geysermc.geyser.api.network.message.MessagePriority; + +import java.util.function.Consumer; /** * Called whenever Geyser is registering network channels. - * @since 2.8.2 + * @since 2.9.1 */ public abstract class SessionDefineNetworkChannelsEvent extends ConnectionEvent { @@ -44,19 +49,159 @@ public abstract class SessionDefineNetworkChannelsEvent extends ConnectionEvent } /** - * Registers a new network channel with a message factory. + * Defines the registration of a new network channel with a message factory. * * @param channel the channel to register * @param messageFactory the factory to create messages from the buffer + * @param the message type created by the factory + * @return a registration builder to configure handlers */ - public abstract void register(@NonNull NetworkChannel channel, @NonNull MessageFactory messageFactory); + public abstract > Builder.@NonNull Initial define(@NonNull NetworkChannel channel, @NonNull MessageFactory messageFactory); /** - * Registers a new network channel with a message factory. + * Defines the registration of a new network channel with a codec and message factory. * * @param channel the channel to register * @param codec the codec to use to encode/decode the buffer * @param messageFactory the factory to create messages from the buffer + * @param the buffer type + * @param the message type created by the factory + * @return a registration builder to configure handlers */ - public abstract void register(@NonNull NetworkChannel channel, @NonNull MessageCodec codec, @NonNull MessageFactory messageFactory); + public abstract > Builder.@NonNull Initial define(@NonNull NetworkChannel channel, @NonNull MessageCodec codec, @NonNull MessageFactory messageFactory); + + /** + * Registration builder for attaching handlers to a channel. + * + * @param the message type + */ + public interface Builder> { + + /** + * Configures the pipeline for this handler. + * + * @param pipeline the pipeline consumer + * @return the builder instance + */ + @NonNull + Builder pipeline(@NonNull Consumer pipeline); + + /** + * Finalizes the registration. + * + * @return the completed registration + */ + @NonNull + Registration register(); + + interface Initial> extends Sided, Bidirectional { + + /** + * {@inheritDoc} + */ + @Override + @NonNull + Initial pipeline(@NonNull Consumer pipeline); + } + + interface Sided> extends Builder { + + /** + * Register a clientbound handler. + */ + @NonNull + Sided clientbound(MessageHandler.@NonNull Sided handler); + + /** + * Register a clientbound handler with a priority. + */ + @NonNull + Sided clientbound(@NonNull MessagePriority priority, MessageHandler.@NonNull Sided handler); + + /** + * Register a serverbound handler. + */ + @NonNull + Sided serverbound(MessageHandler.@NonNull Sided handler); + + /** + * Register a serverbound handler with a priority. + */ + @NonNull + Sided serverbound(@NonNull MessagePriority priority, MessageHandler.@NonNull Sided handler); + + /** + * {@inheritDoc} + */ + @Override + @NonNull + Sided pipeline(@NonNull Consumer pipeline); + } + + interface Bidirectional> extends Builder { + + /** + * Register a bidirectional handler receiving the message and direction. + */ + @NonNull + Bidirectional bidirectional(@NonNull MessageHandler handler); + + /** + * Register a bidirectional handler with a priority. + */ + @NonNull + Bidirectional bidirectional(@NonNull MessagePriority priority, @NonNull MessageHandler handler); + + /** + * {@inheritDoc} + */ + @Override + @NonNull + Bidirectional pipeline(@NonNull Consumer pipeline); + } + + /** + * Pipeline configuration for ordering handlers. + */ + interface Pipeline { + + /** + * Tags this handler in the pipeline. + * + * @param tag the tag to apply + * @return the pipeline instance + */ + @NonNull + Pipeline tag(@NonNull String tag); + + /** + * Places this handler before the handler with the given tag. + *

+ * The tag used here must be previously defined in a separate handler using {@link #tag(String)}. + * However, it should be noted that if the specified tag does not exist at the time of registration, + * the handler will be added to the end of the pipeline without throwing an error. + * + * @param tag the tag to place before + * @return the pipeline instance + */ + @NonNull + Pipeline before(@NonNull String tag); + + /** + * Places this handler after the handler with the given tag. + *

+ * The tag used here must be previously defined in a separate handler using {@link #tag(String)}. + * However, it should be noted that if the specified tag does not exist at the time of registration, + * the handler will be added to the end of the pipeline without throwing an error. + * + * @param tag the tag to place after + * @return the pipeline instance + */ + @NonNull + Pipeline after(@NonNull String tag); + } + } + + public interface Registration> { + } } diff --git a/api/src/main/java/org/geysermc/geyser/api/event/java/ServerReceiveNetworkMessageEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/java/ServerReceiveNetworkMessageEvent.java deleted file mode 100644 index 70035462c..000000000 --- a/api/src/main/java/org/geysermc/geyser/api/event/java/ServerReceiveNetworkMessageEvent.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (c) 2025 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.api.event.java; - -import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.event.Cancellable; -import org.geysermc.geyser.api.connection.GeyserConnection; -import org.geysermc.geyser.api.event.connection.ConnectionEvent; -import org.geysermc.geyser.api.network.MessageDirection; -import org.geysermc.geyser.api.network.NetworkChannel; -import org.geysermc.geyser.api.network.message.Message; - -/** - * Called when Geyser receives a network message from the server. - * @since 2.8.2 - */ -public final class ServerReceiveNetworkMessageEvent extends ConnectionEvent implements Cancellable { - private final NetworkChannel channel; - private final Message message; - private final MessageDirection direction; - private boolean cancelled = false; - - public ServerReceiveNetworkMessageEvent(@NonNull GeyserConnection connection, @NonNull NetworkChannel channel, @NonNull Message message, @NonNull MessageDirection direction) { - super(connection); - - this.channel = channel; - this.message = message; - this.direction = direction; - } - - /** - * Gets the channel that received the message. - *

- * See {@link NetworkChannel} for more information. - * - * @return the channel that received the message - */ - @NonNull - public NetworkChannel channel() { - return this.channel; - } - - /** - * Gets the message that was received. - * - * @return the received message - */ - @NonNull - public Message message() { - return this.message; - } - - /** - * Gets the direction of the message. - * - * @return the direction of the message - */ - @NonNull - public MessageDirection direction() { - return this.direction; - } - - /** - * {@inheritDoc} - */ - @Override - public boolean isCancelled() { - return this.cancelled; - } - - /** - * {@inheritDoc} - */ - @Override - public void setCancelled(boolean cancelled) { - this.cancelled = cancelled; - } -} diff --git a/api/src/main/java/org/geysermc/geyser/api/network/MessageDirection.java b/api/src/main/java/org/geysermc/geyser/api/network/MessageDirection.java index 16d4886b4..bcb569618 100644 --- a/api/src/main/java/org/geysermc/geyser/api/network/MessageDirection.java +++ b/api/src/main/java/org/geysermc/geyser/api/network/MessageDirection.java @@ -27,7 +27,7 @@ package org.geysermc.geyser.api.network; /** * Represents the direction of a message. - * @since 2.8.2 + * @since 2.9.1 */ public enum MessageDirection { /** diff --git a/api/src/main/java/org/geysermc/geyser/api/network/NetworkChannel.java b/api/src/main/java/org/geysermc/geyser/api/network/NetworkChannel.java index 3c627d7eb..c383c378d 100644 --- a/api/src/main/java/org/geysermc/geyser/api/network/NetworkChannel.java +++ b/api/src/main/java/org/geysermc/geyser/api/network/NetworkChannel.java @@ -27,6 +27,7 @@ package org.geysermc.geyser.api.network; import org.checkerframework.checker.index.qual.NonNegative; import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.api.GeyserApi; import org.geysermc.geyser.api.extension.Extension; import org.geysermc.geyser.api.util.Identifier; @@ -67,11 +68,11 @@ import org.geysermc.geyser.api.util.Identifier; * *

* Packet channels can also be registered against packet objects from - * exterbak protocol libraries, such as the ones provided in Geyser. For + * external protocol libraries, such as the ones provided in Geyser. For * an example on how to do this, please see the * Networking API documentation. * - * @since 2.8.2 + * @since 2.9.1 */ public interface NetworkChannel { @@ -91,58 +92,61 @@ public interface NetworkChannel { boolean isPacket(); /** - * Creates a new {@link NetworkChannel} instance. + * Creates a new external {@link NetworkChannel} instance. *

* Extensions should use this method to register * their own channels for more robust identification. * * @param extension the extension that registered this channel * @param channel the name of the channel - * @return a new {@link NetworkChannel} instance + * @param messageType the type of the message sent over this channel + * @return a new external {@link NetworkChannel} instance */ @NonNull - static NetworkChannel of(@NonNull Extension extension, @NonNull String channel) { - return new ExtensionNetworkChannel(extension, channel); + static NetworkChannel of(@NonNull Extension extension, @NonNull String channel, @NonNull Class messageType) { + return GeyserApi.api().provider(NetworkChannel.class, extension, channel, messageType); } /** - * Creates a new {@link NetworkChannel} instance. + * Creates a new external {@link NetworkChannel} instance. *

* This method is used for external channels provided * by third parties, such as plugins or mods. * * @param id the channel id * @param channel the name of the channel - * @return a new {@link NetworkChannel} instance + * @param messageType the type of the message sent over this channel + * @return a new external {@link NetworkChannel} instance */ @NonNull - static NetworkChannel of(@NonNull String id, @NonNull String channel) { - return of(Identifier.of(id, channel)); + static NetworkChannel of(@NonNull String id, @NonNull String channel, @NonNull Class messageType) { + return of(Identifier.of(id, channel), messageType); } /** - * Creates a new {@link NetworkChannel} instance. + * Creates a new external {@link NetworkChannel} instance. *

* This method is used for external channels provided * by third parties, such as plugins or mods. * * @param identifier the {@link Identifier} of the channel - * @return a new {@link NetworkChannel} instance + * @param messageType the type of the message sent over this channel + * @return a new external {@link NetworkChannel} instance */ @NonNull - static NetworkChannel of(@NonNull Identifier identifier) { - return new ExternalNetworkChannel(identifier); + static NetworkChannel of(@NonNull Identifier identifier, @NonNull Class messageType) { + return GeyserApi.api().provider(NetworkChannel.class, identifier, messageType); } /** - * Creates a new {@link PacketChannel} instance for a packet channel. + * Creates a new packet {@link NetworkChannel} instance for a packet channel. * * @param key the packet key * @param packetId the packet ID * @param packetType the type of the packet - * @return a new {@link PacketChannel} instance for a packet channel + * @return a new packet {@link NetworkChannel} instance for a packet channel */ static NetworkChannel packet(@NonNull String key, @NonNegative int packetId, @NonNull Class packetType) { - return new PacketChannel(key, packetId, packetType); + return GeyserApi.api().provider(NetworkChannel.class, key, packetId, packetType); } } diff --git a/api/src/main/java/org/geysermc/geyser/api/network/NetworkManager.java b/api/src/main/java/org/geysermc/geyser/api/network/NetworkManager.java index 9aa291155..76eec94fd 100644 --- a/api/src/main/java/org/geysermc/geyser/api/network/NetworkManager.java +++ b/api/src/main/java/org/geysermc/geyser/api/network/NetworkManager.java @@ -36,7 +36,7 @@ import java.util.Set; * Represents the network manager responsible for handling network operations * for a {@link GeyserConnection}. * - * @since 2.8.2 + * @since 2.9.1 */ public interface NetworkManager { diff --git a/api/src/main/java/org/geysermc/geyser/api/network/message/DataType.java b/api/src/main/java/org/geysermc/geyser/api/network/message/DataType.java index 6bbd86753..a6933ed05 100644 --- a/api/src/main/java/org/geysermc/geyser/api/network/message/DataType.java +++ b/api/src/main/java/org/geysermc/geyser/api/network/message/DataType.java @@ -35,7 +35,7 @@ import java.util.Optional; * Represents a data type that can be sent or received over the network. * * @param the type - * @since 2.8.2 + * @since 2.9.1 */ public final class DataType { /** diff --git a/api/src/main/java/org/geysermc/geyser/api/network/message/Message.java b/api/src/main/java/org/geysermc/geyser/api/network/message/Message.java index e09e43b7d..2cc81fd2c 100644 --- a/api/src/main/java/org/geysermc/geyser/api/network/message/Message.java +++ b/api/src/main/java/org/geysermc/geyser/api/network/message/Message.java @@ -33,7 +33,7 @@ import java.util.function.Supplier; /** * Represents a message that can be sent over the network. - * @since 2.8.2 + * @since 2.9.1 */ public interface Message { @@ -69,8 +69,10 @@ public interface Message { * @param packet the packet object to create the message from * @return a new packet message */ - static PacketWrapped of(@NonNull Object packet) { - return GeyserApi.api().provider(PacketWrapped.class, packet); + @SuppressWarnings("unchecked") + @NonNull + static PacketWrapped of(@NonNull Object packet) { + return (PacketWrapped) GeyserApi.api().provider(PacketWrapped.class, packet); } /** @@ -80,7 +82,7 @@ public interface Message { * @return a new packet message */ @NonNull - static MessageFactory of(@NonNull Supplier packetSupplier) { + static MessageFactory> of(@NonNull Supplier

packetSupplier) { return buffer -> of(packetSupplier.get()); } @@ -92,7 +94,7 @@ public interface Message { * @return a new packet message factory */ @NonNull - static MessageFactory of(@NonNull Function substitutor, @NonNull Function packetSupplier) { + static MessageFactory> of(@NonNull Function substitutor, @NonNull Function packetSupplier) { return buffer -> of(packetSupplier.apply(substitutor.apply(buffer))); } } @@ -102,7 +104,7 @@ public interface Message { * * @param the type of message buffer */ - interface PacketWrapped extends PacketBase { + interface PacketWrapped extends PacketBase { /** * Gets the packet associated with this message. @@ -110,6 +112,6 @@ public interface Message { * @return the packet */ @NonNull - Object packet(); + P packet(); } } diff --git a/api/src/main/java/org/geysermc/geyser/api/network/message/MessageBuffer.java b/api/src/main/java/org/geysermc/geyser/api/network/message/MessageBuffer.java index 7b47ecaf1..678bbbbcc 100644 --- a/api/src/main/java/org/geysermc/geyser/api/network/message/MessageBuffer.java +++ b/api/src/main/java/org/geysermc/geyser/api/network/message/MessageBuffer.java @@ -29,7 +29,7 @@ import org.checkerframework.checker.nullness.qual.NonNull; /** * A buffer for messages that can be sent over the network. - * @since 2.8.2 + * @since 2.9.1 */ public interface MessageBuffer { diff --git a/api/src/main/java/org/geysermc/geyser/api/network/message/MessageCodec.java b/api/src/main/java/org/geysermc/geyser/api/network/message/MessageCodec.java index b3f43c335..8e720b313 100644 --- a/api/src/main/java/org/geysermc/geyser/api/network/message/MessageCodec.java +++ b/api/src/main/java/org/geysermc/geyser/api/network/message/MessageCodec.java @@ -31,7 +31,7 @@ import org.checkerframework.checker.nullness.qual.NonNull; * A codec for encoding and decoding messages. * * @param the type of {@link MessageBuffer} used for encoding and decoding - * @since 2.8.2 + * @since 2.9.1 */ public interface MessageCodec { diff --git a/api/src/main/java/org/geysermc/geyser/api/network/message/MessageFactory.java b/api/src/main/java/org/geysermc/geyser/api/network/message/MessageFactory.java index 3d205823a..14c3c7bb3 100644 --- a/api/src/main/java/org/geysermc/geyser/api/network/message/MessageFactory.java +++ b/api/src/main/java/org/geysermc/geyser/api/network/message/MessageFactory.java @@ -31,10 +31,11 @@ import org.checkerframework.checker.nullness.qual.NonNull; * A factory interface for creating messages from a given message buffer. * * @param the type of the message buffer - * @since 2.8.2 + * @param the type of the message + * @since 2.9.1 */ @FunctionalInterface -public interface MessageFactory { +public interface MessageFactory> { /** * Creates a new message from the provided buffer. @@ -43,5 +44,5 @@ public interface MessageFactory { * @return a new message created from the buffer */ @NonNull - Message create(@NonNull T buffer); + M create(@NonNull T buffer); } diff --git a/api/src/main/java/org/geysermc/geyser/api/network/message/MessageHandler.java b/api/src/main/java/org/geysermc/geyser/api/network/message/MessageHandler.java new file mode 100644 index 000000000..c5fb760ee --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/network/message/MessageHandler.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2025 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api.network.message; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.api.network.MessageDirection; + +/** + * Represents a handler for processing messages. + * + * @param the type of message to handle + */ +@FunctionalInterface +public interface MessageHandler> { + + /** + * Handles the given message in the specified direction. + * + * @param message the message to handle + * @param direction the direction of the message + * @return the state after handling the message + */ + @NonNull + State handle(@NonNull T message, @NonNull MessageDirection direction); + + /** + * A message handler that belongs to a specific side (clientbound or serverbound). + * + * @param the type of message to handle + */ + interface Sided> { + + /** + * Handles the given message in the specified direction. + * + * @param message the message to handle + * @return the state after handling the message + */ + @NonNull + State handle(@NonNull T message); + } + + /** + * Represents the state after handling a message. + */ + enum State { + /** + * The message was handled and should not be processed further. + */ + HANDLED, + /** + * The message was not handled and should be passed through for further processing. + */ + UNHANDLED, + /** + * Indicates that the message has been modified but should still be + * passed through to the next handler or processing step. + */ + MODIFIED + } +} diff --git a/api/src/main/java/org/geysermc/geyser/api/network/message/MessagePriority.java b/api/src/main/java/org/geysermc/geyser/api/network/message/MessagePriority.java new file mode 100644 index 000000000..de5dfe1a2 --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/network/message/MessagePriority.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2025 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api.network.message; + +import org.checkerframework.checker.nullness.qual.NonNull; + +/** + * Represents the priority of a message when being processed. + * @since 2.9.1 + */ +public enum MessagePriority { + FIRST(100), + EARLY(50), + NORMAL(0), + LATE(-50), + LAST(-100); + + private final int value; + + MessagePriority(int value) { + this.value = value; + } + + /** + * Gets the numeric value associated with this priority. Higher means earlier. + * + * @return the priority value + */ + public int value() { + return value; + } + + /** + * Creates a custom priority in the range [-100, 100]. + * + * @param value the priority value + * @return the priority + * @throws IllegalArgumentException if outside allowed range + */ + @NonNull + public static MessagePriority of(int value) { + if (value >= 75) return LAST; + if (value >= 25) return LATE; + if (value <= -75) return FIRST; + if (value <= -25) return EARLY; + return NORMAL; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/network/BaseNetworkChannel.java b/core/src/main/java/org/geysermc/geyser/network/BaseNetworkChannel.java new file mode 100644 index 000000000..e2f26c00b --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/network/BaseNetworkChannel.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2025 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.network; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.api.network.NetworkChannel; + +public abstract class BaseNetworkChannel implements NetworkChannel { + private final Class messageType; + + public BaseNetworkChannel(Class messageType) { + this.messageType = messageType; + } + + /** + * Gets the type of message this channel handles. + * + * @return the message type + */ + @NonNull + public Class messageType() { + return messageType; + } +} diff --git a/api/src/main/java/org/geysermc/geyser/api/network/ExtensionNetworkChannel.java b/core/src/main/java/org/geysermc/geyser/network/ExtensionNetworkChannel.java similarity index 76% rename from api/src/main/java/org/geysermc/geyser/api/network/ExtensionNetworkChannel.java rename to core/src/main/java/org/geysermc/geyser/network/ExtensionNetworkChannel.java index 9cc3413d8..5afe0d4b1 100644 --- a/api/src/main/java/org/geysermc/geyser/api/network/ExtensionNetworkChannel.java +++ b/core/src/main/java/org/geysermc/geyser/network/ExtensionNetworkChannel.java @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.geyser.api.network; +package org.geysermc.geyser.network; import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.api.extension.Extension; @@ -33,13 +33,14 @@ import java.util.Objects; /** * Represents a network channel associated with an extension. - * @since 2.8.2 */ -public class ExtensionNetworkChannel implements NetworkChannel { +public class ExtensionNetworkChannel extends BaseNetworkChannel { private final Extension extension; private final String channel; - protected ExtensionNetworkChannel(@NonNull Extension extension, @NonNull String channel) { + public ExtensionNetworkChannel(@NonNull Extension extension, @NonNull String channel, @NonNull Class messageType) { + super(messageType); + this.extension = extension; this.channel = channel; } @@ -63,14 +64,14 @@ public class ExtensionNetworkChannel implements NetworkChannel { @Override public boolean equals(Object o) { - if (o == null || !NetworkChannel.class.isAssignableFrom(o.getClass())) return false; - NetworkChannel that = (NetworkChannel) o; - return Objects.equals(this.identifier(), that.identifier()); + if (o == null || getClass() != o.getClass()) return false; + ExtensionNetworkChannel that = (ExtensionNetworkChannel) o; + return Objects.equals(this.extension, that.extension) && Objects.equals(this.channel, that.channel) && Objects.equals(this.messageType(), that.messageType()); } @Override public int hashCode() { - return Objects.hash(this.identifier()); + return Objects.hash(this.identifier(), this.messageType()); } @Override @@ -78,6 +79,7 @@ public class ExtensionNetworkChannel implements NetworkChannel { return "ExtensionNetworkChannel{" + "extension=" + this.extension.description().id() + ", channel='" + this.channel + '\'' + + ", messageType=" + this.messageType() + '}'; } } diff --git a/api/src/main/java/org/geysermc/geyser/api/network/ExternalNetworkChannel.java b/core/src/main/java/org/geysermc/geyser/network/ExternalNetworkChannel.java similarity index 77% rename from api/src/main/java/org/geysermc/geyser/api/network/ExternalNetworkChannel.java rename to core/src/main/java/org/geysermc/geyser/network/ExternalNetworkChannel.java index 479727a15..bce86b108 100644 --- a/api/src/main/java/org/geysermc/geyser/api/network/ExternalNetworkChannel.java +++ b/core/src/main/java/org/geysermc/geyser/network/ExternalNetworkChannel.java @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.geyser.api.network; +package org.geysermc.geyser.network; import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.api.util.Identifier; @@ -34,12 +34,13 @@ import java.util.Objects; * Represents a network channel not associated with any specific extension. *

* This can be used for external communication channels, like mods or plugins. - * @since 2.8.2 */ -public class ExternalNetworkChannel implements NetworkChannel { +public class ExternalNetworkChannel extends BaseNetworkChannel { private final Identifier identifier; - protected ExternalNetworkChannel(@NonNull Identifier identifier) { + public ExternalNetworkChannel(@NonNull Identifier identifier, @NonNull Class messageType) { + super(messageType); + this.identifier = identifier; } @@ -62,20 +63,21 @@ public class ExternalNetworkChannel implements NetworkChannel { @Override public boolean equals(Object o) { - if (o == null || !NetworkChannel.class.isAssignableFrom(o.getClass())) return false; - NetworkChannel that = (NetworkChannel) o; - return Objects.equals(this.identifier(), that.identifier()); + if (o == null || getClass() != o.getClass()) return false; + ExternalNetworkChannel that = (ExternalNetworkChannel) o; + return Objects.equals(this.identifier, that.identifier) && Objects.equals(this.messageType(), that.messageType()); } @Override public int hashCode() { - return Objects.hash(this.identifier()); + return Objects.hash(this.identifier(), this.messageType()); } @Override public String toString() { return "ExternalNetworkChannel{" + "identifier='" + this.identifier + '\'' + + ", messageType=" + this.messageType() + '}'; } } diff --git a/core/src/main/java/org/geysermc/geyser/network/GeyserNetworkManager.java b/core/src/main/java/org/geysermc/geyser/network/GeyserNetworkManager.java index 312958060..5b795229f 100644 --- a/core/src/main/java/org/geysermc/geyser/network/GeyserNetworkManager.java +++ b/core/src/main/java/org/geysermc/geyser/network/GeyserNetworkManager.java @@ -31,30 +31,33 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import net.kyori.adventure.key.Key; import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; import org.cloudburstmc.protocol.bedrock.codec.BedrockCodec; import org.cloudburstmc.protocol.bedrock.codec.BedrockCodecHelper; import org.cloudburstmc.protocol.bedrock.codec.BedrockPacketDefinition; import org.cloudburstmc.protocol.bedrock.packet.BedrockPacket; -import org.geysermc.geyser.api.GeyserApi; +import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.event.bedrock.SessionDefineNetworkChannelsEvent; -import org.geysermc.geyser.api.event.java.ServerReceiveNetworkMessageEvent; import org.geysermc.geyser.api.network.MessageDirection; import org.geysermc.geyser.api.network.NetworkChannel; import org.geysermc.geyser.api.network.NetworkManager; -import org.geysermc.geyser.api.network.PacketChannel; import org.geysermc.geyser.api.network.message.Message; import org.geysermc.geyser.api.network.message.MessageBuffer; import org.geysermc.geyser.api.network.message.MessageCodec; import org.geysermc.geyser.api.network.message.MessageFactory; +import org.geysermc.geyser.api.network.message.MessageHandler; import org.geysermc.geyser.network.message.BedrockPacketMessage; import org.geysermc.geyser.network.message.ByteBufCodec; import org.geysermc.geyser.network.message.ByteBufMessageBuffer; import org.geysermc.geyser.network.message.JavaPacketMessage; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.mcprotocollib.protocol.packet.common.serverbound.ServerboundCustomPayloadPacket; -import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.VisibleForTesting; -import java.util.HashMap; +import javax.annotation.Nonnull; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.Function; @@ -62,7 +65,7 @@ import java.util.function.Function; public class GeyserNetworkManager implements NetworkManager { private final GeyserSession session; - private final Map> definitions = new HashMap<>(); + private final Map>> definitions = new LinkedHashMap<>(); private final Int2ObjectMap packetChannels = new Int2ObjectOpenHashMap<>(); public GeyserNetworkManager(GeyserSession session) { @@ -71,31 +74,77 @@ public class GeyserNetworkManager implements NetworkManager { SessionDefineNetworkChannelsEvent event = new SessionDefineNetworkChannelsEvent(session) { @Override - public void register(@NonNull NetworkChannel channel, @NonNull MessageFactory messageFactory) { - GeyserNetworkManager.this.registerMessage(channel, new MessageDefinition<>(ByteBufCodec.INSTANCE, messageFactory)); + public > Builder.@NonNull Initial define(@NonNull NetworkChannel channel, @NonNull MessageFactory messageFactory) { + return new NetworkDefinitionBuilder<>(registration -> onRegister(channel, ByteBufCodec.INSTANCE, messageFactory, registration)); } @Override - public void register(@NonNull NetworkChannel channel, @NonNull MessageCodec codec, @NonNull MessageFactory messageFactory) { - GeyserNetworkManager.this.registerMessage(channel, new MessageDefinition<>(codec, messageFactory)); + public > Builder.@NonNull Initial define(@NonNull NetworkChannel channel, @NonNull MessageCodec codec, @NonNull MessageFactory messageFactory) { + return new NetworkDefinitionBuilder<>(registration -> onRegister(channel, codec, messageFactory, registration)); } }; - GeyserApi.api().eventBus().fire(event); + GeyserImpl.getInstance().getEventBus().fire(event); } - @Override - public @NonNull Set registeredChannels() { - return Set.copyOf(this.definitions.keySet()); + @VisibleForTesting + > void onRegister(@NonNull NetworkChannel channel, @NonNull MessageFactory messageFactory, NetworkDefinitionBuilder.@NonNull RegistrationImpl registration) { + this.onRegister(channel, ByteBufCodec.INSTANCE, messageFactory, registration); } @SuppressWarnings("unchecked") + private > void onRegister(@NonNull NetworkChannel channel, @NonNull MessageCodec codec, + @NonNull MessageFactory messageFactory, NetworkDefinitionBuilder.@NonNull RegistrationImpl registration) { + MessageHandler handler; + int priority; + + NetworkDefinitionBuilder.HandlerEntry bidirectional = registration.handler(); + NetworkDefinitionBuilder.SidedHandlerEntry clientbound = registration.clientbound(); + NetworkDefinitionBuilder.SidedHandlerEntry serverbound = registration.serverbound(); + + if (bidirectional != null) { + handler = bidirectional.handler(); + priority = bidirectional.priority() != null ? bidirectional.priority().value() : 0; + } else { + handler = null; + + int cbPriority = clientbound != null && clientbound.priority() != null ? clientbound.priority().value() : Integer.MIN_VALUE; + int sbPriority = serverbound != null && serverbound.priority() != null ? serverbound.priority().value() : Integer.MIN_VALUE; + priority = Math.max(cbPriority, sbPriority); + + if (priority == Integer.MIN_VALUE) { + priority = 0; + } + } + + MessageDefinition definition = new MessageDefinition<>((MessageCodec) codec, + messageFactory, + handler, + clientbound != null ? clientbound.handler() : null, + serverbound != null ? serverbound.handler() : null, + priority, + clientbound != null && clientbound.priority() != null ? clientbound.priority().value() : null, + serverbound != null && serverbound.priority() != null ? serverbound.priority().value() : null, + registration.tag(), + registration.beforeTag(), + registration.afterTag() + ); + + this.registerMessage(channel, definition); + } + + @Override + @Nonnull + public Set registeredChannels() { + return Set.copyOf(this.definitions.keySet()); + } + @Override public void send(@NonNull NetworkChannel channel, @NonNull Message message, @NonNull MessageDirection direction) { if (channel.isPacket() && message instanceof Message.PacketBase packetBase) { - if (packetBase instanceof BedrockPacketMessage packetMessage) { + if (packetBase instanceof BedrockPacketMessage packetMessage) { this.session.sendUpstreamPacket(packetMessage.packet()); - } else if (packetBase instanceof JavaPacketMessage packetMessage) { + } else if (packetBase instanceof JavaPacketMessage packetMessage) { this.session.sendDownstreamPacket(packetMessage.packet()); } else if (packetBase instanceof Message.Packet packet) { PacketChannel packetChannel = (PacketChannel) channel; @@ -123,10 +172,7 @@ public class GeyserNetworkManager implements NetworkManager { return; } - MessageDefinition definition = (MessageDefinition) this.definitions.get(channel); - if (definition == null) { - throw new IllegalArgumentException("No message definition registered for channel: " + channel); - } + MessageDefinition> definition = this.findMessageDefinition(channel, message); T buffer = definition.codec.createBuffer(); message.encode(buffer); @@ -139,29 +185,40 @@ public class GeyserNetworkManager implements NetworkManager { this.session.sendDownstreamPacket(packet); } - public Message createMessage(@NonNull NetworkChannel channel, byte @NotNull[] data) { - return this.createMessage0(channel, definition -> definition.createBuffer(data)); + @NonNull + public List> createMessages(@NonNull NetworkChannel channel, byte @NonNull[] data) { + return this.createMessages0(channel, definition -> definition.createBuffer(data)); } - public Message createMessage(@NonNull NetworkChannel channel, @NonNull T buffer) { - return this.createMessage0(channel, def -> buffer); + @NonNull + public List> createMessages(@NonNull NetworkChannel channel, @NonNull T buffer) { + return this.createMessages0(channel, def -> buffer); } @SuppressWarnings("unchecked") - private Message createMessage0(@NonNull NetworkChannel channel, @NonNull Function, T> creator) { - MessageDefinition definition = (MessageDefinition) this.definitions.get(channel); - if (definition == null) { + @NonNull + private > List createMessages0(@NonNull NetworkChannel channel, @NonNull Function, T> creator) { + List> definitions = this.definitions.get(channel); + if (definitions == null || definitions.isEmpty()) { throw new IllegalArgumentException("No message definition registered for channel: " + channel); } - T buffer = creator.apply(definition); - Message message = definition.createMessage(buffer); - if (message instanceof BedrockPacketMessage packetMessage) { - packetMessage.postProcess(this.session, (ByteBufMessageBuffer) buffer); + List messages = new ArrayList<>(); + for (MessageDefinition def : definitions) { + MessageDefinition definition = (MessageDefinition) def; + T buffer = creator.apply(definition); + M message = definition.createMessage(buffer); + if (message instanceof BedrockPacketMessage packetMessage) { + packetMessage.postProcess(this.session, (ByteBufMessageBuffer) buffer); + } + + messages.add(message); } - return message; + + return messages; } + @Nullable public PacketChannel getPacketChannel(int packetId) { return this.packetChannels.get(packetId); } @@ -179,42 +236,184 @@ public class GeyserNetworkManager implements NetworkManager { return true; } - Message message; - if (channel.packetType().isInstance(packet)) { - message = new BedrockPacketMessage(packet); + List> messages; + if (channel.messageType().isInstance(packet)) { + messages = List.of(new BedrockPacketMessage<>(packet)); } else { ByteBuf buffer = Unpooled.buffer(); definition.getSerializer().serialize(buffer, this.session.getUpstream().getCodecHelper(), packet); - message = this.createMessage(channel, new ByteBufMessageBuffer(ByteBufCodec.INSTANCE_LE, buffer)); + messages = this.createMessages(channel, new ByteBufMessageBuffer(ByteBufCodec.INSTANCE_LE, buffer)); } - ServerReceiveNetworkMessageEvent event = new ServerReceiveNetworkMessageEvent(this.session, channel, message, direction); - this.session.getGeyser().eventBus().fire(event); - - // If the event is canceled, we do not want to process the packet further - return !event.isCancelled(); + return this.handleMessages(channel, messages, direction); } - private void registerMessage(@NonNull NetworkChannel channel, @NonNull MessageDefinition codec) { - if (this.definitions.containsKey(channel)) { - throw new IllegalArgumentException("Channel is already registered: " + channel); + @SuppressWarnings("unchecked") + public boolean handleMessages(@NonNull NetworkChannel channel, @NonNull List> messages, @NonNull MessageDirection direction) { + List> rawList = this.definitions.get(channel); + if (rawList == null || rawList.isEmpty()) { + return true; } - this.definitions.put(channel, codec); + // Build a direction-aware ordered list while preserving pipeline tag anchors + List> ordered = new ArrayList<>(); + List> unpinnedBlock = new ArrayList<>(); + for (MessageDefinition def : rawList) { + boolean pinned = def.tag() != null || def.beforeTag() != null || def.afterTag() != null; + if (pinned) { + // flush any accumulated unpinned block sorted by effective priority for this direction + if (!unpinnedBlock.isEmpty()) { + unpinnedBlock.sort((a, b) -> Integer.compare( + b.priority(direction), + a.priority(direction) + )); + ordered.addAll(unpinnedBlock); + unpinnedBlock.clear(); + } + ordered.add(def); + } else { + unpinnedBlock.add(def); + } + } + if (!unpinnedBlock.isEmpty()) { + unpinnedBlock.sort((a, b) -> Integer.compare( + b.priority(direction), + a.priority(direction) + )); + ordered.addAll(unpinnedBlock); + unpinnedBlock.clear(); + } + + for (Message message : messages) { + for (MessageDefinition def : ordered) { + if (!(channel instanceof BaseNetworkChannel base) || !base.messageType().isInstance(message)) { + continue; + } + + MessageDefinition> definition = (MessageDefinition>) def; + + MessageHandler.State state; + if (definition.handler != null) { + state = definition.handler.handle(message, direction); + } else if (direction == MessageDirection.CLIENTBOUND && definition.clientboundHandler != null) { + state = definition.clientboundHandler.handle(message); + } else if (direction == MessageDirection.SERVERBOUND && definition.serverboundHandler != null) { + state = definition.serverboundHandler.handle(message); + } else { + continue; // no suitable handler; try next definition + } + + if (state == MessageHandler.State.HANDLED) { + return false; + } + } + } + + return true; + } + + @SuppressWarnings("unchecked") + @NonNull + private > MessageDefinition findMessageDefinition(@NonNull NetworkChannel channel, @NonNull Message message) { + List> definitions = this.definitions.get(channel); + if (definitions == null || definitions.isEmpty()) { + throw new IllegalArgumentException("No message definition registered for channel: " + channel); + } + + MessageDefinition> definition = null; + for (MessageDefinition def : definitions) { + if (channel instanceof BaseNetworkChannel baseChannel) { + if (baseChannel.messageType().isInstance(message)) { + definition = (MessageDefinition>) def; + break; + } + } + } + + if (definition == null) { + throw new IllegalArgumentException("No suitable message definition found for channel: " + channel + " and message type: " + message.getClass()); + } + + return (MessageDefinition) definition; + } + + private > void registerMessage(@NonNull NetworkChannel channel, @NonNull MessageDefinition definition) { + List> list = this.definitions.computeIfAbsent(channel, key -> new ArrayList<>()); + + // Determine the insert position based on pipeline tags or priority + int insertIndex = -1; + if (definition.beforeTag() != null) { + for (int i = 0; i < list.size(); i++) { + MessageDefinition existing = list.get(i); + if (definition.beforeTag().equals(existing.tag())) { + insertIndex = i; + break; + } + } + } else if (definition.afterTag() != null) { + for (int i = 0; i < list.size(); i++) { + MessageDefinition existing = list.get(i); + if (definition.afterTag().equals(existing.tag())) { + insertIndex = i + 1; + break; + } + } + } + + if (insertIndex == -1) { + // Fallback: insert by descending priority + insertIndex = list.size(); + for (int i = 0; i < list.size(); i++) { + MessageDefinition existing = list.get(i); + if (definition.priority() > existing.priority()) { + insertIndex = i; + break; + } + } + } + + list.add(insertIndex, definition); + if (channel.isPacket() && channel instanceof PacketChannel packetChannel) { int packetId = packetChannel.packetId(); this.packetChannels.put(packetId, packetChannel); } } - public record MessageDefinition(MessageCodec codec, MessageFactory messageFactory) { + public record MessageDefinition>( + @NonNull MessageCodec codec, + @NonNull MessageFactory messageFactory, + @Nullable MessageHandler handler, + MessageHandler.Sided clientboundHandler, + MessageHandler.Sided serverboundHandler, + int priority, + @Nullable Integer clientboundPriority, + @Nullable Integer serverboundPriority, + @Nullable String tag, + @Nullable String beforeTag, + @Nullable String afterTag + ) { - public T createBuffer(byte @NotNull[] data) { + @NonNull + public T createBuffer(byte @NonNull[] data) { return this.codec.createBuffer(data); } - public Message createMessage(@NonNull T buffer) { + @NonNull + public M createMessage(@NonNull T buffer) { return this.messageFactory.create(buffer); } + + public int priority(@NonNull MessageDirection direction) { + if (this.handler != null) { + return this.priority; + } + + if (direction == MessageDirection.CLIENTBOUND) { + return this.clientboundPriority != null ? this.clientboundPriority : 0; + } + + return this.serverboundPriority != null ? this.serverboundPriority : 0; + } } } diff --git a/core/src/main/java/org/geysermc/geyser/network/NetworkDefinitionBuilder.java b/core/src/main/java/org/geysermc/geyser/network/NetworkDefinitionBuilder.java new file mode 100644 index 000000000..be6596733 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/network/NetworkDefinitionBuilder.java @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2025 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.network; + +import com.google.common.base.Preconditions; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.api.event.bedrock.SessionDefineNetworkChannelsEvent; +import org.geysermc.geyser.api.network.message.Message; +import org.geysermc.geyser.api.network.message.MessageBuffer; +import org.geysermc.geyser.api.network.message.MessageHandler; +import org.geysermc.geyser.api.network.message.MessagePriority; + +import java.util.Objects; +import java.util.function.Consumer; + +public class NetworkDefinitionBuilder> implements SessionDefineNetworkChannelsEvent.Builder.Initial { + private HandlerEntry handler; + private SidedHandlerEntry clientbound; + private SidedHandlerEntry serverbound; + + private String tag; + private String beforeTag; + private String afterTag; + + private final Consumer> registrationCallback; + private boolean registered; + + public NetworkDefinitionBuilder(@NonNull Consumer> registrationCallback) { + this.registrationCallback = registrationCallback; + } + + public SessionDefineNetworkChannelsEvent.Builder.@NonNull Initial pipeline(@NonNull Consumer pipeline) { + Objects.requireNonNull(pipeline, "pipeline"); + PipelineImpl impl = new PipelineImpl(); + pipeline.accept(impl); + this.tag = impl.tag; + this.beforeTag = impl.beforeTag; + this.afterTag = impl.afterTag; + return this; + } + + @Override + public SessionDefineNetworkChannelsEvent.Builder.@NonNull Sided clientbound(MessageHandler.@NonNull Sided handler) { + return this.clientbound(MessagePriority.NORMAL, handler); + } + + @Override + public SessionDefineNetworkChannelsEvent.Builder.@NonNull Sided clientbound(@NonNull MessagePriority priority, MessageHandler.@NonNull Sided handler) { + Objects.requireNonNull(priority, "priority"); + Objects.requireNonNull(handler, "handler"); + this.clientbound = new SidedHandlerEntry<>(priority, handler); + return this; + } + + @Override + public SessionDefineNetworkChannelsEvent.Builder.@NonNull Sided serverbound(MessageHandler.@NonNull Sided handler) { + return this.serverbound(MessagePriority.NORMAL, handler); + } + + @Override + public SessionDefineNetworkChannelsEvent.Builder.@NonNull Sided serverbound(@NonNull MessagePriority priority, MessageHandler.@NonNull Sided handler) { + Objects.requireNonNull(priority, "priority"); + Objects.requireNonNull(handler, "handler"); + this.serverbound = new SidedHandlerEntry<>(priority, handler); + return this; + } + + @Override + public SessionDefineNetworkChannelsEvent.Builder.@NonNull Bidirectional bidirectional(@NonNull MessageHandler handler) { + return this.bidirectional(MessagePriority.NORMAL, handler); + } + + @Override + public SessionDefineNetworkChannelsEvent.Builder.@NonNull Bidirectional bidirectional(@NonNull MessagePriority priority, @NonNull MessageHandler handler) { + Objects.requireNonNull(priority, "priority"); + Objects.requireNonNull(handler, "handler"); + this.handler = new HandlerEntry<>(priority, handler); + return this; + } + + @Override + public SessionDefineNetworkChannelsEvent.@NonNull Registration register() { + Preconditions.checkState(!this.registered, "This message has already been registered"); + Preconditions.checkState(this.handler == null || (this.clientbound == null && this.serverbound == null), "Cannot register both bidirectional and sided handlers for the same message"); + RegistrationImpl registration = new RegistrationImpl<>( + this.handler, + this.clientbound, + this.serverbound, + this.tag, + this.beforeTag, + this.afterTag + ); + + this.registered = true; + this.registrationCallback.accept(registration); + return registration; + } + + private static final class PipelineImpl implements SessionDefineNetworkChannelsEvent.Builder.Pipeline { + private String tag; + private String beforeTag; + private String afterTag; + + @Override + public SessionDefineNetworkChannelsEvent.Builder.@NonNull Pipeline tag(@NonNull String tag) { + this.tag = Objects.requireNonNull(tag, "tag"); + return this; + } + + @Override + public SessionDefineNetworkChannelsEvent.Builder.@NonNull Pipeline before(@NonNull String tag) { + this.beforeTag = Objects.requireNonNull(tag, "tag"); + return this; + } + + @Override + public SessionDefineNetworkChannelsEvent.Builder.@NonNull Pipeline after(@NonNull String tag) { + this.afterTag = Objects.requireNonNull(tag, "tag"); + return this; + } + } + + public record RegistrationImpl>( + HandlerEntry handler, + SidedHandlerEntry clientbound, + SidedHandlerEntry serverbound, + String tag, + String beforeTag, + String afterTag + ) implements SessionDefineNetworkChannelsEvent.Registration { + } + + public record SidedHandlerEntry>( + MessagePriority priority, + MessageHandler.Sided handler + ) { + } + + public record HandlerEntry>( + MessagePriority priority, + MessageHandler handler + ) { + } +} diff --git a/api/src/main/java/org/geysermc/geyser/api/network/PacketChannel.java b/core/src/main/java/org/geysermc/geyser/network/PacketChannel.java similarity index 82% rename from api/src/main/java/org/geysermc/geyser/api/network/PacketChannel.java rename to core/src/main/java/org/geysermc/geyser/network/PacketChannel.java index 8067cc59d..9c8fc25d9 100644 --- a/api/src/main/java/org/geysermc/geyser/api/network/PacketChannel.java +++ b/core/src/main/java/org/geysermc/geyser/network/PacketChannel.java @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.geyser.api.network; +package org.geysermc.geyser.network; import org.checkerframework.checker.index.qual.NonNegative; import org.checkerframework.checker.nullness.qual.NonNull; @@ -37,19 +37,16 @@ import java.util.Objects; * This channel is used for listening to communication over * packets between the server and client and can be used to * send or receive packets. - * @since 2.8.2 */ public class PacketChannel extends ExternalNetworkChannel { private static final String PACKET_CHANNEL_KEY = "packet"; private final int packetId; - private final Class packetType; - protected PacketChannel(@NonNull String key, @NonNegative int packetId, @NonNull Class packetType) { - super(Identifier.of(PACKET_CHANNEL_KEY, key)); + public PacketChannel(@NonNull String key, @NonNegative int packetId, @NonNull Class packetType) { + super(Identifier.of(PACKET_CHANNEL_KEY, key), packetType); this.packetId = packetId; - this.packetType = packetType; } /** @@ -62,16 +59,6 @@ public class PacketChannel extends ExternalNetworkChannel { return this.packetId; } - /** - * Gets the type of the packet associated with this channel. - * - * @return the class of the packet type - */ - @NonNull - public Class packetType() { - return this.packetType; - } - /** * {@inheritDoc} */ diff --git a/core/src/main/java/org/geysermc/geyser/network/message/BedrockPacketMessage.java b/core/src/main/java/org/geysermc/geyser/network/message/BedrockPacketMessage.java index 3a09d648a..744231f31 100644 --- a/core/src/main/java/org/geysermc/geyser/network/message/BedrockPacketMessage.java +++ b/core/src/main/java/org/geysermc/geyser/network/message/BedrockPacketMessage.java @@ -33,7 +33,7 @@ import org.cloudburstmc.protocol.bedrock.packet.BedrockPacket; import org.geysermc.geyser.api.network.message.Message; import org.geysermc.geyser.session.GeyserSession; -public record BedrockPacketMessage(@NonNull BedrockPacket packet) implements Message.PacketWrapped { +public record BedrockPacketMessage(@NonNull T packet) implements Message.PacketWrapped { @SuppressWarnings("unchecked") public void postProcess(@NonNull GeyserSession session, @NonNull ByteBufMessageBuffer buffer) { diff --git a/core/src/main/java/org/geysermc/geyser/network/message/ByteBufCodec.java b/core/src/main/java/org/geysermc/geyser/network/message/ByteBufCodec.java index d73e768cf..272170c4c 100644 --- a/core/src/main/java/org/geysermc/geyser/network/message/ByteBufCodec.java +++ b/core/src/main/java/org/geysermc/geyser/network/message/ByteBufCodec.java @@ -29,7 +29,6 @@ import io.netty.buffer.Unpooled; import org.checkerframework.checker.nullness.qual.NonNull; import org.cloudburstmc.protocol.common.util.VarInts; import org.geysermc.geyser.api.network.message.MessageCodec; -import org.jetbrains.annotations.NotNull; import java.nio.charset.StandardCharsets; @@ -41,62 +40,62 @@ public class ByteBufCodec implements MessageCodec { } @Override - public boolean readBoolean(@NotNull ByteBufMessageBuffer buffer) { + public boolean readBoolean(@NonNull ByteBufMessageBuffer buffer) { return buffer.buffer().readBoolean(); } @Override - public byte readByte(@NotNull ByteBufMessageBuffer buffer) { + public byte readByte(@NonNull ByteBufMessageBuffer buffer) { return buffer.buffer().readByte(); } @Override - public short readShort(@NotNull ByteBufMessageBuffer buffer) { + public short readShort(@NonNull ByteBufMessageBuffer buffer) { return buffer.buffer().readShort(); } @Override - public int readInt(@NotNull ByteBufMessageBuffer buffer) { + public int readInt(@NonNull ByteBufMessageBuffer buffer) { return buffer.buffer().readInt(); } @Override - public float readFloat(@NotNull ByteBufMessageBuffer buffer) { + public float readFloat(@NonNull ByteBufMessageBuffer buffer) { return buffer.buffer().readFloat(); } @Override - public double readDouble(@NotNull ByteBufMessageBuffer buffer) { + public double readDouble(@NonNull ByteBufMessageBuffer buffer) { return buffer.buffer().readDouble(); } @Override - public long readLong(@NotNull ByteBufMessageBuffer buffer) { + public long readLong(@NonNull ByteBufMessageBuffer buffer) { return buffer.buffer().readLong(); } @Override - public int readVarInt(@NotNull ByteBufMessageBuffer buffer) { + public int readVarInt(@NonNull ByteBufMessageBuffer buffer) { return VarInts.readInt(buffer.buffer()); } @Override - public int readUnsignedVarInt(@NotNull ByteBufMessageBuffer buffer) { + public int readUnsignedVarInt(@NonNull ByteBufMessageBuffer buffer) { return VarInts.readUnsignedInt(buffer.buffer()); } @Override - public long readVarLong(@NotNull ByteBufMessageBuffer buffer) { + public long readVarLong(@NonNull ByteBufMessageBuffer buffer) { return VarInts.readLong(buffer.buffer()); } @Override - public long readUnsignedVarLong(@NotNull ByteBufMessageBuffer buffer) { + public long readUnsignedVarLong(@NonNull ByteBufMessageBuffer buffer) { return VarInts.readUnsignedLong(buffer.buffer()); } @Override - public @NotNull String readString(@NotNull ByteBufMessageBuffer buffer) { + public @NonNull String readString(@NonNull ByteBufMessageBuffer buffer) { int size = VarInts.readUnsignedInt(buffer.buffer()); byte[] bytes = new byte[size]; buffer.buffer().readBytes(bytes); @@ -105,74 +104,74 @@ public class ByteBufCodec implements MessageCodec { } @Override - public void writeBoolean(@NotNull ByteBufMessageBuffer buffer, boolean value) { + public void writeBoolean(@NonNull ByteBufMessageBuffer buffer, boolean value) { buffer.buffer().writeBoolean(value); } @Override - public void writeByte(@NotNull ByteBufMessageBuffer buffer, byte value) { + public void writeByte(@NonNull ByteBufMessageBuffer buffer, byte value) { buffer.buffer().writeByte(value); } @Override - public void writeShort(@NotNull ByteBufMessageBuffer buffer, short value) { + public void writeShort(@NonNull ByteBufMessageBuffer buffer, short value) { buffer.buffer().writeShort(value); } @Override - public void writeInt(@NotNull ByteBufMessageBuffer buffer, int value) { + public void writeInt(@NonNull ByteBufMessageBuffer buffer, int value) { buffer.buffer().writeInt(value); } @Override - public void writeFloat(@NotNull ByteBufMessageBuffer buffer, float value) { + public void writeFloat(@NonNull ByteBufMessageBuffer buffer, float value) { buffer.buffer().writeFloat(value); } @Override - public void writeDouble(@NotNull ByteBufMessageBuffer buffer, double value) { + public void writeDouble(@NonNull ByteBufMessageBuffer buffer, double value) { buffer.buffer().writeDouble(value); } @Override - public void writeLong(@NotNull ByteBufMessageBuffer buffer, long value) { + public void writeLong(@NonNull ByteBufMessageBuffer buffer, long value) { buffer.buffer().writeLong(value); } @Override - public void writeVarInt(@NotNull ByteBufMessageBuffer buffer, int value) { + public void writeVarInt(@NonNull ByteBufMessageBuffer buffer, int value) { VarInts.writeInt(buffer.buffer(), value); } @Override - public void writeUnsignedVarInt(@NotNull ByteBufMessageBuffer buffer, int value) { + public void writeUnsignedVarInt(@NonNull ByteBufMessageBuffer buffer, int value) { VarInts.writeUnsignedInt(buffer.buffer(), value); } @Override - public void writeVarLong(@NotNull ByteBufMessageBuffer buffer, long value) { + public void writeVarLong(@NonNull ByteBufMessageBuffer buffer, long value) { VarInts.writeLong(buffer.buffer(), value); } @Override - public void writeUnsignedVarLong(@NotNull ByteBufMessageBuffer buffer, long value) { + public void writeUnsignedVarLong(@NonNull ByteBufMessageBuffer buffer, long value) { VarInts.writeUnsignedLong(buffer.buffer(), value); } @Override - public void writeString(@NotNull ByteBufMessageBuffer buffer, @NonNull String value) { + public void writeString(@NonNull ByteBufMessageBuffer buffer, @NonNull String value) { byte[] bytes = value.getBytes(StandardCharsets.UTF_8); VarInts.writeUnsignedInt(buffer.buffer(), bytes.length); buffer.buffer().writeBytes(bytes); } @Override - public @NotNull ByteBufMessageBuffer createBuffer() { + public @NonNull ByteBufMessageBuffer createBuffer() { return new ByteBufMessageBuffer(this); } @Override - public @NotNull ByteBufMessageBuffer createBuffer(byte @NotNull [] data) { + public @NonNull ByteBufMessageBuffer createBuffer(byte @NonNull [] data) { return new ByteBufMessageBuffer(this, Unpooled.wrappedBuffer(data)); } } diff --git a/core/src/main/java/org/geysermc/geyser/network/message/ByteBufCodecLE.java b/core/src/main/java/org/geysermc/geyser/network/message/ByteBufCodecLE.java index 98e401e73..c409b1a02 100644 --- a/core/src/main/java/org/geysermc/geyser/network/message/ByteBufCodecLE.java +++ b/core/src/main/java/org/geysermc/geyser/network/message/ByteBufCodecLE.java @@ -29,7 +29,6 @@ import io.netty.buffer.Unpooled; import org.checkerframework.checker.nullness.qual.NonNull; import org.cloudburstmc.protocol.common.util.VarInts; import org.geysermc.geyser.api.network.message.MessageCodec; -import org.jetbrains.annotations.NotNull; import java.nio.charset.StandardCharsets; @@ -39,62 +38,62 @@ public class ByteBufCodecLE implements MessageCodec { } @Override - public boolean readBoolean(@NotNull ByteBufMessageBuffer buffer) { + public boolean readBoolean(@NonNull ByteBufMessageBuffer buffer) { return buffer.buffer().readBoolean(); } @Override - public byte readByte(@NotNull ByteBufMessageBuffer buffer) { + public byte readByte(@NonNull ByteBufMessageBuffer buffer) { return buffer.buffer().readByte(); } @Override - public short readShort(@NotNull ByteBufMessageBuffer buffer) { + public short readShort(@NonNull ByteBufMessageBuffer buffer) { return buffer.buffer().readShortLE(); } @Override - public int readInt(@NotNull ByteBufMessageBuffer buffer) { + public int readInt(@NonNull ByteBufMessageBuffer buffer) { return buffer.buffer().readIntLE(); } @Override - public float readFloat(@NotNull ByteBufMessageBuffer buffer) { + public float readFloat(@NonNull ByteBufMessageBuffer buffer) { return buffer.buffer().readFloatLE(); } @Override - public double readDouble(@NotNull ByteBufMessageBuffer buffer) { + public double readDouble(@NonNull ByteBufMessageBuffer buffer) { return buffer.buffer().readDoubleLE(); } @Override - public long readLong(@NotNull ByteBufMessageBuffer buffer) { + public long readLong(@NonNull ByteBufMessageBuffer buffer) { return buffer.buffer().readLongLE(); } @Override - public int readVarInt(@NotNull ByteBufMessageBuffer buffer) { + public int readVarInt(@NonNull ByteBufMessageBuffer buffer) { return VarInts.readInt(buffer.buffer()); } @Override - public int readUnsignedVarInt(@NotNull ByteBufMessageBuffer buffer) { + public int readUnsignedVarInt(@NonNull ByteBufMessageBuffer buffer) { return VarInts.readUnsignedInt(buffer.buffer()); } @Override - public long readVarLong(@NotNull ByteBufMessageBuffer buffer) { + public long readVarLong(@NonNull ByteBufMessageBuffer buffer) { return VarInts.readLong(buffer.buffer()); } @Override - public long readUnsignedVarLong(@NotNull ByteBufMessageBuffer buffer) { + public long readUnsignedVarLong(@NonNull ByteBufMessageBuffer buffer) { return VarInts.readUnsignedLong(buffer.buffer()); } @Override - public @NotNull String readString(@NotNull ByteBufMessageBuffer buffer) { + public @NonNull String readString(@NonNull ByteBufMessageBuffer buffer) { int size = VarInts.readUnsignedInt(buffer.buffer()); byte[] bytes = new byte[size]; buffer.buffer().readBytes(bytes); @@ -103,74 +102,74 @@ public class ByteBufCodecLE implements MessageCodec { } @Override - public void writeBoolean(@NotNull ByteBufMessageBuffer buffer, boolean value) { + public void writeBoolean(@NonNull ByteBufMessageBuffer buffer, boolean value) { buffer.buffer().writeBoolean(value); } @Override - public void writeByte(@NotNull ByteBufMessageBuffer buffer, byte value) { + public void writeByte(@NonNull ByteBufMessageBuffer buffer, byte value) { buffer.buffer().writeByte(value); } @Override - public void writeShort(@NotNull ByteBufMessageBuffer buffer, short value) { + public void writeShort(@NonNull ByteBufMessageBuffer buffer, short value) { buffer.buffer().writeShortLE(value); } @Override - public void writeInt(@NotNull ByteBufMessageBuffer buffer, int value) { + public void writeInt(@NonNull ByteBufMessageBuffer buffer, int value) { buffer.buffer().writeIntLE(value); } @Override - public void writeFloat(@NotNull ByteBufMessageBuffer buffer, float value) { + public void writeFloat(@NonNull ByteBufMessageBuffer buffer, float value) { buffer.buffer().writeFloatLE(value); } @Override - public void writeDouble(@NotNull ByteBufMessageBuffer buffer, double value) { + public void writeDouble(@NonNull ByteBufMessageBuffer buffer, double value) { buffer.buffer().writeDoubleLE(value); } @Override - public void writeLong(@NotNull ByteBufMessageBuffer buffer, long value) { + public void writeLong(@NonNull ByteBufMessageBuffer buffer, long value) { buffer.buffer().writeLongLE(value); } @Override - public void writeVarInt(@NotNull ByteBufMessageBuffer buffer, int value) { + public void writeVarInt(@NonNull ByteBufMessageBuffer buffer, int value) { VarInts.writeInt(buffer.buffer(), value); } @Override - public void writeUnsignedVarInt(@NotNull ByteBufMessageBuffer buffer, int value) { + public void writeUnsignedVarInt(@NonNull ByteBufMessageBuffer buffer, int value) { VarInts.writeUnsignedInt(buffer.buffer(), value); } @Override - public void writeVarLong(@NotNull ByteBufMessageBuffer buffer, long value) { + public void writeVarLong(@NonNull ByteBufMessageBuffer buffer, long value) { VarInts.writeLong(buffer.buffer(), value); } @Override - public void writeUnsignedVarLong(@NotNull ByteBufMessageBuffer buffer, long value) { + public void writeUnsignedVarLong(@NonNull ByteBufMessageBuffer buffer, long value) { VarInts.writeUnsignedLong(buffer.buffer(), value); } @Override - public void writeString(@NotNull ByteBufMessageBuffer buffer, @NonNull String value) { + public void writeString(@NonNull ByteBufMessageBuffer buffer, @NonNull String value) { byte[] bytes = value.getBytes(StandardCharsets.UTF_8); VarInts.writeUnsignedInt(buffer.buffer(), bytes.length); buffer.buffer().writeBytes(bytes); } @Override - public @NotNull ByteBufMessageBuffer createBuffer() { + public @NonNull ByteBufMessageBuffer createBuffer() { return new ByteBufMessageBuffer(this); } @Override - public @NotNull ByteBufMessageBuffer createBuffer(byte @NotNull [] data) { + public @NonNull ByteBufMessageBuffer createBuffer(byte @NonNull [] data) { return new ByteBufMessageBuffer(this, Unpooled.wrappedBuffer(data)); } } diff --git a/core/src/main/java/org/geysermc/geyser/network/message/JavaPacketMessage.java b/core/src/main/java/org/geysermc/geyser/network/message/JavaPacketMessage.java index 6a2fd336f..51ff19420 100644 --- a/core/src/main/java/org/geysermc/geyser/network/message/JavaPacketMessage.java +++ b/core/src/main/java/org/geysermc/geyser/network/message/JavaPacketMessage.java @@ -29,7 +29,7 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.api.network.message.Message; import org.geysermc.mcprotocollib.protocol.codec.MinecraftPacket; -public record JavaPacketMessage(MinecraftPacket packet) implements Message.PacketWrapped { +public record JavaPacketMessage(T packet) implements Message.PacketWrapped { @Override public void encode(@NonNull ByteBufMessageBuffer buffer) { diff --git a/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java b/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java index 441fabb99..11e0db267 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java @@ -40,6 +40,7 @@ import org.geysermc.geyser.api.extension.Extension; import org.geysermc.geyser.api.item.custom.CustomItemData; import org.geysermc.geyser.api.item.custom.CustomItemOptions; import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData; +import org.geysermc.geyser.api.network.NetworkChannel; import org.geysermc.geyser.api.network.message.Message; import org.geysermc.geyser.api.pack.PathPackCodec; import org.geysermc.geyser.api.pack.UrlPackCodec; @@ -61,6 +62,9 @@ import org.geysermc.geyser.level.block.GeyserGeometryComponent; import org.geysermc.geyser.level.block.GeyserJavaBlockState; import org.geysermc.geyser.level.block.GeyserMaterialInstance; import org.geysermc.geyser.level.block.GeyserNonVanillaCustomBlockData; +import org.geysermc.geyser.network.ExtensionNetworkChannel; +import org.geysermc.geyser.network.ExternalNetworkChannel; +import org.geysermc.geyser.network.PacketChannel; import org.geysermc.geyser.network.message.BedrockPacketMessage; import org.geysermc.geyser.network.message.JavaPacketMessage; import org.geysermc.geyser.pack.option.GeyserPriorityOption; @@ -72,6 +76,7 @@ import org.geysermc.geyser.registry.provider.ProviderSupplier; import org.geysermc.mcprotocollib.protocol.codec.MinecraftPacket; import java.nio.file.Path; +import java.util.Arrays; import java.util.Map; /** @@ -121,14 +126,29 @@ public class ProviderRegistryLoader implements RegistryLoader, Prov } if (args[0] instanceof BedrockPacket bedrockPacket) { - return new BedrockPacketMessage(bedrockPacket); + return new BedrockPacketMessage<>(bedrockPacket); } else if (args[0] instanceof MinecraftPacket javaPacket) { - return new JavaPacketMessage(javaPacket); + return new JavaPacketMessage<>(javaPacket); } else { throw new IllegalArgumentException("Unsupported packet type: " + args[0].getClass().getName()); } }); + providers.put(NetworkChannel.class, args -> { + // Extension network channel + if (args.length == 3 && args[0] instanceof Extension extension && args[1] instanceof String channel && args[2] instanceof Class messageType) { + return new ExtensionNetworkChannel(extension, channel, messageType); + } else if (args.length == 3 && args[0] instanceof String key && args[1] instanceof Integer packetId && args[2] instanceof Class packetType) { + // Packet channel + return new PacketChannel(key, packetId, packetType); + } else if (args.length == 2 && args[0] instanceof Identifier identifier && args[1] instanceof Class messageType) { + // External network channel + return new ExternalNetworkChannel(identifier, messageType); + } else { + throw new IllegalArgumentException("Unknown arguments provided for NetworkChannel provider. Could not create a channel given the arguments: " + Arrays.toString(args)); + } + }); + return providers; } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCustomPayloadTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCustomPayloadTranslator.java index 64a46c89e..0ff8d59a0 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCustomPayloadTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCustomPayloadTranslator.java @@ -40,8 +40,6 @@ import org.geysermc.erosion.packet.geyserbound.GeyserboundPacket; import org.geysermc.floodgate.pluginmessage.PluginMessageChannels; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserLogger; -import org.geysermc.geyser.api.event.EventBus; -import org.geysermc.geyser.api.event.java.ServerReceiveNetworkMessageEvent; import org.geysermc.geyser.api.network.MessageDirection; import org.geysermc.geyser.api.network.NetworkChannel; import org.geysermc.geyser.api.network.message.Message; @@ -54,6 +52,9 @@ import org.geysermc.mcprotocollib.protocol.packet.common.clientbound.Clientbound import org.geysermc.mcprotocollib.protocol.packet.common.serverbound.ServerboundCustomPayloadPacket; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; @Translator(packet = ClientboundCustomPayloadPacket.class) public class JavaCustomPayloadTranslator extends PacketTranslator { @@ -153,16 +154,28 @@ public class JavaCustomPayloadTranslator extends PacketTranslator { GeyserNetworkManager networkManager = session.getNetworkManager(); - NetworkChannel networkChannel = NetworkChannel.of(packet.getChannel().namespace(), packet.getChannel().value()); - if (!networkManager.registeredChannels().contains(networkChannel)) { - logger.debug("Received a custom payload for an unregistered channel: " + networkChannel.channel()); + Set channels = networkManager.registeredChannels(); + if (channels.isEmpty()) { + this.logger.debug("Received a custom payload for an unregistered channel: " + channel); return; } - Message message = networkManager.createMessage(networkChannel, packet.getData()); + List identifiedChannels = new ArrayList<>(); + for (NetworkChannel registeredChannel : channels) { + if (!registeredChannel.isPacket() && registeredChannel.identifier().toString().equals(channel)) { + identifiedChannels.add(registeredChannel); + } + } - EventBus eventBus = session.getGeyser().getEventBus(); - eventBus.fire(new ServerReceiveNetworkMessageEvent(session, networkChannel, message, MessageDirection.CLIENTBOUND)); + if (identifiedChannels.isEmpty()) { + this.logger.debug("Received a custom payload for an unregistered channel: " + channel); + return; + } + + for (NetworkChannel networkChannel : identifiedChannels) { + List> message = networkManager.createMessages(networkChannel, packet.getData()); + networkManager.handleMessages(networkChannel, message, MessageDirection.CLIENTBOUND); + } }); } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaLoginTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaLoginTranslator.java index eb94afdb8..741911f1d 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaLoginTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaLoginTranslator.java @@ -137,7 +137,8 @@ public class JavaLoginTranslator extends PacketTranslator channel.key() + ":" + channel.channel()) + .filter(channel -> !channel.isPacket()) + .map(channel -> channel.identifier().namespace() + ":" + channel.identifier().path()) .collect(Collectors.joining("\0")); session.sendDownstreamPacket(new ServerboundCustomPayloadPacket(register, channels.getBytes(StandardCharsets.UTF_8))); diff --git a/core/src/test/java/org/geysermc/geyser/network/MessageRegistrationOrderTest.java b/core/src/test/java/org/geysermc/geyser/network/MessageRegistrationOrderTest.java new file mode 100644 index 000000000..78d102966 --- /dev/null +++ b/core/src/test/java/org/geysermc/geyser/network/MessageRegistrationOrderTest.java @@ -0,0 +1,208 @@ +/* + * Copyright (c) 2025 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.network; + +import net.kyori.adventure.key.Key; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.api.network.MessageDirection; +import org.geysermc.geyser.api.network.NetworkChannel; +import org.geysermc.geyser.api.network.message.DataType; +import org.geysermc.geyser.api.network.message.Message; +import org.geysermc.geyser.api.network.message.MessageBuffer; +import org.geysermc.geyser.api.network.message.MessageHandler; +import org.geysermc.geyser.api.network.message.MessagePriority; +import org.geysermc.geyser.impl.IdentifierImpl; +import org.geysermc.geyser.session.GeyserSession; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.geysermc.geyser.util.GeyserMockContext.mockContext; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +public class MessageRegistrationOrderTest { + private final NetworkChannel xuidChannel = new ExternalNetworkChannel(new IdentifierImpl(Key.key("geyser_test", "xuid")), XuidMessage.class); + + @Test + void testRegistrationOrder() { + mockContext(context -> { + GeyserSession session = context.mock(GeyserSession.class); + GeyserNetworkManager manager = new GeyserNetworkManager(session); + + AtomicInteger state = new AtomicInteger(0); + + new NetworkDefinitionBuilder(registration -> manager.onRegister(this.xuidChannel, XuidMessage::new, registration)) + .clientbound(MessagePriority.EARLY, message -> { + assertEquals(0, state.getAndIncrement()); + return MessageHandler.State.UNHANDLED; + }) + .register(); + + new NetworkDefinitionBuilder(registration -> manager.onRegister(this.xuidChannel, XuidMessage::new, registration)) + .clientbound(MessagePriority.LATE, message -> { + assertEquals(2, state.getAndIncrement()); + return MessageHandler.State.UNHANDLED; + }) + .register(); + + new NetworkDefinitionBuilder(registration -> manager.onRegister(this.xuidChannel, XuidMessage::new, registration)) + .clientbound(MessagePriority.NORMAL, message -> { + assertEquals(1, state.getAndIncrement()); + return MessageHandler.State.UNHANDLED; + }) + .register(); + + manager.handleMessages(this.xuidChannel, List.of(new XuidMessage("test-xuid")), MessageDirection.CLIENTBOUND); + }); + } + + @Test + void testPipelineTags() { + mockContext(context -> { + GeyserSession session = context.mock(GeyserSession.class); + GeyserNetworkManager manager = new GeyserNetworkManager(session); + + AtomicInteger state = new AtomicInteger(0); + + new NetworkDefinitionBuilder(registration -> manager.onRegister(this.xuidChannel, XuidMessage::new, registration)) + .clientbound(message -> { + assertEquals(2, state.getAndIncrement()); + return MessageHandler.State.UNHANDLED; + }) + .pipeline(pipeline -> { + pipeline.tag("monitor"); + }) + .register(); + + new NetworkDefinitionBuilder(registration -> manager.onRegister(this.xuidChannel, XuidMessage::new, registration)) + .clientbound(message -> { + assertEquals(1, state.getAndIncrement()); + return MessageHandler.State.UNHANDLED; + }) + .pipeline(pipeline -> { + pipeline.tag("initial-handler"); + pipeline.before("monitor"); + }) + .register(); + + new NetworkDefinitionBuilder(registration -> manager.onRegister(this.xuidChannel, XuidMessage::new, registration)) + .clientbound(message -> { + assertEquals(3, state.getAndIncrement()); + return MessageHandler.State.UNHANDLED; + }) + .pipeline(pipeline -> { + pipeline.tag("tail"); + pipeline.after("monitor"); + }) + .register(); + + // No pipeline, so should automatically be added to the tail + new NetworkDefinitionBuilder(registration -> manager.onRegister(this.xuidChannel, XuidMessage::new, registration)) + .clientbound(message -> { + assertEquals(4, state.getAndIncrement()); + return MessageHandler.State.UNHANDLED; + }) + .register(); + + // Early priority - should come first regardless of tail structure + new NetworkDefinitionBuilder(registration -> manager.onRegister(this.xuidChannel, XuidMessage::new, registration)) + .clientbound(MessagePriority.EARLY, message -> { + assertEquals(0, state.getAndIncrement()); + return MessageHandler.State.UNHANDLED; + }) + .register(); + + manager.handleMessages(this.xuidChannel, List.of(new XuidMessage("test-xuid")), MessageDirection.CLIENTBOUND); + }); + } + + @Test + void testMixedHandlers() { + mockContext(context -> { + GeyserSession session = context.mock(GeyserSession.class); + GeyserNetworkManager manager = new GeyserNetworkManager(session); + + AtomicInteger state = new AtomicInteger(0); + + // Simple early clientbound + new NetworkDefinitionBuilder(registration -> manager.onRegister(this.xuidChannel, XuidMessage::new, registration)) + .clientbound(MessagePriority.EARLY, message -> { + assertEquals(0, state.getAndIncrement()); + return MessageHandler.State.UNHANDLED; + }) + .register(); + + // Late clientbound but first serverbound + new NetworkDefinitionBuilder(registration -> manager.onRegister(this.xuidChannel, XuidMessage::new, registration)) + .clientbound(MessagePriority.LATE, message -> { + assertEquals(2, state.getAndIncrement()); + return MessageHandler.State.UNHANDLED; + }) + .serverbound(MessagePriority.FIRST, message -> { + fail("Serverbound handler should not be called in this test"); + return MessageHandler.State.UNHANDLED; + }) + .register(); + + // Normal (default) bidirectional + new NetworkDefinitionBuilder(registration -> manager.onRegister(this.xuidChannel, XuidMessage::new, registration)) + .bidirectional((message, direction) -> { + if (direction == MessageDirection.SERVERBOUND) { + fail("Serverbound handler should not be called in this test"); + return MessageHandler.State.UNHANDLED; + } + + assertEquals(1, state.getAndIncrement()); + return MessageHandler.State.UNHANDLED; + }) + .register(); + + // Serverbound only - should never be called + new NetworkDefinitionBuilder(registration -> manager.onRegister(this.xuidChannel, XuidMessage::new, registration)) + .serverbound(MessagePriority.NORMAL, message -> { + fail("Serverbound handler should not be called in this test"); + return MessageHandler.State.UNHANDLED; + }) + .register(); + + manager.handleMessages(this.xuidChannel, List.of(new XuidMessage("test-xuid")), MessageDirection.CLIENTBOUND); + }); + } + + public record XuidMessage(String xuid) implements Message.Simple { + + public XuidMessage(MessageBuffer buffer) { + this(buffer.read(DataType.STRING)); + } + + @Override + public void encode(@NonNull MessageBuffer buffer) { + buffer.write(DataType.STRING, this.xuid); + } + } +} diff --git a/core/src/test/java/org/geysermc/geyser/scoreboard/network/NameVisibilityScoreboardTest.java b/core/src/test/java/org/geysermc/geyser/scoreboard/network/NameVisibilityScoreboardTest.java index 29882ca2e..05396746d 100644 --- a/core/src/test/java/org/geysermc/geyser/scoreboard/network/NameVisibilityScoreboardTest.java +++ b/core/src/test/java/org/geysermc/geyser/scoreboard/network/NameVisibilityScoreboardTest.java @@ -25,8 +25,8 @@ package org.geysermc.geyser.scoreboard.network; -import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNextPacket; -import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNoNextPacket; +import static org.geysermc.geyser.util.AssertUtils.assertNextPacket; +import static org.geysermc.geyser.util.AssertUtils.assertNoNextPacket; import static org.geysermc.geyser.scoreboard.network.util.GeyserMockContextScoreboard.spawnPlayerSilently; import static org.geysermc.geyser.scoreboard.network.util.GeyserMockContextScoreboard.mockContextScoreboard; diff --git a/core/src/test/java/org/geysermc/geyser/scoreboard/network/ScoreboardIssueTests.java b/core/src/test/java/org/geysermc/geyser/scoreboard/network/ScoreboardIssueTests.java index fcb05c92d..539766763 100644 --- a/core/src/test/java/org/geysermc/geyser/scoreboard/network/ScoreboardIssueTests.java +++ b/core/src/test/java/org/geysermc/geyser/scoreboard/network/ScoreboardIssueTests.java @@ -61,7 +61,7 @@ import java.util.EnumSet; import java.util.Optional; import java.util.UUID; -import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.*; +import static org.geysermc.geyser.util.AssertUtils.*; import static org.geysermc.geyser.scoreboard.network.util.GeyserMockContextScoreboard.mockContextScoreboard; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; diff --git a/core/src/test/java/org/geysermc/geyser/scoreboard/network/belowname/BasicBelownameScoreboardTests.java b/core/src/test/java/org/geysermc/geyser/scoreboard/network/belowname/BasicBelownameScoreboardTests.java index dfe85a0ee..5e9cb6a34 100644 --- a/core/src/test/java/org/geysermc/geyser/scoreboard/network/belowname/BasicBelownameScoreboardTests.java +++ b/core/src/test/java/org/geysermc/geyser/scoreboard/network/belowname/BasicBelownameScoreboardTests.java @@ -25,8 +25,8 @@ package org.geysermc.geyser.scoreboard.network.belowname; -import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNextPacket; -import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNoNextPacket; +import static org.geysermc.geyser.util.AssertUtils.assertNextPacket; +import static org.geysermc.geyser.util.AssertUtils.assertNoNextPacket; import static org.geysermc.geyser.scoreboard.network.util.GeyserMockContextScoreboard.spawnPlayerSilently; import static org.geysermc.geyser.scoreboard.network.util.GeyserMockContextScoreboard.mockContextScoreboard; diff --git a/core/src/test/java/org/geysermc/geyser/scoreboard/network/playerlist/BasicPlayerlistScoreboardTests.java b/core/src/test/java/org/geysermc/geyser/scoreboard/network/playerlist/BasicPlayerlistScoreboardTests.java index 4ac5ee098..e479f44f8 100644 --- a/core/src/test/java/org/geysermc/geyser/scoreboard/network/playerlist/BasicPlayerlistScoreboardTests.java +++ b/core/src/test/java/org/geysermc/geyser/scoreboard/network/playerlist/BasicPlayerlistScoreboardTests.java @@ -25,8 +25,8 @@ package org.geysermc.geyser.scoreboard.network.playerlist; -import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNextPacket; -import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNoNextPacket; +import static org.geysermc.geyser.util.AssertUtils.assertNextPacket; +import static org.geysermc.geyser.util.AssertUtils.assertNoNextPacket; import static org.geysermc.geyser.scoreboard.network.util.GeyserMockContextScoreboard.mockContextScoreboard; import java.util.List; diff --git a/core/src/test/java/org/geysermc/geyser/scoreboard/network/server/CubecraftScoreboardTest.java b/core/src/test/java/org/geysermc/geyser/scoreboard/network/server/CubecraftScoreboardTest.java index 80f562fc3..60b881c80 100644 --- a/core/src/test/java/org/geysermc/geyser/scoreboard/network/server/CubecraftScoreboardTest.java +++ b/core/src/test/java/org/geysermc/geyser/scoreboard/network/server/CubecraftScoreboardTest.java @@ -25,9 +25,9 @@ package org.geysermc.geyser.scoreboard.network.server; -import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNextPacket; -import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNextPacketMatch; -import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNoNextPacket; +import static org.geysermc.geyser.util.AssertUtils.assertNextPacket; +import static org.geysermc.geyser.util.AssertUtils.assertNextPacketMatch; +import static org.geysermc.geyser.util.AssertUtils.assertNoNextPacket; import static org.geysermc.geyser.scoreboard.network.util.GeyserMockContextScoreboard.spawnPlayer; import static org.geysermc.geyser.scoreboard.network.util.GeyserMockContextScoreboard.mockContextScoreboard; import static org.junit.jupiter.api.Assertions.assertEquals; diff --git a/core/src/test/java/org/geysermc/geyser/scoreboard/network/sidebar/BasicSidebarScoreboardTests.java b/core/src/test/java/org/geysermc/geyser/scoreboard/network/sidebar/BasicSidebarScoreboardTests.java index bd0d64c80..01b33f512 100644 --- a/core/src/test/java/org/geysermc/geyser/scoreboard/network/sidebar/BasicSidebarScoreboardTests.java +++ b/core/src/test/java/org/geysermc/geyser/scoreboard/network/sidebar/BasicSidebarScoreboardTests.java @@ -25,8 +25,8 @@ package org.geysermc.geyser.scoreboard.network.sidebar; -import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNextPacket; -import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNoNextPacket; +import static org.geysermc.geyser.util.AssertUtils.assertNextPacket; +import static org.geysermc.geyser.util.AssertUtils.assertNoNextPacket; import static org.geysermc.geyser.scoreboard.network.util.GeyserMockContextScoreboard.mockContextScoreboard; import java.util.List; diff --git a/core/src/test/java/org/geysermc/geyser/scoreboard/network/sidebar/OrderAndLimitSidebarScoreboardTests.java b/core/src/test/java/org/geysermc/geyser/scoreboard/network/sidebar/OrderAndLimitSidebarScoreboardTests.java index aab837456..c99b889c2 100644 --- a/core/src/test/java/org/geysermc/geyser/scoreboard/network/sidebar/OrderAndLimitSidebarScoreboardTests.java +++ b/core/src/test/java/org/geysermc/geyser/scoreboard/network/sidebar/OrderAndLimitSidebarScoreboardTests.java @@ -25,8 +25,8 @@ package org.geysermc.geyser.scoreboard.network.sidebar; -import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNextPacket; -import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNoNextPacket; +import static org.geysermc.geyser.util.AssertUtils.assertNextPacket; +import static org.geysermc.geyser.util.AssertUtils.assertNoNextPacket; import static org.geysermc.geyser.scoreboard.network.util.GeyserMockContextScoreboard.mockContextScoreboard; import java.util.List; diff --git a/core/src/test/java/org/geysermc/geyser/scoreboard/network/sidebar/VanillaSidebarScoreboardTests.java b/core/src/test/java/org/geysermc/geyser/scoreboard/network/sidebar/VanillaSidebarScoreboardTests.java index f511f59c7..dcfd5242a 100644 --- a/core/src/test/java/org/geysermc/geyser/scoreboard/network/sidebar/VanillaSidebarScoreboardTests.java +++ b/core/src/test/java/org/geysermc/geyser/scoreboard/network/sidebar/VanillaSidebarScoreboardTests.java @@ -25,8 +25,8 @@ package org.geysermc.geyser.scoreboard.network.sidebar; -import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNextPacket; -import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNoNextPacket; +import static org.geysermc.geyser.util.AssertUtils.assertNextPacket; +import static org.geysermc.geyser.util.AssertUtils.assertNoNextPacket; import static org.geysermc.geyser.scoreboard.network.util.GeyserMockContextScoreboard.mockContextScoreboard; import java.util.List; diff --git a/core/src/test/java/org/geysermc/geyser/scoreboard/network/util/GeyserMockContextScoreboard.java b/core/src/test/java/org/geysermc/geyser/scoreboard/network/util/GeyserMockContextScoreboard.java index 813503918..1c00aa2f3 100644 --- a/core/src/test/java/org/geysermc/geyser/scoreboard/network/util/GeyserMockContextScoreboard.java +++ b/core/src/test/java/org/geysermc/geyser/scoreboard/network/util/GeyserMockContextScoreboard.java @@ -25,9 +25,9 @@ package org.geysermc.geyser.scoreboard.network.util; -import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNextPacketType; -import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNoNextPacket; -import static org.geysermc.geyser.scoreboard.network.util.GeyserMockContext.mockContext; +import static org.geysermc.geyser.util.AssertUtils.assertNextPacketType; +import static org.geysermc.geyser.util.AssertUtils.assertNoNextPacket; +import static org.geysermc.geyser.util.GeyserMockContext.mockContext; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.spy; @@ -45,6 +45,7 @@ import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.cache.EntityCache; import org.geysermc.geyser.session.cache.WorldCache; import org.geysermc.geyser.session.cache.waypoint.WaypointCache; +import org.geysermc.geyser.util.GeyserMockContext; import org.mockito.stubbing.Answer; public class GeyserMockContextScoreboard { diff --git a/core/src/test/java/org/geysermc/geyser/scoreboard/network/util/AssertUtils.java b/core/src/test/java/org/geysermc/geyser/util/AssertUtils.java similarity index 96% rename from core/src/test/java/org/geysermc/geyser/scoreboard/network/util/AssertUtils.java rename to core/src/test/java/org/geysermc/geyser/util/AssertUtils.java index 9177f205a..d1517594a 100644 --- a/core/src/test/java/org/geysermc/geyser/scoreboard/network/util/AssertUtils.java +++ b/core/src/test/java/org/geysermc/geyser/util/AssertUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 GeyserMC. http://geysermc.org + * Copyright (c) 2024-2025 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.geyser.scoreboard.network.util; +package org.geysermc.geyser.util; import java.util.Collections; import java.util.function.Consumer; diff --git a/core/src/test/java/org/geysermc/geyser/scoreboard/network/util/EmptyGeyserLogger.java b/core/src/test/java/org/geysermc/geyser/util/EmptyGeyserLogger.java similarity index 94% rename from core/src/test/java/org/geysermc/geyser/scoreboard/network/util/EmptyGeyserLogger.java rename to core/src/test/java/org/geysermc/geyser/util/EmptyGeyserLogger.java index e033b7288..a95be4048 100644 --- a/core/src/test/java/org/geysermc/geyser/scoreboard/network/util/EmptyGeyserLogger.java +++ b/core/src/test/java/org/geysermc/geyser/util/EmptyGeyserLogger.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 GeyserMC. http://geysermc.org + * Copyright (c) 2024-2025 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.geyser.scoreboard.network.util; +package org.geysermc.geyser.util; import org.geysermc.geyser.GeyserLogger; diff --git a/core/src/test/java/org/geysermc/geyser/scoreboard/network/util/GeyserMockContext.java b/core/src/test/java/org/geysermc/geyser/util/GeyserMockContext.java similarity index 93% rename from core/src/test/java/org/geysermc/geyser/scoreboard/network/util/GeyserMockContext.java rename to core/src/test/java/org/geysermc/geyser/util/GeyserMockContext.java index 2b89867fb..295a2cb6e 100644 --- a/core/src/test/java/org/geysermc/geyser/scoreboard/network/util/GeyserMockContext.java +++ b/core/src/test/java/org/geysermc/geyser/util/GeyserMockContext.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 GeyserMC. http://geysermc.org + * Copyright (c) 2024-2025 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,22 +23,23 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.geyser.scoreboard.network.util; +package org.geysermc.geyser.util; -import static org.mockito.Mockito.mockStatic; -import static org.mockito.Mockito.when; +import org.cloudburstmc.protocol.bedrock.packet.BedrockPacket; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.configuration.GeyserConfiguration; +import org.geysermc.geyser.event.GeyserEventBus; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.mockito.Mockito; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.function.Consumer; -import org.cloudburstmc.protocol.bedrock.packet.BedrockPacket; -import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.configuration.GeyserConfiguration; -import org.geysermc.geyser.registry.Registries; -import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.translator.protocol.PacketTranslator; -import org.mockito.Mockito; + +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; public class GeyserMockContext { private final List mocksAndSpies = new ArrayList<>(); @@ -58,6 +59,9 @@ public class GeyserMockContext { var logger = context.storeObject(new EmptyGeyserLogger()); when(geyserImpl.getLogger()).thenReturn(logger); + var eventBus = context.mock(GeyserEventBus.class); + when(geyserImpl.getEventBus()).thenReturn(eventBus); + try (var geyserImplMock = mockStatic(GeyserImpl.class)) { geyserImplMock.when(GeyserImpl::getInstance).thenReturn(geyserImpl); @@ -113,7 +117,7 @@ public class GeyserMockContext { return mockOrSpy(GeyserSession.class); } - void addPacket(BedrockPacket packet) { + public void addPacket(BedrockPacket packet) { packets.add(packet); }