1use super::{
2 ConditionalLootFunction, Identifier, ItemStack, LootCondition, LootContext, LootType,
3 NumberProvider, REGISTRY, RegistryExt, RngExt, TaggedRegistryExt,
4};
5
6#[derive(Debug, Clone)]
8pub enum LootEntry {
9 Item {
11 name: Identifier,
12 weight: i32,
13 quality: i32,
14 conditions: &'static [LootCondition],
15 functions: &'static [ConditionalLootFunction],
16 },
17 LootTableRef {
19 name: Identifier,
20 weight: i32,
21 quality: i32,
22 conditions: &'static [LootCondition],
23 functions: &'static [ConditionalLootFunction],
24 },
25 InlineLootTable {
27 pools: &'static [LootPool],
28 weight: i32,
29 quality: i32,
30 conditions: &'static [LootCondition],
31 functions: &'static [ConditionalLootFunction],
32 },
33 Tag {
35 name: Identifier,
36 expand: bool,
37 weight: i32,
38 quality: i32,
39 conditions: &'static [LootCondition],
40 functions: &'static [ConditionalLootFunction],
41 },
42 Alternatives {
44 children: &'static [LootEntry],
45 conditions: &'static [LootCondition],
46 },
47 Group {
49 children: &'static [LootEntry],
50 conditions: &'static [LootCondition],
51 },
52 Sequence {
54 children: &'static [LootEntry],
55 conditions: &'static [LootCondition],
56 },
57 Empty {
59 weight: i32,
60 conditions: &'static [LootCondition],
61 },
62 Dynamic {
64 name: Identifier,
65 conditions: &'static [LootCondition],
66 },
67 Slots {
69 slots: SlotRange,
71 conditions: &'static [LootCondition],
72 functions: &'static [ConditionalLootFunction],
73 },
74}
75
76#[derive(Debug, Clone, Copy)]
78pub enum SlotRange {
79 Single(i32),
81 Range { min: i32, max: i32 },
83 Contents,
85 Named(&'static [&'static str]),
87}
88
89impl LootEntry {
90 #[must_use]
92 pub const fn weight(&self) -> i32 {
93 match self {
94 Self::Item { weight, .. } => *weight,
95 Self::LootTableRef { weight, .. } => *weight,
96 Self::InlineLootTable { weight, .. } => *weight,
97 Self::Tag { weight, .. } => *weight,
98 Self::Empty { weight, .. } => *weight,
99 Self::Alternatives { .. }
101 | Self::Group { .. }
102 | Self::Sequence { .. }
103 | Self::Dynamic { .. }
104 | Self::Slots { .. } => 1,
105 }
106 }
107
108 #[must_use]
110 pub const fn quality(&self) -> i32 {
111 match self {
112 Self::Item { quality, .. } => *quality,
113 Self::LootTableRef { quality, .. } => *quality,
114 Self::InlineLootTable { quality, .. } => *quality,
115 Self::Tag { quality, .. } => *quality,
116 Self::Empty { .. }
117 | Self::Alternatives { .. }
118 | Self::Group { .. }
119 | Self::Sequence { .. }
120 | Self::Dynamic { .. }
121 | Self::Slots { .. } => 0,
122 }
123 }
124
125 #[must_use]
128 pub fn effective_weight(&self, luck: f32) -> i32 {
129 let base = self.weight() as f32;
130 let quality = self.quality() as f32;
131 (base + quality * luck).floor().max(0.0) as i32
132 }
133
134 #[must_use]
136 pub const fn conditions(&self) -> &'static [LootCondition] {
137 match self {
138 Self::Item { conditions, .. } => conditions,
139 Self::LootTableRef { conditions, .. } => conditions,
140 Self::InlineLootTable { conditions, .. } => conditions,
141 Self::Tag { conditions, .. } => conditions,
142 Self::Alternatives { conditions, .. } => conditions,
143 Self::Group { conditions, .. } => conditions,
144 Self::Sequence { conditions, .. } => conditions,
145 Self::Empty { conditions, .. } => conditions,
146 Self::Dynamic { conditions, .. } => conditions,
147 Self::Slots { conditions, .. } => conditions,
148 }
149 }
150
151 #[must_use]
153 pub const fn functions(&self) -> &'static [ConditionalLootFunction] {
154 match self {
155 Self::Item { functions, .. } => functions,
156 Self::LootTableRef { functions, .. } => functions,
157 Self::InlineLootTable { functions, .. } => functions,
158 Self::Tag { functions, .. } => functions,
159 Self::Slots { functions, .. } => functions,
160 Self::Empty { .. }
161 | Self::Alternatives { .. }
162 | Self::Group { .. }
163 | Self::Sequence { .. }
164 | Self::Dynamic { .. } => &[],
165 }
166 }
167}
168
169#[derive(Debug, Clone)]
171pub struct LootPool {
172 pub rolls: NumberProvider,
173 pub bonus_rolls: f32,
174 pub entries: &'static [LootEntry],
175 pub conditions: &'static [LootCondition],
176 pub functions: &'static [ConditionalLootFunction],
177}
178
179#[derive(Debug)]
181pub struct LootTable {
182 pub key: Identifier,
183 pub loot_type: LootType,
184 pub pools: &'static [LootPool],
185 pub functions: &'static [ConditionalLootFunction],
186 pub random_sequence: Option<Identifier>,
187}
188
189impl LootTable {
190 pub fn get_random_items<R: rand::Rng>(&self, ctx: &mut LootContext<'_, R>) -> Vec<ItemStack> {
206 let mut result = Vec::new();
207 for pool in self.pools {
208 pool.add_random_items(ctx, &mut result);
209 }
210
211 if !self.functions.is_empty() {
213 for item in &mut result {
214 for cond_func in self.functions {
215 if cond_func.conditions.iter().all(|c| c.test(ctx)) {
216 cond_func.function.apply(item, ctx);
217 }
218 }
219 }
220 result.retain(|item| item.count > 0);
222 }
223
224 result
225 }
226}
227
228impl LootPool {
229 fn add_random_items<R: rand::Rng>(
231 &self,
232 ctx: &mut LootContext<'_, R>,
233 result: &mut Vec<ItemStack>,
234 ) {
235 for condition in self.conditions {
237 if !condition.test(ctx) {
238 return;
239 }
240 }
241
242 let start_index = result.len();
244
245 let roll_count = self.rolls.get_int(ctx.rng) + (self.bonus_rolls * ctx.luck).floor() as i32;
247
248 for _ in 0..roll_count {
249 self.add_random_item(ctx, result);
250 }
251
252 if !self.functions.is_empty() {
254 for item in result.iter_mut().skip(start_index) {
255 for cond_func in self.functions {
256 if cond_func.conditions.iter().all(|c| c.test(ctx)) {
257 cond_func.function.apply(item, ctx);
258 }
259 }
260 }
261 result.retain(|item| item.count > 0);
263 }
264 }
265
266 fn add_random_item<R: rand::Rng>(
268 &self,
269 ctx: &mut LootContext<'_, R>,
270 result: &mut Vec<ItemStack>,
271 ) {
272 let mut valid_entries: Vec<(&LootEntry, i32)> = Vec::new();
274 let mut total_weight = 0;
275
276 for entry in self.entries {
277 let passes_conditions = entry.conditions().iter().all(|c| c.test(ctx));
279
280 if !passes_conditions {
281 continue;
282 }
283
284 let weight = entry.effective_weight(ctx.luck);
285 if weight > 0 {
286 valid_entries.push((entry, weight));
287 total_weight += weight;
288 }
289 }
290
291 if total_weight == 0 || valid_entries.is_empty() {
292 return;
293 }
294
295 let selected = if valid_entries.len() == 1 {
297 valid_entries[0].0
298 } else {
299 let mut index = ctx.rng.random_range(0..total_weight);
300 let mut selected_entry = valid_entries[0].0;
301 for (entry, weight) in &valid_entries {
302 index -= weight;
303 if index < 0 {
304 selected_entry = entry;
305 break;
306 }
307 }
308 selected_entry
309 };
310
311 selected.create_items(ctx, result);
313 }
314}
315
316impl LootEntry {
317 fn create_items<R: rand::Rng>(
319 &self,
320 ctx: &mut LootContext<'_, R>,
321 result: &mut Vec<ItemStack>,
322 ) {
323 match self {
324 LootEntry::Item {
325 name, functions, ..
326 } => {
327 if let Some(item_ref) = REGISTRY.items.by_key(name) {
328 let mut item = ItemStack::new(item_ref);
329
330 for cond_func in *functions {
332 if cond_func.conditions.iter().all(|c| c.test(ctx)) {
333 cond_func.function.apply(&mut item, ctx);
334 }
335 }
336
337 if item.count > 0 {
338 result.push(item);
339 }
340 }
341 }
342 LootEntry::LootTableRef {
343 name, functions, ..
344 } => {
345 if let Some(table) = REGISTRY.loot_tables.by_key(name) {
347 let mut items = table.get_random_items(ctx);
348 for item in &mut items {
350 for cond_func in *functions {
351 if cond_func.conditions.iter().all(|c| c.test(ctx)) {
352 cond_func.function.apply(item, ctx);
353 }
354 }
355 }
356 result.extend(items.into_iter().filter(|i| i.count > 0));
357 }
358 }
359 LootEntry::InlineLootTable {
360 pools, functions, ..
361 } => {
362 let mut items = Vec::new();
364 for pool in *pools {
365 pool.add_random_items(ctx, &mut items);
366 }
367 for item in &mut items {
369 for cond_func in *functions {
370 if cond_func.conditions.iter().all(|c| c.test(ctx)) {
371 cond_func.function.apply(item, ctx);
372 }
373 }
374 }
375 result.extend(items.into_iter().filter(|i| i.count > 0));
376 }
377 LootEntry::Tag {
378 name,
379 expand,
380 functions,
381 ..
382 } => {
383 if let Some(items) = REGISTRY.items.get_tag(name) {
385 if *expand {
386 if !items.is_empty() {
388 let index = ctx.rng.random_range(0..items.len());
389 let mut item = ItemStack::new(items[index]);
390 for cond_func in *functions {
391 if cond_func.conditions.iter().all(|c| c.test(ctx)) {
392 cond_func.function.apply(&mut item, ctx);
393 }
394 }
395 if item.count > 0 {
396 result.push(item);
397 }
398 }
399 } else {
400 for item_ref in items {
402 let mut item = ItemStack::new(item_ref);
403 for cond_func in *functions {
404 if cond_func.conditions.iter().all(|c| c.test(ctx)) {
405 cond_func.function.apply(&mut item, ctx);
406 }
407 }
408 if item.count > 0 {
409 result.push(item);
410 }
411 }
412 }
413 }
414 }
415 LootEntry::Alternatives { children, .. } => {
416 for child in *children {
418 let passes_conditions = child.conditions().iter().all(|c| c.test(ctx));
420 if !passes_conditions {
421 continue; }
423
424 let before_len = result.len();
425 child.create_items(ctx, result);
426 if result.len() > before_len {
427 break; }
429 }
430 }
431 LootEntry::Group { children, .. } => {
432 for child in *children {
434 let passes_conditions = child.conditions().iter().all(|c| c.test(ctx));
435 if passes_conditions {
436 child.create_items(ctx, result);
437 }
438 }
439 }
440 LootEntry::Sequence { children, .. } => {
441 for child in *children {
445 let passes_conditions = child.conditions().iter().all(|c| c.test(ctx));
446 if !passes_conditions {
447 break; }
449 child.create_items(ctx, result);
450 }
451 }
452 LootEntry::Empty { .. } => {
453 }
455 LootEntry::Dynamic { name, .. } => {
456 let _ = name;
467 }
468 LootEntry::Slots {
469 slots, functions, ..
470 } => {
471 let _ = slots;
478 let _ = functions;
479 }
480 }
481 }
482}