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:
parent
081146da30
commit
e7ad87c730
9 changed files with 306 additions and 5 deletions
149
src/display/color.rs
Normal file
149
src/display/color.rs
Normal 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())
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
43
src/entities/creature.rs
Normal file
43
src/entities/creature.rs
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
22
src/entities/entity_char.rs
Normal file
22
src/entities/entity_char.rs
Normal 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)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
57
src/entities/raws.rs
Normal 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())
|
||||||
|
}
|
10
src/entities/raws/gormlak.toml
Normal file
10
src/entities/raws/gormlak.toml
Normal 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
|
12
src/game.rs
12
src/game.rs
|
@ -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,
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in a new issue