From 2d33c90af8dbc8329a1b457a53c44f3ea8a32dff Mon Sep 17 00:00:00 2001 From: Llywelwyn Date: Sat, 23 Sep 2023 11:12:28 +0100 Subject: [PATCH 01/10] fixes infini-dungeon difficulty --- src/map_builders/mod.rs | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/map_builders/mod.rs b/src/map_builders/mod.rs index 81045c6..7b38efe 100644 --- a/src/map_builders/mod.rs +++ b/src/map_builders/mod.rs @@ -438,21 +438,19 @@ pub fn random_builder( } pub fn level_builder( - new_id: i32, + id: i32, rng: &mut RandomNumberGenerator, width: i32, height: i32, initial_player_level: i32 ) -> BuilderChain { - // TODO: With difficulty and ID/depth decoupled, this can be used for branches later. - let difficulty = new_id; - match new_id { + match id { ID_OVERMAP => overmap_builder(), - ID_TOWN => town_builder(new_id, rng, width, height, 0, initial_player_level), - ID_TOWN2 => forest_builder(new_id, rng, width, height, 1, initial_player_level), + ID_TOWN => town_builder(id, rng, width, height, 0, initial_player_level), + ID_TOWN2 => forest_builder(id, rng, width, height, 1, initial_player_level), ID_TOWN3 => random_builder( - new_id, + id, rng, width, height, @@ -462,25 +460,25 @@ pub fn level_builder( true, BuildType::Room ), - _ if new_id >= ID_INFINITE => + _ if id >= ID_INFINITE => random_builder( - new_id, + id, rng, width, height, - difficulty, - new_id - ID_INFINITE + 1, + 4 + diff(ID_INFINITE, id), + 1 + diff(ID_INFINITE, id), initial_player_level, false, BuildType::Room ), - _ => + _ => // This should be unreachable!() eventually. Right now it's reachable with the debug/cheat menu. It should not be in normal gameplay. random_builder( - new_id, + id, rng, width, height, - difficulty, + 1, 404, initial_player_level, false, @@ -488,3 +486,7 @@ pub fn level_builder( ), } } + +fn diff(branch_id: i32, lvl_id: i32) -> i32 { + return lvl_id - branch_id; +} From fa3b906dce3cc3c3d187de9d23229a53786f2e6e Mon Sep 17 00:00:00 2001 From: Llywelwyn Date: Sat, 23 Sep 2023 11:21:34 +0100 Subject: [PATCH 02/10] adds article to ID morgue msg --- src/gamelog/events.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gamelog/events.rs b/src/gamelog/events.rs index bef5ff6..3e0006f 100644 --- a/src/gamelog/events.rs +++ b/src/gamelog/events.rs @@ -126,7 +126,7 @@ pub fn record_event(event: EVENT) { new_event = format!("Discovered {}", name); } EVENT::Identified(name) => { - new_event = format!("Identified {}", name); + new_event = format!("Identified {}", crate::gui::with_article(name)); } EVENT::PlayerDied(str) => { // Generating the String is handled in the death effect, to avoid passing the ecs here. From 1fa7432dfebd79638ca4212196a9f221e47d0ffc Mon Sep 17 00:00:00 2001 From: Llywelwyn Date: Sun, 1 Oct 2023 20:56:46 +0100 Subject: [PATCH 03/10] room accretion - initial --- src/map_builders/mod.rs | 2 + src/map_builders/room_accretion/consts.rs | 110 +++++++++++++++ src/map_builders/room_accretion/mod.rs | 161 ++++++++++++++++++++++ 3 files changed, 273 insertions(+) create mode 100644 src/map_builders/room_accretion/consts.rs create mode 100644 src/map_builders/room_accretion/mod.rs diff --git a/src/map_builders/mod.rs b/src/map_builders/mod.rs index 7b38efe..70a3c0f 100644 --- a/src/map_builders/mod.rs +++ b/src/map_builders/mod.rs @@ -1,6 +1,8 @@ use super::{ spawner, Map, Position, Rect, TileType }; use bracket_lib::prelude::*; +mod room_accretion; +use room_accretion::RoomAccretionBuilder; mod bsp_dungeon; use bsp_dungeon::BspDungeonBuilder; mod bsp_interior; diff --git a/src/map_builders/room_accretion/consts.rs b/src/map_builders/room_accretion/consts.rs new file mode 100644 index 0000000..f4170df --- /dev/null +++ b/src/map_builders/room_accretion/consts.rs @@ -0,0 +1,110 @@ +use lazy_static::lazy_static; +use bracket_lib::prelude::*; + +pub enum Operator { + LessThan, + GreaterThan, + LessThanEqualTo, + GreaterThanEqualTo, + EqualTo, +} + +impl Operator { + pub fn eval(&self, a: i32, b: i32) -> bool { + match self { + Operator::LessThan => a < b, + Operator::GreaterThan => a > b, + Operator::LessThanEqualTo => a <= b, + Operator::GreaterThanEqualTo => a >= b, + Operator::EqualTo => a == b, + } + } + pub fn string(&self) -> &str { + match self { + Operator::LessThan => "<", + Operator::GreaterThan => ">", + Operator::LessThanEqualTo => "<=", + Operator::GreaterThanEqualTo => ">=", + Operator::EqualTo => "==", + } + } +} + +pub struct CellRules { + pub adjacent_type: i32, + pub into: i32, + pub operator: Operator, + pub n: i32, +} + +impl CellRules { + const fn new(adjacent_type: i32, into: i32, operator: Operator, n: i32) -> CellRules { + CellRules { + adjacent_type, + into, + operator, + n, + } + } +} + +lazy_static! { + pub static ref CA: Vec> = vec![ + vec![ + CellRules::new(1, 1, Operator::GreaterThanEqualTo, 5), + CellRules::new(0, 1, Operator::LessThan, 2) + ], + vec![CellRules::new(1, 1, Operator::GreaterThanEqualTo, 5)] + ]; +} + +#[derive(Debug, Copy, Clone)] +pub enum Direction { + NoDir = -1, + North = 0, + East = 1, + South = 2, + West = 3, +} + +impl Direction { + pub fn transform(&self) -> Point { + match self { + Direction::NoDir => unreachable!("Direction::NoDir should never be transformed"), + Direction::North => Point::new(0, -1), + Direction::East => Point::new(1, 0), + Direction::South => Point::new(0, 1), + Direction::West => Point::new(-1, 0), + } + } +} + +pub struct DirectionIterator { + current: Direction, +} + +impl DirectionIterator { + pub fn new() -> DirectionIterator { + DirectionIterator { + current: Direction::North, + } + } +} + +impl Iterator for DirectionIterator { + type Item = Direction; + fn next(&mut self) -> Option { + use Direction::*; + let next_direction = match self.current { + North => East, + East => South, + South => West, + West => { + return None; + } + NoDir => unreachable!("Direction::NoDir should never be iterated over."), + }; + self.current = next_direction; + Some(next_direction) + } +} diff --git a/src/map_builders/room_accretion/mod.rs b/src/map_builders/room_accretion/mod.rs new file mode 100644 index 0000000..2e003f1 --- /dev/null +++ b/src/map_builders/room_accretion/mod.rs @@ -0,0 +1,161 @@ +use super::{ BuilderMap, Map, InitialMapBuilder, TileType, Point }; +use bracket_lib::prelude::*; + +mod consts; +use consts::*; + +/// Room Accretion map builder. +pub struct RoomAccretionBuilder {} + +impl InitialMapBuilder for RoomAccretionBuilder { + #[allow(dead_code)] + fn build_map(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) { + self.build(rng, build_data); + } +} + +impl RoomAccretionBuilder { + /// Constructor for Room Accretion. + pub fn new() -> Box { + Box::new(RoomAccretionBuilder {}) + } + + fn build(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) { + // + } +} + +fn grid_with_dimensions(h: usize, w: usize, value: i32) -> Vec> { + let mut grid = Vec::with_capacity(h); + for _ in 0..h { + let row = vec![value; w]; + grid.push(row); + } + grid +} + +fn in_bounds(x: i32, y: i32, build_data: &BuilderMap) -> bool { + x > 0 && x < build_data.height && y > 0 && y < build_data.width +} + +fn draw_continuous_shape_on_grid( + room: &Vec>, + top_offset: usize, + left_offset: usize, + grid: &mut Vec> +) { + for row in 0..room.len() { + for col in 0..room[0].len() { + if room[row][col] != 0 { + let target_row = row + top_offset; + let target_col = col + left_offset; + if target_row < grid.len() && target_col < grid[0].len() { + grid[target_row][target_col] = room[row][col]; + } + } + } + } +} + +struct Coordinate { + pub location: Point, + pub value: i32, +} + +fn draw_individual_coordinates_on_grid(coordinates: &Vec, grid: &mut Vec>) { + for c in coordinates { + let x = c.location.x as usize; + let y = c.location.y as usize; + if y < grid.len() && x < grid[0].len() { + grid[y][x] = c.value; + } + } +} + +fn get_cell_neighbours( + cells: &Vec>, + row: usize, + col: usize, + h: usize, + w: usize +) -> Vec { + let mut neighbours = Vec::new(); + for x in row.saturating_sub(1)..=std::cmp::min(row + 1, h - 1) { + for y in col.saturating_sub(1)..=std::cmp::min(col + 1, w - 1) { + if x != row || y != col { + neighbours.push(cells[x][y]); + } + } + } + neighbours +} + +fn make_ca_room(rng: &mut RandomNumberGenerator) -> Vec> { + let width = rng.range(5, 10); + let height = rng.range(5, 10); + let mut cells = grid_with_dimensions(height, width, 0); + cells = cells + .into_iter() + .map(|row| { + row.into_iter() + .map(|_| if rng.roll_dice(1, 2) == 1 { 1 } else { 0 }) + .collect() + }) + .collect(); + + let transform_cell = |state: i32, neighbours: &Vec| -> i32 { + let rules: &[CellRules] = &CA[state as usize]; + let mut new_state = state; + for rule in rules { + let n_neighbours = neighbours + .iter() + .filter(|&&neighbour| neighbour == rule.adjacent_type) + .count(); + if rule.operator.eval(n_neighbours as i32, rule.n) { + new_state = rule.into; + } + } + new_state + }; + + for _ in 0..5 { + let mut new_cells = vec![vec![0; width]; height]; + for row in 0..height { + for col in 0..height { + let neighbours = get_cell_neighbours(&cells, row, col, height, width); + let new_state = transform_cell(cells[row][col], &neighbours); + new_cells[row][col] = new_state; + } + } + cells = new_cells; + } + + cells +} + +fn direction_of_door( + grid: Vec>, + row: usize, + col: usize, + build_data: &BuilderMap +) -> Direction { + if grid[row][col] != 0 { + return Direction::NoDir; + } + let mut solution = Direction::NoDir; + let mut dir_iter = DirectionIterator::new(); + for dir in &mut dir_iter { + let new_col = (col as i32) + dir.transform().x; + let new_row = (row as i32) + dir.transform().y; + let opp_col = (col as i32) - dir.transform().x; + let opp_row = (row as i32) - dir.transform().y; + if + in_bounds(new_col, new_row, &build_data) && + in_bounds(new_col, new_row, &build_data) && + grid[opp_row as usize][opp_col as usize] != 0 + { + solution = dir; + } + } + return solution; +} From 97ca3a25e339495c1a8cc3efc1aba49141203609 Mon Sep 17 00:00:00 2001 From: Llywelwyn Date: Mon, 2 Oct 2023 04:43:01 +0100 Subject: [PATCH 04/10] doors and door directions - RA --- src/map_builders/room_accretion/consts.rs | 5 +- src/map_builders/room_accretion/mod.rs | 66 ++++++++++++++++++++++- 2 files changed, 69 insertions(+), 2 deletions(-) diff --git a/src/map_builders/room_accretion/consts.rs b/src/map_builders/room_accretion/consts.rs index f4170df..ae3c434 100644 --- a/src/map_builders/room_accretion/consts.rs +++ b/src/map_builders/room_accretion/consts.rs @@ -1,6 +1,9 @@ use lazy_static::lazy_static; use bracket_lib::prelude::*; +pub const HEIGHT: usize = 64; +pub const WIDTH: usize = 64; + pub enum Operator { LessThan, GreaterThan, @@ -58,7 +61,7 @@ lazy_static! { ]; } -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq)] pub enum Direction { NoDir = -1, North = 0, diff --git a/src/map_builders/room_accretion/mod.rs b/src/map_builders/room_accretion/mod.rs index 2e003f1..0871cb3 100644 --- a/src/map_builders/room_accretion/mod.rs +++ b/src/map_builders/room_accretion/mod.rs @@ -134,7 +134,7 @@ fn make_ca_room(rng: &mut RandomNumberGenerator) -> Vec> { } fn direction_of_door( - grid: Vec>, + grid: &Vec>, row: usize, col: usize, build_data: &BuilderMap @@ -159,3 +159,67 @@ fn direction_of_door( } return solution; } + +#[derive(Copy, Clone, PartialEq)] +pub struct DoorSite { + pub x: i32, + pub y: i32, + pub dir: Direction, +} + +fn choose_random_door_site( + room: Vec>, + rng: &mut RandomNumberGenerator, + build_data: &BuilderMap +) -> Vec { + let mut grid = grid_with_dimensions(HEIGHT, WIDTH, 0); + let mut door_sites: Vec = Vec::new(); + const LEFT_OFFSET: usize = ((WIDTH as f32) / 2.0) as usize; + const TOP_OFFSET: usize = ((HEIGHT as f32) / 2.0) as usize; + draw_continuous_shape_on_grid(&room, TOP_OFFSET, LEFT_OFFSET, &mut grid); + for row in 0..HEIGHT { + for col in 0..WIDTH { + if grid[row][col] == 0 { + let door_dir = direction_of_door(&grid, row, col, &build_data); + if door_dir == Direction::NoDir { + continue; + } + let mut door_failed = false; + let (mut trace_row, mut trace_col) = ( + (row as i32) + door_dir.transform().y, + (col as i32) + door_dir.transform().x, + ); + let mut i = 0; + while i < 10 && in_bounds(trace_row, trace_col, &build_data) && !door_failed { + if grid[trace_row as usize][trace_col as usize] != 0 { + door_failed = true; + } + trace_col += door_dir.transform().x; + trace_row += door_dir.transform().y; + i += 1; + } + if !door_failed { + // May need more information here. + door_sites.push(DoorSite { + x: col as i32, + y: row as i32, + dir: door_dir, + }); + } + } + } + } + let mut chosen_doors: Vec = Vec::new(); + let mut dir_iter = DirectionIterator::new(); + for dir in &mut dir_iter { + let doors_facing_this_dir: Vec<&DoorSite> = door_sites + .iter() + .filter(|&door| door.dir == dir) + .collect(); + if !doors_facing_this_dir.is_empty() { + let index = rng.range(0, doors_facing_this_dir.len()); + chosen_doors.push(*doors_facing_this_dir[index]); + } + } + chosen_doors +} From 7a27321bec72e5401794291ed22e047d36b9aeee Mon Sep 17 00:00:00 2001 From: Llywelwyn Date: Mon, 2 Oct 2023 07:00:28 +0100 Subject: [PATCH 05/10] initial tweaks - starting room w/ corridors + doors --- src/config/mod.rs | 2 +- src/map_builders/mod.rs | 9 +- src/map_builders/room_accretion/consts.rs | 13 +- src/map_builders/room_accretion/mod.rs | 167 +++++++++++++++++++--- 4 files changed, 168 insertions(+), 23 deletions(-) diff --git a/src/config/mod.rs b/src/config/mod.rs index f86c5df..5309151 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -34,7 +34,7 @@ impl Default for Config { fn default() -> Self { Config { logging: LogConfig { - show_mapgen: false, + show_mapgen: true, log_combat: false, log_spawning: false, log_ticks: false, diff --git a/src/map_builders/mod.rs b/src/map_builders/mod.rs index 70a3c0f..b9dcbfe 100644 --- a/src/map_builders/mod.rs +++ b/src/map_builders/mod.rs @@ -447,7 +447,7 @@ pub fn level_builder( initial_player_level: i32 ) -> BuilderChain { match id { - ID_OVERMAP => overmap_builder(), + ID_OVERMAP => room_accretion(), ID_TOWN => town_builder(id, rng, width, height, 0, initial_player_level), ID_TOWN2 => forest_builder(id, rng, width, height, 1, initial_player_level), ID_TOWN3 => @@ -492,3 +492,10 @@ pub fn level_builder( fn diff(branch_id: i32, lvl_id: i32) -> i32 { return lvl_id - branch_id; } + +fn room_accretion() -> BuilderChain { + let mut builder = BuilderChain::new(false, 110, 64, 64, 0, "room_accretion", "accretion", 0, 1); + builder.start_with(RoomAccretionBuilder::new()); + builder.with(AreaStartingPosition::new(XStart::CENTRE, YStart::CENTRE)); + builder +} diff --git a/src/map_builders/room_accretion/consts.rs b/src/map_builders/room_accretion/consts.rs index ae3c434..9fdedb2 100644 --- a/src/map_builders/room_accretion/consts.rs +++ b/src/map_builders/room_accretion/consts.rs @@ -3,6 +3,11 @@ use bracket_lib::prelude::*; pub const HEIGHT: usize = 64; pub const WIDTH: usize = 64; +pub const HALLWAY_CHANCE: f32 = 0.5; +pub const VERTICAL_CORRIDOR_MIN_LENGTH: i32 = 2; +pub const VERTICAL_CORRIDOR_MAX_LENGTH: i32 = 9; +pub const HORIZONTAL_CORRIDOR_MIN_LENGTH: i32 = 5; +pub const HORIZONTAL_CORRIDOR_MAX_LENGTH: i32 = 15; pub enum Operator { LessThan, @@ -53,11 +58,11 @@ impl CellRules { lazy_static! { pub static ref CA: Vec> = vec![ + vec![CellRules::new(1, 1, Operator::GreaterThanEqualTo, 4)], vec![ - CellRules::new(1, 1, Operator::GreaterThanEqualTo, 5), - CellRules::new(0, 1, Operator::LessThan, 2) - ], - vec![CellRules::new(1, 1, Operator::GreaterThanEqualTo, 5)] + CellRules::new(0, 0, Operator::GreaterThanEqualTo, 5), + CellRules::new(1, 0, Operator::LessThan, 2) + ] ]; } diff --git a/src/map_builders/room_accretion/mod.rs b/src/map_builders/room_accretion/mod.rs index 0871cb3..648317f 100644 --- a/src/map_builders/room_accretion/mod.rs +++ b/src/map_builders/room_accretion/mod.rs @@ -21,21 +21,16 @@ impl RoomAccretionBuilder { } fn build(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) { - // + accrete_rooms(rng, build_data); } } fn grid_with_dimensions(h: usize, w: usize, value: i32) -> Vec> { - let mut grid = Vec::with_capacity(h); - for _ in 0..h { - let row = vec![value; w]; - grid.push(row); - } - grid + vec![vec![value; w]; h] } -fn in_bounds(x: i32, y: i32, build_data: &BuilderMap) -> bool { - x > 0 && x < build_data.height && y > 0 && y < build_data.width +fn in_bounds(row: i32, col: i32, build_data: &BuilderMap) -> bool { + row > 0 && row < build_data.height && col > 0 && col < build_data.width } fn draw_continuous_shape_on_grid( @@ -87,10 +82,11 @@ fn get_cell_neighbours( } } } + console::log(&format!("neighbours: {:?}", neighbours)); neighbours } -fn make_ca_room(rng: &mut RandomNumberGenerator) -> Vec> { +fn make_ca_room(rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) -> Vec> { let width = rng.range(5, 10); let height = rng.range(5, 10); let mut cells = grid_with_dimensions(height, width, 0); @@ -121,7 +117,7 @@ fn make_ca_room(rng: &mut RandomNumberGenerator) -> Vec> { for _ in 0..5 { let mut new_cells = vec![vec![0; width]; height]; for row in 0..height { - for col in 0..height { + for col in 0..width { let neighbours = get_cell_neighbours(&cells, row, col, height, width); let new_state = transform_cell(cells[row][col], &neighbours); new_cells[row][col] = new_state; @@ -150,8 +146,8 @@ fn direction_of_door( let opp_col = (col as i32) - dir.transform().x; let opp_row = (row as i32) - dir.transform().y; if - in_bounds(new_col, new_row, &build_data) && - in_bounds(new_col, new_row, &build_data) && + in_bounds(new_row, new_col, &build_data) && + in_bounds(opp_row, opp_col, &build_data) && grid[opp_row as usize][opp_col as usize] != 0 { solution = dir; @@ -160,7 +156,7 @@ fn direction_of_door( return solution; } -#[derive(Copy, Clone, PartialEq)] +#[derive(Debug, Copy, Clone, PartialEq)] pub struct DoorSite { pub x: i32, pub y: i32, @@ -171,7 +167,7 @@ fn choose_random_door_site( room: Vec>, rng: &mut RandomNumberGenerator, build_data: &BuilderMap -) -> Vec { +) -> Vec> { let mut grid = grid_with_dimensions(HEIGHT, WIDTH, 0); let mut door_sites: Vec = Vec::new(); const LEFT_OFFSET: usize = ((WIDTH as f32) / 2.0) as usize; @@ -209,7 +205,7 @@ fn choose_random_door_site( } } } - let mut chosen_doors: Vec = Vec::new(); + let mut chosen_doors: Vec> = vec![None; 4]; let mut dir_iter = DirectionIterator::new(); for dir in &mut dir_iter { let doors_facing_this_dir: Vec<&DoorSite> = door_sites @@ -218,8 +214,145 @@ fn choose_random_door_site( .collect(); if !doors_facing_this_dir.is_empty() { let index = rng.range(0, doors_facing_this_dir.len()); - chosen_doors.push(*doors_facing_this_dir[index]); + chosen_doors[dir as usize] = Some(*doors_facing_this_dir[index]); } } chosen_doors } + +fn shuffle(list: &mut Vec, rng: &mut RandomNumberGenerator) { + let len = list.len(); + for i in (1..len).rev() { + let j = rng.range(0, i + 1); + list.swap(i, j); + } +} + +fn attach_hallway_to( + door_sites: &mut Vec>, + hyperspace: &mut Vec>, + rng: &mut RandomNumberGenerator, + build_data: &BuilderMap +) { + let mut directions = vec![Direction::North, Direction::East, Direction::South, Direction::West]; + shuffle(&mut directions, rng); + let mut hallway_dir: Direction = Direction::NoDir; + for i in 0..4 { + hallway_dir = directions[i]; + console::log( + &format!( + "i: {:?} | hallway_dir: {:?} (as usize: {:?}) | door_sites[hallway_dir]: {:?}", + i, + hallway_dir, + hallway_dir as usize, + door_sites[hallway_dir as usize] + ) + ); + if + door_sites[hallway_dir as usize].is_some() && + in_bounds( + door_sites[hallway_dir as usize].unwrap().y + + hallway_dir.transform().y * VERTICAL_CORRIDOR_MAX_LENGTH, + door_sites[hallway_dir as usize].unwrap().x + + hallway_dir.transform().x * HORIZONTAL_CORRIDOR_MAX_LENGTH, + &build_data + ) + { + break; + } + } + let transform = hallway_dir.transform(); + let hallway_len: i32 = match hallway_dir { + Direction::NoDir => { + console::log("no hallway_dir"); + return; + } + Direction::North | Direction::South => + rng.range(VERTICAL_CORRIDOR_MIN_LENGTH, VERTICAL_CORRIDOR_MAX_LENGTH + 1), + Direction::East | Direction::West => + rng.range(HORIZONTAL_CORRIDOR_MIN_LENGTH, HORIZONTAL_CORRIDOR_MAX_LENGTH + 1), + }; + console::log(&format!("hallway_len: {:?}", hallway_len)); + let mut x = door_sites[hallway_dir as usize].unwrap().x; + let mut y = door_sites[hallway_dir as usize].unwrap().y; + for _i in 0..hallway_len { + if in_bounds(y, x, &build_data) { + hyperspace[y as usize][x as usize] = 1; // Dig out corridor. + } + x += transform.x; + y += transform.y; + } + let new_site = DoorSite { + x, + y, + dir: hallway_dir, + }; + console::log(&format!("new_site: {:?}", new_site)); + door_sites[hallway_dir as usize] = Some(new_site); // Move door to end of corridor. +} + +fn design_room_in_hyperspace( + rng: &mut RandomNumberGenerator, + build_data: &mut BuilderMap +) -> Vec> { + // Project onto hyperspace + let mut hyperspace = grid_with_dimensions(HEIGHT, WIDTH, 0); + let room_type = rng.range(0, 1); + let room = match room_type { + 0 => make_ca_room(rng, build_data), + _ => unreachable!("Invalid room type."), + }; + draw_continuous_shape_on_grid(&room, HEIGHT / 2, WIDTH / 2, &mut hyperspace); + let mut door_sites = choose_random_door_site(room, rng, &build_data); + let roll: f32 = rng.rand(); + if roll < HALLWAY_CHANCE { + attach_hallway_to(&mut door_sites, &mut hyperspace, rng, &build_data); + } + let coords: Vec = door_sites + .iter() + .filter(|&door| door.is_some()) + .map(|&door| Coordinate { + location: Point::new(door.unwrap().x, door.unwrap().y), + value: 2, + }) + .collect(); + draw_individual_coordinates_on_grid(&coords, &mut hyperspace); + hyperspace +} + +fn map_i32_to_tiletype(val: i32, build_data: &mut BuilderMap) -> TileType { + match val { + 0 => TileType::Wall, + 1 => TileType::Floor, + 2 => TileType::Floor, // With door. + _ => unreachable!("Unknown TileType"), + } +} + +fn flatten_hyperspace_into_dungeon( + hyperspace: Vec>, + build_data: &mut BuilderMap +) -> Vec { + let flattened_hyperspace: Vec = hyperspace.into_iter().flatten().collect(); + flattened_hyperspace + .into_iter() + .enumerate() + .map(|(idx, cell)| { + if cell != 0 { + match cell { + 2 => build_data.spawn_list.push((idx, "door".to_string())), + _ => {} + } + map_i32_to_tiletype(cell, build_data) + } else { + build_data.map.tiles[idx % (build_data.map.width as usize)] + } + }) + .collect() +} + +fn accrete_rooms(rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) { + let hyperspace = design_room_in_hyperspace(rng, build_data); + build_data.map.tiles = flatten_hyperspace_into_dungeon(hyperspace, build_data); + build_data.take_snapshot(); +} From 190543a3611c9cfa5b257edacf0ef7f7cea26d7b Mon Sep 17 00:00:00 2001 From: Llywelwyn Date: Mon, 2 Oct 2023 07:39:45 +0100 Subject: [PATCH 06/10] move all doors to the ends of corridors --- src/map_builders/room_accretion/consts.rs | 9 +++ src/map_builders/room_accretion/mod.rs | 81 +++++++++++++++++------ 2 files changed, 69 insertions(+), 21 deletions(-) diff --git a/src/map_builders/room_accretion/consts.rs b/src/map_builders/room_accretion/consts.rs index 9fdedb2..8c10d16 100644 --- a/src/map_builders/room_accretion/consts.rs +++ b/src/map_builders/room_accretion/consts.rs @@ -85,6 +85,15 @@ impl Direction { Direction::West => Point::new(-1, 0), } } + pub fn opposite_dir(&self) -> Direction { + match self { + Direction::NoDir => unreachable!("Direction::NoDir has no opposite."), + Direction::North => Direction::South, + Direction::East => Direction::West, + Direction::South => Direction::North, + Direction::West => Direction::East, + } + } } pub struct DirectionIterator { diff --git a/src/map_builders/room_accretion/mod.rs b/src/map_builders/room_accretion/mod.rs index 648317f..13f729b 100644 --- a/src/map_builders/room_accretion/mod.rs +++ b/src/map_builders/room_accretion/mod.rs @@ -87,8 +87,8 @@ fn get_cell_neighbours( } fn make_ca_room(rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) -> Vec> { - let width = rng.range(5, 10); - let height = rng.range(5, 10); + let width = rng.range(5, 12); + let height = rng.range(5, 12); let mut cells = grid_with_dimensions(height, width, 0); cells = cells .into_iter() @@ -125,10 +125,44 @@ fn make_ca_room(rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) -> } cells = new_cells; } - + // TODO: Floodfill to keep largest contiguous blob cells } +fn room_fits_at( + hyperspace: Vec>, + top_offset: usize, + left_offset: usize, + build_data: &BuilderMap +) -> bool { + let mut x_dungeon: usize; + let mut y_dungeon: usize; + for y in 0..HEIGHT { + for x in 0..WIDTH { + if hyperspace[y][x] != 2 { + y_dungeon = y + top_offset; + x_dungeon = x + left_offset; + for i in y_dungeon.saturating_sub(1)..=std::cmp::min(y_dungeon + 1, WIDTH - 1) { + for j in x_dungeon.saturating_sub(1)..=std::cmp::min( + x_dungeon + 1, + HEIGHT - 1 + ) { + let pt = build_data.map.xy_idx(i as i32, j as i32); + if + !in_bounds(i as i32, j as i32, &build_data) || + !(build_data.map.tiles[pt] == TileType::Wall) || + build_data.spawn_list.contains(&(pt, "door".to_string())) + { + return false; + } + } + } + } + } + } + return true; +} + fn direction_of_door( grid: &Vec>, row: usize, @@ -228,6 +262,10 @@ fn shuffle(list: &mut Vec, rng: &mut RandomNumberGenerator) { } } +fn clamp(x: T, min: T, max: T) -> T { + if x < min { min } else if x > max { max } else { x } +} + fn attach_hallway_to( door_sites: &mut Vec>, hyperspace: &mut Vec>, @@ -239,15 +277,6 @@ fn attach_hallway_to( let mut hallway_dir: Direction = Direction::NoDir; for i in 0..4 { hallway_dir = directions[i]; - console::log( - &format!( - "i: {:?} | hallway_dir: {:?} (as usize: {:?}) | door_sites[hallway_dir]: {:?}", - i, - hallway_dir, - hallway_dir as usize, - door_sites[hallway_dir as usize] - ) - ); if door_sites[hallway_dir as usize].is_some() && in_bounds( @@ -264,7 +293,6 @@ fn attach_hallway_to( let transform = hallway_dir.transform(); let hallway_len: i32 = match hallway_dir { Direction::NoDir => { - console::log("no hallway_dir"); return; } Direction::North | Direction::South => @@ -272,7 +300,6 @@ fn attach_hallway_to( Direction::East | Direction::West => rng.range(HORIZONTAL_CORRIDOR_MIN_LENGTH, HORIZONTAL_CORRIDOR_MAX_LENGTH + 1), }; - console::log(&format!("hallway_len: {:?}", hallway_len)); let mut x = door_sites[hallway_dir as usize].unwrap().x; let mut y = door_sites[hallway_dir as usize].unwrap().y; for _i in 0..hallway_len { @@ -282,13 +309,25 @@ fn attach_hallway_to( x += transform.x; y += transform.y; } - let new_site = DoorSite { - x, - y, - dir: hallway_dir, - }; - console::log(&format!("new_site: {:?}", new_site)); - door_sites[hallway_dir as usize] = Some(new_site); // Move door to end of corridor. + + y = clamp(y - transform.y, 0, (HEIGHT as i32) - 1); + x = clamp(x - transform.x, 0, (WIDTH as i32) - 1); + + let mut dir_iter = DirectionIterator::new(); + for dir in &mut dir_iter { + if dir != hallway_dir.opposite_dir() { + let door_y = y + dir.transform().y; + let door_x = x + dir.transform().x; + door_sites[dir as usize] = Some(DoorSite { + x: door_x, + y: door_y, + dir, + }); + } else { + door_sites[dir as usize] = None; + } + } + console::log(&format!("door_sites: {:?}", door_sites)); } fn design_room_in_hyperspace( From b5743819ece6d614fe00eebfc73a35d07b2ad1ef Mon Sep 17 00:00:00 2001 From: Llywelwyn Date: Mon, 2 Oct 2023 21:11:12 +0100 Subject: [PATCH 07/10] .describe() for Intrinsics, for use in tooltips later --- src/components.rs | 28 ++++++++++++++++++++++++++++ src/gui/tooltip.rs | 6 ++++++ 2 files changed, 34 insertions(+) diff --git a/src/components.rs b/src/components.rs index 8c56cce..522b235 100644 --- a/src/components.rs +++ b/src/components.rs @@ -427,11 +427,39 @@ pub enum Intrinsic { Speed, // 4/3x speed multiplier } +impl Intrinsic { + pub fn describe(&self) -> &str { + match self { + Intrinsic::Regeneration => "regenerates health", + Intrinsic::Speed => "is hasted", + } + } +} + #[derive(Component, Serialize, Deserialize, Debug, Clone)] pub struct Intrinsics { pub list: HashSet, } +impl Intrinsics { + pub fn describe(&self) -> String { + let mut descriptions = Vec::new(); + for intrinsic in &self.list { + descriptions.push(intrinsic.describe()); + } + match descriptions.len() { + 0 => + unreachable!("describe() should never be called on an empty Intrinsics component."), + 1 => format!("It {}.", descriptions[0]), + _ => { + let last = descriptions.pop().unwrap(); + let joined = descriptions.join(", "); + format!("It {}, and {}.", joined, last) + } + } + } +} + #[derive(Component, Debug, ConvertSaveload, Clone)] pub struct InflictsDamage { pub damage_type: DamageType, diff --git a/src/gui/tooltip.rs b/src/gui/tooltip.rs index e11d50f..94c3f97 100644 --- a/src/gui/tooltip.rs +++ b/src/gui/tooltip.rs @@ -111,6 +111,12 @@ pub fn draw_tooltips(ecs: &World, ctx: &mut BTerm, xy: Option<(i32, i32)>) { if position.x == mouse_pos_adjusted.0 && position.y == mouse_pos_adjusted.1 { let mut tip = Tooltip::new(); tip.add(crate::gui::obfuscate_name_ecs(ecs, entity).0, renderable.fg); + let intrinsics = ecs.read_storage::(); + if let Some(intrinsics) = intrinsics.get(entity) { + if !intrinsics.list.is_empty() { + tip.add(intrinsics.describe(), RGB::named(WHITE)); + } + } // Attributes let attr = attributes.get(entity); if let Some(a) = attr { From 4d21bd46d4561f81a0ad7c29115af28584f59d83 Mon Sep 17 00:00:00 2001 From: Llywelwyn Date: Mon, 2 Oct 2023 22:14:00 +0100 Subject: [PATCH 08/10] add_intr!() macro for adding intrinsics to the Player If needed, player can just be replaced by another arg to the macro so this works on every other entity - but right now the player is the only thing to gain/lose intrinsics. --- src/components.rs | 6 ++++++ src/lib.rs | 3 +++ src/macros/mod.rs | 44 ++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 1 + src/saveload_system.rs | 2 ++ 5 files changed, 56 insertions(+) create mode 100644 src/macros/mod.rs diff --git a/src/components.rs b/src/components.rs index 522b235..22f3b9e 100644 --- a/src/components.rs +++ b/src/components.rs @@ -460,6 +460,12 @@ impl Intrinsics { } } +#[derive(Component, Serialize, Deserialize, Debug, Clone)] +pub struct IntrinsicChanged { + pub gained: HashSet, + pub lost: HashSet, +} + #[derive(Component, Debug, ConvertSaveload, Clone)] pub struct InflictsDamage { pub damage_type: DamageType, diff --git a/src/lib.rs b/src/lib.rs index 812c7be..6bbdd04 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,6 +8,9 @@ extern crate serde; #[macro_use] extern crate lazy_static; +#[macro_use] +pub mod macros; + pub mod camera; pub mod components; pub mod raws; diff --git a/src/macros/mod.rs b/src/macros/mod.rs new file mode 100644 index 0000000..6d965c4 --- /dev/null +++ b/src/macros/mod.rs @@ -0,0 +1,44 @@ +// macros/mod.rs + +#[macro_export] +macro_rules! player { + ($ecs:expr, $component:ty) => { + { + let player = $ecs.fetch::(); + let component = $ecs.read_storage::<$component>(); + if let Some(player_component) = component.get(*player) { + true + } else { + false + } + } + }; +} + +#[macro_export] +macro_rules! add_intr { + ($ecs:expr, $intrinsic:expr) => { + let player = $ecs.fetch::(); + let mut intrinsics = $ecs.write_storage::(); + if let Some(player_intrinsics) = intrinsics.get_mut(*player) { + if !player_intrinsics.list.contains(&$intrinsic) { + player_intrinsics.list.insert($intrinsic); + let mut intrinsic_changed = $ecs.write_storage::(); + if let Some(this_intrinsic_changed) = intrinsic_changed.get_mut(*player) { + this_intrinsic_changed.gained.insert($intrinsic); + } else { + intrinsic_changed.insert(*player, crate::IntrinsicChanged { + gained: { + let mut m = std::collections::HashSet::new(); + m.insert($intrinsic); + m + }, + lost: std::collections::HashSet::new() + }).expect("Failed to insert IntrinsicChanged component."); + } + } + } else { + unreachable!("add_intr!(): The player should always have an Intrinsics component."); + } + }; +} diff --git a/src/main.rs b/src/main.rs index f82ebbd..f430376 100644 --- a/src/main.rs +++ b/src/main.rs @@ -111,6 +111,7 @@ fn main() -> BError { gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); + gs.ecs.register::(); gs.ecs.register::>(); gs.ecs.register::(); gs.ecs.register::(); diff --git a/src/saveload_system.rs b/src/saveload_system.rs index 894e4ff..362b35f 100644 --- a/src/saveload_system.rs +++ b/src/saveload_system.rs @@ -95,6 +95,7 @@ pub fn save_game(ecs: &mut World) { IdentifiedItem, InBackpack, InflictsDamage, + IntrinsicChanged, Intrinsics, Item, KnownSpells, @@ -227,6 +228,7 @@ pub fn load_game(ecs: &mut World) { IdentifiedItem, InBackpack, InflictsDamage, + IntrinsicChanged, Intrinsics, Item, KnownSpells, From fa4612cf1ffa6ba3aad3f70122df6b79f51b8c3c Mon Sep 17 00:00:00 2001 From: Llywelwyn Date: Mon, 2 Oct 2023 23:02:34 +0100 Subject: [PATCH 09/10] changed get_noncursed() to helper on BUC struct --- src/components.rs | 9 +++++++++ src/effects/triggers.rs | 4 ++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/components.rs b/src/components.rs index 22f3b9e..696eab4 100644 --- a/src/components.rs +++ b/src/components.rs @@ -243,6 +243,15 @@ pub enum BUC { Blessed, } +impl BUC { + pub fn noncursed(&self) -> bool { + match self { + BUC::Cursed => false, + _ => true, + } + } +} + #[derive(Component, Debug, Serialize, Deserialize, Eq, PartialEq, Hash, Clone)] pub struct Beatitude { pub buc: BUC, diff --git a/src/effects/triggers.rs b/src/effects/triggers.rs index ac38ccb..a82496c 100644 --- a/src/effects/triggers.rs +++ b/src/effects/triggers.rs @@ -1,4 +1,4 @@ -use super::{ add_effect, get_noncursed, particles, spatial, EffectType, Entity, Targets, World }; +use super::{ add_effect, particles, spatial, EffectType, Entity, Targets, World }; use crate::{ gamelog, gui::item_colour_ecs, @@ -205,7 +205,7 @@ fn handle_healing( healing_item.modifier; add_effect( event.source, - EffectType::Healing { amount: roll, increment_max: get_noncursed(&event.buc) }, + EffectType::Healing { amount: roll, increment_max: event.buc.noncursed() }, event.target.clone() ); for target in get_entity_targets(&event.target) { From 46bbe14beac85b515422a6d0812bb6987e5f94f6 Mon Sep 17 00:00:00 2001 From: Llywelwyn Date: Mon, 2 Oct 2023 23:02:46 +0100 Subject: [PATCH 10/10] added effects for adding intrinsics --- src/effects/intrinsics.rs | 11 +++++ src/effects/mod.rs | 8 +++- src/macros/mod.rs | 95 +++++++++++++++++++++++++++++---------- 3 files changed, 90 insertions(+), 24 deletions(-) create mode 100644 src/effects/intrinsics.rs diff --git a/src/effects/intrinsics.rs b/src/effects/intrinsics.rs new file mode 100644 index 0000000..01776e1 --- /dev/null +++ b/src/effects/intrinsics.rs @@ -0,0 +1,11 @@ +use super::{ EffectSpawner, EffectType }; +use specs::prelude::*; + +pub fn add_intrinsic(ecs: &mut World, effect: &EffectSpawner, target: Entity) { + let intrinsic = if let EffectType::AddIntrinsic { intrinsic } = &effect.effect_type { + intrinsic + } else { + unreachable!("add_intrinsic() called with the wrong EffectType") + }; + add_intr!(ecs, target, *intrinsic); +} diff --git a/src/effects/mod.rs b/src/effects/mod.rs index c23b52b..5552f5a 100644 --- a/src/effects/mod.rs +++ b/src/effects/mod.rs @@ -4,13 +4,14 @@ use bracket_lib::prelude::*; use specs::prelude::*; use std::collections::VecDeque; use std::sync::Mutex; -use crate::components::DamageType; +use crate::components::*; mod damage; mod hunger; mod particles; mod targeting; mod triggers; +mod intrinsics; pub use targeting::aoe_tiles; @@ -51,6 +52,9 @@ pub enum EffectType { ModifyNutrition { amount: i32, }, + AddIntrinsic { + intrinsic: Intrinsic, + }, TriggerFire { trigger: Entity, }, @@ -153,6 +157,7 @@ fn tile_effect_hits_entities(effect: &EffectType) -> bool { EffectType::Healing { .. } => true, EffectType::ModifyNutrition { .. } => true, EffectType::Confusion { .. } => true, + EffectType::AddIntrinsic { .. } => true, _ => false, } } @@ -175,6 +180,7 @@ fn affect_entity(ecs: &mut World, effect: &EffectSpawner, target: Entity) { } EffectType::EntityDeath => damage::entity_death(ecs, effect, target), EffectType::ModifyNutrition { .. } => hunger::modify_nutrition(ecs, effect, target), + EffectType::AddIntrinsic { .. } => intrinsics::add_intrinsic(ecs, effect, target), _ => {} } } diff --git a/src/macros/mod.rs b/src/macros/mod.rs index 6d965c4..a064f44 100644 --- a/src/macros/mod.rs +++ b/src/macros/mod.rs @@ -1,7 +1,8 @@ // macros/mod.rs #[macro_export] -macro_rules! player { +/// Used to check if the player has a given component. +macro_rules! player_has_component { ($ecs:expr, $component:ty) => { { let player = $ecs.fetch::(); @@ -16,29 +17,77 @@ macro_rules! player { } #[macro_export] -macro_rules! add_intr { - ($ecs:expr, $intrinsic:expr) => { - let player = $ecs.fetch::(); - let mut intrinsics = $ecs.write_storage::(); - if let Some(player_intrinsics) = intrinsics.get_mut(*player) { - if !player_intrinsics.list.contains(&$intrinsic) { - player_intrinsics.list.insert($intrinsic); - let mut intrinsic_changed = $ecs.write_storage::(); - if let Some(this_intrinsic_changed) = intrinsic_changed.get_mut(*player) { - this_intrinsic_changed.gained.insert($intrinsic); - } else { - intrinsic_changed.insert(*player, crate::IntrinsicChanged { - gained: { - let mut m = std::collections::HashSet::new(); - m.insert($intrinsic); - m - }, - lost: std::collections::HashSet::new() - }).expect("Failed to insert IntrinsicChanged component."); - } +/// Used to check if a given entity has a given Intrinsic. +macro_rules! has { + ($ecs:expr, $entity:expr, $intrinsic:expr) => { + { + let intrinsics = $ecs.read_storage::(); + if let Some(has_intrinsics) = intrinsics.get($entity) { + has_intrinsics.list.contains(&$intrinsic) + } else { + false + } + } + }; +} + +#[macro_export] +/// Used to check if the player has a given Intrinsic. +macro_rules! player_has { + ($ecs:expr, $intrinsic:expr) => { + { + let player = $ecs.fetch::(); + let intrinsics = $ecs.read_storage::(); + if let Some(player_intrinsics) = intrinsics.get(*player) { + player_intrinsics.list.contains(&$intrinsic) + } else { + false + } + } + }; +} + +#[macro_export] +/// Handles adding an Intrinsic to the player, and adding it to the IntrinsicChanged component. +macro_rules! add_intr { + ($ecs:expr, $entity:expr, $intrinsic:expr) => { + { + let mut intrinsics = $ecs.write_storage::(); + if let Some(player_intrinsics) = intrinsics.get_mut($entity) { + if !player_intrinsics.list.contains(&$intrinsic) { + player_intrinsics.list.insert($intrinsic); + let mut intrinsic_changed = $ecs.write_storage::(); + if let Some(this_intrinsic_changed) = intrinsic_changed.get_mut($entity) { + this_intrinsic_changed.gained.insert($intrinsic); + } else { + intrinsic_changed.insert($entity, crate::IntrinsicChanged { + gained: { + let mut m = std::collections::HashSet::new(); + m.insert($intrinsic); + m + }, + lost: std::collections::HashSet::new() + }).expect("Failed to insert IntrinsicChanged component."); + } + } + } else { + intrinsics.insert($entity, crate::Intrinsics { + list: { + let mut m = std::collections::HashSet::new(); + m.insert($intrinsic); + m + } + }).expect("Failed to insert Intrinsics component."); + let mut intrinsic_changed = $ecs.write_storage::(); + intrinsic_changed.insert($entity, crate::IntrinsicChanged { + gained: { + let mut m = std::collections::HashSet::new(); + m.insert($intrinsic); + m + }, + lost: std::collections::HashSet::new() + }).expect("Failed to insert IntrinsicChanged component."); } - } else { - unreachable!("add_intr!(): The player should always have an Intrinsics component."); } }; }