Skip to main content

steel_registry/
carver.rs

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