1.8.8 Deal several hits in 10 ticks (or less)

Discussion in 'Spigot Plugin Development' started by ZBLL, Jun 10, 2021.

  1. I want to do multiple hits to a player if an X event happens. This is my code:
    Code (Java):

    @EventHandler
    public void onHitEvent(EntityDamageByEntityEvent e) {
        if (e.getDamager() instanceof Player && e.getEntity() instanceof Player) {
            if (/* my thing happens */) {
                if (Math.random() < 0.08) {
                    e.setDamage(0.0D);
                    ((Player)e.getEntity()).setNoDamageTicks(0);
                    ((Player) e.getEntity()).setHealth(((Player) e.getEntity()).getHealth() - 1);
                    e.getEntity().setVelocity(e.getEntity().getVelocity().setX(2).multiply(1));

                    Bukkit.getScheduler().runTaskLater(UnderscoreEnchants.instance, () -> {
                        ((Player)e.getEntity()).setNoDamageTicks(0);
                        ((Player) e.getEntity()).setHealth(((Player) e.getEntity()).getHealth() - 1);
                        e.getEntity().setVelocity(e.getEntity().getVelocity().setX(2).multiply(1));
                    }, 3);

                    Bukkit.getScheduler().runTaskLater(UnderscoreEnchants.instance, () -> {
                        ((Player)e.getEntity()).setNoDamageTicks(0);
                        ((Player) e.getEntity()).setHealth(((Player) e.getEntity()).getHealth() - 1);
                        e.getEntity().setVelocity(e.getEntity().getVelocity().setX(2).multiply(1));
                    }, 3);
                }

            }
        }
    }
     
    The point is, I didn't test it and I am very unsure if this is even the correct way to do it. So if my thing happens, I want to deal 3 hits to the player (each removing 1 HP (0,5 hearts)), each with a delay of 2 ticks. We all know that you can only deal one hit per 10 ticks, therefore I tried using a setNoDamageTicks() to remove the delay. But this looks very trippy and I think there is a much more efficient way to do this. Could you help me possibly, maybe take a look at the code?
     
  2. Your tasks aren't nested, so your second 2 hits will happen in the same tick. Also, you're using a 3 tick delay when you specifically said you're looking for 2 ticks.

    In addition, all your hit code is duplicated. Either make a separate method for performing the hit or use a repeating task that cancels itself after 3 iterations.

    I.E. (in pseudocode)
    Code (Java):
    task1 -> {
      doHit(entity);
      task2 -> {
        doHit(entity);
        task3 -> {
          doHit(entity);
        }
      }
    }
    or
    Code (Java):
    AtomicInteger count = new AtomicInteger();
    repeatingTask -> {
      if (count.incrementAndGet() > 3) {
        cancel();
        return;
      }
      doHit(entity);
    }
    where doHit is a method applying damage and velocity to the provided entity
     
    • Friendly Friendly x 1
  3. Oh, that's true. What if I do it like this though?
    Code (Java):

    ((Player)e.getEntity()).setNoDamageTicks(0);
    ((Player) e.getEntity()).setHealth(((Player) e.getEntity()).getHealth() - 1);
    e.getEntity().setVelocity(e.getEntity().getVelocity().setX(2).multiply(1));

    Bukkit.getScheduler().runTaskLater(UnderscoreEnchants.instance, () -> {
        ((Player)e.getEntity()).setNoDamageTicks(0);
        ((Player) e.getEntity()).setHealth(((Player) e.getEntity()).getHealth() - 1);
        e.getEntity().setVelocity(e.getEntity().getVelocity().setX(2).multiply(1));
    }, 3);

    Bukkit.getScheduler().runTaskLater(UnderscoreEnchants.instance, () -> {
        ((Player)e.getEntity()).setNoDamageTicks(0);
        ((Player) e.getEntity()).setHealth(((Player) e.getEntity()).getHealth() - 1);
        e.getEntity().setVelocity(e.getEntity().getVelocity().setX(2).multiply(1));
    }, 6);
     
    Running the first one instantly, the second one after 3 ticks and the last one after 3 more ticks (6 total). And other than that, is everything fine with my code so far?
     
  4. That would probably work, but you're still using a lot of duplicate code. Really strongly advise you to extract that to a helper method and possibly use a loop if that's the route you want to take, i.e.
    Code (Java):
    for (int i = 0; i <= 6; i+=3) {
      scheduleHitTask(i, (Player) e.getEntity());
    }
    Code (Java):
    private void scheduleHitTask(int delay, Player player) {
      Bukkit.getScheduler().runTaskLater(UnderscoreEnchants.instance, () -> {
        player.setNoDamageTicks(0);
        player.setHealth(player.getHealth() - 1);
        player.setVelocity(player.getVelocity().setX(2).multiply(1));
      }, delay);
    }
    Duplicate code = messy code. Extract all duplicate code and use loops where possible to eliminate redundant statements.
     
    #4 Jikoo, Jun 10, 2021
    Last edited: Jun 10, 2021
    • Winner Winner x 1
  5. That looks very reasonable! Thanks, I always get confused with the for loop for some reason. I often use it, but often it doesn't work on the first try. So I hope that works! I won't mark the thread as solved yet since I don't have the proper environment to test my code right now, but I hope for the best. Thanks again!
     
  6. By the way, I can't really do the e.getStuff() there, I need to add EntityDamageByEntityEvent e as a parameter for the function as well. But aside that, it looks fine!
     
  7. Yeah, sorry. I assume you'd just pass the player, save you a couple casts. I'll edit that in.
     
    • Like Like x 1
  8. Figured that this would even be better:
    Code (Java):

    Bukkit.getScheduler().runTaskLater(UnderscoreEnchants.instance, () -> {
        ((Player)e.getEntity()).setNoDamageTicks(0);
        ((Player) e.getEntity()).damage(1D);
    }, i);
     
    That's because I wanted to emulate the proper velocity of a hit, therefore this would be much better