diff --git a/raws/mobs.json b/raws/mobs.json index 5be0ac7..6442d0c 100644 --- a/raws/mobs.json +++ b/raws/mobs.json @@ -96,6 +96,24 @@ "vision_range": 4, "attacks": [{ "name": "bites", "hit_bonus": 0, "damage": "1d3" }] }, + { + "id": "deer_little", + "name": "fawn", + "renderable": { "glyph": "q", "fg": "#a57037", "bg": "#000000", "order": 1 }, + "flags": ["BYSTANDER", "BLOCKS_TILE"], + "bac": 8, + "vision_range": 8, + "attacks": [{ "name": "kicks", "hit_bonus": 0, "damage": "1d2" }] + }, + { + "id": "sheep_little", + "name": "lamb", + "renderable": { "glyph": "q", "fg": "#e7e7e7", "bg": "#000000", "order": 1 }, + "flags": ["BYSTANDER", "BLOCKS_TILE"], + "bac": 10, + "vision_range": 4, + "attacks": [{ "name": "kicks", "hit_bonus": 0, "damage": "1d2" }] + }, { "id": "chicken_little", "name": "chick", @@ -105,6 +123,45 @@ "vision_range": 4, "attacks": [{ "name": "bites", "hit_bonus": 0, "damage": "1d2" }] }, + { + "id": "horse_little", + "name": "pony", + "renderable": { "glyph": "u", "fg": "#b36c29", "bg": "#000000", "order": 1 }, + "flags": ["BYSTANDER", "BLOCKS_TILE"], + "level": 3, + "bac": 6, + "vision_range": 8, + "attacks": [ + { "name": "kicks", "hit_bonus": 0, "damage": "1d6" }, + { "name": "bites", "hit_bonus": 0, "damage": "1d2" } + ] + }, + { + "id": "horse", + "name": "horse", + "renderable": { "glyph": "u", "fg": "#744d29", "bg": "#000000", "order": 1 }, + "flags": ["MONSTER", "BLOCKS_TILE"], + "level": 5, + "bac": 5, + "vision_range": 8, + "attacks": [ + { "name": "kicks", "hit_bonus": 0, "damage": "1d8" }, + { "name": "bites", "hit_bonus": 0, "damage": "1d3" } + ] + }, + { + "id": "horse_large", + "name": "warhorse", + "renderable": { "glyph": "u", "fg": "#8a3520", "bg": "#000000", "order": 1 }, + "flags": ["MONSTER", "BLOCKS_TILE"], + "level": 7, + "bac": 4, + "vision_range": 8, + "attacks": [ + { "name": "kicks", "hit_bonus": 0, "damage": "1d10" }, + { "name": "bites", "hit_bonus": 0, "damage": "1d4" } + ] + }, { "id": "rat_giant", "name": "giant rat", diff --git a/raws/spawn_tables.json b/raws/spawn_tables.json index d8ef6f4..15a5357 100644 --- a/raws/spawn_tables.json +++ b/raws/spawn_tables.json @@ -67,6 +67,8 @@ { "id": "kobold", "weight": 1, "difficulty": 1}, { "id": "fox", "weight": 1, "difficulty": 1}, { "id": "jackal", "weight": 4, "difficulty": 1}, + { "id": "deer_little", "weight": 1, "difficulty": 1}, + { "id": "sheep_little", "weight": 1, "difficulty": 1}, { "id": "rat_giant", "weight": 2, "difficulty": 2}, { "id": "coyote", "weight": 4, "difficulty": 2}, { "id": "dog_little", "weight": 1, "difficulty": 3}, @@ -74,9 +76,12 @@ { "id": "orc_large", "weight": 1, "difficulty": 3}, { "id": "goblin_chieftain", "weight": 1, "difficulty": 3}, { "id": "ogre", "weight": 1, "difficulty": 4}, + { "id": "horse_little", "weight": 2, "difficulty": 4}, { "id": "dog", "weight": 1, "difficulty": 5}, { "id": "wolf", "weight": 2, "difficulty": 6}, - { "id": "dog_large", "weight": 1, "difficulty": 7} + { "id": "dog_large", "weight": 1, "difficulty": 7}, + { "id": "horse", "weight": 2, "difficulty": 7}, + { "id": "horse_large", "weight": 2, "difficulty": 9} ] }, { diff --git a/src/camera.rs b/src/camera.rs index 9f4e59e..1444d56 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -33,7 +33,7 @@ pub fn render_camera(ecs: &World, ctx: &mut Rltk) { if t_x >= 0 && t_x < map.width && t_y >= 0 && t_y < map.height { let idx = map.xy_idx(t_x, t_y); if map.revealed_tiles[idx] { - let (glyph, fg, bg) = crate::map::themes::get_tile_glyph(idx, &*map); + let (glyph, fg, bg) = crate::map::themes::get_tile_renderables_for_id(idx, &*map); ctx.set(x + x_offset, y + y_offset, fg, bg, glyph); } } else if SHOW_BOUNDARIES { @@ -63,7 +63,7 @@ pub fn render_camera(ecs: &World, ctx: &mut Rltk) { if pos.x < max_x && pos.y < max_y && pos.x >= min_x && pos.y >= min_y { let mut draw = false; let mut fg = render.fg; - let (_glyph, _fg, bg) = crate::map::themes::get_tile_glyph(idx, &*map); + let (_glyph, _fg, bg) = crate::map::themes::get_tile_renderables_for_id(idx, &*map); // Draw entities on visible tiles if map.visible_tiles[idx] { draw = true; @@ -114,7 +114,7 @@ pub fn render_debug_map(map: &Map, ctx: &mut Rltk) { if tx >= 0 && tx < map_width && ty >= 0 && ty < map_height { let idx = map.xy_idx(tx, ty); if map.revealed_tiles[idx] { - let (glyph, fg, bg) = crate::map::themes::get_tile_glyph(idx, &*map); + let (glyph, fg, bg) = crate::map::themes::get_tile_renderables_for_id(idx, &*map); ctx.set(x, y, fg, bg, glyph); } } else if SHOW_BOUNDARIES { diff --git a/src/gui/tooltip.rs b/src/gui/tooltip.rs index d6980c8..e0271c1 100644 --- a/src/gui/tooltip.rs +++ b/src/gui/tooltip.rs @@ -26,17 +26,21 @@ impl Tooltip { return self.lines.len() as i32 + 2i32; } fn render(&self, ctx: &mut Rltk, x: i32, y: i32) { + let white = RGB::named(rltk::WHITE); let weak = RGB::named(rltk::CYAN); let strong = RGB::named(rltk::ORANGE); + let attribute = RGB::named(rltk::PINK); ctx.draw_box(x, y, self.width() - 1, self.height() - 1, RGB::named(WHITE), RGB::named(BLACK)); for (i, s) in self.lines.iter().enumerate() { let col = if i == 0 { - RGB::named(WHITE) + white } else if s.starts_with('-') { weak - } else { + } else if s.starts_with('*') { strong + } else { + attribute }; ctx.print_color(x + 1, y + i as i32 + 1, col, RGB::named(BLACK), &s); } @@ -91,9 +95,12 @@ pub fn draw_tooltips(ecs: &World, ctx: &mut Rltk) { if a.intelligence.bonus > 2 { s += "smart "}; if a.wisdom.bonus < -2 { s += "unwise "}; if a.wisdom.bonus > 2 { s += "wisened "}; - if a.charisma.bonus < -2 { s += "ugly "}; - if a.charisma.bonus > 2 { s += "attractive "}; + if a.charisma.bonus < -2 { s += "ugly"}; + if a.charisma.bonus > 2 { s += "attractive"}; if !s.is_empty() { + if s.ends_with(" ") { + s.pop(); + } tip.add(s); } } @@ -114,13 +121,12 @@ pub fn draw_tooltips(ecs: &World, ctx: &mut Rltk) { if tooltips.is_empty() { return ; } - let box_gray : RGB = RGB::from_hex("#999999").expect("Oops"); let white = RGB::named(rltk::WHITE); let arrow; let arrow_x; let arrow_y = mouse_pos.1; - if mouse_pos.0 < 50 { + if mouse_pos.0 < 35 { // Render to the left arrow = to_cp437('→'); arrow_x = mouse_pos.0 - 1; @@ -129,7 +135,7 @@ pub fn draw_tooltips(ecs: &World, ctx: &mut Rltk) { arrow = to_cp437('←'); arrow_x = mouse_pos.0 + 1; } - ctx.set(arrow_x, arrow_y, white, box_gray, arrow); + ctx.set(arrow_x, arrow_y, white, RGB::named(rltk::BLACK), arrow); let mut total_height = 0; for t in tooltips.iter() { @@ -142,10 +148,10 @@ pub fn draw_tooltips(ecs: &World, ctx: &mut Rltk) { } for t in tooltips.iter() { - let x = if mouse_pos.0 < 40 { + let x = if mouse_pos.0 < 35 { mouse_pos.0 - (1 + t.width()) } else { - mouse_pos.0 + (1 + t.width()) + mouse_pos.0 + (1 + 1) }; t.render(ctx, x, y); y += t.height(); diff --git a/src/main.rs b/src/main.rs index 508b000..8938009 100644 --- a/src/main.rs +++ b/src/main.rs @@ -43,7 +43,7 @@ mod rex_assets; extern crate lazy_static; //Consts -pub const SHOW_MAPGEN: bool = false; +pub const SHOW_MAPGEN: bool = true; #[derive(PartialEq, Copy, Clone)] pub enum RunState { diff --git a/src/map/colours.rs b/src/map/colours.rs new file mode 100644 index 0000000..9b7c31b --- /dev/null +++ b/src/map/colours.rs @@ -0,0 +1,24 @@ +pub const NON_VISIBLE_MULTIPLIER: f32 = 0.7; +pub const BLOODSTAIN_COLOUR: (u8, u8, u8) = (153, 0, 0); + +// DEFAULT THEME +pub const DEFAULT_BG_COLOUR: (u8, u8, u8) = (29, 50, 50); +pub const WALL_COLOUR: (u8, u8, u8) = (229, 191, 94); +pub const FLOOR_COLOUR: (u8, u8, u8) = (25, 204, 122); +pub const DOWN_STAIR_COLOUR: (u8, u8, u8) = (200, 200, 0); +pub const WOOD_FLOOR_COLOUR: (u8, u8, u8) = (41, 30, 20); +pub const FENCE_FG_COLOUR: (u8, u8, u8) = (110, 24, 0); +pub const FENCE_COLOUR: (u8, u8, u8) = (45, 30, 10); +pub const BRIDGE_COLOUR: (u8, u8, u8) = (42, 48, 37); +pub const GRAVEL_COLOUR: (u8, u8, u8) = (26, 26, 53); +pub const ROAD_COLOUR: (u8, u8, u8) = (8, 38, 40); +pub const GRASS_COLOUR: (u8, u8, u8) = (9, 65, 6); +pub const FOLIAGE_COLOUR: (u8, u8, u8) = (5, 60, 5); +pub const HEAVY_FOLIAGE_COLOUR: (u8, u8, u8) = (5, 55, 5); +pub const SAND_COLOUR: (u8, u8, u8) = (70, 70, 21); +pub const SHALLOW_WATER_COLOUR: (u8, u8, u8) = (24, 47, 99); +pub const DEEP_WATER_COLOUR: (u8, u8, u8) = (18, 33, 63); +pub const BARS_COLOUR: (u8, u8, u8) = (100, 100, 100); + +// FOREST THEME +pub const FOREST_WALL_COLOUR: (u8, u8, u8) = (0, 153, 0); diff --git a/src/map/glyphs.rs b/src/map/glyphs.rs new file mode 100644 index 0000000..8bd0474 --- /dev/null +++ b/src/map/glyphs.rs @@ -0,0 +1,19 @@ +// DEFAULT THEME +pub const WALL_GLYPH: char = '#'; +pub const FLOOR_GLYPH: char = '.'; +pub const DOWN_STAIR_GLYPH: char = '>'; +pub const WOOD_FLOOR_GLYPH: char = '.'; +pub const FENCE_GLYPH: char = '='; +pub const BRIDGE_GLYPH: char = '.'; +pub const GRAVEL_GLYPH: char = ';'; +pub const ROAD_GLYPH: char = '.'; +pub const GRASS_GLYPH: char = '"'; +pub const FOLIAGE_GLYPH: char = ':'; +pub const HEAVY_FOLIAGE_GLYPH: char = ';'; +pub const SAND_GLYPH: char = '.'; +pub const SHALLOW_WATER_GLYPH: char = '~'; +pub const DEEP_WATER_GLYPH: char = '≈'; +pub const BARS_GLYPH: char = '#'; + +// FOREST THEME +pub const FOREST_WALL_GLYPH: char = '♣'; diff --git a/src/map/mod.rs b/src/map/mod.rs index eb2626e..2c7e1df 100644 --- a/src/map/mod.rs +++ b/src/map/mod.rs @@ -2,6 +2,8 @@ use rltk::{Algorithm2D, BaseMap, Point}; use serde::{Deserialize, Serialize}; use specs::prelude::*; use std::collections::HashSet; +pub mod colours; +mod glyphs; mod tiletype; pub use tiletype::{tile_cost, tile_opaque, tile_walkable, TileType}; pub mod themes; @@ -50,7 +52,7 @@ impl Map { lit_tiles: vec![true; map_tile_count], // NYI: Light sources. Once those exist, we can set this to false. telepath_tiles: vec![false; map_tile_count], colour_offset: vec![(1.0, 1.0, 1.0); map_tile_count], - additional_fg_offset: rltk::RGB::from_u8(HALF_OFFSET, HALF_OFFSET, HALF_OFFSET), + additional_fg_offset: rltk::RGB::from_u8(OFFSET_PERCENT as u8, OFFSET_PERCENT as u8, OFFSET_PERCENT as u8), blocked: vec![false; map_tile_count], id: new_id, name: name.to_string(), @@ -60,7 +62,6 @@ impl Map { tile_content: vec![Vec::new(); map_tile_count], }; - const HALF_OFFSET: u8 = 5; const OFFSET_PERCENT: i32 = 10; const TWICE_OFFSET: i32 = OFFSET_PERCENT * 2; let mut rng = rltk::RandomNumberGenerator::new(); diff --git a/src/map/themes.rs b/src/map/themes.rs index 2efd386..071b4ed 100644 --- a/src/map/themes.rs +++ b/src/map/themes.rs @@ -1,109 +1,70 @@ -use super::{Map, TileType}; +use super::{colours::*, glyphs::*, Map, TileType}; use rltk::RGB; use std::ops::{Add, Mul}; -pub fn offset(rgb: rltk::RGB, offsets: (f32, f32, f32)) -> RGB { - let r = rgb.r * offsets.0; - let g = rgb.g * offsets.1; - let b = rgb.b * offsets.2; +pub fn get_tile_renderables_for_id(idx: usize, map: &Map) -> (rltk::FontCharType, RGB, RGB) { + let (glyph, mut fg, mut bg) = match map.id { + 2 => get_forest_theme_renderables(idx, map), + _ => get_default_theme_renderables(idx, map), + }; - return rltk::RGB::from_f32(r, g, b); -} - -pub fn get_tile_glyph(idx: usize, map: &Map) -> (rltk::FontCharType, RGB, RGB) { - let offsets = map.colour_offset[idx]; - let glyph: rltk::FontCharType; - let mut fg: RGB = RGB::new(); - let mut bg: RGB; - - let default_bg: RGB = RGB::from_u8(29, 50, 50); - - match map.tiles[idx] { - TileType::Floor => { - glyph = rltk::to_cp437('.'); - fg = RGB::from_f32(0.1, 0.8, 0.5); - bg = default_bg; - } - TileType::WoodFloor => { - glyph = rltk::to_cp437('.'); - bg = RGB::from_u8(41, 30, 20); - } - TileType::Fence => { - glyph = rltk::to_cp437('='); - fg = RGB::from_u8(110, 24, 0); - bg = RGB::from_u8(45, 30, 10); - } - TileType::Wall => { - let x = idx as i32 % map.width; - let y = idx as i32 / map.width; - glyph = wall_glyph(&*map, x, y); - fg = RGB::from_f32(0.9, 0.75, 0.37); - bg = default_bg; - } - TileType::DownStair => { - glyph = rltk::to_cp437('>'); - fg = RGB::from_u8(200, 200, 0); - bg = default_bg; - } - TileType::Bridge => { - glyph = rltk::to_cp437('.'); - bg = RGB::from_u8(42, 48, 37); - } - TileType::Gravel => { - glyph = rltk::to_cp437(';'); - bg = RGB::from_u8(26, 26, 53); - } - TileType::Road => { - glyph = rltk::to_cp437('.'); - //fg = RGB::from_u8(112, 105, 94); - bg = RGB::from_u8(8, 38, 40); - } - TileType::Grass => { - glyph = rltk::to_cp437('"'); - bg = RGB::from_u8(9, 65, 6); - } - TileType::Foliage => { - glyph = rltk::to_cp437(':'); - bg = RGB::from_u8(5, 60, 5); - } - TileType::HeavyFoliage => { - glyph = rltk::to_cp437(';'); - bg = RGB::from_u8(5, 55, 5); - } - TileType::Sand => { - glyph = rltk::to_cp437('.'); - bg = RGB::from_u8(70, 70, 21); - } - TileType::ShallowWater => { - glyph = rltk::to_cp437('~'); - bg = RGB::from_u8(24, 47, 99); - } - TileType::DeepWater => { - glyph = rltk::to_cp437('≈'); - bg = RGB::from_u8(18, 33, 63); - } - } - if map.bloodstains.contains(&idx) { - bg = bg.add(RGB::from_f32(0.6, 0., 0.)); - } - - // If the foreground hasn't been changed, just add - // the bg to it. Otherwise, leave it as is. + // If one of the colours was left blank, make them the same. if fg == RGB::new() { - fg = fg.add(bg).add(map.additional_fg_offset).add(map.additional_fg_offset); + fg = bg + } else if bg == RGB::new() { + bg = fg; } - fg = offset(fg, offsets); - bg = offset(bg, offsets); - - if !map.visible_tiles[idx] { - fg = fg.mul(0.7); - bg = bg.mul(0.7); - } + fg = fg.add(map.additional_fg_offset); + (fg, bg) = apply_colour_offset(fg, bg, map, idx); + bg = apply_bloodstain_if_necessary(bg, map, idx); + (fg, bg) = darken_if_not_visible(fg, bg, map, idx); return (glyph, fg, bg); } +#[rustfmt::skip] +pub fn get_default_theme_renderables(idx: usize, map: &Map) -> (rltk::FontCharType, RGB, RGB) { + let glyph: rltk::FontCharType; + let mut fg: RGB = RGB::new(); + let mut bg: RGB = RGB::new(); + + match map.tiles[idx] { + TileType::Floor => { glyph = rltk::to_cp437(FLOOR_GLYPH); fg = RGB::named(FLOOR_COLOUR); bg = RGB::named(DEFAULT_BG_COLOUR); } + TileType::WoodFloor => { glyph = rltk::to_cp437(WOOD_FLOOR_GLYPH); bg = RGB::named(WOOD_FLOOR_COLOUR); } + TileType::Fence => { glyph = rltk::to_cp437(FENCE_GLYPH); fg = RGB::named(FENCE_FG_COLOUR); bg = RGB::named(FENCE_COLOUR); } + TileType::Wall => { let x = idx as i32 % map.width; let y = idx as i32 / map.width; glyph = wall_glyph(&*map, x, y); fg = RGB::named(WALL_COLOUR); bg = RGB::named(DEFAULT_BG_COLOUR); } + TileType::DownStair => { glyph = rltk::to_cp437(DOWN_STAIR_GLYPH); fg = RGB::named(DOWN_STAIR_COLOUR); bg = RGB::named(DEFAULT_BG_COLOUR); } + TileType::Bridge => { glyph = rltk::to_cp437(BRIDGE_GLYPH); bg = RGB::named(BRIDGE_COLOUR); } + TileType::Gravel => { glyph = rltk::to_cp437(GRAVEL_GLYPH); bg = RGB::named(GRAVEL_COLOUR); } + TileType::Road => { glyph = rltk::to_cp437(ROAD_GLYPH); bg = RGB::named(ROAD_COLOUR); } + TileType::Grass => { glyph = rltk::to_cp437(GRASS_GLYPH); bg = RGB::named(GRASS_COLOUR); } + TileType::Foliage => { glyph = rltk::to_cp437(FOLIAGE_GLYPH); bg = RGB::named(FOLIAGE_COLOUR); } + TileType::HeavyFoliage => { glyph = rltk::to_cp437(HEAVY_FOLIAGE_GLYPH); bg = RGB::named(HEAVY_FOLIAGE_COLOUR); } + TileType::Sand => { glyph = rltk::to_cp437(SAND_GLYPH); bg = RGB::named(SAND_COLOUR); } + TileType::ShallowWater => { glyph = rltk::to_cp437(SHALLOW_WATER_GLYPH); bg = RGB::named(SHALLOW_WATER_COLOUR); } + TileType::DeepWater => { glyph = rltk::to_cp437(DEEP_WATER_GLYPH); bg = RGB::named(DEEP_WATER_COLOUR); } + TileType::Bars => { glyph = rltk::to_cp437(BARS_GLYPH); fg = RGB::named(BARS_COLOUR); bg = RGB::named(FLOOR_COLOUR); } + } + return (glyph, fg, bg); +} + +#[rustfmt::skip] +fn get_forest_theme_renderables(idx:usize, map: &Map) -> (rltk::FontCharType, RGB, RGB) { + let glyph; + let mut fg = RGB::new(); + let mut bg = RGB::new(); + + match map.tiles[idx] { + TileType::Wall => { glyph = rltk::to_cp437(FOREST_WALL_GLYPH); fg = RGB::named(FOREST_WALL_COLOUR); bg = RGB::named(GRASS_COLOUR) } + TileType::Road => { glyph = rltk::to_cp437(ROAD_GLYPH); bg = RGB::named(ROAD_COLOUR); } + TileType::ShallowWater => { glyph = rltk::to_cp437(SHALLOW_WATER_GLYPH); bg = RGB::named(SHALLOW_WATER_COLOUR); } + _ => { (glyph, fg, _) = get_default_theme_renderables(idx, map); bg = RGB::named(GRASS_COLOUR) } + } + + (glyph, fg, bg) +} + fn is_revealed_and_wall(map: &Map, x: i32, y: i32) -> bool { let idx = map.xy_idx(x, y); map.tiles[idx] == TileType::Wall && map.revealed_tiles[idx] @@ -245,3 +206,33 @@ fn wall_glyph(map: &Map, x: i32, y: i32) -> rltk::FontCharType { _ => 35, // We missed one? } } + +fn apply_colour_offset(mut fg: RGB, mut bg: RGB, map: &Map, idx: usize) -> (RGB, RGB) { + let offsets = map.colour_offset[idx]; + fg = multiply_by_float(fg.add(map.additional_fg_offset), offsets); + bg = multiply_by_float(bg, offsets); + return (fg, bg); +} + +fn darken_if_not_visible(mut fg: RGB, mut bg: RGB, map: &Map, idx: usize) -> (RGB, RGB) { + if !map.visible_tiles[idx] { + fg = fg.mul(NON_VISIBLE_MULTIPLIER); + bg = bg.mul(NON_VISIBLE_MULTIPLIER); + } + return (fg, bg); +} + +fn apply_bloodstain_if_necessary(mut bg: RGB, map: &Map, idx: usize) -> RGB { + if map.bloodstains.contains(&idx) { + bg = bg.add(RGB::named(BLOODSTAIN_COLOUR)); + } + return bg; +} + +pub fn multiply_by_float(rgb: rltk::RGB, offsets: (f32, f32, f32)) -> RGB { + let r = rgb.r * offsets.0; + let g = rgb.g * offsets.1; + let b = rgb.b * offsets.2; + + return rltk::RGB::from_f32(r, g, b); +} diff --git a/src/map/tiletype.rs b/src/map/tiletype.rs index 503173c..978a9be 100644 --- a/src/map/tiletype.rs +++ b/src/map/tiletype.rs @@ -7,6 +7,7 @@ pub enum TileType { // Impassable (transparent) DeepWater, Fence, + Bars, // Floors (walkable) Floor, WoodFloor, diff --git a/src/map_builders/forest.rs b/src/map_builders/forest.rs new file mode 100644 index 0000000..2d66a8d --- /dev/null +++ b/src/map_builders/forest.rs @@ -0,0 +1,109 @@ +use super::{ + AreaStartingPosition, BuilderChain, BuilderMap, CellularAutomataBuilder, CullUnreachable, MetaMapBuilder, TileType, + VoronoiSpawning, XStart, YStart, +}; +use rltk::prelude::*; + +pub fn forest_builder( + new_id: i32, + _rng: &mut rltk::RandomNumberGenerator, + width: i32, + height: i32, + difficulty: i32, +) -> BuilderChain { + let mut chain = BuilderChain::new(new_id, width, height, difficulty, "Into the Woods"); + chain.start_with(CellularAutomataBuilder::new()); + chain.with(AreaStartingPosition::new(XStart::CENTRE, YStart::CENTRE)); + chain.with(CullUnreachable::new()); + chain.with(AreaStartingPosition::new(XStart::LEFT, YStart::CENTRE)); + // Setup an exit and spawn mobs + chain.with(VoronoiSpawning::new()); + chain.with(RoadExit::new()); + return chain; +} + +pub struct RoadExit {} + +impl MetaMapBuilder for RoadExit { + fn build_map(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) { + self.build(rng, build_data); + } +} + +impl RoadExit { + #[allow(dead_code)] + pub fn new() -> Box { + return Box::new(RoadExit {}); + } + + fn find_exit(&self, build_data: &mut BuilderMap, seed_x: i32, seed_y: i32) -> (i32, i32) { + let mut available_floors: Vec<(usize, f32)> = Vec::new(); + for (idx, tiletype) in build_data.map.tiles.iter().enumerate() { + if crate::map::tile_walkable(*tiletype) { + available_floors.push(( + idx, + DistanceAlg::PythagorasSquared.distance2d( + Point::new(idx as i32 % build_data.map.width, idx as i32 / build_data.map.width), + Point::new(seed_x, seed_y), + ), + )); + } + } + if available_floors.is_empty() { + panic!("No valid floors to start on."); + } + available_floors.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap()); + let end_x = available_floors[0].0 as i32 % build_data.map.width; + let end_y = available_floors[0].0 as i32 / build_data.map.width; + return (end_x, end_y); + } + + fn paint_road(&self, build_data: &mut BuilderMap, x: i32, y: i32) { + if x < 1 || x > build_data.map.width - 2 || y < 1 || y > build_data.map.width - 2 { + return; + } + let idx = build_data.map.xy_idx(x, y); + if build_data.map.tiles[idx] != TileType::DownStair { + build_data.map.tiles[idx] = TileType::Road; + } + } + + fn build(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) { + let starting_pos = build_data.starting_position.as_ref().unwrap().clone(); + let start_idx = build_data.map.xy_idx(starting_pos.x, starting_pos.y); + let (end_x, end_y) = self.find_exit(build_data, build_data.map.width - 2, build_data.height / 2); + let end_idx = build_data.map.xy_idx(end_x, end_y); + build_data.map.populate_blocked(); + + let path = a_star_search(start_idx, end_idx, &mut build_data.map); + for idx in path.steps.iter() { + let x = *idx as i32 % build_data.map.width; + let y = *idx as i32 / build_data.map.width; + self.paint_road(build_data, x, y); + self.paint_road(build_data, x - 1, y); + self.paint_road(build_data, x + 1, y); + self.paint_road(build_data, x, y - 1); + self.paint_road(build_data, x, y + 1); + } + build_data.take_snapshot(); + + let exit_dir = rng.roll_dice(1, 2); + let (seed_x, seed_y, stream_start_x, stream_start_y) = if exit_dir == 1 { + (build_data.map.width - 1, 1, 0, build_data.height - 1) + } else { + (build_data.map.width - 1, build_data.height - 1, 1, build_data.height - 1) + }; + let (stairs_x, stairs_y) = self.find_exit(build_data, seed_x, seed_y); + let stairs_idx = build_data.map.xy_idx(stairs_x, stairs_y); + let (stream_x, stream_y) = self.find_exit(build_data, stream_start_x, stream_start_y); + let stream_idx = build_data.map.xy_idx(stream_x, stream_y) as usize; + let stream = a_star_search(stairs_idx, stream_idx, &mut build_data.map); + for tile in stream.steps.iter() { + if build_data.map.tiles[*tile as usize] == TileType::Floor { + build_data.map.tiles[*tile as usize] = TileType::ShallowWater; + } + } + build_data.map.tiles[stairs_idx] = TileType::DownStair; + build_data.take_snapshot(); + } +} diff --git a/src/map_builders/mod.rs b/src/map_builders/mod.rs index 37eb7dd..69c1392 100644 --- a/src/map_builders/mod.rs +++ b/src/map_builders/mod.rs @@ -60,6 +60,8 @@ mod fill_edges; use fill_edges::FillEdges; mod town; use town::town_builder; +mod forest; +use forest::forest_builder; // Shared data to be passed around build chain pub struct BuilderMap { @@ -342,6 +344,7 @@ pub fn level_builder(new_id: i32, rng: &mut rltk::RandomNumberGenerator, width: let difficulty = new_id; match new_id { 1 => town_builder(new_id, rng, width, height), + 2 => forest_builder(new_id, rng, width, height, difficulty), _ => random_builder(new_id, rng, width, height, difficulty), } }