about summary refs log tree commit diff
diff options
context:
space:
mode:
authorGriffin Smith <root@gws.fyi>2019-07-14T20·20-0400
committerGriffin Smith <root@gws.fyi>2019-07-14T20·20-0400
commit575a051e6efcd8fd3b0a146f49040e543ae8e5b0 (patch)
tree3507592582cdffdd73ba7ca9ae893117682581b7
parente7ad87c7301f266dece36e7558c0f212e370aac6 (diff)
Implement extremely basic combat
There's a gormlak, you can kill it.
That's it.
-rw-r--r--src/entities/character.rs15
-rw-r--r--src/entities/creature.rs24
-rw-r--r--src/entities/entity.rs87
-rw-r--r--src/entities/mod.rs18
-rw-r--r--src/entities/raws.rs1
-rw-r--r--src/game.rs72
-rw-r--r--src/main.rs5
-rw-r--r--src/messages.toml4
-rw-r--r--src/types/entity_map.rs42
9 files changed, 220 insertions, 48 deletions
diff --git a/src/entities/character.rs b/src/entities/character.rs
index fb5a89591c..7bcb8b5c87 100644
--- a/src/entities/character.rs
+++ b/src/entities/character.rs
@@ -1,5 +1,5 @@
 use crate::display;
-use crate::entities::Entity;
+use crate::entities::EntityID;
 use crate::types::{Position, Speed};
 use proptest_derive::Arbitrary;
 use std::io::{self, Write};
@@ -9,6 +9,8 @@ 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,
 }
