steel_core/behavior/blocks/building/
slime_block.rs1use 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#[block_behavior]
19pub struct SlimeBlock {
20 block: BlockRef,
21}
22
23impl SlimeBlock {
24 #[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}