summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--dungeon/src/enemy.rs194
-rw-r--r--dungeon/src/entity.rs219
-rw-r--r--dungeon/src/lib.rs7
-rw-r--r--game/src/main.rs2
-rw-r--r--graphics/src/render.rs4
5 files changed, 189 insertions, 237 deletions
diff --git a/dungeon/src/enemy.rs b/dungeon/src/enemy.rs
deleted file mode 100644
index 0d432dd..0000000
--- a/dungeon/src/enemy.rs
+++ /dev/null
@@ -1,194 +0,0 @@
-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>),
-}
-
-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(Clone, Debug, PartialEq, Eq, Copy)]
-pub enum EnemyType {
- Zombie,
-}
-
-#[derive(Clone, Debug, PartialEq)]
-pub struct Enemy {
- pub entity: Entity,
- pub enemy_type: EnemyType,
- pub attack_type: EnemyAttackType,
- move_state: EnemyMoveState,
-}
-
-impl Enemy {
- pub fn new(enemy_type: EnemyType, pos: Pos) -> Self {
- match enemy_type {
- EnemyType::Zombie => Self::zombie(pos),
- }
- }
-
- fn _new(
- pos: Pos,
- health: u32,
- move_speed: EntityMoveSpeed,
- enemy_type: EnemyType,
- attack_type: EnemyAttackType,
- ) -> Self {
- let entity = Entity::enemy(pos, move_speed, health);
- Self {
- entity,
- enemy_type,
- attack_type,
- move_state: EnemyMoveState::new_idle(),
- }
- }
-
- fn zombie(pos: Pos) -> Self {
- Self::_new(
- pos,
- ZOMBIE_HEALTH,
- EntityMoveSpeed::Slow,
- EnemyType::Zombie,
- 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;
- }
- }
- };
- }
-}
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
diff --git a/dungeon/src/lib.rs b/dungeon/src/lib.rs
index e85b718..cfd2fbe 100644
--- a/dungeon/src/lib.rs
+++ b/dungeon/src/lib.rs
@@ -3,7 +3,6 @@
pub mod astar;
pub mod bsp;
-pub mod enemy;
pub mod entity;
pub mod map;
pub mod pos;
@@ -13,15 +12,13 @@ pub use entity::*;
pub use map::*;
pub use pos::*;
-use crate::enemy::{Enemy, EnemyType};
-
/// The `Dungeon` type represents the game state of the
/// dungeon crawler.
#[derive(Clone, Debug, PartialEq)]
pub struct Dungeon {
pub floor: Floor,
pub player: Player,
- pub enemies: Vec<Enemy>,
+ pub enemies: Vec<Entity>,
}
impl Dungeon {
/// Creates a new `Dungeon`.
@@ -71,7 +68,7 @@ impl From<Floor> for Dungeon {
// TODO: initalize rest of game state
// TODO: Randomize enemy positions/types
- let enemies = vec![Enemy::new(EnemyType::Zombie, floor.random_pos())];
+ let enemies = vec![Entity::zombie(floor.random_pos())];
Self {
floor,
diff --git a/game/src/main.rs b/game/src/main.rs
index 919e22c..1c76abc 100644
--- a/game/src/main.rs
+++ b/game/src/main.rs
@@ -14,8 +14,8 @@ fn main() -> Result<()> {
for enemy in dungeon.enemies.iter_mut() {
enemy.handle_movement(
dungeon.player.entity.pos,
+ dungeon.floor.tiles(),
window.delta_time(),
- dungeon.floor.tiles_mut(),
);
}
diff --git a/graphics/src/render.rs b/graphics/src/render.rs
index a6b9af0..1fe4b85 100644
--- a/graphics/src/render.rs
+++ b/graphics/src/render.rs
@@ -357,7 +357,7 @@ impl Renderer {
{
self.draw_entity(r, &dungeon.player.entity);
for enemy in &dungeon.enemies {
- self.draw_entity(r, &enemy.entity);
+ self.draw_entity(r, enemy);
}
}
@@ -468,7 +468,7 @@ impl Renderer {
// Draw enemy dots
for enemy in &dungeon.enemies {
- draw_dot(enemy.entity.pos, Color::RED);
+ draw_dot(enemy.pos, Color::RED);
}
// Draw player dot