docs/paper/messaging/SKILL.md
# Plugin Messaging Skill — Paper ## Purpose Reference this skill when a Paper backend plugin needs to send or receive data to/from a Velocity or Waterfall proxy using the plugin messaging channel system. ## When to Use This Skill - Sending a player to a different backend server via the proxy - Getting the IP of the proxy the player connected through - Notifying the proxy of a game event (e.g., game over, player rank change) - Receiving instructions from the proxy plugin (e.g., switch server, a
npx skillsauth add MrPippi/MPS docs/paper/messagingInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Reference this skill when a Paper backend plugin needs to send or receive data to/from a Velocity or Waterfall proxy using the plugin messaging channel system.
| Class / Method | Purpose | Notes |
|---------------|---------|-------|
| Messenger#registerOutgoingPluginChannel(Plugin, String) | Allow plugin to send on this channel | Required before sending |
| Messenger#registerIncomingPluginChannel(Plugin, String, PluginMessageListener) | Register to receive messages on channel | Callback fires async |
| Player#sendPluginMessage(Plugin, String, byte[]) | Send a plugin message via a player | Player must be online |
| PluginMessageListener | Interface for receiving plugin messages | Implement onPluginMessageReceived |
| ByteArrayDataOutput (Guava) | Serialise message payload | ByteStreams.newDataOutput() |
| ByteArrayDataInput (Guava) | Deserialise received payload | ByteStreams.newDataInput(bytes) |
| "BungeeCord" | Standard proxy communication channel | Works with BungeeCord, Waterfall, Velocity (compat mode) |
| "minecraft:brand" | Built-in brand channel | Do not use for custom data |
package com.yourorg.myplugin.messaging;
import com.google.common.io.ByteArrayDataInput;
import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.plugin.messaging.PluginMessageListener;
import org.slf4j.Logger;
public class MessagingHandler implements PluginMessageListener {
private static final String CHANNEL = "BungeeCord";
private final JavaPlugin plugin;
private final Logger logger;
public MessagingHandler(JavaPlugin plugin) {
this.plugin = plugin;
this.logger = plugin.getSLF4JLogger();
}
public void register() {
plugin.getServer().getMessenger()
.registerOutgoingPluginChannel(plugin, CHANNEL);
plugin.getServer().getMessenger()
.registerIncomingPluginChannel(plugin, CHANNEL, this);
}
public void unregister() {
plugin.getServer().getMessenger()
.unregisterOutgoingPluginChannel(plugin, CHANNEL);
plugin.getServer().getMessenger()
.unregisterIncomingPluginChannel(plugin, CHANNEL, this);
}
// Send player to another backend server
public void connectToServer(Player player, String serverName) {
ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.writeUTF("Connect");
out.writeUTF(serverName);
player.sendPluginMessage(plugin, CHANNEL, out.toByteArray());
}
// Request the proxy to tell us which server this player is on
public void requestServerName(Player player) {
ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.writeUTF("GetServer");
player.sendPluginMessage(plugin, CHANNEL, out.toByteArray());
}
// Receive and dispatch incoming messages from the proxy
@Override
public void onPluginMessageReceived(String channel, Player player, byte[] message) {
if (!channel.equals(CHANNEL)) return;
// NOTE: This callback fires on an async thread.
ByteArrayDataInput in = ByteStreams.newDataInput(message);
String subChannel = in.readUTF();
switch (subChannel) {
case "GetServer" -> {
String serverName = in.readUTF();
logger.info("Player {} is on server: {}", player.getName(), serverName);
}
case "GetIP" -> {
String ip = in.readUTF();
logger.info("Player {} connected from IP: {}", player.getName(), ip);
}
default -> logger.warn("Unknown BungeeCord subchannel: {}", subChannel);
}
}
}
Register in onEnable, unregister in onDisable:
private MessagingHandler messagingHandler;
@Override
public void onEnable() {
messagingHandler = new MessagingHandler(this);
messagingHandler.register();
}
@Override
public void onDisable() {
messagingHandler.unregister();
}
Sending plugin messages without a player: Plugin messages must be sent through a player that is currently connected to this server. There is no "serverless" send. If no players are online, queue the message and send when the first player joins.
Using plugin messaging for large data: The BungeeCord channel has a 32767-byte payload limit. For large data transfers, use a shared database instead.
Forgetting to register the channel: Calling sendPluginMessage() on an unregistered outgoing channel will throw an exception. Always call registerOutgoingPluginChannel first.
Reading incoming messages on the main thread: The onPluginMessageReceived callback fires on an async thread. Do not mutate Bukkit objects inside it without scheduling back to the main thread.
Using BungeeCord channel with Velocity's modern forwarding: Velocity must have bungee-plugin-messaging-channel: true in velocity.toml for BungeeCord channel compatibility.
BungeeCord channel API unchanged. Guava's ByteStreams is bundled with Paper."yourplugin:channel" format and configure both sides. See plugin-channels.md.development
透過 NMS Scoreboard/Objective/Team API 操作 sidebar、tablist 顯示名稱與計分板(Paper NMS + Mojang-mapped)/ Operate sidebar, tablist, and scoreboard via NMS Scoreboard/Objective/Team API
research
操作 GameProfile 進行 skin 注入,用於 NPC 外觀設定與假玩家實體(Paper NMS + Mojang-mapped)/ Manipulate GameProfile for skin injection used in NPC appearance and fake player entities
tools
透過 ClientboundLevelParticlesPacket 實現進階 NMS 粒子效果:客戶端專屬、大量粒子、自定義參數(Paper NMS + Mojang-mapped)/ Advanced NMS particle effects via ClientboundLevelParticlesPacket with per-client and bulk support
documentation
直接操作 CompoundTag 讀寫物品、實體、方塊實體的 NBT 資料(Paper NMS + Mojang-mapped)/ Read and write NBT data on items, entities, and block entities via CompoundTag