Monster and item progression.

This commit is contained in:
Michael Smith 2021-11-17 22:59:16 +01:00
parent b8f68df801
commit 3211619b96

View File

@ -59,6 +59,11 @@ const CONFUSE_RANGE: i32 = 8;
const CONFUSE_NUM_TURNS: i32 = 10;
const FIREBALL_RADIUS: i32 = 3;
const FIREBALL_DAMAGE: i32 = 12;
// Experience and level-ups
const LEVEL_UP_BASE: i32 = 200;
const LEVEL_UP_FACTOR: i32 = 150;
const LEVEL_SCREEN_WIDTH: i32 = 40;
const CHARACTER_SCREEN_WIDTH: i32 = 30;
struct Tcod {
root: Root,
@ -82,6 +87,7 @@ struct Object {
ai: Option<Ai>,
item: Option<Item>,
always_visible: bool,
level: i32,
}
impl Object {
@ -98,6 +104,7 @@ impl Object {
ai: None,
item: None,
always_visible: false,
level: 1,
}
}
@ -122,7 +129,7 @@ impl Object {
((dx.pow(2) + dy.pow(2)) as f32).sqrt()
}
pub fn take_damage(&mut self, damage: i32, game: &mut Game) {
pub fn take_damage(&mut self, damage: i32, game: &mut Game) -> Option<i32> {
// Apply damage if possible
if let Some(fighter) = self.fighter.as_mut() {
if damage > 0 {
@ -135,8 +142,10 @@ impl Object {
if fighter.hp <= 0 {
self.alive = false;
fighter.on_death.callback(self, game);
return Some(fighter.xp);
}
}
None
}
pub fn attack(&mut self, target: &mut Object, game: &mut Game) {
@ -152,7 +161,10 @@ impl Object {
WHITE,
);
target.take_damage(damage, game);
if let Some(xp) = target.take_damage(damage, game) {
// Yield experience to the player
self.fighter.as_mut().unwrap().xp += xp;
}
} else {
game.messages.add(
format!(
@ -251,6 +263,7 @@ struct Fighter {
hp: i32,
defense: i32,
power: i32,
xp: i32,
on_death: DeathCallback,
}
@ -343,6 +356,11 @@ fn handle_keys(tcod: &mut Tcod, game: &mut Game, objects: &mut Vec<Object>) -> P
}
(Key { code: Escape, .. }, _, _) => Exit, // exit game
(Key { code: Spacebar, .. }, _, true) => {
// Do nothing, i.e. wait for the monster to come to you
TookTurn
}
(Key { code: Up, .. }, _, true) => {
player_move_or_attack(0, -1, game, objects);
TookTurn
@ -408,6 +426,29 @@ fn handle_keys(tcod: &mut Tcod, game: &mut Game, objects: &mut Vec<Object>) -> P
DidntTakeTurn
}
(Key { code: Text, .. }, "c", true) => {
// Show player stats
let player = &objects[PLAYER];
let level = player.level;
let level_up_xp = LEVEL_UP_BASE + player.level * LEVEL_UP_FACTOR;
if let Some(fighter) = player.fighter.as_ref() {
let msg = format!(
"Character information
Level: {}
Experience: {}
Experience to level up: {}
Maximum HP: {},
Attack: {},
Defense: {}",
level, fighter.xp, level_up_xp, fighter.max_hp, fighter.power, fighter.defense
);
msgbox(&msg, CHARACTER_SCREEN_WIDTH, &mut tcod.root);
}
DidntTakeTurn
}
_ => DidntTakeTurn,
}
}
@ -506,6 +547,7 @@ fn place_objects(room: Rect, map: &Map, objects: &mut Vec<Object>) {
hp: 10,
defense: 0,
power: 3,
xp: 35,
on_death: DeathCallback::Monster,
});
orc.ai = Some(Ai::Basic);
@ -518,6 +560,7 @@ fn place_objects(room: Rect, map: &Map, objects: &mut Vec<Object>) {
hp: 16,
defense: 1,
power: 4,
xp: 100,
on_death: DeathCallback::Monster,
});
troll.ai = Some(Ai::Basic);
@ -709,9 +752,14 @@ fn player_death(player: &mut Object, game: &mut Game) {
fn monster_death(monster: &mut Object, game: &mut Game) {
// Transform it into a nasty corpse! It doesn't block, can't be
// attacked and doesn't move
game
.messages
.add(format!("{} is dead!", monster.name), ORANGE);
game.messages.add(
format!(
"{} is dead! You gain {} experience points.",
monster.name,
monster.fighter.unwrap().xp
),
ORANGE,
);
monster.char = '%';
monster.color = DARK_RED;
monster.blocks = false;
@ -936,7 +984,9 @@ fn cast_lightning(
),
LIGHT_BLUE,
);
objects[monster_id].take_damage(LIGHTNING_DAMAGE, game);
if let Some(xp) = objects[monster_id].take_damage(LIGHTNING_DAMAGE, game) {
objects[PLAYER].fighter.as_mut().unwrap().xp += xp;
}
UseResult::UsedUp
} else {
// No enemy found within maximum range
@ -1007,7 +1057,8 @@ fn cast_fireball(
ORANGE,
);
for obj in objects {
let mut xp_to_gain = 0;
for (id, obj) in objects.iter_mut().enumerate() {
if obj.distance(x, y) <= FIREBALL_RADIUS as f32 && obj.fighter.is_some() {
game.messages.add(
format!(
@ -1016,9 +1067,15 @@ fn cast_fireball(
),
ORANGE,
);
obj.take_damage(FIREBALL_DAMAGE, game);
if let Some(xp) = obj.take_damage(FIREBALL_DAMAGE, game) {
if id != PLAYER {
// Don't reward the player for burning themselves!
xp_to_gain += xp;
}
}
}
}
objects[PLAYER].fighter.as_mut().unwrap().xp += xp_to_gain;
UseResult::UsedUp
}
@ -1262,6 +1319,7 @@ fn new_game(tcod: &mut Tcod) -> (Game, Vec<Object>) {
hp: 30,
defense: 2,
power: 5,
xp: 0,
on_death: DeathCallback::Player,
});
@ -1308,6 +1366,9 @@ fn play_game(tcod: &mut Tcod, game: &mut Game, objects: &mut Vec<Object>) {
tcod.root.flush();
// Level up if needed
level_up(tcod, game, objects);
// Handle keys and exit game if needed
previous_player_position = objects[PLAYER].pos();
let player_action = handle_keys(tcod, game, objects);
@ -1424,6 +1485,56 @@ fn next_level(tcod: &mut Tcod, game: &mut Game, objects: &mut Vec<Object>) {
initialise_fov(tcod, &game.map);
}
fn level_up(tcod: &mut Tcod, game: &mut Game, objects: &mut [Object]) {
let player = &mut objects[PLAYER];
let level_up_xp = LEVEL_UP_BASE + player.level * LEVEL_UP_FACTOR;
// Check if the player's experience is enough to level up
if player.fighter.as_ref().map_or(0, |f| f.xp) >= level_up_xp {
// It is, level up!
player.level += 1;
game.messages.add(
format!(
"Your battle skills grow stronger! You reached level {}!",
player.level
),
YELLOW,
);
// Increase player's stats
let fighter = player.fighter.as_mut().unwrap();
let mut choice = None;
while choice.is_none() {
// Keep asking until a choice is made
choice = menu(
"Level up! Choose a stat to raise:\n",
&[
format!("Constitution (+ 20 HP, from {}", fighter.max_hp),
format!("Strength (+1 attack, from {}", fighter.power),
format!("Agility (+1 defense, from {}", fighter.defense),
],
LEVEL_SCREEN_WIDTH,
&mut tcod.root,
);
}
fighter.xp -= level_up_xp;
match choice.unwrap() {
0 => {
fighter.max_hp += 20;
fighter.hp += 20;
}
1 => {
fighter.power += 1;
}
2 => {
fighter.defense += 1;
}
_ => unreachable!(),
}
}
}
fn main() {
tcod::system::set_fps(FPS);