1use super::Rng;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
16pub enum SpawnTier {
17 Common,
18 Uncommon,
19 Rare,
20 Epic,
21 Legendary,
22 Boss,
23}
24
25impl SpawnTier {
26 pub fn base_weight(self) -> f32 {
28 match self {
29 SpawnTier::Common => 100.0,
30 SpawnTier::Uncommon => 40.0,
31 SpawnTier::Rare => 15.0,
32 SpawnTier::Epic => 5.0,
33 SpawnTier::Legendary => 1.5,
34 SpawnTier::Boss => 1.0,
35 }
36 }
37
38 pub fn name(self) -> &'static str {
40 match self {
41 SpawnTier::Common => "Common",
42 SpawnTier::Uncommon => "Uncommon",
43 SpawnTier::Rare => "Rare",
44 SpawnTier::Epic => "Epic",
45 SpawnTier::Legendary => "Legendary",
46 SpawnTier::Boss => "Boss",
47 }
48 }
49
50 pub fn color(self) -> glam::Vec4 {
52 match self {
53 SpawnTier::Common => glam::Vec4::new(0.8, 0.8, 0.8, 1.0),
54 SpawnTier::Uncommon => glam::Vec4::new(0.0, 1.0, 0.2, 1.0),
55 SpawnTier::Rare => glam::Vec4::new(0.2, 0.5, 1.0, 1.0),
56 SpawnTier::Epic => glam::Vec4::new(0.7, 0.0, 1.0, 1.0),
57 SpawnTier::Legendary => glam::Vec4::new(1.0, 0.6, 0.0, 1.0),
58 SpawnTier::Boss => glam::Vec4::new(1.0, 0.0, 0.0, 1.0),
59 }
60 }
61}
62
63#[derive(Debug, Clone)]
67pub struct SpawnEntry {
68 pub id: String,
70 pub name: String,
72 pub weight: f32,
74 pub tier: SpawnTier,
76 pub min_depth: u32,
78 pub max_depth: u32,
80 pub tags: Vec<String>,
82 pub group: (u32, u32),
84 pub stats: (f32, f32, f32),
86}
87
88impl SpawnEntry {
89 pub fn new(id: impl Into<String>, name: impl Into<String>, tier: SpawnTier) -> Self {
90 let id = id.into();
91 let name = name.into();
92 let weight = tier.base_weight();
93 Self {
94 id, name, weight, tier,
95 min_depth: 1,
96 max_depth: u32::MAX,
97 tags: Vec::new(),
98 group: (1, 1),
99 stats: (10.0, 2.0, 5.0),
100 }
101 }
102
103 pub fn with_depth(mut self, min: u32, max: u32) -> Self {
104 self.min_depth = min; self.max_depth = max; self
105 }
106
107 pub fn with_weight(mut self, w: f32) -> Self { self.weight = w; self }
108
109 pub fn with_group(mut self, min: u32, max: u32) -> Self {
110 self.group = (min, max); self
111 }
112
113 pub fn with_tags(mut self, tags: &[&str]) -> Self {
114 self.tags = tags.iter().map(|&s| s.to_string()).collect(); self
115 }
116
117 pub fn with_stats(mut self, hp: f32, dmg: f32, xp: f32) -> Self {
118 self.stats = (hp, dmg, xp); self
119 }
120
121 pub fn valid_for_depth(&self, depth: u32) -> bool {
123 depth >= self.min_depth && depth <= self.max_depth
124 }
125
126 pub fn has_tag(&self, tag: &str) -> bool {
128 self.tags.iter().any(|t| t == tag)
129 }
130
131 pub fn scaled_stats(&self, depth: u32) -> (f32, f32, f32) {
133 let scale = 1.0 + (depth as f32 - 1.0) * 0.15;
134 let (hp, dmg, xp) = self.stats;
135 (hp * scale, dmg * (1.0 + (depth as f32 - 1.0) * 0.08), xp * scale)
136 }
137}
138
139#[derive(Debug, Clone)]
143pub struct SpawnResult {
144 pub entry: SpawnEntry,
145 pub count: u32,
146 pub position: Option<(i32, i32)>,
147}
148
149#[derive(Debug, Clone, Default)]
161pub struct SpawnTable {
162 entries: Vec<SpawnEntry>,
163}
164
165impl SpawnTable {
166 pub fn new() -> Self { Self { entries: Vec::new() } }
167
168 pub fn add(&mut self, entry: SpawnEntry) -> &mut Self {
169 self.entries.push(entry);
170 self
171 }
172
173 pub fn add_many(&mut self, entries: Vec<SpawnEntry>) -> &mut Self {
175 self.entries.extend(entries);
176 self
177 }
178
179 pub fn roll_one(&self, rng: &mut Rng, depth: u32, tag: Option<&str>) -> Option<&SpawnEntry> {
181 let valid: Vec<(&SpawnEntry, f32)> = self.entries.iter()
182 .filter(|e| e.valid_for_depth(depth))
183 .filter(|e| tag.map_or(true, |t| e.has_tag(t)))
184 .map(|e| (e, e.weight))
185 .collect();
186 rng.pick_weighted(&valid).copied()
187 }
188
189 pub fn roll(&self, rng: &mut Rng, n: usize, depth: u32) -> Vec<SpawnResult> {
191 (0..n).filter_map(|_| {
192 self.roll_one(rng, depth, None).map(|entry| {
193 let count = rng.range_i32(entry.group.0 as i32, entry.group.1 as i32) as u32;
194 SpawnResult { entry: entry.clone(), count, position: None }
195 })
196 }).collect()
197 }
198
199 pub fn roll_guaranteed(&self, rng: &mut Rng, depth: u32, tiers: &[SpawnTier]) -> Vec<SpawnResult> {
201 let mut results = Vec::new();
202 for &tier in tiers {
203 let valid: Vec<(&SpawnEntry, f32)> = self.entries.iter()
204 .filter(|e| e.tier == tier && e.valid_for_depth(depth))
205 .map(|e| (e, e.weight))
206 .collect();
207 if let Some(entry) = rng.pick_weighted(&valid).copied() {
208 let count = rng.range_i32(entry.group.0 as i32, entry.group.1 as i32) as u32;
209 results.push(SpawnResult { entry: entry.clone(), count, position: None });
210 }
211 }
212 results
213 }
214
215 pub fn by_tag(&self, tag: &str) -> Vec<&SpawnEntry> {
217 self.entries.iter().filter(|e| e.has_tag(tag)).collect()
218 }
219
220 pub fn available_at_depth(&self, depth: u32) -> Vec<&SpawnEntry> {
222 let mut entries: Vec<&SpawnEntry> = self.entries.iter()
223 .filter(|e| e.valid_for_depth(depth))
224 .collect();
225 entries.sort_by(|a, b| b.weight.partial_cmp(&a.weight).unwrap());
226 entries
227 }
228
229 pub fn len(&self) -> usize { self.entries.len() }
230 pub fn is_empty(&self) -> bool { self.entries.is_empty() }
231}
232
233pub fn chaos_rpg_creatures() -> SpawnTable {
237 let mut t = SpawnTable::new();
238
239 t.add(SpawnEntry::new("skeleton", "Skeleton", SpawnTier::Common)
241 .with_depth(1, 8).with_group(1, 3).with_tags(&["undead", "melee"])
242 .with_stats(8.0, 2.0, 4.0));
243 t.add(SpawnEntry::new("zombie", "Zombie", SpawnTier::Common)
244 .with_depth(1, 6).with_group(1, 2).with_tags(&["undead", "melee"])
245 .with_stats(15.0, 1.5, 3.0));
246 t.add(SpawnEntry::new("skeleton_archer","Skeleton Archer", SpawnTier::Uncommon)
247 .with_depth(2, 10).with_group(1, 2).with_tags(&["undead", "ranged"])
248 .with_stats(6.0, 3.0, 6.0));
249
250 t.add(SpawnEntry::new("cave_troll", "Cave Troll", SpawnTier::Uncommon)
252 .with_depth(3, 12).with_group(1, 1).with_tags(&["beast", "melee"])
253 .with_stats(30.0, 5.0, 15.0));
254 t.add(SpawnEntry::new("shadow_wraith", "Shadow Wraith", SpawnTier::Rare)
255 .with_depth(4, 15).with_group(1, 1).with_tags(&["undead", "shadow", "melee"])
256 .with_stats(20.0, 6.0, 25.0));
257 t.add(SpawnEntry::new("chaos_imp", "Chaos Imp", SpawnTier::Uncommon)
258 .with_depth(3, 20).with_group(2, 4).with_tags(&["demon", "chaos"])
259 .with_stats(5.0, 4.0, 8.0));
260
261 t.add(SpawnEntry::new("stone_golem", "Stone Golem", SpawnTier::Rare)
263 .with_depth(5, u32::MAX).with_group(1, 1).with_tags(&["construct", "melee"])
264 .with_stats(50.0, 8.0, 40.0));
265 t.add(SpawnEntry::new("lich", "Lich", SpawnTier::Epic)
266 .with_depth(6, u32::MAX).with_group(1, 1).with_tags(&["undead", "caster"])
267 .with_stats(40.0, 12.0, 80.0));
268 t.add(SpawnEntry::new("void_stalker", "Void Stalker", SpawnTier::Rare)
269 .with_depth(7, u32::MAX).with_group(1, 2).with_tags(&["void", "ranged"])
270 .with_stats(25.0, 10.0, 55.0));
271
272 t.add(SpawnEntry::new("chaos_champion", "Chaos Champion", SpawnTier::Epic)
274 .with_depth(8, u32::MAX).with_group(1, 1).with_tags(&["demon", "melee", "elite"])
275 .with_stats(80.0, 15.0, 120.0));
276 t.add(SpawnEntry::new("elder_dragon", "Elder Dragon", SpawnTier::Legendary)
277 .with_depth(10, u32::MAX).with_group(1, 1).with_tags(&["dragon", "elite"])
278 .with_stats(200.0, 25.0, 500.0));
279
280 t.add(SpawnEntry::new("bone_king", "Bone King", SpawnTier::Boss)
282 .with_depth(3, 3).with_group(1, 1).with_tags(&["undead", "boss"])
283 .with_stats(120.0, 18.0, 300.0));
284 t.add(SpawnEntry::new("chaos_archon", "Chaos Archon", SpawnTier::Boss)
285 .with_depth(6, 6).with_group(1, 1).with_tags(&["demon", "chaos", "boss"])
286 .with_stats(250.0, 30.0, 800.0));
287 t.add(SpawnEntry::new("void_sovereign", "Void Sovereign", SpawnTier::Boss)
288 .with_depth(10, 10).with_group(1, 1).with_tags(&["void", "boss"])
289 .with_stats(500.0, 50.0, 2000.0));
290
291 t
292}
293
294pub fn chaos_rpg_items() -> SpawnTable {
296 let mut t = SpawnTable::new();
297
298 t.add(SpawnEntry::new("health_potion", "Health Potion", SpawnTier::Common)
299 .with_stats(0.0, 0.0, 0.0));
300 t.add(SpawnEntry::new("mana_potion", "Mana Potion", SpawnTier::Common));
301 t.add(SpawnEntry::new("iron_sword", "Iron Sword", SpawnTier::Common)
302 .with_depth(1, 4));
303 t.add(SpawnEntry::new("steel_sword", "Steel Sword", SpawnTier::Uncommon)
304 .with_depth(3, 8));
305 t.add(SpawnEntry::new("chaos_shard", "Chaos Shard", SpawnTier::Rare)
306 .with_depth(4, u32::MAX));
307 t.add(SpawnEntry::new("void_crystal", "Void Crystal", SpawnTier::Epic)
308 .with_depth(7, u32::MAX));
309 t.add(SpawnEntry::new("amulet_of_chaos","Amulet of Chaos", SpawnTier::Legendary)
310 .with_depth(9, u32::MAX));
311
312 t
313}