steel_core/entity/ai/goal/
float_goal.rs1use 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}