summaryrefslogtreecommitdiff
path: root/dungeon/src/player.rs
diff options
context:
space:
mode:
Diffstat (limited to 'dungeon/src/player.rs')
-rw-r--r--dungeon/src/player.rs294
1 files changed, 294 insertions, 0 deletions
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
+ }
+}