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::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, "@")
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 })
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 }
|
||||
"#,
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
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::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 => (),
|
||||
}
|
||||
|
|
|
@ -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}}."
|
||||
|
|
|
@ -3,3 +3,5 @@ pub mod static_cfg;
|
|||
#[macro_use]
|
||||
pub mod template;
|
||||
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