Skip to content
1 change: 1 addition & 0 deletions config/sidebar.paper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ const paper: SidebarsConfig = {
"dev/api/roadmap",
"dev/api/pdc",
"dev/api/custom-inventory-holder",
"dev/api/plugin-messaging",
],
},
{
Expand Down
8 changes: 6 additions & 2 deletions config/sidebar.velocity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,10 @@ const velocity: SidebarsConfig = {
type: "generated-index",
slug: "/cat/dev/how-to-guides",
},
items: ["dev/how-to/dependencies", "dev/how-to/porting-from-velocity-1"],
items: [
"dev/how-to/dependencies",
"dev/how-to/porting-from-velocity-1",
],
},
{
type: "category",
Expand All @@ -104,7 +107,8 @@ const velocity: SidebarsConfig = {
},
"dev/api/event",
"dev/api/command",
"dev/api/scheduler"
"dev/api/scheduler",
"dev/how-to/plugin-messaging",
],
},
],
Expand Down
196 changes: 196 additions & 0 deletions docs/paper/dev/api/plugin-messaging.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
---
slug: /dev/plugin-messaging
---

# Plugin Messaging

First introduced in [2012](https://web.archive.org/web/20220711204310/https://dinnerbone.com/blog/2012/01/13/minecraft-plugin-channels-messaging/),
Plugin messaging is a way for Paper plugins to communicate with clients. When your servers are behind a proxy,
it will allow your Paper plugins to communicate with the proxy server.

## BungeeCord Channel

The BungeeCord channel is used for communication between your Paper server and a BungeeCord (Or protocol supporting) proxy.

Originally, the channel supported by the BungeeCord proxy was called `BungeeCord`. In versions 1.13 and above,
the channel was renamed to `bungeecord:main` to create a key structure for plugin messaging channels.

Paper handles this change internally, and automatically changes any messages sent on the `BungeeCord` channel
to the `bungeecord:main` channel. This means that your Paper plugins should continue to use the `BungeeCord` channel.

## Sending Plugin Messages

First, we're going to take a look at your Paper server. Your Paper plugin will need to register that it
will be sending on any given plugin channel. You should to do this alongside your other event listener registrations.

```java
public final class PluginMessagingSample extends JavaPlugin {

@Override
public void onEnable() {
getServer().getMessenger().registerOutgoingPluginChannel(this, "BungeeCord");
// Blah blah blah...
}

}
```

Now that we're registered, we can send messages on the `BungeeCord` channel.

Plugin messages are formatted as byte arrays and can be sent using the `sendPluginMessage` method on a `Player` object.
Let's take a look at an example of sending a plugin message to the `BungeeCord` channel to send our player to another server.

```java
public final class PluginMessagingSample extends JavaPlugin implements Listener {

@Override
public void onEnable() {
getServer().getPluginManager().registerEvents(this, this);
getServer().getMessenger().registerOutgoingPluginChannel(this, "BungeeCord");
}

@EventHandler
public void onPlayerJump(PlayerJumpEvent event) {
Player player = event.getPlayer();

ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.writeUTF("Connect");
out.writeUTF("hub2");
player.sendPluginMessage(this, "BungeeCord", out.toByteArray());
}

}
```

:::tip

These channels rely on the Minecraft protocol, and are sent as a special type of packet called a
[Plugin Message](https://wiki.vg/Plugin_channels#Plugin_Messages). They piggyback on player connections, so if there is no
player connected to the server, it will not be able to send or receive plugin messages.

:::

### What did we just do?

We sent a plugin message on the `BungeeCord` channel! The message we sent was a byte array that contained two strings converted to bytes: `Connect` and `hub2`.

Our proxy server received the message through the player who triggered the `PlayerJumpEvent` on our Java server.
Then, it recognized the channel as its own and in alignment with BungeeCord's format sent our player to the `hub2` server.

For BungeeCord, we can think of this message as a case-sensitive command with arguments.
Here, our command is `Connect` and our only argument is `hub2`, but some "commands" may have multiple arguments.
For other channels introduced by client side mods, refer to their documentation to best understand how to format your messages.

### BungeeCord Plugin Message Types

Although we sent a `Connect` message to the proxy, there are a few other cases that proxies will act on.
These are the following:

| Message Type | Description | Arguments | Response |
|:------------------|:-------------------------------------------------------|:-----------------------------------------------------------------|:--------------------------------------------------|
| `Connect` | Connects the player to the specified server. | `server name` | N/A |
| `ConnectOther` | Connects another player to the specified server. | `player name`, `server name` | N/A |
| `IP` | Returns the IP of the specified player. | `player name` | `IP`, `port` |
| `PlayerCount` | Returns the number of players on the specified server. | `server name` | `server name`, player count` |
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The player count in the last column doesn't seems well formatted

| `PlayerList` | Returns a list of players on the specified server. | `server name` | `server name`, `CSV player names` |
| `GetServers` | Returns a list of all servers. | N/A | `CSV server names` |
| `Message` | Sends a message to the specified player. | `player name`, `message` | N/A |
| `GetServer` | Returns the server the player is connected to. | N/A | `server name` |
| `UUID` | Returns the UUID of player. | N/A | `UUID` |
| `UUIDOther` | Returns the UUID of the specified player. | `player name` | `player name`, `UUID` |
| `ServerIp` | Returns the IP of the specified server. | `server name` | `server name`, `IP`, `port` |
| `KickPlayer` | Kicks the specified player. | `player name`, `reason` | N/A |
| `Forward` | Forwards a plugin message to another server. | `server`, `subchannel`, `size of plugin message`, `message` | `subchannel`, `size of plugin message`, `message` |
| `ForwardToPlayer` | Forwards a plugin message to another player. | `player name`, `subchannel`, `size of plugin message`, `message` | `subchannel`, `size of plugin message`, `message` |

#### Example: `PlayerCount`

```java
public class MyPlugin extends JavaPlugin implements PluginMessageListener {

@Override
public void onEnable() {
this.getServer().getMessenger().registerOutgoingPluginChannel(this, "BungeeCord");
this.getServer().getMessenger().registerIncomingPluginChannel(this, "BungeeCord", this);

Player player = ...;
ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.writeUTF("PlayerCount");
out.writeUTF("lobby");
player.sendPluginMessage(this, "BungeeCord", out.toByteArray());
// The response will be handled in onPluginMessageReceived
}

@Override
public void onPluginMessageReceived(String channel, Player player, byte[] message) {
if (!channel.equals("BungeeCord")) {
return;
}
ByteArrayDataInput in = ByteStreams.newDataInput(message);
String subchannel = in.readUTF();
if (subchannel.equals("PlayerCount")) {
// This is our response to the PlayerCount request
String server = in.readUTF();
int playerCount = in.readInt();
}
}
}
```

#### Example: `Forward`

```java
public class MyPlugin extends JavaPlugin implements PluginMessageListener {

@Override
public void onEnable() {
this.getServer().getMessenger().registerOutgoingPluginChannel(this, "BungeeCord");
this.getServer().getMessenger().registerIncomingPluginChannel(this, "BungeeCord", this);

Player player = ...;
ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.writeUTF("Forward");
out.writeUTF("ALL"); // This is the target server. "ALL" will message all servers appart from the one sending the message
out.writeUTF("SecretInternalChannel"); // This is the channel.

ByteArrayOutputStream msgbytes = new ByteArrayOutputStream();
DataOutputStream msgout = new DataOutputStream(msgbytes);
msgout.writeUTF("Paper is the meaning of life"); // You can do anything you want with msgout
msgout.writeShort(42); // Writing a random short

out.writeShort(msgbytes.toByteArray().length); // This is the length.
out.write(msgbytes.toByteArray()); // This is the message.

player.sendPluginMessage(this, "BungeeCord", out.toByteArray());
// The response will be handled in onPluginMessageReceived
}

@Override
public void onPluginMessageReceived(String channel, Player player, byte[] message) {
if (!channel.equals("BungeeCord")) {
return;
}
ByteArrayDataInput in = ByteStreams.newDataInput(message);
String subchannel = in.readUTF();
if (subchannel.equals("SecretInternalChannel")) {
short len = in.readShort();
byte[] msgbytes = new byte[len];
in.readFully(msgbytes);

DataInputStream msgIn = new DataInputStream(new ByteArrayInputStream(msgbytes));
String secretMessage = msgIn.readUTF(); // Read the data in the same way you wrote it
short meaningofLife = msgIn.readShort();
}
}
}
```

This message is used to forward a plugin message to another server. This is useful for server-to-server communication within a proxy network.
For example, if a certain player is banned on one server, you can forward a message to all other servers to ban them there too.

:::caution[Example of banning a player on all servers]

This is not a recommended way to ban players due to the fact that there may not be anyone online on the target servers,
but it is an example of how this can be used.

:::
72 changes: 72 additions & 0 deletions docs/velocity/dev/how-to/plugin-messaging.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
---
slug: /dev/plugin-messaging
---

# Plugin Messaging

First introduced in [2012](https://web.archive.org/web/20220711204310/https://dinnerbone.com/blog/2012/01/13/minecraft-plugin-channels-messaging/),
Comment thread
olijeffers0n marked this conversation as resolved.
Plugin messaging is a way for Paper plugins to communicate with clients.

## Compatibility with BungeeCord

When your servers are behind a proxy, it will allow your Paper plugins to communicate with the proxy server.
By default, your Velocity server will respond to the `bungeecord:main` channel unless you have disabled
`bungee-plugin-message-channel` in the Velocity configuration. Let's take a look at how plugin messaging works,
and more importantly, how your Velocity server is already prepared to handle it.

## Sending Plugin Messages

Firstly, our Velocity server will need to register that it will be sending on any given plugin channel.
You should to do this alongside your other event listener registrations:

```java
@Subscribe
public void onProxyInitialization(ProxyInitializeEvent event) {
proxyServer.getChannelRegistrar().register(MinecraftChannelIdentifier.from("bungeecord:main"));
}
```

Then, you can send messages on the `bungeecord:main` channel.
Plugin messages are formatted as byte arrays and can be sent using the `sendPluginMessage` method on a `Player` object.

Let's take a look at an example of sending a plugin message to the `bungeecord:main` channel to send our player to another server.

```java
@Subscribe
public void onPlayerChat(PlayerChatEvent event) {
Player player = event.getPlayer();

ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.writeUTF("Connect");
out.writeUTF("hub2");
player.sendPluginMessage(plugin, "bungeecord:main", out.toByteArray());
}
```

## Receiving Plugin Messages

Now that we've sent a plugin message, we'll need to receive it on the other end.
This is done by registering a listener for the PluginMessageEvent.

```java
@Subscribe
public void onPluginMessage(PluginMessageEvent event) {
if (!event.getIdentifier().getId().equals("identifier")) {
return;
}

ByteArrayDataInput in = ByteStreams.newDataInput(event.getData());
String subChannel = in.readUTF();

if (subChannel.equals("Connect")) {
String server = in.readUTF();
// Do something with the server name
}
}
```

:::tip[The "bungeecord" specification]

See [here](/paper/dev/plugin-messaging#bungeecord-plugin-message-types) for a list of all the plugin messages that BungeeCord / Velocity supports.

:::