Appearance
8. Plugin Messaging Channels
Paper and Velocity share the same MySQL database but do not share a JVM. They synchronize via Velocity plugin-message channels. Velocity is the sender/receiver for almost every cross-server fan-out; Paper consumes the messages via its PluginMessageListener.
All channel identifiers are prefixed appeltools: and are defined in common/.../Types/PluginChannels.java.
8.1 Channel Catalogue
| Channel | Direction | Payload | Purpose |
|---|---|---|---|
appeltools:chat | proxy → paper | String (sender UUID, msg, channel) | Cross-server chat broadcast (after Velocity has filtered/queued it). |
appeltools:command | paper → proxy | String[] (server, sender, command) | A paper node ran a command that needs proxy-side action (e.g. /msg reply from another server). |
appeltools:staffmode | both | UUID, boolean | Toggles staff mode; updates per-player state on each node. |
appeltools:staffchat | both | UUID, String | Staff-chat broadcasts. |
appeltools:maintenance | proxy → paper | boolean | Maintenance on/off; paper nodes begin/stop kicking non-bypass. |
appeltools:freeze | proxy → paper | UUID, boolean | Toggle freeze on/off for a target player. |
appeltools:switch | paper → proxy | UUID, String (serverName) | Player switched server; proxy-side caches serverName for the player. |
8.2 Velocity-side Wiring
velocity/.../Events/PluginMessageListener is the central dispatcher:
java
@Subscribe
public void onPluginMessage(PluginMessageEvent event) {
String channel = event.getIdentifier().getId();
if (!channel.startsWith("appeltools:")) return;
String[] data = new String(event.getData(), StandardCharsets.UTF_8)
.split("\u0000", -1);
switch (channel) {
case "appeltools:command" -> handleCommand(data);
case "appeltools:staffmode" -> handleStaffMode(data);
case "appeltools:switch" -> handleSwitch(data);
// ...
}
}The listener is registered in AppelToolsVelocity's bootstrap section.
Outbound (Velocity → Paper)
java
proxy.sendToServer(serverInfo, ByteStreams.newByteArrayOutputStream()
.writeUTF(...)
.toByteArray());
// or
proxy.sendPluginMessage("appeltools:chat",
payload.getBytes(StandardCharsets.UTF_8));The PluginMessageListener accepts outbound "back to proxy" channels (e.g. when a paper node sends a command that must be acted on).
8.3 Paper-side Wiring
paper/.../Events/PluginMessageListener:
java
@EventHandler
public void onPluginMessage(PluginMessageEvent event) {
String channel = event.getChannel();
if (!channel.startsWith("appeltools:")) return;
switch (channel) {
case "appeltools:chat" -> handleChat(event.getData());
case "appeltools:staffmode" -> handleStaffMode(event.getData());
case "appeltools:staffchat" -> handleStaffChat(event.getData());
case "appeltools:maintenance" -> handleMaintenance(event.getData());
case "appeltools:freeze" -> handleFreeze(event.getData());
}
}paper-plugin.yml declares the channels Velocity is allowed to send:
yaml
channels:
- appeltools:chat
- appeltools:command
- appeltools:staffmode
- appeltools:staffchat
- appeltools:maintenance
- appeltools:freeze8.4 Channel Reference (per-flow)
8.4.1 Chat (appeltools:chat)
Velocity → Paper
When a player chats in Velocity's ChatListener, the message:
- Goes through
SignedQueuefor moderation. - Is broadcast on Velocity to all subscribers.
- Is forwarded to every paper server as a chat so the local Bukkit
AsyncPlayerChatEventfires (allowing per-paper formatting/muting).
Payload: <senderUuid>\u0000<message>\u0000<channel>.
8.4.2 Command (appeltools:command)
Paper → Velocity
Paper captures certain commands (e.g. /msg reply) and forwards them to Velocity because the actual player might not be on this server.
Payload: <originServerName>\u0000<senderUuid>\u0000<command>.
8.4.3 Staff Mode (appeltools:staffmode)
Both
Syncs staff-mode state across servers. When a paper player enters staff mode (e.g. via plugin-message), the local ATPPlayer.staffModeToggled is updated and the inventory is saved.
Payload: <uuid>\u0000<boolean>.
8.4.4 Staff Chat (appeltools:staffchat)
Both
Broadcasts a staff-chat message to all staff online. Sent from Velocity when /sc is run, and from paper when /sc is run locally.
Payload: <senderUuid>\u0000<message>.
8.4.5 Maintenance (appeltools:maintenance)
Velocity → Paper
When /mt toggles maintenance, Velocity fans out to all paper servers so each can kick non-bypass players.
Payload: <boolean>.
8.4.6 Freeze (appeltools:freeze)
Velocity → Paper
When /freeze runs on Velocity, the freeze must be applied on the actual paper node where the player currently is. The channel lets the right paper server apply the listener.
Payload: <targetUuid>\u0000<boolean>.
8.4.7 Server Switch (appeltools:switch)
Paper → Velocity
Each paper node sends a "user X is now on me" message so Velocity's PlayerServerSwitchListener can keep player.serverName fresh for friend/party logic.
Payload: <playerUuid>\u0000<serverName>.
8.5 Why Plugin Messages (instead of direct API)
- Velocity and Paper are different processes with different APIs.
- Paper has no cross-server abstraction; Velocity does, but Paper is unaware.
- Plugin messages are Velocity's bidirectional channel for arbitrary bytes.
- The alternative (HTTP/MySQL polling) was rejected because it would add latency.
8.6 Channel Registration (Paper)
In paper-plugin.yml:
yaml
paper-loader:
channels:
- appeltools:chat
- appeltools:command
- appeltools:staffmode
- appeltools:staffchat
- appeltools:maintenance
- appeltools:freezeThe paper loader accepts only listed channels; any other inbound channel is ignored.
8.8 Reliability
- Plugin messages are at-most-once. They are not retried. If a paper node misses a maintenance message, it falls back to its local DB-mirrored state on next request.
- All persistent state is in MySQL. The plugin messages are for promptness, not durability.
8.9 Flow Examples
Maintenance flow:
/mt on (Velocity)
├─ MaintenanceCommand writes state to MySQL
├─ posts Embeds.createMaintenanceEmbed(true)
├─ PluginMessage → all Paper servers: "appeltools:maintenance true"
└─ Each Paper server's PluginMessageListener enables local enforcementStaff mode flow:
/smtoggle (Paper)
├─ ATPPlayer flips staffModeToggled
├─ StaffInventoryManager saves inventory
├─ StaffModeListener sets PremiumVanish + tool kit
├─ PluginMessage → Velocity: "appeltools:staffmode <uuid> true"
└─ Velocity updates ATVPlayer.staffModeToggled and posts to Discord