follow parent goal (#1614)

* feat: follow parent goal for baby entities

* remove move control and add y range filter
This commit is contained in:
Richard Marshall
2026-02-19 02:23:47 +00:00
committed by GitHub
parent 296b8c23f9
commit 45b1bf7ba1
2 changed files with 128 additions and 0 deletions

View File

@@ -0,0 +1,127 @@
use std::sync::Arc;
use std::sync::atomic::Ordering::Relaxed;
use super::{Controls, Goal, GoalFuture, to_goal_ticks};
use crate::entity::{EntityBase, ai::pathfinder::NavigatorGoal, mob::Mob};
const SEARCH_RADIUS: f64 = 8.0;
const MIN_DISTANCE_SQ: f64 = 9.0;
const MAX_DISTANCE_SQ: f64 = 256.0;
const SEARCH_Y_RANGE: f64 = 4.0;
pub struct FollowParentGoal {
speed: f64,
parent: Option<Arc<dyn EntityBase>>,
delay: i32,
}
impl FollowParentGoal {
#[must_use]
pub fn new(speed: f64) -> Self {
Self {
speed,
parent: None,
delay: 0,
}
}
fn find_parent(mob: &dyn Mob) -> Option<Arc<dyn EntityBase>> {
let mob_entity = mob.get_mob_entity();
let entity = &mob_entity.living_entity.entity;
let pos = entity.pos.load();
let my_type = entity.entity_type;
let world = entity.world.load();
let nearby = world.get_nearby_entities(pos, SEARCH_RADIUS);
let mut closest: Option<(f64, Arc<dyn EntityBase>)> = None;
for candidate in nearby.values() {
let c_entity = candidate.get_entity();
if c_entity.entity_type != my_type {
continue;
}
if c_entity.age.load(Relaxed) < 0 {
continue;
}
let c_pos = c_entity.pos.load();
if (pos.y - c_pos.y).abs() > SEARCH_Y_RANGE {
continue;
}
let dist_sq = pos.squared_distance_to_vec(&c_pos);
if dist_sq < MIN_DISTANCE_SQ {
continue;
}
if closest.as_ref().is_none_or(|(d, _)| dist_sq < *d) {
closest = Some((dist_sq, candidate.clone()));
}
}
closest.map(|(_, e)| e)
}
}
impl Goal for FollowParentGoal {
fn can_start<'a>(&'a mut self, mob: &'a dyn Mob) -> GoalFuture<'a, bool> {
Box::pin(async move {
let age = mob.get_mob_entity().living_entity.entity.age.load(Relaxed);
if age >= 0 {
return false;
}
self.parent = Self::find_parent(mob);
self.parent.is_some()
})
}
fn should_continue<'a>(&'a self, mob: &'a dyn Mob) -> GoalFuture<'a, bool> {
Box::pin(async move {
let age = mob.get_mob_entity().living_entity.entity.age.load(Relaxed);
if age >= 0 {
return false;
}
let Some(parent) = &self.parent else {
return false;
};
let parent_entity = parent.get_entity();
if !parent_entity.is_alive() {
return false;
}
let mob_pos = mob.get_mob_entity().living_entity.entity.pos.load();
let parent_pos = parent_entity.pos.load();
let dist_sq = mob_pos.squared_distance_to_vec(&parent_pos);
(MIN_DISTANCE_SQ..=MAX_DISTANCE_SQ).contains(&dist_sq)
})
}
fn start<'a>(&'a mut self, _mob: &'a dyn Mob) -> GoalFuture<'a, ()> {
Box::pin(async move {
self.delay = 0;
})
}
fn tick<'a>(&'a mut self, mob: &'a dyn Mob) -> GoalFuture<'a, ()> {
Box::pin(async move {
self.delay -= 1;
if self.delay > 0 {
return;
}
self.delay = to_goal_ticks(10);
if let Some(parent) = &self.parent {
let mob_pos = mob.get_mob_entity().living_entity.entity.pos.load();
let parent_pos = parent.get_entity().pos.load();
let mut navigator = mob.get_mob_entity().navigator.lock().await;
navigator.set_progress(NavigatorGoal::new(mob_pos, parent_pos, self.speed));
}
})
}
fn stop<'a>(&'a mut self, _mob: &'a dyn Mob) -> GoalFuture<'a, ()> {
Box::pin(async move {
self.parent = None;
})
}
fn controls(&self) -> Controls {
Controls::empty()
}
}

View File

@@ -9,6 +9,7 @@ pub mod creeper_ignite;
pub mod eat_grass;
pub mod escape_danger;
pub mod follow_owner;
pub mod follow_parent;
pub mod goal_selector;
pub mod look_around;
pub mod look_at_entity;