diff options
| author | Freya Murphy <freya@freyacat.org> | 2025-11-20 18:23:20 -0500 |
|---|---|---|
| committer | Freya Murphy <freya@freyacat.org> | 2025-11-20 18:23:20 -0500 |
| commit | 2d4644889dafa69c06e284b8b2cd18d2f65e57bc (patch) | |
| tree | c26f19e9eedc845d3bfd86557880257af334f0cf /dungeon | |
| parent | dungeon: make chest items optional (no item = open) (diff) | |
| download | DungeonCrawl-2d4644889dafa69c06e284b8b2cd18d2f65e57bc.tar.gz DungeonCrawl-2d4644889dafa69c06e284b8b2cd18d2f65e57bc.tar.bz2 DungeonCrawl-2d4644889dafa69c06e284b8b2cd18d2f65e57bc.zip | |
dungeon: refactor player out of entity.rs
Diffstat (limited to 'dungeon')
| -rw-r--r-- | dungeon/src/entity.rs | 206 | ||||
| -rw-r--r-- | dungeon/src/lib.rs | 24 | ||||
| -rw-r--r-- | dungeon/src/player.rs | 294 |
3 files changed, 304 insertions, 220 deletions
diff --git a/dungeon/src/entity.rs b/dungeon/src/entity.rs index d6ca7e5..5b4ac38 100644 --- a/dungeon/src/entity.rs +++ b/dungeon/src/entity.rs @@ -1,26 +1,18 @@ //! The `entity` module contains structures of all entities including players and enimies. -use std::{ - mem::take, - time::{Duration, Instant}, -}; +use std::mem::take; use rand::Rng; use crate::{ - Dungeon, PlayerAction, astar, const_pos, + Dungeon, astar, map::Floor, + player::Player, player_input::PlayerInput, pos::{Direction, FPos, Pos}, rng::DungeonRng, }; -/// `PLAYER_INVENTORY_SIZE` is the maximum size of the inventory -pub const PLAYER_INVENTORY_SIZE: u16 = 5; - -/// `PLAYER_INVENTORY_SIZE_USIZE` is the maximum size of the inventory -pub const PLAYER_INVENTORY_SIZE_USIZE: usize = PLAYER_INVENTORY_SIZE as usize; - /// 'MIN_ROAM_DIST' and 'MAX_ROAM_DIST' are the enemy roam ranges pub const MIN_ROAM_DIST: u16 = 1; pub const MAX_ROAM_DIST: u16 = 4; @@ -55,7 +47,7 @@ impl Item { player.entity.heal(3); } Self::SpeedPotion => { - player.potion_timer = Instant::now().checked_add(Duration::from_secs(5)); + player.potion_timer.set_secs(5); player.entity.speed = player.entity.speed.inc(); } Self::Bomb => { @@ -339,73 +331,8 @@ impl Entity { } } -/// The current "weapon" level we have -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub enum Weapon { - RustyKnife, - ShiningBlade, - GodlyBlade, -} -impl Weapon { - /// Returns the number of hit points of damage this weapon does - #[must_use] - pub const fn attack_dmg(self) -> u32 { - match self { - Self::RustyKnife => 2, - Self::ShiningBlade => 3, - Self::GodlyBlade => 5, - } - } -} - -/// The `Player` type represents the main player entity -#[derive(Clone, Debug, PartialEq)] -pub struct Player { - pub entity: Entity, - pub inventory: Vec<Item>, - pub weapon: Weapon, - // How long until we reset potion effects? - pub potion_timer: Option<Instant>, - pub active_inv_slot: usize, - pub last_drop_time: Instant, -} -impl Player { - /// Instantiates the game player at a given `Pos` - /// - /// # Examples - /// - /// ``` - /// use dungeon::{pos::Pos, entity::Player}; - /// - /// let pos = Pos::new(1, 2).unwrap(); - /// let player = Player::new(pos); - /// ``` - pub fn new(pos: Pos) -> Self { - let entity = Entity::player(pos); - let inventory = vec![]; - let weapon = Weapon::RustyKnife; - let potion_timer = None; - let active_inv_slot = 0; - let last_drop_time = Instant::now(); - Self { - entity, - inventory, - weapon, - potion_timer, - active_inv_slot, - last_drop_time, - } - } -} -impl Default for Player { - fn default() -> Self { - let pos = const_pos!(1, 1); - Self::new(pos) - } -} - struct Updater<'a> { - floor: &'a mut Floor, + floor: &'a Floor, rng: &'a mut DungeonRng, player_pos: Pos, input: PlayerInput, @@ -540,104 +467,11 @@ impl Updater<'_> { entity.dir = dir; } } - - /// Update player potion timer - fn update_player_potion_effects(player: &mut Player) { - if let Some(timer) = player.potion_timer - && Instant::now() > timer - { - player.potion_timer = None; - player.entity.speed = player.entity.kind.initial_speed(); - } - } - - /// Handle player "Use Item" - fn handle_player_use_item(&mut self, player: &mut Player, action: &mut PlayerAction) { - if self.input.use_item && player.active_inv_slot < player.inventory.len() { - let item = player.inventory.remove(player.active_inv_slot); - action.potion = item.is_potion(); - action.bomb = item.is_bomb(); - item.consume(player, self.floor); - } - } - - /// Handle player "Drop Item" - fn handle_player_drop_item( - &self, - player: &mut Player, - action: &mut PlayerAction, - entities: &mut Vec<Entity>, - ) { - if self.input.drop && player.active_inv_slot < player.inventory.len() { - let item = player.inventory.remove(player.active_inv_slot); - entities.push(Entity::new( - player.entity.pos, - Direction::East, - EntityKind::Item(item), - )); - player.active_inv_slot = player - .active_inv_slot - .max(player.inventory.len().saturating_sub(1)); - action.drop_item = true; - player.last_drop_time = Instant::now(); - } - } - - /// Handle player "Attack" - const fn handle_player_attack(&self, _player: &mut Player, action: &mut PlayerAction) { - if self.input.attack { - // TODO: attack - action.attack = true; - } - } - - /// Handle player "Pickup Items" - fn handle_player_pickup_items( - player: &mut Player, - action: &mut PlayerAction, - entities: &mut Vec<Entity>, - ) { - if player.last_drop_time.elapsed() < Duration::from_secs(3) { - // delay picking up dropped items - return; - } - - let mut idx = 0; - loop { - if idx >= entities.len() { - break; - } - if entities[idx].fpos.abs_diff(player.entity.fpos).magnitude() >= 0.25 { - idx += 1; - continue; - } - let Some(item) = entities[idx].get_item() else { - idx += 1; - continue; - }; - if player.inventory.len() < PLAYER_INVENTORY_SIZE_USIZE { - entities.remove(idx); - player.inventory.push(item); - action.pickup_item = true; - } else { - idx += 1; - } - } - } - - /// Handle player "Change INV Slot" - const fn handle_player_inv_slot(&self, player: &mut Player) { - if let Some(slot) = self.input.inv_slot - && slot < PLAYER_INVENTORY_SIZE_USIZE - { - player.active_inv_slot = slot; - } - } } impl Dungeon { pub(crate) fn update_entities(&mut self, input: PlayerInput, delta_time: f32) { let mut updater = Updater { - floor: &mut self.floor, + floor: &self.floor, rng: &mut self.game_rng, player_pos: self.player.entity.pos, input, @@ -654,32 +488,4 @@ impl Dungeon { } self.entities.retain(Entity::is_alive); } - - pub(crate) fn update_player( - &mut self, - input: PlayerInput, - delta_time: f32, - ) -> PlayerAction { - let mut updater = Updater { - floor: &mut self.floor, - rng: &mut self.game_rng, - player_pos: self.player.entity.pos, - input, - delta_time, - }; - - let mut action = PlayerAction::default(); - Updater::update_player_potion_effects(&mut self.player); - updater.handle_player_use_item(&mut self.player, &mut action); - updater.handle_player_drop_item(&mut self.player, &mut action, &mut self.entities); - Updater::handle_player_pickup_items( - &mut self.player, - &mut action, - &mut self.entities, - ); - updater.handle_player_attack(&mut self.player, &mut action); - updater.handle_player_inv_slot(&mut self.player); - - action - } } diff --git a/dungeon/src/lib.rs b/dungeon/src/lib.rs index fa75c10..91ab378 100644 --- a/dungeon/src/lib.rs +++ b/dungeon/src/lib.rs @@ -6,6 +6,7 @@ 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; @@ -13,32 +14,15 @@ pub mod rng; use rand::{Rng, SeedableRng, TryRngCore, rngs::OsRng}; use crate::{ - entity::{Entity, Player}, + entity::Entity, map::{Floor, Tile}, msg::Message, + player::{Player, PlayerAction}, player_input::PlayerInput, pos::FPos, rng::DungeonRng, }; -/// Lets the caller know what actions -/// exactly the player has done -#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct PlayerAction { - // The player picked up an item - pub pickup_item: bool, - // The player dropped an item - pub drop_item: bool, - // The player moved - pub walk: bool, - // The player attacked - pub attack: bool, - // The player used a bomb - pub bomb: bool, - // The player used a potion - pub potion: bool, -} - /// Lets the caller know what has /// changed in the game state this /// tick @@ -145,7 +129,7 @@ impl Dungeon { let changed = self.msg.update(player_input); UpdateResult::MessageUpdated(changed) } else { - let mut action = self.update_player(player_input, delta_time); + 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 diff --git a/dungeon/src/player.rs b/dungeon/src/player.rs new file mode 100644 index 0000000..34b0ab1 --- /dev/null +++ b/dungeon/src/player.rs @@ -0,0 +1,294 @@ +use std::time::Instant; + +use crate::{ + Dungeon, const_pos, + entity::{Entity, EntityKind, Item}, + map::Floor, + player_input::PlayerInput, + pos::{Direction, Pos}, +}; + +/// `PLAYER_INVENTORY_SIZE` is the maximum size of the inventory +pub const PLAYER_INVENTORY_SIZE: u16 = 5; + +/// `PLAYER_INVENTORY_SIZE_USIZE` is the maximum size of the inventory +pub const PLAYER_INVENTORY_SIZE_USIZE: usize = PLAYER_INVENTORY_SIZE as usize; + +/// Lets the caller know what actions +/// exactly the player has done +#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct PlayerAction { + // The player picked up an item + pub pickup_item: bool, + // The player dropped an item + pub drop_item: bool, + // The player moved + pub walk: bool, + // The player attacked + pub attack: bool, + // The player used a bomb + pub bomb: bool, + // The player used a potion + pub potion: bool, +} + +/// Timer +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)] +pub struct Timer { + start: Option<Instant>, + secs: u64, +} +impl Timer { + pub const fn new() -> Self { + Self { + start: None, + secs: 0, + } + } + + /// Set the timer that triggers in a given amount of seconds + pub fn set_secs(&mut self, secs: u64) { + self.start = Some(Instant::now()); + self.secs = secs; + } + + /// Checks if the timer has finished + pub fn finished(&self) -> bool { + if let Some(start) = self.start { + start.elapsed().as_secs() >= self.secs + } else { + false + } + } + + /// Returns if the timer is set + pub const fn is_set(&self) -> bool { + self.start.is_some() + } + + /// Resets the timer + pub const fn reset(&mut self) { + *self = Self::new(); + } +} + +/// The current "weapon" level we have +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum Weapon { + RustyKnife, + ShiningBlade, + GodlyBlade, +} +impl Weapon { + /// Returns the number of hit points of damage this weapon does + #[must_use] + pub const fn attack_dmg(self) -> u32 { + match self { + Self::RustyKnife => 2, + Self::ShiningBlade => 3, + Self::GodlyBlade => 5, + } + } +} + +/// Represents the player inventory +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)] +pub struct Inventory { + inner: [Option<Item>; PLAYER_INVENTORY_SIZE_USIZE], + active: usize, +} +impl Inventory { + pub const fn new() -> Self { + let inner = [None; PLAYER_INVENTORY_SIZE_USIZE]; + let active = 0; + Self { inner, active } + } + + /// Attemps to set the active inv slot + pub const fn set_active(&mut self, active: usize) { + if active < PLAYER_INVENTORY_SIZE_USIZE { + self.active = active; + } + } + + /// Attemps to take an item based on the active inv slot. + pub fn take(&mut self) -> Option<Item> { + std::mem::take(&mut self.inner[self.active]) + } + + /// Attemps to pickup an item from the ground, returning the + /// item if the inventory is full + pub fn pickup(&mut self, item: Item) -> Option<Item> { + for slot in &mut self.inner { + if slot.is_none() { + *slot = Some(item); + return None; + } + } + Some(item) + } + + /// Returns an iterator over each inv slot, and if the slot is active + pub fn iter(&self) -> impl Iterator<Item = (Option<Item>, bool)> { + self.inner.iter().enumerate().map(|(idx, item)| { + let active = self.active == idx; + (*item, active) + }) + } +} + +/// The `Player` type represents the main player entity +#[derive(Debug, Clone, PartialEq)] +pub struct Player { + pub entity: Entity, + pub inventory: Inventory, + pub weapon: Weapon, + pub potion_timer: Timer, + pub pickup_timer: Timer, +} +impl Player { + /// Instantiates the game player at a given `Pos` + /// + /// # Examples + /// + /// ``` + /// use dungeon::{pos::Pos, player::Player}; + /// + /// let pos = Pos::new(1, 2).unwrap(); + /// let player = Player::new(pos); + /// ``` + pub const fn new(pos: Pos) -> Self { + let entity = Entity::player(pos); + let inventory = Inventory::new(); + let weapon = Weapon::RustyKnife; + let potion_timer = Timer::new(); + let pickup_timer = Timer::new(); + Self { + entity, + inventory, + weapon, + potion_timer, + pickup_timer, + } + } + + /// Reset potion effects if `potion_timer` is up + fn update_potion_effects(&mut self) { + if self.potion_timer.finished() { + self.potion_timer.reset(); + self.entity.speed = self.entity.kind.initial_speed(); + } + } +} +impl Default for Player { + fn default() -> Self { + let pos = const_pos!(1, 1); + Self::new(pos) + } +} + +struct Updater<'a> { + player: &'a mut Player, + floor: &'a mut Floor, + entities: &'a mut Vec<Entity>, + input: PlayerInput, + action: PlayerAction, +} +impl Updater<'_> { + /// Handle "Use Item" action + fn handle_use_item(&mut self) { + if !self.input.use_item { + return; + } + if let Some(item) = self.player.inventory.take() { + self.action.potion = item.is_potion(); + self.action.bomb = item.is_bomb(); + item.consume(self.player, self.floor); + } + } + + /// Handle "Drop Item" action + fn handle_drop_item(&mut self) { + if !self.input.drop { + return; + } + if let Some(item) = self.player.inventory.take() { + self.entities.push(Entity::new( + self.player.entity.pos, + Direction::East, + EntityKind::Item(item), + )); + self.action.drop_item = true; + self.player.pickup_timer.set_secs(5); + } + } + + /// Handle "Pickup Item" action + fn handle_pickup_item(&mut self) { + if !self.player.pickup_timer.finished() || self.player.pickup_timer.is_set() { + return; + } + self.player.pickup_timer.reset(); + + let mut idx = 0; + loop { + if idx >= self.entities.len() { + break; + } + if self.entities[idx] + .fpos + .abs_diff(self.player.entity.fpos) + .magnitude() >= 0.25 + { + idx += 1; + continue; + } + let Some(item) = self.entities[idx].get_item() else { + idx += 1; + continue; + }; + if self.player.inventory.pickup(item).is_none() { + self.entities.remove(idx); + self.action.pickup_item = true; + } else { + idx += 1; + } + } + } + + /// Handle "Player Attack" action + const fn handle_attack(&mut self) { + if self.input.attack { + // TODO: attack + self.action.attack = true; + } + } + + /// Handle "Inv Slot" action + const fn handle_inv_slot(&mut self) { + if let Some(slot) = self.input.inv_slot { + self.player.inventory.set_active(slot); + } + } +} + +impl Dungeon { + pub(crate) fn update_player(&mut self, input: PlayerInput) -> PlayerAction { + let mut updater = Updater { + player: &mut self.player, + floor: &mut self.floor, + entities: &mut self.entities, + input, + action: PlayerAction::default(), + }; + + updater.player.update_potion_effects(); + updater.handle_use_item(); + updater.handle_drop_item(); + updater.handle_pickup_item(); + updater.handle_attack(); + updater.handle_inv_slot(); + + updater.action + } +} |