summaryrefslogtreecommitdiff
path: root/dungeon
diff options
context:
space:
mode:
authorFreya Murphy <freya@freyacat.org>2025-11-19 22:20:56 -0500
committerFreya Murphy <freya@freyacat.org>2025-11-19 22:20:56 -0500
commitf0e715fd3fad342c785a5b4a8637cedfaccb8731 (patch)
treecb07fa591e78c4bac440a0a1e9f2ece461d045a0 /dungeon
parentnix: fix fenix flake and update lock (diff)
downloadDungeonCrawl-f0e715fd3fad342c785a5b4a8637cedfaccb8731.tar.gz
DungeonCrawl-f0e715fd3fad342c785a5b4a8637cedfaccb8731.tar.bz2
DungeonCrawl-f0e715fd3fad342c785a5b4a8637cedfaccb8731.zip
dungeon: implement items
Diffstat (limited to 'dungeon')
-rw-r--r--dungeon/src/entity.rs132
-rw-r--r--dungeon/src/lib.rs12
-rw-r--r--dungeon/src/map.rs26
3 files changed, 140 insertions, 30 deletions
diff --git a/dungeon/src/entity.rs b/dungeon/src/entity.rs
index 3a8f131..22d66e0 100644
--- a/dungeon/src/entity.rs
+++ b/dungeon/src/entity.rs
@@ -1,6 +1,9 @@
//! The `entity` module contains structures of all entities including players and enimies.
-use std::mem::take;
+use std::{
+ mem::take,
+ time::{Duration, Instant},
+};
use rand::Rng;
@@ -12,9 +15,6 @@ use crate::{
rng::DungeonRng,
};
-/// `PLAYER_FULL_HEALTH` is the starting health of the player entity
-pub const PLAYER_FULL_HEALTH: u32 = 10;
-
/// `PLAYER_INVENTORY_SIZE` is the maximum size of the inventory
pub const PLAYER_INVENTORY_SIZE: u16 = 5;
@@ -25,9 +25,6 @@ pub const PLAYER_INVENTORY_SIZE_USIZE: usize = PLAYER_INVENTORY_SIZE as usize;
pub const MIN_ROAM_DIST: u16 = 1;
pub const MAX_ROAM_DIST: u16 = 4;
-/// 'ZOMBIE_HEALTH' is the starting health of a zombie enemy
-pub const ZOMBIE_HEALTH: u32 = 5;
-
/// 'ENEMY_VISION_RADIUS' is the range used to determine if the player is seen by an enemy
pub const ENEMY_VISION_RADIUS: u16 = 10;
@@ -37,9 +34,67 @@ pub const IDLE_WAIT: f32 = 1.;
/// The `Item` type represents any item an entity may be using
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Item {
- Potion { heal: u32 },
- Weapon { atack: u32 },
- Armor { defense: u32 },
+ /// Heals health when used
+ HeartFragment,
+ /// Heals health when used
+ HealthPotion,
+ /// Increases speed temporarily
+ SpeedPotion,
+ /// Blows up a portion of the map
+ Bomb,
+ /// Blows up a larget portion of the map
+ LargeBomb,
+}
+impl Item {
+ pub fn consume(self, dungeon: &mut Dungeon) {
+ match self {
+ Self::HeartFragment => {
+ dungeon.player.entity.heal(1);
+ }
+ Self::HealthPotion => {
+ dungeon.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();
+ }
+ Self::Bomb => {
+ dungeon.floor.explode(dungeon.player.entity.pos, 1);
+ }
+ Self::LargeBomb => {
+ dungeon.floor.explode(dungeon.player.entity.pos, 2);
+ }
+ }
+ }
+}
+
+/// Different speed entities can move
+#[repr(u8)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+pub enum EntitySpeed {
+ Stopped = 0,
+ Slow = 2,
+ Normal = 3,
+ Fast = 4,
+}
+impl EntitySpeed {
+ /// Returns speed for this current time slice
+ #[must_use]
+ pub const fn per_frame(self, delta_time: f32) -> f32 {
+ (self as u8) as f32 * delta_time
+ }
+
+ /// Increment to the next speed
+ #[must_use]
+ pub const fn inc(self) -> Self {
+ match self {
+ Self::Stopped => Self::Stopped,
+ Self::Slow => Self::Normal,
+ Self::Normal => Self::Fast,
+ Self::Fast => Self::Fast,
+ }
+ }
}
/// The `EntityKind` represents what kind of entity this is.
@@ -54,11 +109,20 @@ pub enum EntityKind {
}
impl EntityKind {
/// Returns the move speed value for this type of entity in tiles/s
- pub const fn move_speed(&self) -> f32 {
+ pub const fn initial_speed(&self) -> EntitySpeed {
match &self {
- Self::Player => 3.5,
- Self::Zombie(_) => 2.0,
- _ => 0.0,
+ Self::Player => EntitySpeed::Normal,
+ Self::Zombie(_) => EntitySpeed::Slow,
+ _ => EntitySpeed::Stopped,
+ }
+ }
+
+ /// Returns the known max/initial health
+ pub const fn initial_health(&self) -> u32 {
+ match self {
+ Self::Player => 10,
+ Self::Zombie(_) => 5,
+ _ => 0,
}
}
}
@@ -150,7 +214,9 @@ pub struct Entity {
/// Which kind this entity is (along with entity kind specific data)
pub kind: EntityKind,
/// The amount of health this entity has (None if this Entity does not use health)
- pub health: Option<u32>,
+ pub health: u32,
+ /// The current movement speed
+ pub speed: EntitySpeed,
/// The fixed grid position we are moving to
pub moving_to: Option<Pos>,
}
@@ -165,23 +231,20 @@ impl Entity {
/// let pos = Pos::new(0, 0).unwrap();
/// let dir = Direction::North;
/// let kind = EntityKind::Player;
- /// let health = Some(10);
- /// let entity = Entity::new(pos, dir, kind, health);
+ /// let entity = Entity::new(pos, dir, kind);
/// ```
#[must_use]
- pub const fn new(
- pos: Pos,
- dir: Direction,
- kind: EntityKind,
- health: Option<u32>,
- ) -> Self {
+ pub const fn new(pos: Pos, dir: Direction, kind: EntityKind) -> Self {
let fpos = FPos::from_pos(pos);
+ let health = kind.initial_health();
+ let speed = kind.initial_speed();
Self {
pos,
fpos,
dir,
kind,
health,
+ speed,
moving_to: None,
}
}
@@ -200,8 +263,7 @@ impl Entity {
pub const fn player(pos: Pos) -> Self {
let dir = Direction::East;
let kind = EntityKind::Player;
- let health = Some(PLAYER_FULL_HEALTH);
- Self::new(pos, dir, kind, health)
+ Self::new(pos, dir, kind)
}
/// Creates the Zombie version of the `Entity`
@@ -218,8 +280,7 @@ impl Entity {
pub const fn zombie(pos: Pos) -> Self {
let dir = Direction::East;
let kind = EntityKind::Zombie(EnemyMoveState::idle());
- let health = Some(ZOMBIE_HEALTH);
- Self::new(pos, dir, kind, health)
+ Self::new(pos, dir, kind)
}
/// Returns the position in front of the entity
@@ -234,6 +295,12 @@ impl Entity {
self.moving_to = None;
}
+ /// Heals an entity a set amount of HP
+ pub fn heal(&mut self, amt: u32) {
+ let max = self.kind.initial_health();
+ self.health = (self.health + amt).min(max);
+ }
+
/// Returns a reference to this entities current AI
pub const fn get_ai(&self) -> Option<&EnemyMoveState> {
match &self.kind {
@@ -248,6 +315,8 @@ impl Entity {
pub struct Player {
pub entity: Entity,
pub inventory: Vec<Item>,
+ // How long until we reset potion effects?
+ pub potion_timer: Option<Instant>,
}
impl Player {
/// Instantiates the game player at a given `Pos`
@@ -263,7 +332,12 @@ impl Player {
pub const fn new(pos: Pos) -> Self {
let entity = Entity::player(pos);
let inventory = vec![];
- Self { entity, inventory }
+ let potion_timer = None;
+ Self {
+ entity,
+ inventory,
+ potion_timer,
+ }
}
}
impl Default for Player {
@@ -395,7 +469,7 @@ impl Updater<'_> {
let current = entity.fpos;
// we are far from the goal, so we need to move towards it
- let to_move = entity.kind.move_speed() * self.delta_time;
+ let to_move = entity.speed.per_frame(self.delta_time);
entity.fpos.move_towards_manhattan(fdest, to_move);
if fdest.abs_diff(entity.fpos).magnitude() <= 0.1 {
diff --git a/dungeon/src/lib.rs b/dungeon/src/lib.rs
index 4e48b68..13ab397 100644
--- a/dungeon/src/lib.rs
+++ b/dungeon/src/lib.rs
@@ -10,6 +10,8 @@ pub mod player_input;
pub mod pos;
pub mod rng;
+use std::time::Instant;
+
use rand::{Rng, SeedableRng, TryRngCore, rngs::OsRng};
use crate::{
@@ -119,6 +121,7 @@ impl Dungeon {
UpdateResult::MessageUpdated(changed)
} else {
self.update_entities(player_input, delta_time);
+ self.update_player();
if self.floor.get(self.player.entity.pos) == Tile::Stairs {
// we are moving to a new floor
@@ -132,6 +135,15 @@ impl Dungeon {
}
}
+ fn update_player(&mut self) {
+ 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();
+ }
+ }
+
fn spawn_enimies(&mut self) {
// TODO: better entity spawning
let zombie = Entity::zombie(self.floor.random_walkable_pos(&mut self.level_rng));
diff --git a/dungeon/src/map.rs b/dungeon/src/map.rs
index 0d05b73..7403e31 100644
--- a/dungeon/src/map.rs
+++ b/dungeon/src/map.rs
@@ -53,7 +53,11 @@ impl Tile {
matches!(self, Self::Room | Self::Hallway | Self::Stairs)
}
- // Index by u16
+ /// Returns if the tile is blast resistant
+ #[must_use]
+ pub const fn blast_resistant(self) -> bool {
+ matches!(self, Self::Stairs)
+ }
}
impl Display for Tile {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
@@ -164,6 +168,26 @@ impl Floor {
break pos;
}
}
+
+ /// Blows up a set number of tiles with a given radius
+ pub fn explode(&mut self, center_pos: Pos, radius: i16) {
+ let tiles_mut = self.tiles_mut();
+ for x_off in -radius..=radius {
+ for y_off in -radius..=radius {
+ let Some(x) = center_pos.x().checked_add_signed(x_off) else {
+ continue;
+ };
+ let Some(y) = center_pos.y().checked_add_signed(y_off) else {
+ continue;
+ };
+ let Some(pos) = Pos::new(x, y) else { continue };
+ if pos.is_border() || tiles_mut[pos.idx()].blast_resistant() {
+ continue;
+ }
+ tiles_mut[pos.idx()] = Tile::Room;
+ }
+ }
+ }
}
impl Display for Floor {
/// Display the floor as a string for debugging