Skip to main content

steel_core/behavior/block/
context.rs

1use crate::block_entity::BlockEntity;
2
3use super::{
4    Arc, Axis, BlockLocalAabb, BlockPos, BlockStateId, DVec3, DamageSource, Entity, EntityTypeRef,
5    ItemStack, SharedBlockEntity, SmallVec, SoundEventRef, VoxelShape, World, vanilla_damage_types,
6    vanilla_entities,
7};
8use crate::entity::entities::FallingBlockEntity;
9
10pub struct PickupResult {
11    pub filled_bucket: ItemStack,
12    pub sound: Option<SoundEventRef>,
13}
14
15/// Result of invoking a block's Vanilla block-entity factory.
16///
17/// `NoEntity` is distinct from `Unimplemented`: some Vanilla blocks, notably the moving-piston
18/// placeholder, intentionally return no entity from normal block creation even though their
19/// state accepts an explicitly-created block entity.
20pub enum BlockEntityCreation {
21    /// The block factory created its entity.
22    Created(SharedBlockEntity),
23    /// The implemented Vanilla factory intentionally created no entity.
24    NoEntity,
25    /// Steel has not implemented this block's factory yet.
26    Unimplemented,
27}
28
29impl BlockEntityCreation {
30    /// Converts an optional registered implementation into a factory result.
31    #[must_use]
32    pub fn from_registered_factory(entity: Option<SharedBlockEntity>) -> Self {
33        entity.map_or(Self::Unimplemented, Self::Created)
34    }
35
36    /// Returns the created entity, if the factory produced one.
37    #[must_use]
38    pub fn into_created(self) -> Option<SharedBlockEntity> {
39        match self {
40            Self::Created(entity) => Some(entity),
41            Self::NoEntity | Self::Unimplemented => None,
42        }
43    }
44}
45
46/// Shared behavior exposed by blocks in vanilla's `BaseRailBlock` hierarchy.
47///
48/// Rail topology uses this capability in addition to the `minecraft:rails` tag.
49/// This keeps class-hierarchy checks extensible without relying on concrete
50/// downcasts.
51pub trait RailBehavior: Send + Sync {
52    /// Returns whether this rail forbids curved shapes.
53    fn is_straight(&self) -> bool;
54}
55
56/// Shared behavior exposed by blocks implementing vanilla's `Fallable` interface.
57///
58/// Falling entities use this capability for landing, failed-placement, and
59/// damage-source callbacks without depending on one concrete Rust block type.
60pub trait Fallable: Send + Sync {
61    /// Called after a falling entity successfully places its carried state.
62    fn on_land(
63        &self,
64        _world: &Arc<World>,
65        _pos: BlockPos,
66        _state: BlockStateId,
67        _replaced_state: BlockStateId,
68        _entity: &FallingBlockEntity,
69    ) {
70    }
71
72    /// Called when a falling entity breaks instead of placing its carried state.
73    fn on_broken_after_fall(
74        &self,
75        _world: &Arc<World>,
76        _pos: BlockPos,
77        _entity: &FallingBlockEntity,
78    ) {
79    }
80
81    /// Returns the damage source used when this falling block hurts entities.
82    fn get_fall_damage_source(&self, entity: &FallingBlockEntity) -> DamageSource {
83        DamageSource::environment(&vanilla_damage_types::FALLING_BLOCK)
84            .with_direct_entity(entity.id())
85            .with_causing_entity(entity.id())
86    }
87
88    /// Returns whether this behavior is in vanilla's `ConcretePowderBlock` hierarchy.
89    fn is_concrete_powder(&self) -> bool {
90        false
91    }
92}
93
94/// Resolved block-local collision boxes for a live block state.
95///
96/// Most blocks materialize their extracted static voxel shape here. Dynamic
97/// blocks such as moving pistons can instead return boxes computed from live
98/// world data without forcing runtime shapes into the static registry.
99pub type BlockCollisionBoxes = SmallVec<[BlockLocalAabb; 4]>;
100
101/// Live parameters used to resolve a block's loot.
102///
103/// This is the Steel counterpart to vanilla's block `LootParams`. Behaviors can
104/// override loot generation while retaining the original tool, entity, luck,
105/// and position when delegating to another block state.
106pub struct BlockLootContext<'a> {
107    world: &'a Arc<World>,
108    pos: BlockPos,
109    entity: Option<&'a dyn Entity>,
110    block_entity: Option<&'a dyn BlockEntity>,
111    tool: Option<&'a ItemStack>,
112    luck: f32,
113}
114
115impl<'a> BlockLootContext<'a> {
116    /// Creates a no-tool block loot context.
117    #[must_use]
118    pub const fn new(world: &'a Arc<World>, pos: BlockPos) -> Self {
119        Self {
120            world,
121            pos,
122            entity: None,
123            block_entity: None,
124            tool: None,
125            luck: 0.0,
126        }
127    }
128
129    /// Adds the entity responsible for destroying the block.
130    #[must_use]
131    pub const fn with_entity(mut self, entity: Option<&'a dyn Entity>) -> Self {
132        self.entity = entity;
133        self
134    }
135
136    /// Adds the block entity at the broken position.
137    #[must_use]
138    pub const fn with_block_entity(mut self, block_entity: Option<&'a dyn BlockEntity>) -> Self {
139        self.block_entity = block_entity;
140        self
141    }
142
143    /// Adds the tool used to destroy the block.
144    #[must_use]
145    pub const fn with_tool(mut self, tool: &'a ItemStack) -> Self {
146        self.tool = Some(tool);
147        self
148    }
149
150    /// Adds the luck used to evaluate the loot table.
151    #[must_use]
152    pub const fn with_luck(mut self, luck: f32) -> Self {
153        self.luck = luck;
154        self
155    }
156
157    /// Returns the world containing the block.
158    #[must_use]
159    pub const fn world(&self) -> &'a Arc<World> {
160        self.world
161    }
162
163    /// Returns the block position whose loot is being resolved.
164    #[must_use]
165    pub const fn pos(&self) -> BlockPos {
166        self.pos
167    }
168
169    /// Resolves loot for another state with the same vanilla loot parameters.
170    #[must_use]
171    pub fn get_drops(&self, state: BlockStateId) -> Vec<ItemStack> {
172        World::block_drops(state, self)
173    }
174
175    pub(crate) const fn entity(&self) -> Option<&'a dyn Entity> {
176        self.entity
177    }
178
179    pub(crate) const fn block_entity(&self) -> Option<&'a dyn BlockEntity> {
180        self.block_entity
181    }
182
183    pub(crate) const fn tool(&self) -> Option<&'a ItemStack> {
184        self.tool
185    }
186
187    pub(crate) const fn luck(&self) -> f32 {
188        self.luck
189    }
190}
191
192const COLLISION_CONTEXT_ABOVE_EPSILON: f64 = 1.0e-5;
193
194/// Entity facts used by vanilla `CollisionContext` for block collision shapes.
195#[derive(Debug, Clone, Copy, PartialEq)]
196pub struct BlockCollisionContext {
197    entity_bottom: Option<f64>,
198    fall_distance: f64,
199    can_walk_on_powder_snow: bool,
200    is_falling_block: bool,
201    descending: bool,
202    placement: bool,
203}
204
205impl BlockCollisionContext {
206    /// Collision context for source-less collision queries.
207    #[must_use]
208    pub const fn empty() -> Self {
209        Self {
210            entity_bottom: None,
211            fall_distance: 0.0,
212            can_walk_on_powder_snow: false,
213            is_falling_block: false,
214            descending: false,
215            placement: false,
216        }
217    }
218
219    /// Collision context for normal entity movement.
220    #[must_use]
221    pub const fn entity(entity_bottom: f64, descending: bool) -> Self {
222        Self {
223            entity_bottom: Some(entity_bottom),
224            fall_distance: 0.0,
225            can_walk_on_powder_snow: false,
226            is_falling_block: false,
227            descending,
228            placement: false,
229        }
230    }
231
232    /// Collision context for vanilla `CollisionContext.withPosition(entity, position)`.
233    ///
234    /// In Steel's reduced representation this also matches
235    /// `CollisionContext.placementContext`.
236    #[must_use]
237    pub const fn with_position(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: true,
245        }
246    }
247
248    /// Placement obstruction context when no entity initiated the placement.
249    ///
250    /// This matches vanilla `CollisionContext.placementContext(null)`.
251    #[must_use]
252    pub const fn placement_without_entity() -> Self {
253        Self::with_position(f64::MIN, false)
254    }
255
256    /// Collision context for vanilla `CollisionContext.positionContext(y)`.
257    #[must_use]
258    pub const fn position_context(y: f64) -> Self {
259        Self {
260            entity_bottom: Some(y),
261            fall_distance: 0.0,
262            can_walk_on_powder_snow: false,
263            is_falling_block: false,
264            descending: false,
265            placement: false,
266        }
267    }
268
269    /// Returns a copy with vanilla accumulated fall distance.
270    #[must_use]
271    pub const fn with_fall_distance(mut self, fall_distance: f64) -> Self {
272        self.fall_distance = fall_distance;
273        self
274    }
275
276    /// Returns a copy with vanilla powder-snow walkability.
277    #[must_use]
278    pub const fn with_can_walk_on_powder_snow(mut self, can_walk_on_powder_snow: bool) -> Self {
279        self.can_walk_on_powder_snow = can_walk_on_powder_snow;
280        self
281    }
282
283    /// Returns a copy with vanilla falling-block collision context.
284    #[must_use]
285    pub const fn with_falling_block(mut self, is_falling_block: bool) -> Self {
286        self.is_falling_block = is_falling_block;
287        self
288    }
289
290    /// Returns accumulated vanilla fall distance for context-sensitive block collision.
291    #[must_use]
292    pub const fn fall_distance(self) -> f64 {
293        self.fall_distance
294    }
295
296    /// Returns whether the source entity can walk on powder snow.
297    #[must_use]
298    pub const fn can_walk_on_powder_snow(self) -> bool {
299        self.can_walk_on_powder_snow
300    }
301
302    /// Returns whether the source entity is a vanilla falling block.
303    #[must_use]
304    pub const fn is_falling_block(self) -> bool {
305        self.is_falling_block
306    }
307
308    /// Returns whether the source entity is descending through context-sensitive blocks.
309    #[must_use]
310    pub const fn is_descending(self) -> bool {
311        self.descending
312    }
313
314    /// Returns whether this context is used for placement-style collision checks.
315    #[must_use]
316    pub const fn is_placement(self) -> bool {
317        self.placement
318    }
319
320    /// Vanilla `EntityCollisionContext.isAbove`.
321    #[must_use]
322    pub fn is_above(self, shape: VoxelShape, pos: BlockPos, default_value: bool) -> bool {
323        let Some(entity_bottom) = self.entity_bottom else {
324            return default_value;
325        };
326
327        entity_bottom > f64::from(pos.y()) + shape.max(Axis::Y) - COLLISION_CONTEXT_ABOVE_EPSILON
328    }
329}
330
331/// Entity facts needed by `Block.updateEntityMovementAfterFallOn`.
332#[derive(Debug, Clone, Copy, PartialEq)]
333pub struct EntityLandingContext {
334    /// Entity velocity before the block landing hook adjusts it.
335    pub velocity: DVec3,
336    /// Whether the entity uses vanilla living-entity bounce behavior.
337    pub is_living_entity: bool,
338    /// Whether vanilla bounce behavior should be suppressed.
339    pub suppresses_bounce: bool,
340}
341
342/// Entity facts needed by `Block.fallOn`.
343#[derive(Debug, Clone, Copy, PartialEq)]
344pub struct EntityFallOnFacts {
345    /// Vanilla entity type of the landing entity.
346    pub entity_type: EntityTypeRef,
347    /// Whether the landing entity implements vanilla living-entity behavior.
348    pub is_living_entity: bool,
349    /// Current entity bounding-box X/Z width.
350    pub bounding_box_width: f64,
351    /// Current entity bounding-box height.
352    pub bounding_box_height: f64,
353    /// Vanilla small and big living-entity fall sounds.
354    pub fall_sounds: (SoundEventRef, SoundEventRef),
355}
356
357impl EntityFallOnFacts {
358    /// Creates fall-on facts from explicit entity values.
359    #[must_use]
360    pub const fn new(
361        entity_type: EntityTypeRef,
362        is_living_entity: bool,
363        bounding_box_width: f64,
364        bounding_box_height: f64,
365        fall_sounds: (SoundEventRef, SoundEventRef),
366    ) -> Self {
367        Self {
368            entity_type,
369            is_living_entity,
370            bounding_box_width,
371            bounding_box_height,
372            fall_sounds,
373        }
374    }
375
376    /// Creates fall-on facts from an entity.
377    #[must_use]
378    pub fn from_entity(entity: &dyn Entity) -> Self {
379        let bounding_box = entity.bounding_box();
380        Self::new(
381            entity.entity_type(),
382            entity.is_living_entity(),
383            bounding_box.width(),
384            bounding_box.height(),
385            entity.fall_sounds(),
386        )
387    }
388
389    /// Returns true for vanilla players.
390    #[must_use]
391    pub fn is_player(self) -> bool {
392        self.entity_type == &vanilla_entities::PLAYER
393    }
394
395    /// Vanilla farmland trampling size check:
396    /// `getBbWidth() * getBbWidth() * getBbHeight()`.
397    #[must_use]
398    pub fn bounding_box_width_squared_height(self) -> f64 {
399        self.bounding_box_width * self.bounding_box_width * self.bounding_box_height
400    }
401}
402
403/// Entity facts needed by `Block.fallOn`.
404#[derive(Clone, Copy)]
405pub struct EntityFallOnContext<'a> {
406    /// Accumulated vanilla fall distance at landing time.
407    pub fall_distance: f64,
408    /// Whether vanilla bounce behavior should be suppressed.
409    pub suppresses_bounce: bool,
410    /// Entity facts available to vanilla fall-on hooks.
411    pub entity: EntityFallOnFacts,
412    /// Source entity for vanilla side effects such as game events.
413    pub source_entity: Option<&'a dyn Entity>,
414}
415
416impl<'a> EntityFallOnContext<'a> {
417    /// Creates a fall-on context for a ground collision.
418    #[must_use]
419    pub const fn new(
420        fall_distance: f64,
421        suppresses_bounce: bool,
422        entity: EntityFallOnFacts,
423        source_entity: Option<&'a dyn Entity>,
424    ) -> Self {
425        Self {
426            fall_distance,
427            suppresses_bounce,
428            entity,
429            source_entity,
430        }
431    }
432
433    /// Creates a fall-on context from a landing entity.
434    #[must_use]
435    pub fn from_entity(fall_distance: f64, entity: &'a dyn Entity) -> Self {
436        Self::new(
437            fall_distance,
438            entity.is_suppressing_bounce(),
439            EntityFallOnFacts::from_entity(entity),
440            Some(entity),
441        )
442    }
443
444    /// Returns this context with a transformed fall distance.
445    #[must_use]
446    pub const fn with_fall_distance(mut self, fall_distance: f64) -> Self {
447        self.fall_distance = fall_distance;
448        self
449    }
450
451    /// Returns the source entity for vanilla side effects.
452    #[must_use]
453    pub const fn source_entity(self) -> Option<&'a dyn Entity> {
454        self.source_entity
455    }
456}
457
458/// Fall damage requested by a block landing hook.
459#[derive(Debug, Clone)]
460pub struct EntityFallDamage {
461    /// Fall distance to pass to `Entity.causeFallDamage`.
462    pub fall_distance: f64,
463    /// Block-specific damage multiplier.
464    pub damage_modifier: f32,
465    /// Damage source for this landing.
466    pub source: DamageSource,
467}
468
469impl EntityFallDamage {
470    /// Creates a fall-damage action.
471    #[must_use]
472    pub const fn new(fall_distance: f64, damage_modifier: f32, source: DamageSource) -> Self {
473        Self {
474            fall_distance,
475            damage_modifier,
476            source,
477        }
478    }
479}
480
481impl EntityLandingContext {
482    /// Creates a landing context for a vertical movement collision.
483    #[must_use]
484    pub const fn new(velocity: DVec3, is_living_entity: bool, suppresses_bounce: bool) -> Self {
485        Self {
486            velocity,
487            is_living_entity,
488            suppresses_bounce,
489        }
490    }
491
492    /// Vanilla default `Block.updateEntityMovementAfterFallOn` result.
493    #[must_use]
494    pub const fn default_velocity_after_fall_on(self) -> DVec3 {
495        DVec3::new(self.velocity.x, 0.0, self.velocity.z)
496    }
497}