1use super::{Rng, spawn::SpawnTier};
4
5pub type LootTier = SpawnTier;
9
10#[derive(Debug, Clone)]
14pub struct LootDrop {
15 pub id: String,
16 pub name: String,
17 pub tier: LootTier,
18 pub quantity: (u32, u32), pub weight: f32,
20 pub depth_min: u32,
21 pub depth_max: u32,
22}
23
24impl LootDrop {
25 pub fn new(id: impl Into<String>, name: impl Into<String>, tier: LootTier) -> Self {
26 let weight = tier.base_weight();
27 Self {
28 id: id.into(), name: name.into(), tier, quantity: (1, 1),
29 weight, depth_min: 1, depth_max: u32::MAX,
30 }
31 }
32
33 pub fn with_quantity(mut self, min: u32, max: u32) -> Self {
34 self.quantity = (min, max); self
35 }
36
37 pub fn with_depth(mut self, min: u32, max: u32) -> Self {
38 self.depth_min = min; self.depth_max = max; self
39 }
40
41 pub fn with_weight(mut self, w: f32) -> Self { self.weight = w; self }
42
43 pub fn is_valid_at(&self, depth: u32) -> bool {
44 depth >= self.depth_min && depth <= self.depth_max
45 }
46}
47
48#[derive(Debug, Clone, Default)]
52pub struct LootTable {
53 drops: Vec<LootDrop>,
54 pub drop_chance: f32,
56 pub rolls: u32,
58}
59
60#[derive(Debug, Clone)]
62pub struct RolledLoot {
63 pub drop: LootDrop,
64 pub quantity: u32,
65}
66
67impl LootTable {
68 pub fn new(drop_chance: f32, rolls: u32) -> Self {
69 Self { drops: Vec::new(), drop_chance: drop_chance.clamp(0.0, 1.0), rolls }
70 }
71
72 pub fn add(&mut self, drop: LootDrop) -> &mut Self {
73 self.drops.push(drop); self
74 }
75
76 pub fn roll(&self, rng: &mut Rng, depth: u32) -> Vec<RolledLoot> {
78 let mut result = Vec::new();
79 for _ in 0..self.rolls {
80 if !rng.chance(self.drop_chance) { continue; }
81
82 let valid: Vec<(&LootDrop, f32)> = self.drops.iter()
83 .filter(|d| d.is_valid_at(depth))
84 .map(|d| (d, d.weight))
85 .collect();
86
87 if let Some(drop) = rng.pick_weighted(&valid).copied() {
88 let qty = rng.range_i32(drop.quantity.0 as i32, drop.quantity.1 as i32) as u32;
89 result.push(RolledLoot { drop: drop.clone(), quantity: qty });
90 }
91 }
92 result
93 }
94
95 pub fn roll_tier(&self, rng: &mut Rng, depth: u32, tier: LootTier) -> Option<RolledLoot> {
97 let valid: Vec<(&LootDrop, f32)> = self.drops.iter()
98 .filter(|d| d.tier == tier && d.is_valid_at(depth))
99 .map(|d| (d, d.weight))
100 .collect();
101 rng.pick_weighted(&valid).copied().map(|drop| {
102 let qty = rng.range_i32(drop.quantity.0 as i32, drop.quantity.1 as i32) as u32;
103 RolledLoot { drop: drop.clone(), quantity: qty }
104 })
105 }
106
107 pub fn len(&self) -> usize { self.drops.len() }
108}
109
110pub fn chaos_rpg_loot() -> LootTable {
112 let mut t = LootTable::new(0.65, 2);
113
114 t.add(LootDrop::new("health_potion", "Health Potion", LootTier::Common).with_quantity(1, 3));
116 t.add(LootDrop::new("mana_potion", "Mana Potion", LootTier::Common).with_quantity(1, 2));
117 t.add(LootDrop::new("antidote", "Antidote", LootTier::Common).with_quantity(1, 2));
118
119 t.add(LootDrop::new("gold", "Gold", LootTier::Common).with_quantity(1, 50));
121
122 t.add(LootDrop::new("rusty_dagger", "Rusty Dagger", LootTier::Common).with_depth(1, 3));
124 t.add(LootDrop::new("iron_sword", "Iron Sword", LootTier::Common).with_depth(1, 5));
125 t.add(LootDrop::new("chainmail", "Chainmail", LootTier::Uncommon).with_depth(2, 7));
126 t.add(LootDrop::new("steel_sword", "Steel Sword", LootTier::Uncommon).with_depth(3, 10));
127 t.add(LootDrop::new("chaos_blade", "Chaos Blade", LootTier::Rare).with_depth(5, u32::MAX));
128 t.add(LootDrop::new("void_staff", "Void Staff", LootTier::Epic).with_depth(7, u32::MAX));
129 t.add(LootDrop::new("crown_of_chaos", "Crown of Chaos", LootTier::Legendary).with_depth(9, u32::MAX));
130
131 t.add(LootDrop::new("chaos_shard", "Chaos Shard", LootTier::Rare).with_quantity(1, 3));
133 t.add(LootDrop::new("void_crystal", "Void Crystal", LootTier::Epic).with_depth(7, u32::MAX));
134
135 t
136}