//! 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 msg; pub mod player; pub mod player_input; pub mod pos; pub mod rng; use rand::{Rng, SeedableRng, TryRngCore, rngs::OsRng}; use crate::{ entity::Entity, map::{Floor, Tile}, msg::Message, player::{Player, PlayerAction}, player_input::PlayerInput, pos::FPos, rng::DungeonRng, }; /// Lets the caller know what has /// changed in the game state this /// tick #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum UpdateResult { /// Default, entities have moved, player done stuff Default(PlayerAction), /// We have moved to the next floor NextFloor, /// Message on screen updated. /// Contains if a char was added. MessageUpdated(bool), /// Game over :( GameOver, /// We have done nothing! Nothing, } /// The `Dungeon` type represents the game state of the /// dungeon crawler. #[derive(Debug, Clone)] pub struct Dungeon { pub floor: Floor, pub player: Player, pub entities: Vec, pub msg: Message, seed: u64, level_rng: DungeonRng, game_rng: DungeonRng, game_over: bool, } impl Dungeon { /// Creates a new `Dungeon` with a provided seed. /// /// # Examples /// /// ```no_run /// use dungeon::Dungeon; /// /// let seed = 234690523482u64; /// let dungeon = Dungeon::new(seed); /// ``` #[must_use] pub fn new(seed: u64) -> Self { let mut game_rng = DungeonRng::seed_from_u64(seed); let mut level_rng = DungeonRng::seed_from_u64(game_rng.random()); let floor = bsp::generate(&mut level_rng); let player = Player::new(floor.player_start()); let entities = vec![]; let msg = Message::empty(); let game_over = false; let mut dungeon = Self { floor, player, entities, msg, seed, level_rng, game_rng, game_over, }; dungeon.spawn_enimies(); dungeon } /// Creates a new `Dungeon` with a random seed /// /// # Examples /// /// ```no_run /// use dungeon::Dungeon; /// /// let dungeon = Dungeon::random(); /// ``` #[must_use] pub fn random() -> Self { let seed = OsRng.try_next_u64().unwrap_or_default(); Self::new(seed) } /// Returns the current position of the camera (viewer) #[must_use] pub const fn camera(&self) -> FPos { self.player.entity.fpos } /// Returns the seed used to generate the map #[must_use] pub const fn seed(&self) -> u64 { self.seed } /// Returns the runtime random number gen for the `Floor` #[must_use] pub const fn rng(&mut self) -> &mut DungeonRng { &mut self.game_rng } pub fn update(&mut self, player_input: PlayerInput, delta_time: f32) -> UpdateResult { if self.game_over { UpdateResult::Nothing } else if self.msg.visible() { let changed = self.msg.update(player_input); UpdateResult::MessageUpdated(changed) } else { let mut action = self.update_player(player_input); let curr_player_pos = self.player.entity.fpos; self.update_entities(player_input, delta_time); if self .player .entity .fpos .abs_diff(curr_player_pos) .magnitude() > 0.0 { // player has moved action.walk = true; } if self.player.entity.health < 1 { // player has died :( self.game_over = true; return UpdateResult::GameOver; } if self.floor.get(self.player.entity.pos) == Tile::Stairs { // we are moving to a new floor self.floor = bsp::generate(&mut self.level_rng); self.player.entity.teleport(self.floor.player_start()); self.entities.clear(); self.spawn_enimies(); return UpdateResult::NextFloor; } UpdateResult::Default(action) } } fn spawn_enimies(&mut self) { // TODO: better entity spawning let zombie = Entity::zombie(self.floor.random_walkable_pos(&mut self.level_rng)); self.entities.push(zombie); } }