Monster and item progression.
This commit is contained in:
parent
b8f68df801
commit
3211619b96
127
src/main.rs
127
src/main.rs
@ -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);
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user