Skip to main content

steel_registry/
carver.rs

1//! Configured carver registry.
2//!
3//! Mirrors vanilla's `ConfiguredWorldCarver` — each entry is a carver kind
4//! (cave or canyon) paired with its configuration. Referenced by biomes via
5//! [`Biome`](crate::biome::Biome)'s `carvers` field and sampled during the
6//! `CARVERS` chunk generation stage.
7
8use std::sync::OnceLock;
9
10use rustc_hash::FxHashMap;
11use steel_utils::Identifier;
12use steel_utils::random::{Random, legacy_random::LegacyRandom};
13use steel_utils::value_providers::{FloatProvider, HeightProvider, IntProvider};
14
15/// Shared per-carver configuration fields present on every carver type.
16///
17/// Mirrors `CaveWorldCarver`/`CanyonWorldCarver`'s common leading fields.
18/// `y_scale`/`lava_level`/`replaceable` are gone from vanilla's schema —
19/// replaceability is now the global `#minecraft:uncarvable` tag and the lava
20/// cutoff lives in the Aquifer's own fixed floor (both in steel-core).
21#[derive(Debug, Clone)]
22pub struct CarverConfiguration {
23    /// Per-chunk start probability (in `[0, 1]`).
24    pub probability: f32,
25    /// Carver origin Y coordinate provider.
26    pub y: HeightProvider,
27}
28
29/// Cave/nether-cave configuration.
30#[derive(Debug, Clone)]
31pub struct CaveCarverConfiguration {
32    /// Base configuration.
33    pub base: CarverConfiguration,
34    /// Number of cave starts to attempt per source chunk.
35    pub count: IntProvider,
36    /// Per-tunnel thickness draw.
37    pub thickness: FloatProvider,
38    /// When set, `thickness` gets an extra 1-in-10 boost.
39    pub weird_thickness_bias: bool,
40    /// Vertical-stretch multiplier, sampled once per cave, for its room.
41    pub room_vertical_radius_multiplier: FloatProvider,
42    /// Per-tunnel horizontal-radius stretch factor.
43    pub horizontal_radius_multiplier: FloatProvider,
44    /// Per-tunnel vertical-radius stretch factor.
45    pub vertical_radius_multiplier: FloatProvider,
46    /// Vertical-stretch multiplier, sampled once per cave, for its tunnels.
47    pub start_vertical_radius_multiplier: FloatProvider,
48    /// Cave floor cutoff in `[-1, 1]`; blocks below it are skipped.
49    pub floor_level: FloatProvider,
50}
51
52/// Canyon shape parameters — controls tunnel shape and width-per-height
53/// variation.
54#[derive(Debug, Clone)]
55pub struct CanyonShapeConfiguration {
56    /// Fraction of the max carving distance used as the actual tunnel length.
57    pub distance_factor: FloatProvider,
58    /// Overall tunnel thickness.
59    pub thickness: FloatProvider,
60    /// Lower values = fresher width noise each step; higher = smoother.
61    pub width_smoothness: i32,
62    /// Per-step horizontal-radius multiplier.
63    pub horizontal_radius_factor: FloatProvider,
64    /// Baseline vertical-radius multiplier applied along the whole tunnel.
65    pub vertical_radius_default_factor: f32,
66    /// Extra vertical-radius multiplier that peaks at the tunnel midpoint.
67    pub vertical_radius_center_factor: f32,
68    /// Vertical-stretch multiplier (moved here from the old base config).
69    pub y_scale: FloatProvider,
70}
71
72impl CanyonShapeConfiguration {
73    /// Mirrors vanilla `CanyonWorldCarver.initWidthFactors` — fresh squared
74    /// width factor at every `width_smoothness`-th Y level, otherwise
75    /// repeating the previous value.
76    #[must_use]
77    pub fn init_width_factors(&self, gen_depth: i32, random: &mut LegacyRandom) -> Vec<f32> {
78        let depth = gen_depth as usize;
79        let mut factors = vec![0.0_f32; depth];
80        let mut current = 1.0_f32;
81        for (y_index, slot) in factors.iter_mut().enumerate() {
82            if y_index == 0 || random.next_i32_bounded(self.width_smoothness) == 0 {
83                current = 1.0 + random.next_f32() * random.next_f32();
84            }
85            *slot = current * current;
86        }
87        factors
88    }
89
90    /// Mirrors vanilla `CanyonWorldCarver.updateVerticalRadius` — applies the
91    /// shape's default/center factors plus a `Mth.randomBetween(0.75, 1.0)`
92    /// jitter.
93    #[must_use]
94    pub fn update_vertical_radius(
95        &self,
96        random: &mut LegacyRandom,
97        vertical_radius: f64,
98        distance: f32,
99        current_step: f32,
100    ) -> f64 {
101        // Vanilla: `Mth.abs(0.5F - currentStep/distance)` — float arithmetic.
102        let vertical_multiplier = 1.0_f32 - (0.5 - current_step / distance).abs() * 2.0;
103        let factor = self.vertical_radius_default_factor
104            + self.vertical_radius_center_factor * vertical_multiplier;
105        // `Mth.randomBetween(random, 0.75F, 1.0F)` = 0.75 + nextFloat()*0.25.
106        let jitter = 0.75 + random.next_f32() * 0.25;
107        f64::from(factor) * vertical_radius * f64::from(jitter)
108    }
109}
110
111/// Canyon (ravine) configuration.
112#[derive(Debug, Clone)]
113pub struct CanyonCarverConfiguration {
114    /// Base configuration.
115    pub base: CarverConfiguration,
116    /// Per-tunnel vertical rotation amount.
117    pub vertical_rotation: FloatProvider,
118    /// Shape configuration.
119    pub shape: CanyonShapeConfiguration,
120}
121
122/// Which carver algorithm a [`ConfiguredCarver`] uses, along with its
123/// fully-resolved configuration.
124#[derive(Debug, Clone)]
125pub enum ConfiguredCarverKind {
126    /// Branching cave tunnels (`CaveWorldCarver`).
127    Cave(CaveCarverConfiguration),
128    /// Long narrow ravines (`CanyonWorldCarver`).
129    Canyon(CanyonCarverConfiguration),
130}
131
132/// A fully-configured carver, as referenced by biomes via their
133/// `carvers` field.
134///
135/// Mirrors vanilla's `ConfiguredWorldCarver<?>`.
136#[derive(Debug)]
137pub struct ConfiguredCarver {
138    /// Registry key (e.g. `minecraft:cave`, `minecraft:canyon`).
139    pub key: Identifier,
140    /// Which carver algorithm this is + its configuration.
141    pub kind: ConfiguredCarverKind,
142    /// Cached registry ID, set during registration for O(1) lookup on hot
143    /// paths.
144    pub id: OnceLock<usize>,
145}
146
147impl ConfiguredCarver {
148    /// The base (shared) configuration for this carver.
149    #[must_use]
150    pub const fn base(&self) -> &CarverConfiguration {
151        match &self.kind {
152            ConfiguredCarverKind::Cave(c) => &c.base,
153            ConfiguredCarverKind::Canyon(c) => &c.base,
154        }
155    }
156}
157
158/// Read-only reference to a registered [`ConfiguredCarver`].
159pub type ConfiguredCarverRef = &'static ConfiguredCarver;
160
161/// Registry of configured carvers, keyed by namespaced identifier.
162pub struct ConfiguredCarverRegistry {
163    carvers_by_id: Vec<ConfiguredCarverRef>,
164    carvers_by_key: FxHashMap<Identifier, usize>,
165    allows_registering: bool,
166}
167
168impl ConfiguredCarverRegistry {
169    /// Creates an empty registry.
170    #[must_use]
171    pub fn new() -> Self {
172        Self {
173            carvers_by_id: Vec::new(),
174            carvers_by_key: FxHashMap::default(),
175            allows_registering: true,
176        }
177    }
178
179    /// Registers a carver and returns its numeric ID.
180    pub fn register(&mut self, entry: ConfiguredCarverRef) -> usize {
181        assert!(
182            self.allows_registering,
183            "Cannot register ConfiguredCarver after registry has been frozen"
184        );
185        let id = self.carvers_by_id.len();
186        let cached = entry.id.get_or_init(|| id);
187        assert_eq!(*cached, id, "carver registered with conflicting id");
188        self.carvers_by_id.push(entry);
189        self.carvers_by_key.insert(entry.key.clone(), id);
190        id
191    }
192
193    /// Iterates over all registered carvers with their IDs.
194    pub fn iter(&self) -> impl Iterator<Item = (usize, ConfiguredCarverRef)> + '_ {
195        self.carvers_by_id
196            .iter()
197            .enumerate()
198            .map(|(id, &entry)| (id, entry))
199    }
200}
201
202impl Default for ConfiguredCarverRegistry {
203    fn default() -> Self {
204        Self::new()
205    }
206}
207
208crate::impl_registry_ext!(
209    ConfiguredCarverRegistry,
210    ConfiguredCarver,
211    carvers_by_id,
212    carvers_by_key
213);
214
215crate::impl_registry_entry_eq!(ConfiguredCarver);
216
217impl crate::RegistryEntry for ConfiguredCarver {
218    fn key(&self) -> &Identifier {
219        &self.key
220    }
221
222    fn try_id(&self) -> Option<usize> {
223        self.id.get().copied()
224    }
225}