Skip to main content

steel_core/behavior/blocks/building/
slime_block.rs

1use std::sync::Arc;
2
3use glam::DVec3;
4use steel_macros::block_behavior;
5use steel_registry::{blocks::BlockRef, vanilla_damage_types};
6use steel_utils::{BlockPos, BlockStateId};
7
8use crate::{
9    behavior::{
10        BlockBehavior, BlockPlaceContext, EntityFallDamage, EntityFallOnContext,
11        EntityLandingContext,
12    },
13    entity::{Entity, damage::DamageSource},
14    world::World,
15};
16
17/// Behavior for slime blocks.
18#[block_behavior]
19pub struct SlimeBlock {
20    block: BlockRef,
21}
22
23impl SlimeBlock {
24    /// Creates a slime block behavior.
25    #[must_use]
26    pub const fn new(block: BlockRef) -> Self {
27        Self { block }
28    }
29
30    #[must_use]
31    fn velocity_after_fall(context: EntityLandingContext) -> DVec3 {
32        if context.velocity.y >= 0.0 {
33            return context.velocity;
34        }
35
36        let bounce_factor = if context.is_living_entity { 1.0 } else { 0.8 };
37        DVec3::new(
38            context.velocity.x,
39            -context.velocity.y * bounce_factor,
40            context.velocity.z,
41        )
42    }
43
44    #[must_use]
45    fn velocity_after_step_on(velocity: DVec3, is_stepping_carefully: bool) -> DVec3 {
46        let abs_delta_y = velocity.y.abs();
47        if abs_delta_y >= 0.1 || is_stepping_carefully {
48            return velocity;
49        }
50
51        let scale = 0.4 + abs_delta_y * 0.2;
52        DVec3::new(velocity.x * scale, velocity.y, velocity.z * scale)
53    }
54}
55
56impl BlockBehavior for SlimeBlock {
57    fn get_state_for_placement(&self, _context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
58        Some(self.block.default_state())
59    }
60
61    fn fall_on(
62        &self,
63        _state: BlockStateId,
64        _world: &Arc<World>,
65        _pos: BlockPos,
66        context: EntityFallOnContext<'_>,
67    ) -> Option<EntityFallDamage> {
68        if context.suppresses_bounce {
69            None
70        } else {
71            Some(EntityFallDamage::new(
72                context.fall_distance,
73                0.0,
74                DamageSource::environment(&vanilla_damage_types::FALL),
75            ))
76        }
77    }
78
79    fn update_entity_movement_after_fall_on(
80        &self,
81        state: BlockStateId,
82        world: &Arc<World>,
83        pos: BlockPos,
84        context: EntityLandingContext,
85    ) -> DVec3 {
86        if context.suppresses_bounce {
87            return self.default_update_entity_movement_after_fall_on(state, world, pos, context);
88        }
89
90        Self::velocity_after_fall(context)
91    }
92
93    fn step_on(&self, state: BlockStateId, world: &Arc<World>, pos: BlockPos, entity: &dyn Entity) {
94        entity.set_velocity(Self::velocity_after_step_on(
95            entity.velocity(),
96            entity.is_stepping_carefully(),
97        ));
98
99        self.default_step_on(state, world, pos, entity);
100    }
101}
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106
107    fn landing(
108        velocity: DVec3,
109        is_living_entity: bool,
110        suppresses_bounce: bool,
111    ) -> EntityLandingContext {
112        EntityLandingContext::new(velocity, is_living_entity, suppresses_bounce)
113    }
114
115    #[test]
116    fn living_entities_bounce_with_full_fall_speed() {
117        let velocity =
118            SlimeBlock::velocity_after_fall(landing(DVec3::new(0.25, -1.5, -0.5), true, false));
119
120        assert_eq!(velocity, DVec3::new(0.25, 1.5, -0.5));
121    }
122
123    #[test]
124    fn non_living_entities_bounce_with_vanilla_reduction() {
125        let velocity =
126            SlimeBlock::velocity_after_fall(landing(DVec3::new(0.0, -2.0, 0.0), false, false));
127
128        assert_eq!(velocity, DVec3::new(0.0, 1.6, 0.0));
129    }
130
131    #[test]
132    fn upward_velocity_is_not_changed_by_bounce_logic() {
133        let velocity =
134            SlimeBlock::velocity_after_fall(landing(DVec3::new(0.0, 0.2, 0.0), true, false));
135
136        assert_eq!(velocity, DVec3::new(0.0, 0.2, 0.0));
137    }
138
139    #[test]
140    fn step_on_damps_horizontal_velocity_for_non_careful_entities() {
141        let velocity = SlimeBlock::velocity_after_step_on(DVec3::new(1.0, 0.05, -2.0), false);
142
143        assert!((velocity - DVec3::new(0.41, 0.05, -0.82)).length() < 1.0e-12);
144    }
145
146    #[test]
147    fn step_on_keeps_velocity_for_careful_entities() {
148        let velocity = DVec3::new(1.0, 0.05, -2.0);
149
150        assert_eq!(SlimeBlock::velocity_after_step_on(velocity, true), velocity);
151    }
152
153    #[test]
154    fn step_on_keeps_horizontal_velocity_when_vertical_speed_is_large() {
155        let velocity = DVec3::new(1.0, -0.1, -2.0);
156
157        assert_eq!(
158            SlimeBlock::velocity_after_step_on(velocity, false),
159            velocity
160        );
161    }
162}