diff options
Diffstat (limited to 'dungeon/src/enemy.rs')
| -rw-r--r-- | dungeon/src/enemy.rs | 150 |
1 files changed, 144 insertions, 6 deletions
diff --git a/dungeon/src/enemy.rs b/dungeon/src/enemy.rs index 44fa764..0d432dd 100644 --- a/dungeon/src/enemy.rs +++ b/dungeon/src/enemy.rs @@ -1,24 +1,80 @@ -use crate::{Entity, EntityMoveSpeed, Pos}; +use crate::{Direction, Entity, EntityMoveSpeed, Pos, Tile, astar}; + +pub const MIN_ROAM_DIST: u16 = 1; +pub const MAX_ROAM_DIST: u16 = 4; pub const ZOMBIE_HEALTH: u32 = 50; +pub const ENEMY_VISION_RADIUS: u16 = 5; + +pub const IDLE_WAIT: f32 = 1.; + +#[derive(Clone, Debug, PartialEq)] +pub enum EnemyMoveState { + Idle(f32), + Roam(Vec<Pos>), + Attack(Vec<Pos>), +} -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +impl EnemyMoveState { + pub 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; + assert!( + loop_index < 100, + "EnemyMoveState::new_roam couldn't find a valid spot around {starting_pos:?} in between {MIN_ROAM_DIST}-{MAX_ROAM_DIST} tiles", + ); + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] pub enum EnemyAttackType { Melee, Ranged, } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[derive(Clone, Debug, PartialEq, Eq, Copy)] pub enum EnemyType { Zombie, } -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub struct Enemy { pub entity: Entity, pub enemy_type: EnemyType, pub attack_type: EnemyAttackType, - time_acc: f32, + move_state: EnemyMoveState, } impl Enemy { @@ -40,7 +96,7 @@ impl Enemy { entity, enemy_type, attack_type, - time_acc: 0., + move_state: EnemyMoveState::new_idle(), } } @@ -53,4 +109,86 @@ impl Enemy { EnemyAttackType::Melee, ) } + + pub fn handle_movement(&mut self, player_pos: Pos, delta_time: f32, tiles: &[Tile]) { + // Check if player is in range + if !matches!(self.move_state, EnemyMoveState::Attack { .. }) + && let Some(move_state) = + EnemyMoveState::new_attack(self.entity.pos, player_pos, tiles) + { + self.move_state = move_state; + return; + } + + match &mut self.move_state { + EnemyMoveState::Idle(idle_accum) => { + *idle_accum += delta_time; + if *idle_accum < IDLE_WAIT { + return; + } + + self.move_state = EnemyMoveState::new_roam(self.entity.pos, tiles); + } + EnemyMoveState::Roam(moves) => { + let p = if let Some(p) = moves.last() { + p + } else { + self.move_state = EnemyMoveState::new_idle(); + return; + }; + + Self::movement_helper(&mut self.entity, *p, moves, delta_time); + } + EnemyMoveState::Attack(moves) => { + // Stop at one move left so the enemy is flush with the player tile + if moves.len() == 1 { + return; + } + + Self::movement_helper( + &mut self.entity, + *moves.last().unwrap_or(&Pos::default()), + moves, + delta_time, + ); + } + } + } + + fn movement_helper( + entity: &mut Entity, + 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; + } + } + } 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; + } + } + } 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; + } + } + } 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; + } + } + }; + } } |