Skip to main content

steel_core/entity/ai/goal/
breed_goal.rs

1use glam::DVec3;
2
3use super::reduced_tick_delay;
4use super::selector::{Goal, GoalControls};
5use crate::entity::ai::targeting::TargetingConditions;
6use crate::entity::{Animal, PathfinderMob, SharedEntity};
7
8const PARTNER_SEARCH_RANGE: f64 = 8.0;
9const BREED_DISTANCE_SQR: f64 = 9.0;
10const BREED_TIME: i32 = 60;
11
12pub struct BreedGoal {
13    partner: Option<SharedEntity>,
14    love_time: i32,
15    speed_modifier: f64,
16}
17
18impl BreedGoal {
19    #[must_use]
20    pub(crate) const fn new(speed_modifier: f64) -> Self {
21        Self {
22            partner: None,
23            love_time: 0,
24            speed_modifier,
25        }
26    }
27
28    fn get_free_partner(mob: &dyn PathfinderMob, animal: &dyn Animal) -> Option<SharedEntity> {
29        let world = mob.level()?;
30        let search_box = mob.bounding_box().inflate(PARTNER_SEARCH_RANGE);
31        let partner_targeting = TargetingConditions::for_non_combat()
32            .range(PARTNER_SEARCH_RANGE)
33            .ignore_line_of_sight();
34
35        world.nearest_entity_in_aabb_matching(&search_box, mob.position(), |entity| {
36            let Some(candidate) = entity.as_animal() else {
37                return false;
38            };
39            if !partner_targeting.test(world.as_ref(), Some(mob), candidate) {
40                return false;
41            }
42            if !animal.can_mate(candidate) {
43                return false;
44            }
45
46            !entity
47                .as_pathfinder_mob()
48                .is_some_and(PathfinderMob::is_panicking)
49        })
50    }
51}
52
53impl Goal for BreedGoal {
54    fn controls(&self) -> GoalControls {
55        GoalControls::MOVE | GoalControls::LOOK
56    }
57
58    fn can_use(&mut self, mob: &dyn PathfinderMob) -> bool {
59        let Some(animal) = mob.as_animal() else {
60            return false;
61        };
62        if !animal.is_in_love() {
63            return false;
64        }
65
66        self.partner = Self::get_free_partner(mob, animal);
67        self.partner.is_some()
68    }
69
70    fn can_continue_to_use(&mut self, _mob: &dyn PathfinderMob) -> bool {
71        let Some(partner) = &self.partner else {
72            return false;
73        };
74        if !partner.is_alive() {
75            return false;
76        }
77        if self.love_time >= BREED_TIME {
78            return false;
79        }
80        if partner
81            .as_pathfinder_mob()
82            .is_some_and(PathfinderMob::is_panicking)
83        {
84            return false;
85        }
86
87        partner.as_animal().is_some_and(Animal::is_in_love)
88    }
89
90    fn stop(&mut self, _mob: &dyn PathfinderMob) {
91        self.partner = None;
92        self.love_time = 0;
93    }
94
95    fn tick(&mut self, mob: &dyn PathfinderMob) {
96        let Some(partner) = &self.partner else {
97            return;
98        };
99        let Some(animal) = mob.as_animal() else {
100            return;
101        };
102        let Some(partner_animal) = partner.as_animal() else {
103            return;
104        };
105
106        let partner_position = partner.position();
107        mob.mob_base().controls().lock().look_control.set_look_at(
108            DVec3::new(partner_position.x, partner.get_eye_y(), partner_position.z),
109            10.0,
110            mob.max_head_x_rot(),
111        );
112        mob.move_to_pos(partner_position, self.speed_modifier);
113
114        self.love_time += 1;
115        if self.love_time < reduced_tick_delay(BREED_TIME)
116            || mob.position().distance_squared(partner_position) >= BREED_DISTANCE_SQR
117        {
118            return;
119        }
120
121        let Some(world) = mob.level() else {
122            return;
123        };
124        animal.spawn_child_from_breeding(&world, partner_animal);
125    }
126}
127
128#[cfg(test)]
129mod tests {
130    use std::sync::Weak;
131
132    use glam::DVec3;
133    use steel_registry::{init_vanilla_registry, vanilla_entities};
134
135    use super::*;
136    use crate::entity::entities::PigEntity;
137
138    #[test]
139    fn breed_goal_uses_move_and_look_controls() {
140        let goal = BreedGoal::new(1.0);
141
142        assert_eq!(goal.controls(), GoalControls::MOVE | GoalControls::LOOK);
143    }
144
145    #[test]
146    fn breed_goal_requires_love_mode() {
147        init_vanilla_registry();
148        let pig = PigEntity::new(&vanilla_entities::PIG, 1, DVec3::ZERO, Weak::new());
149        let mut goal = BreedGoal::new(1.0);
150
151        assert!(!goal.can_use(&pig));
152    }
153}