improve combat system
This commit is contained in:
		
							parent
							
								
									efc4cdd363
								
							
						
					
					
						commit
						cefb0cc5f2
					
				
							
								
								
									
										28
									
								
								src/game.rs
									
									
									
									
									
								
							
							
						
						
									
										28
									
								
								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());
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										28
									
								
								src/level.rs
									
									
									
									
									
								
							
							
						
						
									
										28
									
								
								src/level.rs
									
									
									
									
									
								
							@ -48,11 +48,7 @@ impl Level {
 | 
			
		||||
        &mut self,
 | 
			
		||||
        x: i16,
 | 
			
		||||
        y: i16,
 | 
			
		||||
    ) -> (
 | 
			
		||||
        Option<StructureElement>,
 | 
			
		||||
        PossibleMonster,
 | 
			
		||||
        PossibleArtifact
 | 
			
		||||
    ) {
 | 
			
		||||
    ) -> (Option<StructureElement>, 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() {
 | 
			
		||||
 | 
			
		||||
@ -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,
 | 
			
		||||
 | 
			
		||||
@ -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 }),
 | 
			
		||||
 | 
			
		||||
@ -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<usize>,
 | 
			
		||||
        }
 | 
			
		||||
        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() {
 | 
			
		||||
 | 
			
		||||
@ -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 {
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user