map.rs 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. use std::cmp::{max, min};
  2. use crate::rect::Rect;
  3. use specs::prelude::*;
  4. use rltk::{Algorithm2D, BaseMap, Console, Point, RandomNumberGenerator, Rltk, RGB};
  5. const MAPWIDTH: usize = 80;
  6. const MAPHEIGHT: usize = 43;
  7. const MAPCOUNT: usize = MAPHEIGHT * MAPWIDTH;
  8. #[derive(PartialEq, Copy, Clone)]
  9. pub enum TileType {
  10. Wall,
  11. Floor,
  12. }
  13. #[derive(Default)]
  14. pub struct Map {
  15. pub tiles: Vec<TileType>,
  16. pub rooms: Vec<Rect>,
  17. pub width: i32,
  18. pub height: i32,
  19. pub revealed_tiles: Vec<bool>,
  20. pub visible_tiles: Vec<bool>,
  21. pub blocked: Vec<bool>,
  22. pub tile_content: Vec<Vec<Entity>>,
  23. }
  24. impl Map {
  25. pub fn xy_idx(&self, x: i32, y: i32) -> usize {
  26. (y as usize * self.width as usize) + x as usize
  27. }
  28. fn apply_room_to_map(&mut self, room: &Rect) {
  29. for y in room.y1 + 1..=room.y2 {
  30. for x in room.x1 + 1..=room.x2 {
  31. let idx = self.xy_idx(x, y);
  32. self.tiles[idx] = TileType::Floor;
  33. }
  34. }
  35. }
  36. fn apply_horizontal_tunnel(&mut self, x1: i32, x2: i32, y: i32) {
  37. for x in min(x1, x2)..=max(x1, x2) {
  38. let idx = self.xy_idx(x, y);
  39. if idx > 0 && idx < self.width as usize * self.height as usize {
  40. self.tiles[idx as usize] = TileType::Floor;
  41. }
  42. }
  43. }
  44. fn apply_vertical_tunnel(&mut self, y1: i32, y2: i32, x: i32) {
  45. for y in min(y1, y2)..=max(y1, y2) {
  46. let idx = self.xy_idx(x, y);
  47. if idx > 0 && idx < self.width as usize * self.height as usize {
  48. self.tiles[idx as usize] = TileType::Floor;
  49. }
  50. }
  51. }
  52. fn is_exit_valid(&self, x: i32, y: i32) -> bool {
  53. if x < 1 || x > self.width - 1 || y < 1 || y > self.height - 1 {
  54. return false;
  55. }
  56. let idx = (y * self.width) + x;
  57. !self.blocked[idx as usize]
  58. }
  59. pub fn populate_blocked(&mut self) {
  60. for (i, tile) in self.tiles.iter_mut().enumerate() {
  61. self.blocked[i] = *tile == TileType::Wall;
  62. }
  63. }
  64. pub fn clear_content_index(&mut self) {
  65. for content in self.tile_content.iter_mut() {
  66. content.clear();
  67. }
  68. }
  69. /// Makes a new map using the algorithm from http://rogueliketutorials.com/tutorials/tcod/part-3/
  70. /// This gives a handful of random rooms and corridors joining them together.
  71. pub fn new_map_rooms_and_corridors() -> Map {
  72. let mut map = Map {
  73. tiles: vec![TileType::Wall; MAPCOUNT],
  74. rooms: Vec::new(),
  75. width: MAPWIDTH as i32,
  76. height: MAPHEIGHT as i32,
  77. revealed_tiles: vec![false; MAPCOUNT],
  78. visible_tiles: vec![false; MAPCOUNT],
  79. blocked: vec![false; MAPCOUNT],
  80. tile_content: vec![Vec::new(); MAPCOUNT],
  81. };
  82. const MAX_ROOMS: i32 = 30;
  83. const MIN_SIZE: i32 = 6;
  84. const MAX_SIZE: i32 = 10;
  85. let mut rng = RandomNumberGenerator::new();
  86. for _i in 0..MAX_ROOMS {
  87. let w = rng.range(MIN_SIZE, MAX_SIZE);
  88. let h = rng.range(MIN_SIZE, MAX_SIZE);
  89. let x = rng.roll_dice(1, map.width - w - 1) - 1;
  90. let y = rng.roll_dice(1, map.height - h - 1) - 1;
  91. let new_room = Rect::new(x, y, w, h);
  92. let mut ok = true;
  93. for other_room in map.rooms.iter() {
  94. if new_room.intersect(other_room) {
  95. ok = false
  96. }
  97. }
  98. if ok {
  99. map.apply_room_to_map(&new_room);
  100. if !map.rooms.is_empty() {
  101. let (new_x, new_y) = new_room.center();
  102. let (prev_x, prev_y) = map.rooms[map.rooms.len() - 1].center();
  103. if rng.range(0, 1) == 1 {
  104. map.apply_horizontal_tunnel(prev_x, new_x, prev_y);
  105. map.apply_vertical_tunnel(prev_y, new_y, new_x);
  106. } else {
  107. map.apply_vertical_tunnel(prev_y, new_y, prev_x);
  108. map.apply_horizontal_tunnel(prev_x, new_x, new_y);
  109. }
  110. }
  111. map.rooms.push(new_room);
  112. }
  113. }
  114. map
  115. }
  116. pub fn get_attackable<'a>(
  117. &self,
  118. target: usize,
  119. stats: &ReadStorage<'a, crate::components::CombatStats>,
  120. ) -> Option<Entity> {
  121. for potential in self.tile_content[target].iter() {
  122. if stats.contains(*potential) {
  123. return Some(*potential)
  124. }
  125. }
  126. None
  127. }
  128. }
  129. impl BaseMap for Map {
  130. fn is_opaque(&self, idx: i32) -> bool {
  131. self.tiles[idx as usize] == TileType::Wall
  132. }
  133. fn get_available_exits(&self, idx: i32) -> Vec<(i32, f32)> {
  134. let mut exits: Vec<(i32, f32)> = Vec::new();
  135. let x = idx % self.width;
  136. let y = idx / self.width;
  137. // Cardinal directions
  138. if self.is_exit_valid(x - 1, y) {
  139. exits.push((idx - 1, 1.0))
  140. };
  141. if self.is_exit_valid(x + 1, y) {
  142. exits.push((idx + 1, 1.0))
  143. };
  144. if self.is_exit_valid(x, y - 1) {
  145. exits.push((idx - self.width, 1.0))
  146. };
  147. if self.is_exit_valid(x, y + 1) {
  148. exits.push((idx + self.width, 1.0))
  149. };
  150. // Diagonals
  151. if self.is_exit_valid(x - 1, y - 1) {
  152. exits.push(((idx - self.width) - 1, 1.45));
  153. }
  154. if self.is_exit_valid(x + 1, y - 1) {
  155. exits.push(((idx - self.width) + 1, 1.45));
  156. }
  157. if self.is_exit_valid(x - 1, y + 1) {
  158. exits.push(((idx + self.width) - 1, 1.45));
  159. }
  160. if self.is_exit_valid(x + 1, y + 1) {
  161. exits.push(((idx + self.width) + 1, 1.45));
  162. }
  163. exits
  164. }
  165. fn get_pathing_distance(&self, idx1: i32, idx2: i32) -> f32 {
  166. let p1 = Point::new(idx1 % self.width, idx1 / self.width);
  167. let p2 = Point::new(idx2 % self.width, idx2 / self.width);
  168. rltk::DistanceAlg::Pythagoras.distance2d(p1, p2)
  169. }
  170. }
  171. impl Algorithm2D for Map {
  172. fn point2d_to_index(&self, pt: Point) -> i32 {
  173. (pt.y * self.width) + pt.x
  174. }
  175. fn index_to_point2d(&self, idx: i32) -> Point {
  176. Point {
  177. x: idx % self.width,
  178. y: idx / self.width,
  179. }
  180. }
  181. }
  182. pub fn draw_map(ecs: &World, ctx: &mut Rltk) {
  183. let map = ecs.fetch::<Map>();
  184. let mut y = 0;
  185. let mut x = 0;
  186. for (idx, tile) in map.tiles.iter().enumerate() {
  187. // Render a tile depending upon the tile type
  188. if map.revealed_tiles[idx] {
  189. let glyph;
  190. let mut fg;
  191. match tile {
  192. TileType::Floor => {
  193. glyph = rltk::to_cp437('.');
  194. fg = RGB::from_f32(0.0, 0.5, 0.5);
  195. }
  196. TileType::Wall => {
  197. glyph = rltk::to_cp437('#');
  198. fg = RGB::from_f32(0., 1.0, 0.);
  199. }
  200. }
  201. if !map.visible_tiles[idx] {
  202. fg = fg.to_greyscale()
  203. }
  204. ctx.set(x, y, fg, RGB::from_f32(0., 0., 0.), glyph);
  205. }
  206. // Move the coordinates
  207. x += 1;
  208. if x > MAPWIDTH as i32 - 1 {
  209. x = 0;
  210. y += 1;
  211. }
  212. }
  213. }