fix: the brute's walk home waits 80 ticks between tries

StrollToPoi sets nextOkStartTime 80 ticks after each try; without it a
brute 10-100 blocks from a home it cannot reach ran a path search on
every goal-selector pass.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Ludovic MarieandClaude Opus 5.5 committed 2026-11-03 23:36:50 +01:00
1 parent 5dea1cebb2
commit fff6d3065e
1 file changed
+36 -4
+36 -4
View File
@@ -1301,7 +1301,7 @@ pub fn register_brute(m: &mut Mob) {
// `PiglinBruteAi.createIdleMovementBehaviors`: `StrollToPoi(HOME, 0.6, 2,
// 100)` ahead of the plain stroll, so a brute that drifts off walks back to
// the spot `PiglinBruteAi.initMemories` remembered for it.
g.add(5, BruteStrollHome);
g.add(5, BruteStrollHome { next_ok: 0 });
g.add(5, Stroll::avoiding_water(0.6));
g.add(6, LookAtPlayer::new(8.0));
let mut hurt = HurtByTarget::new().alert();
@@ -1326,9 +1326,14 @@ const HOME_TOO_FAR: f64 = 100.0;
/// wandering `StrollAroundPoi(HOME, 0.6F, 5)` of the same activity does around
/// the memory. A goal selector here starts the first goal that may run, where
/// vanilla draws a weighted one each time, so a home target that outranked the
/// stroll inside its own reach would leave the brute standing at home.
/// stroll inside its own reach would leave the brute standing at home. As in
/// vanilla, a try is followed by 80 ticks without one (`nextOkStartTime`), so a
/// home it cannot path to costs one search every four seconds, not every tick.
#[derive(Clone, Debug)]
struct BruteStrollHome;
struct BruteStrollHome {
/// `StrollToPoi.nextOkStartTime`: the game time before which it does not try.
next_ok: i64,
}
impl Goal for BruteStrollHome {
goal_clone!();
fn flags(&self) -> u8 {
@@ -1345,9 +1350,13 @@ impl Goal for BruteStrollHome {
f64::from(home[2]) + 0.5,
);
let distance = m.distance_sqr(center);
if distance >= HOME_TOO_FAR * HOME_TOO_FAR || distance <= 10.0 * 10.0 {
if distance >= HOME_TOO_FAR * HOME_TOO_FAR
|| distance <= 10.0 * 10.0
|| cx.w.game_time < self.next_ok
{
return false;
}
self.next_ok = cx.w.game_time + 80;
m.ai.pending_path = m.create_path_to(cx.w, home, HOME_CLOSE_ENOUGH);
m.ai.pending_path.is_some()
}
@@ -2423,6 +2432,29 @@ mod brute_home_tests {
}
});
}
/// `StrollToPoi.nextOkStartTime`: after a try the brute waits 80 ticks before the
/// next one, so a home out of reach is not searched for on every tick.
#[test]
fn a_brute_tries_the_way_home_once_every_80_ticks() {
with_built_test_world(&[], build, |w, _| {
let mut lr = LegacyRandom::new(3);
let mut events = Vec::new();
let mut cx = Cx {
w,
lr: &mut lr,
events: &mut events,
};
let mut m = brute(Vec3::new(0.5, 0.0, 0.5), Some([24, 0, 0]));
let mut goal = BruteStrollHome { next_ok: 0 };
assert!(goal.can_use(&mut m, &mut cx), "the first try finds a path");
m.ai.pending_path = None;
assert!(
!goal.can_use(&mut m, &mut cx),
"no second search in the same 80 ticks"
);
assert_eq!(goal.next_ok, w.game_time + 80);
});
}
/// `PiglinBruteAi.wasHurtBy` erases nothing: a hit does not cost the brute
/// the home it walks back to. `PiglinAi.wasHurtBy` erases
/// CELEBRATE_LOCATION, which is the same field for a piglin.