diff --git a/.gitignore b/.gitignore index 828c7fa..730f505 100644 --- a/.gitignore +++ b/.gitignore @@ -111,3 +111,6 @@ Cargo.lock *.pdb # End of https://www.toptal.com/developers/gitignore/api/rust,jetbrains+all + +# Ignore save game file +savegame.json diff --git a/src/gui.rs b/src/gui.rs index 8e823e9..563abab 100644 --- a/src/gui.rs +++ b/src/gui.rs @@ -51,6 +51,16 @@ pub fn draw_ui(ecs: &World, ctx: &mut Rltk) { ); } + let map = ecs.fetch::(); + let depth = format!("Depth: {}", map.depth); + ctx.print_color( + 2, + 43, + RGB::named(rltk::YELLOW), + RGB::named(rltk::BLACK), + &depth, + ); + // Display logs let log = ecs.fetch::(); let mut y = 44; diff --git a/src/main.rs b/src/main.rs index 5ceaa56..9241cf7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -59,6 +59,7 @@ pub enum RunState { menu_selection: gui::MainMenuSelection, }, SaveGame, + NextLevel, } pub struct State { @@ -252,6 +253,10 @@ impl GameState for State { menu_selection: gui::MainMenuSelection::LoadGame, } } + RunState::NextLevel => { + self.goto_next_level(); + newrunstate = RunState::PreRun; + } } { @@ -263,6 +268,95 @@ impl GameState for State { } } +impl State { + fn entities_to_remove_on_level_change(&mut self) -> Vec { + let entities = self.ecs.entities(); + let player = self.ecs.read_storage::(); + let backpack = self.ecs.read_storage::(); + let player_entity = self.ecs.fetch::(); + + let mut to_delete: Vec = Vec::new(); + for entity in entities.join() { + let mut should_delete = true; + + // Don't delete the player + let p = player.get(entity); + if let Some(_p) = p { + should_delete = false; + } + + // Don't delete the player's equipment + let bp = backpack.get(entity); + if let Some(bp) = bp { + if bp.owner == *player_entity { + should_delete = false; + } + } + + if should_delete { + to_delete.push(entity); + } + } + + to_delete + } + + fn goto_next_level(&mut self) { + // Delete entities that aren't the palyer or their equipment + let to_delete = self.entities_to_remove_on_level_change(); + for target in to_delete { + self.ecs + .delete_entity(target) + .expect("failed to delete entity"); + } + + // Build a new map and place the player + let worldmap; + { + let mut worldmap_resource = self.ecs.write_resource::(); + let current_depth = worldmap_resource.depth; + + *worldmap_resource = Map::new_map_rooms_and_corridors(current_depth + 1); + worldmap = worldmap_resource.clone(); + } + + // Spawn bad guys + for room in worldmap.rooms.iter().skip(1) { + spawner::spawn_room(&mut self.ecs, room); + } + + // Place the player and update resources + let (player_x, player_y) = worldmap.rooms[0].center(); + let mut player_position = self.ecs.write_resource::(); + *player_position = Point::new(player_x, player_y); + let mut position_components = self.ecs.write_storage::(); + let player_entity = self.ecs.fetch::(); + let player_pos_comp = position_components.get_mut(*player_entity); + if let Some(player_pos_comp) = player_pos_comp { + player_pos_comp.x = player_x; + player_pos_comp.y = player_y; + } + + // Mark the player's visibility as dirty + let mut viewshed_components = self.ecs.write_storage::(); + let vs = viewshed_components.get_mut(*player_entity); + if let Some(vs) = vs { + vs.dirty = true; + } + + // Notify the player and give them some health + let mut gamelog = self.ecs.fetch_mut::(); + gamelog + .entries + .push("You descend to the next level, and take a moment to heal.".to_string()); + let mut player_health_store = self.ecs.write_storage::(); + let player_health = player_health_store.get_mut(*player_entity); + if let Some(player_health) = player_health { + player_health.hp = i32::max(player_health.hp, player_health.max_hp / 2); + } + } +} + fn main() -> rltk::BError { use rltk::RltkBuilder; @@ -301,7 +395,7 @@ fn main() -> rltk::BError { gs.ecs.insert(SimpleMarkerAllocator::::new()); - let map = Map::new_map_rooms_and_corridors(); + let map = Map::new_map_rooms_and_corridors(1); let (player_x, player_y) = map.rooms[0].center(); let player_entity = spawner::player(&mut gs.ecs, player_x, player_y); diff --git a/src/map.rs b/src/map.rs index 1a1d230..0f61fb1 100644 --- a/src/map.rs +++ b/src/map.rs @@ -12,6 +12,7 @@ pub const MAP_COUNT: usize = MAP_HEIGHT * MAP_WIDTH; pub enum TileType { Wall, Floor, + DownStairs, } #[derive(Default, Serialize, Deserialize, Clone)] @@ -23,6 +24,7 @@ pub struct Map { pub revealed_tiles: Vec, pub visible_tiles: Vec, pub blocked: Vec, + pub depth: i32, #[serde(skip_serializing)] #[serde(skip_deserializing)] @@ -65,7 +67,7 @@ impl Map { /// Makes a new map using the algorithm from http://rogueliketutorials.com/tutorials/tcod/part-3/ /// This gives a handful of random rooms and corridors joining them together - pub fn new_map_rooms_and_corridors() -> Map { + pub fn new_map_rooms_and_corridors(new_depth: i32) -> Map { let mut map = Map { tiles: vec![TileType::Wall; MAP_COUNT], rooms: Vec::new(), @@ -75,6 +77,7 @@ impl Map { visible_tiles: vec![false; MAP_COUNT], blocked: vec![false; MAP_COUNT], tile_content: vec![Vec::new(); MAP_COUNT], + depth: new_depth, }; const MAX_ROOMS: i32 = 30; @@ -118,6 +121,10 @@ impl Map { } } + let stairs_position = map.rooms[map.rooms.len() - 1].center(); + let stairs_idx = map.xy_idx(stairs_position.0, stairs_position.1); + map.tiles[stairs_idx] = TileType::DownStairs; + map } @@ -222,6 +229,10 @@ pub fn draw_map(ecs: &World, ctx: &mut Rltk) { glyph = rltk::to_cp437('#'); fg = RGB::from_f32(0., 1.0, 0.); } + TileType::DownStairs => { + glyph = rltk::to_cp437('>'); + fg = RGB::from_f32(0., 1.0, 1.0); + } } if !map.visible_tiles[idx] { diff --git a/src/player.rs b/src/player.rs index 6ce3ca4..7111c0e 100644 --- a/src/player.rs +++ b/src/player.rs @@ -1,7 +1,7 @@ -use crate::{ - game_log::GameLog, CombatStats, Item, Map, Player, Position, RunState, State, Viewshed, - WantsToMelee, WantsToPickupItem, +use crate::components::{ + CombatStats, Item, Player, Position, Viewshed, WantsToMelee, WantsToPickupItem, }; +use crate::{game_log::GameLog, Map, RunState, State, TileType}; use rltk::{Point, Rltk, VirtualKeyCode}; use specs::prelude::*; use std::cmp::{max, min}; @@ -107,6 +107,13 @@ pub fn player_input(gs: &mut State, ctx: &mut Rltk) -> RunState { // Save and Quit VirtualKeyCode::Escape => return RunState::SaveGame, + // Level changes + VirtualKeyCode::Period => { + if try_next_level(&mut gs.ecs) { + return RunState::NextLevel; + } + } + _ => return RunState::AwaitingInput, }, } @@ -147,3 +154,19 @@ fn get_item(ecs: &mut World) { } } } + +pub fn try_next_level(ecs: &mut World) -> bool { + let player_pos = ecs.fetch::(); + let map = ecs.fetch::(); + let player_idx = map.xy_idx(player_pos.x, player_pos.y); + + if map.tiles[player_idx] == TileType::DownStairs { + true + } else { + let mut gamelog = ecs.fetch_mut::(); + gamelog + .entries + .push("There is no way down from here.".to_string()); + false + } +}