[TUTORIAL] Advanced Plugin Messaging! (Spigot & BungeeCord)

Discussion in 'Spigot Discussion' started by tomicake, Mar 5, 2015.

  1. Hello,

    this tutorial is for developers, who could use the Plugin Messaging Channel of BungeeCord and Spigot (may not recommended for 1 BungeeCord & 1 Bukkit/Spigot Server) to send an information from Spigot/Bukkit to BungeeCord and back! This is useful if you don't have / want MySQL. But its recommended, because its very easier to handle I think!

    Lets go!

    Spigot:

    Ok, first we have to register Plugin Channels, save a new Class, wich implemets PluginMessageListener and register a new Command
    Code (Text):
    public class Main extends JavaPlugin {

        public static PluginChannelListener pcl;

        @Override
        public void onEnable(){
            Bukkit.getMessenger().registerOutgoingPluginChannel(this, "BungeeCord");
            // allow to send to BungeeCord
            Bukkit.getMessenger().registerIncomingPluginChannel(this, "Return", pcl = new PluginChannelListener());
            // gets a Message from Bungee
     
            getCommand("get").setExecutor(new GetFromBungeeCord());
        }
    }
    In our new PluginChannelListener Class:

    Create a new HashMap <Player, Object>. In this hashmap later we put the playerbinded Object (Integer or String). Then we have to make the onPluginMessageReceived Method synchronized and create a new Method, wich returns our Object.

    Code (Text):
    public class PluginChannelListener implements PluginMessageListener {

        private static HashMap<Player, Object> obj = new HashMap<Player, Object>();

        @Override
        public synchronized void onPluginMessageReceived(String channel, Player player, byte[] message) {
     
        }

        public synchronized Object get(Player p, boolean integer) {  // here you can add parameters (e.g. String table, String column, ...)
            return null;
        }

    }
    In the onPluginMessageReceived method we make a new DataInputStream.
    Code (Text):
    @Override
        public synchronized void onPluginMessageReceived(String channel, Player player, byte[] message) {
     
            DataInputStream in = new DataInputStream(new ByteArrayInputStream(message));
     
        }
    Checking subchannel, saving input to the hashmap and notifyAll.
    Code (Text):
    @Override
        public synchronized void onPluginMessageReceived(String channel, Player player, byte[] message) {
     
            DataInputStream in = new DataInputStream(new ByteArrayInputStream(message));
            try {
                String subchannel = in.readUTF();
                if(subchannel.equals("get")){
                    String input = in.readUTF();
                    obj.put(player, input);
             
                    notifyAll();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
     
     
        }
    Now the send and get method:

    Sending a message to BungeeCord. Send. Wait. Return.
    Code (Text):
    public synchronized Object get(Player p, boolean integer) {  // here you can add parameters (e.g. String table, String column, ...)
            sendToBungeeCord(p, "get", integer ? "points" : "nickname");
     
            try {
                wait();
            } catch(InterruptedException e){}
     
            return obj.get(p);
        }

        public void sendToBungeeCord(Player p, String channel, String sub){
            ByteArrayOutputStream b = new ByteArrayOutputStream();
            DataOutputStream out = new DataOutputStream(b);
            try {
                out.writeUTF(channel);
                out.writeUTF(sub);
            } catch (IOException e) {
                e.printStackTrace();
            }
            p.sendPluginMessage(Main.getPlugin(Main.class), "BungeeCord", b.toByteArray());
        }
    In our new GetFromBungeeCord Class:

    I create a simple testcommand to demonstrate, that its working:
    Code (Text):
    @Override
        public boolean onCommand(CommandSender sender, Command arg1, String arg2,
                String[] args) {
     
            if(sender instanceof Player){ // p.s. its only possible if its playerbinded not server!
                Player p = (Player) sender;
                if(args.length == 1){
                    String s = (String) Main.pcl.get(p, args[0].equalsIgnoreCase("nick"));
                    p.sendMessage(ChatColor.BLUE + "Got: " + "\n" + ChatColor.GREEN + s);
                }
            }
            return true;
     
        }
    BungeeCord:

    Register events.
    Code (Text):
    public class Main extends Plugin {
        public static HashMap<String, String> nicks = new HashMap<String, String>();
        public static HashMap<String, Integer> points = new HashMap<String, Integer>();
        // i know. there is a better way :)

        @Override
        public void onEnable(){
            nicks.put("TomiCake", "Mushroom");
            nicks.put("s31fds1", "aBetterName");
     
            points.put("TomiCake", 1223);
            points.put("s31fds1", 124);
            // i know. there is a much better way :)
     
            BungeeCord.getInstance().getPluginManager().registerListener(this, new ChannelListener());

            BungeeCord.getInstance().registerChannel("Return");
        }

    }
     
    In the Class ChannelListener we register the PluginMessageEvent.
    Code (Text):
    public class ChannelListener implements Listener {

        @EventHandler
        public void onPluginMessage(PluginMessageEvent e){
     
        }

    }
    This message is the message we sent from spigot. Now we decrypt the message and send the result back to Spigot.
    Code (Text):
    public class ChannelListener implements Listener {

        @EventHandler
        public void onPluginMessage(PluginMessageEvent e) {
            if (e.getTag().equalsIgnoreCase("BungeeCord")) {
                DataInputStream in = new DataInputStream(new ByteArrayInputStream(e.getData()));
                try {
                    String channel = in.readUTF(); // channel we delivered
                    if(channel.equals("get")){
                        ServerInfo server = BungeeCord.getInstance().getPlayer(e.getReceiver().toString()).getServer().getInfo();
                        String input = in.readUTF(); // the inputstring
                        if(input.equals("nickname")){
                            sendToBukkit(channel, Main.nicks.get(e.getReceiver().toString()), server);
                        } else {
                            sendToBukkit(channel, Main.points.get(e.getReceiver().toString()).toString(), server);
                        }
                 
                    }
                } catch (IOException e1) {
                    e1.printStackTrace();
                }
         
            }
        }

        public void sendToBukkit(String channel, String message, ServerInfo server) {
            ByteArrayOutputStream stream = new ByteArrayOutputStream();
            DataOutputStream out = new DataOutputStream(stream);
            try {
                out.writeUTF(channel);
                out.writeUTF(message);
            } catch (IOException e) {
                e.printStackTrace();
            }
            server.sendData("Return", stream.toByteArray());

        }
    }
    Thats all. I hope it works! Have Fun!
    If you have a improvement proposal, let me know!

    Special Thanks to the Wiki :D
    Bukkit & Bungee Plugin Messaging Channel
    (brajo, Dmck2b, Dead-i, EncryptedCurse, Tux, Maximvdw, joshwenke, libraryaddict, md_5, JWF)


    ~ TomiCake
     
    #1 tomicake, Mar 5, 2015
    Last edited: Mar 8, 2015
    • Like Like x 16
    • Useful Useful x 11
    • Informative Informative x 2
  2. Nice Tut, maybe add some new features?
     
    • Like Like x 1
  3. Thanks, the new feature is getting an Information from Bungee. Thats very useful if you have many servers and you don't want to connect everyone with MySQL or so.
     
    • Like Like x 1
  4. Thanks! Helped a lot :)
     
  5. I only had a quick skim over it since I don't have much time but it seems good. :) Thanks
     
  6. Hey,

    i will write an API for that. Its better to update and for programming...

    Thank for your replies :)

    ~ TomiCake
     
  7. cow

    cow

    i have a question:
    is there any limit on the length of a message that can be sent through a player?
     
  8. Make sure to close resources, or it will lead to memory leaks as I understand it.
     
  9. This was a tremendous help, I will definitely be using this in my upcoming plugins!
     
  10. Wbb

    Wbb

    you just slove my question!! I only read how to message from spigot to bungeecord on wiki, but don't know how to send my own message from bg to spigot! love you
     
  11. Nice tutorial!
     
  12. Thank you for writing this tutorial!
     
  13. thank goodness i found this tutorial!

    Thank you so much :)
     
    • Winner Winner x 1
  14. MiniDigger

    Supporter

    don't think that this is a good idea...
    plugin messages are async, don't force them to by sync. you basically tell the server, do nothing till the answer is here. so if for some reason your messages takes longer, everything on the server needs to wait. your tps will drop drastically as your server is basically frozen.
    You should work with callbacks.
    send should take a callback that will get execute when the message is there. to send should send the message and put the callback into a map. if you receive the message (that could be in 1 ms or a 1minute (or even never, if something goes wrong)) you get the callback out of the map and execute it. that way your server can still do stuff while you wait on the message.
     
    • Agree Agree x 2
  15. @MiniDigger This is outdated. Blocking the main thread while message receives makes no sense. Also it isnt very useful because you need one player to be online on each server. Making secondary connections via Netty could avoid these problems.
     
  16. MiniDigger

    Supporter

    the plugin message api is really useful, you just used it wrong, as explained in my comment. for many things, the one player limitation is not a problem (like you don't care about chat messages from another server on a empty server where no one well see them anyways)
    sure, your own socket connection could work too and work around that one limitation the system has, but it is way more complicated to learn, setup and use.
     
    • Agree Agree x 1