From 45b1bf7ba1b767e566518ccc6b257740517f5871 Mon Sep 17 00:00:00 2001 From: Richard Marshall Date: Thu, 19 Feb 2026 02:23:47 +0000 Subject: [PATCH] follow parent goal (#1614) * feat: follow parent goal for baby entities * remove move control and add y range filter --- pumpkin/src/entity/ai/goal/follow_parent.rs | 127 ++++++++++++++++++++ pumpkin/src/entity/ai/goal/mod.rs | 1 + 2 files changed, 128 insertions(+) create mode 100644 pumpkin/src/entity/ai/goal/follow_parent.rs diff --git a/pumpkin/src/entity/ai/goal/follow_parent.rs b/pumpkin/src/entity/ai/goal/follow_parent.rs new file mode 100644 index 000000000..f7d0d9b65 --- /dev/null +++ b/pumpkin/src/entity/ai/goal/follow_parent.rs @@ -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>, + 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> { + 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)> = 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() + } +} diff --git a/pumpkin/src/entity/ai/goal/mod.rs b/pumpkin/src/entity/ai/goal/mod.rs index 042a45350..1a226abf9 100644 --- a/pumpkin/src/entity/ai/goal/mod.rs +++ b/pumpkin/src/entity/ai/goal/mod.rs @@ -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;