spawner.rs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  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, HungerClock,
  8. HungerState, ProvidesFood, MagicMapper, Hidden, EntryTrigger, SingleActivation, Map, TileType };
  9. use crate::specs::saveload::{MarkedBuilder, SimpleMarker};
  10. use std::collections::HashMap;
  11. /// Spawns the player and returns his/her entity object.
  12. pub fn player(ecs : &mut World, player_x : i32, player_y : i32) -> Entity {
  13. ecs
  14. .create_entity()
  15. .with(Position { x: player_x, y: player_y })
  16. .with(Renderable {
  17. glyph: rltk::to_cp437('@'),
  18. fg: RGB::named(rltk::YELLOW),
  19. bg: RGB::named(rltk::BLACK),
  20. render_order: 0
  21. })
  22. .with(Player{})
  23. .with(Viewshed{ visible_tiles : Vec::new(), range: 8, dirty: true })
  24. .with(Name{name: "Player".to_string() })
  25. .with(CombatStats{ max_hp: 30, hp: 30, defense: 2, power: 5 })
  26. .with(HungerClock{ state: HungerState::WellFed, duration: 20 })
  27. .marked::<SimpleMarker<SerializeMe>>()
  28. .build()
  29. }
  30. const MAX_MONSTERS : i32 = 4;
  31. fn room_table(map_depth: i32) -> RandomTable {
  32. RandomTable::new()
  33. .add("Goblin", 10)
  34. .add("Orc", 1 + map_depth)
  35. .add("Health Potion", 7)
  36. .add("Fireball Scroll", 2 + map_depth)
  37. .add("Confusion Scroll", 2 + map_depth)
  38. .add("Magic Missile Scroll", 4)
  39. .add("Dagger", 3)
  40. .add("Shield", 3)
  41. .add("Longsword", map_depth - 1)
  42. .add("Tower Shield", map_depth - 1)
  43. .add("Rations", 10)
  44. .add("Magic Mapping Scroll", 2)
  45. .add("Bear Trap", 5)
  46. }
  47. /// Fills a room with stuff!
  48. pub fn spawn_room(ecs: &mut World, room : &Rect, map_depth: i32) {
  49. let mut possible_targets : Vec<usize> = Vec::new();
  50. { // Borrow scope - to keep access to the map separated
  51. let map = ecs.fetch::<Map>();
  52. for y in room.y1 + 1 .. room.y2 {
  53. for x in room.x1 + 1 .. room.x2 {
  54. let idx = map.xy_idx(x, y);
  55. if map.tiles[idx] == TileType::Floor {
  56. possible_targets.push(idx);
  57. }
  58. }
  59. }
  60. }
  61. spawn_region(ecs, &possible_targets, map_depth);
  62. }
  63. /// Fills a region with stuff!
  64. pub fn spawn_region(ecs: &mut World, area : &[usize], map_depth: i32) {
  65. let spawn_table = room_table(map_depth);
  66. let mut spawn_points : HashMap<usize, String> = HashMap::new();
  67. let mut areas : Vec<usize> = Vec::from(area);
  68. // Scope to keep the borrow checker happy
  69. {
  70. let mut rng = ecs.write_resource::<RandomNumberGenerator>();
  71. let num_spawns = i32::min(areas.len() as i32, rng.roll_dice(1, MAX_MONSTERS + 3) + (map_depth - 1) - 3);
  72. if num_spawns == 0 { return; }
  73. for _i in 0 .. num_spawns {
  74. let array_index = if areas.len() == 1 { 0usize } else { (rng.roll_dice(1, areas.len() as i32)-1) as usize };
  75. let map_idx = areas[array_index];
  76. spawn_points.insert(map_idx, spawn_table.roll(&mut rng));
  77. areas.remove(array_index);
  78. }
  79. }
  80. // Actually spawn the monsters
  81. for spawn in spawn_points.iter() {
  82. spawn_entity(ecs, &spawn);
  83. }
  84. }
  85. /// Spawns a named entity (name in tuple.1) at the location in (tuple.0)
  86. fn spawn_entity(ecs: &mut World, spawn : &(&usize, &String)) {
  87. let x = (*spawn.0 % MAPWIDTH) as i32;
  88. let y = (*spawn.0 / MAPWIDTH) as i32;
  89. match spawn.1.as_ref() {
  90. "Goblin" => goblin(ecs, x, y),
  91. "Orc" => orc(ecs, x, y),
  92. "Health Potion" => health_potion(ecs, x, y),
  93. "Fireball Scroll" => fireball_scroll(ecs, x, y),
  94. "Confusion Scroll" => confusion_scroll(ecs, x, y),
  95. "Magic Missile Scroll" => magic_missile_scroll(ecs, x, y),
  96. "Dagger" => dagger(ecs, x, y),
  97. "Shield" => shield(ecs, x, y),
  98. "Longsword" => longsword(ecs, x, y),
  99. "Tower Shield" => tower_shield(ecs, x, y),
  100. "Rations" => rations(ecs, x, y),
  101. "Magic Mapping Scroll" => magic_mapping_scroll(ecs, x, y),
  102. "Bear Trap" => bear_trap(ecs, x, y),
  103. _ => {}
  104. }
  105. }
  106. fn orc(ecs: &mut World, x: i32, y: i32) { monster(ecs, x, y, rltk::to_cp437('o'), "Orc"); }
  107. fn goblin(ecs: &mut World, x: i32, y: i32) { monster(ecs, x, y, rltk::to_cp437('g'), "Goblin"); }
  108. fn monster<S : ToString>(ecs: &mut World, x: i32, y: i32, glyph : u8, name : S) {
  109. ecs.create_entity()
  110. .with(Position{ x, y })
  111. .with(Renderable{
  112. glyph,
  113. fg: RGB::named(rltk::RED),
  114. bg: RGB::named(rltk::BLACK),
  115. render_order: 1
  116. })
  117. .with(Viewshed{ visible_tiles : Vec::new(), range: 8, dirty: true })
  118. .with(Monster{})
  119. .with(Name{ name : name.to_string() })
  120. .with(BlocksTile{})
  121. .with(CombatStats{ max_hp: 16, hp: 16, defense: 1, power: 4 })
  122. .marked::<SimpleMarker<SerializeMe>>()
  123. .build();
  124. }
  125. fn health_potion(ecs: &mut World, x: i32, y: i32) {
  126. ecs.create_entity()
  127. .with(Position{ x, y })
  128. .with(Renderable{
  129. glyph: rltk::to_cp437('¡'),
  130. fg: RGB::named(rltk::MAGENTA),
  131. bg: RGB::named(rltk::BLACK),
  132. render_order: 2
  133. })
  134. .with(Name{ name : "Health Potion".to_string() })
  135. .with(Item{})
  136. .with(Consumable{})
  137. .with(ProvidesHealing{ heal_amount: 8 })
  138. .marked::<SimpleMarker<SerializeMe>>()
  139. .build();
  140. }
  141. fn magic_missile_scroll(ecs: &mut World, x: i32, y: i32) {
  142. ecs.create_entity()
  143. .with(Position{ x, y })
  144. .with(Renderable{
  145. glyph: rltk::to_cp437(')'),
  146. fg: RGB::named(rltk::CYAN),
  147. bg: RGB::named(rltk::BLACK),
  148. render_order: 2
  149. })
  150. .with(Name{ name : "Magic Missile Scroll".to_string() })
  151. .with(Item{})
  152. .with(Consumable{})
  153. .with(Ranged{ range: 6 })
  154. .with(InflictsDamage{ damage: 20 })
  155. .marked::<SimpleMarker<SerializeMe>>()
  156. .build();
  157. }
  158. fn fireball_scroll(ecs: &mut World, x: i32, y: i32) {
  159. ecs.create_entity()
  160. .with(Position{ x, y })
  161. .with(Renderable{
  162. glyph: rltk::to_cp437(')'),
  163. fg: RGB::named(rltk::ORANGE),
  164. bg: RGB::named(rltk::BLACK),
  165. render_order: 2
  166. })
  167. .with(Name{ name : "Fireball Scroll".to_string() })
  168. .with(Item{})
  169. .with(Consumable{})
  170. .with(Ranged{ range: 6 })
  171. .with(InflictsDamage{ damage: 20 })
  172. .with(AreaOfEffect{ radius: 3 })
  173. .marked::<SimpleMarker<SerializeMe>>()
  174. .build();
  175. }
  176. fn confusion_scroll(ecs: &mut World, x: i32, y: i32) {
  177. ecs.create_entity()
  178. .with(Position{ x, y })
  179. .with(Renderable{
  180. glyph: rltk::to_cp437(')'),
  181. fg: RGB::named(rltk::PINK),
  182. bg: RGB::named(rltk::BLACK),
  183. render_order: 2
  184. })
  185. .with(Name{ name : "Confusion Scroll".to_string() })
  186. .with(Item{})
  187. .with(Consumable{})
  188. .with(Ranged{ range: 6 })
  189. .with(Confusion{ turns: 4 })
  190. .marked::<SimpleMarker<SerializeMe>>()
  191. .build();
  192. }
  193. fn dagger(ecs: &mut World, x: i32, y: i32) {
  194. ecs.create_entity()
  195. .with(Position{ x, y })
  196. .with(Renderable{
  197. glyph: rltk::to_cp437('/'),
  198. fg: RGB::named(rltk::CYAN),
  199. bg: RGB::named(rltk::BLACK),
  200. render_order: 2
  201. })
  202. .with(Name{ name : "Dagger".to_string() })
  203. .with(Item{})
  204. .with(Equippable{ slot: EquipmentSlot::Melee })
  205. .with(MeleePowerBonus{ power: 2 })
  206. .marked::<SimpleMarker<SerializeMe>>()
  207. .build();
  208. }
  209. fn shield(ecs: &mut World, x: i32, y: i32) {
  210. ecs.create_entity()
  211. .with(Position{ x, y })
  212. .with(Renderable{
  213. glyph: rltk::to_cp437('('),
  214. fg: RGB::named(rltk::CYAN),
  215. bg: RGB::named(rltk::BLACK),
  216. render_order: 2
  217. })
  218. .with(Name{ name : "Shield".to_string() })
  219. .with(Item{})
  220. .with(Equippable{ slot: EquipmentSlot::Shield })
  221. .with(DefenseBonus{ defense: 1 })
  222. .marked::<SimpleMarker<SerializeMe>>()
  223. .build();
  224. }
  225. fn longsword(ecs: &mut World, x: i32, y: i32) {
  226. ecs.create_entity()
  227. .with(Position{ x, y })
  228. .with(Renderable{
  229. glyph: rltk::to_cp437('/'),
  230. fg: RGB::named(rltk::YELLOW),
  231. bg: RGB::named(rltk::BLACK),
  232. render_order: 2
  233. })
  234. .with(Name{ name : "Longsword".to_string() })
  235. .with(Item{})
  236. .with(Equippable{ slot: EquipmentSlot::Melee })
  237. .with(MeleePowerBonus{ power: 4 })
  238. .marked::<SimpleMarker<SerializeMe>>()
  239. .build();
  240. }
  241. fn tower_shield(ecs: &mut World, x: i32, y: i32) {
  242. ecs.create_entity()
  243. .with(Position{ x, y })
  244. .with(Renderable{
  245. glyph: rltk::to_cp437('('),
  246. fg: RGB::named(rltk::YELLOW),
  247. bg: RGB::named(rltk::BLACK),
  248. render_order: 2
  249. })
  250. .with(Name{ name : "Tower Shield".to_string() })
  251. .with(Item{})
  252. .with(Equippable{ slot: EquipmentSlot::Shield })
  253. .with(DefenseBonus{ defense: 3 })
  254. .marked::<SimpleMarker<SerializeMe>>()
  255. .build();
  256. }
  257. fn rations(ecs: &mut World, x: i32, y: i32) {
  258. ecs.create_entity()
  259. .with(Position{ x, y })
  260. .with(Renderable{
  261. glyph: rltk::to_cp437('%'),
  262. fg: RGB::named(rltk::GREEN),
  263. bg: RGB::named(rltk::BLACK),
  264. render_order: 2
  265. })
  266. .with(Name{ name : "Rations".to_string() })
  267. .with(Item{})
  268. .with(ProvidesFood{})
  269. .with(Consumable{})
  270. .marked::<SimpleMarker<SerializeMe>>()
  271. .build();
  272. }
  273. fn magic_mapping_scroll(ecs: &mut World, x: i32, y: i32) {
  274. ecs.create_entity()
  275. .with(Position{ x, y })
  276. .with(Renderable{
  277. glyph: rltk::to_cp437(')'),
  278. fg: RGB::named(rltk::CYAN3),
  279. bg: RGB::named(rltk::BLACK),
  280. render_order: 2
  281. })
  282. .with(Name{ name : "Scroll of Magic Mapping".to_string() })
  283. .with(Item{})
  284. .with(MagicMapper{})
  285. .with(Consumable{})
  286. .marked::<SimpleMarker<SerializeMe>>()
  287. .build();
  288. }
  289. fn bear_trap(ecs: &mut World, x: i32, y: i32) {
  290. ecs.create_entity()
  291. .with(Position{ x, y })
  292. .with(Renderable{
  293. glyph: rltk::to_cp437('^'),
  294. fg: RGB::named(rltk::RED),
  295. bg: RGB::named(rltk::BLACK),
  296. render_order: 2
  297. })
  298. .with(Name{ name : "Bear Trap".to_string() })
  299. .with(Hidden{})
  300. .with(EntryTrigger{})
  301. .with(InflictsDamage{ damage: 6 })
  302. .with(SingleActivation{})
  303. .marked::<SimpleMarker<SerializeMe>>()
  304. .build();
  305. }