@@ -16,6 +18,7 @@ pub struct Character {
 impl Character {
     pub fn new() -> Character {
         Character {
+            id: None,
             position: Position { x: 0, y: 0 },
         }
     }
@@ -23,12 +26,14 @@ impl Character {
     pub fn speed(&self) -> Speed {
         Speed(100)
     }
-}
 
-positioned!(Character);
-positioned_mut!(Character);
+    pub fn damage(&self) -> u16 {
+        // TODO
+        1
+    }
+}
 
-impl Entity for Character {}
+entity!(Character);
 
 impl display::Draw for Character {
     fn do_draw(&self, out: &mut Write) -> io::Result<()> {
diff --git a/src/entities/creature.rs b/src/entities/creature.rs
index 6ddeade218..55445f951b 100644
--- a/src/entities/creature.rs
+++ b/src/entities/creature.rs
@@ -1,11 +1,13 @@
 use crate::display;
 use crate::entities::raws::CreatureType;
 use crate::entities::raws::EntityRaw;
-use crate::entities::{raw, Entity};
+use crate::entities::{raw, EntityID};
 use crate::types::Position;
 use std::io::{self, Write};
 
+#[derive(Debug)]
 pub struct Creature {
+    pub id: Option<EntityID>,
     pub typ: &'static CreatureType<'static>,
     pub position: Position,
     pub hitpoints: u16,
@@ -24,17 +26,29 @@ impl Creature {
         position: Position,
     ) -> Self {
         Creature {
+            id: None,
             typ,
             position,
             hitpoints: typ.max_hitpoints,
         }
     }
-}
 
-positioned!(Creature);
-positioned_mut!(Creature);
+    /// Damage the given creature by the given amount
+    pub fn damage(&mut self, amount: u16) {
+        if self.hitpoints <= amount {
+            self.hitpoints = 0;
+        } else {
+            self.hitpoints -= amount;
+        }
+    }
+
+    /// Returns true if this creature has died
+    pub fn dead(&self) -> bool {
+        self.hitpoints <= 0
+    }
+}
 
-impl Entity for Creature {}
+entity!(Creature);
 
 impl display::Draw for Creature {
     fn do_draw(&self, out: &mut Write) -> io::Result<()> {
diff --git a/src/entities/entity.rs b/src/entities/entity.rs
new file mode 100644
index 0000000000..30f7ea9a3d
--- /dev/null
+++ b/src/entities/entity.rs
@@ -0,0 +1,87 @@
+use crate::display::Draw;
+use crate::entities::EntityID;
+use crate::types::{Positioned, PositionedMut};
+use downcast_rs::Downcast;
+use std::fmt::Debug;
+use std::io::{self, Write};
+
+pub trait Identified<ID>: Debug {
+    fn opt_id(&self) -> Option<ID>;
+    fn set_id(&mut self, id: ID);
+
+    fn id(&self) -> ID {
+        self.opt_id()
+            .expect(format!("Entity ({:?}) is not in the game", self).as_str())
+    }
+}
+
+impl<'a, A, ID> Identified<ID> for &'a mut A
+where
+    A: Identified<ID>,
+{
+    fn opt_id(&self) -> Option<ID> {
+        (**self).opt_id()
+    }
+    fn set_id(&mut self, id: ID) {
+        (**self).set_id(id);
+    }
+}
+
+impl<ID, A: Identified<ID>> Identified<ID> for Box<A> {
+    fn opt_id(&self) -> Option<ID> {
+        (**self).opt_id()
+    }
+    fn set_id(&mut self, id: ID) {
+        (**self).set_id(id);
+    }
+}
+
+pub trait Entity:
+    Positioned + PositionedMut + Identified<EntityID> + Draw + Downcast
+{
+}
+
+impl Identified<EntityID> for Box<dyn Entity> {
+    fn opt_id(&self) -> Option<EntityID> {
+        (**self).opt_id()
+    }
+    fn set_id(&mut self, id: EntityID) {
+        (**self).set_id(id);
+    }
+}
+
+#[macro_export]
+macro_rules! identified {
+    ($name: ident, $typ: ident) => {
+        identified!($name, $typ, id);
+    };
+    ($name: ident, $typ: ident, $attr: ident) => {
+        impl crate::entities::entity::Identified<$typ> for $name {
+            fn opt_id(&self) -> Option<$typ> {
+                self.$attr
+            }
+
+            fn set_id(&mut self, id: $typ) {
+                self.$attr = Some(id)
+            }
+        }
+    };
+}
+
+#[macro_export]
+macro_rules! entity {
+    ($name: ident) => {
+        positioned!($name);
+        positioned_mut!($name);
+        identified!($name, EntityID);
+        impl crate::entities::entity::Entity for $name {}
+    };
+}
+
+impl_downcast!(Entity);
+
+impl Draw for Box<dyn Entity> {
+    fn do_draw(&self, out: &mut Write) -> io::Result<()> {
+        (**self).do_draw(out)
+    }
+}
diff --git a/src/entities/mod.rs b/src/entities/mod.rs
index c4f46bf4a7..ed83f2f462 100644
--- a/src/entities/mod.rs
+++ b/src/entities/mod.rs
@@ -1,3 +1,5 @@
+#[macro_use]
+pub mod entity;
 pub mod character;
 pub mod creature;
 pub mod entity_char;
@@ -5,20 +7,8 @@ pub mod raws;
 
 pub use character::Character;
 pub use creature::Creature;
+pub use entity::{Entity, Identified};
 pub use entity_char::EntityChar;
 pub use raws::raw;
 
-use crate::display::Draw;
-use crate::types::{Positioned, PositionedMut};
-use downcast_rs::Downcast;
-use std::io::{self, Write};
-
-pub trait Entity: Positioned + PositionedMut + Draw + Downcast {}
-
-impl_downcast!(Entity);
-
-impl Draw for Box<dyn Entity> {
-    fn do_draw(&self, out: &mut Write) -> io::Result<()> {
-        (**self).do_draw(out)
-    }
-}
+pub type EntityID = u32;
diff --git a/src/entities/raws.rs b/src/entities/raws.rs
index beeb90a40c..da061d89d8 100644
--- a/src/entities/raws.rs
+++ b/src/entities/raws.rs
@@ -49,7 +49,6 @@ lazy_static! {
 }
 
 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)
diff --git a/src/game.rs b/src/game.rs
index f86d32d046..57c04cfb88 100644
--- a/src/game.rs
+++ b/src/game.rs
@@ -1,15 +1,12 @@
 use crate::display::{self, Viewport};
-use crate::entities::Character;
-use crate::entities::{Creature, Entity};
+use crate::entities::{Character, Creature, Entity, EntityID, Identified};
 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,
+    pos, BoundingBox, Collision, Dimensions, Position, Positioned,
+    PositionedMut, Ticks,
 };
 use rand::rngs::SmallRng;
 use rand::SeedableRng;
@@ -100,21 +97,29 @@ impl<'a> Game<'a> {
         }
     }
 
+    /// Returns a list of all creature entities at the given position
+    fn creatures_at<'b>(&'b self, pos: Position) -> Vec<&'b Creature> {
+        self.entities
+            .at(pos)
+            .iter()
+            .filter_map(|e| e.downcast_ref())
+            .collect()
+    }
+
     /// Returns a collision, if any, at the given Position in the game
     fn collision_at(&self, pos: Position) -> Option<Collision> {
         if !pos.within(self.viewport.inner) {
             Some(Collision::Stop)
         } else {
-            None
+            if self.creatures_at(pos).len() > 0 {
+                Some(Collision::Combat)
+            } else {
+                None
+            }
         }
     }
 
     fn character(&self) -> &Character {
-        debug!(
-            "ents: {:?} cid: {:?}",
-            self.entities.ids().map(|id| *id).collect::<Vec<u32>>(),
-            self.character_entity_id
-        );
         (*self.entities.get(self.character_entity_id).unwrap())
             .downcast_ref()
             .unwrap()
@@ -128,6 +133,14 @@ impl<'a> Game<'a> {
         Ok(())
     }
 
+    /// 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) {
+            self.viewport.clear(entity.position())?;
+        }
+        Ok(())
+    }
+
     /// Step the game forward the given number of ticks
     fn tick(&mut self, ticks: Ticks) {}
 
