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:
parent
34b20b7786
commit
9db5fad2f9
12 changed files with 168 additions and 48 deletions
|
@ -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, "@")
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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 })
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 }
|
||||||
"#,
|
"#,
|
||||||
|
|
|
@ -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"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
93
src/game.rs
93
src/game.rs
|
@ -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 => (),
|
||||||
}
|
}
|
||||||
|
|
|
@ -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}}."
|
||||||
|
|
|
@ -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
17
src/util/trait_impls.rs
Normal 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)*
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
Loading…
Reference in a new issue