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 utils;
|
||||
pub mod viewport;
|
||||
|
@ -17,13 +18,13 @@ pub trait Draw: Positioned {
|
|||
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<()> {
|
||||
(**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<()> {
|
||||
(**self).do_draw(out)
|
||||
}
|
||||
|
|
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 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::types::{Positioned, PositionedMut};
|
||||
pub use character::Character;
|
||||
use downcast_rs::Downcast;
|
||||
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::entities::Character;
|
||||
use crate::entities::Entity;
|
||||
use crate::entities::{Creature, Entity};
|
||||
use crate::messages::message;
|
||||
use crate::settings::Settings;
|
||||
use crate::types::command::Command;
|
||||
use crate::types::entity_map::EntityID;
|
||||
use crate::types::entity_map::EntityMap;
|
||||
use crate::types::pos;
|
||||
use crate::types::Ticks;
|
||||
use crate::types::{
|
||||
BoundingBox, Collision, Dimensions, Position, Positioned, PositionedMut,
|
||||
|
@ -74,6 +75,15 @@ impl<'a> Game<'a> {
|
|||
None => SmallRng::from_entropy(),
|
||||
};
|
||||
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 {
|
||||
settings,
|
||||
rng,
|
||||
|
|
|
@ -348,7 +348,8 @@ pub struct Ticks(pub u16);
|
|||
pub struct Tiles(pub f32);
|
||||
|
||||
/// 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);
|
||||
|
||||
impl Speed {
|
||||
|
|
Loading…
Reference in a new issue