Targeting: the Fireball
This commit is contained in:
parent
3a31fab361
commit
2640bea49e
238
src/main.rs
238
src/main.rs
@ -48,6 +48,11 @@ const MSG_WIDTH: i32 = SCREEN_WIDTH - BAR_WIDTH - 2;
|
|||||||
const MSG_HEIGHT: usize = PANEL_HEIGHT as usize - 1;
|
const MSG_HEIGHT: usize = PANEL_HEIGHT as usize - 1;
|
||||||
const MAX_ROOM_ITEMS: i32 = 2;
|
const MAX_ROOM_ITEMS: i32 = 2;
|
||||||
const INVENTORY_WIDTH: i32 = 50;
|
const INVENTORY_WIDTH: i32 = 50;
|
||||||
|
const HEAL_AMOUNT: i32 = 4;
|
||||||
|
const LIGHTNING_DAMAGE: i32 = 40;
|
||||||
|
const LIGHTNING_RANGE: i32 = 5;
|
||||||
|
const CONFUSE_RANGE: i32 = 8;
|
||||||
|
const CONFUSE_NUM_TURNS: i32 = 10;
|
||||||
|
|
||||||
struct Tcod {
|
struct Tcod {
|
||||||
root: Root,
|
root: Root,
|
||||||
@ -150,6 +155,15 @@ impl Object {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn heal(&mut self, amount: i32) {
|
||||||
|
if let Some(ref mut fighter) = self.fighter {
|
||||||
|
fighter.hp += amount;
|
||||||
|
if fighter.hp > fighter.max_hp {
|
||||||
|
fighter.hp = fighter.max_hp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
@ -255,6 +269,10 @@ enum PlayerAction {
|
|||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
enum Ai {
|
enum Ai {
|
||||||
Basic,
|
Basic,
|
||||||
|
Confused {
|
||||||
|
previous_ai: Box<Ai>,
|
||||||
|
num_turns: i32,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
@ -277,6 +295,13 @@ impl DeathCallback {
|
|||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
enum Item {
|
enum Item {
|
||||||
Heal,
|
Heal,
|
||||||
|
Lightning,
|
||||||
|
Confuse,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum UseResult {
|
||||||
|
UsedUp,
|
||||||
|
Cancelled,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_keys(tcod: &mut Tcod, game: &mut Game, objects: &mut Vec<Object>) -> PlayerAction {
|
fn handle_keys(tcod: &mut Tcod, game: &mut Game, objects: &mut Vec<Object>) -> PlayerAction {
|
||||||
@ -332,12 +357,15 @@ fn handle_keys(tcod: &mut Tcod, game: &mut Game, objects: &mut Vec<Object>) -> P
|
|||||||
|
|
||||||
(Key { code: Text, .. }, "i", true) => {
|
(Key { code: Text, .. }, "i", true) => {
|
||||||
// Show the inventory
|
// Show the inventory
|
||||||
inventory_menu(
|
let inventory_index = inventory_menu(
|
||||||
&game.inventory,
|
&game.inventory,
|
||||||
"Press the key next to an item to use it, or any other to cancel.\n",
|
"Press the key next to an item to use it, or any other to cancel.\n",
|
||||||
&mut tcod.root,
|
&mut tcod.root,
|
||||||
);
|
);
|
||||||
TookTurn
|
if let Some(inventory_index) = inventory_index {
|
||||||
|
use_item(inventory_index, tcod, game, objects);
|
||||||
|
}
|
||||||
|
DidntTakeTurn
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => DidntTakeTurn,
|
_ => DidntTakeTurn,
|
||||||
@ -460,10 +488,25 @@ fn place_objects(room: Rect, map: &Map, objects: &mut Vec<Object>) {
|
|||||||
|
|
||||||
// Only place it if the tile is note blocked
|
// Only place it if the tile is note blocked
|
||||||
if !is_blocked(x, y, map, objects) {
|
if !is_blocked(x, y, map, objects) {
|
||||||
// Create a healing potion
|
let dice = rand::random::<f32>();
|
||||||
let mut object = Object::new(x, y, '!', "healing potion", VIOLET, false);
|
let item = if dice < 0.7 {
|
||||||
object.item = Some(Item::Heal);
|
// Create a healing potion (70% chance)
|
||||||
objects.push(object);
|
let mut object = Object::new(x, y, '!', "healing potion", VIOLET, false);
|
||||||
|
object.item = Some(Item::Heal);
|
||||||
|
object
|
||||||
|
} else if dice < 0.7 + 0.1 {
|
||||||
|
// Create a lightning bolt scroll (10% chance)
|
||||||
|
let mut object = Object::new(x, y, '#', "scroll of lightning bolt", LIGHT_YELLOW, false);
|
||||||
|
object.item = Some(Item::Lightning);
|
||||||
|
object
|
||||||
|
} else {
|
||||||
|
// Create a confuse scroll (10% chance)
|
||||||
|
let mut object = Object::new(x, y, '#', "scroll of confusion", LIGHT_YELLOW, false);
|
||||||
|
object.item = Some(Item::Confuse);
|
||||||
|
object
|
||||||
|
};
|
||||||
|
|
||||||
|
objects.push(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -522,6 +565,21 @@ fn move_towards(id: usize, target_x: i32, target_y: i32, map: &Map, objects: &mu
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn ai_take_turn(monster_id: usize, tcod: &Tcod, game: &mut Game, objects: &mut [Object]) {
|
fn ai_take_turn(monster_id: usize, tcod: &Tcod, game: &mut Game, objects: &mut [Object]) {
|
||||||
|
use Ai::*;
|
||||||
|
|
||||||
|
if let Some(ai) = objects[monster_id].ai.take() {
|
||||||
|
let new_ai = match ai {
|
||||||
|
Basic => ai_basic(monster_id, tcod, game, objects),
|
||||||
|
Confused {
|
||||||
|
previous_ai,
|
||||||
|
num_turns,
|
||||||
|
} => ai_confused(monster_id, tcod, game, objects, previous_ai, num_turns),
|
||||||
|
};
|
||||||
|
objects[monster_id].ai = Some(new_ai);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ai_basic(monster_id: usize, tcod: &Tcod, game: &mut Game, objects: &mut [Object]) -> Ai {
|
||||||
// A basic monster takes its turn. If you can see it, it can see you.
|
// A basic monster takes its turn. If you can see it, it can see you.
|
||||||
let (monster_x, monster_y) = objects[monster_id].pos();
|
let (monster_x, monster_y) = objects[monster_id].pos();
|
||||||
if tcod.fov.is_in_fov(monster_x, monster_y) {
|
if tcod.fov.is_in_fov(monster_x, monster_y) {
|
||||||
@ -535,6 +593,40 @@ fn ai_take_turn(monster_id: usize, tcod: &Tcod, game: &mut Game, objects: &mut [
|
|||||||
monster.attack(player, game);
|
monster.attack(player, game);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Ai::Basic
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ai_confused(
|
||||||
|
monster_id: usize,
|
||||||
|
_tcod: &Tcod,
|
||||||
|
game: &mut Game,
|
||||||
|
objects: &mut [Object],
|
||||||
|
previous_ai: Box<Ai>,
|
||||||
|
num_turns: i32,
|
||||||
|
) -> Ai {
|
||||||
|
if num_turns >= 0 {
|
||||||
|
// Still confused ...
|
||||||
|
// Move in a random direction, and decrease the number of turns confused
|
||||||
|
move_by(
|
||||||
|
monster_id,
|
||||||
|
rand::thread_rng().gen_range(-1, 2),
|
||||||
|
rand::thread_rng().gen_range(-1, 2),
|
||||||
|
&game.map,
|
||||||
|
objects,
|
||||||
|
);
|
||||||
|
|
||||||
|
Ai::Confused {
|
||||||
|
previous_ai: previous_ai,
|
||||||
|
num_turns: num_turns - 1,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Restore previous AI (this one will be deleted)
|
||||||
|
game.messages.add(
|
||||||
|
format!("The {} is no longer confused!", objects[monster_id].name),
|
||||||
|
RED,
|
||||||
|
);
|
||||||
|
*previous_ai
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mut_two<T>(first_index: usize, second_index: usize, items: &mut [T]) -> (&mut T, &mut T) {
|
fn mut_two<T>(first_index: usize, second_index: usize, items: &mut [T]) -> (&mut T, &mut T) {
|
||||||
@ -716,6 +808,140 @@ fn pick_item_up(object_id: usize, game: &mut Game, objects: &mut Vec<Object>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn use_item(inventory_id: usize, tcod: &mut Tcod, game: &mut Game, objects: &mut [Object]) {
|
||||||
|
use Item::*;
|
||||||
|
// just call the "use_function" if it is defined
|
||||||
|
if let Some(item) = game.inventory[inventory_id].item {
|
||||||
|
let on_use = match item {
|
||||||
|
Heal => cast_heal,
|
||||||
|
Lightning => cast_lightning,
|
||||||
|
Confuse => cast_confuse,
|
||||||
|
};
|
||||||
|
match on_use(inventory_id, tcod, game, objects) {
|
||||||
|
UseResult::UsedUp => {
|
||||||
|
// Destroy after use, unless it was cancelled for some reason
|
||||||
|
game.inventory.remove(inventory_id);
|
||||||
|
}
|
||||||
|
UseResult::Cancelled => {
|
||||||
|
game.messages.add("Cancelled", WHITE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
game.messages.add(
|
||||||
|
format!("The {} cannot be used.", game.inventory[inventory_id].name),
|
||||||
|
WHITE,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cast_heal(
|
||||||
|
_inventory_id: usize,
|
||||||
|
_tcod: &mut Tcod,
|
||||||
|
game: &mut Game,
|
||||||
|
objects: &mut [Object],
|
||||||
|
) -> UseResult {
|
||||||
|
// Heal the player
|
||||||
|
if let Some(fighter) = objects[PLAYER].fighter {
|
||||||
|
if fighter.hp == fighter.max_hp {
|
||||||
|
game.messages.add("You are already at full health.", RED);
|
||||||
|
return UseResult::Cancelled;
|
||||||
|
}
|
||||||
|
|
||||||
|
game
|
||||||
|
.messages
|
||||||
|
.add("Your wounds start to feel better!", LIGHT_VIOLET);
|
||||||
|
objects[PLAYER].heal(HEAL_AMOUNT);
|
||||||
|
return UseResult::UsedUp;
|
||||||
|
}
|
||||||
|
UseResult::Cancelled
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cast_lightning(
|
||||||
|
_inventory_id: usize,
|
||||||
|
tcod: &mut Tcod,
|
||||||
|
game: &mut Game,
|
||||||
|
objects: &mut [Object],
|
||||||
|
) -> UseResult {
|
||||||
|
// Find closest enemy (inside a maximum range) and damage it
|
||||||
|
let monster_id = closest_monster(tcod, objects, LIGHTNING_RANGE);
|
||||||
|
if let Some(monster_id) = monster_id {
|
||||||
|
// Zap it !
|
||||||
|
game.messages.add(
|
||||||
|
format!(
|
||||||
|
"A lightning bolt strikes the {} with a loud thunder! \
|
||||||
|
The damage is {} hit points.",
|
||||||
|
objects[monster_id].name, LIGHTNING_DAMAGE
|
||||||
|
),
|
||||||
|
LIGHT_BLUE,
|
||||||
|
);
|
||||||
|
objects[monster_id].take_damage(LIGHTNING_DAMAGE, game);
|
||||||
|
UseResult::UsedUp
|
||||||
|
} else {
|
||||||
|
// No enemy found within maximum range
|
||||||
|
game
|
||||||
|
.messages
|
||||||
|
.add("No enemy is close enough to strike.", RED);
|
||||||
|
|
||||||
|
UseResult::Cancelled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cast_confuse(
|
||||||
|
_inventory_id: usize,
|
||||||
|
tcod: &mut Tcod,
|
||||||
|
game: &mut Game,
|
||||||
|
objects: &mut [Object],
|
||||||
|
) -> UseResult {
|
||||||
|
let monster_id = target_monster(CONFUSE_RANGE, objects, tcod);
|
||||||
|
if let Some(monster_id) = monster_id {
|
||||||
|
let old_ai = objects[monster_id].ai.take().unwrap_or(Ai::Basic);
|
||||||
|
// Replace the monster's AI with a "confused" one; after
|
||||||
|
// some turns it will restore the old AI
|
||||||
|
objects[monster_id].ai = Some(Ai::Confused {
|
||||||
|
previous_ai: Box::new(old_ai),
|
||||||
|
num_turns: CONFUSE_NUM_TURNS,
|
||||||
|
});
|
||||||
|
game.messages.add(
|
||||||
|
format!(
|
||||||
|
"The eyes of {} look vacant, as he starts to stumble around!",
|
||||||
|
objects[monster_id].name
|
||||||
|
),
|
||||||
|
LIGHT_GREEN,
|
||||||
|
);
|
||||||
|
UseResult::UsedUp
|
||||||
|
} else {
|
||||||
|
// No enemy found within maximum range
|
||||||
|
game
|
||||||
|
.messages
|
||||||
|
.add("No enemy is close enough to strike.", RED);
|
||||||
|
UseResult::Cancelled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find closest enemy, up to a maximum range, and in the player's FOV
|
||||||
|
fn closest_monster(tcod: &Tcod, objects: &[Object], max_range: i32) -> Option<usize> {
|
||||||
|
let mut closest_enemy = None;
|
||||||
|
// Start with (slightly more) than maximum range
|
||||||
|
let mut closest_dist = (max_range + 1) as f32;
|
||||||
|
|
||||||
|
for (id, object) in objects.iter().enumerate() {
|
||||||
|
if (id != PLAYER)
|
||||||
|
&& object.fighter.is_some()
|
||||||
|
&& object.ai.is_some()
|
||||||
|
&& tcod.fov.is_in_fov(object.x, object.y)
|
||||||
|
{
|
||||||
|
// Calculate the distance between this object and the player
|
||||||
|
let dist = objects[PLAYER].distance_to(object);
|
||||||
|
if dist < closest_dist {
|
||||||
|
// It's closer, so remember it
|
||||||
|
closest_enemy = Some(id);
|
||||||
|
closest_dist = dist;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
closest_enemy
|
||||||
|
}
|
||||||
|
|
||||||
fn render_all(tcod: &mut Tcod, game: &mut Game, objects: &[Object], fov_recompute: bool) {
|
fn render_all(tcod: &mut Tcod, game: &mut Game, objects: &[Object], fov_recompute: bool) {
|
||||||
if fov_recompute {
|
if fov_recompute {
|
||||||
let player = &objects[PLAYER];
|
let player = &objects[PLAYER];
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user