Skip to main content

steel_core/entity/base/
movement.rs

1use glam::DVec3;
2use steel_utils::BlockPos;
3
4/// A vanilla movement segment used by block-contact effects.
5#[derive(Debug, Clone, Copy, PartialEq)]
6pub struct EntityMovement {
7    from: DVec3,
8    to: DVec3,
9    axis_dependent_original_movement: Option<DVec3>,
10}
11
12impl EntityMovement {
13    /// Creates a movement segment without axis-dependent original movement.
14    #[must_use]
15    pub const fn new(from: DVec3, to: DVec3) -> Self {
16        Self {
17            from,
18            to,
19            axis_dependent_original_movement: None,
20        }
21    }
22
23    /// Creates a movement segment with the original requested movement.
24    #[must_use]
25    pub const fn with_axis_dependent_original_movement(
26        from: DVec3,
27        to: DVec3,
28        axis_dependent_original_movement: DVec3,
29    ) -> Self {
30        Self {
31            from,
32            to,
33            axis_dependent_original_movement: Some(axis_dependent_original_movement),
34        }
35    }
36
37    /// Returns the segment start position.
38    #[must_use]
39    pub const fn from(self) -> DVec3 {
40        self.from
41    }
42
43    /// Returns the segment end position.
44    #[must_use]
45    pub const fn to(self) -> DVec3 {
46        self.to
47    }
48
49    /// Returns the requested movement used for vanilla axis-ordered scans.
50    #[must_use]
51    pub const fn axis_dependent_original_movement(self) -> Option<DVec3> {
52        self.axis_dependent_original_movement
53    }
54}
55
56/// Vanilla server-driven gate for vertical collision and ground-contact updates.
57#[derive(Debug, Clone, Copy, PartialEq, Eq)]
58pub enum EntityVerticalMovementStateUpdate {
59    /// Preserve the existing vertical collision and ground-contact state.
60    Preserve,
61    /// Refresh vertical collision and ground-contact state from the movement result.
62    Refresh,
63}
64
65impl EntityVerticalMovementStateUpdate {
66    /// Returns the vanilla update behavior for a completed movement request.
67    #[must_use]
68    pub fn for_move(requested_delta: DVec3, server_driven_movement: bool) -> Self {
69        if requested_delta.y.abs() > 0.0 || server_driven_movement {
70            Self::Refresh
71        } else {
72            Self::Preserve
73        }
74    }
75
76    /// Returns whether vertical collision and ground contact should be refreshed.
77    #[inline]
78    #[must_use]
79    pub const fn refreshes_state(self) -> bool {
80        matches!(self, Self::Refresh)
81    }
82}
83
84/// Vanilla collision and ground-contact flags updated by `Entity.move`.
85#[derive(Debug, Clone, Copy, PartialEq, Eq)]
86pub struct EntityMovementFlags {
87    on_ground: bool,
88    horizontal_collision: bool,
89    vertical_collision: bool,
90    vertical_collision_below: bool,
91}
92
93impl EntityMovementFlags {
94    /// Creates movement flags for an entity that has not moved yet.
95    #[must_use]
96    pub const fn new() -> Self {
97        Self {
98            on_ground: false,
99            horizontal_collision: false,
100            vertical_collision: false,
101            vertical_collision_below: false,
102        }
103    }
104
105    /// Creates movement flags from a completed movement pass.
106    #[must_use]
107    pub fn after_move(
108        on_ground: bool,
109        horizontal_collision: bool,
110        vertical_collision: bool,
111        requested_delta: DVec3,
112    ) -> Self {
113        Self {
114            on_ground,
115            horizontal_collision,
116            vertical_collision,
117            vertical_collision_below: vertical_collision && requested_delta.y < 0.0,
118        }
119    }
120
121    /// Creates movement flags from a completed movement pass while preserving
122    /// vertical/ground state when vanilla skips that update.
123    #[must_use]
124    pub fn after_move_with_previous(
125        previous: Self,
126        vertical_state_update: EntityVerticalMovementStateUpdate,
127        on_ground: bool,
128        horizontal_collision: bool,
129        vertical_collision: bool,
130        requested_delta: DVec3,
131    ) -> Self {
132        let mut next = previous.with_horizontal_collision(horizontal_collision);
133        if vertical_state_update.refreshes_state() {
134            next.on_ground = on_ground;
135            next.vertical_collision = vertical_collision;
136            next.vertical_collision_below = vertical_collision && requested_delta.y < 0.0;
137        }
138        next
139    }
140
141    /// Returns true if the entity is touching the ground.
142    #[inline]
143    #[must_use]
144    pub const fn on_ground(self) -> bool {
145        self.on_ground
146    }
147
148    /// Returns true if the last movement was clipped horizontally.
149    #[inline]
150    #[must_use]
151    pub const fn horizontal_collision(self) -> bool {
152        self.horizontal_collision
153    }
154
155    /// Returns true if the last movement was clipped vertically.
156    #[inline]
157    #[must_use]
158    pub const fn vertical_collision(self) -> bool {
159        self.vertical_collision
160    }
161
162    /// Returns true if the last vertical collision was below the entity.
163    #[inline]
164    #[must_use]
165    pub const fn vertical_collision_below(self) -> bool {
166        self.vertical_collision_below
167    }
168
169    /// Returns the same flags with a new ground-contact value.
170    #[must_use]
171    pub const fn with_on_ground(mut self, on_ground: bool) -> Self {
172        self.on_ground = on_ground;
173        self
174    }
175
176    /// Returns the same flags with a new horizontal-collision value.
177    #[must_use]
178    pub const fn with_horizontal_collision(mut self, horizontal_collision: bool) -> Self {
179        self.horizontal_collision = horizontal_collision;
180        self
181    }
182
183    /// Returns the same ground state with collision flags cleared.
184    #[must_use]
185    pub const fn without_collisions(mut self) -> Self {
186        self.horizontal_collision = false;
187        self.vertical_collision = false;
188        self.vertical_collision_below = false;
189        self
190    }
191}
192
193impl Default for EntityMovementFlags {
194    fn default() -> Self {
195        Self::new()
196    }
197}
198
199/// Vanilla ground-support state updated by `Entity.checkSupportingBlock`.
200#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
201pub struct EntityGroundContact {
202    supporting_block: Option<BlockPos>,
203    on_ground_no_blocks: bool,
204}
205
206impl EntityGroundContact {
207    /// Creates airborne ground-contact state.
208    #[must_use]
209    pub const fn airborne() -> Self {
210        Self {
211            supporting_block: None,
212            on_ground_no_blocks: false,
213        }
214    }
215
216    /// Creates grounded contact state from the support search result.
217    #[must_use]
218    pub const fn on_ground(supporting_block: Option<BlockPos>) -> Self {
219        Self {
220            supporting_block,
221            on_ground_no_blocks: supporting_block.is_none(),
222        }
223    }
224
225    /// Returns the supporting block selected by vanilla support rules.
226    #[must_use]
227    pub const fn supporting_block(self) -> Option<BlockPos> {
228        self.supporting_block
229    }
230
231    /// Returns true when the entity is grounded but no block support was found.
232    #[must_use]
233    pub const fn on_ground_no_blocks(self) -> bool {
234        self.on_ground_no_blocks
235    }
236}
237
238/// Vanilla movement side effects emitted by `Entity.move`.
239#[derive(Debug, Clone, Copy, PartialEq, Eq)]
240pub enum EntityMovementEmission {
241    /// Emit no movement sounds or game events.
242    None,
243    /// Emit movement sounds only.
244    Sounds,
245    /// Emit movement game events only.
246    Events,
247    /// Emit both movement sounds and game events.
248    All,
249}
250
251impl EntityMovementEmission {
252    /// Returns whether this movement emits any side effects.
253    #[must_use]
254    pub const fn emits_anything(self) -> bool {
255        !matches!(self, Self::None)
256    }
257
258    /// Returns whether this movement emits game events.
259    #[must_use]
260    pub const fn emits_events(self) -> bool {
261        matches!(self, Self::Events | Self::All)
262    }
263
264    /// Returns whether this movement emits sounds.
265    #[must_use]
266    pub const fn emits_sounds(self) -> bool {
267        matches!(self, Self::Sounds | Self::All)
268    }
269}
270
271/// Vanilla movement distance counters used by step, swim, and flap side effects.
272#[derive(Debug, Clone, Copy, PartialEq)]
273pub struct EntityMovementProgress {
274    pub(super) move_dist: f32,
275    pub(super) fly_dist: f32,
276    pub(super) next_step: f32,
277    pub(super) crystal_sound_intensity: f32,
278    pub(super) last_crystal_sound_play_tick: i32,
279}
280
281impl EntityMovementProgress {
282    /// Creates default vanilla movement progress state.
283    #[must_use]
284    pub const fn new() -> Self {
285        Self {
286            move_dist: 0.0,
287            fly_dist: 0.0,
288            next_step: 1.0,
289            crystal_sound_intensity: 0.0,
290            last_crystal_sound_play_tick: 0,
291        }
292    }
293
294    /// Adds movement distance from a completed movement pass.
295    pub fn add_movement(&mut self, clipped_movement: DVec3, climbing: bool) {
296        let moved_distance = (clipped_movement.length() * 0.6) as f32;
297        let horizontal_moved_distance = ((clipped_movement.x * clipped_movement.x
298            + clipped_movement.z * clipped_movement.z)
299            .sqrt()
300            * 0.6) as f32;
301
302        self.move_dist += if climbing {
303            moved_distance
304        } else {
305            horizontal_moved_distance
306        };
307        self.fly_dist += moved_distance;
308    }
309
310    /// Returns vanilla `moveDist`.
311    #[must_use]
312    pub const fn move_dist(self) -> f32 {
313        self.move_dist
314    }
315
316    /// Returns vanilla `flyDist`.
317    #[must_use]
318    pub const fn fly_dist(self) -> f32 {
319        self.fly_dist
320    }
321
322    /// Returns vanilla `nextStep`.
323    #[must_use]
324    pub const fn next_step(self) -> f32 {
325        self.next_step
326    }
327
328    /// Returns whether movement crossed the next step threshold.
329    #[must_use]
330    pub const fn crossed_next_step(self) -> bool {
331        self.move_dist > self.next_step
332    }
333}
334
335impl Default for EntityMovementProgress {
336    fn default() -> Self {
337        Self::new()
338    }
339}