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