Skip to content

ZombifiedPiglins remain angry indefinitely with persistentAngerTarget locked to a dead/removed Mob (1.21.11+, exposed by SpearUseGoal friendly-fire) #13829

@HyacinthHaru

Description

@HyacinthHaru

Expected behavior

When a ZombifiedPiglin's persistentAngerTarget ends up pointing to a non-Player entity (e.g. another piglin) that subsequently becomes dead/removed, vanilla NeutralMob.updatePersistentAnger should clean up the anger state, and the piglin should return to neutral.

The intent is already partially expressed by the existing cleanup branch:

if (target != null && target.isDeadOrDying() && persistentAngerTarget != null
        && persistentAngerTarget.matches(target) && target instanceof Mob) {
    this.stopBeingAngry();
}

…but it only fires while the dead entity is still referenced by target. Once TargetGoal.stop() clears target to null, no remaining branch handles the leftover stale persistentAngerTarget.

Observed/Actual behavior

ZombifiedPiglins become permanently stuck in:

  • getPersistentAngerEndTime() > level.getGameTime()isAngry() == true
  • persistentAngerTarget is non-null, but resolves to a removed Mob (or to nothing)
  • target is null (cleared by TargetGoal.stop() after the attacker piglin died)

Visible symptoms:

  • Anger sound plays continuously
  • Mob does not move toward any player
  • LookControl still ticks (random head turns)
  • Hitting one stuck mob restores normal aggro on only that mob
  • Unloading + reloading the chunk fixes every stuck mob in that chunk
  • MSPT-independent (observed at 2–50 mspt)

The stuck count grows monotonically over time on a busy farm (sample data below).

