After a Zoglin is attacked by a horde of silverfish, some will not move after the Zoglin has been killed. If a player moves into the aggro range of the then-immobile silverfish, their AI will resume to normal. Should be easy enough to recreate.
Code analysis
Code analysis by @unknown in this comment
Linked issues
Attachments
Comments 9
The reason this bug happens is that the Target of the silverfish does not get set to null once the entity dies.
Before I go deeper into this, this works for all entities that use setGroupRevenge() for RevengeGoal, and it can be triggered by any entity.
The only reason you would have only noticed this with silverfish is that they only have 1 wander goal, the wander goal checks if they have a Target first, if they do then it won't start the goal. So the silverfish seem mostly dead until another goal gets activated.
Code Analysis - Yarn 22w11a
net.minecraft.entity.mob.SilverfishEntity
protected void initGoals() {
this.callForHelpGoal = new SilverfishEntity.CallForHelpGoal(this);
this.goalSelector.add(1, new SwimGoal(this));
this.goalSelector.add(1, new PowderSnowJumpGoal(this, this.world));
this.goalSelector.add(3, this.callForHelpGoal);
this.goalSelector.add(4, new MeleeAttackGoal(this, 1.0, false));
this.goalSelector.add(5, new SilverfishEntity.WanderAndInfestGoal(this)); // This is the only idle movement goal
this.targetSelector.add(1, (new RevengeGoal(this)).setGroupRevenge()); // Here you can see `setGroupRevenge()` is used
this.targetSelector.add(2, new ActiveTargetGoal(this, PlayerEntity.class, true));
}
net.minecraft.entity.mob.SilverfishEntity$WanderAndInfestGoal
public boolean canStart() {
if (this.mob.getTarget() != null) { // Here it fails due to target not being null
return false;
} else if (!this.mob.getNavigation().isIdle()) {
return false;
} else {
/* Unrelated - Removed */
}
}
This happens because when the entity dies, the silverfish don't update their target, their target is not null but it's also not valid.
Here's an easy fix for this:
net.minecraft.entity.mob.SilverfishEntity$WanderAndInfestGoal (Fixed)
public boolean canStart() {
if (this.mob.getTarget() != null) {
if (this.mob.getTarget().isAlive()) return false;
this.mob.setTarget(null); // Set to null if the target is dead
}
if (!this.mob.getNavigation().isIdle()) {
return false;
} else {
/* Unrelated - Removed */
}
}
Another easy way to reproduce this by going into survival with a wolf. Letting one of the silverfish hit you, then go into creative. Once your wolf kills one, they will all attack it and kill it and be frozen just like the zoglin.
Now I said it affects other entities, although the other entities are much harder to notice.
If you do the same experiment with blaze you won't be able to tell until you spawn another zoglin, then you will notice that none of the blaze are helping out. This is how you can test if they are also affected. Here's why:
net.minecraft.entity.ai.goal.RevengeGoal
protected void callSameTypeForRevenge() {
double d = this.getFollowRange();
Box box = Box.from(this.mob.getPos()).expand(d, 10.0, d);
List<? extends MobEntity> list = this.mob.world.getEntitiesByClass(this.mob.getClass(), box, EntityPredicates.EXCEPT_SPECTATOR);
Iterator var5 = list.iterator();
while(true) {
MobEntity mobEntity;
while(true) {
if (!var5.hasNext()) return;
mobEntity = (MobEntity)var5.next();
if (this.mob != mobEntity
&& mobEntity.getTarget() == null // FAILS HERE, the blaze won't help because they are already targetting something
&& (!(this.mob instanceof TameableEntity) || ((TameableEntity)this.mob).getOwner() == ((TameableEntity)mobEntity).getOwner())
&& !mobEntity.isTeammate(this.mob.getAttacker())) {
if (this.noHelpTypes == null) break;
/* Bunch of junk - Removed */
}
}
this.setMobEntityTarget(mobEntity, this.mob.getAttacker());
}
}
So what mobs does this affect:
Bee
Blaze
Dolphin
Drowned
Endermite
Evoker
Illusioner
Panda
Pillager
Rabbit
Ravager
Shulker
Silverfish
Vex
Vindicator
Wolf
Zombie
ZombifiedPiglin
The fix I suggested does not fix the issue where they stop doing group attacks. So that's only a fix for the silverfish.
To fix this correctly you could just do what you do for attackers in LivingEntity.
net.minecraft.entity.LivingEntity
public void baseTick() {
/* unrelated - removed */
super.baseTick();
/* unrelated - removed */
if (this.attacker != null) {
if (!this.attacker.isAlive()) { // If Entity is dead, set it to null
this.setAttacker((LivingEntity)null);
} else if (this.age - this.lastAttackedTime > 100) {
this.setAttacker((LivingEntity)null);
}
}
/* unrelated - removed */
}
net.minecraft.entity.mob.MobEntity (Fixed)
public void baseTick() {
super.baseTick();
this.world.getProfiler().push("mobBaseTick");
if (this.isAlive() && this.random.nextInt(1000) < this.ambientSoundChance++) {
this.resetSoundDelay();
this.playAmbientSound();
}
if (this.target != null && target.isDead()) { // New if statement for fix
this.setTarget(null);
}
this.world.getProfiler().pop();
}
Working Fix: https://github.com/fxmorin/carpet-fixes/commit/ba1d1446b98f0f2e34eb2c259c323760561f7018
Let's be honest tho, you guys need to move the old entities to the new system anyway, and since the new system is event-driven, it makes this super simple to fix. You just listen for entities dying and check if it's the entity you are targetting.
I would also like to suggest that we rename this bug report since it affects a lot more than just silverfish or zoglins
To put a cherry on top, this affects tons of goals. Not just the silverfish wander goal
Can confirm, their AI seems to completely crash once the Zoglin is dead.