From 5b4f28a7c866d135284c3dc3524cbfb3e34bbc26 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Wed, 27 Oct 2021 21:40:42 +0200 Subject: [PATCH] Status bar --- src/main.rs | 134 ++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 119 insertions(+), 15 deletions(-) diff --git a/src/main.rs b/src/main.rs index e8b794e..083834b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -93,6 +93,41 @@ impl Object { let dy = other.y - self.y; ((dx.pow(2) + dy.pow(2)) as f32).sqrt() } + + pub fn take_damage(&mut self, damage: i32) { + // Apply damage if possible + if let Some(fighter) = self.fighter.as_mut() { + if damage > 0 { + fighter.hp -= damage; + } + } + + // Check for death, call the death function + if let Some(fighter) = self.fighter { + if fighter.hp <= 0 { + self.alive = false; + fighter.on_death.callback(self); + } + } + } + + pub fn attack(&mut self, target: &mut Object) { + // A simple formula for attack damage + let damage = self.fighter.map_or(0, |f| f.power) - target.fighter.map_or(0, |f| f.defense); + if damage > 0 { + // Make the target take some damage + println!( + "{} attacks {} for {} hit points.", + self.name, target.name, damage + ); + target.take_damage(damage); + } else { + println!( + "{} attacks {} but it has no effect!", + self.name, target.name + ) + } + } } #[derive(Clone, Copy, Debug)] @@ -163,6 +198,7 @@ struct Fighter { hp: i32, defense: i32, power: i32, + on_death: DeathCallback, } #[derive(Clone, Copy, Debug, PartialEq)] @@ -177,6 +213,23 @@ enum Ai { Basic, } +#[derive(Clone, Copy, Debug, PartialEq)] +enum DeathCallback { + Player, + Monster, +} + +impl DeathCallback { + fn callback(self, object: &mut Object) { + use DeathCallback::*; + let callback: fn(&mut Object) = match self { + Player => player_death, + Monster => monster_death, + }; + callback(object); + } +} + fn handle_keys(tcod: &mut Tcod, game: &Game, objects: &mut Vec) -> PlayerAction { use tcod::input::Key; use tcod::input::KeyCode::*; @@ -305,6 +358,7 @@ fn place_objects(room: Rect, map: &Map, objects: &mut Vec) { hp: 10, defense: 0, power: 3, + on_death: DeathCallback::Monster, }); orc.ai = Some(Ai::Basic); orc @@ -316,6 +370,7 @@ fn place_objects(room: Rect, map: &Map, objects: &mut Vec) { hp: 16, defense: 1, power: 4, + on_death: DeathCallback::Monster, }); troll.ai = Some(Ai::Basic); troll @@ -351,15 +406,15 @@ fn player_move_or_attack(dx: i32, dy: i32, game: &Game, objects: &mut [Object]) let y = objects[PLAYER].y + dy; // Try to find an attackable object there - let target_id = objects.iter().position(|object| object.pos() == (x, y)); + let target_id = objects + .iter() + .position(|object| object.fighter.is_some() && object.pos() == (x, y)); // Attack target if found, move otherwise match target_id { Some(target_id) => { - println!( - "The {} laughs at your puny efforts to attack him!", - objects[target_id].name - ); + let (player, target) = mut_two(PLAYER, target_id, objects); + player.attack(target); } None => { move_by(PLAYER, dx, dy, &game.map, objects); @@ -390,15 +445,44 @@ fn ai_take_turn(monster_id: usize, tcod: &Tcod, game: &Game, objects: &mut [Obje move_towards(monster_id, player_x, player_y, &game.map, objects); } else if objects[PLAYER].fighter.map_or(false, |f| f.hp > 0) { // Close enough, attach! (if the player is still alive.) - let monster = &objects[monster_id]; - println!( - "The attack of the {} bounces off your shiny metal armor!", - monster.name - ); + let (monster, player) = mut_two(monster_id, PLAYER, objects); + monster.attack(player); } } } +fn mut_two(first_index: usize, second_index: usize, items: &mut [T]) -> (&mut T, &mut T) { + assert!(first_index != second_index); + let split_at_index = cmp::max(first_index, second_index); + let (first_slice, second_slice) = items.split_at_mut(split_at_index); + if first_index < second_index { + (&mut first_slice[first_index], &mut second_slice[0]) + } else { + (&mut second_slice[0], &mut first_slice[second_index]) + } +} + +fn player_death(player: &mut Object) { + // The game ended! + println!("You died!"); + + // For added effect, transform the player into corpse! + player.char = '%'; + player.color = DARK_RED; +} + +fn monster_death(monster: &mut Object) { + // Transform it into a nasty corpse! It doesn't block, can't be + // attacked and doesn't move + println!("{} is dead!", monster.name); + monster.char = '%'; + monster.color = DARK_RED; + monster.blocks = false; + monster.fighter = None; + monster.ai = None; + monster.name = format!("remains of {}", monster.name); +} + fn render_all(tcod: &mut Tcod, game: &mut Game, objects: &[Object], fov_recompute: bool) { if fov_recompute { let player = &objects[PLAYER]; @@ -407,11 +491,18 @@ fn render_all(tcod: &mut Tcod, game: &mut Game, objects: &[Object], fov_recomput .compute_fov(player.x, player.y, TORCH_RADIUS, FOV_LIGHT_WALLS, FOV_ALGO); } - // Draw all objects in the list - for object in objects { - if tcod.fov.is_in_fov(object.x, object.y) { - object.draw(&mut tcod.con); - } + // Only render objects in fov + let mut to_draw: Vec<_> = objects + .iter() + .filter(|o| tcod.fov.is_in_fov(o.x, o.y)) + .collect(); + + // Sort so that non-blocking objects are rendered behind blocking objects + to_draw.sort_by(|o1, o2| o1.blocks.cmp(&o2.blocks)); + + // Draw objects in the list + for object in &to_draw { + object.draw(&mut tcod.con); } // Go through all tiles and set their background color @@ -439,6 +530,18 @@ fn render_all(tcod: &mut Tcod, game: &mut Game, objects: &[Object], fov_recomput } } + // Show the player's stats + tcod.root.set_default_foreground(WHITE); + if let Some(fighter) = objects[PLAYER].fighter { + tcod.root.print_ex( + 1, + SCREEN_HEIGHT - 2, + BackgroundFlag::None, + TextAlignment::Left, + format!("HP: {}/{} ", fighter.hp, fighter.max_hp), + ); + } + // Blit contents of "con" to the root console blit( &tcod.con, @@ -475,6 +578,7 @@ fn main() { hp: 30, defense: 2, power: 5, + on_death: DeathCallback::Player, }); // List of objects in the world