Describe what you see when you walk over it

If the character walks over any number of entities, describe those
entities to the character.
This commit is contained in:
Griffin Smith 2019-07-29 11:22:39 -04:00
parent 34b20b7786
commit 9db5fad2f9
12 changed files with 168 additions and 48 deletions

View file

@ -1,22 +1,17 @@
use crate::display; use crate::display;
use crate::entities::EntityID;
use crate::types::{Position, Speed}; use crate::types::{Position, Speed};
use proptest_derive::Arbitrary;
use std::io::{self, Write}; use std::io::{self, Write};
use termion::cursor;
const DEFAULT_SPEED: Speed = Speed(100); const DEFAULT_SPEED: Speed = Speed(100);
#[derive(Debug, PartialEq, Eq, Arbitrary, Clone)] entity! {
pub struct Character { pub struct Character {
pub id: Option<EntityID>, pub o_name: Option<String>,
}
/// The position of the character, relative to the game
pub position: Position,
pub o_name: Option<String>,
} }
static_description!(Character, "yourself");
impl Character { impl Character {
pub fn new() -> Character { pub fn new() -> Character {
Character { Character {
@ -46,8 +41,6 @@ impl Character {
} }
} }
entity!(Character);
impl display::Draw for Character { impl display::Draw for Character {
fn do_draw(&self, out: &mut Write) -> io::Result<()> { fn do_draw(&self, out: &mut Write) -> io::Result<()> {
write!(out, "@") write!(out, "@")

View file

@ -1,7 +1,7 @@
use crate::display; use crate::display;
use crate::entities::raws::CreatureType; use crate::entities::raws::CreatureType;
use crate::entities::raws::EntityRaw; use crate::entities::raws::EntityRaw;
use crate::entities::{raw, EntityID}; use crate::entities::{raw, Describe, EntityID};
use crate::types::Position; use crate::types::Position;
use std::io::{self, Write}; use std::io::{self, Write};
@ -50,6 +50,12 @@ impl Creature {
entity!(Creature); entity!(Creature);
impl Describe for Creature {
fn description(&self) -> String {
self.typ.description.to_string()
}
}
impl display::Draw for Creature { impl display::Draw for Creature {
fn do_draw(&self, out: &mut Write) -> io::Result<()> { fn do_draw(&self, out: &mut Write) -> io::Result<()> {
write!(out, "{}", self.typ.chr) write!(out, "{}", self.typ.chr)

View file

@ -1,6 +1,7 @@
use crate::display::DrawWithNeighbors; use crate::display::DrawWithNeighbors;
use crate::entities::EntityID; use crate::entities::EntityID;
use crate::types::Neighbors; use crate::types::Neighbors;
use crate::types::Position;
use crate::types::{Positioned, PositionedMut}; use crate::types::{Positioned, PositionedMut};
use downcast_rs::Downcast; use downcast_rs::Downcast;
use std::fmt::Debug; 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: 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) (**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)
}
}

View file

@ -10,6 +10,8 @@ entity! {
} }
} }
static_description!(Wall, "a wall");
impl Wall { impl Wall {
pub fn new(position: Position, style: BoxStyle) -> Self { pub fn new(position: Position, style: BoxStyle) -> Self {
new_entity!(Wall { position, style }) new_entity!(Wall { position, style })

View file

@ -1,6 +1,6 @@
use crate::display; use crate::display;
use crate::entities::raws::{raw, EntityRaw, ItemType}; use crate::entities::raws::{raw, EntityRaw, ItemType};
use crate::entities::EntityID; use crate::entities::{Describe, EntityID};
use crate::types::Position; use crate::types::Position;
use std::io::{self, Write}; use std::io::{self, Write};
@ -37,6 +37,12 @@ impl Item {
entity!(Item); entity!(Item);
impl Describe for Item {
fn description(&self) -> String {
self.typ.description.to_string()
}
}
impl display::Draw for Item { impl display::Draw for Item {
fn do_draw(&self, out: &mut Write) -> io::Result<()> { fn do_draw(&self, out: &mut Write) -> io::Result<()> {
write!(out, "{}", self.typ.chr) write!(out, "{}", self.typ.chr)

View file

@ -12,7 +12,7 @@ pub mod raws;
pub use character::Character; pub use character::Character;
pub use creature::Creature; pub use creature::Creature;
pub use entity::{Entity, Identified}; pub use entity::{AnEntity, Describe, Entity, Identified};
pub use entity_char::EntityChar; pub use entity_char::EntityChar;
pub use item::Item; pub use item::Item;
pub use raws::raw; pub use raws::raw;

View file

@ -30,9 +30,13 @@ pub struct EdibleItem<'a> {
pub struct ItemType<'a> { pub struct ItemType<'a> {
pub name: &'a str, 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, pub description: &'a str,
/// A longer description of the item
pub long_description: &'a str,
pub edible_item: Option<EdibleItem<'a>>, pub edible_item: Option<EdibleItem<'a>>,
#[serde(rename = "char")] #[serde(rename = "char")]
@ -49,7 +53,8 @@ mod item_type_tests {
r#"{ r#"{
"Item": { "Item": {
"name": "noodles", "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" }, "char": { "char": "n" },
"edible_item": { "edible_item": {
"eat_message": "You slurp up the noodles", "eat_message": "You slurp up the noodles",
@ -67,7 +72,8 @@ mod item_type_tests {
let toml_result = toml::from_str( let toml_result = toml::from_str(
r#"[Item] r#"[Item]
name = "noodles" 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 = "🍜" } char = { char = "🍜" }
edible_item = { eat_message = "You slurp up the noodles", hitpoints_healed = 2 } edible_item = { eat_message = "You slurp up the noodles", hitpoints_healed = 2 }
"#, "#,

View file

@ -5,11 +5,11 @@
"char": "n", "char": "n",
"color": "yellow" "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": { "edible_item": {
"eat_message": "You slurp up the noodles", "eat_message": "You slurp up the noodles",
"hitpoints_healed": 2 "hitpoints_healed": 2
}, }
"display_name": "big bowl o' noodles"
} }
} }

View file

@ -1,14 +1,14 @@
use crate::description::list_to_sentence;
use crate::display::{self, Viewport}; use crate::display::{self, Viewport};
use crate::entities::{ use crate::entities::{
Character, Creature, Entity, EntityID, Identified, Item, AnEntity, Character, Creature, EntityID, Identified, Item,
}; };
use crate::messages::message; use crate::messages::message;
use crate::settings::Settings; use crate::settings::Settings;
use crate::types::command::Command; use crate::types::command::Command;
use crate::types::entity_map::EntityMap; use crate::types::entity_map::EntityMap;
use crate::types::{ use crate::types::{
pos, BoundingBox, Collision, Dimensions, Position, Positioned, pos, BoundingBox, Collision, Dimensions, Position, Positioned, Ticks,
PositionedMut, Ticks,
}; };
use crate::util::promise::Cancelled; use crate::util::promise::Cancelled;
use crate::util::promise::{promise, Complete, Promise, Promises}; use crate::util::promise::{promise, Complete, Promise, Promises};
@ -24,25 +24,30 @@ type Stdout<'a> = RawTerminal<StdoutLock<'a>>;
type Rng = SmallRng; 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 { enum PromptResolution {
Uncancellable(Complete<String>), Uncancellable(Complete<String>),
Cancellable(Complete<Result<String, Cancelled>>), 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 { impl PromptResolution {
fn is_cancellable(&self) -> bool { fn is_cancellable(&self) -> bool {
use PromptResolution::*; 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 /// Remove the given entity from the game, drawing over it if it's visible
fn remove_entity(&mut self, entity_id: EntityID) -> io::Result<()> { fn remove_entity(&mut self, entity_id: EntityID) -> io::Result<()> {
if let Some(entity) = self.entities.remove(entity_id) { if let Some(entity) = self.entities.remove(entity_id) {
@ -446,17 +488,18 @@ impl<'a> Game<'a> {
match old_position { match old_position {
Some(old_pos) => { Some(old_pos) => {
let character = self.character(); let character = self.character();
self.viewport.game_cursor_position = let char_pos = character.position.clone();
character.position; self.viewport.game_cursor_position = char_pos;
self.viewport.clear(old_pos)?; self.viewport.clear(old_pos)?;
self.draw_entities_at(old_pos)?; self.draw_entities_at(old_pos)?;
self.draw_entity(self.character_entity_id)?; self.draw_entity(self.character_entity_id)?;
self.tick( self.describe_entities_at(
self.character().speed().tiles_to_ticks( char_pos,
(old_pos - self.character().position) EntityDescriptionMode::Walk,
.as_tiles(), )?;
), self.tick(self.character().speed().tiles_to_ticks(
); (old_pos - char_pos).as_tiles(),
));
} }
None => (), None => (),
} }

View file

@ -1,5 +1,7 @@
[global] [global]
welcome = "Welcome to Xanthous, {{character.name}}! It's dangerous out there, why not stay inside?" 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] [combat]
attack = "You attack the {{creature.name}}." attack = "You attack the {{creature.name}}."

View file

@ -3,3 +3,5 @@ pub mod static_cfg;
#[macro_use] #[macro_use]
pub mod template; pub mod template;
pub mod promise; pub mod promise;
#[macro_use]
pub mod trait_impls;

17
src/util/trait_impls.rs Normal file
View file

@ -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)*
}
};
}