Player Death Event Thrown Twice

Discussion in 'Plugin Development' started by Smeary_Subset, Oct 31, 2022.

Thread Status:
Not open for further replies.
  1. Offline

    Smeary_Subset

    I have a minigame that allows players to "knife" other players by punching them with an empty hand and being at most 1.5 blocks away. If they do, the knifed player is executed via player.setHealth(0.0).

    Strangely, every time this happens, PlayerDeathEvent is being thrown twice. I know this because of a print statement in my PlayerDeathEvent method after a single knife occurs:

    Code:
        int count = 1;
        @EventHandler
        private void onPlayerDeath(PlayerDeathEvent event) {
            Bukkit.getLogger().info("Death Event: " + count);
            deathHandler.run(event);
            count++;
        }
    Console output:
    Code:
    [19:01:37 INFO]: Death Event: 1
    [19:01:37 INFO]: Death Event: 2

    Snippet from ListenerManager class that registers all my listeners. My PlayerDeathEvent is in my DeathEvents listener, registered in the combat() method.
    Code:
        private void registerGameListeners() {
            background();
            combat();
            kit();
            ability();
        }
        private void background() {
            pluginManager.registerEvents(new EnvironmentEvents(arena), CLI.plugin);
            pluginManager.registerEvents(new PlayerEvents(playerManager, arena, minimumYThreshold), CLI.plugin);
        }
        private void combat() {
            final double knifeDistance = 1.5;
            pluginManager.registerEvents(new DeathEvents(playerManager, knifeDistance), CLI.plugin);
            pluginManager.registerEvents(new KnifeEvents(playerManager, knifeDistance), CLI.plugin);
            pluginManager.registerEvents(new ImmunityEvents(playerManager), CLI.plugin);
        }
        private void kit() {
            pluginManager.registerEvents(new AquaBomberEvents(playerManager, arena), CLI.plugin);
            pluginManager.registerEvents(new BucketEvents(arena), CLI.plugin);
            pluginManager.registerEvents(new FireballEvents(playerManager), CLI.plugin);
            pluginManager.registerEvents(new MiscBlockPlaceEvents(), CLI.plugin);
            pluginManager.registerEvents(new SpectralArrowEvents(playerManager), CLI.plugin);
            pluginManager.registerEvents(new TNTEvents(playerManager), CLI.plugin);
        }
        private void ability() {
            for(AbstractAbility ability : abilitiesInGame()) {
                if(ability instanceof HuntersApproach)
                    pluginManager.registerEvents(new HuntersApproachEvents(playerManager), CLI.plugin);
                else if(ability instanceof SmokeBomb)
                    pluginManager.registerEvents(new SmokeBombEvents(), CLI.plugin);
                else if(ability instanceof HardHit)
                    pluginManager.registerEvents(new HardHitEvents(playerManager), CLI.plugin);
                else if(ability instanceof Parry)
                    pluginManager.registerEvents(new ParryEvents(playerManager), CLI.plugin);
                else if(ability instanceof ToughSkin)
                    pluginManager.registerEvents(new ToughSkinEvents(playerManager), CLI.plugin);
                else if(ability instanceof Endersmash)
                    pluginManager.registerEvents(new EndersmashEvents(playerManager), CLI.plugin);
                else if(ability instanceof Rampage)
                    pluginManager.registerEvents(new RampageEvents(playerManager), CLI.plugin);
            }
        }
        private Set<AbstractAbility> abilitiesInGame() {
            Set<AbstractAbility> abilities = new HashSet<>();
            for(MyPlayer player : PlayerManager.playerList)
                abilities.add(player.getAbility());
            return abilities;
        }

    Answers to questions you may have:
    1. The listener is not being registered twice.
    2. I do have two PlayerDeathEvents in separate classes, but I have tried commenting out the other and the issue still persists.
    3. According to MD_5, this bug was present in an old version: https://www.spigotmc.org/threads/playerdeathevent-called-twice.403352/. Perhaps it has returned?
     
    Last edited: Nov 4, 2022
  2. Offline

    Kars

    How do you know the eventlistener is not being registered twice? From what i can see it is being registered twice because it is registered for every player once.

    "abilitiesInGame" for every player you add an ability and then "abilities" you register an eventlistener for every ability.
     
  3. Offline

    Smeary_Subset

    As mentioned, the problematic PlayerDeathEvent is not registered in the ability() method, it's registered in combat(). Also, the listener is only thrown twice when a player is knifed, not when they die from natural ways (I know, it's really weird). Anyways, just now I went ahead and verified it's not being registered twice using log statements.

    Side note to address your other concern: The ability listeners are not being registered more than once. The abilitiesInGame() method returns a Set, which as you likely know cannot contain duplicate values. Regardless, those listeners do not contain the player death event that causes the problem.
     
  4. Offline

    Kars

  5. Offline

    Smeary_Subset

    I didn't say DeathHandler anywhere. There's DeathEvents which is the listener containing PlayerDeathEvent and ListenerManager that registers/unregisters my minigame's listeners
     
  6. Offline

    Kars

    @Smeary_Subset
     
  7. Offline

    Smeary_Subset

    Oops, my bad. Death Handler is my private class that handles all the processing that needs to be done when a player dies, such as incrementing a player's score when they land a kill, preventing items from dropping on death, and respawning the killed player. I don't think it has anything to do with the issue which is why I did not include it initially.

    Code:
    private class DeathHandler {
            private PlayerDeathEvent event;
    
            void run(PlayerDeathEvent event) {
                this.event = event;
                Player killedPlayer = event.getEntity();
    
                if (inGame(killedPlayer)) {
                    MyPlayer killed = killed();
                    clearDrops();
    
                    Player playerKiller = event.getEntity().getKiller();
                    if (playerKiller != null && inGame(playerKiller)) {
    
                        if (killerIsSelf(playerKiller))
                            playerManager.registerDeath(killed, killed, true);
    
                        else {
                            MyPlayer killer = killer();
                            boolean killerUsedKnife = killerUsedKnife(killer, killed);
                            playerManager.registerDeath(killer, killed, killerUsedKnife);
                        }
                    }
    
                    else if (killedThemself(killedPlayer))
                        playerManager.registerDeath(killed, killed, true); // true so score decreases
                }
            }
    
            private boolean inGame(Player player) {
                return playerManager.isInGame(player);
            }
    
            private void clearDrops() {
                event.getDrops().clear();
            }
    
            private MyPlayer killer() {
                return playerManager.getMyPlayer(event.getEntity().getKiller());
            }
    
            private MyPlayer killed() {
                return playerManager.getMyPlayer(event.getEntity());
            }
    
            private boolean killedThemself(Player player) {
                return (killerIsNull(player) || killerIsSelf(player)) && diedFromNonPvp(player);
            }
    
            private boolean killerIsNull(Player player) {
                return player.getKiller() == null;
            }
    
            private boolean killerIsSelf(Player player) {
                return player.getKiller() == player;
            }
    
            private boolean diedFromNonPvp(Player player) {
                try {
                    EntityDamageEvent.DamageCause damageCause = player.getLastDamageCause().getCause();
                    return damageCause != null &&
                            (damageCause == EntityDamageEvent.DamageCause.FALL ||
                                    damageCause == EntityDamageEvent.DamageCause.BLOCK_EXPLOSION ||
                                    damageCause == EntityDamageEvent.DamageCause.CONTACT ||
                                    damageCause == EntityDamageEvent.DamageCause.LAVA ||
                                    damageCause == EntityDamageEvent.DamageCause.WITHER ||
                                    damageCause == EntityDamageEvent.DamageCause.FIRE_TICK ||
                                    damageCause == EntityDamageEvent.DamageCause.FIRE ||
                                    damageCause == EntityDamageEvent.DamageCause.CUSTOM ||
                                    damageCause == EntityDamageEvent.DamageCause.DROWNING ||
                                    damageCause == EntityDamageEvent.DamageCause.POISON);
                } catch (NullPointerException ignored) {
                    return true;
                }
            }
    
            private boolean killerUsedKnife(MyPlayer killer, MyPlayer killed) {
                return killerHandEmpty(killer) && killerInKnifeRange(killer, killed);
            }
    
            private boolean killerHandEmpty(MyPlayer killer) {
                return killer.getInventory().getItemInMainHand().getType() == Material.AIR;
            }
    
            private boolean killerInKnifeRange(MyPlayer killer, MyPlayer killed) {
                return killed.getLocation().distanceSquared(killer.getLocation()) <= Math.abs(KNIFE_DISTANCE);
            }
        }
     
  8. Offline

    Kars

    @Smeary_Subset alright no luck there.
    Show the part where the player's health is set to 0.
     
  9. Offline

    Smeary_Subset

    Code:
    @EventHandler
        private void playerKnifed(EntityDamageByEntityEvent event) {
            new KnifeHandler(event).run();
        }
    
        private class KnifeHandler {
            private final EntityDamageByEntityEvent event;
            private MyPlayer damaged;
            private MyPlayer damager;
    
            private KnifeHandler(EntityDamageByEntityEvent event) {
                this.event = event;
            }
    
            void run() {
                if(entityIsPlayer() && entityInGame()) {
                    if(entityHasImmunity()) {
                        event.setCancelled(true);
                        return;
                    }
    
                    if(damagerIsPlayer() && damagerInGame()) {
                        if(attemptedToKnife()) {
                            if(damagedInKnifeRange()) {
                                if (damager.hasKnife()) {
                                    playSlimeSound();
                                    tellDamagedTheyWereKnifed();
                                    executedDamaged();
                                } else tellDamagerTheyHaveNoKnives();
                            } else event.setCancelled(true); // prevents punch spam
                        }
                    }
                }
            }
    
            private boolean entityIsPlayer() {
                return event.getEntity() instanceof Player;
            }
            private boolean entityInGame() {
                damaged = playerManager.getMyPlayer((Player) event.getEntity());
                return damaged != null;
            }
    
            private boolean entityHasImmunity() {
                return damaged.hasRespawnImmunity();
            }
    
            private boolean damagerIsPlayer() {
                return event.getDamager() instanceof Player;
            }
            private boolean damagerInGame() {
                damager = playerManager.getMyPlayer((Player) event.getDamager());
                return damager != null;
            }
    
            private boolean attemptedToKnife() {
                return damageCauseEntityAttack() && damagerHasEmptyHand();
            }
            private boolean damagerHasEmptyHand() {
                return ((Player) event.getDamager()).getInventory().getItemInMainHand().getType() == Material.AIR;
            }
            private boolean damageCauseEntityAttack() {
                return event.getCause() == EntityDamageEvent.DamageCause.ENTITY_ATTACK;
            }
            private boolean damagedInKnifeRange() {
                return damaged.getLocation().distanceSquared(damager.getLocation()) <= Math.abs(KNIFE_DISTANCE);
            }
    
            private void playSlimeSound() {
                damaged.playSound(damaged.getLocation(), Sound.ENTITY_SLIME_JUMP, 1, 1);
                damager.playSound(damager.getLocation(), Sound.ENTITY_SLIME_JUMP, 1, 1);
            }
    
            private void executedDamaged() {
                damaged.setHealth(0);
            }
    
            private void tellDamagedTheyWereKnifed() {
                damaged.sendMessage(ChatColor.YELLOW + "You were knifed by " + damager.getName() + "!");
            }
    
            private void tellDamagerTheyHaveNoKnives() {
                damager.sendMessage(ChatColor.YELLOW + "You are out of knives!");
            }
        }
    According to an old post, MD_5 said this was a bug and was present in an older version. Maybe it has returned? https://www.spigotmc.org/threads/playerdeathevent-called-twice.403352/
     
  10. Offline

    Kars

    @Smeary_Subset could be. I don't know i can't tell why it's happening.
     
  11. Offline

    Smeary_Subset

    I ended up making a work around because I think it's definitely a bug.

    @md_5 Sorry to tag you randomly. Just wanted to make you aware of something that likely may be a bug.
     
Thread Status:
Not open for further replies.

Share This Page