diff --git a/src/components.rs b/src/components.rs index c5e31e0..ee360d4 100644 --- a/src/components.rs +++ b/src/components.rs @@ -142,11 +142,9 @@ pub struct WantsToDropItem { pub item: Entity, } -pub struct SerializeMe; - -#[derive(Component, Serialize, Deserialize, Clone)] -pub struct SerializationHelper { - pub map: crate::map::Map, +#[derive(Component, Debug, ConvertSaveload, Clone)] +pub struct WantsToRemoveItem { + pub item: Entity, } #[derive(PartialEq, Copy, Clone, Serialize, Deserialize)] @@ -176,7 +174,13 @@ pub struct DefenseBonus { pub defense: i32, } -#[derive(Component, Debug, ConvertSaveload, Clone)] -pub struct WantsToRemoveItem { - pub item: Entity, +// Serialization helper code. We need to implement ConvertSaveLoad for each type that contains an +// Entity. + +pub struct SerializeMe; + +// Special component that exists to help serialize the game data +#[derive(Component, Serialize, Deserialize, Clone)] +pub struct SerializationHelper { + pub map: super::map::Map, } diff --git a/src/game_log.rs b/src/game_log.rs index d236c93..ab83087 100644 --- a/src/game_log.rs +++ b/src/game_log.rs @@ -4,9 +4,8 @@ pub struct GameLog { impl GameLog { pub fn new(first_entry: S) -> Self { - let mut entries: Vec = Vec::new(); - entries.push(first_entry.to_string()); - - GameLog { entries } + GameLog { + entries: vec![first_entry.to_string()], + } } } diff --git a/src/gui.rs b/src/gui.rs index 4f06e10..ebfc586 100644 --- a/src/gui.rs +++ b/src/gui.rs @@ -3,19 +3,6 @@ use crate::{game_log::GameLog, Equipped, Map, RunState, State}; use rltk::{Point, Rltk, VirtualKeyCode, RGB}; use specs::prelude::*; -#[derive(PartialEq, Copy, Clone)] -pub enum MainMenuSelection { - NewGame, - LoadGame, - Quit, -} - -#[derive(PartialEq, Copy, Clone)] -pub enum MainMenuResult { - NoSelection { selected: MainMenuSelection }, - Selected { selected: MainMenuSelection }, -} - pub fn draw_ui(ecs: &World, ctx: &mut Rltk) { ctx.draw_box( 0, @@ -359,6 +346,95 @@ pub fn drop_item_menu(gs: &mut State, ctx: &mut Rltk) -> (ItemMenuResult, Option } } +pub fn remove_item_menu(gs: &mut State, ctx: &mut Rltk) -> (ItemMenuResult, Option) { + let player_entity = gs.ecs.fetch::(); + let names = gs.ecs.read_storage::(); + let backpack = gs.ecs.read_storage::(); + let entities = gs.ecs.entities(); + + let inventory = (&backpack, &names) + .join() + .filter(|item| item.0.owner == *player_entity); + let count = inventory.count(); + + let mut y = (25 - (count / 2)) as i32; + ctx.draw_box( + 15, + y - 2, + 31, + (count + 3) as i32, + RGB::named(rltk::WHITE), + RGB::named(rltk::BLACK), + ); + ctx.print_color( + 18, + y - 2, + RGB::named(rltk::YELLOW), + RGB::named(rltk::BLACK), + "Remove Which Item?", + ); + ctx.print_color( + 18, + y + count as i32 + 1, + RGB::named(rltk::YELLOW), + RGB::named(rltk::BLACK), + "ESCAPE to cancel", + ); + + let mut equippable: Vec = Vec::new(); + let mut j = 0; + for (entity, _pack, name) in (&entities, &backpack, &names) + .join() + .filter(|item| item.1.owner == *player_entity) + { + ctx.set( + 17, + y, + RGB::named(rltk::WHITE), + RGB::named(rltk::BLACK), + rltk::to_cp437('('), + ); + ctx.set( + 18, + y, + RGB::named(rltk::YELLOW), + RGB::named(rltk::BLACK), + 97 + j as rltk::FontCharType, + ); + ctx.set( + 19, + y, + RGB::named(rltk::WHITE), + RGB::named(rltk::BLACK), + rltk::to_cp437(')'), + ); + + ctx.print(21, y, &name.name.to_string()); + + equippable.push(entity); + y += 1; + j += 1; + } + + match ctx.key { + None => (ItemMenuResult::NoResponse, None), + Some(key) => match key { + VirtualKeyCode::Escape => (ItemMenuResult::Cancel, None), + _ => { + let selection = rltk::letter_to_option(key); + if selection > -1 && selection < count as i32 { + return ( + ItemMenuResult::Selected, + Some(equippable[selection as usize]), + ); + } + + (ItemMenuResult::NoResponse, None) + } + }, + } +} + pub fn ranged_target( gs: &mut State, ctx: &mut Rltk, @@ -418,6 +494,19 @@ pub fn ranged_target( (ItemMenuResult::NoResponse, None) } +#[derive(PartialEq, Copy, Clone)] +pub enum MainMenuSelection { + NewGame, + LoadGame, + Quit, +} + +#[derive(PartialEq, Copy, Clone)] +pub enum MainMenuResult { + NoSelection { selected: MainMenuSelection }, + Selected { selected: MainMenuSelection }, +} + pub fn main_menu(gs: &mut State, ctx: &mut Rltk) -> MainMenuResult { let save_exists = crate::saveload_system::does_save_exist(); let runstate = gs.ecs.fetch::(); @@ -531,95 +620,6 @@ pub fn main_menu(gs: &mut State, ctx: &mut Rltk) -> MainMenuResult { } } -pub fn remove_item_menu(gs: &mut State, ctx: &mut Rltk) -> (ItemMenuResult, Option) { - let player_entity = gs.ecs.fetch::(); - let names = gs.ecs.read_storage::(); - let backpack = gs.ecs.read_storage::(); - let entities = gs.ecs.entities(); - - let inventory = (&backpack, &names) - .join() - .filter(|item| item.0.owner == *player_entity); - let count = inventory.count(); - - let mut y = (25 - (count / 2)) as i32; - ctx.draw_box( - 15, - y - 2, - 31, - (count + 3) as i32, - RGB::named(rltk::WHITE), - RGB::named(rltk::BLACK), - ); - ctx.print_color( - 18, - y - 2, - RGB::named(rltk::YELLOW), - RGB::named(rltk::BLACK), - "Remove Which Item?", - ); - ctx.print_color( - 18, - y + count as i32 + 1, - RGB::named(rltk::YELLOW), - RGB::named(rltk::BLACK), - "ESCAPE to cancel", - ); - - let mut equippable: Vec = Vec::new(); - let mut j = 0; - for (entity, _pack, name) in (&entities, &backpack, &names) - .join() - .filter(|item| item.1.owner == *player_entity) - { - ctx.set( - 17, - y, - RGB::named(rltk::WHITE), - RGB::named(rltk::BLACK), - rltk::to_cp437('('), - ); - ctx.set( - 18, - y, - RGB::named(rltk::YELLOW), - RGB::named(rltk::BLACK), - 97 + j as rltk::FontCharType, - ); - ctx.set( - 19, - y, - RGB::named(rltk::WHITE), - RGB::named(rltk::BLACK), - rltk::to_cp437(')'), - ); - - ctx.print(21, y, &name.name.to_string()); - - equippable.push(entity); - y += 1; - j += 1; - } - - match ctx.key { - None => (ItemMenuResult::NoResponse, None), - Some(key) => match key { - VirtualKeyCode::Escape => (ItemMenuResult::Cancel, None), - _ => { - let selection = rltk::letter_to_option(key); - if selection > -1 && selection < count as i32 { - return ( - ItemMenuResult::Selected, - Some(equippable[selection as usize]), - ); - } - - (ItemMenuResult::NoResponse, None) - } - }, - } -} - #[derive(PartialEq, Copy, Clone)] pub enum GameOverResult { NoSelection, diff --git a/src/inventory_system.rs b/src/inventory_system.rs index dcbfc17..cc7b319 100644 --- a/src/inventory_system.rs +++ b/src/inventory_system.rs @@ -65,6 +65,7 @@ impl<'a> System<'a> for ItemUseSystem { WriteStorage<'a, InBackpack>, ); + #[allow(clippy::cognitive_complexity)] fn run(&mut self, data: Self::SystemData) { let ( player_entity, @@ -267,6 +268,7 @@ impl<'a> System<'a> for ItemUseSystem { pub struct ItemDropSystem {} impl<'a> System<'a> for ItemDropSystem { + #[allow(clippy::type_complexity)] type SystemData = ( ReadExpect<'a, Entity>, WriteExpect<'a, GameLog>, @@ -323,6 +325,7 @@ impl<'a> System<'a> for ItemDropSystem { pub struct ItemRemoveSystem {} impl<'a> System<'a> for ItemRemoveSystem { + #[allow(clippy::type_complexity)] type SystemData = ( Entities<'a>, WriteStorage<'a, WantsToRemoveItem>, diff --git a/src/main.rs b/src/main.rs index a37249d..3d49f55 100644 --- a/src/main.rs +++ b/src/main.rs @@ -43,8 +43,6 @@ macro_rules! register { } } -pub const MAP_SIZE: usize = 80 * 50; - #[derive(PartialEq, Copy, Clone)] pub enum RunState { AwaitingInput, @@ -213,6 +211,25 @@ impl GameState for State { } } } + RunState::ShowRemoveItem => { + let result = gui::remove_item_menu(self, ctx); + match result.0 { + gui::ItemMenuResult::Cancel => newrunstate = RunState::AwaitingInput, + gui::ItemMenuResult::NoResponse => {} + gui::ItemMenuResult::Selected => { + let item_entity = result.1.unwrap(); + let mut intent = self.ecs.write_storage::(); + intent + .insert( + *self.ecs.fetch::(), + WantsToRemoveItem { item: item_entity }, + ) + .expect("Unable to insert intent to remove item"); + + newrunstate = RunState::PlayerTurn; + } + } + } RunState::ShowTargeting { range, item } => { let result = gui::ranged_target(self, ctx, range); match result.0 { @@ -253,6 +270,15 @@ impl GameState for State { } }, }, + RunState::GameOver => match gui::game_over(ctx) { + gui::GameOverResult::NoSelection => {} + gui::GameOverResult::QuitToMenu => { + self.game_over_cleanup(); + newrunstate = RunState::MainMenu { + menu_selection: gui::MainMenuSelection::NewGame, + }; + } + }, RunState::SaveGame => { saveload_system::save_game(&mut self.ecs); @@ -264,34 +290,6 @@ impl GameState for State { self.goto_next_level(); newrunstate = RunState::PreRun; } - RunState::ShowRemoveItem => { - let result = gui::remove_item_menu(self, ctx); - match result.0 { - gui::ItemMenuResult::Cancel => newrunstate = RunState::AwaitingInput, - gui::ItemMenuResult::NoResponse => {} - gui::ItemMenuResult::Selected => { - let item_entity = result.1.unwrap(); - let mut intent = self.ecs.write_storage::(); - intent - .insert( - *self.ecs.fetch::(), - WantsToRemoveItem { item: item_entity }, - ) - .expect("Unable to insert intent to remove item"); - - newrunstate = RunState::PlayerTurn; - } - } - } - RunState::GameOver => match gui::game_over(ctx) { - gui::GameOverResult::NoSelection => {} - gui::GameOverResult::QuitToMenu => { - self.game_over_cleanup(); - newrunstate = RunState::MainMenu { - menu_selection: gui::MainMenuSelection::NewGame, - }; - } - }, } { @@ -504,7 +502,9 @@ fn main() -> rltk::BError { gs.ecs.insert(map); gs.ecs.insert(Point::new(player_x, player_y)); gs.ecs.insert(player_entity); - gs.ecs.insert(RunState::PreRun); + gs.ecs.insert(RunState::MainMenu { + menu_selection: gui::MainMenuSelection::NewGame, + }); gs.ecs.insert(GameLog::new("Welcome to Rusty Roguelike")); rltk::main_loop(context, gs) diff --git a/src/map.rs b/src/map.rs index 0f61fb1..248cfdf 100644 --- a/src/map.rs +++ b/src/map.rs @@ -65,6 +65,28 @@ impl Map { } } + fn is_exit_valid(&self, x: i32, y: i32) -> bool { + if x < 1 || x > self.width - 1 || y < 1 || y > self.height - 1 { + return false; + } + + let idx = self.xy_idx(x, y); + + !self.blocked[idx] + } + + pub fn populate_blocked(&mut self) { + for (i, tile) in self.tiles.iter_mut().enumerate() { + self.blocked[i] = *tile == TileType::Wall; + } + } + + pub fn clear_content_index(&mut self) { + for content in self.tile_content.iter_mut() { + content.clear(); + } + } + /// 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(new_depth: i32) -> Map { @@ -127,34 +149,6 @@ impl Map { map } - - fn is_exit_valid(&self, x: i32, y: i32) -> bool { - if x < 1 || x > self.width - 1 || y < 1 || y > self.height - 1 { - return false; - } - - let idx = self.xy_idx(x, y); - - !self.blocked[idx] - } - - pub fn populate_blocked(&mut self) { - for (i, tile) in self.tiles.iter_mut().enumerate() { - self.blocked[i] = *tile == TileType::Wall; - } - } - - pub fn clear_content_index(&mut self) { - for content in self.tile_content.iter_mut() { - content.clear(); - } - } -} - -impl Algorithm2D for Map { - fn dimensions(&self) -> Point { - Point::new(self.width, self.height) - } } impl BaseMap for Map { @@ -208,6 +202,12 @@ impl BaseMap for Map { } } +impl Algorithm2D for Map { + fn dimensions(&self) -> Point { + Point::new(self.width, self.height) + } +} + pub fn draw_map(ecs: &World, ctx: &mut Rltk) { let map = ecs.fetch::(); diff --git a/src/melee_combat_system.rs b/src/melee_combat_system.rs index 7114085..b9679ff 100644 --- a/src/melee_combat_system.rs +++ b/src/melee_combat_system.rs @@ -7,6 +7,7 @@ use specs::prelude::*; pub struct MeleeCombatSystem {} impl<'a> System<'a> for MeleeCombatSystem { + #[allow(clippy::type_complexity)] type SystemData = ( Entities<'a>, WriteExpect<'a, GameLog>, diff --git a/src/player.rs b/src/player.rs index c720b4b..bfc84f5 100644 --- a/src/player.rs +++ b/src/player.rs @@ -54,6 +54,86 @@ pub fn try_move_player(delta_x: i32, delta_y: i32, 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 + } +} + +fn get_item(ecs: &mut World) { + let player_pos = ecs.fetch::(); + let player_entity = ecs.fetch::(); + let entities = ecs.entities(); + let items = ecs.read_storage::(); + let positions = ecs.read_storage::(); + let mut gamelog = ecs.fetch_mut::(); + + let mut target_item: Option = None; + for (item_entity, _item, position) in (&entities, &items, &positions).join() { + if position.x == player_pos.x && position.y == player_pos.y { + target_item = Some(item_entity); + } + } + + match target_item { + None => gamelog + .entries + .push("There is nothing here to pick up.".to_string()), + Some(item) => { + let mut pickup = ecs.write_storage::(); + pickup + .insert( + *player_entity, + WantsToPickupItem { + collected_by: *player_entity, + item, + }, + ) + .expect("Unable to pick up item?!"); + } + } +} + +fn skip_turn(ecs: &mut World) -> RunState { + let player_entity = ecs.fetch::(); + let viewshed_components = ecs.read_storage::(); + let monsters = ecs.read_storage::(); + + let worldmap_resource = ecs.fetch::(); + + let mut can_heal = true; + let viewshed = viewshed_components.get(*player_entity).unwrap(); + for tile in viewshed.visible_tiles.iter() { + let idx = worldmap_resource.xy_idx(tile.x, tile.y); + for entity_id in worldmap_resource.tile_content[idx].iter() { + match monsters.get(*entity_id) { + None => {} + Some(_) => { + can_heal = false; + } + } + } + } + + if can_heal { + let mut health_components = ecs.write_storage::(); + let player_hp = health_components.get_mut(*player_entity).unwrap(); + player_hp.hp = i32::min(player_hp.hp + 1, player_hp.max_hp); + } + + RunState::PlayerTurn +} + pub fn player_input(gs: &mut State, ctx: &mut Rltk) -> RunState { // Player movement match ctx.key { @@ -126,83 +206,3 @@ pub fn player_input(gs: &mut State, ctx: &mut Rltk) -> RunState { RunState::PlayerTurn } - -fn get_item(ecs: &mut World) { - let player_pos = ecs.fetch::(); - let player_entity = ecs.fetch::(); - let entities = ecs.entities(); - let items = ecs.read_storage::(); - let positions = ecs.read_storage::(); - let mut gamelog = ecs.fetch_mut::(); - - let mut target_item: Option = None; - for (item_entity, _item, position) in (&entities, &items, &positions).join() { - if position.x == player_pos.x && position.y == player_pos.y { - target_item = Some(item_entity); - } - } - - match target_item { - None => gamelog - .entries - .push("There is nothing here to pick up.".to_string()), - Some(item) => { - let mut pickup = ecs.write_storage::(); - pickup - .insert( - *player_entity, - WantsToPickupItem { - collected_by: *player_entity, - item, - }, - ) - .expect("Unable to pick up item?!"); - } - } -} - -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 - } -} - -fn skip_turn(ecs: &mut World) -> RunState { - let player_entity = ecs.fetch::(); - let viewshed_components = ecs.read_storage::(); - let monsters = ecs.read_storage::(); - - let worldmap_resource = ecs.fetch::(); - - let mut can_heal = true; - let viewshed = viewshed_components.get(*player_entity).unwrap(); - for tile in viewshed.visible_tiles.iter() { - let idx = worldmap_resource.xy_idx(tile.x, tile.y); - for entity_id in worldmap_resource.tile_content[idx].iter() { - match monsters.get(*entity_id) { - None => {} - Some(_) => { - can_heal = false; - } - } - } - } - - if can_heal { - let mut health_components = ecs.write_storage::(); - let player_hp = health_components.get_mut(*player_entity).unwrap(); - player_hp.hp = i32::min(player_hp.hp + 1, player_hp.max_hp); - } - - RunState::PlayerTurn -} diff --git a/src/saveload_system.rs b/src/saveload_system.rs index cf12730..a4ca3d8 100644 --- a/src/saveload_system.rs +++ b/src/saveload_system.rs @@ -22,21 +22,6 @@ macro_rules! serialize_individually { }; } -macro_rules! deserialize_individually { - ($ecs:expr, $de:expr, $data:expr, $( $type:ty),*) => { - $( - DeserializeComponents::::deserialize( - &mut ( &mut $ecs.write_storage::<$type>(), ), - &mut $data.0, // entities - &mut $data.1, // marker - &mut $data.2, // allocater - &mut $de, - ) - .unwrap(); - )* - }; -} - #[cfg(target_arch = "wasm32")] pub fn save_game(_ecs: &mut World) {} @@ -102,6 +87,21 @@ pub fn does_save_exist() -> bool { Path::new("./savegame.json").exists() } +macro_rules! deserialize_individually { + ($ecs:expr, $de:expr, $data:expr, $( $type:ty),*) => { + $( + DeserializeComponents::::deserialize( + &mut ( &mut $ecs.write_storage::<$type>(), ), + &mut $data.0, // entities + &mut $data.1, // marker + &mut $data.2, // allocater + &mut $de, + ) + .unwrap(); + )* + }; +} + pub fn load_game(ecs: &mut World) { { // Delete everything @@ -183,7 +183,7 @@ pub fn load_game(ecs: &mut World) { } ecs.delete_entity(deleteme.unwrap()) - .expect("Unable to delete helper entitiy"); + .expect("Unable to delete helper entity"); } pub fn delete_save() { diff --git a/src/spawner.rs b/src/spawner.rs index b5ff8ec..a176352 100644 --- a/src/spawner.rs +++ b/src/spawner.rs @@ -3,7 +3,7 @@ use crate::components::{ Equippable, InflictsDamage, Item, MeleePowerBonus, Monster, Name, Player, Position, ProvidesHealing, Ranged, Renderable, SerializeMe, Viewshed, }; -use crate::{random_table::RandomTable, Rect, MAP_WIDTH}; +use crate::{random_table::RandomTable, Rect, map::MAP_WIDTH}; use rltk::{RandomNumberGenerator, RGB}; use specs::prelude::*; use specs::saveload::{MarkedBuilder, SimpleMarker}; @@ -146,6 +146,7 @@ fn health_potion(ecs: &mut World, x: i32, y: i32) { .with(Item {}) .with(Consumable {}) .with(ProvidesHealing { heal_amount: 8 }) + .marked::>() .build(); } @@ -162,7 +163,7 @@ fn magic_missile_scroll(ecs: &mut World, x: i32, y: i32) { .with(Item {}) .with(Consumable {}) .with(Ranged { range: 6 }) - .with(InflictsDamage { damage: 8 }) + .with(InflictsDamage { damage: 20 }) .marked::>() .build(); }