summaryrefslogtreecommitdiff
path: root/dungeon/src/entity.rs
diff options
context:
space:
mode:
Diffstat (limited to 'dungeon/src/entity.rs')
-rw-r--r--dungeon/src/entity.rs219
1 files changed, 184 insertions, 35 deletions
diff --git a/dungeon/src/entity.rs b/dungeon/src/entity.rs
index f20c534..aa77d98 100644
--- a/dungeon/src/entity.rs
+++ b/dungeon/src/entity.rs
@@ -1,6 +1,6 @@
//! The `entity` module contains structures of all entities including players and enimies.
-use crate::{Direction, FPos, Pos, const_pos};
+use crate::{Direction, FPos, Pos, Tile, astar, const_pos};
/// `PLAYER_FULL_HEALTH` is the starting health of the player entity
pub const PLAYER_FULL_HEALTH: u32 = 10;
@@ -17,28 +17,79 @@ pub enum Item {
}
/// The `EntityKind` represents what kind of entity this is.
-#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+#[derive(Clone, Debug, PartialEq)]
pub enum EntityKind {
/// The main player
Player,
- Enemy,
+ Zombie(EnemyMoveState),
/// An item (not in an inventory) on the floor of the dungeon
Item(Item),
}
-#[derive(Clone, Copy, Debug, PartialEq, Eq)]
-pub enum EntityMoveSpeed {
- Slow,
- Medium,
- Fast,
-}
-impl EntityMoveSpeed {
- /// Returns value in tiles/second
- pub fn value(&self) -> f32 {
+impl EntityKind {
+ // Tiles/s
+ pub fn move_speed(&self) -> f32 {
match &self {
- Self::Slow => 1.,
- Self::Medium => 2.,
- Self::Fast => 3.,
+ Self::Player => 2.,
+ Self::Zombie(_) => 4.,
+ _ => 0.,
+ }
+ }
+}
+
+pub const MIN_ROAM_DIST: u16 = 1;
+pub const MAX_ROAM_DIST: u16 = 4;
+
+pub const ZOMBIE_HEALTH: u32 = 5;
+pub const ENEMY_VISION_RADIUS: u16 = 10;
+
+pub const IDLE_WAIT: f32 = 1.;
+
+#[derive(Clone, Debug, PartialEq)]
+pub enum EnemyMoveState {
+ Idle(f32),
+ Roam(Vec<Pos>),
+ Attack(Vec<Pos>),
+}
+
+impl EnemyMoveState {
+ pub const fn new_idle() -> Self {
+ Self::Idle(0.)
+ }
+
+ pub fn new_attack(starting_pos: Pos, player_pos: Pos, tiles: &[Tile]) -> Option<Self> {
+ if player_pos.manhattan(starting_pos) < ENEMY_VISION_RADIUS {
+ let data = astar::find_path(tiles, starting_pos, player_pos)?;
+ let mut path = data.0;
+ path.reverse();
+ return Some(Self::Attack(path));
+ }
+ None
+ }
+
+ pub fn new_roam(starting_pos: Pos, tiles: &[Tile]) -> Self {
+ let mut rand_dir = Direction::get_random_dir();
+ let mut rand_tiles = rand::random_range(MIN_ROAM_DIST..=MAX_ROAM_DIST);
+ let mut loop_index = 0;
+ loop {
+ if let Some(p) = starting_pos.step_by(rand_dir, rand_tiles) {
+ if !tiles[p.idx()].is_wall() {
+ if let Some(data) = astar::find_path(tiles, starting_pos, p) {
+ let mut path = data.0;
+ path.reverse();
+ return Self::Roam(path);
+ }
+ }
+ }
+
+ rand_dir = Direction::get_random_dir();
+ rand_tiles = rand::random_range(MIN_ROAM_DIST..=MAX_ROAM_DIST);
+
+ // Safety check
+ loop_index += 1;
+ if loop_index >= 100 {
+ return Self::new_idle();
+ }
}
}
}
@@ -46,7 +97,7 @@ impl EntityMoveSpeed {
/// The `Entity` kind represents the main player, or any other
/// ai autonomous character that can move freely across the
/// dungeon.
-#[derive(Clone, Copy, Debug, PartialEq)]
+#[derive(Clone, Debug, PartialEq)]
pub struct Entity {
/// The fixed grid position of the entity
pub pos: Pos,
@@ -56,8 +107,6 @@ pub struct Entity {
pub dir: Direction,
/// Which kind this entity is (along with entity kind specific data)
pub kind: EntityKind,
- /// Move speed of this entity
- pub move_speed: EntityMoveSpeed,
/// The amount of health this entity has (None if this Entity does not use health)
pub health: Option<u32>,
}
@@ -67,21 +116,19 @@ impl Entity {
/// # Examples
///
/// ```
- /// use dungeon::{Pos, Direction, Entity, EntityKind, EntityMoveSpeed};
+ /// use dungeon::{Pos, Direction, Entity, EntityKind};
///
/// let pos = Pos::new(0, 0).unwrap();
/// let dir = Direction::North;
/// let kind = EntityKind::Player;
/// let health = Some(10);
- /// let move_speed = EntityMoveSpeed::Medium;
- /// let entity = Entity::new(pos, dir, kind, move_speed, health);
+ /// let entity = Entity::new(pos, dir, kind, health);
/// ```
#[must_use]
pub const fn new(
pos: Pos,
dir: Direction,
kind: EntityKind,
- move_speed: EntityMoveSpeed,
health: Option<u32>,
) -> Self {
let fpos = FPos::from_pos(pos);
@@ -90,7 +137,6 @@ impl Entity {
fpos,
dir,
kind,
- move_speed,
health,
}
}
@@ -109,36 +155,139 @@ impl Entity {
pub const fn player(pos: Pos) -> Self {
let dir = Direction::East;
let kind = EntityKind::Player;
- let move_speed = EntityMoveSpeed::Medium;
let health = Some(PLAYER_FULL_HEALTH);
- Self::new(pos, dir, kind, move_speed, health)
+ Self::new(pos, dir, kind, health)
}
- /// Creates an Enemy version of the `Entity`
+ /// Creates the Zombie version of the `Entity`
///
/// # Examples
///
/// ```
- /// use dungeon::{Pos, Entity, EntityMoveSpeed};
+ /// use dungeon::{Pos, Entity};
///
/// let pos = Pos::new(0, 0).unwrap();
- /// let move_speed = EntityMoveSpeed::Medium;
- /// let health = 8;
- /// let enemy = Entity::enemy(pos, move_speed, health);
+ /// let player = Entity::zombie(pos);
/// ```
- #[must_use]
- pub const fn enemy(pos: Pos, move_speed: EntityMoveSpeed, health: u32) -> Self {
+ pub const fn zombie(pos: Pos) -> Self {
let dir = Direction::East;
- let kind = EntityKind::Enemy;
- Self::new(pos, dir, kind, move_speed, Some(health))
+ let kind = EntityKind::Zombie(EnemyMoveState::new_idle());
+ let health = Some(ZOMBIE_HEALTH);
+ Self::new(pos, dir, kind, health)
}
pub fn move_by_dir(&mut self, dir: Direction, delta_time: f32) {
- if let Some(fp) = self.fpos.step_by(dir, delta_time * self.move_speed.value()) {
+ if let Some(fp) = self.fpos.step_by(dir, delta_time * self.kind.move_speed()) {
// TODO: collision
self.fpos = fp;
}
}
+
+ pub fn handle_movement(&mut self, player_pos: Pos, tiles: &[Tile], delta_time: f32) {
+ match &self.kind {
+ EntityKind::Zombie(move_state) => {
+ self.zombie_movement(move_state.clone(), player_pos, delta_time, tiles);
+ }
+ EntityKind::Player => {}
+ _ => {}
+ }
+ }
+
+ pub fn zombie_movement(
+ &mut self,
+ move_state: EnemyMoveState,
+ player_pos: Pos,
+ delta_time: f32,
+ tiles: &[Tile],
+ ) {
+ // Check if player is in range
+ if !matches!(move_state, EnemyMoveState::Attack { .. })
+ && let Some(m_state) = EnemyMoveState::new_attack(self.pos, player_pos, tiles)
+ {
+ self.kind = EntityKind::Zombie(m_state);
+ return;
+ }
+
+ match move_state {
+ EnemyMoveState::Idle(idle_accum) => {
+ if idle_accum < IDLE_WAIT {
+ self.kind =
+ EntityKind::Zombie(EnemyMoveState::Idle(idle_accum + delta_time));
+ return;
+ }
+
+ self.kind = EntityKind::Zombie(EnemyMoveState::new_roam(self.pos, tiles));
+ }
+ EnemyMoveState::Roam(mut moves) => {
+ let p = if let Some(p) = moves.last() {
+ p
+ } else {
+ self.kind = EntityKind::Zombie(EnemyMoveState::new_idle());
+ return;
+ };
+
+ Self::movement_helper(self, *p, &mut moves, delta_time);
+
+ self.kind = EntityKind::Zombie(EnemyMoveState::Roam(moves));
+ }
+ EnemyMoveState::Attack(mut moves) => {
+ // Stop at one move left so the enemy is flush with the player tile
+ if moves.len() == 1 {
+ return;
+ }
+
+ Self::movement_helper(
+ self,
+ *moves.last().unwrap_or(&Pos::default()),
+ &mut moves,
+ delta_time,
+ );
+
+ self.kind = EntityKind::Zombie(EnemyMoveState::Attack(moves));
+ }
+ }
+ }
+
+ fn movement_helper(
+ entity: &mut Self,
+ next_move: Pos,
+ moves: &mut Vec<Pos>,
+ delta_time: f32,
+ ) {
+ if entity.pos.y() > next_move.y() {
+ entity.move_by_dir(Direction::North, delta_time);
+ if entity.fpos.y() <= next_move.y() as f32 {
+ if let Some(p) = moves.pop() {
+ entity.pos = p;
+ entity.fpos = FPos::from_pos(p);
+ }
+ }
+ } else if entity.pos.y() < next_move.y() {
+ entity.move_by_dir(Direction::South, delta_time);
+ if entity.fpos.y() >= next_move.y() as f32 {
+ if let Some(p) = moves.pop() {
+ entity.pos = p;
+ entity.fpos = FPos::from_pos(p);
+ }
+ }
+ } else if entity.pos.x() < next_move.x() {
+ entity.move_by_dir(Direction::East, delta_time);
+ if entity.fpos.x() >= next_move.x() as f32 {
+ if let Some(p) = moves.pop() {
+ entity.pos = p;
+ entity.fpos = FPos::from_pos(p);
+ }
+ }
+ } else {
+ entity.move_by_dir(Direction::West, delta_time);
+ if entity.fpos.x() <= next_move.x() as f32 {
+ if let Some(p) = moves.pop() {
+ entity.pos = p;
+ entity.fpos = FPos::from_pos(p);
+ }
+ }
+ };
+ }
}
/// The `Player` type represents the main player entity