1use serde::{Deserialize, Deserializer, de::Error as _};
2
3use crate::random::Random;
4
5#[derive(Debug, Clone)]
10pub enum IntProvider {
11 Constant(i32),
13 Uniform {
15 min_inclusive: i32,
17 max_inclusive: i32,
19 },
20 BiasedToBottom {
22 min_inclusive: i32,
24 max_inclusive: i32,
26 },
27 VeryBiasedToBottom {
29 min_inclusive: i32,
31 max_inclusive: i32,
33 inner: i32,
35 },
36 Trapezoid {
38 min: i32,
40 max: i32,
42 plateau: i32,
44 },
45 ClampedNormal {
47 mean: f32,
49 deviation: f32,
51 min_inclusive: i32,
53 max_inclusive: i32,
55 },
56 Clamped {
58 source: Box<IntProvider>,
60 min_inclusive: i32,
62 max_inclusive: i32,
64 },
65 WeightedList {
67 distribution: Vec<WeightedIntProvider>,
69 },
70}
71
72#[derive(Debug, Clone)]
74pub struct WeightedIntProvider {
75 pub data: IntProvider,
77 pub weight: i32,
79}
80
81#[derive(Debug, Clone, Copy)]
86pub struct UniformIntProvider {
87 pub min_inclusive: i32,
89 pub max_inclusive: i32,
91}
92
93impl UniformIntProvider {
94 pub fn sample<R: Random + ?Sized>(self, random: &mut R) -> i32 {
96 random.next_i32_between(self.min_inclusive, self.max_inclusive)
97 }
98
99 #[must_use]
101 pub const fn with_max_inclusive(self, max_inclusive: i32) -> Self {
102 Self {
103 min_inclusive: self.min_inclusive,
104 max_inclusive,
105 }
106 }
107}
108
109impl<'de> Deserialize<'de> for UniformIntProvider {
110 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
111 #[derive(Deserialize)]
112 #[serde(deny_unknown_fields)]
113 struct Range {
114 min_inclusive: i32,
115 max_inclusive: i32,
116 }
117
118 #[derive(Deserialize)]
119 #[serde(tag = "type", deny_unknown_fields)]
120 enum Tagged {
121 #[serde(rename = "minecraft:uniform")]
122 Uniform {
123 min_inclusive: i32,
124 max_inclusive: i32,
125 },
126 }
127
128 let value = serde_json::Value::deserialize(d)?;
129 let has_type = value
130 .as_object()
131 .is_some_and(|object| object.contains_key("type"));
132
133 let (min_inclusive, max_inclusive) = if has_type {
134 match serde_json::from_value(value).map_err(D::Error::custom)? {
135 Tagged::Uniform {
136 min_inclusive,
137 max_inclusive,
138 } => (min_inclusive, max_inclusive),
139 }
140 } else {
141 let Range {
142 min_inclusive,
143 max_inclusive,
144 } = Range::deserialize(value).map_err(D::Error::custom)?;
145 (min_inclusive, max_inclusive)
146 };
147
148 if min_inclusive > max_inclusive {
149 return Err(D::Error::custom(
150 "UniformIntProvider min_inclusive exceeds max_inclusive",
151 ));
152 }
153
154 Ok(Self {
155 min_inclusive,
156 max_inclusive,
157 })
158 }
159}
160
161impl IntProvider {
162 #[must_use]
164 pub fn min(&self) -> i32 {
165 match self {
166 Self::Constant(value) => *value,
167 Self::Uniform { min_inclusive, .. }
168 | Self::BiasedToBottom { min_inclusive, .. }
169 | Self::VeryBiasedToBottom { min_inclusive, .. }
170 | Self::Clamped { min_inclusive, .. }
171 | Self::ClampedNormal { min_inclusive, .. } => *min_inclusive,
172 Self::Trapezoid { min, .. } => *min,
173 Self::WeightedList { distribution } => {
174 let mut min = 0;
175 let mut found = false;
176 for entry in distribution {
177 let value = entry.data.min();
178 if !found || value < min {
179 min = value;
180 found = true;
181 }
182 }
183 min
184 }
185 }
186 }
187
188 #[must_use]
190 pub fn max(&self) -> i32 {
191 match self {
192 Self::Constant(value) => *value,
193 Self::Uniform { max_inclusive, .. }
194 | Self::BiasedToBottom { max_inclusive, .. }
195 | Self::VeryBiasedToBottom { max_inclusive, .. }
196 | Self::Clamped { max_inclusive, .. }
197 | Self::ClampedNormal { max_inclusive, .. } => *max_inclusive,
198 Self::Trapezoid { max, .. } => *max,
199 Self::WeightedList { distribution } => {
200 let mut max = 0;
201 let mut found = false;
202 for entry in distribution {
203 let value = entry.data.max();
204 if !found || value > max {
205 max = value;
206 found = true;
207 }
208 }
209 max
210 }
211 }
212 }
213
214 pub fn sample<R: Random + ?Sized>(&self, random: &mut R) -> i32 {
219 match self {
220 Self::Constant(v) => *v,
221 Self::Uniform {
222 min_inclusive,
223 max_inclusive,
224 } => random.next_i32_between(*min_inclusive, *max_inclusive),
225 Self::BiasedToBottom {
226 min_inclusive,
227 max_inclusive,
228 } => {
229 let span = *max_inclusive - *min_inclusive + 1;
230 let bound = random.next_i32_bounded(span) + 1;
231 *min_inclusive + random.next_i32_bounded(bound)
232 }
233 Self::VeryBiasedToBottom {
234 min_inclusive,
235 max_inclusive,
236 inner,
237 } => {
238 let limit = *max_inclusive - *min_inclusive - *inner + 1;
239 if limit <= 0 {
240 *min_inclusive
241 } else {
242 let upper_inclusive = random.next_i32_bounded(limit) + *min_inclusive + *inner;
243 let biased_upper_inclusive =
244 random.next_i32_between(*min_inclusive, upper_inclusive - 1);
245 random.next_i32_between(*min_inclusive, biased_upper_inclusive - 1 + *inner)
246 }
247 }
248 Self::Trapezoid { min, max, plateau } => {
249 if *plateau == 0 && *max == -*min {
250 random.next_i32_bounded(*max + 1) - random.next_i32_bounded(*max + 1)
251 } else {
252 let range = *max - *min;
253 if *plateau >= range {
254 random.next_i32_between(*min, *max)
255 } else {
256 let plateau_start = (range - *plateau) / 2;
257 let plateau_end = range - plateau_start;
258 *min + random.next_i32_between(0, plateau_end)
259 + random.next_i32_between(0, plateau_start)
260 }
261 }
262 }
263 Self::ClampedNormal {
264 mean,
265 deviation,
266 min_inclusive,
267 max_inclusive,
268 } => {
269 let sample = *mean + *deviation * random.next_gaussian() as f32;
270 sample.clamp(*min_inclusive as f32, *max_inclusive as f32) as i32
271 }
272 Self::Clamped {
273 source,
274 min_inclusive,
275 max_inclusive,
276 } => source.sample(random).clamp(*min_inclusive, *max_inclusive),
277 Self::WeightedList { distribution } => {
278 let total_weight: i32 = distribution.iter().map(|entry| entry.weight).sum();
279 if total_weight <= 0 {
280 return 0;
281 }
282 let mut target = random.next_i32_bounded(total_weight);
283 for entry in distribution {
284 target -= entry.weight;
285 if target < 0 {
286 return entry.data.sample(random);
287 }
288 }
289 0
290 }
291 }
292 }
293}
294
295impl<'de> Deserialize<'de> for IntProvider {
296 #[expect(
297 clippy::too_many_lines,
298 reason = "keeps the vanilla int-provider schema variants in one deserialization table"
299 )]
300 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
301 #[derive(Deserialize)]
302 #[serde(tag = "type", deny_unknown_fields)]
303 enum Tagged {
304 #[serde(rename = "minecraft:constant")]
305 Constant { value: i32 },
306 #[serde(rename = "minecraft:uniform")]
307 Uniform {
308 min_inclusive: i32,
309 max_inclusive: i32,
310 },
311 #[serde(rename = "minecraft:biased_to_bottom")]
312 BiasedToBottom {
313 min_inclusive: i32,
314 max_inclusive: i32,
315 },
316 #[serde(rename = "minecraft:very_biased_to_bottom")]
317 VeryBiasedToBottom {
318 min_inclusive: i32,
319 max_inclusive: i32,
320 #[serde(default = "default_inner")]
321 inner: i32,
322 },
323 #[serde(rename = "minecraft:trapezoid")]
324 Trapezoid { min: i32, max: i32, plateau: i32 },
325 #[serde(rename = "minecraft:clamped_normal")]
326 ClampedNormal {
327 mean: f32,
328 deviation: f32,
329 min_inclusive: i32,
330 max_inclusive: i32,
331 },
332 #[serde(rename = "minecraft:clamped")]
333 Clamped {
334 source: Box<IntProvider>,
335 min_inclusive: i32,
336 max_inclusive: i32,
337 },
338 #[serde(rename = "minecraft:weighted_list")]
339 WeightedList {
340 distribution: Vec<WeightedIntProvider>,
341 },
342 }
343
344 const fn default_inner() -> i32 {
345 1
346 }
347
348 let value = serde_json::Value::deserialize(d)?;
349 if value.is_number() {
350 return Ok(Self::Constant(
351 i32::deserialize(value).map_err(D::Error::custom)?,
352 ));
353 }
354
355 Ok(
356 match serde_json::from_value(value).map_err(D::Error::custom)? {
357 Tagged::Constant { value } => Self::Constant(value),
358 Tagged::Uniform {
359 min_inclusive,
360 max_inclusive,
361 } => Self::Uniform {
362 min_inclusive,
363 max_inclusive,
364 },
365 Tagged::BiasedToBottom {
366 min_inclusive,
367 max_inclusive,
368 } => Self::BiasedToBottom {
369 min_inclusive,
370 max_inclusive,
371 },
372 Tagged::VeryBiasedToBottom {
373 min_inclusive,
374 max_inclusive,
375 inner,
376 } => Self::VeryBiasedToBottom {
377 min_inclusive,
378 max_inclusive,
379 inner,
380 },
381 Tagged::Trapezoid { min, max, plateau } => Self::Trapezoid { min, max, plateau },
382 Tagged::ClampedNormal {
383 mean,
384 deviation,
385 min_inclusive,
386 max_inclusive,
387 } => Self::ClampedNormal {
388 mean,
389 deviation,
390 min_inclusive,
391 max_inclusive,
392 },
393 Tagged::Clamped {
394 source,
395 min_inclusive,
396 max_inclusive,
397 } => Self::Clamped {
398 source,
399 min_inclusive,
400 max_inclusive,
401 },
402 Tagged::WeightedList { distribution } => Self::WeightedList { distribution },
403 },
404 )
405 }
406}
407
408impl<'de> Deserialize<'de> for WeightedIntProvider {
409 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
410 #[derive(Deserialize)]
411 #[serde(deny_unknown_fields)]
412 struct Raw {
413 data: IntProvider,
414 weight: i32,
415 }
416
417 let raw = Raw::deserialize(d)?;
418 Ok(Self {
419 data: raw.data,
420 weight: raw.weight,
421 })
422 }
423}