Skip to main content

steel_core/behavior/block/
collision.rs

1use super::{
2    Arc, Axis, BLOCK_BEHAVIORS, BlockCollisionContext, BlockPos, BlockStateExt, BlockStateId,
3    BooleanOp, DVec3, World, WorldAabb, collide, join_unoptimized_boxes,
4};
5
6/// Vanilla `Block.pushEntitiesUp` for block-state replacements that add collision.
7///
8/// Returns `new_state` so callers can mirror vanilla call sites that transform
9/// the replacement state before setting it in the world.
10pub(crate) fn push_entities_up(
11    old_state: BlockStateId,
12    new_state: BlockStateId,
13    world: &Arc<World>,
14    pos: BlockPos,
15) -> BlockStateId {
16    let added_collision = added_collision_boxes(old_state, new_state, world, pos);
17    let Some(query_box) = world_aabb_bounds(&added_collision) else {
18        return new_state;
19    };
20
21    for entity in world.get_entities_in_aabb(&query_box) {
22        let offset = collide(
23            Axis::Y,
24            &entity.bounding_box().translate(DVec3::ZERO.with_y(1.0)),
25            &added_collision,
26            -1.0,
27        );
28        if let Err(error) =
29            entity.try_set_position(entity.position() + DVec3::new(0.0, 1.0 + offset, 0.0))
30        {
31            log::debug!(
32                "Failed to push entity {} up after block collision change at {pos:?}: {error}",
33                entity.id()
34            );
35        }
36    }
37
38    new_state
39}
40
41fn added_collision_boxes(
42    old_state: BlockStateId,
43    new_state: BlockStateId,
44    world: &Arc<World>,
45    pos: BlockPos,
46) -> Vec<WorldAabb> {
47    let context = BlockCollisionContext::empty();
48    let old_shape = BLOCK_BEHAVIORS
49        .get_behavior(old_state.get_block())
50        .get_collision_shape(old_state, world.as_ref(), pos, context);
51    let new_shape = BLOCK_BEHAVIORS
52        .get_behavior(new_state.get_block())
53        .get_collision_shape(new_state, world.as_ref(), pos, context);
54
55    join_unoptimized_boxes(old_shape, new_shape, BooleanOp::OnlySecond)
56        .into_iter()
57        .map(|aabb| aabb.at_block(pos))
58        .collect()
59}
60
61pub(super) fn world_aabb_bounds(boxes: &[WorldAabb]) -> Option<WorldAabb> {
62    let first = boxes.first()?;
63    let mut min_x = first.min_x();
64    let mut min_y = first.min_y();
65    let mut min_z = first.min_z();
66    let mut max_x = first.max_x();
67    let mut max_y = first.max_y();
68    let mut max_z = first.max_z();
69
70    for aabb in boxes {
71        min_x = min_x.min(aabb.min_x());
72        min_y = min_y.min(aabb.min_y());
73        min_z = min_z.min(aabb.min_z());
74        max_x = max_x.max(aabb.max_x());
75        max_y = max_y.max(aabb.max_y());
76        max_z = max_z.max(aabb.max_z());
77    }
78
79    Some(WorldAabb::new(min_x, min_y, min_z, max_x, max_y, max_z))
80}