about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorGriffin Smith <root@gws.fyi>2019-07-20T01·55-0400
committerGriffin Smith <root@gws.fyi>2019-07-20T01·55-0400
commit29c80ac8ba0d733c6c452d8fd39e9561553495b0 (patch)
treef8384229d84b0d281c2fbbe976aeb5ebc21bdc24 /src
parent4e9138aa6ff72e34392e3467c40d5ddf095f0027 (diff)
Add the beginning of item entities
Add a new Item raw type and entity type, with preliminary, basic support
for food. There's a really frustrating toml-rs bug that prevents writing
these nicely as toml right now, so I also added support for mixing JSON
and TOML in a single config dir
Diffstat (limited to 'src')
-rw-r--r--src/display/color.rs6
-rw-r--r--src/entities/entity_char.rs2
-rw-r--r--src/entities/item.rs44
-rw-r--r--src/entities/mod.rs3
-rw-r--r--src/entities/raw_types.rs104
-rw-r--r--src/entities/raws.rs44
-rw-r--r--src/entities/raws/noodles.json14
-rw-r--r--src/game.rs7
-rw-r--r--src/main.rs3
-rw-r--r--src/messages.rs11
-rw-r--r--src/messages.toml3
-rw-r--r--src/util/static_cfg.rs58
12 files changed, 264 insertions, 35 deletions
diff --git a/src/display/color.rs b/src/display/color.rs
index 7de1f124b704..7d024a960d97 100644
--- a/src/display/color.rs
+++ b/src/display/color.rs
@@ -35,6 +35,12 @@ impl<'a> color::Color for &'a Color {
     }
 }
 
+impl Default for Color {
+    fn default() -> Self {
+        Color::new(color::Reset)
+    }
+}
+
 pub struct ColorVisitor {
     marker: PhantomData<fn() -> Color>,
 }
diff --git a/src/entities/entity_char.rs b/src/entities/entity_char.rs
index 578aaf3da5c3..2f845820021e 100644
--- a/src/entities/entity_char.rs
+++ b/src/entities/entity_char.rs
@@ -4,7 +4,9 @@ use termion::color;
 
 #[derive(Debug, Deserialize)]
 pub struct EntityChar {
+    #[serde(default)]
     color: Color,
+
     #[serde(rename = "char")]
     chr: char,
 }
diff --git a/src/entities/item.rs b/src/entities/item.rs
new file mode 100644
index 000000000000..d0ecc090e2e4
--- /dev/null
+++ b/src/entities/item.rs
@@ -0,0 +1,44 @@
+use crate::display;
+use crate::entities::raws::{raw, EntityRaw, ItemType};
+use crate::entities::EntityID;
+use crate::types::Position;
+use std::io::{self, Write};
+
+#[derive(Debug, Clone)]
+pub struct Item {
+    pub id: Option<EntityID>,
+    pub typ: &'static ItemType<'static>,
+    pub position: Position,
+}
+
+impl Item {
+    pub fn new_from_raw(name: &'static str, position: Position) -> Self {
+        match raw(name) {
+            EntityRaw::Item(typ) => Self::new_with_type(typ, position),
+            _ => panic!("Invalid raw type for {:?}, expected Item", name),
+        }
+    }
+
+    pub fn new_with_type(
+        typ: &'static ItemType<'static>,
+        position: Position,
+    ) -> Self {
+        Item {
+            id: None,
+            typ,
+            position,
+        }
+    }
+
+    pub fn is_edible(&self) -> bool {
+        self.typ.is_edible()
+    }
+}
+
+entity!(Item);
+
+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 ed83f2f462db..c54a587e6aba 100644
--- a/src/entities/mod.rs
+++ b/src/entities/mod.rs
@@ -3,12 +3,15 @@ pub mod entity;
 pub mod character;
 pub mod creature;
 pub mod entity_char;
+pub mod item;
+pub mod raw_types;
 pub mod raws;
 
 pub use character::Character;
 pub use creature::Creature;
 pub use entity::{Entity, Identified};
 pub use entity_char::EntityChar;
