about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorGriffin Smith <root@gws.fyi>2019-07-29T15·22-0400
committerGriffin Smith <root@gws.fyi>2019-07-29T15·22-0400
commit9db5fad2f900732d59f9714ac4517952d26506d7 (patch)
tree1f8de240a1f7ccecd37282b0f984e1d0a7d38000 /src
parent34b20b7786a8f6753bb449425772958e0285c385 (diff)
Describe what you see when you walk over it
If the character walks over any number of entities, describe those
entities to the character.
Diffstat (limited to 'src')
-rw-r--r--src/entities/character.rs19
-rw-r--r--src/entities/creature.rs8
-rw-r--r--src/entities/entity.rs45
-rw-r--r--src/entities/environment.rs2
-rw-r--r--src/entities/item.rs8
-rw-r--r--src/entities/mod.rs2
-rw-r--r--src/entities/raw_types.rs12
-rw-r--r--src/entities/raws/noodles.json6
-rw-r--r--src/game.rs93
-rw-r--r--src/messages.toml2
-rw-r--r--src/util/mod.rs2
-rw-r--r--src/util/trait_impls.rs17
12 files changed, 168 insertions, 48 deletions
diff --git a/src/entities/character.rs b/src/entities/character.rs
index b2da47609670..59d4d00a4b65 100644
--- a/src/entities/character.rs
+++ b/src/entities/character.rs
@@ -1,22 +1,17 @@
 use crate::display;
-use crate::entities::EntityID;
 use crate::types::{Position, Speed};
-use proptest_derive::Arbitrary;
 use std::io::{self, Write};
-use termion::cursor;
 
 const DEFAULT_SPEED: Speed = Speed(100);
 
