//! The `dungon` crate contains the core functionality for //! interacting with a `Dungeon` and its components. pub mod astar; pub mod bsp; pub mod entity; pub mod map; pub mod player_input; pub mod pos; pub use bsp::*; pub use entity::*; pub use map::*; pub use player_input::*; pub use pos::*; use rand::{ SeedableRng, TryRngCore, rngs::{OsRng, SmallRng}, }; /// The `Dungeon` type represents the game state of the /// dungeon crawler. #[derive(Debug, Clone)] pub struct Dungeon { pub floor: Floor, pub player: Player, pub enemies: Vec, } impl Dungeon { /// Creates a new `Dungeon` with a provided seed. /// /// # Examples /// /// ```no_run /// use dungeon::Dungeon; /// use rand::{SeedableRng, rngs::SmallRng}; /// /// let seed = 234690523482u64; /// let rng = SmallRng::seed_from_u64(seed); /// let dungeon = Dungeon::with_rng(seed, rng); /// ``` #[must_use] pub fn with_rng(seed: u64, rng: SmallRng) -> Self { Self::from(bsp::generate(seed, rng)) } /// Returns the current position of the camera (viewer) #[must_use] pub fn camera(&self) -> FPos { self.player.entity.fpos } pub fn update(&mut self, player_input: PlayerInput, delta_time: f32) { self.act_player(player_input, delta_time); self.act_non_players(delta_time); } fn act_player(&mut self, player_input: PlayerInput, delta_time: f32) { let mut move_distance = self.player.entity.kind.move_speed() * delta_time; // having this be a loop is a *little* unnecessary, // but is technically more correct if the entity is fast enough // to move more than one tile in a single frame // (plus, it was easier to write if i thought of this like a state machine) loop { match (self.player.moving_to, player_input.direction) { (Some(pos), _) => { move_distance -= self .player .entity .fpos .move_towards_manhattan(pos.into(), move_distance); if move_distance == 0.0 { // can't move any further break; } // otherwise, reached `pos` self.player.entity.pos = pos; self.player.moving_to = None; // continue moving } (None, Some(dir)) => { // set direction & find out next position self.player.entity.dir = dir; if let Some(pos) = self.player.entity.pos.step(dir) && self.floor.get(pos).is_walkable() { self.player.moving_to = Some(pos); } else { break; } } (None, None) => break, } } // TODO: reuse a similar structure across all entities } fn act_non_players(&mut self, delta_time: f32) { for enemy in self.enemies.iter_mut() { enemy.handle_movement(self.player.entity.pos, &mut self.floor, delta_time); } } } impl Default for Dungeon { /// Creates a new `Dungeon` with a default rng. /// /// # Examples /// /// ```no_run /// use dungeon::Dungeon; /// /// let dungeon = Dungeon::default(); /// ``` fn default() -> Self { let seed = OsRng.try_next_u64().unwrap_or(0); let rng = SmallRng::seed_from_u64(seed); let floor = bsp::generate(seed, rng); Self::from(floor) } } impl From for Dungeon { fn from(mut floor: Floor) -> Self { let player = Player::new(floor.player_start()); // TODO: initalize rest of game state // TODO: Randomize enemy positions/types let enemies = vec![Entity::zombie(floor.random_walkable_pos())]; Self { floor, player, enemies, } } }