How to regenerate exploded blocks one by one?

Discussion in 'Plugin Development' started by Ultracrepadarian, Oct 14, 2016.

Thread Status:
Not open for further replies.
  1. Hi. I've been attempting for a while now to regenerate a HashMap of all blocks one by one, block by block. But even when I attempt to do so, It seems to regenerate all at once.
    Show Spoiler

    Code:
    package com.krazytar.plugins.RageController;
    
    import java.util.HashMap;
    import org.bukkit.Bukkit;
    import org.bukkit.Location;
    import org.bukkit.Material;
    import org.bukkit.World;
    import org.bukkit.block.Block;
    import org.bukkit.block.BlockState;
    import org.bukkit.command.Command;
    import org.bukkit.command.CommandSender;
    import org.bukkit.entity.Player;
    import org.bukkit.event.EventHandler;
    import org.bukkit.event.Listener;
    import org.bukkit.event.entity.EntityExplodeEvent;
    import org.bukkit.plugin.java.JavaPlugin;
    
    public class Main extends JavaPlugin implements Listener {
           
        HashMap<Location, Material> blocks = new HashMap<>();
        int task;
        static Main instance;
        public Main() {
            instance = this;
        }
       
        @Override
        public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
            Player player = (Player) sender;
            if(cmd.getName().equalsIgnoreCase("regen")) {
                if(args[0].equalsIgnoreCase("start")) {
                    player.sendMessage("Stopping task...");
                    Bukkit.getScheduler().cancelAllTasks();
                }
            }
           
            return true;
        }
    
       
       
        @Override
        public void onEnable() {
            Bukkit.getServer().getPluginManager().registerEvents(this, this);
        }
       
        @EventHandler
        public void explosionEvent(EntityExplodeEvent e) {
            if(e.getLocation().getWorld().getName().equalsIgnoreCase("RageController")) {
                for(Block b: e.blockList()) {
                    final BlockState state = b.getState();
                    b.setType(Material.AIR); //Stops item drops
                    int delay  = 1200;
                    if(b.getType() == Material.SAND || b.getType() == Material.GRAVEL) {
                        delay += 1;
                    }
                    if(!blocks.containsKey(e)) {
                        blocks.put(b.getLocation(), b.getType());
                        startRegen(state, delay);
                    } else {
                        //Ignore
                    }
                }
            }
        }
       
        public void startRegen(BlockState state, int delay) {
            if(!Bukkit.getScheduler().isCurrentlyRunning(task)) {
               
                    task = Bukkit.getScheduler().scheduleSyncDelayedTask(instance, new Runnable() {
                        @Override
                        public void run() {
                            for(Location loc: blocks.keySet()) {
                                    for(Material material: blocks.values()) {
                                        if(blocks.get(loc).equals(material)) {
    
                                            Bukkit.getScheduler().scheduleSyncDelayedTask(instance, new Runnable() {
                                                @Override
                                                public void run() {
                                                   
                                                    World world = getServer().getWorld("RageController");
                                                    Block block = world.getBlockAt(loc);
                                                    block.setType(material);
                                                    blocks.remove(loc);
                                                    state.update(true, false);
                                                   
                                                }
                                            }, 20L);
                                        }
                                    }
                            }
                        }
                }, 1200L);
            }
        }
       
    }

    I tried following a tutorial on YT, which suggested that I save the block state, set all the blocks exploded to air, then update the blockstate later. But ever since I added that chunk of code, Its only been regenerating some blocks and not others.
    Help is appreciated. :)
     
  2. Offline

    ShaneCraftDev

    What did you expect this runnable would do instead? You iterate over every keyvalue pair in the hashmap and create a runnable within which should place a block after some game ticks (I don't know how much 20L would take ingame), however that does not mean the creation of this runnable takes that long or that the current iteration waits for runnable to finish executing before the next iteration. You would want to create a new runnable inside another runnable (recursion) or, quite dirty, multiply the ticks by the current iteration count for this new runnable.

    Something to note. You do not need to create a static instance of this class, as you call this method from within this class. You could just pass the current instance of your class as (Java)Plugin as parameter for the scheduler.
    Another note, do not use java's equal function for Enums, this is bad practice.
     
  3. Alright. I didn't catch that at all. Keep in mind that I AM a novice programmer and still need this sort of stuff explained to me in english :p Not to say you didn't explain it well, but you didn't explain it on my level. Would you mind restating the problem to me on simpler terms?
    @ShaneCraftDev
     
  4. @Ultracrepadarian
    What you need to do is replace the forloop where you iterate over all the blocks with a BukkitRunnable, so it spans on multiple ticks (remember, a for loop goes through the entries as quickly as possible, meaning it'll only take a few milliseconds).
     
  5. Offline

    ShaneCraftDev

    @Ultracrepadarian
    I don't know what you call novice, or what a 'novice programmer' can or should be able to do but basically you are iterating over your set of blocks and materials (keyvalue pair) and within this iteration you are creating a new runnable for your scheduler. You set the duration to 20L (what ever this value might be in gameticks) but that does not mean the current iteration lasts for this set duration. This forloop is extremely easy to handle for the cpu, so the next iteration might be done in the next nanosecond. What this means is that you have created 2 new runnables on nearly the exact same time, resulting from your point of view (in game) that the blocks get placed at the same moment.

    What you can do to stop this, is to multiply the duration with the current count of the iteration for your new runnable, eg: 20L * count, where count is the current iteration in your for loop. OR, as you should do it, create a runnable inside of your runnable (using recursion). So it actually creates a new runnable when the initial runnable ends. This way you do not end up with unhandled threads (runnables) as this will only create a new runnable when it has finished running the current one. When the server stops, or crashes, these runnables you've created with your for loop might still run, but this is if you really want to learn programming in Java (and other none-threadsafe languages).
     
  6. @AlvinB
    I have two for loops, one iterating over the blocks, and one over the location. Which one should I do it with? Or should I add it right before the first one?
    @ShaneCraftDev
    Thanks a ton for the explanation! Now I understand it much better :)
     
  7. Offline

    Creeperzombi3

    @Ultracrepadarian
    Just use the Keyset loop and have the material be the value you get from map.get(value)
     
  8. @Creeperzombi3
    Alright, I don't fully understand. Do you mean put another runnable in front of that loop?

    EDIT: I apologize for making ya'll go through all the trouble, I'd just like to fully understand it and not just ask ya'll to code it for me.
     
  9. Offline

    Lordloss

    No, please, dont. Most of them are really bad for learning bukkit.

    So you cancel ALL tasks from every bukkit plugin running? Is this what you want?

    I dont really understand why you stack multiple for loops and schedulers for this.
    What i got from your explanation is, you want to regenerate one block at a time every 20 ticks or something. Correct me if im wrong.
    I would do the following: As the first blocks get destroyed, start a repeating task. In this task get the first block from the list, regenerate it, remove it from the list. If the List is empty, cancel the task.
     
  10. @Lordloss
    Alright, I'll try to refrain from using them.
    I suppose I never thought It would cancel all tasks, so much as just the tasks in the current class.
    Basically I want to do this: Once a block is destroyed, it starts a delayedTask set for two minutes, and adds the block location, as well as the block material to a hashmap.

    Then, once the task starts two minutes later, it will loop through all the locations and Materials, and, if they match, it will set the block. Now I would think there would be an easier way to do this, but I don't seem to know it.

    Then, after that, It attempts to regenerate the block.
     
    Last edited: Oct 17, 2016
  11. Offline

    Lordloss

    Jep there is an easier way of doing this. I would start the 2 minute timer, and after the time is over call another method regenerate() or something like that.
    Within this method you start a repeating task which has the time as delay you want between every regenerated block. There simply take the next block from your map, reset it and remove it.
    Some pseudocode:


    Code:
    onFirstBlockBreak {
    
        add block to map.
        if delayed task not running, and block resetting hasnt started yet, start it.
     
        task 2 min or how you want it (call regenerate())
    
    }
    
    Regenerate {
    
        start repeating task (
     
            reset next block from map.
            remove block from map.
            if map is empty = cancel task.
     
        )
    
    }
     
  12. What do you mean by this?
    @Lordloss
     
  13. Offline

    ipodtouch0218

    @Ultracrepadarian
    He means regenerate (replace) the next block from the map, can't really get more clear than that.
     
Thread Status:
Not open for further replies.

Share This Page