Util TextBuilder - Create JSON messages easily

Discussion in 'Resources' started by Reflxction, May 4, 2018.

  1. Offline

    Reflxction

    Note: I know I use many if statements in order to create events, as I was not sure if there was any easier way to do it that is actually efficient and will work for ANY type of events and text, without making the user type any external JSON stuff, so if you know any better way I'd love to hear it.

    So, after suffering a horrible fate trying to get JSON messages to work, I finally got them using the well-known way that we make using the ChatSerializer method, where we would be extremely careful not to mess up the curly and squared brackets, and get the syntax correctly. Not gonna lie, this was probably very hard and time-consuming. I made this utility class to end this pain for me and for others.

    Examples:
    Hover events:
    1) Show text on hover
    Code:
                TextBuilder builder = new TextBuilder(ChatColor.GOLD + "Beep boop")
                       .setHoverEvent(TextBuilder.HoverEventType.SHOW_TEXT, ChatColor.AQUA + "Hover text example")
                         .buildText();
    [​IMG]

    2) Show item information on hover
    Code:
                TextBuilder builder = new TextBuilder(ChatColor.AQUA + "Give me a cake now")
                        .setHoverEvent(TextBuilder.HoverEventType.SHOW_ITEM, "{id:cake}")
                         .buildText();
    [​IMG]
    The value uses JSON, since I cannot predict the syntax of the items json syntax, you can use any of the online item generators and put it as a value for that.

    3) Show achievement on hover
    Code:
    TextBuilder builder = new TextBuilder(ChatColor.GRAY + "We're travelling 69 km/h")
                        .setHoverEvent(TextBuilder.HoverEventType.SHOW_ACHIEVEMENT, "onARail")
                         .buildText();
    For a full list of achievement names/values, visit http://minecraft.wikia.com/wiki/Achievements

    [​IMG]

    4) Show statistics on hover
    Code:
         TextBuilder builder = new TextBuilder(ChatColor.BLUE + "You've dealt a lot of damage!")
                        .setHoverEvent(TextBuilder.HoverEventType.SHOW_STATISTIC, "damageDealt")
                         .buildText();
    For a full list of statistic names, visit https://minecraft.gamepedia.com/Statistics

    Click events:
    1) Run command on click
    Code:
    TextBuilder builder = new TextBuilder(ChatColor.AQUA + "Shiny diamonds!")
                        .setClickEvent(TextBuilder.ClickEventType.RUN_COMMAND, "/give @p diamond")
                         .buildText();
    2) Suggest text on click (when the player clicks on the message it puts the text in their chat box)
    Code:
    TextBuilder builder = new TextBuilder(ChatColor.RED + "Click to obtain the server IP")
                        .setClickEvent(TextBuilder.ClickEventType.SUGGEST_TEXT, "blablabla.blablablabla.org")
                         .buildText();
                
    3) Open URL on click
    Code:
    TextBuilder builder = new TextBuilder(ChatColor.BLUE + "Click to go to the Bukkit forums!")
                        .setClickEvent(TextBuilder.ClickEventType.OPEN_URL, "https://bukkit.org/")
                         .buildText();
    okok, after doing all that, you can send the player this modified text using:
    Code:
    builder.sendMessage(player);
    Full class example:
    Code:
    import org.bukkit.Bukkit;
    import org.bukkit.ChatColor;
    import org.bukkit.command.Command;
    import org.bukkit.command.CommandExecutor;
    import org.bukkit.command.CommandSender;
    
    public class KillTrap implements CommandExecutor {
    
        @Override
        public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
            if(command.getName().equalsIgnoreCase("killtrap")) {
                TextBuilder builder = new TextBuilder(ChatColor.GREEN + "Click here for free diamonds!")
                        .setClickEvent(TextBuilder.ClickEventType.RUN_COMMAND, "/kill")
                        .setHoverEvent(TextBuilder.HoverEventType.SHOW_TEXT, ChatColor.AQUA + "Totally not a trap, click me!")
                         .buildText();
                Bukkit.getOnlinePlayers().forEach(builder::sendMessage);
            }
            return false;
        }
    }
    
    TextBuilder: https://gist.github.com/ReflxctionDev/84a5753a69f426f48ffe75f7ab3adcbb
    Special thanks to @PhantomUnicorns for creating the reflection part, which now adds support to any Minecraft version without the need to change imports or anything else
     
    Last edited: May 7, 2018
    Caedus and Zombie_Striker like this.
  2. Offline

    PhantomUnicorns

    You forgot an event; show_entity for hoverEvent. Other than that it looks good, I can code a reflection part for you if you want?
     
  3. Offline

    Reflxction

    Yeah I was aware of this event, however I thought it'd need its own separate method as it takes about 3-4 parameters, but I'll add this tomorrow probably. And I'm not sure what will reflection be needed for, other than providing multiple version support. If you could provide more info I'd be thankful
     
  4. Offline

    PhantomUnicorns

    I was thinking so people post one plugin and don't have to constantly upload and download a new one (for reflection) it's also really small for this one.
     
  5. Offline

    Reflxction

    @PhantomUnicorns Okay, can you provide me with one please? I'm bad at reflection :p I'll credit you in the post
     
  6. Offline

    PhantomUnicorns

    Here's the code (the send packet code with some fields it needs, it uses the minimal amount (you don't want reflection in the method or it will slow it down).

    Code (open)

    Code:
        private static Class<?> _CRAFTPLAYER_CLASS;
        private static Constructor<?> _PACKET_PLAY_OUT_CHAT_CONSTRUCTOR;
        private static Method _A_METHOD, _GETHANDLE_METHOD, _SEND_PACKET_METHOD;
        private static Field _PLAYER_CONNECTION_FIELD;
    
        static {
            String name = Bukkit.getServer().getClass().getName();
            name = name.substring(name.indexOf("craftbukkit.") + "craftbukkit.".length());
            final String _VERSION = name.substring(0, name.indexOf("."));
            try {
                Class<?> _ICHAT_BASE_COMPONENT_CLASS = Class
                        .forName("net.minecraft.server." + _VERSION + ".IChatBaseComponent");
                Class<?> _PACKET_PLAY_OUT_CHAT_CLASS = Class
                        .forName("net.minecraft.server." + _VERSION + ".PacketPlayOutChat");
                _CRAFTPLAYER_CLASS = Class.forName("org.bukkit.craftbukkit." + _VERSION + ".entity.CraftPlayer");
                Class<?> _ENTITYPLAYER_CLASS = Class.forName("net.minecraft.server." + _VERSION + ".EntityPlayer");
                Class<?> _PLAYER_CONNECTION_CLASS = Class.forName("net.minecraft.server." + _VERSION + ".PlayerConnection");
    
                _PACKET_PLAY_OUT_CHAT_CONSTRUCTOR = _PACKET_PLAY_OUT_CHAT_CLASS
                            .getConstructor(_ICHAT_BASE_COMPONENT_CLASS, byte.class);
    
                _A_METHOD = Class.forName("net.minecraft.server." + _VERSION + ".IChatBaseComponent$ChatSerializer")
                        .getMethod("a", String.class);
                _GETHANDLE_METHOD = _CRAFTPLAYER_CLASS.getMethod("getHandle");
                _SEND_PACKET_METHOD = _PLAYER_CONNECTION_CLASS.getMethod("sendPacket",
                        Class.forName("net.minecraft.server." + _VERSION + ".Packet"));
    
                _PLAYER_CONNECTION_FIELD = _ENTITYPLAYER_CLASS.getDeclaredField("playerConnection");
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    
        public static void send(Player player) {
            try {
                Object messageComponent = _A_METHOD.invoke(null, json);
                Object packet = _PACKET_PLAY_OUT_CHAT_CONSTRUCTOR.newInstance(messageComponent);
                Object craftPlayer = _CRAFTPLAYER_CLASS.cast(player);
                Object entityPlayer = _GETHANDLE_METHOD.invoke(craftPlayer);
                Object playerConnection = _PLAYER_CONNECTION_FIELD.get(entityPlayer);
                _SEND_PACKET_METHOD.invoke(playerConnection, packet);
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    
     
    Reflxction likes this.
  7. Offline

    TGamingStudio

    Nice work !
     
  8. Offline

    PhantomUnicorns

    Was looking at your code and you only allow one event per message and it complies the whole string everytime you set an event. I think you should change the way you handle this to compile the events at the end when they send/compile the message.
     
    Reflxction likes this.
  9. Offline

    Reflxction

    @PhantomUnicorns Ah thanks for reminding me with this. If you mean for example only 1 event of hover and not 2, it's impossible to have 2 hover events. If you mean only hover and not click, it does allow that (it works if you have a click event and a hover event altogether, check the "Full class example"). Thanks for telling me about that I should compile the string at the end, I've changed it to do that.
    I also added your reflection code, thanks, I just understood how reflections work and how to use them :D
     
    Last edited: May 7, 2018
  10. Offline

    PhantomUnicorns

    @xTechno_ You can have multiple click events and hover events.... Use [] and separate texts using {} example:
    [{text:"text"}, {text:"teststest"}]
    You can have a different click event and hover event for each.
     
    Reflxction likes this.
  11. I would suggest adding an option to use legacy formatting when possible, except in cases where hover or click events are used. It would greatly reduce the size of the packet sent to the player :)
     
    Reflxction likes this.
  12. Offline

    Reflxction

Share This Page