Send messages each having different delays

Discussion in 'Spigot Plugin Development' started by MrDienns, May 21, 2017.

  1. Greetings

    I'm trying to send multiple messages that all have different delays between each other. Longer messages would have longer delays so the player wouldn't have any trouble reading it. Here's an example:

    1. First message, fires without delay, 2 second delay before next one fires
    2. Second message, waits 3 seconds before next one fires
    3. Third message, waits 1 second before next one fires
    4. Fourth message, waits 5 seconds before next one fires
    5. Final message
    What would be the best way of doing this? Doing this with runnables seems fairly tricky. I was thinking of simply Thread.sleep(...) but I'm not sure how great that is. It is async so it wouldn't block the server anyways, but it just seems like something stupid to use. Any suggestions would be great.

    Thanks
     
  2. You use runnables. It's not hard at all.

    Here's an example of how you would do it if you're writing in one class.

    Code (Text):
                Bukkit.getServer().getScheduler().scheduleSyncDelayedTask(this, new Runnable() {
                    public void run() {
                        MagicTitles.sendFullMagicTitle(p, ChatColor.translateAlternateColorCodes('&', "&aWelcome &d" + p.getName() + "!"), ChatColor.translateAlternateColorCodes('&', "&cto...."), 40, 10, 60, 20);
                        Bukkit.broadcastMessage(ChatColor.translateAlternateColorCodes('&', "&d" + p.getName() + " &ajoined for the first time!"));
                        p.playSound(p.getLocation(), Sound.ENTITY_FIREWORK_LAUNCH, 1, 1);
                    }
                }, 1 * 10);
                Bukkit.getServer().getScheduler().scheduleSyncDelayedTask(this, new Runnable() {
                    public void run() {
                        p.playSound(p.getLocation(), Sound.ENTITY_ENDERDRAGON_GROWL, 1, 1);
                        MagicTitles.sendFullMagicTitle(p, ChatColor.translateAlternateColorCodes('&', "&e&lMinecraftBF!"), ChatColor.translateAlternateColorCodes('&', "&d&lKitPvP but unique."), 20, 5, 60, 10);
                    }
                }, 1 * 75);
                Bukkit.getServer().getScheduler().scheduleSyncDelayedTask(this, new Runnable(){
                    public void run() {
                        p.sendMessage(ChatColor.translateAlternateColorCodes('&', "&7[&c*&7] &aClass selection is ahead, just walk over."));
                    }
                }, 1 * 150);
                Bukkit.getServer().getScheduler().scheduleSyncDelayedTask(this, new Runnable(){
                    public void run() {
                        p.sendMessage(ChatColor.translateAlternateColorCodes('&', "&7[&c*&7] &aDo &d/tutorial &ato see other cool features of this server!"));
                    }
                }, 1 * 210);

    Or in multiple classes:

    Code (Text):
    new BukkitRunnable(){
                        public void run()
                        {
                            p.playSound(p.getLocation(), Sound.BLOCK_PISTON_EXTEND, 1, 1);
                        }
                    }.runTaskLater(plugin, 10);
                    new BukkitRunnable(){
                        public void run()
                        {
                            p.playSound(p.getLocation(), Sound.BLOCK_PISTON_CONTRACT, 1, 1);
                            shootcooldown.remove(p);
                        }
                    }.runTaskLater(plugin, 20);

    Hope this helps
     
    • Like Like x 1
    • Creative Creative x 1
  3. Your provided code is parallel, while I was personally thinking it should be series so it doesn't glitch up the delay times. You do seem to calculate around with the delay time. I suppose that's one way of doing it. I'll try it out. :D
     
  4. Code (Text):
    public class BukkitTask {

        private JavaPlugin plugin;
       
        public BukkitTask(JavaPlugin plugin) {
            this.plugin = plugin;
        }
       
        public void sendMessage(Player p, String msg, long ticks) {
            new BukkitRunnable() {
                @Override
                public void run() {
                    p.sendMessage(msg);
                    cancel();
                }
            }.runTaskLater(plugin, ticks);
        }
       
    }
    maybe.
     
    • Funny Funny x 1
  5. I would personally go for something like this:

    By looping through a synchronized list, you can safely call the wait() method on it. Therefor, you can in the loop, decide when to continue to the next one. I quickly put together something like this, you might wanna divide it up into multiple classes.

    1. I made a MessageJourney object, which will contain all the messages.
    2. Then I made a MyPlayer object, which is a helper object for starting the journey for the player. It is important that it is a LinkedHashSet, so it cares about order.
    3. Now I created a synced list from this set, and looped through it in a sync block (to be able to safely call wait() on the synced list).
    4. Profit.

    With a system like this, you can add as many messages as you'd like, and the delays will all be based on how many characters are in the string.

    And no, it is not harmful to the main thread. It is not the same as calling sleep() on the main thread.

    Code below, with usage:
    Code (Text):
    /*
    * Project created by ExpDev
    */

    import org.bukkit.Bukkit;
    import org.bukkit.ChatColor;
    import org.bukkit.entity.Player;
    import org.bukkit.plugin.java.JavaPlugin;

    import java.util.*;

    public class MessageTicksTest extends JavaPlugin {


        public void onPlayerSomething() {
            // The player we are gonna send the messages to
            Player player = Bukkit.getPlayer("ExpDev");

            // Creating the helper object from them
            MyPlayer myPlayer = new MyPlayer(player);

            // Starting the message journey!
            myPlayer.startJourney(
                    new MessageJourney(
                            ChatColor.GREEN + "Shrt msg",
                            ChatColor.GRAY + "Longer msg",
                            ChatColor.AQUA + "Even longer message",
                            ChatColor.LIGHT_PURPLE + "You can't beat how long this message is",
                            ChatColor.RED + "And you defiantly can't beat how long this message is!! ! :)"
                    )
            );
        }

        /**
         * The journey
         */
        class MessageJourney {

            private Set<String> messages = new LinkedHashSet<String>();

            MessageJourney(String... messages) {
                // Adding our messages
                Collections.addAll(this.messages, messages);
            }

            Set<String> getMessages() {
                return messages;
            }
        }

        /**
         * Helper class for player
         */
        class MyPlayer {

            private Player player;

            MyPlayer(Player player) {
                this.player = player;
            }

            void startJourney(MessageJourney journey) {
                Player player = this.player;
                if (player == null) {
                    // Player null, can't send dem messages then
                    return;
                }

                // It is important that it is sync
                List<String> syncedList = Collections.synchronizedList(new ArrayList<String>(journey.getMessages()));

                synchronized (syncedList) {
                    for (String msg : syncedList) {
                        // Send the message
                        player.sendMessage(msg);

                        // How many letters in sentence? Can also do split(" ") and count words
                        char[] chars = msg.toCharArray();

                        // Calculate how long before sending next message
                        long millisBeforeNext = chars.length * 100; // abcdefg123 = 1 second

                        try {
                            // Waiting before continuing...
                            syncedList.wait(millisBeforeNext);
                        } catch (InterruptedException ignored) {} // We were interrupted
                    }
                }
            }
        }
    }
    In action:
    Divided into parts, as gyazo refuses to record longer gifs.
    Pt 1: https://gyazo.com/e835edd02fce5335ffc02a19b60d736e
    Pt 2: https://gyazo.com/225344a68d4551b4fef2cea792edc2b6

    Note that command was run twice, cuz my gyazo yeh, wouldn't record a long enough gif. So the part 2 gif, the two first 3 are very fast because I started recording at the end of the second message, however the fourth is correct time.


    Hope this helped. Maybe someone else has a better idea!

    EDIT:
    I just thought of something else too which you can do.

    By knowing when the last message was sent, you can easily calculate when the next message should be sent.
    Code (Text):
    long ticksBeforeNext = -1L;
    for (String msg : MessageJourney#getMessages) {
      if (ticksBeforeNext < 0) {
        // first msg. Just send message and click continue
        Player#sendMessage(msg);
        ticksBeforeNext = 0;
        continue;
      }
      // so how long previous message takes + how long this one takes.
      // E.g: if last message shouldn't be sent before 20 ticks, then this message shouldn't be sent before 20 ticks
      // + the ticks it should take to send this message+also the time it should take to send the messages
      // before that again, so in the end it is basically just calculating when a lot of delayed tasks should be ran.
      ticksBeforeNext = ticksBeforeNext + (msg.toCharArray().length * 5); // abcd = 1 second.

      // schedule a task to send the player a message at this time
      Bukkit.scheduleSyncDelayedTask(Plugin#, () -> { Player#sendMessage(msg) }, ticksBeforeNext);
    }
    Haven't tested it, but in my head rn this should work. Might be better in terms of Bukkit (I recommend it if it works) :) So basically you would just put the above where the syncronized block is and remove the syncedlist

    EDIT: Here you go, this is how I did it (untested but should work): https://gitlab.com/ExpDev07/TestPlugin/tree/master/src/main/java/me/expdev/testplugin . Check The Command class for usage, and the other classes for the setups.

    EDIT: @stevensilvergood while your method works, I believe it limits a lot. I don't know if you can call it "hard coding", but what you are doing is creating a very repetitive and wacky system.

    Every time you want a new message, would you like to create a completely new task, and at the same time calculating in your head how long before it should send? I know I wouldn't. 10 messages would equal 100 lines of code with that kind of system.

    While mine maybe isn't the best, it certainly knocks out any opponents above. It's not like it's the first time the Object.wait() has been used in iterations, actually it is the correct practice to do so.
     
    #5 ExpDev, May 21, 2017
    Last edited: May 22, 2017