agentsclimarketplace

Generate plugin message handler

Skill MrPippi/MJP-Claude-Skills/.cursor/skills/velocity/generate-plugin-message-handler

Minecraft Java Plugin Claude Skills

Install
npx -y skills add MrPippi/MJP-Claude-Skills --skill generate-plugin-message-handler

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

為 Velocity proxy plugin 產生 PluginMessageEvent 處理器,支援自訂 namespaced channel 和 BungeeCord 相容 channel,含 ByteStreams 序列化、ForwardResult 設定、proxy-to-backend 回應範例。當使用者說「plugin messaging」、「PluginMessageEvent」、「proxy channel」、「傳訊息給後端伺服器」時自動應用。

SKILL.md

7.3 KB, as published. Nobody here has run it

Generate Plugin Message Handler (Velocity) / 產生 Velocity 插件訊息處理器

技能名稱 / Skill Name

generate-plugin-message-handler

目的 / Purpose

為 Velocity proxy plugin 產生完整的 plugin messaging 處理類別,含頻道註冊、入站訊息解析、proxy 向後端回應的範例。

觸發條件 / Triggers

  • 「plugin messaging on Velocity」
  • 「PluginMessageEvent」
  • 「proxy channel」
  • 「傳訊息給後端伺服器」
  • 「MinecraftChannelIdentifier」
  • 「backend proxy communication」

輸入參數 / Inputs

參數範例說明
plugin_packagecom.mynetwork.myplugin插件套件名稱
channel_namespacemyplugin頻道命名空間(小寫英數)
channel_nameproxy頻道名稱(結果為 myplugin:proxy
actions["BROADCAST", "MOVE_PLAYER", "PLAYER_COUNT"]要處理的 action 列表

輸出產物 / Outputs

  • messaging/ProxyMessagingHandler.java — 完整處理器類別

代碼範本 / Code Template

package {plugin_package}.messaging;

import com.google.common.io.ByteArrayDataInput;
import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.connection.PluginMessageEvent;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ProxyServer;
import com.velocitypowered.api.proxy.ServerConnection;
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
import net.kyori.adventure.text.minimessage.MiniMessage;
import org.slf4j.Logger;

public class ProxyMessagingHandler {

    // Channel identifier: "{channel_namespace}:{channel_name}"
    public static final MinecraftChannelIdentifier CHANNEL =
        MinecraftChannelIdentifier.from("{channel_namespace}:{channel_name}");

    private final ProxyServer server;
    private final Logger logger;

    public ProxyMessagingHandler(ProxyServer server, Logger logger) {
        this.server = server;
        this.logger = logger;
    }

    /** Call this from onProxyInitialize to register the channel. */
    public void register() {
        server.getChannelRegistrar().register(CHANNEL);
    }

    /** Call this from onProxyShutdown to clean up. */
    public void unregister() {
        server.getChannelRegistrar().unregister(CHANNEL);
    }

    /** Handles all incoming plugin messages on our channel. */
    @Subscribe
    public void onPluginMessage(PluginMessageEvent event) {
        // Filter: only our channel
        if (!event.getIdentifier().equals(CHANNEL)) return;

        // Filter: only accept messages from backend servers (not from players)
        if (!(event.getSource() instanceof ServerConnection sourceConn)) return;

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

        switch (action) {
            case "BROADCAST"     -> handleBroadcast(in);
            case "MOVE_PLAYER"   -> handleMovePlayer(in);
            case "PLAYER_COUNT"  -> handlePlayerCount(sourceConn);
            default              -> logger.warn("Unknown action from backend: {}", action);
        }

        // Mark as handled — prevents forwarding to the player or other listeners
        event.setResult(PluginMessageEvent.ForwardResult.handled());
    }

    // --- Action Handlers ---

    private void handleBroadcast(ByteArrayDataInput in) {
        String miniMessage = in.readUTF();
        server.getAllPlayers().forEach(p ->
            p.sendMessage(MiniMessage.miniMessage().deserialize(miniMessage))
        );
        logger.info("Proxy broadcast: {}", miniMessage);
    }

    private void handleMovePlayer(ByteArrayDataInput in) {
        String playerName  = in.readUTF();
        String targetServer = in.readUTF();

        server.getPlayer(playerName).ifPresentOrElse(
            player -> server.getServer(targetServer).ifPresentOrElse(
                srv   -> player.createConnectionRequest(srv).fireAndForget(),
                ()    -> logger.warn("Move failed: server '{}' not found", targetServer)
            ),
            () -> logger.warn("Move failed: player '{}' not found", playerName)
        );
    }

    private void handlePlayerCount(ServerConnection requester) {
        // Respond to the backend server that asked
        ByteArrayDataOutput out = ByteStreams.newDataOutput();
        out.writeUTF("PLAYER_COUNT_RESPONSE");
        out.writeInt(server.getAllPlayers().size());

        requester.getPlayer().sendPluginMessage(CHANNEL, out.toByteArray());
    }

    // --- Outbound: Proxy → Backend ---

    /**
     * Send a message from the proxy to a specific backend server.
     * Requires any player currently on that server as a routing conduit.
     */
    public void sendToBackend(String serverName, String action, java.util.function.Consumer<ByteArrayDataOutput> writer) {
        server.getServer(serverName).ifPresent(registeredServer -> {
            ByteArrayDataOutput out = ByteStreams.newDataOutput();
            out.writeUTF(action);
            writer.accept(out);
            byte[] payload = out.toByteArray();

            // Route via the first online player on that server
            registeredServer.getPlayersConnected().stream()
                .findFirst()
                .ifPresent(p -> p.sendPluginMessage(CHANNEL, payload));
        });
    }
}

Register in main plugin class:

private ProxyMessagingHandler messagingHandler;

@Subscribe
public void onProxyInitialize(ProxyInitializeEvent event) {
    messagingHandler = new ProxyMessagingHandler(server, logger);
    messagingHandler.register();
    server.getEventManager().register(this, messagingHandler);
}

@Subscribe
public void onProxyShutdown(ProxyShutdownEvent event) {
    if (messagingHandler != null) messagingHandler.unregister();
}

Backend (Paper) side — register incoming channel:

// In onEnable():
getServer().getMessenger().registerIncomingPluginChannel(
    this, "{channel_namespace}:{channel_name}",
    (channel, player, data) -> {
        ByteArrayDataInput in = ByteStreams.newDataInput(data);
        String responseAction = in.readUTF();
        // handle response...
    }
);
getServer().getMessenger().registerOutgoingPluginChannel(
    this, "{channel_namespace}:{channel_name}"
);

失敗回退 / Fallback

  • 如果沒有呼叫 event.setResult(ForwardResult.handled()),Velocity 會將訊息轉發給其他監聽者或玩家,可能造成重複處理。
  • 頻道命名空間必須全小寫英數,否則 MinecraftChannelIdentifier.from() 拋出異常。
  • sendToBackend() 需要目標伺服器上至少有一位玩家,否則訊息無法路由。若無玩家,需在伺服器本身掛載 ServerConnection 或使用其他 IPC 方案(如 Redis)。
  • 請確認後端 Paper 插件已用 registerIncomingPluginChannel()registerOutgoingPluginChannel() 雙向註冊同一頻道。

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.