From cefb0cc5f215c8fe0b9e03f8fd123a2fff2ff2b2 Mon Sep 17 00:00:00 2001 From: Joachim Lusiardi Date: Sun, 20 Oct 2024 22:12:28 +0200 Subject: [PATCH] improve combat system --- src/game.rs | 28 ++++++++++++++------ src/level.rs | 28 +++++++++++++------- src/level_generator.rs | 9 +++++-- src/main.rs | 4 +-- src/monster.rs | 60 +++++++++++++++++++++++++----------------- src/player.rs | 5 ++++ 6 files changed, 89 insertions(+), 45 deletions(-) diff --git a/src/game.rs b/src/game.rs index 60d5dac..b0f5681 100644 --- a/src/game.rs +++ b/src/game.rs @@ -49,8 +49,10 @@ impl Game { fn player_reached_goal(&mut self) -> bool { match self.next_element(0, 0) { None => {} - Some(a) => if a == StructureElement::End { - return true + Some(a) => { + if a == StructureElement::End { + return true; + } } }; false @@ -153,6 +155,7 @@ impl Game { .discover(&Position::new(player_level, new_x, new_y)); (-dx, -dy) } + pub fn player_fights_monster(&mut self) -> bool { let player_pos = &self.player.get_immutable_position(); let player_level = player_pos.get_level(); @@ -162,12 +165,21 @@ impl Game { None => {} Some(m) => { // TODO fight the monster - self.player.change_life(-1); - m.decrease_life(1); - self.messages - .insert(0, format!("{} hits you.", m.get_name()).to_string()); - self.messages - .insert(0, format!("you hit {}.", m.get_name()).to_string()); + + let monster_dmg = m.damage() as i16; + let player_dmg = self.player.damage(); + self.player.change_life(-monster_dmg); + m.decrease_life(player_dmg); + + // inform player + self.messages.insert( + 0, + format!("{} hits you for {} damage.", m.get_name(), monster_dmg).to_string(), + ); + self.messages.insert( + 0, + format!("you hit {} for {} damage.", m.get_name(), player_dmg).to_string(), + ); // monster died, player gains experience if m.is_dead() { self.player.gain_experience(m.get_experience_gain()); diff --git a/src/level.rs b/src/level.rs index 82f6b5c..235079f 100644 --- a/src/level.rs +++ b/src/level.rs @@ -48,11 +48,7 @@ impl Level { &mut self, x: i16, y: i16, - ) -> ( - Option, - PossibleMonster, - PossibleArtifact - ) { + ) -> (Option, PossibleMonster, PossibleArtifact) { if x < 0 || y < 0 { return (None, None, None); } @@ -151,15 +147,29 @@ impl Level { if player.get_immutable_position().get_x() == new_x && player.get_immutable_position().get_y() == new_y { - self.monsters[index].decrease_life(1); - player.change_life(-1); + // TODO handle fight between monster and player + let monster_dmg = self.monsters[index].damage() as i16; + let player_dmg = player.damage(); + self.monsters[index].decrease_life(player_dmg); + player.change_life(-monster_dmg); + messages.insert( 0, - format!("{} hits you.", self.monsters[index].get_name()).to_string(), + format!( + "{} hits you for {} damage.", + self.monsters[index].get_name(), + monster_dmg + ) + .to_string(), ); messages.insert( 0, - format!("you hit {}.", self.monsters[index].get_name()).to_string(), + format!( + "you hit {} for {} damage.", + self.monsters[index].get_name(), + player_dmg + ) + .to_string(), ); // if the attack did not kill the opponent, back down if !player.is_dead() { diff --git a/src/level_generator.rs b/src/level_generator.rs index b26b2cf..ca17f64 100644 --- a/src/level_generator.rs +++ b/src/level_generator.rs @@ -11,7 +11,7 @@ use rand::Rng; use crate::artifacts::{Artifact, Chest, Potion}; use crate::level::{Level, StructureElement}; -use crate::monster::{Monster, Orc, Rat}; +use crate::monster::{Monster, Orc, Rat, Snake}; use crate::position::Position; const ROOMS_VERTICAL: usize = 7; @@ -301,11 +301,16 @@ impl LevelGenerator { let t_y = top + room.offset_y + rng.gen_range(0..room.height); // TODO randomize enemies here match rng.gen_range(1..=100) { - 1..=50 => { + 1..=30 => { enemies.push(Box::new(Orc::new_with_position(Position::new( self.level, t_x, t_y, )))); } + 31..=60 => { + enemies.push(Box::new(Snake::new_with_position(Position::new( + self.level, t_x, t_y, + )))); + } _ => { enemies.push(Box::new(Rat::new_with_position(Position::new( self.level, t_x, t_y, diff --git a/src/main.rs b/src/main.rs index 737731b..bfe7e77 100644 --- a/src/main.rs +++ b/src/main.rs @@ -35,7 +35,7 @@ pub const FRAME_LENGTH: u64 = 100; // fn main() -> Result<()> { - let mut game = Game::new(Player::new(realname().as_str(), 10)); + let mut game = Game::new(Player::new(realname().as_str(), 30)); stdout().execute(EnterAlternateScreen)?; enable_raw_mode()?; @@ -108,7 +108,7 @@ fn main() -> Result<()> { game.get_player().get_max_life(), game.get_player().get_experience(), game.get_player().get_gold(), - game.get_player().get_immutable_position().get_level() + game.get_player().get_immutable_position().get_level()+1 )) .block(block) .wrap(Wrap { trim: true }), diff --git a/src/monster.rs b/src/monster.rs index 349490f..ecee504 100644 --- a/src/monster.rs +++ b/src/monster.rs @@ -1,3 +1,6 @@ +use std::ops::RangeInclusive; + +use rand::Rng; use ratatui::prelude::Color; use crate::position::{HasPosition, Position}; @@ -12,10 +15,21 @@ pub trait Monster: HasPosition { fn get_ticks_between_steps(&self) -> u128; #[cfg(test)] fn get_life(&self) -> usize; + fn damage(&self) -> usize; } -macro_rules! default_monster { - ($($t:ty),+ $(,)?) => ($( +macro_rules! create_monster { + ($($t:ident),+ $(,)?) => ($( + pub struct $t { + name: String, + life: usize, + position: Position, + symbol: String, + color: Color, + experience_gain: usize, + ticks_between_steps: u128, + damage_range: RangeInclusive, + } impl Monster for $t { fn get_name(&self) -> &str { &self.name } fn is_dead(&self) -> bool { self.life <= 0 } @@ -25,6 +39,7 @@ macro_rules! default_monster { self.life = self.life.saturating_sub(by); } fn get_ticks_between_steps(&self) -> u128 { self.ticks_between_steps } + fn damage(&self) -> usize { rand::thread_rng().gen_range(self.damage_range.clone()) } #[cfg(test)] fn get_life(&self) -> usize { self.life } @@ -40,16 +55,6 @@ macro_rules! default_monster { )+) } -pub struct Rat { - name: String, - life: usize, - position: Position, - symbol: String, - color: Color, - experience_gain: usize, - ticks_between_steps: u128, -} - impl Rat { pub fn new_with_position(position: Position) -> Self { Self { @@ -60,20 +65,11 @@ impl Rat { color: Color::Black, experience_gain: 5, ticks_between_steps: 5, + damage_range: 1..=2, } } } -default_monster!(Rat); - -pub struct Orc { - name: String, - life: usize, - position: Position, - symbol: String, - color: Color, - experience_gain: usize, - ticks_between_steps: u128, -} +create_monster!(Rat); impl Orc { pub fn new_with_position(position: Position) -> Self { @@ -85,11 +81,27 @@ impl Orc { color: Color::DarkGray, experience_gain: 10, ticks_between_steps: 10, + damage_range: 2..=3, } } } +create_monster!(Orc); -default_monster!(Orc); +impl Snake { + pub fn new_with_position(position: Position) -> Self { + Self { + name: "snake".to_string(), + life: 3, + position, + symbol: String::from("S"), + color: Color::DarkGray, + experience_gain: 10, + ticks_between_steps: 20, + damage_range: 1..=4, + } + } +} +create_monster!(Snake); #[test] fn monsters_can_move() { diff --git a/src/player.rs b/src/player.rs index b6debe0..b37e979 100644 --- a/src/player.rs +++ b/src/player.rs @@ -1,3 +1,4 @@ +use rand::Rng; use std::cmp::{max, min}; use crate::position::{HasPosition, Position}; @@ -60,6 +61,10 @@ impl Player { pub fn get_experience(&self) -> usize { self.experience } + + pub fn damage(&self) -> usize { + rand::thread_rng().gen_range(1..4) + } } impl HasPosition for Player {