diff --git a/src/components.rs b/src/components.rs index 61bdb83..096464d 100644 --- a/src/components.rs +++ b/src/components.rs @@ -431,6 +431,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, @@ -659,11 +668,45 @@ 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, 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/config/mod.rs b/src/config/mod.rs index 661e203..ffc11d4 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/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 db099ff..99f6534 100644 --- a/src/effects/mod.rs +++ b/src/effects/mod.rs @@ -4,7 +4,7 @@ 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; @@ -12,6 +12,7 @@ mod particles; mod targeting; mod triggers; mod attr; +mod intrinsics; pub use targeting::aoe_tiles; @@ -56,6 +57,9 @@ pub enum EffectType { ModifyNutrition { amount: i32, }, + AddIntrinsic { + intrinsic: Intrinsic, + }, TriggerFire { trigger: Entity, }, @@ -158,6 +162,7 @@ fn tile_effect_hits_entities(effect: &EffectType) -> bool { EffectType::Healing { .. } => true, EffectType::ModifyNutrition { .. } => true, EffectType::Confusion { .. } => true, + EffectType::AddIntrinsic { .. } => true, _ => false, } } @@ -181,6 +186,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/effects/triggers.rs b/src/effects/triggers.rs index 0d40a17..686fbec 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, @@ -210,7 +210,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) { diff --git a/src/gamelog/events.rs b/src/gamelog/events.rs index 722cd16..f16a273 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. diff --git a/src/gui/tooltip.rs b/src/gui/tooltip.rs index 04c9898..4919a1f 100644 --- a/src/gui/tooltip.rs +++ b/src/gui/tooltip.rs @@ -114,6 +114,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 { diff --git a/src/lib.rs b/src/lib.rs index e619f18..e0bc40f 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..a064f44 --- /dev/null +++ b/src/macros/mod.rs @@ -0,0 +1,93 @@ +// macros/mod.rs + +#[macro_export] +/// Used to check if the player has a given component. +macro_rules! player_has_component { + ($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] +/// 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."); + } + } + }; +} diff --git a/src/main.rs b/src/main.rs index 7548e5f..3222884 100644 --- a/src/main.rs +++ b/src/main.rs @@ -138,6 +138,7 @@ fn setup(gfx: &mut Graphics) -> State { 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/map_builders/mod.rs b/src/map_builders/mod.rs index 7acb549..4a783d5 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; @@ -488,3 +490,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 new file mode 100644 index 0000000..8c10d16 --- /dev/null +++ b/src/map_builders/room_accretion/consts.rs @@ -0,0 +1,127 @@ +use lazy_static::lazy_static; +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, + 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, 4)], + vec![ + CellRules::new(0, 0, Operator::GreaterThanEqualTo, 5), + CellRules::new(1, 0, Operator::LessThan, 2) + ] + ]; +} + +#[derive(Debug, Copy, Clone, PartialEq)] +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 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 { + 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..13f729b --- /dev/null +++ b/src/map_builders/room_accretion/mod.rs @@ -0,0 +1,397 @@ +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) { + accrete_rooms(rng, build_data); + } +} + +fn grid_with_dimensions(h: usize, w: usize, value: i32) -> Vec> { + vec![vec![value; w]; h] +} + +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( + 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]); + } + } + } + console::log(&format!("neighbours: {:?}", neighbours)); + neighbours +} + +fn make_ca_room(rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) -> Vec> { + 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() + .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..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; + } + } + 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, + 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_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; + } + } + return solution; +} + +#[derive(Debug, 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![None; 4]; + 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[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 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>, + 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]; + 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 => { + 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), + }; + 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; + } + + 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( + 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(); +} diff --git a/src/saveload_system.rs b/src/saveload_system.rs index 01ebe98..4011145 100644 --- a/src/saveload_system.rs +++ b/src/saveload_system.rs @@ -101,6 +101,7 @@ pub fn save_game(ecs: &mut World) { IdentifiedItem, InBackpack, InflictsDamage, + IntrinsicChanged, Intrinsics, Item, Key, @@ -238,6 +239,7 @@ pub fn load_game(ecs: &mut World) { IdentifiedItem, InBackpack, InflictsDamage, + IntrinsicChanged, Intrinsics, Item, Key,