src/spawner.rs
1	use rltk::{ RGB, RandomNumberGenerator };
2 use specs::prelude::*;
3 use super::{CombatStats, Player, Renderable,
4 Name, Position, Viewshed,
5 Monster, BlocksTile, Rect,
6 MAPWIDTH, Item, ProvidesHealing,
7 Consumable, InflictsDamage, Ranged,
8 AreaOfEffect,SerializeMe,RandomTable,
9 EquipmentSlot,Equippable, MeleePowerBonus,
10 DefenseBonus};
11 use specs::saveload::{MarkedBuilder, SimpleMarker};
12 use std::collections::HashMap;
13
14 const MAX_MONSTERS : i32 = 4;
15
16 fn room_table(map_depth: i32) -> RandomTable {
17 RandomTable::new()
18 .add("Goblin", 10)
19 .add("Orc", 1 + map_depth)
20 .add("Health Potion", 7)
21 .add("Fireball Scroll", 2 + map_depth)
22 .add("Magic Missile Scroll", 4 + map_depth)
23 .add("Staff", 3)
24 .add("Holy Cross", 3)
25 .add("Shepards Staff", map_depth - 1)
26 .add("Tower Shield", map_depth - 1)
27 }
28
29 #[allow(clippy::map_entry)]
30 pub fn spawn_room(ecs: &mut World, room : &Rect, map_depth: i32) {
31 let spawn_table = room_table(map_depth);
32 let mut spawn_points : HashMap<usize, String> = HashMap::new();
33
34 // Scope to keep the borrow checker happy
35 {
36 let mut rng = ecs.write_resource::<RandomNumberGenerator>();
37 let num_spawns = rng.roll_dice(1, MAX_MONSTERS + 3) + (map_depth - 1) - 3;
38
39 for _i in 0 .. num_spawns {
40 let mut added = false;
41 let mut tries = 0;
42 while !added && tries < 20 {
43 let x = (room.x1 + rng.roll_dice(1, i32::abs(room.x2 - room.x1))) as usize;
44 let y = (room.y1 + rng.roll_dice(1, i32::abs(room.y2 - room.y1))) as usize;
45 let idx = (y * MAPWIDTH) + x;
46 if !spawn_points.contains_key(&idx) {
47 spawn_points.insert(idx, spawn_table.roll(&mut rng));
48 added = true;
49 } else {
50 tries += 1;
51 }
52 }
53 }
54 }
55
56 // Actually spawn the monsters
57 for spawn in spawn_points.iter() {
58 let x = (*spawn.0 % MAPWIDTH) as i32;
59 let y = (*spawn.0 / MAPWIDTH) as i32;
60
61 match spawn.1.as_ref() {
62 "Goblin" => goblin(ecs, x, y),
63 "Orc" => orc(ecs, x, y),
64 "Health Potion" => health_potion(ecs, x, y),
65 "Fireball Scroll" => fireball_scroll(ecs, x, y),
66 "Magic Missile Scroll" => magic_missile_scroll(ecs, x, y),
67 "Staff" => staff(ecs, x, y),
68 "Holy Cross" => holy_cross(ecs, x, y),
69 "Shepards Staff" => shepards_staff(ecs, x, y),
70 "Tower Sheild" => tower_shield(ecs, x, y),
71 _ => {}
72 }
73 }
74 }
75
76 fn shepards_staff(ecs: &mut World, x: i32, y: i32) {
77 ecs.create_entity()
78 .with(Position{ x, y })
79 .with(Renderable{
80 glyph: rltk::to_cp437('/'),
81 fg: RGB::named(rltk::YELLOW),
82 bg: RGB::named(rltk::BLACK),
83 render_order: 2
84 })
85 .with(Name{ name : "Shepards Staff".to_string() })
86 .with(Item{})
87 .with(Equippable{ slot: EquipmentSlot::Melee })
88 .with(MeleePowerBonus{ power: 4 })
89 .marked::<SimpleMarker<SerializeMe>>()
90 .build();
91 }
92
93 fn tower_shield(ecs: &mut World, x: i32, y: i32) {
94 ecs.create_entity()
95 .with(Position{ x, y })
96 .with(Renderable{
97 glyph: rltk::to_cp437('('),
98 fg: RGB::named(rltk::YELLOW),
99 bg: RGB::named(rltk::BLACK),
100 render_order: 2
101 })
102 .with(Name{ name : "Tower Shield".to_string() })
103 .with(Item{})
104 .with(Equippable{ slot: EquipmentSlot::Shield })
105 .with(DefenseBonus{ defense: 3 })
106 .marked::<SimpleMarker<SerializeMe>>()
107 .build();
108 }
109
110 fn staff(ecs: &mut World, x: i32, y: i32) {
111 ecs.create_entity()
112 .with(Position{ x, y })
113 .with(Renderable{
114 glyph: rltk::to_cp437('|'),
115 fg: RGB::named(rltk::CYAN),
116 bg: RGB::named(rltk::BLACK),
117 render_order: 2
118 })
119 .with(Name{ name : "Staff".to_string() })
120 .with(Item{})
121 .marked::<SimpleMarker<SerializeMe>>()
122 .with(Equippable{ slot: EquipmentSlot::Melee })
123 .with(MeleePowerBonus{power: 2})
124 .build();
125 }
126
127 fn holy_cross(ecs: &mut World, x: i32, y: i32) {
128 ecs.create_entity()
129 .with(Position{ x, y })
130 .with(Renderable{
131 glyph: rltk::to_cp437('+'),
132 fg: RGB::named(rltk::CYAN),
133 bg: RGB::named(rltk::BLACK),
134 render_order: 2
135 })
136 .with(Name{ name : "Holy Cross".to_string() })
137 .with(Item{})
138 .marked::<SimpleMarker<SerializeMe>>()
139 .with(Equippable{ slot: EquipmentSlot::Shield })
140 .with(DefenseBonus{ defense: 1})
141 .build();
142 }
143
144 fn fireball_scroll(ecs: &mut World, x: i32, y: i32) {
145 ecs.create_entity()
146 .with(Position{ x, y })
147 .with(Renderable{
148 glyph: rltk::to_cp437(')'),
149 fg: RGB::named(rltk::ORANGE),
150 bg: RGB::named(rltk::BLACK),
151 render_order: 2
152 })
153 .with(Name{ name : "Fireball Scroll".to_string() })
154 .with(Item{})
155 .with(Consumable{})
156 .with(Ranged{ range: 6 })
157 .with(InflictsDamage{ damage: 20 })
158 .with(AreaOfEffect{ radius: 3 })
159 .marked::<SimpleMarker<SerializeMe>>()
160 .build();
161 }
162
163 fn magic_missile_scroll(ecs: &mut World, x: i32, y: i32) {
164 ecs.create_entity()
165 .with(Position{ x, y })
166 .with(Renderable{
167 glyph: rltk::to_cp437(')'),
168 fg: RGB::named(rltk::CYAN),
169 bg: RGB::named(rltk::BLACK),
170 render_order: 2
171 })
172 .with(Name{ name : "Magic Missile Scroll".to_string() })
173 .with(Item{})
174 .with(Consumable{})
175 .with(Ranged{ range: 6 })
176 .with(InflictsDamage{ damage: 8 })
177 .marked::<SimpleMarker<SerializeMe>>()
178 .build();
179 }
180
181 fn health_potion(ecs: &mut World, x: i32, y: i32) {
182 ecs.create_entity()
183 .with(Position{ x, y })
184 .with(Renderable{
185 glyph: rltk::to_cp437('ยก'),
186 fg: RGB::named(rltk::MAGENTA),
187 bg: RGB::named(rltk::BLACK),
188 render_order: 2
189 })
190 .with(Name{ name : "Health Potion".to_string() })
191 .with(Item{})
192 .with(Consumable{})
193 .with(ProvidesHealing{ heal_amount: 8 })
194 .marked::<SimpleMarker<SerializeMe>>()
195 .build();
196 }
197
198 /// Spawns the player and returns his/her entity object.
199 pub fn player(ecs : &mut World, player_x : i32, player_y : i32) -> Entity {
200 ecs
201 .create_entity()
202 .with(Position { x: player_x, y: player_y })
203 .with(Renderable {
204 glyph: rltk::to_cp437('@'),
205 fg: RGB::named(rltk::YELLOW),
206 bg: RGB::named(rltk::BLACK),
207 render_order: 0
208 })
209 .with(Player{})
210 .with(Viewshed{ visible_tiles : Vec::new(), range: 8, dirty: true })
211 .with(Name{name: "Player".to_string() })
212 .with(CombatStats{ max_hp: 30, hp: 30, defense: 2, power: 5 })
213 .marked::<SimpleMarker<SerializeMe>>()
214 .build()
215 }
216
217 fn orc(ecs: &mut World, x: i32, y: i32) { monster(ecs, x, y, rltk::to_cp437('o'), "Orc"); }
218 fn goblin(ecs: &mut World, x: i32, y: i32) { monster(ecs, x, y, rltk::to_cp437('g'), "Goblin"); }
219
220 fn monster<S : ToString>(ecs: &mut World, x: i32, y: i32, glyph : rltk::FontCharType, name : S) {
221 ecs.create_entity()
222 .with(Position{ x, y })
223 .with(Renderable{
224 glyph,
225 fg: RGB::named(rltk::RED),
226 bg: RGB::named(rltk::BLACK),
227 render_order: 1
228 })
229 .with(Viewshed{ visible_tiles : Vec::new(), range: 8, dirty: true })
230 .with(Monster{})
231 .with(Name{ name : name.to_string() })
232 .with(BlocksTile{})
233 .with(CombatStats{ max_hp: 16, hp: 16, defense: 1, power: 4 })
234 .marked::<SimpleMarker<SerializeMe>>()
235 .build();
236 }