Mouse-look
This commit is contained in:
parent
5b4f28a7c8
commit
b3431cbdf9
179
src/main.rs
179
src/main.rs
@ -13,7 +13,7 @@ const SCREEN_WIDTH: i32 = 80;
|
|||||||
const SCREEN_HEIGHT: i32 = 50;
|
const SCREEN_HEIGHT: i32 = 50;
|
||||||
const FPS: i32 = 20;
|
const FPS: i32 = 20;
|
||||||
const MAP_WIDTH: i32 = 80;
|
const MAP_WIDTH: i32 = 80;
|
||||||
const MAP_HEIGHT: i32 = 45;
|
const MAP_HEIGHT: i32 = 43;
|
||||||
const COLOR_DARK_WALL: Color = Color { r: 0, g: 0, b: 100 };
|
const COLOR_DARK_WALL: Color = Color { r: 0, g: 0, b: 100 };
|
||||||
const COLOR_LIGHT_WALL: Color = Color {
|
const COLOR_LIGHT_WALL: Color = Color {
|
||||||
r: 130,
|
r: 130,
|
||||||
@ -38,10 +38,18 @@ const FOV_LIGHT_WALLS: bool = true;
|
|||||||
const TORCH_RADIUS: i32 = 10;
|
const TORCH_RADIUS: i32 = 10;
|
||||||
const MAX_ROOM_MONSTERS: i32 = 3;
|
const MAX_ROOM_MONSTERS: i32 = 3;
|
||||||
const PLAYER: usize = 0;
|
const PLAYER: usize = 0;
|
||||||
|
// Sizes and coordinates relevant for the GUI
|
||||||
|
const BAR_WIDTH: i32 = 20;
|
||||||
|
const PANEL_HEIGHT: i32 = 7;
|
||||||
|
const PANEL_Y: i32 = SCREEN_HEIGHT - PANEL_HEIGHT;
|
||||||
|
const MSG_X: i32 = BAR_WIDTH + 2;
|
||||||
|
const MSG_WIDTH: i32 = SCREEN_WIDTH - BAR_WIDTH - 2;
|
||||||
|
const MSG_HEIGHT: usize = PANEL_HEIGHT as usize - 1;
|
||||||
|
|
||||||
struct Tcod {
|
struct Tcod {
|
||||||
root: Root,
|
root: Root,
|
||||||
con: Offscreen,
|
con: Offscreen,
|
||||||
|
panel: Offscreen,
|
||||||
fov: FovMap,
|
fov: FovMap,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,7 +102,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) {
|
pub fn take_damage(&mut self, damage: i32, game: &mut Game) {
|
||||||
// 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 {
|
||||||
@ -106,26 +114,33 @@ impl Object {
|
|||||||
if let Some(fighter) = self.fighter {
|
if let Some(fighter) = self.fighter {
|
||||||
if fighter.hp <= 0 {
|
if fighter.hp <= 0 {
|
||||||
self.alive = false;
|
self.alive = false;
|
||||||
fighter.on_death.callback(self);
|
fighter.on_death.callback(self, game);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn attack(&mut self, target: &mut Object) {
|
pub fn attack(&mut self, target: &mut Object, game: &mut Game) {
|
||||||
// A simple formula for attack damage
|
// A simple formula for attack damage
|
||||||
let damage = self.fighter.map_or(0, |f| f.power) - target.fighter.map_or(0, |f| f.defense);
|
let damage = self.fighter.map_or(0, |f| f.power) - target.fighter.map_or(0, |f| f.defense);
|
||||||
if damage > 0 {
|
if damage > 0 {
|
||||||
// Make the target take some damage
|
// Make the target take some damage
|
||||||
println!(
|
game.messages.add(
|
||||||
|
format!(
|
||||||
"{} attacks {} for {} hit points.",
|
"{} attacks {} for {} hit points.",
|
||||||
self.name, target.name, damage
|
self.name, target.name, damage
|
||||||
|
),
|
||||||
|
WHITE,
|
||||||
);
|
);
|
||||||
target.take_damage(damage);
|
|
||||||
|
target.take_damage(damage, game);
|
||||||
} else {
|
} else {
|
||||||
println!(
|
game.messages.add(
|
||||||
|
format!(
|
||||||
"{} attacks {} but it has no effect!",
|
"{} attacks {} but it has no effect!",
|
||||||
self.name, target.name
|
self.name, target.name
|
||||||
)
|
),
|
||||||
|
WHITE,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -159,6 +174,7 @@ type Map = Vec<Vec<Tile>>;
|
|||||||
|
|
||||||
struct Game {
|
struct Game {
|
||||||
map: Map,
|
map: Map,
|
||||||
|
messages: Messages,
|
||||||
}
|
}
|
||||||
|
|
||||||
// A rectangle on the map, used to characterise a room.
|
// A rectangle on the map, used to characterise a room.
|
||||||
@ -201,6 +217,26 @@ struct Fighter {
|
|||||||
on_death: DeathCallback,
|
on_death: DeathCallback,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct Messages {
|
||||||
|
messages: Vec<(String, Color)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Messages {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self { messages: vec![] }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the new message as a tuple, with the text and the color
|
||||||
|
pub fn add<T: Into<String>>(&mut self, message: T, color: Color) {
|
||||||
|
self.messages.push((message.into(), color));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a `DoubleEndedIterator` over the messages
|
||||||
|
pub fn iter(&self) -> impl DoubleEndedIterator<Item = &(String, Color)> {
|
||||||
|
self.messages.iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
enum PlayerAction {
|
enum PlayerAction {
|
||||||
TookTurn,
|
TookTurn,
|
||||||
@ -220,17 +256,17 @@ enum DeathCallback {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl DeathCallback {
|
impl DeathCallback {
|
||||||
fn callback(self, object: &mut Object) {
|
fn callback(self, object: &mut Object, game: &mut Game) {
|
||||||
use DeathCallback::*;
|
use DeathCallback::*;
|
||||||
let callback: fn(&mut Object) = match self {
|
let callback = match self {
|
||||||
Player => player_death,
|
Player => player_death,
|
||||||
Monster => monster_death,
|
Monster => monster_death,
|
||||||
};
|
};
|
||||||
callback(object);
|
callback(object, game);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_keys(tcod: &mut Tcod, game: &Game, objects: &mut Vec<Object>) -> PlayerAction {
|
fn handle_keys(tcod: &mut Tcod, game: &mut Game, objects: &mut Vec<Object>) -> PlayerAction {
|
||||||
use tcod::input::Key;
|
use tcod::input::Key;
|
||||||
use tcod::input::KeyCode::*;
|
use tcod::input::KeyCode::*;
|
||||||
use PlayerAction::*;
|
use PlayerAction::*;
|
||||||
@ -400,7 +436,7 @@ fn move_by(id: usize, dx: i32, dy: i32, map: &Map, objects: &mut [Object]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn player_move_or_attack(dx: i32, dy: i32, game: &Game, objects: &mut [Object]) {
|
fn player_move_or_attack(dx: i32, dy: i32, game: &mut Game, objects: &mut [Object]) {
|
||||||
// The coordinates the player is moving to / attacking
|
// The coordinates the player is moving to / attacking
|
||||||
let x = objects[PLAYER].x + dx;
|
let x = objects[PLAYER].x + dx;
|
||||||
let y = objects[PLAYER].y + dy;
|
let y = objects[PLAYER].y + dy;
|
||||||
@ -414,7 +450,7 @@ fn player_move_or_attack(dx: i32, dy: i32, game: &Game, objects: &mut [Object])
|
|||||||
match target_id {
|
match target_id {
|
||||||
Some(target_id) => {
|
Some(target_id) => {
|
||||||
let (player, target) = mut_two(PLAYER, target_id, objects);
|
let (player, target) = mut_two(PLAYER, target_id, objects);
|
||||||
player.attack(target);
|
player.attack(target, game);
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
move_by(PLAYER, dx, dy, &game.map, objects);
|
move_by(PLAYER, dx, dy, &game.map, objects);
|
||||||
@ -435,7 +471,7 @@ fn move_towards(id: usize, target_x: i32, target_y: i32, map: &Map, objects: &mu
|
|||||||
move_by(id, dx, dy, map, objects);
|
move_by(id, dx, dy, map, objects);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ai_take_turn(monster_id: usize, tcod: &Tcod, game: &Game, objects: &mut [Object]) {
|
fn ai_take_turn(monster_id: usize, tcod: &Tcod, game: &mut Game, objects: &mut [Object]) {
|
||||||
// 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) {
|
||||||
@ -446,7 +482,7 @@ fn ai_take_turn(monster_id: usize, tcod: &Tcod, game: &Game, objects: &mut [Obje
|
|||||||
} else if objects[PLAYER].fighter.map_or(false, |f| f.hp > 0) {
|
} else if objects[PLAYER].fighter.map_or(false, |f| f.hp > 0) {
|
||||||
// Close enough, attach! (if the player is still alive.)
|
// Close enough, attach! (if the player is still alive.)
|
||||||
let (monster, player) = mut_two(monster_id, PLAYER, objects);
|
let (monster, player) = mut_two(monster_id, PLAYER, objects);
|
||||||
monster.attack(player);
|
monster.attack(player, game);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -462,19 +498,21 @@ fn mut_two<T>(first_index: usize, second_index: usize, items: &mut [T]) -> (&mut
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn player_death(player: &mut Object) {
|
fn player_death(player: &mut Object, game: &mut Game) {
|
||||||
// The game ended!
|
// The game ended!
|
||||||
println!("You died!");
|
game.messages.add("You died!", RED);
|
||||||
|
|
||||||
// For added effect, transform the player into corpse!
|
// For added effect, transform the player into corpse!
|
||||||
player.char = '%';
|
player.char = '%';
|
||||||
player.color = DARK_RED;
|
player.color = DARK_RED;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn monster_death(monster: &mut Object) {
|
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
|
||||||
println!("{} is dead!", monster.name);
|
game
|
||||||
|
.messages
|
||||||
|
.add(format!("{} is dead!", monster.name), ORANGE);
|
||||||
monster.char = '%';
|
monster.char = '%';
|
||||||
monster.color = DARK_RED;
|
monster.color = DARK_RED;
|
||||||
monster.blocks = false;
|
monster.blocks = false;
|
||||||
@ -483,6 +521,41 @@ fn monster_death(monster: &mut Object) {
|
|||||||
monster.name = format!("remains of {}", monster.name);
|
monster.name = format!("remains of {}", monster.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn render_bar(
|
||||||
|
panel: &mut Offscreen,
|
||||||
|
x: i32,
|
||||||
|
y: i32,
|
||||||
|
total_width: i32,
|
||||||
|
name: &str,
|
||||||
|
value: i32,
|
||||||
|
maximum: i32,
|
||||||
|
bar_color: Color,
|
||||||
|
back_color: Color,
|
||||||
|
) {
|
||||||
|
// Render a bar (HP, experience, etc). First calculate the width of the bar
|
||||||
|
let bar_width = (value as f32 / maximum as f32 * total_width as f32) as i32;
|
||||||
|
|
||||||
|
// Render the background first
|
||||||
|
panel.set_default_background(back_color);
|
||||||
|
panel.rect(x, y, total_width, 1, false, BackgroundFlag::Screen);
|
||||||
|
|
||||||
|
// Now render the bar on top
|
||||||
|
panel.set_default_background(bar_color);
|
||||||
|
if bar_width > 0 {
|
||||||
|
panel.rect(x, y, bar_width, 1, false, BackgroundFlag::Screen);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, some centered text with the values
|
||||||
|
panel.set_default_foreground(WHITE);
|
||||||
|
panel.print_ex(
|
||||||
|
x + total_width / 2,
|
||||||
|
y,
|
||||||
|
BackgroundFlag::None,
|
||||||
|
TextAlignment::Center,
|
||||||
|
&format!("{}: {}/{}", name, value, maximum),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
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];
|
||||||
@ -530,18 +603,6 @@ 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 contents of "con" to the root console
|
||||||
blit(
|
blit(
|
||||||
&tcod.con,
|
&tcod.con,
|
||||||
@ -552,6 +613,48 @@ fn render_all(tcod: &mut Tcod, game: &mut Game, objects: &[Object], fov_recomput
|
|||||||
1.0,
|
1.0,
|
||||||
1.0,
|
1.0,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Prepare to render the GUI panel
|
||||||
|
tcod.panel.set_default_background(BLACK);
|
||||||
|
tcod.panel.clear();
|
||||||
|
|
||||||
|
// Show the player's stats
|
||||||
|
let hp = objects[PLAYER].fighter.map_or(0, |f| f.hp);
|
||||||
|
let max_hp = objects[PLAYER].fighter.map_or(0, |f| f.max_hp);
|
||||||
|
render_bar(
|
||||||
|
&mut tcod.panel,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
BAR_WIDTH,
|
||||||
|
"HP",
|
||||||
|
hp,
|
||||||
|
max_hp,
|
||||||
|
LIGHT_RED,
|
||||||
|
DARKER_RED,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Print the game messages, one line at a time
|
||||||
|
let mut y = MSG_HEIGHT as i32;
|
||||||
|
for &(ref msg, color) in game.messages.iter().rev() {
|
||||||
|
let msg_height = tcod.panel.get_height_rect(MSG_X, y, MSG_WIDTH, 0, msg);
|
||||||
|
y -= msg_height;
|
||||||
|
if y < 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
tcod.panel.set_default_foreground(color);
|
||||||
|
tcod.panel.print_rect(MSG_X, y, MSG_WIDTH, 0, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blit contents of "panel" to the root console
|
||||||
|
blit(
|
||||||
|
&tcod.panel,
|
||||||
|
(0, 0),
|
||||||
|
(SCREEN_WIDTH, PANEL_HEIGHT),
|
||||||
|
&mut tcod.root,
|
||||||
|
(0, PANEL_Y),
|
||||||
|
1.0,
|
||||||
|
1.0,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
@ -567,6 +670,7 @@ fn main() {
|
|||||||
let mut tcod = Tcod {
|
let mut tcod = Tcod {
|
||||||
root,
|
root,
|
||||||
con: Offscreen::new(MAP_WIDTH, MAP_HEIGHT),
|
con: Offscreen::new(MAP_WIDTH, MAP_HEIGHT),
|
||||||
|
panel: Offscreen::new(SCREEN_WIDTH, PANEL_HEIGHT),
|
||||||
fov: FovMap::new(MAP_WIDTH, MAP_HEIGHT),
|
fov: FovMap::new(MAP_WIDTH, MAP_HEIGHT),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -587,6 +691,7 @@ fn main() {
|
|||||||
let mut game = Game {
|
let mut game = Game {
|
||||||
// Generate map
|
// Generate map
|
||||||
map: make_map(&mut objects),
|
map: make_map(&mut objects),
|
||||||
|
messages: Messages::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Populate FOV map, according to generated map
|
// Populate FOV map, according to generated map
|
||||||
@ -603,6 +708,12 @@ fn main() {
|
|||||||
|
|
||||||
let mut previous_player_position = (-1, -1);
|
let mut previous_player_position = (-1, -1);
|
||||||
|
|
||||||
|
// A warm welcoming message!
|
||||||
|
game.messages.add(
|
||||||
|
"Welcome stranger! Prepare to perish in the Tomb of Doom.",
|
||||||
|
RED,
|
||||||
|
);
|
||||||
|
|
||||||
// Main game loop
|
// Main game loop
|
||||||
while !tcod.root.window_closed() {
|
while !tcod.root.window_closed() {
|
||||||
tcod.con.clear();
|
tcod.con.clear();
|
||||||
@ -612,7 +723,7 @@ fn main() {
|
|||||||
tcod.root.flush();
|
tcod.root.flush();
|
||||||
|
|
||||||
previous_player_position = objects[PLAYER].pos();
|
previous_player_position = objects[PLAYER].pos();
|
||||||
let player_action = handle_keys(&mut tcod, &game, &mut objects);
|
let player_action = handle_keys(&mut tcod, &mut game, &mut objects);
|
||||||
if player_action == PlayerAction::Exit {
|
if player_action == PlayerAction::Exit {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -621,7 +732,7 @@ fn main() {
|
|||||||
if objects[PLAYER].alive && player_action != PlayerAction::DidntTakeTurn {
|
if objects[PLAYER].alive && player_action != PlayerAction::DidntTakeTurn {
|
||||||
for id in 0..objects.len() {
|
for id in 0..objects.len() {
|
||||||
if objects[id].ai.is_some() {
|
if objects[id].ai.is_some() {
|
||||||
ai_take_turn(id, &tcod, &game, &mut objects);
|
ai_take_turn(id, &tcod, &mut game, &mut objects);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user