Resource JSON Clickable Texts

Discussion in 'Spigot Plugin Development' started by ExoticDev, Aug 19, 2017.

  1. Hello Spigot users, today I am going to be showing a useful method to make JSON clickable texts and runnables that is going to be ran when a user has clicked the text.

    So, we are going to need 2 classes, a Main class, and a Command class for saving some data, and if you want you can make a reflection class, but in this "tutorial" I am going to putting everything in the Main class.

    Let's start with the Main class:


    First off, let us begin with the onEnable and the fields.

    (Be sure to extend JavaPlugin and implement Listener)
    Code (Text):
    import org.bukkit.plugin.java.JavaPlugin;

    import org.bukkit.plugin.java.Listener;
    import java.util.ArrayList;
    import java.util.List;

    public class JSONMain extends JavaPlugin implements Listener {
     
        private static String MINECRAFT_SERVER;
     
        public void onEnable() {
            // Initialize the MINECRAFT_SERVER field to the NMS package for reflection later on.
            MINECRAFT_SERVER = "net.minecraft.server." + this.getServer().getClass().getPackage().getName().substring(23);
       
            this.getServer().getPluginManager().registerEvents(this, this);
        }

    Now we are going to make our Command class to save some data, something just like this:

    Code (Text):
    public class JSONCommand {
     
        private String command;
        private Runnable event;

        public JSONCommand(String command, Runnable event) {
            this.command = command;
            this.event = event;
        }

        public String getCommand() {
            return this.command;
        }

        public Runnable getEvent() {
            return this.event;
        }
    }
    Now we get in to the spicy stuff, first off, let's add two fields called lastID and commands, it should look like this:
    Code (Text):
    private static int lastID;
    private static List<JSONCommand> commands = new ArrayList<>();
    Now we need to make a method so we can get the JSON format with our text and of course the ClickEvent,
    Code (Text):
        public static String getJSON(Runnable onClickEvent, String displayText) {
            // Get our unique ID for our command.
            int uniqueID = lastID + 1;

            // Make a new JSONCommand instance for our command and clickevent
            // The command could be whatever, just make sure it's unique to any other.
            JSONCommand command = new JSONCommand("/runcodethroughforcedcommandid" + uniqueID, onClickEvent);
       
            this.commands.add(command);

            lastID = uniqueID;

            // And finally, let's return our JSON format with our comand and our displayText.
            return "{\"text\":\"" + displayText + "\",\"clickEvent\":{\"action\":\"run_command\",\"value\":\"" + command.getCommand() + "\"}}";
        }
    Now let's make a PlayerCommandPreprocessEvent event to run the runnable we specified in the Command class.
    Code (Text):
        @EventHandler
        public void onForcedJSONCommand(PlayerCommandPreprocessEvent event) {
            String command = event.getMessage();

            for(JSONCommand commands : commands) {
                if(commands.getCommand().equalsIgnoreCase(command)) {
                    System.out.println("^ [Do not worry about this command, it is used for clickable text messages] ^");
                    // Run our runnable since ensured the command is actually a json command.
                    commands.getEvent().run();

                    // Cancel the event since it isn't a command, and it would come up with "Unkown error blablabla"
                    event.setCancelled(true);
                }
            }
        }
    Now let's actually send the JSON message to a player, i will be doing this in the PlayerJoinEvent,
    Code (Text):
        @EventHandler
        public void onPlayerJoin(PlayerJoinEvent event) {
            Player player = event.getPlayer();
       
            // Don't worry, we will be doing this method next.
            sendMessageWithJSON(player, getJSON(new Runnable() {

                @Override
                public void run() {
                    // The code that will be ran when the user clicks the text.
                    // For now let's make the user fly like a bird.
                    player.sendMessage("REEEEEEEEEEEEEEEE");
                    player.setVelocity(player.getLocation().getDirection().multiply(420D).setY(420D));
                }
                // Here we specify our display text, you could use ChatColors if you'd really like to.
            }, ChatColor.AQUA + "Don't you dare click me"));
        }
    Now let's make the method to send the JSON message,
    Code (Text):
        public static void sendMessageWithJSON(Player player, String json) {
            Object packetPlayOutChat = null;

            try {
                packetPlayOutChat = getClass("PacketPlayOutChat").getConstructor(getClass("IChatBaseComponent"), byte.class).newInstance(getIChatBaseComponentFromJSONString(json), (byte) 1);
            } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | InstantiationException e) {
                e.printStackTrace();
            }

            sendPacket(player, packetPlayOutChat);
        }
    Now let's make our reflection utilities,
    Code (Text):
        public static Object getIChatBaseComponentFromJSONString(String json) {
            try {
                return getMethod(getChatSerializer(), "a", String.class).invoke(null, json);
            } catch (Exception exception) {
                exception.printStackTrace();
            }

            return json;
        }
     
        private static void sendPacket(Player player, Object packet) {
            Object nmsPlayer = null;
       
            try {
                nmsPlayer = getMethod(player.getClass(), "getHandle").invoke(player);
            } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e1) {
                e1.printStackTrace();
            }
       
            Object playerConnection = getFieldValue(nmsPlayer.getClass(), nmsPlayer, "playerConnection");

            try {
                getMethod(playerConnection.getClass(), "sendPacket", getClass("Packet")).invoke(playerConnection, packet);
            } catch (IllegalAccessException | InvocationTargetException e) {
                e.printStackTrace();
            }
        }
     
        private static Class<?> getClass(String className) {
            try {
                return Class.forName(MINECRAFT_SERVER + "." + className);
            } catch (Exception exception) {
                exception.printStackTrace();
            }

            return null;
        }
     
        private static Class<?> getChatSerializer() {
            Class<?> serializer = getClass("IChatBaseComponent$ChatSerializer");

            try {
                return serializer.newInstance().getClass();
            } catch (InstantiationException | IllegalAccessException e) {
                e.printStackTrace();
            }

            return null;
        }
     
        private static Method getMethod(Class<?> clazz, String methodName, Class<?>... parameters) {
            try {
                return clazz.getMethod(methodName, parameters);
            } catch (NoSuchMethodException | SecurityException e) {
                e.printStackTrace();
            }

            return null;
        }
     
        private static Object getFieldValue(Class<?> clazz, Object object, String fieldName) {
            Field field;

            try {
                field = clazz.getField(fieldName);
           
                return field.get(object);
            } catch (IllegalAccessException | NoSuchFieldException | SecurityException e) {
                e.printStackTrace();
            }

            return null;
        }
    }
    And that's it, the final product should look like this:
    Code (Text):
    import java.lang.reflect.Field;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    import java.util.ArrayList;
    import java.util.List;

    import org.bukkit.ChatColor;
    import org.bukkit.entity.Player;
    import org.bukkit.event.EventHandler;
    import org.bukkit.event.Listener;
    import org.bukkit.event.player.PlayerCommandPreprocessEvent;
    import org.bukkit.event.player.PlayerJoinEvent;
    import org.bukkit.plugin.java.JavaPlugin;

    public class JSONMain extends JavaPlugin implements Listener {
     
        private static List<JSONCommand> commands = new ArrayList<>();
        private static String MINECRAFT_SERVER;
        private static int lastID;
     
        public void onEnable() {
            MINECRAFT_SERVER = "net.minecraft.server." + this.getServer().getClass().getPackage().getName().substring(23);
     
            this.getServer().getPluginManager().registerEvents(this, this);
        }
     
        @EventHandler
        public void onPlayerJoin(PlayerJoinEvent event) {
            Player player = event.getPlayer();
     
            sendMessageWithJSON(player, getJSON(new Runnable() {

                @Override
                public void run() {
                    player.sendMessage("REEEEEEEEEEEEEEEE");
                    player.setVelocity(player.getLocation().getDirection().multiply(420D).setY(420D));
                }
         
            }, ChatColor.AQUA + "Don't you dare click me"));
        }
     
        @EventHandler
        public void onForcedJSONCommand(PlayerCommandPreprocessEvent event) {
            String command = event.getMessage();

            for(JSONCommand commands : commands) {
                if(commands.getCommand().equalsIgnoreCase(command)) {
                    System.out.println("^ [Do not worry about this command, it is used for clickable text messages] ^");

                    commands.getEvent().run();

                    event.setCancelled(true);
                }
            }
        }

        public static String getJSON(Runnable onClickEvent, String displayText) {
            int uniqueID = lastID + 1;

            JSONCommand command = new JSONCommand("/runcodethroughforcedcommandid" + uniqueID, onClickEvent);

            this.commands.add(command);

            lastID = uniqueID;

            return "{\"text\":\"" + displayText + ChatColor.RESET + "\",\"clickEvent\":{\"action\":\"run_command\",\"value\":\"" + command.getCommand() + "\"}}";
        }

        public static void sendMessageWithJSON(Player player, String json) {
            Object packetPlayOutChat = null;

            try {
                packetPlayOutChat = getClass("PacketPlayOutChat").getConstructor(getClass("IChatBaseComponent"), byte.class).newInstance(getIChatBaseComponentFromJSONString(json), (byte) 1);
            } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | InstantiationException e) {
                e.printStackTrace();
            }

            sendPacket(player, packetPlayOutChat);
        }
     
        public static Object getIChatBaseComponentFromJSONString(String json) {
            try {
                return getMethod(getChatSerializer(), "a", String.class).invoke(null, json);
            } catch (Exception exception) {
                exception.printStackTrace();
            }

            return json;
        }
     
        private static void sendPacket(Player player, Object packet) {
            Object nmsPlayer = null;
     
            try {
                nmsPlayer = getMethod(player.getClass(), "getHandle").invoke(player);
            } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e1) {
                e1.printStackTrace();
            }
     
            Object playerConnection = getFieldValue(nmsPlayer.getClass(), nmsPlayer, "playerConnection");

            try {
                getMethod(playerConnection.getClass(), "sendPacket", getClass("Packet")).invoke(playerConnection, packet);
            } catch (IllegalAccessException | InvocationTargetException e) {
                e.printStackTrace();
            }
        }
     
        private static Class<?> getClass(String className) {
            try {
                return Class.forName(MINECRAFT_SERVER + "." + className);
            } catch (Exception exception) {
                exception.printStackTrace();
            }

            return null;
        }
     
        private static Class<?> getChatSerializer() {
            Class<?> serializer = getClass("IChatBaseComponent$ChatSerializer");

            try {
                return serializer.newInstance().getClass();
            } catch (InstantiationException | IllegalAccessException e) {
                e.printStackTrace();
            }

            return null;
        }
     
        private static Method getMethod(Class<?> clazz, String methodName, Class<?>... parameters) {
            try {
                return clazz.getMethod(methodName, parameters);
            } catch (NoSuchMethodException | SecurityException e) {
                e.printStackTrace();
            }

            return null;
        }
     
        private static Object getFieldValue(Class<?> clazz, Object object, String fieldName) {
            Field field;

            try {
                field = clazz.getField(fieldName);
         
                return field.get(object);
            } catch (IllegalAccessException | NoSuchFieldException | SecurityException e) {
                e.printStackTrace();
            }

            return null;
        }
    }
    Code (Text):
    public class JSONCommand {
     
        private String command;
        private Runnable event;

        public JSONCommand(String command, Runnable event) {
            this.command = command;
            this.event = event;
        }

        public String getCommand() {
            return this.command;
        }

        public Runnable getEvent() {
            return this.event;
        }
    }

    This SHOULD work from versions 1.8 - 1.12 tell me if it isn't, and i'll fix it, c;
     
    #1 ExoticDev, Aug 19, 2017
    Last edited: Aug 19, 2017
  2. Or simply use Spigot's chat API, or the tellraw command.
     
    • Agree Agree x 1