Uploaded image for project: 'Minecraft: Java Edition'
  1. Minecraft: Java Edition
  2. MC-121048

When an entity dies, the combat tracker only records the killing blow

XMLWordPrintable

    • Icon: Bug Bug
    • Resolution: Fixed
    • 23w03a
    • Minecraft 1.12.2, Minecraft 1.13.2, 1.15.2, 20w09a, 20w10a, 20w11a, 20w12a, 20w14a, 20w15a, 20w16a, 20w17a, 20w21a, 1.16 Pre-release 2, 1.16 Pre-release 3, 1.16 Pre-release 4, 1.16 Pre-release 7, 1.16 Pre-release 8, 1.16 Release Candidate 1, 1.16, 1.16.1, 20w27a, 20w30a, 1.16.2, 1.16.3, 20w51a, 21w14a, 21w18a, 1.17 Release Candidate 1, 1.17, 1.17.1 Pre-release 1, 1.17.1, 21w37a, 21w42a, 21w43a, 1.18 Pre-release 4, 1.18.1, 1.18.2, 22w12a, 22w14a, 22w15a, 22w17a, 1.19 Pre-release 2, 1.19, 1.19.1 Pre-release 4, 1.19.1 Pre-release 6, 1.19.1, 1.19.2, 22w42a, 22w43a, 22w44a, 22w46a, 1.19.3 Pre-release 1, 1.19.3
    • None
    • Confirmed
    • Combat
    • Important
    • Platform

      Code analysis below uses 1.19's code, deobfuscated manually using the official obfuscation mappings for 1.19.

      Living entities have a CombatTracker, which tracks the details of recent hits. When such an entity is damaged, the combat tracker's recordDamage method is called to record it.

      recordDamage first calls recheckStatus, which resets the tracked data if certain conditions are met. It then adds the data for the damage currently being taken.

      The issue here is that, when recordDamage is called for the killing blow, recheckStatus will clear the tracker due to the condition !this.mob.isAlive() being met. This is because isAlive checks that the entity's health is above 0.

      Looking at the method LivingEntity#actuallyHurt shows the problem:

      protected void actuallyHurt(DamageSource damageSrc, float amount) {
         if (!this.isInvulnerableTo(damageSrc)) {
            amount = this.getDamageAfterArmorAbsorb(damageSrc, amount);
            amount = this.getDamageAfterMagicAbsorb(damageSrc, amount);
            float f2 = amount;
            amount = Math.max(amount - this.getAbsorptionAmount(), 0.0F);
            this.setAbsorptionAmount(this.getAbsorptionAmount() - f2 - amount);
            float f = f2 - amount;
            if (f > 0.0F && f < 3.4028235E37F && damageSrc.getEntity() instanceof ServerPlayer) {
               ((ServerPlayer)damageSrc.getEntity()).awardStat(Stats.DAMAGE_DEALT_ABSORBED, Math.round(f * 10.0F));
            }
            if (f2 != 0.0F) {
               float f1 = this.getHealth();
               this.setHealth(f1 - f2);
               this.getCombatTracker().recordDamage(damageSrc, f1, f2);
               this.setAbsorptionAmount(this.getAbsorptionAmount() - amount);
               this.gameEvent(GameEvent.ENTITY_DAMAGE);
            }
         }
      }
      

      The health is updated before recordDamage is called, which makes the entity show up as dead and causes the combat data to be cleared.

      Then, shortly afterwards, die is called for the just-killed entity. This checks getKillCredit to determine who to credit with the kill.

      getKillCredit will first consult CombatTracker#getKiller which contains logic to go through the combat history and pick out the "best" attacker.

      However this logic is currently irrelevant, as due to the above issue, the tracker will only contain an entry for the killing blow and nothing else.

      The solution here is to change LivingEntity#actuallyHurt to call recordDamage first, followed by setHealth.

      Fixing this bug will allow for the "doomed to fall" death messages to appear.

        1. MC-121048.mp4
          4.18 MB
        2. MC-121048.png
          MC-121048.png
          3.09 MB

            Fantastime [Mojang] Maxime Lebrot
            ManosSef ManosSef
            Votes:
            15 Vote for this issue
            Watchers:
            8 Start watching this issue

              Created:
              Updated:
              Resolved:
              CHK: