A creative or spectator player is never a mob's target

Mob.getTarget/setTarget run the target through asValidTarget, so a creative
or spectator player reads as no target however it was set (an alert from
another zombified piglin included). Horizon read ai.target directly and
kept chasing. Drop it at the start of the mob's tick; a zombified piglin or
iron golem angry at that player stops being angry (NeutralMob).

Part of #6790

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
This commit is contained in:
ClickingMasterandClaude Sonnet 5.5 committed 2026-11-01 22:41:29 -08:00
1 parent 3a264943c7
commit 36a4682121
2 files changed
+45

No files matched your search

+15
View File
@@ -549,6 +549,20 @@ pub fn is_monster(s: Species) -> bool {
)
}
/// One full entity tick. Returns false when the mob is gone.
/// `Mob.getTarget` / `Mob.setTarget` (`asValidTarget`): a player in creative
/// or spectator mode is never a mob's target, however the target was set (an
/// alert from another mob included). A `NeutralMob` angry at that player stops
/// being angry (`NeutralMob.updatePersistentAnger`).
fn drop_invalid_player_target(m: &mut Mob, w: &MobWorld) {
if m.ai.target == Some(Who::Player)
&& w.player.as_ref().is_some_and(|p| p.creative || p.spectator)
{
m.ai.target = None;
if matches!(m.species(), Species::ZombifiedPiglin | Species::IronGolem) {
m.extra.anger = 0;
}
}
}
pub fn tick(
m: &mut Mob,
w: &MobWorld,
@@ -577,6 +591,7 @@ pub fn tick(
if m.ai.last_hurt_by_player > 0 {
m.ai.last_hurt_by_player -= 1;
}
drop_invalid_player_target(m, w);
// Mob.tick pre-update hooks (Creeper.tick swells before super.tick).
pre_tick(m, events);
if m.removed {
+30
View File
@@ -1376,3 +1376,33 @@ mod trident_tests {
assert_eq!(ranged_attack_uncertainty(3), 2.0, "hard");
}
}
#[cfg(test)]
mod creative_target_tests {
use super::*;
use crate::mobs::world::{test_player, with_test_world};
use std::sync::Arc;
/// #6790: an angry zombified piglin lets go of a player in creative mode
/// (`TargetGoal.canContinueToUse` / `TargetingConditions` skip invulnerable
/// players).
#[test]
fn an_angry_zombified_piglin_drops_a_creative_player() {
with_test_world(&[], |w, types| {
let mut w = w.clone();
let mut player = test_player(Vec3::new(3.5, 64.0, 0.5));
player.creative = true;
w.player = Some(player);
let kind = Arc::new(types.types["minecraft:zombified_piglin"].clone());
let mut m = Mob::new(1, kind, Vec3::new(0.5, 64.0, 0.5), 0.0);
m.ai.target = Some(Who::Player);
m.extra.anger = 300;
let mut lr = horizon_core::random::LegacyRandom::new(0);
for _ in 0..40 {
let mut events = Vec::new();
m.tick(&w, &mut lr, &mut events);
}
assert_eq!(m.ai.target, None, "anger {}", m.extra.anger);
});
}
}