steel_core/behavior/blocks/building/
bed_block.rs1use 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#[block_behavior]
23pub struct BedBlock {
24 block: BlockRef,
25}
26
27impl BedBlock {
28 #[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}