@@ -153,6 +166,37 @@ impl<'a> Game<'a> {
         self.viewport.write_message(message)
     }
 
+    fn attack(&mut self, creature_id: EntityID) -> io::Result<()> {
+        info!("Attacking creature {:?}", creature_id);
+        self.say("combat.attack")?;
+        let damage = self.character().damage();
+        let creature = self
+            .entities
+            .get_mut(creature_id)
+            .and_then(|e| e.downcast_mut::<Creature>())
+            .expect(
+                format!("Creature ID went away: {:?}", creature_id).as_str(),
+            );
+        creature.damage(damage);
+        if creature.dead() {
+            self.say("combat.killed")?;
+            info!("Killed creature {:?}", creature_id);
+            self.remove_entity(creature_id)?;
+        }
+        Ok(())
+    }
+
+    fn attack_at(&mut self, pos: Position) -> io::Result<()> {
+        let creatures = self.creatures_at(pos);
+        if creatures.len() == 1 {
+            let creature = creatures.get(0).unwrap();
+            self.attack(creature.id())
+        } else {
+            // TODO prompt with a menu of creatures to combat
+            unimplemented!()
+        }
+    }
+
     /// Run the game
     pub fn run(mut self) -> io::Result<()> {
         info!("Running game");
@@ -180,7 +224,9 @@ impl<'a> Game<'a> {
                                 new_pos,
                             );
                         }
-                        Some(Combat) => unimplemented!(),
+                        Some(Combat) => {
+                            self.attack_at(new_pos)?;
+                        }
                         Some(Stop) => (),
                     }
                 }
diff --git a/src/main.rs b/src/main.rs
index f7257a8896..636663e050 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -24,11 +24,12 @@ extern crate include_dir;
 
 #[macro_use]
 mod util;
-mod display;
-mod game;
 #[macro_use]
 mod types;
+#[macro_use]
 mod entities;
+mod display;
+mod game;
 mod messages;
 mod settings;
 
diff --git a/src/messages.toml b/src/messages.toml
index 04746462d5..a6b795d97e 100644
--- a/src/messages.toml
+++ b/src/messages.toml
@@ -1,2 +1,6 @@
 [global]
 welcome = "Welcome to Xanthous! It's dangerous out there, why not stay inside?"