+pub use item::Item;
 pub use raws::raw;
 
 pub type EntityID = u32;
diff --git a/src/entities/raw_types.rs b/src/entities/raw_types.rs
new file mode 100644
index 000000000000..8f64e60d9cd4
--- /dev/null
+++ b/src/entities/raw_types.rs
@@ -0,0 +1,104 @@
+use crate::entities::entity_char::EntityChar;
+use crate::messages::Message;
+use crate::types::Speed;
+
+#[derive(Debug, Deserialize)]
+pub struct CreatureType<'a> {
+    /// The name of the creature. Used in raw lookups.
+    pub name: &'a str,
+
+    /// A description of the entity, used by the "look" command
+    pub description: &'a str,
+
+    #[serde(rename = "char")]
+    pub chr: EntityChar,
+    pub max_hitpoints: u16,
+    pub speed: Speed,
+    pub friendly: bool,
+}
+
+#[derive(Debug, Deserialize)]
+pub struct EdibleItem<'a> {
+    #[serde(borrow)]
+    pub eat_message: Option<Message<'a>>,
+
+    /// The number of hitpoints that eating this item heals
+    pub hitpoints_healed: u16,
+}
+
+#[derive(Debug, Deserialize)]
+pub struct ItemType<'a> {
+    pub name: &'a str,
+
+    /// A description of the item, used by the "look" command
+    pub description: &'a str,
+
+    pub edible_item: Option<EdibleItem<'a>>,
+
+    #[serde(rename = "char")]
+    pub chr: EntityChar,
+}
+
+#[cfg(test)]
+mod item_type_tests {
+    use super::*;
+
+    #[test]
+    fn test_deserialize_item_type() {
+        let result = serde_json::from_str(
+            r#"{
+                "Item": {
+                    "name": "noodles",
+                    "description": "You know exactly what kind of noodles",
+                    "char": { "char": "n" },
+                    "edible_item": {
+                        "eat_message": "You slurp up the noodles",
+                        "hitpoints_healed": 2
+                    }
+                }
+            }"#,
+        )
+        .unwrap();
+        assert_matches!(result, EntityRaw::Item(_));
+        if let EntityRaw::Item(item) = result {
+            assert_eq!(item.name, "noodles");
+        }
+
+        let toml_result = toml::from_str(
+            r#"[Item]
+name = "noodles"
+description = "You know exactly what kind of noodles"
+char = { char = "🍜" }
+edible_item = { eat_message = "You slurp up the noodles", hitpoints_healed = 2 }
+"#,
+        )
+        .unwrap();
+
+        assert_matches!(toml_result, EntityRaw::Item(_));
+        if let EntityRaw::Item(item) = toml_result {
+            assert_eq!(item.name, "noodles");
+        }
+    }
+}
+
+impl<'a> ItemType<'a> {
+    pub fn is_edible(&self) -> bool {
+        self.edible_item.is_some()
+    }
+}
+
+#[derive(Debug, Deserialize)]
+pub enum EntityRaw<'a> {
+    Creature(#[serde(borrow)] CreatureType<'a>),
+    Item(#[serde(borrow)] ItemType<'a>),
+}
+
+impl<'a> EntityRaw<'a> {
+    pub fn name(&self) -> &'a str {
+        use EntityRaw::*;
+        match self {
+            Creature(typ) => typ.name,
+            Item(typ) => typ.name,
+        }
+    }
+}
diff --git a/src/entities/raws.rs b/src/entities/raws.rs
index da061d89d8d6..2c4a8203cb17 100644
--- a/src/entities/raws.rs
+++ b/src/entities/raws.rs
@@ -1,37 +1,8 @@
-use crate::entities::entity_char::EntityChar;
-use crate::types::Speed;
+pub use crate::entities::raw_types::{CreatureType, EntityRaw, ItemType};
 use std::collections::HashMap;
 
