Skip to main content

steel_core/entity/ai/goal/
squid_flee_goal.rs

1use steel_registry::{blocks::block_state_ext::BlockStateExt, fluid::FluidStateExt};
2use steel_utils::{BlockPos, Downcast, TICKS_PER_SECOND};
3
4use crate::{
5    entity::{
6        PathfinderMob, SharedEntity,
7        ai::goal::selector::{Goal, GoalControls},
8        entities::SquidEntity,
9    },
10    world::LevelReader,
11};
12
13const SQUID_FLEE_SPEED: f64 = 3.0;
14const SQUID_FLEE_MIN_DISTANCE: f64 = 5.0;
15
16pub struct SquidFleeGoal {
17    flee_ticks: i32,
18}
19
20impl SquidFleeGoal {
21    pub const fn new() -> Self {
22        Self { flee_ticks: 0 }
23    }
24}
25
26impl Goal for SquidFleeGoal {
27    fn controls(&self) -> GoalControls {
28        GoalControls::EMPTY
29    }
30
31    fn can_use(&mut self, mob: &dyn PathfinderMob) -> bool {
32        mob.is_in_water()
33            && mob
34                .last_hurt_by_mob()
35                .is_some_and(|attacker| mob.distance_to_sqr(attacker.position()) < 100.0)
36    }
37
38    fn requires_update_every_tick(&self) -> bool {
39        true
40    }
41
42    fn start(&mut self, _mob: &dyn PathfinderMob) {
43        self.flee_ticks = 0;
44    }
45
46    fn tick(&mut self, mob: &dyn PathfinderMob, entity: &SharedEntity) {
47        self.flee_ticks += 1;
48
49        let Some(attacker) = mob.last_hurt_by_mob() else {
50            return;
51        };
52
53        let Some(squid) = mob.downcast_ref::<SquidEntity>() else {
54            tracing::warn!("SquidFleeGoal assigned to non-squid entity");
55            return;
56        };
57
58        let Some(world) = mob.level() else {
59            return;
60        };
61
62        let squid_pos = entity.position();
63        let attacker_pos = attacker.position();
64        let mut flee_to = squid_pos - attacker_pos;
65
66        let block_pos = BlockPos::from(squid_pos + flee_to);
67        let block_state = world.get_block_state(block_pos);
68        let fluid_state = block_state.get_fluid_state();
69
70        if fluid_state.is_water() || block_state.is_air() {
71            let distance = flee_to.length();
72
73            if distance > 0.0 {
74                // Vanilla calls `fleeTo.normalize()` but throws the result due to `fleeTo` being
75                // an immutable value. As the normalized vector does not get used, we should NOT
76                // normalize the vector in Steel.
77                let avoid_speed = if distance > SQUID_FLEE_MIN_DISTANCE {
78                    SQUID_FLEE_SPEED
79                        - (distance - SQUID_FLEE_MIN_DISTANCE) / SQUID_FLEE_MIN_DISTANCE
80                } else {
81                    SQUID_FLEE_SPEED
82                };
83
84                if avoid_speed > 0.0 {
85                    flee_to *= avoid_speed;
86                }
87            }
88
89            if block_state.is_air() {
90                flee_to.y = 0.0;
91            }
92
93            squid.set_movement_vector(flee_to / f64::from(TICKS_PER_SECOND));
94        }
95    }
96}