+
+[combat]
+attack = "You attack the {{creature_name}}."
+killed = "You killed the {{creature_name}}."
diff --git a/src/types/entity_map.rs b/src/types/entity_map.rs
index 1d58873bb0..12deaa57a6 100644
--- a/src/types/entity_map.rs
+++ b/src/types/entity_map.rs
@@ -1,3 +1,5 @@
+use crate::entities::entity::Identified;
+use crate::entities::EntityID;
 use crate::types::Position;
 use crate::types::Positioned;
 use crate::types::PositionedMut;
@@ -5,8 +7,6 @@ use std::collections::hash_map::HashMap;
 use std::collections::BTreeMap;
 use std::iter::FromIterator;
 
-pub type EntityID = u32;
-
 #[derive(Debug)]
 pub struct EntityMap<A> {
     by_position: BTreeMap<Position, Vec<EntityID>>,
@@ -83,6 +83,10 @@ impl<A> EntityMap<A> {
         self.by_id.get(&id)
     }
 
+    pub fn get_mut<'a>(&'a mut self, id: EntityID) -> Option<&'a mut A> {
+        self.by_id.get_mut(&id)
+    }
+
     pub fn entities<'a>(&'a self) -> impl Iterator<Item = &'a A> {
         self.by_id.values()
     }
@@ -101,10 +105,11 @@ impl<A> EntityMap<A> {
     }
 }
 
-impl<A: Positioned> EntityMap<A> {
-    pub fn insert(&mut self, entity: A) -> EntityID {
+impl<A: Positioned + Identified<EntityID>> EntityMap<A> {
+    pub fn insert(&mut self, mut entity: A) -> EntityID {
         let pos = entity.position();
         let entity_id = self.next_id();
+        entity.set_id(entity_id);
         self.by_id.entry(entity_id).or_insert(entity);
         self.by_position
             .entry(pos)
@@ -112,9 +117,19 @@ impl<A: Positioned> EntityMap<A> {
             .push(entity_id);
         entity_id
     }
+
+    /// Remove the entity with the given ID
+    pub fn remove(&mut self, id: EntityID) -> Option<A> {
+        self.by_id.remove(&id).map(|e| {
+            self.by_position
+                .get_mut(&e.position())
+                .map(|es| es.retain(|e| *e != id));
+            e
+        })
+    }
 }
 
-impl<A: Positioned> FromIterator<A> for EntityMap<A> {
+impl<A: Positioned + Identified<EntityID>> FromIterator<A> for EntityMap<A> {
     fn from_iter<I: IntoIterator<Item = A>>(iter: I) -> Self {
         let mut em = EntityMap::new();
         for ent in iter {
@@ -160,6 +175,7 @@ mod tests {
 
     #[derive(Debug, Arbitrary, PartialEq, Eq, Clone)]
     struct TestEntity {
+        _id: Option<EntityID>,
         position: Position,
         name: String,
     }
@@ -176,6 +192,16 @@ mod tests {
         }
     }
 
+    impl Identified<EntityID> for TestEntity {
+        fn opt_id(&self) -> Option<EntityID> {
+            self._id
+        }
+
+        fn set_id(&mut self, id: EntityID) {
+            self._id = Some(id);
+        }
+    }
+
     fn gen_entity_map() -> BoxedStrategy<EntityMap<TestEntity>> {
         any::<Vec<TestEntity>>()
             .prop_map(|ents| {
@@ -194,7 +220,7 @@ mod tests {
             let mut map = EntityMap::new();
             assert_eq!(map.len(), 0);
             for ent in &items {
-                map.insert(ent);
+                map.insert(ent.clone());
             }
             assert_eq!(map.len(), items.len());
         }
@@ -205,7 +231,7 @@ mod tests {
             ent: TestEntity
         ) {
             em.insert(ent.clone());
-            assert!(em.at(ent.position).iter().any(|e| **e == ent))
+            assert!(em.at(ent.position).iter().any(|e| e.name == ent.name))
         }
 
         #[test]
@@ -214,7 +240,7 @@ mod tests {
             ent: TestEntity
         ) {
             em.insert(ent.clone());
-            assert!(em.entities().any(|e| *e == ent))
+            assert!(em.entities().any(|e| e.name == ent.name))
         }
 
         #[test]