diff --git a/src/display/color.rs b/src/display/color.rs index b1e799c5e..afe003999 100644 --- a/src/display/color.rs +++ b/src/display/color.rs @@ -15,6 +15,15 @@ impl Color { } } +impl PartialEq for Color { + fn eq(&self, other: &Self) -> bool { + format!("{}{}", color::Fg(self), color::Bg(self)) + == format!("{}{}", color::Fg(other), color::Bg(other)) + } +} + +impl Eq for Color {} + impl color::Color for Color { fn write_fg(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.write_fg(f) diff --git a/src/entities/character.rs b/src/entities/character.rs index 360478e8b..3e8336b12 100644 --- a/src/entities/character.rs +++ b/src/entities/character.rs @@ -1,4 +1,5 @@ use crate::display; +use crate::entities::item::Item; use crate::types::{Position, Speed}; use std::io::{self, Write}; @@ -7,6 +8,7 @@ const DEFAULT_SPEED: Speed = Speed(100); entity! { pub struct Character { pub o_name: Option, + pub inventory: Vec>, } } @@ -18,6 +20,7 @@ impl Character { id: None, position: Position { x: 0, y: 0 }, o_name: None, + inventory: Vec::new(), } } diff --git a/src/entities/entity_char.rs b/src/entities/entity_char.rs index 88ca8a55a..70f26bfff 100644 --- a/src/entities/entity_char.rs +++ b/src/entities/entity_char.rs @@ -2,7 +2,7 @@ use crate::display::color::Color; use std::fmt::{self, Display, Formatter}; use termion::color; -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, PartialEq, Eq)] pub struct EntityChar { #[serde(default)] color: Color, diff --git a/src/entities/item.rs b/src/entities/item.rs index aa99fb42e..5f08780d4 100644 --- a/src/entities/item.rs +++ b/src/entities/item.rs @@ -4,7 +4,7 @@ use crate::entities::{Describe, EntityID}; use crate::types::Position; use std::io::{self, Write}; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct Item { pub id: Option, pub typ: &'static ItemType<'static>, diff --git a/src/entities/raw_types.rs b/src/entities/raw_types.rs index 59dd19ed2..4bc291b69 100644 --- a/src/entities/raw_types.rs +++ b/src/entities/raw_types.rs @@ -17,7 +17,7 @@ pub struct CreatureType<'a> { pub friendly: bool, } -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, PartialEq, Eq)] pub struct EdibleItem<'a> { #[serde(borrow)] pub eat_message: Option>, @@ -26,7 +26,7 @@ pub struct EdibleItem<'a> { pub hitpoints_healed: u16, } -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, PartialEq, Eq)] pub struct ItemType<'a> { pub name: &'a str, diff --git a/src/game.rs b/src/game.rs index 2c6906151..c478e0d2f 100644 --- a/src/game.rs +++ b/src/game.rs @@ -1,5 +1,7 @@ use crate::description::list_to_sentence; use crate::display::{self, Viewport}; +use crate::entities::entity::Describe; +use crate::entities::entity::Entity; use crate::entities::{ AnEntity, Character, Creature, EntityID, Identified, Item, }; @@ -187,8 +189,7 @@ impl<'a> Game<'a> { } } - /// Returns a list of all creature entities at the given position - fn creatures_at<'b>(&'b self, pos: Position) -> Vec<&'b Creature> { + fn downcast_entities_at(&self, pos: Position) -> Vec<&A> { self.entities .at(pos) .iter() @@ -196,6 +197,16 @@ impl<'a> Game<'a> { .collect() } + /// Returns a list of all creature entities at the given position + fn creatures_at(&self, pos: Position) -> Vec<&Creature> { + self.downcast_entities_at(pos) + } + + /// Returns a list of all item entities at the given position + fn items_at(&self, pos: Position) -> Vec<&Item> { + self.downcast_entities_at(pos) + } + /// Returns a collision, if any, at the given Position in the game fn collision_at(&self, pos: Position) -> Option { if !pos.within(self.viewport.inner) { @@ -436,6 +447,31 @@ impl<'a> Game<'a> { } } + fn pick_up(&mut self) -> io::Result<()> { + let pos = self.character().position; + let items = self.items_at(pos); + match items.len() { + 0 => Ok(()), + 1 => { + let item_id = items.get(0).unwrap().id(); + let item: Box = + self.entities.remove(item_id).unwrap().downcast().unwrap(); + let desc = item.description(); + self.mut_character().inventory.push(item); + self.say( + "global.pick_up", + &template_params!({ + "item" => { "name" => &desc, }, + }), + ) + } + _ => { + // TODO prompt with a menu of items to pick up + unimplemented!() + } + } + } + fn flush_promises(&mut self) { unsafe { let game = self as *mut Self; @@ -498,6 +534,8 @@ impl<'a> Game<'a> { Some(PreviousMessage) => self.previous_message()?, + Some(PickUp) => self.pick_up()?, + None => (), } diff --git a/src/messages.toml b/src/messages.toml index 7c6255142..a9a6b2e00 100644 --- a/src/messages.toml +++ b/src/messages.toml @@ -2,6 +2,7 @@ welcome = "Welcome to Xanthous, {{character.name}}! It's dangerous out there, why not stay inside?" describe_entities = "You see here {{descriptions}}" describe_no_entities = "You see nothing here." +pick_up = "You pick up the {{item.name}}." [combat] attack = "You attack the {{creature.name}}." diff --git a/src/types/command.rs b/src/types/command.rs index 15017cde9..17ca4d280 100644 --- a/src/types/command.rs +++ b/src/types/command.rs @@ -10,6 +10,9 @@ pub enum Command { /// Move the character in a direction Move(Direction), + /// Pick up any item(s) at the current position + PickUp, + /// Display the previous message PreviousMessage, } @@ -19,6 +22,7 @@ impl Command { use Command::*; match k { Char('q') => Some(Quit), + Char('h') | Char('a') | Key::Left => Some(Move(Left)), Char('k') | Char('w') | Key::Up => Some(Move(Up)), Char('j') | Char('s') | Key::Down => Some(Move(Down)), @@ -29,6 +33,8 @@ impl Command { Char('n') => Some(Move(DownRight)), Ctrl('p') => Some(PreviousMessage), + Char(',') => Some(PickUp), + _ => None, } }