Solved Combat Log

Discussion in 'Spigot Plugin Development' started by Dusk_2_Dawn, Jan 24, 2020.

  1. So I am trying to create a combat log and I am experiencing some issues. It works for the first time that I hit them, but if I hit the player multiple times, the timer runs out of time from the first hit, not the latest one.

    Here is my code
    Code (Java):
    @EventHandler
        public void onDamage(EntityDamageByEntityEvent e) {
            if((e.getEntity() instanceof Player) && (e.getDamager() instanceof Player)) {
                Player player = (Player) e.getEntity();
                Player target = (Player) e.getDamager();

                if((!main.log.contains(player.getName())) && (!main.log.contains(target.getName()))) {
                    main.log.add(player.getName());
                    main.log.add(target.getName());

                    player.sendMessage(main.prefix + ChatColor.RED + "You have entered combat");
                    target.sendMessage(main.prefix + ChatColor.RED + "You have entered combat");

                    Bukkit.getServer().getScheduler().scheduleSyncDelayedTask(main, new BukkitRunnable() {
                        @Override
                        public void run() {
                            if((main.log.contains(player.getName())) && (main.log.contains(target.getName()))) {
                                main.log.remove(player.getName());
                                main.log.remove(target.getName());

                                player.sendMessage(main.prefix + ChatColor.GREEN + "You have left combat");
                                target.sendMessage(main.prefix + ChatColor.GREEN + "You have left combat");
                            }
                        }
                    }, 400L);
                }
            }
        }
    Any ideas?
     
  2. Here you are checking if they are not already tagged. If they are BOTH already tagged, none of the following code will run. Here's how I do my combat tagging:

    - HashMap of Player and Integer
    - On entity damage event, check if entity and damager are players and are (individually) in the hashmap, if they are update their time back to the 15 seconds. if not, put them in the hashmap
    - Have a "tick" method that runs every 20 ticks (or 1 second) which checks the hashmap keys and values to reduce the Integer by 1.
    - If that number is 0 (I do >= just to be safe) remove them from the hashmap and take them out of combat

    If you have any questions I'd be more than happy to answer :)
     
  3. Make a hashmap of your tasks and if they are hit again, kill your old task and update it with a new one.
     
  4. How exactly would I do this? I can make the HashMap but how do I do the rest?
     
  5. Listen for the EntityDamageByEntity event
    - check if the entity is a player (instanceof Player)
    - if so, check if the keySet of the hashmap contains the (Player) e.getEntity()
    - if it's in there, replace the value with however long you want
    - if it's not, put the player with the desired time
    - Repeat with the damager

    Then, I make a method called "tick"
    Code (Text):

    public static void tick() {
        if(!tagged.isEmpty()) {
            for(Player p : tagged.keySet()) {
                if(tagged.get(p)-1 > 0) {
                    tagged.replace(p, tagged.get(p) - 1);
                } else {
                    tagged.remove(p);
                    p.sendMessage("§a§l(§a!§a§l) §aYou are no longer in combat");
                }
            }
        }
    }
     
    Feel free to change as you see fit, as long as you understand what's going on.

    Then in my main class in my onEnable, I put this
    Code (Text):

    getServer().getScheduler().scheduleSyncRepeatingTask(this, CombatTag::tick, 20L, 20L);
     
    so that it repeats every second to tick down the tags.

    Not sure if this is the most efficient way to do it, but it's been working for me so far. If anybody wants to tweak or correct it feel free
     
  6. Is there a way to do this Asynchronously because I don't want this taking up the main thread
     
  7. Code (Text):
    getServer().getScheduler().scheduleAsyncRepeatingTask(this, CombatTag::tick, 20L, 20L);
    Should work
     
  8. So I tested it out and it works, but it keeps spitting console errors.

    Code (Text):
    [18:25:41 WARN]: Exception in thread "Craft Scheduler Thread - 10"[18:25:41 WARN]: org.apache.commons.lang.UnhandledException: Plugin Brawl v1.0.0 generated an exception while executing task 141                                                                                                                                    at org.bukkit.craftbukkit.v1_8_R3.scheduler.CraftAsyncTask.run(CraftAsyncTask.java:56)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
    at java.lang.Thread.run(Unknown Source)
    Caused by: java.util.ConcurrentModificationException
    at java.util.HashMap$HashIterator.nextNode(Unknown Source)
    at java.util.HashMap$KeyIterator.next(Unknown Source)
    at net.uprisemc.brawl.Main.updateCombatLog(Main.java:183)
    at org.bukkit.craftbukkit.v1_8_R3.scheduler.CraftTask.run(CraftTask.java:71)
    at org.bukkit.craftbukkit.v1_8_R3.scheduler.CraftAsyncTask.run(CraftAsyncTask.java:53)
    ... 3 more
     
  9. Whoops haven't tested the code, replace the foreach loop with a standard for loop

    Replace
    Code (Text):

    for(Player p : tagged.keySet()) {
    with a long for loop :) (if you don't know how later I can post an example)
     
  10. I know how to replace it (for(int i = 0; etc...)) but I don't know how to do the rest of the cooldown with that
     
  11. Code (Text):

    public static void tick() {
        if(!tagged.isEmpty()) {
            Iterator<Player> it = tagged.keySet().iterator();
            while (it.hasNext()) {
                Player p = it.next();
                if(tagged.get(p)-1 > 0) {
                    tagged.replace(p, tagged.get(p) - 1);
                } else {
                    tagged.remove(p);
                    p.sendMessage("§a§l(§a!§a§l) §aYou are no longer in combat");
                }
            }
        }
    }
     
    Sorry not by my main comp rn. Try that instead^, if that doesn't work I'll open up my project and tweak it for you.
     
  12. Strahan

    Benefactor

    Don't embed color characters, use the ChatColor enum... that's why it exists.
     
  13. iirc they're equivalent. Only issue would be if Minecraft ever switch from the section symbol and the plugin is used which I don't believe they will anytime soon
     
  14. Strahan

    Benefactor

    Yea, of course they are, otherwise it wouldn't work ;) The danger isn't just Mojang changing the character, it can also cause encoding headaches. It's also far easier to understand the code at a glance. It's been considered best practices for years.
     
  15. Looks like we're playing post ping pong lmao, while I get that it can throw an encoding error I use it for personal projects where I know the encoding and systems will stay the same, and I clearly told the OP to change as they see fit (IE if they wanna use a more reliable method because they're gonna be publicly distributing, they can feel free. I have no need).

    Of course on a public resource or client request, I would be using ChatColor (specifically the bungee one), though I'll keep it in mind, thanks :)
     
  16. If you want to improve the performance of the plugin, just store the System.currentTimeMillis() of the last hit (update it each time the player gets hit) and compare it to the actual System.currentTimeMillis() each time you want to check if he is out of combat.
     
    • Winner Winner x 1
  17. Wanted to write the same thing.
    Creating schedulers on each hit is worse.
     
  18. Strahan

    Benefactor

    Yea, that's fine, but it's not good to do "lazy coding" in some cases vs just doing it right all the time. No matter how good you think you are, if you do things wrong intentionally you end up building habits and eventually you may slip something into production code that you shouldn't. In thirty years of programming I've seen that crop up a lot.

    Even if you would be the type to claim you 100% never make mistakes, still, you're putting example code in the public space; that's certainly a case where you want best practices code not personal shortcut code.
     
  19. Sorry for the late response. I'll keep that in mind if I encounter any issues.