1use crate::block_entity::BlockEntity;
2use steel_math::lerp;
3
4use super::{
5 Arc, Axis, BlockLocalAabb, BlockPos, BlockStateId, DVec3, DamageSource, Entity, EntityTypeRef,
6 ItemStack, SharedBlockEntity, SmallVec, SoundEventRef, VoxelShape, World, vanilla_damage_types,
7 vanilla_entities,
8};
9
10use crate::entity::entities::FallingBlockEntity;
11
12pub struct PickupResult {
13 pub filled_bucket: ItemStack,
14 pub sound: Option<SoundEventRef>,
15}
16
17pub enum BlockEntityCreation {
23 Created(SharedBlockEntity),
25 NoEntity,
27 Unimplemented,
29}
30
31impl BlockEntityCreation {
32 #[must_use]
34 pub fn from_registered_factory(entity: Option<SharedBlockEntity>) -> Self {
35 entity.map_or(Self::Unimplemented, Self::Created)
36 }
37
38 #[must_use]
40 pub fn into_created(self) -> Option<SharedBlockEntity> {
41 match self {
42 Self::Created(entity) => Some(entity),
43 Self::NoEntity | Self::Unimplemented => None,
44 }
45 }
46}
47
48pub trait RailBehavior: Send + Sync {
54 fn is_straight(&self) -> bool;
56}
57
58pub trait Fallable: Send + Sync {
63 fn on_land(
65 &self,
66 _world: &Arc<World>,
67 _pos: BlockPos,
68 _state: BlockStateId,
69 _replaced_state: BlockStateId,
70 _entity: &FallingBlockEntity,
71 ) {
72 }
73
74 fn on_broken_after_fall(
76 &self,
77 _world: &Arc<World>,
78 _pos: BlockPos,
79 _entity: &FallingBlockEntity,
80 ) {
81 }
82
83 fn get_fall_damage_source(&self, entity: &Arc<FallingBlockEntity>) -> DamageSource {
85 DamageSource::direct(
86 &vanilla_damage_types::FALLING_BLOCK,
87 Arc::<FallingBlockEntity>::clone(entity),
88 )
89 }
90
91 fn is_concrete_powder(&self) -> bool {
93 false
94 }
95}
96
97pub type BlockCollisionBoxes = SmallVec<[BlockLocalAabb; 4]>;
103
104pub struct BlockLootContext<'a> {
110 world: &'a Arc<World>,
111 pos: BlockPos,
112 entity: Option<&'a dyn Entity>,
113 block_entity: Option<&'a dyn BlockEntity>,
114 tool: Option<&'a ItemStack>,
115 explosion_radius: Option<f32>,
116 luck: f32,
117}
118
119impl<'a> BlockLootContext<'a> {
120 #[must_use]
122 pub const fn new(world: &'a Arc<World>, pos: BlockPos) -> Self {
123 Self {
124 world,
125 pos,
126 entity: None,
127 block_entity: None,
128 tool: None,
129 explosion_radius: None,
130 luck: 0.0,
131 }
132 }
133
134 #[must_use]
136 pub const fn with_entity(mut self, entity: Option<&'a dyn Entity>) -> Self {
137 self.entity = entity;
138 self
139 }
140
141 #[must_use]
143 pub const fn with_block_entity(mut self, block_entity: Option<&'a dyn BlockEntity>) -> Self {
144 self.block_entity = block_entity;
145 self
146 }
147
148 #[must_use]
150 pub const fn with_tool(mut self, tool: &'a ItemStack) -> Self {
151 self.tool = Some(tool);
152 self
153 }
154
155 #[must_use]
157 pub const fn with_explosion(mut self, radius: f32) -> Self {
158 self.explosion_radius = Some(radius);
159 self
160 }
161
162 #[must_use]
164 pub const fn with_luck(mut self, luck: f32) -> Self {
165 self.luck = luck;
166 self
167 }
168
169 #[must_use]
171 pub const fn world(&self) -> &'a Arc<World> {
172 self.world
173 }
174
175 #[must_use]
177 pub const fn pos(&self) -> BlockPos {
178 self.pos
179 }
180
181 #[must_use]
183 pub fn get_drops(&self, state: BlockStateId) -> Vec<ItemStack> {
184 World::block_drops(state, self)
185 }
186
187 pub(crate) const fn entity(&self) -> Option<&'a dyn Entity> {
188 self.entity
189 }
190
191 pub(crate) const fn block_entity(&self) -> Option<&'a dyn BlockEntity> {
192 self.block_entity
193 }
194
195 pub(crate) const fn tool(&self) -> Option<&'a ItemStack> {
196 self.tool
197 }
198
199 pub(crate) const fn explosion_radius(&self) -> Option<f32> {
200 self.explosion_radius
201 }
202
203 pub(crate) const fn luck(&self) -> f32 {
204 self.luck
205 }
206}
207
208const COLLISION_CONTEXT_ABOVE_EPSILON: f64 = 1.0e-5;
209
210#[derive(Debug, Clone, Copy, PartialEq)]
212pub struct BlockCollisionContext {
213 entity_bottom: Option<f64>,
214 fall_distance: f64,
215 can_walk_on_powder_snow: bool,
216 is_falling_block: bool,
217 descending: bool,
218 placement: bool,
219}
220
221impl BlockCollisionContext {
222 #[must_use]
224 pub const fn empty() -> Self {
225 Self {
226 entity_bottom: None,
227 fall_distance: 0.0,
228 can_walk_on_powder_snow: false,
229 is_falling_block: false,
230 descending: false,
231 placement: false,
232 }
233 }
234
235 #[must_use]
237 pub const fn entity(entity_bottom: f64, descending: bool) -> Self {
238 Self {
239 entity_bottom: Some(entity_bottom),
240 fall_distance: 0.0,
241 can_walk_on_powder_snow: false,
242 is_falling_block: false,
243 descending,
244 placement: false,
245 }
246 }
247
248 #[must_use]
253 pub const fn with_position(entity_bottom: f64, descending: bool) -> Self {
254 Self {
255 entity_bottom: Some(entity_bottom),
256 fall_distance: 0.0,
257 can_walk_on_powder_snow: false,
258 is_falling_block: false,
259 descending,
260 placement: true,
261 }
262 }
263
264 #[must_use]
268 pub const fn placement_without_entity() -> Self {
269 Self::with_position(f64::MIN, false)
270 }
271
272 #[must_use]
275 pub const fn position_context(y: f64) -> Self {
276 Self {
277 entity_bottom: Some(y),
278 fall_distance: 0.0,
279 can_walk_on_powder_snow: false,
280 is_falling_block: false,
281 descending: false,
282 placement: false,
283 }
284 }
285
286 #[must_use]
288 pub const fn with_fall_distance(mut self, fall_distance: f64) -> Self {
289 self.fall_distance = fall_distance;
290 self
291 }
292
293 #[must_use]
295 pub const fn with_can_walk_on_powder_snow(mut self, can_walk_on_powder_snow: bool) -> Self {
296 self.can_walk_on_powder_snow = can_walk_on_powder_snow;
297 self
298 }
299
300 #[must_use]
302 pub const fn with_falling_block(mut self, is_falling_block: bool) -> Self {
303 self.is_falling_block = is_falling_block;
304 self
305 }
306
307 #[must_use]
309 pub const fn fall_distance(self) -> f64 {
310 self.fall_distance
311 }
312
313 #[must_use]
315 pub const fn can_walk_on_powder_snow(self) -> bool {
316 self.can_walk_on_powder_snow
317 }
318
319 #[must_use]
321 pub const fn is_falling_block(self) -> bool {
322 self.is_falling_block
323 }
324
325 #[must_use]
327 pub const fn is_descending(self) -> bool {
328 self.descending
329 }
330
331 #[must_use]
333 pub const fn is_placement(self) -> bool {
334 self.placement
335 }
336
337 #[must_use]
339 pub fn is_above(self, shape: VoxelShape, pos: BlockPos, default_value: bool) -> bool {
340 let Some(entity_bottom) = self.entity_bottom else {
341 return default_value;
342 };
343
344 entity_bottom > f64::from(pos.y()) + shape.max(Axis::Y) - COLLISION_CONTEXT_ABOVE_EPSILON
345 }
346}
347
348#[derive(Debug, Clone, Copy, PartialEq)]
350pub struct EntityLandingContext {
351 pub velocity: DVec3,
353 pub achieved_movement_y: f64,
355 pub is_living_entity: bool,
357 pub suppresses_bounce: bool,
359 pub gravity: f64,
361 pub air_drag: f64,
363}
364
365#[derive(Debug, Clone, Copy, PartialEq)]
367pub struct EntityFallOnFacts {
368 pub entity_type: EntityTypeRef,
370 pub is_living_entity: bool,
372 pub bounding_box_width: f64,
374 pub bounding_box_height: f64,
376 pub fall_sounds: (SoundEventRef, SoundEventRef),
378}
379
380impl EntityFallOnFacts {
381 #[must_use]
383 pub const fn new(
384 entity_type: EntityTypeRef,
385 is_living_entity: bool,
386 bounding_box_width: f64,
387 bounding_box_height: f64,
388 fall_sounds: (SoundEventRef, SoundEventRef),
389 ) -> Self {
390 Self {
391 entity_type,
392 is_living_entity,
393 bounding_box_width,
394 bounding_box_height,
395 fall_sounds,
396 }
397 }
398
399 #[must_use]
401 pub fn from_entity(entity: &dyn Entity) -> Self {
402 let bounding_box = entity.bounding_box();
403 Self::new(
404 entity.entity_type(),
405 entity.is_living_entity(),
406 bounding_box.width(),
407 bounding_box.height(),
408 entity.fall_sounds(),
409 )
410 }
411
412 #[must_use]
414 pub fn is_player(self) -> bool {
415 self.entity_type == &vanilla_entities::PLAYER
416 }
417
418 #[must_use]
421 pub fn bounding_box_width_squared_height(self) -> f64 {
422 self.bounding_box_width * self.bounding_box_width * self.bounding_box_height
423 }
424}
425
426#[derive(Clone, Copy)]
428pub struct EntityFallOnContext<'a> {
429 pub fall_distance: f64,
431 pub suppresses_bounce: bool,
433 pub entity: EntityFallOnFacts,
435 pub source_entity: Option<&'a dyn Entity>,
437}
438
439impl<'a> EntityFallOnContext<'a> {
440 #[must_use]
442 pub const fn new(
443 fall_distance: f64,
444 suppresses_bounce: bool,
445 entity: EntityFallOnFacts,
446 source_entity: Option<&'a dyn Entity>,
447 ) -> Self {
448 Self {
449 fall_distance,
450 suppresses_bounce,
451 entity,
452 source_entity,
453 }
454 }
455
456 #[must_use]
458 pub fn from_entity(fall_distance: f64, entity: &'a dyn Entity) -> Self {
459 Self::new(
460 fall_distance,
461 entity.is_suppressing_bounce(),
462 EntityFallOnFacts::from_entity(entity),
463 Some(entity),
464 )
465 }
466
467 #[must_use]
469 pub const fn with_fall_distance(mut self, fall_distance: f64) -> Self {
470 self.fall_distance = fall_distance;
471 self
472 }
473
474 #[must_use]
476 pub const fn source_entity(self) -> Option<&'a dyn Entity> {
477 self.source_entity
478 }
479}
480
481#[derive(Debug, Clone)]
483pub struct EntityFallDamage {
484 pub fall_distance: f64,
486 pub damage_modifier: f32,
488 pub source: DamageSource,
490}
491
492impl EntityFallDamage {
493 #[must_use]
495 pub const fn new(fall_distance: f64, damage_modifier: f32, source: DamageSource) -> Self {
496 Self {
497 fall_distance,
498 damage_modifier,
499 source,
500 }
501 }
502}
503
504impl EntityLandingContext {
505 #[must_use]
507 pub const fn new(
508 velocity: DVec3,
509 achieved_movement_y: f64,
510 is_living_entity: bool,
511 suppresses_bounce: bool,
512 gravity: f64,
513 air_drag: f64,
514 ) -> Self {
515 Self {
516 velocity,
517 achieved_movement_y,
518 is_living_entity,
519 suppresses_bounce,
520 gravity,
521 air_drag,
522 }
523 }
524
525 #[must_use]
527 pub const fn default_velocity_after_fall_on(self) -> DVec3 {
528 DVec3::new(self.velocity.x, 0.0, self.velocity.z)
529 }
530
531 #[must_use]
534 pub fn velocity_after_bounce(&self, restitution: f32) -> DVec3 {
535 let portion_with_movement = if self.velocity.y == 0.0 {
536 1.0
537 } else {
538 self.achieved_movement_y / self.velocity.y
539 };
540
541 let gravity_compensation = portion_with_movement * self.gravity;
542 let effective_drag = lerp(portion_with_movement, 1.0, self.air_drag);
543
544 let entity_factor: f32 = if self.is_living_entity { 1.0 } else { 0.8 };
545 let restitution = f64::from(restitution * entity_factor);
546
547 DVec3::new(
548 self.velocity.x,
549 (gravity_compensation - self.velocity.y) * effective_drag * restitution,
550 self.velocity.z,
551 )
552 }
553}
554
555#[cfg(test)]
556mod tests {
557 use super::{DVec3, EntityLandingContext};
558
559 fn landing(velocity_y: f64, achieved_movement_y: f64) -> EntityLandingContext {
560 EntityLandingContext::new(
561 DVec3::new(0.25, velocity_y, -0.5),
562 achieved_movement_y,
563 true,
564 false,
565 0.08,
566 0.98,
567 )
568 }
569
570 #[test]
571 fn bounce_uses_fraction_of_requested_movement_actually_achieved() {
572 let context = landing(-1.5, -0.75);
577 let result = context.velocity_after_bounce(1.0);
578 assert!((result.y - 1.5246).abs() < 1.0e-6, "got {}", result.y);
579 }
580
581 #[test]
582 fn non_living_entities_bounce_lower() {
583 let living = landing(-1.5, -0.75).velocity_after_bounce(1.0);
584 let non_living = EntityLandingContext::new(
585 DVec3::new(0.25, -1.5, -0.5),
586 -0.75,
587 false,
588 false,
589 0.08,
590 0.98,
591 )
592 .velocity_after_bounce(1.0);
593
594 assert!(
595 (non_living.y - living.y * 0.8).abs() < 1.0e-6,
596 "non-living {} vs living {}",
597 non_living.y,
598 living.y
599 );
600 }
601}