diff options
Diffstat (limited to 'dungeon/src/entity.rs')
| -rw-r--r-- | dungeon/src/entity.rs | 219 |
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 |