-#[derive(Debug, PartialEq, Eq, Arbitrary, Clone)]
-pub struct Character {
-    pub id: Option<EntityID>,
-
-    /// The position of the character, relative to the game
-    pub position: Position,
-
-    pub o_name: Option<String>,
+entity! {
+    pub struct Character {
+        pub o_name: Option<String>,
+    }
 }
 
+static_description!(Character, "yourself");
+
 impl Character {
     pub fn new() -> Character {
         Character {
@@ -46,8 +41,6 @@ impl Character {
     }
 }
 
-entity!(Character);
-
 impl display::Draw for Character {
     fn do_draw(&self, out: &mut Write) -> io::Result<()> {
         write!(out, "@")
diff --git a/src/entities/creature.rs b/src/entities/creature.rs
index 9fd8d23c752e..4cf6f60bdc6c 100644
--- a/src/entities/creature.rs
+++ b/src/entities/creature.rs
@@ -1,7 +1,7 @@
 use crate::display;
 use crate::entities::raws::CreatureType;
 use crate::entities::raws::EntityRaw;
-use crate::entities::{raw, EntityID};
+use crate::entities::{raw, Describe, EntityID};
 use crate::types::Position;
 use std::io::{self, Write};
 
@@ -50,6 +50,12 @@ impl Creature {
 
 entity!(Creature);
 
+impl Describe for Creature {
+    fn description(&self) -> String {
+        self.typ.description.to_string()
+    }
+}
+
 impl display::Draw for Creature {
     fn do_draw(&self, out: &mut Write) -> io::Result<()> {
         write!(out, "{}", self.typ.chr)
diff --git a/src/entities/entity.rs b/src/entities/entity.rs
index 7fedb77b2562..0043a83ecd54 100644
--- a/src/entities/entity.rs
+++ b/src/entities/entity.rs
@@ -1,6 +1,7 @@
 use crate::display::DrawWithNeighbors;
 use crate::entities::EntityID;
 use crate::types::Neighbors;
+use crate::types::Position;
 use crate::types::{Positioned, PositionedMut};
 use downcast_rs::Downcast;
 use std::fmt::Debug;
@@ -37,8 +38,36 @@ impl<ID, A: Identified<ID>> Identified<ID> for Box<A> {
     }
 }
 
+pub trait Describe {
+    fn description(&self) -> String;
+}
+
+ref_impl! {
+    impl<T: Describe> Describe for &T {
+        fn description(&self) -> String {
+            (**self).description()
+        }
+    }
+}
+
+#[macro_export]
+macro_rules! static_description {
+    ($name: ident, $description: expr) => {
+        impl $crate::entities::entity::Describe for $name {
+            fn description(&self) -> String {
+                $description.to_string()
+            }
+        }
+    };
+}
+
 pub trait Entity:
-    Positioned + PositionedMut + Identified<EntityID> + DrawWithNeighbors + Downcast
+    Positioned
+    + PositionedMut
+    + Identified<EntityID>
+    + DrawWithNeighbors
+    + Downcast
+    + Describe
 {
 }
 
@@ -80,3 +109,17 @@ impl DrawWithNeighbors for Box<dyn Entity> {
         (**self).do_draw_with_neighbors(out, neighbors)
     }
 }
+
+pub type AnEntity = Box<dyn Entity>;
+
+impl Positioned for AnEntity {
+    fn position(&self) -> Position {
+        (**self).position()
+    }
+}
+
+impl PositionedMut for AnEntity {
+    fn set_position(&mut self, pos: Position) {
+        (**self).set_position(pos)
+    }
+}
diff --git a/src/entities/environment.rs b/src/entities/environment.rs
index 64366a505496..042873ec5a12 100644
--- a/src/entities/environment.rs
+++ b/src/entities/environment.rs
@@ -10,6 +10,8 @@ entity! {
     }
 }
 
+static_description!(Wall, "a wall");
+
 impl Wall {
     pub fn new(position: Position, style: BoxStyle) -> Self {
         new_entity!(Wall { position, style })
diff --git a/src/entities/item.rs b/src/entities/item.rs
index d0ecc090e2e4..6e47a87f5b83 100644
--- a/src/entities/item.rs
+++ b/src/entities/item.rs
@@ -1,6 +1,6 @@
 use crate::display;
 use crate::entities::raws::{raw, EntityRaw, ItemType};
-use crate::entities::EntityID;
+use crate::entities::{Describe, EntityID};
 use crate::types::Position;
 use std::io::{self, Write};
 
@@ -37,6 +37,12 @@ impl Item {
 
 entity!(Item);
 
+impl Describe for Item {
+    fn description(&self) -> String {
+        self.typ.description.to_string()
+    }
+}
+
 impl display::Draw for Item {
     fn do_draw(&self, out: &mut Write) -> io::Result<()> {
         write!(out, "{}", self.typ.chr)
diff --git a/src/entities/mod.rs b/src/entities/mod.rs
index 3fe84c76f8ef..a8c39ed8aa78 100644
--- a/src/entities/mod.rs
+++ b/src/entities/mod.rs
@@ -12,7 +12,7 @@ pub mod raws;
 
 pub use character::Character;
 pub use creature::Creature;
-pub use entity::{Entity, Identified};
+pub use entity::{AnEntity, Describe, Entity, Identified};
 pub use entity_char::EntityChar;
 pub use item::Item;
 pub use raws::raw;
diff --git a/src/entities/raw_types.rs b/src/entities/raw_types.rs
index 8f64e60d9cd4..59dd19ed2fa2 100644
--- a/src/entities/raw_types.rs
+++ b/src/entities/raw_types.rs
@@ -30,9 +30,13 @@ pub struct EdibleItem<'a> {
 pub struct ItemType<'a> {
     pub name: &'a str,
 
-    /// A description of the item, used by the "look" command
+    /// A description of the item, used by the "look" command and when walking
+    /// over the item on the ground
     pub description: &'a str,
 
+    /// A longer description of the item
+    pub long_description: &'a str,
+
     pub edible_item: Option<EdibleItem<'a>>,
 
     #[serde(rename = "char")]
@@ -49,7 +53,8 @@ mod item_type_tests {
             r#"{
                 "Item": {
                     "name": "noodles",
-                    "description": "You know exactly what kind of noodles",
+                    "description": "a big bowl o' noodles",
+                    "long_description": "You know exactly what kind of noodles",
                     "char": { "char": "n" },
                     "edible_item": {
                         "eat_message": "You slurp up the noodles",
@@ -67,7 +72,8 @@ mod item_type_tests {
         let toml_result = toml::from_str(
             r#"[Item]
 name = "noodles"
-description = "You know exactly what kind of noodles"
+description = "a big bowl o' noodles"
+long_description = "You know exactly what kind of noodles"
 char = { char = "🍜" }
 edible_item = { eat_message = "You slurp up the noodles", hitpoints_healed = 2 }
 "#,
diff --git a/src/entities/raws/noodles.json b/src/entities/raws/noodles.json
index 6e2ecded7513..dfa2609f5ecb 100644
--- a/src/entities/raws/noodles.json
+++ b/src/entities/raws/noodles.json
@@ -5,11 +5,11 @@
       "char": "n",
       "color": "yellow"
     },
-    "description": "You know exactly what kind of noodles",
+    "description": "a big bowl o' noodles",
+    "long_description": "You know exactly what kind of noodles",
     "edible_item": {
       "eat_message": "You slurp up the noodles",
       "hitpoints_healed": 2
-    },
-    "display_name": "big bowl o' noodles"
+    }
   }
 }
diff --git a/src/game.rs b/src/game.rs
index dd45b3009a37..a42edb553711 100644
--- a/src/game.rs
+++ b/src/game.rs
@@ -1,14 +1,14 @@
+use crate::description::list_to_sentence;
 use crate::display::{self, Viewport};
 use crate::entities::{
-    Character, Creature, Entity, EntityID, Identified, Item,
+    AnEntity, Character, Creature, EntityID, Identified, Item,
 };
 use crate::messages::message;
 use crate::settings::Settings;
 use crate::types::command::Command;
 use crate::types::entity_map::EntityMap;
 use crate::types::{
-    pos, BoundingBox, Collision, Dimensions, Position, Positioned,
-    PositionedMut, Ticks,
+    pos, BoundingBox, Collision, Dimensions, Position, Positioned, Ticks,
 };
 use crate::util::promise::Cancelled;
 use crate::util::promise::{promise, Complete, Promise, Promises};
@@ -24,25 +24,30 @@ type Stdout<'a> = RawTerminal<StdoutLock<'a>>;
 
 type Rng = SmallRng;
 
-type AnEntity = Box<dyn Entity>;
-
-impl Positioned for AnEntity {
-    fn position(&self) -> Position {
-        (**self).position()
-    }
-}
-
-impl PositionedMut for AnEntity {
-    fn set_position(&mut self, pos: Position) {
-        (**self).set_position(pos)
-    }
-}
-
 enum PromptResolution {
     Uncancellable(Complete<String>),
     Cancellable(Complete<Result<String, Cancelled>>),
 }
 
+/// The mode to use when describing entities on a tile to the user
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+enum EntityDescriptionMode {
+    /// Describe the entities that the user is walking over.
+    ///
+    /// This means:
+    /// - Skip the character themselves
+    /// - Describe nothing if there are no items other than the character
+    Walk,
+
+    /// Describe entities that the user is actively asking about.
+    ///
+    /// This means:
+    /// - Describe the character themselves if they've asked to look at the tile
+    ///   they're standing on
+    /// - Explicitly say there's nothing there if there's nothing there.
+    Look,
+}
+
 impl PromptResolution {
     fn is_cancellable(&self) -> bool {
         use PromptResolution::*;
@@ -251,6 +256,43 @@ impl<'a> Game<'a> {
         }
     }
 
+    /// Describe all the entities at a given position to the user.
+    ///
+    /// If `force` is not set to `true`, will not do anything if there are no
+    /// entities
+    fn describe_entities_at(
+        &mut self,
+        pos: Position,
+        mode: EntityDescriptionMode,
+    ) -> io::Result<()> {
+        use EntityDescriptionMode::*;
+        let mut entities = self.entities.at(pos);
+        if mode == Walk {
+            entities.retain(|e| e.id() != self.character_entity_id);
+        }
+
+        if entities.len() == 0 {
+            match mode {
+                Walk => return Ok(()),
+                Look => {
+                    return self.say(
+                        "global.describe_no_entities",
+                        &template_params!(),
+                    )
+                }
+            }
+        }
+
+        let descriptions = list_to_sentence(
+            &entities.iter().map(|e| e.description()).collect(),
+        );
+
+        self.say(
+            "global.describe_entities",
+            &template_params!({ "descriptions" => &descriptions, }),
+        )
+    }
+
     /// Remove the given entity from the game, drawing over it if it's visible
     fn remove_entity(&mut self, entity_id: EntityID) -> io::Result<()> {
         if let Some(entity) = self.entities.remove(entity_id) {
@@ -446,17 +488,18 @@ impl<'a> Game<'a> {
                     match old_position {
                         Some(old_pos) => {
                             let character = self.character();
-                            self.viewport.game_cursor_position =
-                                character.position;
+                            let char_pos = character.position.clone();
+                            self.viewport.game_cursor_position = char_pos;
                             self.viewport.clear(old_pos)?;
                             self.draw_entities_at(old_pos)?;
                             self.draw_entity(self.character_entity_id)?;
-                            self.tick(
-                                self.character().speed().tiles_to_ticks(
-                                    (old_pos - self.character().position)
-                                        .as_tiles(),
-                                ),
-                            );
+                            self.describe_entities_at(
+                                char_pos,
+                                EntityDescriptionMode::Walk,
+                            )?;
+                            self.tick(self.character().speed().tiles_to_ticks(
+                                (old_pos - char_pos).as_tiles(),
+                            ));
                         }
                         None => (),
                     }
diff --git a/src/messages.toml b/src/messages.toml
index 69fd389faccc..7c6255142dd2 100644
--- a/src/messages.toml
+++ b/src/messages.toml
@@ -1,5 +1,7 @@
 [global]
 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."
 
 [combat]
 attack = "You attack the {{creature.name}}."
diff --git a/src/util/mod.rs b/src/util/mod.rs
index c55fdfeae2b4..dd5087a55558 100644
--- a/src/util/mod.rs
+++ b/src/util/mod.rs
@@ -3,3 +3,5 @@ pub mod static_cfg;
 #[macro_use]
 pub mod template;
 pub mod promise;
+#[macro_use]
+pub mod trait_impls;
diff --git a/src/util/trait_impls.rs b/src/util/trait_impls.rs
new file mode 100644
index 000000000000..ba15f7119d26
--- /dev/null
+++ b/src/util/trait_impls.rs
@@ -0,0 +1,17 @@
+macro_rules! ref_impl {
+    (impl<T: $traitb: ident $(+ $bound:ident)*> $traiti:ident for &T {
+        $($body:tt)*
+    }) => {
+        impl<'a, T: $traitb $(+ $bound)*> $traiti for &'a T {
+            $($body)*
+        }
+
+        impl<'a, T: $traitb $(+ $bound)*> $traiti for &'a mut T {
+            $($body)*
+        }
+
+        impl<T: $traitb $(+ $bound)*> $traiti for ::std::boxed::Box<T> {
+            $($body)*
+        }
+    };
+}