From 825d0657b4a312bfa33567846a9abfb782f767e4 Mon Sep 17 00:00:00 2001 From: Freya Murphy Date: Thu, 20 Nov 2025 10:39:36 -0500 Subject: dungeon: pickup items --- dungeon/src/entity.rs | 155 ++++++++++++++++++++++++++++++++++++++++++++++---- dungeon/src/lib.rs | 62 +------------------- 2 files changed, 146 insertions(+), 71 deletions(-) diff --git a/dungeon/src/entity.rs b/dungeon/src/entity.rs index da807c2..d6ca7e5 100644 --- a/dungeon/src/entity.rs +++ b/dungeon/src/entity.rs @@ -8,7 +8,7 @@ use std::{ use rand::Rng; use crate::{ - Dungeon, astar, const_pos, + Dungeon, PlayerAction, astar, const_pos, map::Floor, player_input::PlayerInput, pos::{Direction, FPos, Pos}, @@ -46,24 +46,23 @@ pub enum Item { LargeBomb, } impl Item { - pub fn consume(self, dungeon: &mut Dungeon) { + pub fn consume(self, player: &mut Player, floor: &mut Floor) { match self { Self::HeartFragment => { - dungeon.player.entity.heal(1); + player.entity.heal(1); } Self::HealthPotion => { - dungeon.player.entity.heal(3); + player.entity.heal(3); } Self::SpeedPotion => { - dungeon.player.potion_timer = - Instant::now().checked_add(Duration::from_secs(5)); - dungeon.player.entity.speed = dungeon.player.entity.speed.inc(); + player.potion_timer = Instant::now().checked_add(Duration::from_secs(5)); + player.entity.speed = player.entity.speed.inc(); } Self::Bomb => { - dungeon.floor.explode(dungeon.player.entity.pos, 1); + floor.explode(player.entity.pos, 1); } Self::LargeBomb => { - dungeon.floor.explode(dungeon.player.entity.pos, 2); + floor.explode(player.entity.pos, 2); } } } @@ -330,6 +329,14 @@ impl Entity { pub const fn is_alive(&self) -> bool { !self.is_dead() } + + /// Returns the item in the entity (if this entity is an item) + pub const fn get_item(&self) -> Option { + match &self.kind { + EntityKind::Item(item) => Some(*item), + _ => None, + } + } } /// The current "weapon" level we have @@ -360,6 +367,7 @@ pub struct Player { // How long until we reset potion effects? pub potion_timer: Option, pub active_inv_slot: usize, + pub last_drop_time: Instant, } impl Player { /// Instantiates the game player at a given `Pos` @@ -372,18 +380,20 @@ impl Player { /// let pos = Pos::new(1, 2).unwrap(); /// let player = Player::new(pos); /// ``` - pub const fn new(pos: Pos) -> Self { + 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, } } } @@ -395,7 +405,7 @@ impl Default for Player { } struct Updater<'a> { - floor: &'a Floor, + floor: &'a mut Floor, rng: &'a mut DungeonRng, player_pos: Pos, input: PlayerInput, @@ -530,11 +540,104 @@ 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, + ) { + 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, + ) { + 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: &self.floor, + floor: &mut self.floor, rng: &mut self.game_rng, player_pos: self.player.entity.pos, input, @@ -551,4 +654,32 @@ 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 0fd3e0d..fa75c10 100644 --- a/dungeon/src/lib.rs +++ b/dungeon/src/lib.rs @@ -10,16 +10,14 @@ pub mod player_input; pub mod pos; pub mod rng; -use std::time::Instant; - use rand::{Rng, SeedableRng, TryRngCore, rngs::OsRng}; use crate::{ - entity::{Entity, EntityKind, PLAYER_INVENTORY_SIZE_USIZE, Player}, + entity::{Entity, Player}, map::{Floor, Tile}, msg::Message, player_input::PlayerInput, - pos::{Direction, FPos}, + pos::FPos, rng::DungeonRng, }; @@ -147,8 +145,7 @@ impl Dungeon { let changed = self.msg.update(player_input); UpdateResult::MessageUpdated(changed) } else { - let mut action = self.update_player(player_input); - + let mut action = self.update_player(player_input, delta_time); let curr_player_pos = self.player.entity.fpos; self.update_entities(player_input, delta_time); if self @@ -181,59 +178,6 @@ impl Dungeon { } } - fn update_player(&mut self, player_input: PlayerInput) -> PlayerAction { - let mut action = PlayerAction::default(); - - // update potion timer - if let Some(timer) = self.player.potion_timer - && Instant::now() > timer - { - self.player.potion_timer = None; - self.player.entity.speed = self.player.entity.kind.initial_speed(); - } - - // use item - if player_input.use_item - && self.player.active_inv_slot < self.player.inventory.len() - { - let item = self.player.inventory.remove(self.player.active_inv_slot); - action.potion = item.is_potion(); - action.bomb = item.is_bomb(); - item.consume(self); - } - - if player_input.attack { - // TODO: attack - action.attack = true; - } - - // check for drop input - if player_input.drop && self.player.active_inv_slot < self.player.inventory.len() { - let item = self.player.inventory.remove(self.player.active_inv_slot); - self.entities.push(Entity::new( - self.player.entity.pos, - Direction::East, - EntityKind::Item(item), - )); - self.player.active_inv_slot = self - .player - .active_inv_slot - .max(self.player.inventory.len().saturating_sub(1)); - action.drop_item = true; - } - - // TODO: pickup items - - // check for inv slot request - if let Some(slot) = player_input.inv_slot - && slot < PLAYER_INVENTORY_SIZE_USIZE - { - self.player.active_inv_slot = slot; - } - - action - } - fn spawn_enimies(&mut self) { // TODO: better entity spawning let zombie = Entity::zombie(self.floor.random_walkable_pos(&mut self.level_rng)); -- cgit v1.2.3-freya