[Lib] TechUtils - BossBars, ActionBars, Titles, Simple Config API, No-Flicker Scoreboards, and more

Discussion in 'Spigot Plugin Development' started by Techcable, May 29, 2015.

?

Most Wanted Feature

  1. Data API

    16.3%
  2. Command System

    9.8%
  3. No-Flicker Scoreboards

    73.9%
  1. TechUtils is the ultimate library for bukkit

    Features
    • Object Oriented Players
    • Action Bars
    • Boss Bars
    • Titles
    • No-Flicker scoreboards
    • Annotation-Based Configuration System
    • Automatically Caching UUID Lookup System
      • Uses MCPlayerIndex as it's primary source, but automatically detects when it goes down and uses mojang
    • Load offline player data
    • A scheduler system that doesn't require a plugin reference
    • A annotation-based "proxy system"
      • Inspired by the JRE's Proxy, this also allows extending from classes
    Maven Setup
    This library is designed to be shaded into plugins, so users don't have to download it.
    Code (XML):
    <repositories>
      <repository>
        <id>techcable-repo</id>
        <url>http://repo.techcable.net/content/groups/public/</url>
      </repository>
    </repositories>
    <dependencies>
      <dependency>
        <groupId>net.techcable</groupId>
        <artifactId>techutils</arifactId>
        <version>1.1.0-SNAPSHOT</version>
      </dependency>
    </dependencies>
    Plugins Using It
    • SpawnShield
    • CombatTag (versions greater than 7.0.0)
    • Tell me if your plugin uses it :)
    In Development Features
    • Command System
    • "Data API" -- (Stores custom player data)
    Links
    Github -- Source Code (Readme is very outdated)
    Github Issues -- Issues
    Jenkins -- Development Builds
    Javadocs -- Documentation
     
    #1 Techcable, May 29, 2015
    Last edited: May 30, 2015
    • Like Like x 2
    • Useful Useful x 1
  2. Reserved
     
    • Informative Informative x 1
  3. As I see it, it's a very very good plugin?
    But it will be really good, if you make some examples on it's usage.
    I'm really currious for the configuration API, because I also have one, and I want to see, if the usage of your system is better, than my or not :D
    So could you post an example for it's usage?

    Also, can I help you in the coding of this API? Because of, I have already done some not yet published APIs, which will be really useful... i.e. AnimationAPI and the API for these no flicker scoreboards. If you don't want my contribution in this project, then I will publish these APIs later, as a part of the SpigotLib :)
     
  4. It would be great if you contributed to the project.
    Ill give you contributor status to the repo after you do a few PRs.
    Here is usage for the configuration api:
    Code (Text):
    @Getter
    public class Configuration extends AnnotationConfig {
    @Setting("debug")
    private boolean debug;
    @Setting("npcs.enabled")
    private boolean spawnNpcs;
    @Setting("npcs.names")
    private List<String> names;
    }
    You have to include the default config file in the plugin file. Then it will automatically extract it if it doesn't exist.
     
  5. Very cool! Thank you very much :)

    EDIT: As it's under MIT, can we download the source and use it directly in our plugins?
     
  6. The idea is actually to shade it into your plugin using maven.
    If you don't use maven, just copy it to a different package (so it doesn't conflict with other plugins using TechUtils).
     
    • Like Like x 1
  7. Added a (no-flicker) thread safe Scoreboard System!
    There isn't any documentation right now, but basic usage is as follows:
    Code (Java):
    PrisonPlayer player = plugin.getPlayer(bukkitPlayer);
    final TechBoard board = player.getScoreboard();
    board.display();
    new BukkitRunnable() {
       @Override
       public void run() {
           board.aquireLock(); // Only neccicary when called async
           try {
           board.clear();
           board.addElement("Welcome to TechzoneMC Prison");
           board.addElement("Current Rank is "  + player.getRank().getName());
           board.addElement("Money till rank-up:", player.getBalance() - player.getNextRank().getCost());
           board.addElement("Vote to get $1000");
           board.addElement("Special prizes also available");
           } finally { board.releaseLock(); }
       }
    }.runTaskTimerAsynchronously(plugin, 0, 20);
     
    • Useful Useful x 1
  8. You should use it as a dependency, and not just copy the needed code to your project :)


    Great, can you contact me on Skype, to know, what kind of things should I do in the API now? My Skype name is gyuriskipe.

    So you need to annotate every field. What about the comments? And what about more complex data structure, and custom config serializers or string serializers? With my config system, you can save almost any kind of complex object, and also make custom serializers for simplifying the represantation of a config part.

    i.e. the configuration of my SpigotLib looks like:
    PHP:
    package gyurix.spigotlib;

    import gyurix.configfile.ConfigSerialization;

    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.LinkedHashSet;


    public class Config {
        @ConfigSerialization.ConfigOptions(comment = "Servers default language.")
        public static String defaultLang;
        @ConfigSerialization.ConfigOptions(comment = "Configurations version.")
        public static int version;
        @ConfigSerialization.ConfigOptions(comment = "Tab completion will need the tab.complete perm.")
        public static boolean tabCompletePerm;

        @ConfigSerialization.ConfigOptions(comment = "SpigotLib will handle player join messages.")
        public static boolean joinMessage;
        @ConfigSerialization.ConfigOptions(comment = "SpigotLib will handle player leave messages.")
        public static boolean leaveMessage;
        @ConfigSerialization.ConfigOptions(comment = "SpigotLib will handle player kick messages.")
        public static boolean kickMessage;

        @ConfigSerialization.ConfigOptions(comment = "Set special kick messages, which can't be set in Spigot.")
        public static CustomMessages customMessages;

        @ConfigSerialization.ConfigOptions(comment = "Path for auto backups on every save.")
        public static String backup;
        @ConfigSerialization.ConfigOptions(comment = "Debug mode, use it for error reporting.")
        public static final boolean debug = false;
        @ConfigSerialization.ConfigOptions(comment = "Chat settings.")
        public static Chat chat;
        @ConfigSerialization.ConfigOptions(comment = "Error log, please report all of these errors to plugins dev, gyuriX.")
        public static final ArrayList<String> errors = new ArrayList();

        @ConfigSerialization.ConfigOptions(comment = "Packet name mapping for PacketAPI to reach compatibility between different Minecraft versions.\n" +
                "DON'T CHANGE IT, IF YOU DON'T KNOW EXACTLY WHAT ARE YOU DOING!")
        public static HashMap<String,HashMap<String,String>> packetMapping;

        public static class Chat {
            @ConfigSerialization.ConfigOptions(comment = "Enable SpigotLib chat management.")
            public static boolean enabled;
            @ConfigSerialization.ConfigOptions(comment = "Asynchronising commands will speedup the server while executing more complex commands, butit may alse result unexpected problems.")
            public static LinkedHashSet<String> asyncCommands = new LinkedHashSet();
            @ConfigSerialization.ConfigOptions(comment = "List here all the color codes, that should be replaced, if the player has chat.color.<colorcode> perm.")
            public static String colors;
            @ConfigSerialization.ConfigOptions(comment = "The prefix before colorcodes.")
            public static String colorPrefix;
            @ConfigSerialization.ConfigOptions(comment = "Set the new line character, set it to empty for disabling this feature.\nYou need the chat.multiline permission for using this character.")
            public static String newLineCharacter;
            @ConfigSerialization.ConfigOptions(comment = "Allow writing empty lines with more then one, newline characters, if you have the chat.emptyLines permission.")
            public static boolean allowEmptyLines;
            @ConfigSerialization.ConfigOptions(comment = "Configuration for long messages.")
            public static Long longMessage;

            public static class Long {
                @ConfigSerialization.ConfigOptions(comment = "Enable using this feature, if you have chat.longer perm.")
                public static boolean enabled;
                @ConfigSerialization.ConfigOptions(comment = "If you write more, than the bellow set amount of characters, your message will be puffered, if not, your whole message will be sent.")
                public static int pufferAfter;
                @ConfigSerialization.ConfigOptions(comment = "Limit maximum length of longer messages, per permissionly (chat.longerlimit.<group> permissions)")
                public static HashMap<String, Integer> lengthLimit;
            }
        }

        public static class CustomMessages {
            public static String throotle="§e§lToo fast reconnect!";
            public static String wrongBungeeIp="§c§lAccess denied!";
            public static String ipForwarding="§c§lYou are only allowed to connect to this server through the BungeeCord, with ip forwarding ON";
            public static String invalidSetProtocolPacket="§c§lInvalid set protocol packet!";
            public static String wrongNextState="§c§lWrong next state data in set protocol packet!";
        }
    }
     
    Or a class with StringSerializable feature:
    PHP:
    public class BlockData implements ConfigSerialization.StringSerializable {
        public int id;
        public byte damage;
        public boolean anydamage=true;
        public BlockData(String in){
            try{
                String[] d=in.split(":",2);
                id=Integer.valueOf(d[0]);
                if (d.length==2){
                    anydamage=false;
                    damage=Byte.valueOf(d[1]);
                }
            }
            catch (Throwable e){
                e.printStackTrace();
            }
        }
        public BlockData(Block b){
            id=b.getTypeId();
            damage=b.getData();
        }

        @Override
        public String toString() {
            return damage==-1?""+id:id+":"+damage;
        }

        @Override
        public boolean equals(Object obj) {
            BlockData bd=(BlockData)obj;
            return (damage==-1||bd.damage==-1||damage==bd.damage)&&id==bd.id;
        }

        @Override
        public int hashCode() {
            return id;
        }
     
    #8 gyurix, May 30, 2015
    Last edited: May 30, 2015
  9. Can you post a tutorial of all the features?
     
    • Agree Agree x 1
  10. p
    As of commit 15d5734 complex object serialization is possible.
    I don't think you understand how my configuration system works. You specify a default config in the jar (just like plugin.yml) and my config system loads it if it doesn't already exist in the plugin's folder. All the comments and stuff are in there, in whatever format you like. Then my system loads into the config object.
    I don't really do skype, but i'm available on spigot irc's network in the #techcable channel.
    It would be nice if you could add a packet listener system.
    Here is my general design idea:
    Listen for AsyncPlayerPreJoinEvent
    Inject a ChannelDuplexHandler after the packet decoder and packet encoder.
    whenever a packet is received fire a AsyncPacketReceiveEvent
    whenever a packet is sent fire a AsyncPacketSendEvent
    Here is the tough part, this all has to be achived without an explicit dependency on netty, as techutils is not (yet) version-dependent.
    I have already layed the foundation for extending objects without actually extending them (done via runtime bytecode generation).
    Use TechProxy in the net.techcable.techutils.proxy package.
    Use it like this: (untested pesudo-code)
    Code (Java):
    public class PacketInjector {
        @EventHandler(priority = EventPriority.LOWEST)
        public void onJoin(PlayerJoinEvent event) {
            EntityPlayer player = event.getPlayer().getHandle(); // Do with reflection!!
            PlayerConection conection = player.playerConnection; // Do with reflection!!
            Channel channel = connection.networkManager.channel; /* Do with reflection!! -- Even netty must be done with reflection -- The channel field is complicated as it used to be obfuscated
              You have to iterate through every declared field in the NetworkManager class and find the one that is a channel field
            */

             channel.pipeline().addBefore("packet_handler", getHandlerName(event.getPlayer())); // Do with reflection          
        }

        @EventHandler(priority = EventPriority.MONITOR)
        public void onQuit(final PlayerQuitEvent event) {
        TechScheduler.scheduleAsyncTask(new Runnable() {
      EntityPlayer player = event.getPlayer().getHandle(); // Do with reflection!!
      PlayerConection conection = player.playerConnection; // Do with reflection!!
      Channel channel = connection.networkManager.channel; /* Do with reflection!! -- Even netty must be done with reflection -- See above note on how to get the channel field */
      channel.pipeline().remove(getHandlerName(event.getPlayer())); // Do with reflection
         }, 5); //After 5 ticks all references to the player should be gone unless some idiot tries to connect to a database in argentina
        }

        public void getHandlerName(Player player) {
            return "techutils_" + TechUtils.getId().substring(0, 6) + "_player_" + player.getName() + "_packet_injector";
        }

        public Object createChannelHandler(Player player) {
            Object handler;
            if (Reflection.getUtilClass("io.netty.channel.ChannelHandlerContext").getName().contains("minecraft")) {
                handler = new NewChannelHandler(player)
           } else {
                handler = new OldChannelHandler(player);
           }
           TechProxy proxy = TechProxy.create(new ChannelHandler(player), ChannelDuplexHandler.class /* Do with reflection */));
           return proxy.newInstance();
        }
       
        @RequiredArgsConstructor(access = AcessLevel.PRIVATE)
        private static class NewChannelHandler {
            private final Player player;
            @MethodHandler(value = "write", description = "(Lio/netty/channel/ChannelHandlerContext;Ljava/lang/Object;Lio/netty/channel/ChannelPromise;)V")
            public void write(Object context, Object packet, Object promise) throws Exception {
                 AsyncPacketSendEvent event = new AsyncPacketSendEvent(player, packet);
                 Bukkit.getPluginManager().callEvent(event);
                 packet = event.getPacket();
                 if (event.isCancelled() || packet == null) return;
                 context.write(packet, promise); // Call with reflection
            }

            @MethodHandler(value = "channelRead", description ="(Lio/netty/channel/ChannelHandlerContext;Ljava/lang/Object)V")
             public void read(Object context, Object packet) throws Exception {
                 AsyncPacketReceiveEvent event = new AsyncPacketReceiveEvent(player, packet);
                 Bukkit.getPluginManager().callEvent(event);
                 packet = event.getPacket();
                 if (event.isCancelled() || packet == null) return;
                 context.channelRead(packet, promise); // Call with reflection
             }
        }

        /*
          * OldChannelHandler should be the same as above, except change the Lio/netty stuff to Lnet/minecraft/util/io/netty
          */

    }
     
     

  11. hello how do i use the Scoreboard API inside your code ???
     
    1. Get the custom player object
    2. Get a TechBoard instance by calling getScoreboard()
    3. call scoreboard.display() to update
    4. Call board.clear()
    5. Do board.addElement for each thing on the scoreboard
    Here is an example:
    Code (Java):
    PrisonPlayer player = plugin.getPlayer(bukkitPlayer);
    final TechBoard board = player.getScoreboard();
    board.display();
    new BukkitRunnable() {
       @Override
       public void run() {
           board.clear();
           board.addElement("Welcome to TechzoneMC Prison");
           board.addElement("Current Rank is "  + player.getRank().getName());
           board.addElement("Money till rank-up:", player.getBalance() - player.getNextRank().getCost());
           board.addElement("Vote to get $1000");
           board.addElement("Special prizes also available");
       }
    }.runTaskTimer(plugin, 0, 20);
     
  12. Just noticed that. I had copy/pasted it from your OP. You should fix that. Thanks for the help!
     
  13. konsolas

    Supporter

    Looks like an interesting library. I'll probably be taking a look at the scoreboard side of things.
     
  14. konsolas

    Supporter

    Problem?
     
  15. Do you plan on adding support for multiple boss bars?
     
  16. Choco

    Moderator

    Would that not be a 1.9 feature? :p You've also just necroposted a 6 month old thread.
     
  17. I thought this would be useful for people, that is why I necro'd.
    And thanks for that!
     
    • Like Like x 1