Skip to main content

steel_core/behavior/block/
context.rs

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