diff options
author | Griffin Smith <root@gws.fyi> | 2019-07-20T01·55-0400 |
---|---|---|
committer | Griffin Smith <root@gws.fyi> | 2019-07-20T01·55-0400 |
commit | 29c80ac8ba0d733c6c452d8fd39e9561553495b0 (patch) | |
tree | f8384229d84b0d281c2fbbe976aeb5ebc21bdc24 /src | |
parent | 4e9138aa6ff72e34392e3467c40d5ddf095f0027 (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.rs | 6 | ||||
-rw-r--r-- | src/entities/entity_char.rs | 2 | ||||
-rw-r--r-- | src/entities/item.rs | 44 | ||||
-rw-r--r-- | src/entities/mod.rs | 3 | ||||
-rw-r--r-- | src/entities/raw_types.rs | 104 | ||||
-rw-r--r-- | src/entities/raws.rs | 44 | ||||
-rw-r--r-- | src/entities/raws/noodles.json | 14 | ||||
-rw-r--r-- | src/game.rs | 7 | ||||
-rw-r--r-- | src/main.rs | 3 | ||||
-rw-r--r-- | src/messages.rs | 11 | ||||
-rw-r--r-- | src/messages.toml | 3 | ||||
-rw-r--r-- | src/util/static_cfg.rs | 58 |
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() } |