Skip to content

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 ​

ChannelDirectionPayloadPurpose
appeltools:chatproxy → paperString (sender UUID, msg, channel)Cross-server chat broadcast (after Velocity has filtered/queued it).
appeltools:commandpaper → proxyString[] (server, sender, command)A paper node ran a command that needs proxy-side action (e.g. /msg reply from another server).
appeltools:staffmodebothUUID, booleanToggles staff mode; updates per-player state on each node.
appeltools:staffchatbothUUID, StringStaff-chat broadcasts.
appeltools:maintenanceproxy → paperbooleanMaintenance on/off; paper nodes begin/stop kicking non-bypass.
appeltools:freezeproxy → paperUUID, booleanToggle freeze on/off for a target player.
appeltools:switchpaper → proxyUUID, 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:freeze

8.4 Channel Reference (per-flow) ​

8.4.1 Chat (appeltools:chat) ​

Velocity → Paper

When a player chats in Velocity's ChatListener, the message:

  1. Goes through SignedQueue for moderation.
  2. Is broadcast on Velocity to all subscribers.
  3. Is forwarded to every paper server as a chat so the local Bukkit AsyncPlayerChatEvent fires (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:freeze

The 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 enforcement

Staff 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

Built with VitePress.