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