Skip to main content

steel_core/fluid/
fluid_behavior.rs

1//! Fluid behavior trait and related types.
2//! Fluids like `WaterFluid` and `LavaFluid` implement this trait to inherit behavior.
3use std::sync::Arc;
4
5use glam::DVec3;
6
7use crate::entity::{Entity, InsideBlockEffectCollector};
8use crate::world::World;
9use steel_registry::blocks::properties::Direction;
10use steel_registry::fluid::{FluidRef, FluidState};
11use steel_registry::items::ItemRef;
12use steel_registry::sound_event::SoundEventRef;
13use steel_utils::{BlockPos, BlockStateId};
14
15/// Trait for fluid behavior implementations.
16/// Conceptual equivalent of Minecraft's `Fluid` class.
17pub trait FluidBehavior: Send + Sync {
18    /// Gets the fluid type for this behavior.
19    fn fluid_type(&self) -> FluidRef;
20
21    /// Creates the block state that holds `fluid_state` on its own.
22    fn create_legacy_block(&self, fluid_state: FluidState) -> BlockStateId;
23
24    /// The filled bucket of this fluid, or air if it has none.
25    fn bucket(&self) -> ItemRef;
26
27    /// Checks if this fluid is the same type as another fluid ref.
28    ///
29    /// Used to determine if fluids can flow into each other.
30    ///
31    /// **Override required** for any fluid that has both a source and a flowing variant
32    fn is_same(&self, other: FluidRef) -> bool {
33        self.fluid_type() == other
34    }
35
36    /// Gets the number of ticks between fluid updates.
37    fn tick_delay(&self, world: &Arc<World>) -> i32;
38    /// Gets the amount of fluid level drop per horizontal block.
39    /// Takes `world` because some fluids (lava) differ by dimension.
40    fn drop_off(&self, world: &Arc<World>) -> u8;
41    /// Gets the slope-search distance for horizontal spread.
42    /// Takes `world` because some fluids (lava) differ by dimension.
43    fn slope_find_distance(&self, world: &Arc<World>) -> u8;
44
45    /// Called every tick for fluid blocks.
46    ///
47    /// The block and fluid states are the live states validated by the tick caller.
48    fn tick(
49        &self,
50        world: &Arc<World>,
51        pos: BlockPos,
52        block_state: BlockStateId,
53        fluid_state: FluidState,
54    );
55    /// Called to calculate fluid spreading each tick.
56    fn spread(
57        &self,
58        world: &Arc<World>,
59        pos: BlockPos,
60        block_state: BlockStateId,
61        fluid_state: FluidState,
62    );
63
64    /// Checks if this fluid can be replaced by another fluid.
65    /// This is used to determine if a fluid can flow into a block occupied by another fluid.
66    fn can_be_replaced_with(
67        &self,
68        fluid_state: FluidState,
69        world: &Arc<World>,
70        pos: BlockPos,
71        other_fluid: FluidRef,
72        direction: Direction,
73    ) -> bool;
74
75    /// Called before a block is destroyed by this fluid.
76    fn before_destroying_block(
77        &self,
78        _world: &Arc<World>,
79        _pos: BlockPos,
80        _replaced: BlockStateId,
81    ) {
82        // default: do nothing
83    }
84
85    /// Checks if this fluid can convert to a source block at the given position.
86    fn can_convert_to_source(&self, _world: &Arc<World>) -> bool {
87        false
88    }
89
90    /// Called when an entity is inside this fluid.
91    #[expect(
92        unused_variables,
93        reason = "default trait implementation ignores all params"
94    )]
95    fn entity_inside(
96        &self,
97        world: &Arc<World>,
98        pos: BlockPos,
99        entity: &dyn Entity,
100        effect_collector: &mut InsideBlockEffectCollector,
101    ) {
102    }
103
104    /// Gets the explosion resistance of this fluid.
105    fn explosion_resistance(&self) -> f32 {
106        0.0
107    }
108
109    /// The sound of picking this fluid up with a bucket.
110    fn pickup_sound(&self) -> Option<SoundEventRef> {
111        None
112    }
113
114    /// Called on random tick for this fluid's block.
115    /// Used for lava fire spread.
116    #[expect(
117        unused_variables,
118        reason = "default trait implementation ignores all params"
119    )]
120    fn random_tick(&self, world: &Arc<World>, pos: BlockPos) {}
121
122    /// Returns the tick delay to use when scheduling a newly-spread block,
123    /// taking into account the old and new fluid states.
124    #[expect(
125        unused_variables,
126        reason = "default implementation only uses world and tick_delay; old/new states are available for overrides"
127    )]
128    fn get_spread_delay(
129        &self,
130        world: &Arc<World>,
131        _pos: BlockPos,
132        old_state: FluidState,
133        new_state: FluidState,
134    ) -> i32 {
135        self.tick_delay(world)
136    }
137
138    /// Returns this fluid state's flow vector at a position.
139    #[expect(
140        unused_variables,
141        reason = "default implementation is used by empty/non-flowing fluids"
142    )]
143    fn get_flow(&self, world: &Arc<World>, pos: BlockPos, fluid_state: FluidState) -> DVec3 {
144        DVec3::ZERO
145    }
146}