summaryrefslogtreecommitdiff
path: root/dungeon/src/enemy.rs
diff options
context:
space:
mode:
Diffstat (limited to 'dungeon/src/enemy.rs')
-rw-r--r--dungeon/src/enemy.rs150
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;
+ }
+ }
+ };
+ }
}