The Bungee & Bukkit Plugin Messaging Channel

Discussion in 'BungeeCord Plugin Development' started by Cue, Feb 1, 2013.

  1. Cue

    Cue

    NOTE: This thread is for Bukkit plugins communicating with Bungee.

    Bukkit has a system for plugins to message each other (well, server<->client apparently), which Bungee hooks into along the lines as well. This allows you to send messages between Bungee and Bukkit and is how Janus & co. move you between servers.

    Simply put, here is the basic code to send a message to Bungee from Bukkit to connect to another server:

    Code (Java):
    ByteArrayOutputStream b = new ByteArrayOutputStream();
    DataOutputStream out = new DataOutputStream(b);
     
    try {
        out.writeUTF("Connect");
        out.writeUTF("pvp"); // Target Server
    } catch (IOException e) {
        // Can never happen
    }
    p.sendPluginMessage(this.plugin, "BungeeCord", b.toByteArray());
     
    (Assume p is a player instance)

    There are a number of currently known "subchannels" (such as Connect) you can send to Bungee right now, these are:

    • Connect - Connects player to another server.
    • Forward - Presumably forward a message on to another server? Take a server name or ALL.
    • PlayerCount - Gets the number of players on a server
    • PlayerList - Gets the player list on a server
    • GetServers - Gets the global server list
    • IP - Gets the IP of the player
    Connect's snippet is shown above, but for the others, from what I can make out here are the others. For the following snippets, assume that anything outside of the try block has been initialised/will be sent, etc. Basically, this code is what you would put inside the try { }

    Forward
    Code (Java):
    String data = "This is a sample message";
    out.writeUTF("Forward");
    out.writeUTF("pvp"); // Target Server, can be ALL to send to all servers.
     
    /* A "subchannel" much like Forward, Connect, etc. Think of it as a way of identifying what plugin sent the message to Bungee, I guess? It's mainly for plugin communication between servers I'd say */
    out.writeUTF("ExamplePlugin");
    out.writeShort(data.length); // The length of the rest of the data.
    out.writeUTF(data); // Write out the rest of the data.
    The forward subchannel is an odd one, I have yet to get it to work properly but I'm still tinkering. May be a future thing to put into Bungee: logging options, so we can have a debug log that logs messages sent/received.

    PlayerCount*
    Code (Java):
    out.writeUTF("PlayerCount");
    out.writeUTF("pvp"); // Server to get count from.
    PlayerList*
    Code (Java):
    out.writeUTF("PlayerList");
    out.writeUTF("pvp"); // Server to get list from.
    GetServers*
    Code (Java):
    out.writeUTF("GetServers");
    IP*
    Code (Java):
    out.writeUTF("IP");

    That's all there is to it for these, though it's not that simple:

    * All methods above denoted by an asterisk are non-blocking, when the message is sent, Bungee will process it and send something back as that's what expected, but not in the same cycle or method. To receive the message again, you must register the incoming plugin channel. Just to reiterate, this should be treated like any other event, rather than within the method you sent the message from.

    Code (Java):
    Bukkit.getMessenger().registerIncomingPluginChannel(this, "BungeeCord", messageListener);
    messageListener is just an instance of a class that inherits from PluginMessageListener, much like event and command executors are managed (as class properties remember!).

    As for the listener itself:

    Code (Java):
    import org.bukkit.entity.Player;
    import org.bukkit.plugin.messaging.PluginMessageListener;
     
    public class MyFirstMessageListener implements PluginMessageListener {
     
        private MyFirst plugin;
     
        public MyFirstMessageListener(MyFirst plugin)
        {
            this.plugin = plugin;
        }
     
        @Override
        public void onPluginMessageReceived(String channel, Player player, byte[] message) {
            this.plugin.getLogger().info("Got Plugin Message on " + channel + " from " + player.getName() + " messge was: " + message.toString());
        }
    }
    And that's that.

    As a disclaimer: Not all of this may be 100% accurate, I'm still having problems with the Forward channel and it's currently 2:40am here so I may not have been as coherent or accurate as possible. But it's something to build on!

    Now then, let's try and build on this a bit and see if we can get some more polish on this rather brief doc, no?
     
    #1 Cue, Feb 1, 2013
    Last edited by a moderator: Feb 1, 2013
    • Useful x 17
    • Like x 6
    • Agree x 4
    • Informative x 4
    • Winner x 1
    • Optimistic x 1
  2. Great doc. I like it. As for one question of mine. Is there a way to communicate with more than one server other than

    Code (Text):
    out.writeUTF("pvp");
    To something such as:

    Code (Text):
    out.writeUTF("all");

    And also, if I wanted to do a cross server chatting thing, I could use the writeUTF then the message as using the listener get the message then broadcast it to all players?
     
    #2 hcherndon, Feb 1, 2013
    Last edited: Feb 1, 2013
    • Agree Agree x 1
  3. Cue

    Cue

    Only on the Forward channel, as far as I know.
     
    • Funny Funny x 1
  4. CustomForms

    CustomForms Retired Moderator
    Retired Supporter

    How do i even go about creating a bungee plugin, and how do i use it. I already have some code for across server chat, but im not sure how to implement it.
    Cue
     
    • Funny Funny x 2
  5. md_5

    Administrator Developer

    Thanks, stickied.
    I will be writing an official BungeeCord Bukkit plugin, so plugins can use that API and not deal with all this, or break when I change internal implementations, but I need a name, any idea?

    EDIT: Also Cue the IOException can never ever ever be triggered, so leaving that code blank is fine.
     
    #5 md_5, Feb 1, 2013
    Last edited: Feb 1, 2013
    • Like Like x 5
    • Creative Creative x 2
    • Agree Agree x 1
    • Friendly Friendly x 1
  6. :eek:

    :eek:

    :D <3

    I will be waiting for that.

    And maybe something like. I don't know. BungeeMe? md_5 xD
     
  7. Cue

    Cue

    Coincidentally I was working on one, nothing much, just one with calls to the different functions and a listener.

    And I know, but I hate leaving catch blocks blank. ^_~
     
    • Winner Winner x 1
  8. Cue I tried to use the sample you showed for Forward, but I can't use out.write(data); when I define data as a String...
     
  9. This is indeed very helpful.
     
    • Agree Agree x 1
  10. Forward seems to be broken :/

    When using
    Code (Text):
        @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
        public void onPlayerChat(AsyncPlayerChatEvent event) {
            if (event.getMessage().charAt(0) == '!') {
     
                byte[] ba = new byte[3];
                ba[0] = 'A';
                ba[1] = 'B';
                ba[2] = 'C';
     
                ByteArrayOutputStream bo = new ByteArrayOutputStream();
                DataOutputStream out = new DataOutputStream(bo);
                try {
                    out.writeUTF("Forward");
                    out.writeUTF("ALL");
                    out.writeUTF("chat");
                    out.writeShort(ba.length);
                    out.write(ba);
                } catch (IOException e) {
                }
     
                event.getPlayer().sendPluginMessage(this.plugin, "BungeeCord", bo.toByteArray());
                event.setCancelled(true);
            }
        }
    I am getting [email protected]/main/java/net/md_5/bungee/UserConnection.java#L410
    When using only one Server as target, nothing happens.

    subChannel "Connect" is working fine.

    md_5: is there a bug in src/main/java/net/md_5/bungee/UserConnection.java on Line 407-411
    Code (Text):
    for (String s : BungeeCord.getInstance().getServers().keySet())
    {
        Server server = BungeeCord.getInstance().getServer(s);
        server.sendData("BungeeCord", b.toByteArray());
    }
    and on line 414-418
    Code (Text):
    Server server = BungeeCord.getInstance().getServer(target);
    if (server != null)
    {
        server.sendData("BungeeCord", b.toByteArray());
    }
    ?
     
  11. md_5

    Administrator Developer

    Fixing above issue as detailed on IRC.

    Decided on Trampoline as the client name.
     
    • Like Like x 1
    • Winner Winner x 1
  12. Cue

    Cue

    Apologies, made the correction, I was working off the Bungee code which has it as a byte.
     
  13. Lol, how do you come up with these names md_5?
     
  14. I wonder the same. But then again, I like them. He should go ahead and just make a series of stuff and call it, "The Elastics". :3
     
    • Agree Agree x 1
  15. I'm going to take credit for that one!


    https://github.com/ElasticPortalSuite/
     
    • Funny Funny x 1
  16. The Plugin Messaging Channel seems not to be suited to send messages/commands to empty servers.

    I have been working on a chat & command plugin that uses the Plugin Message Channel.
    But there is a big issue that i don't like: If I send a message on server A and then switch to server B the message is shown again. Same goes for server commands, that i issue via my plugin.

    All forward messages for an empty server are queued and then send via a player that connects to it.

    That means I cannot send data to an empty server. Am i right?

    Is there some other way?
    I want to use the bungeecord channel. I don't want to write a seperate communication interface. ^^' (I'm lazy)

    I'm using a class for the Foward Messaging, so i can easily escape the byte[] data to a sendable object.
    This is the sending method:

    Code (Text):
        public void send(PluginMessageRecipient pmr, Plugin plugin)
                throws IOException {
            ByteArrayOutputStream bo = new ByteArrayOutputStream();
            DataOutputStream out = new DataOutputStream(bo);
     
            out.writeUTF("Forward");
            out.writeUTF(target);
            out.writeUTF(type.toString());
            out.writeShort(data.length);
            out.write(data);
     
            pmr.sendPluginMessage(plugin, "BungeeCord", bo.toByteArray());
     
        }
    "type" is for my subchannels. :p
    Note: pmr has to be a player, if not this does nothing.

    I would wish for a method to send data to empty servers, but I know that BungeeCord hooks into the Player streams.
    The first way i thought of, was to use a timestamps and then filter the messages, but for other stuff, like *realtime* commands this is just not working.
    One other way could be to connect a "fake" player to the servers, but this would be awkward.

    Until there's a nice solution, i start working on a communication plugin for bukkit.
    I'll let you guys know, when it's finished.
     
  17. md_5

    Administrator Developer

    mickare
    All forward messages for an empty server are queued and then send via a player that connects to it.

    As you said, pretty much sums it up. The only option I can look into in the future is logging in with a dummy player to dispatch messages when there is no one online.
     
    • Agree Agree x 3
    • Useful Useful x 2
  18. Can someone please show an example of how to deal with the plugin message once you receive it. For example im asking for the PlayerList via plugin message to bungee and bungee replies with a message. How do i get the list from the byte[] message.
     
  19. bloodsplat
    Look at: https://github.com/ElasticPortalSui...java/net/md_5/bungee/UserConnection.java#L447

    The List is a simple String that uses "," as delimiter.
    To get the String list just use:
    Code (Text):
    DataInputStream in = new DataInputStream(new ByteArrayInputStream(message.data));
    String channel = in.readUTF();
    String server = null;
    String[] playernames = new String[0];
    if(channel.equals("PlayerList")) {
    server = in.readUTF();
    playernames = in.readUTF().split(",");
    }
    *not tested, just written in browser window*

    Same goes for ServerList.
     
    #20 mickare, Feb 8, 2013
    Last edited: Feb 8, 2013
    • Like Like x 1