From 0ac2226947f52b1192ae955dab7c297335c6932b Mon Sep 17 00:00:00 2001 From: Timothy Warren Date: Wed, 3 Nov 2021 15:11:19 -0400 Subject: [PATCH] Start of inventory system --- src/components.rs | 27 +++++++++++-- src/gui.rs | 86 ++++++++++++++++++++++++++++++++++++++++- src/inventory_system.rs | 42 ++++++++++++++++++++ src/main.rs | 15 +++++++ src/player.rs | 46 +++++++++++++++++++++- src/spawner.rs | 43 ++++++++++++++++++++- 6 files changed, 251 insertions(+), 8 deletions(-) create mode 100644 src/inventory_system.rs diff --git a/src/components.rs b/src/components.rs index c2c6f12..a23c6bd 100644 --- a/src/components.rs +++ b/src/components.rs @@ -25,18 +25,18 @@ pub struct Viewshed { pub dirty: bool, } -#[derive(Component)] +#[derive(Component, Debug)] pub struct Monster {} -#[derive(Component)] +#[derive(Component, Debug)] pub struct Name { pub name: String, } -#[derive(Component)] +#[derive(Component, Debug)] pub struct BlocksTile {} -#[derive(Component)] +#[derive(Component, Debug)] pub struct CombatStats { pub max_hp: i32, pub hp: i32, @@ -66,3 +66,22 @@ impl SufferDamage { } } } + +#[derive(Component, Debug)] +pub struct Item {} + +#[derive(Component, Debug)] +pub struct Potion { + pub heal_amount: i32, +} + +#[derive(Component, Debug, Clone)] +pub struct InBackpack { + pub owner: Entity, +} + +#[derive(Component, Debug, Clone)] +pub struct WantsToPickupItem { + pub collected_by: Entity, + pub item: Entity, +} diff --git a/src/gui.rs b/src/gui.rs index 8506c43..afc0dc7 100644 --- a/src/gui.rs +++ b/src/gui.rs @@ -1,5 +1,5 @@ -use crate::{gamelog::GameLog, CombatStats, Map, Name, Player, Position}; -use rltk::{Point, Rltk, RGB}; +use crate::{gamelog::GameLog, CombatStats, InBackpack, Map, Name, Player, Position, State}; +use rltk::{Point, Rltk, VirtualKeyCode, RGB}; use specs::prelude::*; pub fn draw_ui(ecs: &World, ctx: &mut Rltk) { @@ -153,3 +153,85 @@ fn draw_tooltips(ecs: &World, ctx: &mut Rltk) { } } } + +#[derive(PartialEq, Copy, Clone)] +pub enum ItemMenuResult { + Cancel, + NoResponse, + Selected, +} + +pub fn show_inventory(gs: &mut State, ctx: &mut Rltk) -> ItemMenuResult { + let player_entity = gs.ecs.fetch::(); + let names = gs.ecs.read_storage::(); + let backpack = gs.ecs.read_storage::(); + + 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, + 32, + (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), + "Inventory", + ); + ctx.print_color( + 18, + y + count as i32 + 1, + RGB::named(rltk::YELLOW), + RGB::named(rltk::BLACK), + "ESCAPE to cancel", + ); + + let mut j = 0; + for (_pack, name) in (&backpack, &names) + .join() + .filter(|item| item.0.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()); + y += 1; + j += 1; + } + + match ctx.key { + None => ItemMenuResult::NoResponse, + Some(key) => match key { + VirtualKeyCode::Escape => ItemMenuResult::Cancel, + _ => ItemMenuResult::NoResponse, + }, + } +} diff --git a/src/inventory_system.rs b/src/inventory_system.rs new file mode 100644 index 0000000..56f9a5a --- /dev/null +++ b/src/inventory_system.rs @@ -0,0 +1,42 @@ +use crate::{gamelog::GameLog, InBackpack, Name, Position, WantsToPickupItem}; +use specs::prelude::*; + +pub struct ItemCollectionSystem {} + +impl<'a> System<'a> for ItemCollectionSystem { + #[allow(clippy::type_complexity)] + type SystemData = ( + ReadExpect<'a, Entity>, + WriteExpect<'a, GameLog>, + WriteStorage<'a, WantsToPickupItem>, + WriteStorage<'a, Position>, + ReadStorage<'a, Name>, + WriteStorage<'a, InBackpack>, + ); + + fn run(&mut self, data: Self::SystemData) { + let (player_entity, mut gamelog, mut wants_pickup, mut positions, names, mut backpack) = + data; + + for pickup in wants_pickup.join() { + positions.remove(pickup.item); + backpack + .insert( + pickup.item, + InBackpack { + owner: pickup.collected_by, + }, + ) + .expect("Failed to add item to backpack"); + + if pickup.collected_by == *player_entity { + gamelog.entries.push(format!( + "You pick up the {}.", + names.get(pickup.item).unwrap().name + )); + } + } + + wants_pickup.clear(); + } +} diff --git a/src/main.rs b/src/main.rs index 034611a..84f13ac 100644 --- a/src/main.rs +++ b/src/main.rs @@ -22,7 +22,9 @@ use damage_system::DamageSystem; mod gamelog; mod gui; pub use gamelog::GameLog; +mod inventory_system; mod spawner; +use inventory_system::ItemCollectionSystem; pub const MAP_SIZE: usize = 80 * 50; @@ -32,6 +34,7 @@ pub enum RunState { PreRun, PlayerTurn, MonsterTurn, + ShowInventory, } pub struct State { @@ -55,6 +58,9 @@ impl State { let mut damage = DamageSystem {}; damage.run_now(&self.ecs); + let mut pickup = ItemCollectionSystem {}; + pickup.run_now(&self.ecs); + self.ecs.maintain(); } } @@ -84,6 +90,11 @@ impl GameState for State { self.run_systems(); newrunstate = RunState::AwaitingInput; } + RunState::ShowInventory => { + if gui::show_inventory(self, ctx) == gui::ItemMenuResult::Cancel { + newrunstate = RunState::AwaitingInput; + } + } } { @@ -130,6 +141,10 @@ fn main() -> rltk::BError { gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); + gs.ecs.register::(); + gs.ecs.register::(); + gs.ecs.register::(); + gs.ecs.register::(); let map: Map = Map::new_map_rooms_and_corridors(); let (player_x, player_y) = map.rooms[0].center(); diff --git a/src/player.rs b/src/player.rs index 0b10fd8..fbd0655 100644 --- a/src/player.rs +++ b/src/player.rs @@ -1,4 +1,7 @@ -use crate::{CombatStats, Map, Player, Position, RunState, State, Viewshed, WantsToMelee}; +use crate::{ + gamelog::GameLog, CombatStats, Item, Map, Player, Position, RunState, State, Viewshed, + WantsToMelee, WantsToPickupItem, +}; use rltk::{Point, Rltk, VirtualKeyCode}; use specs::prelude::*; use std::cmp::{max, min}; @@ -56,6 +59,7 @@ pub fn player_input(gs: &mut State, ctx: &mut Rltk) -> RunState { match ctx.key { None => return RunState::AwaitingInput, // Nothing happened Some(key) => match key { + // Cardinal directions VirtualKeyCode::Left | VirtualKeyCode::Numpad4 | VirtualKeyCode::H @@ -91,9 +95,49 @@ pub fn player_input(gs: &mut State, ctx: &mut Rltk) -> RunState { try_move_player(-1, 1, &mut gs.ecs) } + // Pick up item + VirtualKeyCode::G => get_item(&mut gs.ecs), + + // Show inventory + VirtualKeyCode::I => return RunState::ShowInventory, + _ => return RunState::AwaitingInput, }, } 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?!"); + } + } +} diff --git a/src/spawner.rs b/src/spawner.rs index fd49a47..19c21db 100644 --- a/src/spawner.rs +++ b/src/spawner.rs @@ -1,5 +1,6 @@ use crate::{ - BlocksTile, CombatStats, Monster, Name, Player, Position, Rect, Renderable, Viewshed, MAP_WIDTH, + BlocksTile, CombatStats, Item, Monster, Name, Player, Position, Potion, Rect, Renderable, + Viewshed, MAP_WIDTH, }; use rltk::{RandomNumberGenerator, RGB}; use specs::prelude::*; @@ -90,11 +91,13 @@ fn monster(ecs: &mut World, x: i32, y: i32, glyph: rltk::FontCharTy /// fills a room with stuff! pub fn spawn_room(ecs: &mut World, room: &Rect) { let mut monster_spawn_points: Vec = Vec::new(); + let mut item_spawn_points: Vec = Vec::new(); // Scope to keep the borrow checker happy { let mut rng = ecs.write_resource::(); let num_monsters = rng.roll_dice(1, MAX_MONSTERS + 2) - 3; + let num_items = rng.roll_dice(1, MAX_ITEMS + 2) - 3; for _i in 0..num_monsters { let mut added = false; @@ -109,6 +112,20 @@ pub fn spawn_room(ecs: &mut World, room: &Rect) { } } } + + for _i in 0..num_items { + let mut added = false; + while !added { + let x = (room.x1 + rng.roll_dice(1, i32::abs(room.x2 - room.x1))) as usize; + let y = (room.y1 + rng.roll_dice(1, i32::abs(room.y2 - room.y1))) as usize; + let idx = (y * MAP_WIDTH) + x; + + if !item_spawn_points.contains(&idx) { + item_spawn_points.push(idx); + added = true; + } + } + } } // Actually spawn the monsters @@ -118,4 +135,28 @@ pub fn spawn_room(ecs: &mut World, room: &Rect) { random_monster(ecs, x as i32, y as i32); } + + // Actually spawn the potions + for idx in item_spawn_points.iter() { + let x = *idx % MAP_WIDTH; + let y = *idx / MAP_WIDTH; + + health_potion(ecs, x as i32, y as i32); + } +} + +fn health_potion(ecs: &mut World, x: i32, y: i32) { + ecs.create_entity() + .with(Position { x, y }) + .with(Renderable { + glyph: rltk::to_cp437('ยก'), + fg: RGB::named(rltk::MAGENTA), + bg: RGB::named(rltk::BLACK), + }) + .with(Name { + name: "Health Potion".to_string(), + }) + .with(Item {}) + .with(Potion { heal_amount: 8 }) + .build(); }