Add (statically-included) entity raws

Add a system for statically-included entity raws (which necessitated
making a deserializable existential Color struct) and test it out by
initializing the game (for now) with a single on-screen gormlak.
This commit is contained in:
Griffin Smith 2019-07-14 14:29:55 -04:00
parent 081146da30
commit e7ad87c730
9 changed files with 306 additions and 5 deletions

149
src/display/color.rs Normal file
View file

@ -0,0 +1,149 @@
use serde::de::{self, Unexpected, Visitor};
use std::fmt;
use std::marker::PhantomData;
use termion::color;
#[derive(Debug)]
pub struct Color(Box<dyn color::Color>);
unsafe impl Sync for Color {}
unsafe impl Send for Color {}
impl Color {
pub fn new<C: color::Color + 'static>(c: C) -> Self {
Color(Box::new(c))
}
}
impl color::Color for Color {
fn write_fg(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.write_fg(f)
}
fn write_bg(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.write_bg(f)
}
}
impl<'a> color::Color for &'a Color {
fn write_fg(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.write_fg(f)
}
fn write_bg(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.write_bg(f)
}
}
pub struct ColorVisitor {
marker: PhantomData<fn() -> Color>,
}
impl ColorVisitor {
fn new() -> Self {
ColorVisitor {
marker: PhantomData,
}
}
}
impl<'de> Visitor<'de> for ColorVisitor {
type Value = Color;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("A color")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
match v.to_lowercase().as_ref() {
"black" => Ok(Color(Box::new(color::Black))),
"blue" => Ok(Color(Box::new(color::Blue))),
"cyan" => Ok(Color(Box::new(color::Cyan))),
"green" => Ok(Color(Box::new(color::Green))),
"light black" | "light_black" => {
Ok(Color(Box::new(color::LightBlack)))
}
"light blue" | "light_blue" => {
Ok(Color(Box::new(color::LightBlue)))
}
"light cyan" | "light_cyan" => {
Ok(Color(Box::new(color::LightCyan)))
}
"light green" | "light_green" => {
Ok(Color(Box::new(color::LightGreen)))
}
"light magenta" | "light_magenta" => {
Ok(Color(Box::new(color::LightMagenta)))
}
"light red" | "light_red" => Ok(Color(Box::new(color::LightRed))),
"light white" | "light_white" => {
Ok(Color(Box::new(color::LightWhite)))
}
"light yellow" | "light_yellow" => {
Ok(Color(Box::new(color::LightYellow)))
}
"magenta" => Ok(Color(Box::new(color::Magenta))),
"magenta" => Ok(Color(Box::new(color::Magenta))),
"red" => Ok(Color(Box::new(color::Red))),
"white" => Ok(Color(Box::new(color::White))),
"yellow" => Ok(Color(Box::new(color::Yellow))),
_ => Err(de::Error::invalid_value(
Unexpected::Str(v),
&"a valid color",
)),
}
}
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
where
A: de::MapAccess<'de>,
{
let mut red = None;
let mut green = None;
let mut blue = None;
while let Some((k, v)) = map.next_entry()? {
match k {
"red" => {
red = Some(v);
}
"green" => {
green = Some(v);
}
"blue" => {
blue = Some(v);
}
_ => {
return Err(de::Error::unknown_field(
k,
&["red", "green", "blue"],
));
}
}
}
match (red, green, blue) {
(Some(r), Some(g), Some(b)) => {
Ok(Color(Box::new(color::Rgb(r, g, b))))
}
(None, _, _) => Err(de::Error::missing_field("red")),
(_, None, _) => Err(de::Error::missing_field("green")),
(_, _, None) => Err(de::Error::missing_field("blue")),
}
}
fn visit_u8<E: de::Error>(self, v: u8) -> Result<Self::Value, E> {
Ok(Color(Box::new(color::AnsiValue(v))))
}
}
impl<'de> serde::Deserialize<'de> for Color {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
deserializer.deserialize_any(ColorVisitor::new())
}
}

View file

