-
Notifications
You must be signed in to change notification settings - Fork 0
Registering Our Protocol Channels
Now that the protocol has been defined, we need to actually register our channels on the client and the server. How you do this depends on the platform you're using, but the concept remains the same. Before you can use a protocol, the channel being communicated upon needs registration so that the server knows to forward our data to Networking and how to serialize and deserialize the data we want to send. This only needs to be done once when the server or client is initialized, but we do need to create an implementation of ChannelRegistrar to handle a lot of the brunt work for us.
This guide is separated into different sections for each supported platform.
Bukkit-based servers require custom payload packets to be registered both for incoming and outgoing messages. This can be done through the Messenger interface, but Networking provides Bukkit servers with an abstract BukkitChannelRegistrar to handle some of the deserialization logic for us. Let's create a new class that extends this abstract class and handle some incoming packets.
public final class ExampleChannelRegistrar extends BukkitChannelRegistrar<ExamplePlugin, ExampleServerboundMessageListener, ExampleClientboundMessageListener> {
public ExampleChannelRegistrar(@NotNull ExamplePlugin plugin) {
super(plugin, Example.PROTOCOL); // We can pass in our protocol directly!
}
@Nullable
@Override
protected ExampleServerboundMessageListener onSuccessfulMessage(Player player, String channel, Message<ExampleServerboundMessageListener> message) {
return null;
}
}This is what we get when implementing this class and providing it with our plugin type (ExamplePlugin), and our two listener types from our protocol. In onSuccessfulMessage(), our goal is to return the ExampleServerboundMessageListener instance that is associated with the Player that sent the message. Let's do that by returning values from a Map<UUID, ExampleServerboundMessageListener> that we can create in our plugin class using implementations we created in the last wiki.
public final class ExamplePlugin extends JavaPlugin implements Listener {
private final Map<UUID, ExampleServerboundMessageListener> listeners = new HashMap<>();
@Override
public void onEnable() {
Bukkit.getPluginManager().registerEvents(this, this);
}
@EventHandler
private void onPlayerJoin(PlayerJoinEvent event) {
Player player = event.getPlayer();
this.listeners.put(player.getUniqueId(), new ExampleServerboundMessageListenerImpl(player)); // Our "impl" type!
}
@EventHandler
private void onPlayerQuit(PlayerQuitEvent event) {
this.listeners.remove(event.getPlayer().getUniqueId()); // Clean up!
}
public ExampleServerboundMessageListener getListener(Player player) {
return listeners.get(player.getUniqueId());
}
}
// And now in our ExampleChannelRegistrar
@Nullable
@Override
protected ExampleServerboundMessageListener onSuccessfulMessage(Player player, String channel, Message<ExampleServerboundMessageListener> message) {
// We have protected access to the "plugin" field which is your plugin instance
return plugin.getListener(player);
}Now that we have our completed ChannelRegistrar implementation, we need to pass it to our protocol. This can be done in onEnable().
public final class ExamplePlugin extends JavaPlugin implements Listener {
// ...
@Override
public void onEnable() {
Example.PROTOCOL.registerChannels(new ExampleChannelRegistrar(this));
// ...
}
// ...
}That's it! Our channels have been registered for Bukkit and the protocol can now send and receive messages.
Fabric-based clients and servers, unlike Bukkit-based servers, do not require outgoing custom payload packets to be registered. Incoming channels do need to be registered however and this process differs based on your environment. Networking provides Fabric environments with an abstract FabricChannelRegistrar to handle some of the deserialization logic for us, but depending on whether you're writing a client or server mod (or both!), you will need to override different methods and pass different parameters.
Note that these examples are using Mojang's official mappings. Replace any Fabric environment class names where applicable.
Let's create a client-sided class for channel registration.
public final class ExampleClientChannelRegistrar extends FabricChannelRegistrar<ExampleServerboundMessageListener, ExampleClientboundMessageListener> {
public ExampleClientChannelRegistrar(@NotNull org.slf4j.Logger logger) {
super(Example.PROTOCOL, logger, true /* passing true lets the registrar know we're on the client! */);
}
@Nullable
@Override // You need to override this yourself. This is not an abstract method
protected ExampleClientboundMessageListener onSuccessfulClientboundMessage(@NotNull ResourceLocation channel, @NotNull Message<ExampleClientboundMessageListener> message) {
return null;
}
}This is what we get when implementing this class and providing it with our two listener types from our protocol. In onSuccessfulClientboundMessage(), our goal is to return the ExampleClientboundMessageListener instance for this client. Let's do that by returning a constant instance of implementations we created in the last wiki.
public final class ExampleMod {
public static final ExampleClientboundMessageListener LISTENER = new ExampleClientboundMessageListenerImpl(); // Our "impl" type!
}
// And now in our ExampleClientChannelRegistrar
@Nullable
@Override
protected ExampleClientboundMessageListener onSuccessfulClientboundMessage(@NotNull ResourceLocation channel, @NotNull Message<ExampleClientboundMessageListener> message) {
return ExampleMod.LISTENER;
}Now that we have our completed ChannelRegistrar implementation, we need to pass it to our protocol. This can be done in a ClientModInitializer.
public final class ExampleMod implements ClientModInitializer {
public static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger("example");
@Override
public void onInitializeClient() {
Example.PROTOCOL.registerChannels(new ExampleClientChannelRegistrar(LOGGER));
}
}Let's create a server-sided class for channel registration.
public final class ExampleServerChannelRegistrar extends FabricChannelRegistrar<ExampleServerboundMessageListener, ExampleClientboundMessageListener> {
public ExampleServerChannelRegistrar(@NotNull org.slf4j.Logger logger) {
super(Example.PROTOCOL, logger, false /* passing false lets the registrar know we're on the server! */);
}
@Nullable
@Override // You need to override this yourself. This is not an abstract method
protected ExampleServerboundMessageListener onSuccessfulServerboundMessage(@NotNull MinecraftServer server, @NotNull ServerPlayer player, @NotNull ResourceLocation channel, @NotNull Message<ExampleServerboundMessageListener> message) {
return null;
}
}This is what we get when implementing this class and providing it with our two listener types from our protocol. In onSuccessfulClientboundMessage(), our goal is to return the ExampleClientboundMessageListener instance for this client. Let's do that by returning a constant instance of implementations we created in the last wiki.
public final class ExampleMod implements DedicatedServerModInitializer {
private static final Map<UUID, ExampleServerboundMessageListener> LISTENERS = new HashMap<>();
@Override
public void onInitializeServer() {
ServerPlayConnectionEvents.JOIN.register((handler, sender, server) -> {
LISTENERS.put(handler.player.getUUID(), new ExampleServerboundMessageListenerImpl()); // Our "impl" type!
});
ServerPlayConnectionEvents.DISCONNECT.register((handler, server) -> {
LISTENERS.remove(handler.player.getUUID()); // Clean up!
});
}
public static ExampleServerboundMessageListener getListener(Player player) {
return LISTENERS.get(player.getUUID());
}
}
// And now in our ExampleServerChannelRegistrar
@Nullable
@Override
protected ExampleServerboundMessageListener onSuccessfulServerboundMessage(@NotNull MinecraftServer server, @NotNull ServerPlayer player, @NotNull ResourceLocation channel, @NotNull Message<ExampleServerboundMessageListener> message) {
return ExampleMod.getListener(player);
}Now that we have our completed ChannelRegistrar implementation, we need to pass it to our protocol. This can be done in a DedicatedServerModInitializer.
public final class ExampleMod implements DedicatedServerModInitializer {
public static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger("example");
@Override
public void onInitializeServer() {
Example.PROTOCOL.registerChannels(new ExampleServerChannelRegistrar(LOGGER));
}
}Using the knowledge we have above, you may notice that in both environments we extended the FabricChannelRegistrar class but overrode different methods. Knowing this, you could in theory accomplish an environment-independent using a separate constructor that passes two boolean values instead of one determining the environment. A full example will not be detailed as above, but the constructor can be detailed as such:
public final class ExampleServerChannelRegistrar extends FabricChannelRegistrar<ExampleServerboundMessageListener, ExampleClientboundMessageListener> {
public ExampleServerChannelRegistrar(@NotNull org.slf4j.Logger logger) {
super(Example.PROTOCOL, logger, true /* registerClientboundReceiver */, true /* registerServerboundReceiver */);
}
// Override your success methods here
}Be careful where this instance is constructed as it may cause a crash if you try to register a client registrar on the server.