1use crate::{
4 RegistryExt, RegistryTags, TaggedRegistryExt,
5 blocks::{block_state_ext::BlockStateExt, properties::BlockStateProperties},
6 vanilla_blocks,
7 vanilla_fluid_tags::FluidTag,
8 vanilla_fluids,
9};
10use rustc_hash::FxHashMap;
11use steel_utils::{BlockStateId, Identifier};
12
13#[derive(Debug)]
15pub struct Fluid {
16 pub key: Identifier,
18 pub is_empty: bool,
20 pub is_source: bool,
22 pub is_randomly_ticking: bool,
24 pub block: Identifier,
26 pub bucket_item: Identifier,
28 pub source_fluid: Option<Identifier>,
30 pub flowing_fluid: Option<Identifier>,
32 pub tick_delay: u32,
34 pub explosion_resistance: f32,
36}
37
38impl Fluid {
39 #[must_use]
41 pub fn has_tag(&'static self, tag: &Identifier) -> bool {
42 REGISTRY.fluids.is_in_tag(self, tag)
43 }
44
45 #[must_use]
51 pub fn source_variant(&'static self) -> FluidRef {
52 let Some(source_key) = &self.source_fluid else {
53 return self;
54 };
55
56 match REGISTRY.fluids.by_key(source_key) {
57 Some(fluid) => fluid,
58 None => panic!(
59 "fluid `{}` references missing source fluid `{source_key}`",
60 self.key
61 ),
62 }
63 }
64
65 #[must_use]
67 pub fn flowing_variant(&'static self) -> FluidRef {
68 let Some(flowing_key) = &self.flowing_fluid else {
69 return self;
70 };
71
72 match REGISTRY.fluids.by_key(flowing_key) {
73 Some(fluid) => fluid,
74 None => panic!(
75 "fluid `{}` references missing flowing fluid `{flowing_key}`",
76 self.key
77 ),
78 }
79 }
80}
81
82pub type FluidRef = &'static Fluid;
83
84#[derive(Debug, Clone, Copy, PartialEq, Eq)]
88pub struct FluidState {
89 pub fluid_id: FluidRef,
91 pub amount: u8,
93 pub falling: bool,
95}
96
97const LEGACY_SOURCE_LEVEL: u8 = 0;
98const LEGACY_MAX_LEVEL: u8 = 8;
99const LEGACY_FALLING_OFFSET: u8 = 8;
100
101impl FluidState {
102 pub const EMPTY: Self = Self {
104 fluid_id: &vanilla_fluids::EMPTY,
105 amount: 0,
106 falling: false,
107 };
108
109 #[must_use]
111 pub const fn new(fluid: FluidRef, amount: u8, falling: bool) -> Self {
112 Self {
113 fluid_id: fluid,
114 amount,
115 falling,
116 }
117 }
118
119 #[must_use]
121 pub const fn source(fluid: FluidRef) -> Self {
122 Self {
123 fluid_id: fluid,
124 amount: 8,
125 falling: false,
126 }
127 }
128
129 #[must_use]
131 pub const fn flowing(fluid: FluidRef, amount: u8, falling: bool) -> Self {
132 Self {
133 fluid_id: fluid,
134 amount,
135 falling,
136 }
137 }
138
139 #[must_use]
141 pub const fn is_empty(&self) -> bool {
142 self.fluid_id.is_empty || self.amount == 0
143 }
144
145 #[must_use]
150 pub const fn is_source(&self) -> bool {
151 self.fluid_id.is_source
152 }
153
154 #[must_use]
160 pub const fn is_full(&self) -> bool {
161 self.amount == 8
162 }
163
164 #[must_use]
166 pub const fn is_randomly_ticking(&self) -> bool {
167 self.fluid_id.is_randomly_ticking
168 }
169
170 #[must_use]
172 pub fn own_height(&self) -> f32 {
173 if self.is_empty() {
174 0.0
175 } else {
176 f32::from(self.amount) / 9.0
177 }
178 }
179
180 #[must_use]
186 pub fn from_block_level(fluid: FluidRef, level: u8) -> Self {
187 if level == 0 {
188 Self::source(fluid.source_variant())
190 } else if level <= 7 {
191 Self::flowing(fluid.flowing_variant(), 8 - level, false)
193 } else {
194 Self::flowing(fluid.flowing_variant(), 8, true)
196 }
197 }
198
199 #[must_use]
201 pub const fn to_block_level(self) -> u8 {
202 if self.is_source() {
203 0
204 } else if self.falling {
205 8
206 } else {
207 8 - self.amount
209 }
210 }
211
212 pub fn create_legacy_block(self) -> BlockStateId {
213 vanilla_blocks::WATER
214 .default_state()
215 .set_value(&BlockStateProperties::LEVEL, Self::get_legacy_level(self))
216 }
217
218 const fn get_legacy_level(self) -> u8 {
219 if self.is_source() {
220 return LEGACY_SOURCE_LEVEL;
221 }
222
223 let falling_offset = if self.falling {
224 LEGACY_FALLING_OFFSET
225 } else {
226 0
227 };
228
229 LEGACY_MAX_LEVEL - self.amount.min(LEGACY_MAX_LEVEL) + falling_offset
230 }
231}
232
233pub struct FluidRegistry {
235 fluids_by_id: Vec<FluidRef>,
236 fluids_by_key: FxHashMap<Identifier, usize>,
237 tags: RegistryTags,
238 allows_registering: bool,
239}
240
241impl Default for FluidRegistry {
242 fn default() -> Self {
243 Self::new()
244 }
245}
246
247impl FluidRegistry {
248 #[must_use]
250 pub fn new() -> Self {
251 Self {
252 fluids_by_id: Vec::new(),
253 fluids_by_key: FxHashMap::default(),
254 tags: RegistryTags::default(),
255 allows_registering: true,
256 }
257 }
258
259 pub fn register(&mut self, fluid: FluidRef) -> usize {
261 assert!(
262 self.allows_registering,
263 "Cannot register fluids after the registry has been frozen"
264 );
265
266 let id = self.fluids_by_id.len();
267 self.fluids_by_key.insert(fluid.key.clone(), id);
268 self.fluids_by_id.push(fluid);
269 id
270 }
271
272 pub fn iter(&self) -> impl Iterator<Item = (usize, FluidRef)> + '_ {
274 self.fluids_by_id
275 .iter()
276 .enumerate()
277 .map(|(id, &fluid)| (id, fluid))
278 }
279}
280
281crate::impl_registry!(FluidRegistry, Fluid, fluids_by_id, fluids_by_key, fluids);
282crate::impl_tagged_registry!(FluidRegistry, fluids_by_key, "fluid");
283
284use crate::REGISTRY;
285
286#[must_use]
288pub fn is_water_fluid(fluid: FluidRef) -> bool {
289 !fluid.is_empty && fluid.has_tag(&FluidTag::WATER)
290}
291
292#[must_use]
294pub fn is_lava_fluid(fluid: FluidRef) -> bool {
295 !fluid.is_empty && fluid.has_tag(&FluidTag::LAVA)
296}
297
298pub trait FluidStateExt {
300 fn is_water(&self) -> bool;
302 fn is_lava(&self) -> bool;
304}
305
306impl FluidStateExt for FluidState {
307 fn is_water(&self) -> bool {
308 is_water_fluid(self.fluid_id)
309 }
310 fn is_lava(&self) -> bool {
311 is_lava_fluid(self.fluid_id)
312 }
313}
314
315#[cfg(test)]
316mod tests {
317 use crate::{init_vanilla_registry, vanilla_fluids};
318
319 use super::*;
320
321 #[test]
322 fn from_block_level_uses_source_variant_for_level_zero() {
323 init_vanilla_registry();
324
325 let water = FluidState::from_block_level(&vanilla_fluids::WATER, 0);
326 let lava = FluidState::from_block_level(&vanilla_fluids::LAVA, 0);
327
328 assert_eq!(water.fluid_id, &vanilla_fluids::WATER);
329 assert_eq!(lava.fluid_id, &vanilla_fluids::LAVA);
330 assert!(water.is_source());
331 assert!(lava.is_source());
332 }
333
334 #[test]
335 fn from_block_level_uses_flowing_variant_for_non_source_levels() {
336 init_vanilla_registry();
337
338 let water = FluidState::from_block_level(&vanilla_fluids::WATER, 1);
339 let lava = FluidState::from_block_level(&vanilla_fluids::LAVA, 8);
340
341 assert_eq!(water.fluid_id, &vanilla_fluids::FLOWING_WATER);
342 assert_eq!(lava.fluid_id, &vanilla_fluids::FLOWING_LAVA);
343 assert!(!water.is_source());
344 assert!(!lava.is_source());
345 assert!(lava.falling);
346 }
347
348 #[test]
349 fn from_block_level_clamps_all_falling_liquid_levels_to_full_amount() {
350 init_vanilla_registry();
351
352 for level in 8..=15 {
353 let water = FluidState::from_block_level(&vanilla_fluids::WATER, level);
354
355 assert_eq!(water.fluid_id, &vanilla_fluids::FLOWING_WATER);
356 assert_eq!(water.amount, 8);
357 assert!(water.falling);
358 assert!(water.is_full());
359 assert!(!water.is_source());
360 }
361 }
362
363 #[test]
364 fn source_fluid_type_is_source_even_when_falling() {
365 init_vanilla_registry();
366
367 let falling_source = FluidState::new(&vanilla_fluids::WATER, 8, true);
368
369 assert!(falling_source.is_source());
370 assert!(falling_source.is_full());
371 }
372}