-#[derive(Debug, Deserialize)]
-pub struct CreatureType<'a> {
-    /// The name of the creature. Used in raw lookups.
-    pub name: &'a str,
-
-    /// A description of the entity, used by the "look" command
-    pub description: &'a str,
-
-    #[serde(rename = "char")]
-    pub chr: EntityChar,
-    pub max_hitpoints: u16,
-    pub speed: Speed,
-    pub friendly: bool,
-}
-
-#[derive(Debug, Deserialize)]
-pub enum EntityRaw<'a> {
-    Creature(#[serde(borrow)] CreatureType<'a>),
-}
-
-impl<'a> EntityRaw<'a> {
-    pub fn name(&self) -> &'a str {
-        match self {
-            EntityRaw::Creature(typ) => typ.name,
-        }
-    }
-}
-
 static_cfg! {
-    static ref RAWS: Vec<EntityRaw<'static>> = toml_dir("src/entities/raws");
+    static ref RAWS: Vec<EntityRaw<'static>> = cfg_dir("src/entities/raws");
 }
 
 lazy_static! {
@@ -54,3 +25,14 @@ pub fn raw(name: &'static str) -> &'static EntityRaw<'static> {
         .map(|e| *e)
         .expect(format!("Raw not found: {}", name).as_str())
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_raws() {
+        RAWS_BY_NAME.keys();
+        assert_eq!(raw("noodles").name(), "noodles");
+    }
+}
diff --git a/src/entities/raws/noodles.json b/src/entities/raws/noodles.json
new file mode 100644
index 000000000000..d4b773cac533
--- /dev/null
+++ b/src/entities/raws/noodles.json
@@ -0,0 +1,14 @@
+{
+  "Item": {
+    "name": "noodles",
+    "char": {
+      "char": "🍜"
+    },
+    "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 af9b0ac938dd..c4fc6d2be10a 100644
--- a/src/game.rs
+++ b/src/game.rs
@@ -1,5 +1,7 @@
 use crate::display::{self, Viewport};
-use crate::entities::{Character, Creature, Entity, EntityID, Identified};
+use crate::entities::{
+    Character, Creature, Entity, EntityID, Identified, Item,
+};
 use crate::messages::message;
 use crate::settings::Settings;
 use crate::types::command::Command;
@@ -80,6 +82,9 @@ impl<'a> Game<'a> {
                 "gormlak",
                 pos(10, 0),
             )));
+
+            entities
+                .insert(Box::new(Item::new_from_raw("noodles", pos(0, 10))));
         }
 
         Game {
diff --git a/src/main.rs b/src/main.rs
index 8bad5c057fd3..69b7304e49d2 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -7,6 +7,7 @@ extern crate serde;
 extern crate toml;
 #[macro_use]
 extern crate serde_derive;
+extern crate serde_json;
 #[macro_use]
 extern crate clap;
 #[macro_use]
@@ -23,6 +24,8 @@ extern crate backtrace;
 extern crate include_dir;
 #[macro_use]
 extern crate nom;
+#[macro_use]
+extern crate matches;
 
 #[macro_use]
 mod util;
diff --git a/src/messages.rs b/src/messages.rs
index 9ca78025ec60..719389fa6136 100644
--- a/src/messages.rs
+++ b/src/messages.rs
@@ -6,7 +6,7 @@ use std::collections::HashMap;
 
 #[derive(Deserialize, Debug, PartialEq, Eq)]
 #[serde(untagged)]
-enum Message<'a> {
+pub enum Message<'a> {
     #[serde(borrow)]
     Single(Template<'a>),
     Choice(Vec<Template<'a>>),
@@ -123,6 +123,13 @@ static_cfg! {
     static ref MESSAGES: NestedMap<'static> = toml_file("messages.toml");
 }
 
+pub fn get<R: Rng + ?Sized>(
+    name: &'static str,
+    rng: &mut R,
+) -> Option<&'static Template<'static>> {
+    MESSAGES.lookup(name).and_then(|msg| msg.resolve(rng))
+}
+
 /// Look up and format a game message based on the given (dot-separated) name,
 /// with the given random generator used to select from choice-based messages
 pub fn message<'a, R: Rng + ?Sized>(
@@ -130,7 +137,7 @@ pub fn message<'a, R: Rng + ?Sized>(
     rng: &mut R,
     params: &TemplateParams<'a>,
 ) -> String {
-    match MESSAGES.lookup(name).and_then(|msg| msg.resolve(rng)) {
+    match get(name, rng) {
         Some(msg) => msg.format(params).unwrap_or_else(|e| {
             error!("Error formatting template: {}", e);
             "Template Error".to_string()
diff --git a/src/messages.toml b/src/messages.toml
index d3e0e1de8a23..e7d097a76f61 100644
--- a/src/messages.toml
+++ b/src/messages.toml
@@ -9,3 +9,6 @@ killed = [
     "The {{creature.name}} kicks it.",
     "The {{creature.name}} beefs it."
     ]
+
+[defaults.item]
+eat = "You eat the {{item.name}}"
diff --git a/src/util/static_cfg.rs b/src/util/static_cfg.rs
index 1b4864df72c9..b20456fb3bd4 100644
--- a/src/util/static_cfg.rs
+++ b/src/util/static_cfg.rs
@@ -14,6 +14,9 @@ macro_rules! __static_cfg_include {
     (json_dir, $filename:expr) => {
         include_dir!($filename)
     };
+    (cfg_dir, $filename:expr) => {
+        include_dir!($filename)
+    };
 }
 
 macro_rules! __static_cfg_type {
@@ -21,6 +24,7 @@ macro_rules! __static_cfg_type {
     (json_file) => (&'static str);
     (toml_dir) => (include_dir::Dir<'static>);
     (json_dir) => (include_dir::Dir<'static>);
+    (cfg_dir) => (include_dir::Dir<'static>);
 }
 
 macro_rules! __static_cfg_parse {
@@ -39,6 +43,10 @@ macro_rules! __static_cfg_parse {
     (json_dir, $e:expr) => {
         crate::util::static_cfg::parse_json_dir($e)
     };
+
+    (cfg_dir, $e:expr) => {
+        crate::util::static_cfg::parse_cfg_dir($e);
+    };
 }
 
 macro_rules! __static_cfg_inner {
@@ -70,13 +78,61 @@ macro_rules! static_cfg {
     () => ()
 }
 
+pub fn parse_cfg_dir<'a, T>(d: Dir<'a>) -> Vec<T>
+where
+    T: de::Deserialize<'a>,
+{
+    d.files()
+        .iter()
+        .filter_map(|f| {
+            let path = f.path();
+            let contents = f.contents_utf8().unwrap();
+            match path.extension().and_then(|e| e.to_str()) {
+                Some("toml") => {
+                    Some(toml::from_str(contents).unwrap_or_else(|e| {
+                        panic!(
+                            "Error parsing TOML file {}: {}",
+                            path.display(),
+                            e
+                        )
+                    }))
+                }
+                Some("json") => {
+                    Some(serde_json::from_str(contents).unwrap_or_else(|e| {
+                        panic!(
+                            "Error parsing JSON file {}: {}",
+                            path.display(),
+                            e
+                        )
+                    }))
+                }
+                // > YAML currently does not support zero-copy deserialization
+                // Some("yaml") => {
+                //     Some(serde_yaml::from_str(contents).unwrap_or_else(|e| {
+                //         panic!(
+                //             "Error parsing YAML file {}: {}",
+                //             path.display(),
+                //             e
+                //         )
+                //     }))
+                // }
+                _ => None,
+            }
+        })
+        .collect()
+}
+
 pub fn parse_toml_dir<'a, T>(d: Dir<'a>) -> Vec<T>
 where
     T: de::Deserialize<'a>,
 {
     d.files()
         .iter()
-        .map(|f| toml::from_str(f.contents_utf8().unwrap()).unwrap())
+        .map(|f| {
+            toml::from_str(f.contents_utf8().unwrap()).unwrap_or_else(|e| {
+                panic!("Error parsing TOML file {}: {}", f.path, e)
+            })
+        })
         .collect()
 }