@ -1,3 +1,4 @@
pub mod color;
pub mod draw_box; pub mod draw_box;
pub mod utils; pub mod utils;
pub mod viewport; pub mod viewport;
@ -17,13 +18,13 @@ pub trait Draw: Positioned {
fn do_draw(&self, out: &mut Write) -> io::Result<()>; fn do_draw(&self, out: &mut Write) -> io::Result<()>;
} }
impl<T : Draw> Draw for &T { impl<T: Draw> Draw for &T {
fn do_draw(&self, out: &mut Write) -> io::Result<()> { fn do_draw(&self, out: &mut Write) -> io::Result<()> {
(**self).do_draw(out) (**self).do_draw(out)
} }
} }
impl<T : Draw> Draw for Box<T> { impl<T: Draw> Draw for Box<T> {
fn do_draw(&self, out: &mut Write) -> io::Result<()> { fn do_draw(&self, out: &mut Write) -> io::Result<()> {
(**self).do_draw(out) (**self).do_draw(out)
} }

43
src/entities/creature.rs Normal file
View file

@ -0,0 +1,43 @@
use crate::display;
use crate::entities::raws::CreatureType;
use crate::entities::raws::EntityRaw;
use crate::entities::{raw, Entity};
use crate::types::Position;
use std::io::{self, Write};
pub struct Creature {
pub typ: &'static CreatureType<'static>,
pub position: Position,
pub hitpoints: u16,
}
impl Creature {
pub fn new_from_raw(name: &'static str, position: Position) -> Self {
match raw(name) {
EntityRaw::Creature(typ) => Self::new_with_type(typ, position),
_ => panic!("Invalid raw type for {:?}, expected Creature", name),
}
}
pub fn new_with_type(
typ: &'static CreatureType<'static>,
position: Position,
) -> Self {
Creature {
typ,
position,
hitpoints: typ.max_hitpoints,
}
}
}
positioned!(Creature);
positioned_mut!(Creature);
impl Entity for Creature {}
impl display::Draw for Creature {
fn do_draw(&self, out: &mut Write) -> io::Result<()> {
write!(out, "{}", self.typ.chr)
}
}

View file

@ -0,0 +1,22 @@
use crate::display::color::Color;
use std::fmt::{self, Display, Formatter};
use termion::color;
#[derive(Debug, Deserialize)]
pub struct EntityChar {
color: Color,
#[serde(rename = "char")]
chr: char,
}
impl Display for EntityChar {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(
f,
"{}{}{}",
color::Fg(&self.color),
self.chr,
color::Fg(color::Reset)
)
}
}

View file

@ -1,7 +1,15 @@
pub mod character; pub mod character;
pub mod creature;
pub mod entity_char;
pub mod raws;
pub use character::Character;
pub use creature::Creature;
pub use entity_char::EntityChar;
pub use raws::raw;
use crate::display::Draw; use crate::display::Draw;
use crate::types::{Positioned, PositionedMut}; use crate::types::{Positioned, PositionedMut};
pub use character::Character;
use downcast_rs::Downcast; use downcast_rs::Downcast;
use std::io::{self, Write}; use std::io::{self, Write};

57
src/entities/raws.rs Normal file
View file

@ -0,0 +1,57 @@
use crate::entities::entity_char::EntityChar;
use crate::types::Speed;
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");
}
lazy_static! {
static ref RAWS_BY_NAME: HashMap<&'static str, &'static EntityRaw<'static>> = {
let mut hm = HashMap::new();
for er in RAWS.iter() {
if hm.contains_key(er.name()) {
panic!("Duplicate entity: {}", er.name())
}
hm.insert(er.name(), er);
}
hm
};
}
pub fn raw(name: &'static str) -> &'static EntityRaw<'static> {
debug!("{:?}", RAWS_BY_NAME.keys().collect::<Vec<&&'static str>>());
RAWS_BY_NAME
.get(name)
.map(|e| *e)
.expect(format!("Raw not found: {}", name).as_str())
}

View file

@ -0,0 +1,10 @@
[Creature]
name = "gormlak"
description = """
A chittering imp-like creature with bright yellow horns. It adores shiny objects
and gathers in swarms.
"""
char = { char = "g", color = "red" }
max_hitpoints = 5
speed = 120
friendly = false

View file

@ -1,11 +1,12 @@
use crate::display::{self, Viewport}; use crate::display::{self, Viewport};
use crate::entities::Character; use crate::entities::Character;
use crate::entities::Entity; use crate::entities::{Creature, Entity};
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::EntityID; use crate::types::entity_map::EntityID;
use crate::types::entity_map::EntityMap; use crate::types::entity_map::EntityMap;
use crate::types::pos;
use crate::types::Ticks; use crate::types::Ticks;
use crate::types::{ use crate::types::{
BoundingBox, Collision, Dimensions, Position, Positioned, PositionedMut, BoundingBox, Collision, Dimensions, Position, Positioned, PositionedMut,
@ -74,6 +75,15 @@ impl<'a> Game<'a> {
None => SmallRng::from_entropy(), None => SmallRng::from_entropy(),
}; };
let mut entities: EntityMap<AnEntity<'a>> = EntityMap::new(); let mut entities: EntityMap<AnEntity<'a>> = EntityMap::new();
// TODO make this dynamic
{
entities.insert(Box::new(Creature::new_from_raw(
"gormlak",
pos(10, 0),
)));
}
Game { Game {
settings, settings,
rng, rng,

View file

@ -348,7 +348,8 @@ pub struct Ticks(pub u16);
pub struct Tiles(pub f32); pub struct Tiles(pub f32);
/// The speed of an entity, expressed in ticks per tile /// The speed of an entity, expressed in ticks per tile
#[derive(Clone, Copy, Debug, PartialEq, Eq, Arbitrary)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Arbitrary, Deserialize)]
#[serde(transparent)]
pub struct Speed(pub u32); pub struct Speed(pub u32);
impl Speed { impl Speed {