Skip to main content

steel_core/entity/ai/goal/
float_goal.rs

1use crate::entity::ai::goal::selector::{Goal, GoalControls};
2use crate::entity::{MobBase, PathfinderMob};
3
4const FLOAT_JUMP_CHANCE: f32 = 0.8;
5
6pub struct FloatGoal;
7
8impl FloatGoal {
9    #[must_use]
10    pub(crate) fn new(mob_base: &MobBase) -> Self {
11        mob_base.navigation().lock().set_can_float(true);
12        Self
13    }
14}
15
16impl Goal for FloatGoal {
17    fn controls(&self) -> GoalControls {
18        GoalControls::JUMP
19    }
20
21    fn can_use(&mut self, mob: &dyn PathfinderMob) -> bool {
22        mob.is_in_water() && mob.fluid_contact().water_height() > mob.get_fluid_jump_threshold()
23            || mob.is_in_lava()
24    }
25
26    fn requires_update_every_tick(&self) -> bool {
27        true
28    }
29
30    fn tick(&mut self, mob: &dyn PathfinderMob) {
31        if rand::random::<f32>() < FLOAT_JUMP_CHANCE {
32            mob.mob_base().controls().lock().jump_control.jump();
33        }
34    }
35}
36
37#[cfg(test)]
38mod tests {
39    use std::sync::Weak;
40
41    use glam::DVec3;
42    use steel_registry::entity_type::EntityTypeRef;
43    use steel_registry::{init_vanilla_registry, vanilla_entities};
44    use steel_utils::locks::SyncMutex;
45
46    use super::*;
47    use crate::entity::{
48        Entity, EntityBase, EntityFluidContact, LivingEntity, LivingEntityBase, Mob,
49    };
50
51    struct TestPathfinderMob {
52        base: EntityBase,
53        living_base: LivingEntityBase,
54        mob_base: MobBase,
55        mob_flags: SyncMutex<i8>,
56        health: SyncMutex<f32>,
57    }
58
59    impl TestPathfinderMob {
60        fn new(water_height: f64, lava_height: f64) -> Self {
61            init_vanilla_registry();
62            let base = EntityBase::new(
63                1,
64                DVec3::ZERO,
65                vanilla_entities::PIG.dimensions,
66                Weak::new(),
67            );
68            base.set_fluid_contact(EntityFluidContact::from_parts(
69                water_height,
70                lava_height,
71                false,
72                false,
73            ));
74            Self {
75                base,
76                living_base: LivingEntityBase::new(&vanilla_entities::PIG),
77                mob_base: MobBase::new(),
78                mob_flags: SyncMutex::new(0),
79                health: SyncMutex::new(10.0),
80            }
81        }
82    }
83
84    crate::entity::impl_test_downcast_type!(TestPathfinderMob);
85
86    impl Entity for TestPathfinderMob {
87        fn base(&self) -> &EntityBase {
88            &self.base
89        }
90
91        fn entity_type(&self) -> EntityTypeRef {
92            &vanilla_entities::PIG
93        }
94    }
95
96    impl LivingEntity for TestPathfinderMob {
97        fn living_base(&self) -> &LivingEntityBase {
98            &self.living_base
99        }
100
101        fn get_health(&self) -> f32 {
102            *self.health.lock()
103        }
104
105        fn set_health(&self, health: f32) {
106            *self.health.lock() = health;
107        }
108    }
109
110    impl Mob for TestPathfinderMob {
111        fn mob_base(&self) -> &MobBase {
112            &self.mob_base
113        }
114
115        fn mob_flags(&self) -> i8 {
116            *self.mob_flags.lock()
117        }
118
119        fn set_mob_flags(&self, flags: i8) {
120            *self.mob_flags.lock() = flags;
121        }
122    }
123
124    impl PathfinderMob for TestPathfinderMob {}
125
126    #[test]
127    fn float_goal_sets_navigation_can_float() {
128        let mob = TestPathfinderMob::new(0.0, 0.0);
129
130        assert!(!mob.mob_base.navigation().lock().can_float());
131
132        let _goal = FloatGoal::new(&mob.mob_base);
133
134        assert!(mob.mob_base.navigation().lock().can_float());
135    }
136
137    #[test]
138    fn float_goal_uses_water_threshold_or_lava() {
139        let shallow_water_mob = TestPathfinderMob::new(0.3, 0.0);
140        let deep_water_mob = TestPathfinderMob::new(0.5, 0.0);
141        let lava_mob = TestPathfinderMob::new(0.0, 0.1);
142        let mut goal = FloatGoal;
143
144        assert!(!goal.can_use(&shallow_water_mob));
145        assert!(goal.can_use(&deep_water_mob));
146        assert!(goal.can_use(&lava_mob));
147    }
148}