Steps/models to reproduce

  1. Paper 1.21.11+ server, default config
  2. Build / use any standard ZombifiedPiglin gold/XP farm in the Nether
  3. Start AFK-attacking a stack at the kill chamber. A fake-player / bot at the kill chamber works best for sustained reproducibility (/fp attack interval 13 with Leaves' built-in fakeplayer was used in the original report, but any Carpet-style fakeplayer mod also works)
  4. Within ~20–30 minutes, an increasing fraction of nearby angry piglins stop pathfinding while continuing to play angry sounds

The tick-by-tick trigger sequence (instrumented build confirmed all of this)

  1. Piglin A uses the new 1.21.11 SpearUseGoal to throw a spear and friendly-fires another piglin B

  2. B.setLastHurtByMob(A) is set (vanilla LivingEntity behaviour)

  3. Next target-selector tick, HurtByTargetGoal.start() calls B.setTarget(A, TARGET_ATTACKED_ENTITY)

  4. B.customServerAiStep then calls updatePersistentAnger(level, true). The MC-305388 backport branch fires:

    boolean newTarget = persistentAngerTarget == null || !persistentAngerTarget.matches(target);
    if (newTarget) {
        this.setPersistentAngerTarget(EntityReference.of(target)); // ← B.persistentAngerTarget overwritten to point to piglin A
    }
    if (newTarget || updateAnger) {
        this.startPersistentAngerTimer();
    }
  5. Player kills A. A becomes dead, then removed.

  6. TargetGoal.canContinueToUse() returns false because mob.canAttack(A)A.canBeSeenAsEnemy()A.isAlive() is false. TargetGoal.stop() calls B.setTarget(null, FORGOT_TARGET). B.target becomes null.

  7. updatePersistentAnger(level, true) runs:

    • BRANCH 1 (target dead + matches): fails — target == null
    • BRANCH 2 maintenance (if (target != null) { … }): skipped
    • BRANCH 2 cleanup (persistentAngerTarget != null && !this.isAngry() && (target == null || …)): fails — isAngry() is still true because the timer was just refreshed in step 4 immediately before A died
  8. No cleanup branch fires. B is stuck.

The timer alone would expire in 20–39 seconds and BRANCH 2 cleanup would then fire — but on a busy piglin farm SpearUseGoal keeps friendly-firing B onto fresh attackers, restarting the timer through step 4 again. Stuck count grows roughly exponentially until the kill chamber becomes a graveyard of standing piglins.

Diagnostic data

A reflection-based sampler (a debug build of RoseStacker) classified ZombifiedPiglins by anger state every 3 s:

[#843 SAMPLE] near <bot> sound=53 healthy=1 stuck=52 (no_uuid=0 no_target=0 dead_target=52 other=0)
[#843 STUCK-SAMPLE] entity=6baaf74b state=DEAD_TARGET timer=563 UUID=8ffbdc24-…
                     target=ZOMBIFIED_PIGLIN(dead) pos=(371.5,-41.0,-259.5)

target=ZOMBIFIED_PIGLIN(dead) confirms a target field briefly observed still holding a removed-piglin reference; persistentAngerTarget is non-null but resolves to nothing valid; timer is still active.

Stuck count over time on a single farm:

T+0  min   stuck=0
T+18 min   stuck=13
T+19 min   stuck=40
T+20 min   stuck=50
… never recovers without external intervention (chunk reload or hitting each individually)

Plugin and Datapack List

Reproduces with no plugins (standard farm + sustained AFK-attacker would expose it, just slower). Easiest reproducer:

  • A fakeplayer / bot for sustained AFK attacking
  • Optionally a mob-stacker plugin for higher mob density (accelerates the bug; not required)

Originally reported with: Leaves built-in fakeplayer + RoseStacker. Independently confirmed without RoseStacker by Leaves maintainer (see "Other" below).

Paper version

This server is running Paper version 1.21.11-130-ver/1.21.11@c5a2736 (2026-04-11T11:14:19Z) (Implementing API version 1.21.11-R0.1-SNAPSHOT)
You are running the latest version

Originally observed on Leaves 1.21.11-DEV-HEAD@3c199cc (2026-03-15) (Implementing API version 1.21.11-R0.1-SNAPSHOT). Independently reproduced on stock Paper 1.21.11 by Leaves maintainer @Fortern (see Leaves #843 thread).

Other

Background

This was originally reported downstream as LeavesMC/Leaves#843 ("【插件不兼容】僵尸猪灵在启用堆叠时经常失去愤怒行为"). Investigation went through several wrong turns before the upstream cause was pinned down:

  1. Initially suspected as a Leaves-only regression (the original reporter's 6-hour test on stock Paper 1.21.11 found no issue). The "Paper is fine" claim turned out to be a sampling artefact — the bug is probabilistic, dependent on farm density, kill rate and friendly-fire frequency. Leaves core member @Fortern independently reproduced the same symptoms on stock Paper 1.21.11 and noted: "1.21.11 zombified piglins holding spears can friendly-fire each other, possibly causing aggro transfer."

  2. Then suspected as RoseStacker-specific. Reported as Rosewood-Development/RoseStacker#174 and the RoseStacker maintainer committed 2cc706e to correctly transfer persistentAngerTarget (EntityReference) and persistentAngerEndTime on decreaseStackSize. That fix is correct and resolves the unstack-boundary path of the issue, but does not cover the SpearUseGoal-induced vanilla path described in this report — that path lives entirely inside NeutralMob.updatePersistentAnger and never goes through any plugin.

Related Paper / Mojang work

  • PaperMC/Paper#13318 — "Zombified Piglin pathfinding breaks after a short relog". Symptoms appear identical to ours, triggered by a different vanilla path (EntityReference cache invalidation on chunk unload/reload). Currently open, no investigation. Plausibly the same root-cause family.
  • PaperMC/Paper PR #13546 — "Fix Bee anger never gets timeout", merged 2026-01-30. Backported MC-305388 from 26.1-snapshot-5. The backport's scope is updateAnger == false mobs (Bees); it intentionally leaves updateAnger == true mobs (ZombifiedPiglin, EnderMan, Wolf, PolarBear) to refresh timer every tick. The scenario in this report is a sibling failure mode that the existing fix doesn't address.
  • Mojang MC-305388 — the Bee variant of the broader 1.21.11 anger-system rewrite issue. (No Mojang ticket found for the piglin/SpearUseGoal variant; happy to file one if it would help the conversation.)

Suggested fix direction (subject to maintainer judgement)

Extend NeutralMob.updatePersistentAnger's BRANCH 2 cleanup to also clean up when persistentAngerTarget resolves to a non-Player (or to nothing) and the runtime target has been cleared:

// near the existing BRANCH 2 cleanup:
if (persistentAngerTarget != null && target == null) {
    LivingEntity resolved = EntityReference.getLivingEntity(persistentAngerTarget, level);
    if (resolved == null || !isValidPlayerTarget(resolved)) {
        this.stopBeingAngry();
        return;
    }
}

This is conceptually consistent with BRANCH 1 (which cleans up when target is dead and matches anger UUID) and avoids the timer-refresh interaction.

If Paper would prefer to not patch this above vanilla, treating it as a known upstream issue and tracking the (to-be-filed) Mojang ticket would also be helpful for downstream forks/plugins to coordinate on.

Workaround currently in use

A periodic janitor that scans angry piglins and calls NeutralMob.stopBeingAngry() on those with stale anger targets has been added to a fork of RoseStacker and verified to keep stuck=0 over multi-hour runs on the affected farm. Reference implementation: https://github.com/Rosewood-Development/RoseStacker/ (commit 15103d03). This is a stopgap; the proper fix belongs in vanilla / Paper.

Metadata

Metadata

Assignees

No one assigned

    Type

    No fields configured for Bug.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions