spawner.rs 8.5 KB

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