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 CONFUSE_NUM_TURNS: i32 = 10;
|
||||||
const FIREBALL_RADIUS: i32 = 3;
|
const FIREBALL_RADIUS: i32 = 3;
|
||||||
const FIREBALL_DAMAGE: i32 = 12;
|
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 {
|
struct Tcod {
|
||||||
root: Root,
|
root: Root,
|
||||||
@ -82,6 +87,7 @@ struct Object {
|
|||||||
ai: Option<Ai>,
|
ai: Option<Ai>,
|
||||||
item: Option<Item>,
|
item: Option<Item>,
|
||||||
always_visible: bool,
|
always_visible: bool,
|
||||||
|
level: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Object {
|
impl Object {
|
||||||
@ -98,6 +104,7 @@ impl Object {
|
|||||||
ai: None,
|
ai: None,
|
||||||
item: None,
|
item: None,
|
||||||
always_visible: false,
|
always_visible: false,
|
||||||
|
level: 1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,7 +129,7 @@ impl Object {
|
|||||||
((dx.pow(2) + dy.pow(2)) as f32).sqrt()
|
((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
|
// Apply damage if possible
|
||||||
if let Some(fighter) = self.fighter.as_mut() {
|
if let Some(fighter) = self.fighter.as_mut() {
|
||||||
if damage > 0 {
|
if damage > 0 {
|
||||||
@ -135,8 +142,10 @@ impl Object {
|
|||||||
if fighter.hp <= 0 {
|
if fighter.hp <= 0 {
|
||||||
self.alive = false;
|
self.alive = false;
|
||||||
fighter.on_death.callback(self, game);
|
fighter.on_death.callback(self, game);
|
||||||
|
return Some(fighter.xp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn attack(&mut self, target: &mut Object, game: &mut Game) {
|
pub fn attack(&mut self, target: &mut Object, game: &mut Game) {
|
||||||
@ -152,7 +161,10 @@ impl Object {
|
|||||||
WHITE,
|
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 {
|
} else {
|
||||||
game.messages.add(
|
game.messages.add(
|
||||||
format!(
|
format!(
|
||||||
@ -251,6 +263,7 @@ struct Fighter {
|
|||||||
hp: i32,
|
hp: i32,
|
||||||
defense: i32,
|
defense: i32,
|
||||||
power: i32,
|
power: i32,
|
||||||
|
xp: i32,
|
||||||
on_death: DeathCallback,
|
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: 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) => {
|
(Key { code: Up, .. }, _, true) => {
|
||||||
player_move_or_attack(0, -1, game, objects);
|
player_move_or_attack(0, -1, game, objects);
|
||||||
TookTurn
|
TookTurn
|
||||||
@ -408,6 +426,29 @@ fn handle_keys(tcod: &mut Tcod, game: &mut Game, objects: &mut Vec<Object>) -> P
|
|||||||
DidntTakeTurn
|
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,
|
_ => DidntTakeTurn,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -506,6 +547,7 @@ fn place_objects(room: Rect, map: &Map, objects: &mut Vec<Object>) {
|
|||||||
hp: 10,
|
hp: 10,
|
||||||
defense: 0,
|
defense: 0,
|
||||||
power: 3,
|
power: 3,
|
||||||
|
xp: 35,
|
||||||
on_death: DeathCallback::Monster,
|
on_death: DeathCallback::Monster,
|
||||||
});
|
});
|
||||||
orc.ai = Some(Ai::Basic);
|
orc.ai = Some(Ai::Basic);
|
||||||
@ -518,6 +560,7 @@ fn place_objects(room: Rect, map: &Map, objects: &mut Vec<Object>) {
|
|||||||
hp: 16,
|
hp: 16,
|
||||||
defense: 1,
|
defense: 1,
|
||||||
power: 4,
|
power: 4,
|
||||||
|
xp: 100,
|
||||||
on_death: DeathCallback::Monster,
|
on_death: DeathCallback::Monster,
|
||||||
});
|
});
|
||||||
troll.ai = Some(Ai::Basic);
|
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) {
|
fn monster_death(monster: &mut Object, game: &mut Game) {
|
||||||
// Transform it into a nasty corpse! It doesn't block, can't be
|
// Transform it into a nasty corpse! It doesn't block, can't be
|
||||||
// attacked and doesn't move
|
// attacked and doesn't move
|
||||||
game
|
game.messages.add(
|
||||||
.messages
|
format!(
|
||||||
.add(format!("{} is dead!", monster.name), ORANGE);
|
"{} is dead! You gain {} experience points.",
|
||||||
|
monster.name,
|
||||||
|
monster.fighter.unwrap().xp
|
||||||
|
),
|
||||||
|
ORANGE,
|
||||||
|
);
|
||||||
monster.char = '%';
|
monster.char = '%';
|
||||||
monster.color = DARK_RED;
|
monster.color = DARK_RED;
|
||||||
monster.blocks = false;
|
monster.blocks = false;
|
||||||
@ -936,7 +984,9 @@ fn cast_lightning(
|
|||||||
),
|
),
|
||||||
LIGHT_BLUE,
|
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
|
UseResult::UsedUp
|
||||||
} else {
|
} else {
|
||||||
// No enemy found within maximum range
|
// No enemy found within maximum range
|
||||||
@ -1007,7 +1057,8 @@ fn cast_fireball(
|
|||||||
ORANGE,
|
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() {
|
if obj.distance(x, y) <= FIREBALL_RADIUS as f32 && obj.fighter.is_some() {
|
||||||
game.messages.add(
|
game.messages.add(
|
||||||
format!(
|
format!(
|
||||||
@ -1016,9 +1067,15 @@ fn cast_fireball(
|
|||||||
),
|
),
|
||||||
ORANGE,
|
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
|
UseResult::UsedUp
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1262,6 +1319,7 @@ fn new_game(tcod: &mut Tcod) -> (Game, Vec<Object>) {
|
|||||||
hp: 30,
|
hp: 30,
|
||||||
defense: 2,
|
defense: 2,
|
||||||
power: 5,
|
power: 5,
|
||||||
|
xp: 0,
|
||||||
on_death: DeathCallback::Player,
|
on_death: DeathCallback::Player,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1308,6 +1366,9 @@ fn play_game(tcod: &mut Tcod, game: &mut Game, objects: &mut Vec<Object>) {
|
|||||||
|
|
||||||
tcod.root.flush();
|
tcod.root.flush();
|
||||||
|
|
||||||
|
// Level up if needed
|
||||||
|
level_up(tcod, game, objects);
|
||||||
|
|
||||||
// Handle keys and exit game if needed
|
// Handle keys and exit game if needed
|
||||||
previous_player_position = objects[PLAYER].pos();
|
previous_player_position = objects[PLAYER].pos();
|
||||||
let player_action = handle_keys(tcod, game, objects);
|
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);
|
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() {
|
fn main() {
|
||||||
tcod::system::set_fps(FPS);
|
tcod::system::set_fps(FPS);
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user