bsp_dungeon.rs 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. use super::{MapBuilder, Map, Rect, apply_room_to_map,
  2. TileType, Position, spawner};
  3. use rltk::RandomNumberGenerator;
  4. use specs::prelude::*;
  5. pub struct BspDungeonBuilder {}
  6. fn add_subrects(rects : &mut Vec<Rect>, rect : Rect) {
  7. let width = i32::abs(rect.x1 - rect.x2);
  8. let height = i32::abs(rect.y1 - rect.y2);
  9. let half_width = i32::max(width / 2, 1);
  10. let half_height = i32::max(height / 2, 1);
  11. rects.push(Rect::new( rect.x1, rect.y1, half_width, half_height ));
  12. rects.push(Rect::new( rect.x1, rect.y1 + half_height, half_width, half_height ));
  13. rects.push(Rect::new( rect.x1 + half_width, rect.y1, half_width, half_height ));
  14. rects.push(Rect::new( rect.x1 + half_width, rect.y1 + half_height, half_width, half_height ));
  15. }
  16. fn get_random_rect(rects : &mut Vec<Rect>, rng : &mut RandomNumberGenerator) -> Rect {
  17. if rects.len() == 1 { return rects[0]; }
  18. let idx = (rng.roll_dice(1, rects.len() as i32)-1) as usize;
  19. rects[idx]
  20. }
  21. fn get_random_sub_rect(rect : Rect, rng : &mut RandomNumberGenerator) -> Rect {
  22. let mut result = rect;
  23. let rect_width = i32::abs(rect.x1 - rect.x2);
  24. let rect_height = i32::abs(rect.y1 - rect.y2);
  25. let w = i32::max(3, rng.roll_dice(1, i32::min(rect_width, 10))-1) + 1;
  26. let h = i32::max(3, rng.roll_dice(1, i32::min(rect_height, 10))-1) + 1;
  27. result.x1 += rng.roll_dice(1, 6)-1;
  28. result.y1 += rng.roll_dice(1, 6)-1;
  29. result.x2 = result.x1 + w;
  30. result.y2 = result.y1 + h;
  31. result
  32. }
  33. fn is_possible(map : &mut Map, mut rect : Rect) -> bool {
  34. if rect.x1 > 1 {
  35. rect.x1 -= 2;
  36. rect.x2 += 2;
  37. }
  38. if rect.y1 > 1 {
  39. rect.y1 -= 2;
  40. rect.y1 += 2;
  41. }
  42. for y in rect.y1 ..= rect.y2 {
  43. for x in rect.x1 ..= rect.x2 {
  44. if x > map.width-1 { return false; }
  45. if y > map.height-1 { return false; }
  46. if x < 0 { return false; }
  47. if y < 0 { return false; }
  48. let idx = map.xy_idx(x, y);
  49. if map.tiles[idx] != TileType::Wall { return false; }
  50. }
  51. }
  52. true
  53. }
  54. fn draw_corridor(map : &mut Map, x1:i32, y1:i32, x2:i32, y2:i32) {
  55. let mut x = x1;
  56. let mut y = y1;
  57. while x != x2 || y != y2 {
  58. if x < x2 {
  59. x += 1;
  60. } else if x > x2 {
  61. x -= 1;
  62. } else if y < y2 {
  63. y += 1;
  64. } else if y > y2 {
  65. y -= 1;
  66. }
  67. let idx = map.xy_idx(x, y);
  68. map.tiles[idx] = TileType::Floor;
  69. }
  70. }
  71. impl MapBuilder for BspDungeonBuilder {
  72. fn build(new_depth: i32) -> (Map, Position) {
  73. let mut map = Map::new(new_depth);
  74. let mut rng = RandomNumberGenerator::new();
  75. let mut rects : Vec<Rect> = Vec::new(); // Vector to hold our rectangles as we divide
  76. rects.push( Rect::new(2, 2, map.width-5, map.height-5) ); // Start with a single map-sized rectangle
  77. let first_room = rects[0];
  78. add_subrects(&mut rects, first_room); // Divide the first room
  79. // Up to 240 times, we get a random rectangle and divide it. If its possible to squeeze a
  80. // room in there, we place it and add it to the rooms list.
  81. let mut n_rooms = 0;
  82. while n_rooms < 240 {
  83. let rect = get_random_rect(&mut rects, &mut rng);
  84. let candidate = get_random_sub_rect(rect, &mut rng);
  85. if is_possible(&mut map, candidate) {
  86. apply_room_to_map(&mut map, &candidate);
  87. map.rooms.push(candidate);
  88. add_subrects(&mut rects, rect);
  89. }
  90. n_rooms += 1;
  91. }
  92. // Now we sort the rooms
  93. map.rooms.sort_by(|a,b| a.x1.cmp(&b.x1) );
  94. // Now we want corridors
  95. for i in 0..map.rooms.len()-1 {
  96. let room = map.rooms[i];
  97. let next_room = map.rooms[i+1];
  98. let start_x = room.x1 + (rng.roll_dice(1, i32::abs(room.x1 - room.x2))-1);
  99. let start_y = room.y1 + (rng.roll_dice(1, i32::abs(room.y1 - room.y2))-1);
  100. let end_x = next_room.x1 + (rng.roll_dice(1, i32::abs(next_room.x1 - next_room.x2))-1);
  101. let end_y = next_room.y1 + (rng.roll_dice(1, i32::abs(next_room.y1 - next_room.y2))-1);
  102. draw_corridor(&mut map, start_x, start_y, end_x, end_y);
  103. }
  104. let player_start = map.rooms[0].center();
  105. let stairs = map.rooms[map.rooms.len()-1].center();
  106. let stairs_idx = map.xy_idx(stairs.0, stairs.1);
  107. map.tiles[stairs_idx] = TileType::DownStairs;
  108. (map, Position{ x : player_start.0, y : player_start.1 })
  109. }
  110. fn spawn(map : &Map, ecs : &mut World, new_depth: i32) {
  111. for room in map.rooms.iter().skip(1) {
  112. spawner::spawn_room(ecs, room, new_depth);
  113. }
  114. }
  115. }