Skip to main content

steel_utils/value_providers/
height.rs

1use serde::{Deserialize, Deserializer, de::Error as _};
2
3use crate::random::Random;
4
5use super::VerticalAnchor;
6
7/// An `int`-valued provider parameterised by world-generation bounds
8/// (`min_y`, `height`).
9///
10/// Mirrors vanilla's `HeightProvider` hierarchy.
11#[derive(Debug, Clone, Copy)]
12pub enum HeightProvider {
13    /// Always resolves to a fixed anchor.
14    Constant(VerticalAnchor),
15    /// Uniform inclusive over \[min, max\].
16    Uniform {
17        /// Inclusive lower bound.
18        min_inclusive: VerticalAnchor,
19        /// Inclusive upper bound.
20        max_inclusive: VerticalAnchor,
21    },
22    /// Sum of two `next_i32_bounded` draws — symmetric triangle when
23    /// `plateau == 0`, trapezoid otherwise.
24    Trapezoid {
25        /// Inclusive lower bound.
26        min_inclusive: VerticalAnchor,
27        /// Inclusive upper bound.
28        max_inclusive: VerticalAnchor,
29        /// Flat-top width; `0` gives a pure triangle.
30        plateau: i32,
31    },
32    /// Biased toward the bottom: two nested `nextInt` draws.
33    BiasedToBottom {
34        /// Inclusive lower bound.
35        min_inclusive: VerticalAnchor,
36        /// Inclusive upper bound.
37        max_inclusive: VerticalAnchor,
38        /// Minimum span of the inner window (default `1`).
39        inner: i32,
40    },
41    /// Heavily biased toward the bottom: three nested `nextInt` draws.
42    VeryBiasedToBottom {
43        /// Inclusive lower bound.
44        min_inclusive: VerticalAnchor,
45        /// Inclusive upper bound.
46        max_inclusive: VerticalAnchor,
47        /// Minimum span of the inner window (default `1`).
48        inner: i32,
49    },
50}
51
52impl HeightProvider {
53    /// Sample a Y coordinate.
54    ///
55    /// Matches vanilla's `HeightProvider.sample` — including the "empty range
56    /// returns min" fallback (vanilla logs a warning once; we silently fall
57    /// back to `min` since this branch isn't hit in practice).
58    pub fn sample<R: Random + ?Sized>(self, random: &mut R, min_y: i32, height: i32) -> i32 {
59        match self {
60            Self::Constant(anchor) => anchor.resolve_y(min_y, height),
61            Self::Uniform {
62                min_inclusive,
63                max_inclusive,
64            } => {
65                let min = min_inclusive.resolve_y(min_y, height);
66                let max = max_inclusive.resolve_y(min_y, height);
67                if min > max {
68                    min
69                } else {
70                    random.next_i32_between(min, max)
71                }
72            }
73            Self::Trapezoid {
74                min_inclusive,
75                max_inclusive,
76                plateau,
77            } => {
78                let min = min_inclusive.resolve_y(min_y, height);
79                let max = max_inclusive.resolve_y(min_y, height);
80                if min > max {
81                    min
82                } else {
83                    let range = max - min;
84                    if plateau >= range {
85                        random.next_i32_between(min, max)
86                    } else {
87                        let plateau_start = (range - plateau) / 2;
88                        let plateau_end = range - plateau_start;
89                        min + random.next_i32_between(0, plateau_end)
90                            + random.next_i32_between(0, plateau_start)
91                    }
92                }
93            }
94            Self::BiasedToBottom {
95                min_inclusive,
96                max_inclusive,
97                inner,
98            } => {
99                let min = min_inclusive.resolve_y(min_y, height);
100                let max = max_inclusive.resolve_y(min_y, height);
101                if max - min - inner < 0 {
102                    min
103                } else {
104                    let limit = random.next_i32_bounded(max - min - inner + 1);
105                    random.next_i32_bounded(limit + inner) + min
106                }
107            }
108            Self::VeryBiasedToBottom {
109                min_inclusive,
110                max_inclusive,
111                inner,
112            } => {
113                let min = min_inclusive.resolve_y(min_y, height);
114                let max = max_inclusive.resolve_y(min_y, height);
115                if max - min - inner < 0 {
116                    min
117                } else {
118                    let upper_inclusive = random.next_i32_between(min + inner, max);
119                    let biased_upper_inclusive = random.next_i32_between(min, upper_inclusive - 1);
120                    random.next_i32_between(min, biased_upper_inclusive - 1 + inner)
121                }
122            }
123        }
124    }
125}
126
127impl<'de> Deserialize<'de> for HeightProvider {
128    fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
129        #[derive(Deserialize)]
130        #[serde(tag = "type", deny_unknown_fields)]
131        enum Tagged {
132            #[serde(rename = "minecraft:constant")]
133            Constant { value: VerticalAnchor },
134            #[serde(rename = "minecraft:uniform")]
135            Uniform {
136                min_inclusive: VerticalAnchor,
137                max_inclusive: VerticalAnchor,
138            },
139            #[serde(rename = "minecraft:trapezoid")]
140            Trapezoid {
141                min_inclusive: VerticalAnchor,
142                max_inclusive: VerticalAnchor,
143                #[serde(default)]
144                plateau: i32,
145            },
146            #[serde(rename = "minecraft:biased_to_bottom")]
147            BiasedToBottom {
148                min_inclusive: VerticalAnchor,
149                max_inclusive: VerticalAnchor,
150                #[serde(default = "default_inner")]
151                inner: i32,
152            },
153            #[serde(rename = "minecraft:very_biased_to_bottom")]
154            VeryBiasedToBottom {
155                min_inclusive: VerticalAnchor,
156                max_inclusive: VerticalAnchor,
157                #[serde(default = "default_inner")]
158                inner: i32,
159            },
160        }
161
162        const fn default_inner() -> i32 {
163            1
164        }
165
166        let value = serde_json::Value::deserialize(d)?;
167        let has_type = value
168            .as_object()
169            .is_some_and(|object| object.contains_key("type"));
170
171        if !has_type {
172            let anchor = VerticalAnchor::deserialize(value).map_err(D::Error::custom)?;
173            return Ok(Self::Constant(anchor));
174        }
175
176        Ok(
177            match serde_json::from_value(value).map_err(D::Error::custom)? {
178                Tagged::Constant { value } => Self::Constant(value),
179                Tagged::Uniform {
180                    min_inclusive,
181                    max_inclusive,
182                } => Self::Uniform {
183                    min_inclusive,
184                    max_inclusive,
185                },
186                Tagged::Trapezoid {
187                    min_inclusive,
188                    max_inclusive,
189                    plateau,
190                } => Self::Trapezoid {
191                    min_inclusive,
192                    max_inclusive,
193                    plateau,
194                },
195                Tagged::BiasedToBottom {
196                    min_inclusive,
197                    max_inclusive,
198                    inner,
199                } => Self::BiasedToBottom {
200                    min_inclusive,
201                    max_inclusive,
202                    inner,
203                },
204                Tagged::VeryBiasedToBottom {
205                    min_inclusive,
206                    max_inclusive,
207                    inner,
208                } => Self::VeryBiasedToBottom {
209                    min_inclusive,
210                    max_inclusive,
211                    inner,
212                },
213            },
214        )
215    }
216}