Skip to main content

steel_core/block_state_provider/
providers.rs

1use super::BlockStateProviderEvaluator;
2use super::noise_cache::NoiseCache;
3use crate::world::LevelReader;
4use smallvec::SmallVec;
5use steel_math::map_clamped;
6use steel_registry::blocks::{
7    BlockRef, block_state_ext::BlockStateExt as _, properties::BlockStateProperties,
8};
9use steel_registry::feature::{
10    BlockHolderSet, BlockStateData, BlockStateProviderKind, DualNoiseProvider,
11    FeatureNoiseParameters, NoiseProvider, NoiseThresholdProvider,
12};
13use steel_registry::{Registry, TaggedRegistryExt as _};
14use steel_utils::random::{Random, RandomSource, legacy_random::LegacyRandom};
15use steel_utils::{BlockPos, BlockStateId, Direction};
16use steel_worldgen::noise::NormalNoise;
17
18pub(crate) trait NoiseScale: Copy {
19    fn scale_coord(self, coord: i32) -> f64;
20}
21
22impl NoiseScale for f32 {
23    fn scale_coord(self, coord: i32) -> f64 {
24        f64::from(coord as f32 * self)
25    }
26}
27
28impl NoiseScale for f64 {
29    fn scale_coord(self, coord: i32) -> f64 {
30        f64::from(coord) * self
31    }
32}
33
34impl BlockStateProviderEvaluator {
35    pub(crate) fn sample_block_state_provider_optional(
36        level: &dyn LevelReader,
37        registry: &Registry,
38        random: &mut impl Random,
39        provider: &BlockStateProviderKind,
40        pos: BlockPos,
41    ) -> Option<BlockStateId> {
42        match provider {
43            BlockStateProviderKind::Reference(provider) => {
44                Self::sample_block_state_provider_optional(
45                    level,
46                    registry,
47                    random,
48                    &provider.kind,
49                    pos,
50                )
51            }
52            BlockStateProviderKind::RuleBased { fallback, rules } => {
53                for rule in rules {
54                    if Self::test_block_predicate(level, registry, &rule.if_true, pos)
55                        && let Some(state) = Self::sample_block_state_provider_optional(
56                            level, registry, random, &rule.then, pos,
57                        )
58                    {
59                        return Some(state);
60                    }
61                }
62
63                fallback.as_ref().and_then(|fallback| {
64                    Self::sample_block_state_provider_optional(
65                        level, registry, random, fallback, pos,
66                    )
67                })
68            }
69            BlockStateProviderKind::RandomBlock { blocks } => {
70                Self::sample_random_block(registry, random, blocks)
71            }
72            _ => Some(Self::sample_block_state_provider(
73                level, registry, random, provider, pos,
74            )),
75        }
76    }
77
78    pub(crate) fn sample_block_state_provider(
79        level: &dyn LevelReader,
80        registry: &Registry,
81        random: &mut impl Random,
82        provider: &BlockStateProviderKind,
83        pos: BlockPos,
84    ) -> BlockStateId {
85        match provider {
86            BlockStateProviderKind::Reference(provider) => {
87                Self::sample_block_state_provider(level, registry, random, &provider.kind, pos)
88            }
89            BlockStateProviderKind::Simple { state } => {
90                Self::block_state_from_data(registry, state)
91            }
92            BlockStateProviderKind::Weighted { entries } => {
93                let total_weight = entries.iter().map(|entry| entry.weight).sum();
94                let mut target = random.next_i32_bounded(total_weight);
95                for entry in entries {
96                    if target < entry.weight {
97                        return Self::block_state_from_data(registry, &entry.data);
98                    }
99                    target -= entry.weight;
100                }
101
102                panic!("weighted blockstate provider failed to select an entry");
103            }
104            BlockStateProviderKind::RotatedBlock { state, direction } => {
105                let direction = direction
106                    .unwrap_or_else(|| Direction::ALL[random.next_i32_bounded(6) as usize]);
107                let state = Self::sample_block_state_provider(level, registry, random, state, pos);
108                let state = if state.try_get_value(&BlockStateProperties::AXIS).is_some() {
109                    state.set_value(&BlockStateProperties::AXIS, direction.axis())
110                } else {
111                    state
112                };
113
114                let state = if state.try_get_value(&BlockStateProperties::FACING).is_some() {
115                    state.set_value(&BlockStateProperties::FACING, direction)
116                } else {
117                    state
118                };
119
120                if direction.is_horizontal()
121                    && state
122                        .try_get_value(&BlockStateProperties::HORIZONTAL_FACING)
123                        .is_some()
124                {
125                    state.set_value(&BlockStateProperties::HORIZONTAL_FACING, direction)
126                } else {
127                    state
128                }
129            }
130            BlockStateProviderKind::RandomizedInt {
131                property,
132                source,
133                values,
134            } => {
135                let state = Self::sample_block_state_provider(level, registry, random, source, pos);
136                let value = values.sample(random);
137
138                Self::set_int_property_by_name(registry, state, property, value)
139            }
140            BlockStateProviderKind::RuleBased { .. } => {
141                if let Some(state) = Self::sample_block_state_provider_optional(
142                    level, registry, random, provider, pos,
143                ) {
144                    state
145                } else {
146                    level.get_block_state(pos)
147                }
148            }
149            BlockStateProviderKind::Noise(provider) => {
150                Self::sample_noise_provider(registry, provider, pos)
151            }
152            BlockStateProviderKind::NoiseThreshold(provider) => {
153                Self::sample_noise_threshold_provider(registry, random, provider, pos)
154            }
155            BlockStateProviderKind::DualNoise(provider) => {
156                Self::sample_dual_noise_provider(registry, provider, pos)
157            }
158            BlockStateProviderKind::CopyProperties { source } => {
159                let sampled =
160                    Self::sample_block_state_provider(level, registry, random, source, pos);
161
162                registry
163                    .blocks
164                    .with_properties_of(sampled, level.get_block_state(pos))
165            }
166            BlockStateProviderKind::RandomBlock { blocks } => {
167                Self::sample_random_block(registry, random, blocks)
168                    .unwrap_or_else(|| level.get_block_state(pos))
169            }
170        }
171    }
172
173    fn sample_random_block(
174        registry: &Registry,
175        random: &mut impl Random,
176        blocks: &BlockHolderSet,
177    ) -> Option<BlockStateId> {
178        let tagged_blocks: SmallVec<[BlockRef; 8]>;
179        let entries = match blocks {
180            BlockHolderSet::Entries(entries) => entries.as_slice(),
181            BlockHolderSet::Tag(tag) => {
182                tagged_blocks = registry.blocks.iter_tag(tag).collect();
183                tagged_blocks.as_slice()
184            }
185        };
186        if entries.is_empty() {
187            return None;
188        }
189        let index = random.next_i32_bounded(entries.len() as i32) as usize;
190        Some(registry.blocks.get_default_state_id(entries[index]))
191    }
192
193    pub(crate) fn set_int_property_by_name(
194        registry: &Registry,
195        state: BlockStateId,
196        property: &str,
197        value: i32,
198    ) -> BlockStateId {
199        let Some(block) = registry.blocks.by_state_id(state) else {
200            panic!("block-state provider received invalid block state id {state:?}");
201        };
202        let value_string = value.to_string();
203        let mut found = false;
204        let properties = registry
205            .blocks
206            .properties(state)
207            .map(|(name, existing)| {
208                if name == property {
209                    found = true;
210                    (name, value_string.as_str())
211                } else {
212                    (name, existing)
213                }
214            })
215            .collect::<Vec<_>>();
216
217        if !found {
218            return state;
219        }
220
221        let Some(new_state) = registry
222            .blocks
223            .state_id_from_block_properties(block, &properties)
224        else {
225            panic!(
226                "randomized int provider produced invalid value {value} for property {property} on {}",
227                block.key
228            );
229        };
230        new_state
231    }
232
233    pub(crate) fn sample_noise_provider(
234        registry: &Registry,
235        provider: &NoiseProvider,
236        pos: BlockPos,
237    ) -> BlockStateId {
238        let noise = NoiseCache::get(&provider.noise, provider.seed);
239        let noise_value = Self::noise_value(&noise, pos, f64::from(provider.scale));
240
241        Self::noise_state_by_value(registry, &provider.states, noise_value)
242    }
243
244    pub(crate) fn sample_noise_threshold_provider(
245        registry: &Registry,
246        random: &mut impl Random,
247        provider: &NoiseThresholdProvider,
248        pos: BlockPos,
249    ) -> BlockStateId {
250        let noise = NoiseCache::get(&provider.noise, provider.seed);
251        let noise_value = Self::noise_value(&noise, pos, f64::from(provider.scale));
252
253        if noise_value < f64::from(provider.threshold) {
254            Self::random_block_state_from_data_list(registry, random, &provider.low_states)
255        } else if random.next_f32() < provider.high_chance {
256            Self::random_block_state_from_data_list(registry, random, &provider.high_states)
257        } else {
258            Self::block_state_from_data(registry, &provider.default_state)
259        }
260    }
261
262    pub(crate) fn sample_dual_noise_provider(
263        registry: &Registry,
264        provider: &DualNoiseProvider,
265        pos: BlockPos,
266    ) -> BlockStateId {
267        let slow_noise = NoiseCache::get(&provider.slow_noise, provider.seed);
268        let variety_noise = Self::noise_value(&slow_noise, pos, provider.slow_scale);
269        let local_variety = map_clamped(
270            variety_noise,
271            -1.0,
272            1.0,
273            f64::from(provider.variety[0]),
274            f64::from(provider.variety[1] + 1),
275        ) as i32;
276        assert!(
277            local_variety > 0,
278            "dual-noise provider local variety must be positive, got {local_variety}"
279        );
280
281        let Ok(capacity) = usize::try_from(local_variety) else {
282            panic!("dual-noise provider local variety {local_variety} exceeds usize range");
283        };
284        let mut possible_states = SmallVec::<[BlockStateId; 8]>::with_capacity(capacity);
285        for i in 0..local_variety {
286            let offset_pos = pos.offset(i * 54_545, 0, i * 34_234);
287            let slow_value = Self::noise_value(&slow_noise, offset_pos, provider.slow_scale);
288            possible_states.push(Self::noise_state_by_value(
289                registry,
290                &provider.states,
291                slow_value,
292            ));
293        }
294
295        let noise = NoiseCache::get(&provider.noise, provider.seed);
296        let noise_value = Self::noise_value(&noise, pos, f64::from(provider.scale));
297
298        Self::noise_state_by_resolved_value(&possible_states, noise_value)
299    }
300
301    pub(crate) fn normal_noise(parameters: &FeatureNoiseParameters, seed: i64) -> NormalNoise {
302        let mut random = RandomSource::Legacy(LegacyRandom::from_seed(seed as u64));
303        NormalNoise::create_from_random_with_params(
304            &mut random,
305            parameters.base_octave,
306            parameters.base_amplitude,
307            parameters.octave_count,
308            parameters.normalize,
309            &parameters.amplitude_modifiers,
310        )
311    }
312
313    pub(crate) fn noise_value<S: NoiseScale>(noise: &NormalNoise, pos: BlockPos, scale: S) -> f64 {
314        f64::from(noise.get_value(
315            scale.scale_coord(pos.x()),
316            scale.scale_coord(pos.y()),
317            scale.scale_coord(pos.z()),
318        ))
319    }
320
321    pub(crate) fn noise_state_by_value(
322        registry: &Registry,
323        states: &[BlockStateData],
324        noise_value: f64,
325    ) -> BlockStateId {
326        assert!(
327            !states.is_empty(),
328            "noise provider state list must not be empty"
329        );
330        let index = Self::noise_state_index(states.len(), noise_value);
331        Self::block_state_from_data(registry, &states[index])
332    }
333
334    pub(crate) fn noise_state_by_resolved_value(
335        states: &[BlockStateId],
336        noise_value: f64,
337    ) -> BlockStateId {
338        assert!(
339            !states.is_empty(),
340            "noise provider state list must not be empty"
341        );
342        states[Self::noise_state_index(states.len(), noise_value)]
343    }
344
345    pub(crate) fn noise_state_index(state_count: usize, noise_value: f64) -> usize {
346        let placement_value = f32::midpoint(1.0_f32, noise_value as f32).clamp(0.0, 0.9999);
347        (placement_value * state_count as f32) as usize
348    }
349
350    pub(crate) fn random_block_state_from_data_list(
351        registry: &Registry,
352        random: &mut impl Random,
353        states: &[BlockStateData],
354    ) -> BlockStateId {
355        assert!(
356            !states.is_empty(),
357            "random block-state provider state list must not be empty"
358        );
359        let Ok(state_count) = i32::try_from(states.len()) else {
360            panic!(
361                "random block-state provider state count {} exceeds i32 range",
362                states.len()
363            );
364        };
365        let index = random.next_i32_bounded(state_count) as usize;
366        Self::block_state_from_data(registry, &states[index])
367    }
368}