Skip to main content

steel_core/behavior/blocks/building/
bed_block.rs

1use std::sync::Arc;
2
3use glam::DVec3;
4use steel_macros::block_behavior;
5use steel_registry::blocks::BlockRef;
6use steel_utils::{BlockPos, BlockStateId};
7
8use crate::{
9    behavior::{
10        BlockBehavior, BlockPlaceContext, EntityFallDamage, EntityFallOnContext,
11        EntityLandingContext,
12    },
13    world::World,
14};
15
16const BED_BOUNCE_SCALE: f64 = 0.660_000_026_226_043_7;
17
18/// Behavior for beds.
19///
20/// TODO: Add two-block placement, bed block entities, sleep interaction, and
21/// invalid-dimension explosion behavior with the rest of the bed system.
22#[block_behavior]
23pub struct BedBlock {
24    block: BlockRef,
25}
26
27impl BedBlock {
28    /// Creates a bed block behavior.
29    #[must_use]
30    pub const fn new(block: BlockRef) -> Self {
31        Self { block }
32    }
33
34    #[must_use]
35    fn fall_context(context: EntityFallOnContext<'_>) -> EntityFallOnContext<'_> {
36        context.with_fall_distance(context.fall_distance * 0.5)
37    }
38
39    #[must_use]
40    fn velocity_after_fall(context: EntityLandingContext) -> DVec3 {
41        if context.velocity.y >= 0.0 {
42            return context.velocity;
43        }
44
45        let entity_factor = if context.is_living_entity { 1.0 } else { 0.8 };
46        DVec3::new(
47            context.velocity.x,
48            -context.velocity.y * BED_BOUNCE_SCALE * entity_factor,
49            context.velocity.z,
50        )
51    }
52}
53
54impl BlockBehavior for BedBlock {
55    fn get_state_for_placement(&self, _context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
56        Some(self.block.default_state())
57    }
58
59    fn fall_on(
60        &self,
61        state: BlockStateId,
62        world: &Arc<World>,
63        pos: BlockPos,
64        context: EntityFallOnContext<'_>,
65    ) -> Option<EntityFallDamage> {
66        self.default_fall_on(state, world, pos, Self::fall_context(context))
67    }
68
69    fn update_entity_movement_after_fall_on(
70        &self,
71        state: BlockStateId,
72        world: &Arc<World>,
73        pos: BlockPos,
74        context: EntityLandingContext,
75    ) -> DVec3 {
76        if context.suppresses_bounce {
77            return self.default_update_entity_movement_after_fall_on(state, world, pos, context);
78        }
79
80        Self::velocity_after_fall(context)
81    }
82}
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87
88    use steel_registry::{sound_events, vanilla_entities};
89
90    use crate::behavior::EntityFallOnFacts;
91
92    fn landing(
93        velocity: DVec3,
94        is_living_entity: bool,
95        suppresses_bounce: bool,
96    ) -> EntityLandingContext {
97        EntityLandingContext::new(velocity, is_living_entity, suppresses_bounce)
98    }
99
100    #[test]
101    fn bed_halves_fall_distance_before_default_damage() {
102        let context = BedBlock::fall_context(EntityFallOnContext::new(
103            12.0,
104            false,
105            EntityFallOnFacts::new(
106                &vanilla_entities::PLAYER,
107                true,
108                0.6,
109                1.8,
110                (
111                    &sound_events::ENTITY_PLAYER_SMALL_FALL,
112                    &sound_events::ENTITY_PLAYER_BIG_FALL,
113                ),
114            ),
115            None,
116        ));
117
118        assert!((context.fall_distance - 6.0).abs() < f64::EPSILON);
119        assert!(!context.suppresses_bounce);
120        assert!(context.entity.is_player());
121    }
122
123    #[test]
124    fn living_entities_bounce_with_bed_factor() {
125        let velocity =
126            BedBlock::velocity_after_fall(landing(DVec3::new(1.0, -3.0, -2.0), true, false));
127
128        assert!((velocity.y - 1.980_000_078_678_131).abs() < f64::EPSILON);
129        assert!((velocity.x - 1.0).abs() < f64::EPSILON);
130        assert!((velocity.z + 2.0).abs() < f64::EPSILON);
131    }
132
133    #[test]
134    fn non_living_entities_bounce_with_vanilla_reduction() {
135        let velocity =
136            BedBlock::velocity_after_fall(landing(DVec3::new(1.0, -3.0, -2.0), false, false));
137
138        assert!((velocity.y - 1.584_000_062_942_505).abs() < f64::EPSILON);
139    }
140
141    #[test]
142    fn upward_velocity_is_not_changed_by_bounce_logic() {
143        let velocity =
144            BedBlock::velocity_after_fall(landing(DVec3::new(1.0, 0.5, -2.0), true, false));
145
146        assert_eq!(velocity, DVec3::new(1.0, 0.5, -2.0));
147    }
148}