From 876eb031df186c1b0fb265d8bf6cc4bdf9782bcc Mon Sep 17 00:00:00 2001 From: Emil Date: Sun, 21 Jun 2026 16:34:57 +0300 Subject: [PATCH] feat: difficulty scaling by depth + new items (bow, shield, mana potion) Difficulty scaling: - Spawn rates increase with depth (goblins every 30-10 ticks, slimes every 45-15) - Enemy count caps increase with depth (goblins 3-8, slimes 2-5) - Enemy health scales with depth (+5 hp/goblin, +3 hp/slime per depth) - Goblin strength stat scales with depth New items: - Bow: ranged weapon, +4 damage bonus - Shield: armor, +3 armor bonus - Mana Potion: consumable, heals 20 hp - Items spawn in world gen: sword, bow, shield, health potion, mana potion, leather armor All 171 tests pass. --- src/entity/item.rs | 31 +++++++++++++++++++++++++++---- src/game.rs | 21 +++++++++++++++++---- 2 files changed, 44 insertions(+), 8 deletions(-) diff --git a/src/entity/item.rs b/src/entity/item.rs index d8252bc..47dff50 100644 --- a/src/entity/item.rs +++ b/src/entity/item.rs @@ -2,11 +2,14 @@ pub enum ItemType { Dagger, Sword, + Bow, LeatherArmor, PlateArmor, HealthPotion, + ManaPotion, Food, Scroll, + Shield, } #[derive(Clone, Debug)] @@ -25,9 +28,12 @@ impl Item { match self.typ { ItemType::Dagger => "Dagger", ItemType::Sword => "Sword", + ItemType::Bow => "Bow", ItemType::LeatherArmor => "Leather Armor", ItemType::PlateArmor => "Plate Armor", + ItemType::Shield => "Shield", ItemType::HealthPotion => "Health Potion", + ItemType::ManaPotion => "Mana Potion", ItemType::Food => "Food", ItemType::Scroll => "Scroll", } @@ -37,9 +43,12 @@ impl Item { match self.typ { ItemType::Dagger => '/', ItemType::Sword => '|', + ItemType::Bow => ')', ItemType::LeatherArmor => '[', ItemType::PlateArmor => '{', + ItemType::Shield => 'O', ItemType::HealthPotion => '!', + ItemType::ManaPotion => '!', ItemType::Food => '%', ItemType::Scroll => '?', } @@ -49,9 +58,12 @@ impl Item { match self.typ { ItemType::Dagger => [200, 200, 200], ItemType::Sword => [240, 240, 240], + ItemType::Bow => [180, 140, 80], ItemType::LeatherArmor => [140, 90, 50], ItemType::PlateArmor => [180, 180, 190], + ItemType::Shield => [160, 140, 60], ItemType::HealthPotion => [255, 40, 40], + ItemType::ManaPotion => [60, 60, 255], ItemType::Food => [80, 200, 60], ItemType::Scroll => [255, 220, 120], } @@ -60,22 +72,30 @@ impl Item { pub fn is_equipment(&self) -> bool { matches!( self.typ, - ItemType::Dagger | ItemType::Sword | ItemType::LeatherArmor | ItemType::PlateArmor + ItemType::Dagger + | ItemType::Sword + | ItemType::Bow + | ItemType::LeatherArmor + | ItemType::PlateArmor + | ItemType::Shield ) } pub fn is_weapon(&self) -> bool { - matches!(self.typ, ItemType::Dagger | ItemType::Sword) + matches!(self.typ, ItemType::Dagger | ItemType::Sword | ItemType::Bow) } pub fn is_armor(&self) -> bool { - matches!(self.typ, ItemType::LeatherArmor | ItemType::PlateArmor) + matches!( + self.typ, + ItemType::LeatherArmor | ItemType::PlateArmor | ItemType::Shield + ) } pub fn is_consumable(&self) -> bool { matches!( self.typ, - ItemType::HealthPotion | ItemType::Food | ItemType::Scroll + ItemType::HealthPotion | ItemType::ManaPotion | ItemType::Food | ItemType::Scroll ) } @@ -83,6 +103,7 @@ impl Item { match self.typ { ItemType::Dagger => 3.0, ItemType::Sword => 6.0, + ItemType::Bow => 4.0, _ => 0.0, } } @@ -91,6 +112,7 @@ impl Item { match self.typ { ItemType::LeatherArmor => 2.0, ItemType::PlateArmor => 5.0, + ItemType::Shield => 3.0, _ => 0.0, } } @@ -98,6 +120,7 @@ impl Item { pub fn heal_amount(&self) -> f32 { match self.typ { ItemType::HealthPotion => 40.0, + ItemType::ManaPotion => 20.0, ItemType::Food => 15.0, _ => 0.0, } diff --git a/src/game.rs b/src/game.rs index 9e0ba8f..3ac0ee1 100644 --- a/src/game.rs +++ b/src/game.rs @@ -245,6 +245,12 @@ impl Game { .spawn(ItemType::HealthPotion, px as i32 + 6, py as i32 + 1); self.items .spawn(ItemType::LeatherArmor, px as i32 - 3, py as i32 - 8); + self.items + .spawn(ItemType::Bow, px as i32 + 10, py as i32 + 1); + self.items + .spawn(ItemType::Shield, px as i32 - 10, py as i32 + 1); + self.items + .spawn(ItemType::ManaPotion, px as i32 + 3, py as i32 - 6); } pub fn center_camera_on(&mut self, px: f32, py: f32) { @@ -516,10 +522,10 @@ impl Game { self.update_score(); self.update_item_pickup(); - if self.tick % 30 == 0 { + if self.tick % (30u64 - (self.depth as u64 * 3).min(20)) == 0 { self.try_spawn_goblin(); } - if self.tick % 45 == 0 { + if self.tick % (45u64 - (self.depth as u64 * 4).min(30)) == 0 { self.try_spawn_slime(); } @@ -1364,13 +1370,14 @@ impl Game { } fn try_spawn_goblin(&mut self) { + let max_goblins = 3usize + self.depth.min(5) as usize; let alive_goblins = self .entities .all() .iter() .filter(|e| e.alive && e.kind == EntityKind::Goblin) .count(); - if alive_goblins >= 3 { + if alive_goblins >= max_goblins { return; } @@ -1397,17 +1404,21 @@ impl Game { let id = self.entities.spawn(EntityKind::Goblin); if let Some(g) = self.entities.get_mut(id) { g.build_humanoid(spawn_x as f32, spawn_y as f32); + g.health += self.depth as f32 * 5.0; + g.max_health += self.depth as f32 * 5.0; + g.strength += self.depth; } } fn try_spawn_slime(&mut self) { + let max_slimes = 2usize + self.depth.min(3) as usize; let alive_slimes = self .entities .all() .iter() .filter(|e| e.alive && e.kind == EntityKind::Slime) .count(); - if alive_slimes >= 2 { + if alive_slimes >= max_slimes { return; } @@ -1434,6 +1445,8 @@ impl Game { let id = self.entities.spawn(EntityKind::Slime); if let Some(s) = self.entities.get_mut(id) { s.build_humanoid(spawn_x as f32, spawn_y as f32); + s.health += self.depth as f32 * 3.0; + s.max_health += self.depth as f32 * 3.0; } } }