From d3af1e04e01c1122c0647447dbb918dbf53277cf Mon Sep 17 00:00:00 2001 From: Ludovic Marie <136255825+ludovic111@users.noreply.github.com> Date: Mon, 26 Oct 2026 02:27:37 +0100 Subject: [PATCH] Mannequins run no mob goals and never despawn A mannequin is an Avatar (a LivingEntity), not a Mob: its serverAiStep has no goals and LivingEntity has no checkDespawn. With the passive brain a summoned mannequin wandered off (14 blocks in 400 ticks on the server thread) and could despawn far from players. Co-Authored-By: Claude Opus 5.5 --- crates/horizon-client/src/mobs/ai.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/crates/horizon-client/src/mobs/ai.rs b/crates/horizon-client/src/mobs/ai.rs index 97da5ea06..c9bc15fda 100644 --- a/crates/horizon-client/src/mobs/ai.rs +++ b/crates/horizon-client/src/mobs/ai.rs @@ -664,8 +664,10 @@ fn ai_step(m: &mut Mob, w: &MobWorld, level_random: &mut LegacyRandom, events: & m.ai.xxa *= 0.98; m.ai.zza *= 0.98; // LivingEntity.isImmobile: no goals or input (a biting, stunned or - // roaring ravager). - let immobile = m.species() == Species::Ravager && super::ravager::immobile(m); + // roaring ravager). A mannequin is an Avatar, not a Mob: LivingEntity's + // serverAiStep has no goals, so it only falls and is pushed. + let immobile = (m.species() == Species::Ravager && super::ravager::immobile(m)) + || m.species() == Species::Mannequin; if m.alive() && !immobile { server_ai_step(m, w, level_random, events); } else { @@ -1018,6 +1020,10 @@ pub fn despawn_check(m: &mut Mob, w: &MobWorld) -> bool { /// Mob.checkDespawn, before the tick (players are the only despawn /// reference). Returns false when the mob is discarded. fn check_despawn(m: &mut Mob, w: &MobWorld) -> bool { + // A mannequin is a LivingEntity, which never despawns. + if m.species() == Species::Mannequin { + return true; + } let Some(p) = w.nearest_player else { return true; };