Adventure gear
This commit is contained in:
parent
3211619b96
commit
30d409149b
308
src/main.rs
308
src/main.rs
@ -2,10 +2,10 @@
|
|||||||
// #![allow(unused_variables)]
|
// #![allow(unused_variables)]
|
||||||
// #![allow(unused_mut)]
|
// #![allow(unused_mut)]
|
||||||
// #![allow(dead_code)]
|
// #![allow(dead_code)]
|
||||||
|
use std::cmp;
|
||||||
|
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::cmp;
|
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
@ -14,11 +14,48 @@ use tcod::console::*;
|
|||||||
use tcod::input::{self, Event, Key, Mouse};
|
use tcod::input::{self, Event, Key, Mouse};
|
||||||
use tcod::map::{FovAlgorithm, Map as FovMap};
|
use tcod::map::{FovAlgorithm, Map as FovMap};
|
||||||
|
|
||||||
|
// Size of the window
|
||||||
const SCREEN_WIDTH: i32 = 80;
|
const SCREEN_WIDTH: i32 = 80;
|
||||||
const SCREEN_HEIGHT: i32 = 50;
|
const SCREEN_HEIGHT: i32 = 50;
|
||||||
const FPS: i32 = 20;
|
|
||||||
|
// Size of the map
|
||||||
const MAP_WIDTH: i32 = 80;
|
const MAP_WIDTH: i32 = 80;
|
||||||
const MAP_HEIGHT: i32 = 43;
|
const MAP_HEIGHT: i32 = 43;
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
const INVENTORY_WIDTH: i32 = 50;
|
||||||
|
const CHARACTER_SCREEN_WIDTH: i32 = 30;
|
||||||
|
const LEVEL_SCREEN_WIDTH: i32 = 40;
|
||||||
|
|
||||||
|
// Parameters for the dungeon generator
|
||||||
|
const ROOM_MAX_SIZE: i32 = 10;
|
||||||
|
const ROOM_MIN_SIZE: i32 = 6;
|
||||||
|
const MAX_ROOMS: i32 = 30;
|
||||||
|
|
||||||
|
const HEAL_AMOUNT: i32 = 40;
|
||||||
|
const LIGHTNING_DAMAGE: i32 = 40;
|
||||||
|
const LIGHTNING_RANGE: i32 = 5;
|
||||||
|
const CONFUSE_RANGE: i32 = 8;
|
||||||
|
const CONFUSE_NUM_TURNS: i32 = 10;
|
||||||
|
const FIREBALL_RADIUS: i32 = 3;
|
||||||
|
const FIREBALL_DAMAGE: i32 = 25;
|
||||||
|
|
||||||
|
// Experience and level-ups
|
||||||
|
const LEVEL_UP_BASE: i32 = 200;
|
||||||
|
const LEVEL_UP_FACTOR: i32 = 150;
|
||||||
|
|
||||||
|
const FOV_ALGO: FovAlgorithm = FovAlgorithm::Basic;
|
||||||
|
const FOV_LIGHT_WALLS: bool = true;
|
||||||
|
const TORCH_RADIUS: i32 = 10;
|
||||||
|
|
||||||
|
const FPS: i32 = 20;
|
||||||
|
|
||||||
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,
|
||||||
@ -35,35 +72,11 @@ const COLOR_LIGHT_GROUND: Color = Color {
|
|||||||
g: 180,
|
g: 180,
|
||||||
b: 50,
|
b: 50,
|
||||||
};
|
};
|
||||||
const ROOM_MAX_SIZE: i32 = 10;
|
|
||||||
const ROOM_MIN_SIZE: i32 = 6;
|
// Player will always be the first object
|
||||||
const MAX_ROOMS: i32 = 30;
|
|
||||||
const FOV_ALGO: FovAlgorithm = FovAlgorithm::Basic;
|
|
||||||
const FOV_LIGHT_WALLS: bool = true;
|
|
||||||
const TORCH_RADIUS: i32 = 10;
|
|
||||||
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;
|
type Map = Vec<Vec<Tile>>;
|
||||||
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;
|
|
||||||
const MAX_ROOM_ITEMS: i32 = 2;
|
|
||||||
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;
|
|
||||||
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 {
|
struct Tcod {
|
||||||
root: Root,
|
root: Root,
|
||||||
@ -216,8 +229,6 @@ impl Tile {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Map = Vec<Vec<Tile>>;
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
struct Game {
|
struct Game {
|
||||||
map: Map,
|
map: Map,
|
||||||
@ -288,6 +299,11 @@ impl Messages {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct Transition {
|
||||||
|
level: u32,
|
||||||
|
value: u32,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
enum PlayerAction {
|
enum PlayerAction {
|
||||||
TookTurn,
|
TookTurn,
|
||||||
@ -453,7 +469,7 @@ Defense: {}",
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_map(objects: &mut Vec<Object>) -> Map {
|
fn make_map(objects: &mut Vec<Object>, level: u32) -> Map {
|
||||||
// Fill the map with unblocked tiles
|
// Fill the map with unblocked tiles
|
||||||
let mut map = vec![vec![Tile::wall(); MAP_HEIGHT as usize]; MAP_WIDTH as usize];
|
let mut map = vec![vec![Tile::wall(); MAP_HEIGHT as usize]; MAP_WIDTH as usize];
|
||||||
|
|
||||||
@ -477,7 +493,7 @@ fn make_map(objects: &mut Vec<Object>) -> Map {
|
|||||||
|
|
||||||
if !failed {
|
if !failed {
|
||||||
create_room(new_room, &mut map);
|
create_room(new_room, &mut map);
|
||||||
place_objects(new_room, &map, objects);
|
place_objects(new_room, &map, objects, level);
|
||||||
let (new_x, new_y) = new_room.center();
|
let (new_x, new_y) = new_room.center();
|
||||||
|
|
||||||
if rooms.is_empty() {
|
if rooms.is_empty() {
|
||||||
@ -530,41 +546,139 @@ fn create_v_tunnel(y1: i32, y2: i32, x: i32, map: &mut Map) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn place_objects(room: Rect, map: &Map, objects: &mut Vec<Object>) {
|
fn place_objects(room: Rect, map: &Map, objects: &mut Vec<Object>, level: u32) {
|
||||||
|
use rand::distributions::{IndependentSample, Weighted, WeightedChoice};
|
||||||
|
|
||||||
|
// Maximum number of monsters per room
|
||||||
|
let max_monsters = from_dungeon_level(
|
||||||
|
&[
|
||||||
|
Transition { level: 1, value: 2 },
|
||||||
|
Transition { level: 4, value: 3 },
|
||||||
|
Transition { level: 6, value: 5 },
|
||||||
|
],
|
||||||
|
level,
|
||||||
|
);
|
||||||
|
|
||||||
// Generate some monsters
|
// Generate some monsters
|
||||||
let num_monsters = rand::thread_rng().gen_range(0, MAX_ROOM_MONSTERS + 1);
|
let num_monsters = rand::thread_rng().gen_range(0, max_monsters + 1);
|
||||||
|
|
||||||
|
// Monster random table
|
||||||
|
let troll_chance = from_dungeon_level(
|
||||||
|
&[
|
||||||
|
Transition {
|
||||||
|
level: 3,
|
||||||
|
value: 15,
|
||||||
|
},
|
||||||
|
Transition {
|
||||||
|
level: 5,
|
||||||
|
value: 30,
|
||||||
|
},
|
||||||
|
Transition {
|
||||||
|
level: 7,
|
||||||
|
value: 60,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
level,
|
||||||
|
);
|
||||||
|
|
||||||
|
let monster_chances = &mut [
|
||||||
|
Weighted {
|
||||||
|
weight: 80,
|
||||||
|
item: "orc",
|
||||||
|
},
|
||||||
|
Weighted {
|
||||||
|
weight: troll_chance,
|
||||||
|
item: "troll",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
let monster_choice = WeightedChoice::new(monster_chances);
|
||||||
|
|
||||||
|
// Maximum number of items per room
|
||||||
|
let max_items = from_dungeon_level(
|
||||||
|
&[
|
||||||
|
Transition { level: 1, value: 1 },
|
||||||
|
Transition { level: 4, value: 2 },
|
||||||
|
],
|
||||||
|
level,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Item random table
|
||||||
|
let item_chances = &mut [
|
||||||
|
Weighted {
|
||||||
|
weight: 35,
|
||||||
|
item: Item::Heal,
|
||||||
|
},
|
||||||
|
Weighted {
|
||||||
|
weight: from_dungeon_level(
|
||||||
|
&[Transition {
|
||||||
|
level: 4,
|
||||||
|
value: 25,
|
||||||
|
}],
|
||||||
|
level,
|
||||||
|
),
|
||||||
|
item: Item::Lightning,
|
||||||
|
},
|
||||||
|
Weighted {
|
||||||
|
weight: from_dungeon_level(
|
||||||
|
&[Transition {
|
||||||
|
level: 6,
|
||||||
|
value: 25,
|
||||||
|
}],
|
||||||
|
level,
|
||||||
|
),
|
||||||
|
item: Item::Fireball,
|
||||||
|
},
|
||||||
|
Weighted {
|
||||||
|
weight: from_dungeon_level(
|
||||||
|
&[Transition {
|
||||||
|
level: 2,
|
||||||
|
value: 10,
|
||||||
|
}],
|
||||||
|
level,
|
||||||
|
),
|
||||||
|
item: Item::Confuse,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
let item_choice = WeightedChoice::new(item_chances);
|
||||||
|
|
||||||
for _ in 0..num_monsters {
|
for _ in 0..num_monsters {
|
||||||
let x = rand::thread_rng().gen_range(room.x1 + 1, room.x2);
|
let x = rand::thread_rng().gen_range(room.x1 + 1, room.x2);
|
||||||
let y = rand::thread_rng().gen_range(room.y1 + 1, room.y2);
|
let y = rand::thread_rng().gen_range(room.y1 + 1, room.y2);
|
||||||
|
|
||||||
if !is_blocked(x, y, map, objects) {
|
if !is_blocked(x, y, map, objects) {
|
||||||
let mut monster = if rand::random::<f32>() < 0.8 {
|
let mut monster = match monster_choice.ind_sample(&mut rand::thread_rng()) {
|
||||||
// 80% chance of spawning an orc
|
"orc" => {
|
||||||
let mut orc = Object::new(x, y, 'o', "orc", DESATURATED_GREEN, true);
|
// Create an orc
|
||||||
orc.fighter = Some(Fighter {
|
let mut orc = Object::new(x, y, 'o', "orc", DESATURATED_GREEN, true);
|
||||||
max_hp: 10,
|
orc.fighter = Some(Fighter {
|
||||||
hp: 10,
|
max_hp: 20,
|
||||||
defense: 0,
|
hp: 20,
|
||||||
power: 3,
|
defense: 0,
|
||||||
xp: 35,
|
power: 4,
|
||||||
on_death: DeathCallback::Monster,
|
xp: 35,
|
||||||
});
|
on_death: DeathCallback::Monster,
|
||||||
orc.ai = Some(Ai::Basic);
|
});
|
||||||
orc
|
orc.ai = Some(Ai::Basic);
|
||||||
} else {
|
orc
|
||||||
// 20% chance of spawning a troll
|
}
|
||||||
let mut troll = Object::new(x, y, 'T', "troll", DARKER_GREEN, true);
|
|
||||||
troll.fighter = Some(Fighter {
|
"troll" => {
|
||||||
max_hp: 16,
|
// Create a troll
|
||||||
hp: 16,
|
let mut troll = Object::new(x, y, 'T', "troll", DARKER_GREEN, true);
|
||||||
defense: 1,
|
troll.fighter = Some(Fighter {
|
||||||
power: 4,
|
max_hp: 30,
|
||||||
xp: 100,
|
hp: 30,
|
||||||
on_death: DeathCallback::Monster,
|
defense: 2,
|
||||||
});
|
power: 8,
|
||||||
troll.ai = Some(Ai::Basic);
|
xp: 100,
|
||||||
troll
|
on_death: DeathCallback::Monster,
|
||||||
|
});
|
||||||
|
troll.ai = Some(Ai::Basic);
|
||||||
|
troll
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
monster.alive = true;
|
monster.alive = true;
|
||||||
@ -573,7 +687,7 @@ fn place_objects(room: Rect, map: &Map, objects: &mut Vec<Object>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Generate some items
|
// Generate some items
|
||||||
let num_items = rand::thread_rng().gen_range(0, MAX_ROOM_ITEMS + 1);
|
let num_items = rand::thread_rng().gen_range(0, max_items + 1);
|
||||||
|
|
||||||
for _ in 0..num_items {
|
for _ in 0..num_items {
|
||||||
// Choose random spot for this item
|
// Choose random spot for this item
|
||||||
@ -582,27 +696,31 @@ 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) {
|
||||||
let dice = rand::random::<f32>();
|
let mut item = match item_choice.ind_sample(&mut rand::thread_rng()) {
|
||||||
let mut item = if dice < 0.7 {
|
Item::Heal => {
|
||||||
// Create a healing potion (70% chance)
|
// Create a healing potion
|
||||||
let mut object = Object::new(x, y, '!', "healing potion", VIOLET, false);
|
let mut object = Object::new(x, y, '!', "healing potion", VIOLET, false);
|
||||||
object.item = Some(Item::Heal);
|
object.item = Some(Item::Heal);
|
||||||
object
|
object
|
||||||
} else if dice < 0.7 + 0.1 {
|
}
|
||||||
// Create a lightning bolt scroll (10% chance)
|
Item::Lightning => {
|
||||||
let mut object = Object::new(x, y, '#', "scroll of lightning bolt", LIGHT_YELLOW, false);
|
// Create a lightning bolt scroll
|
||||||
object.item = Some(Item::Lightning);
|
let mut object = Object::new(x, y, '#', "scroll of lightning bolt", LIGHT_YELLOW, false);
|
||||||
object
|
object.item = Some(Item::Lightning);
|
||||||
} else if dice < 0.7 + 0.1 + 0.1 {
|
object
|
||||||
// Create a fireball scroll (10% chance)
|
}
|
||||||
let mut object = Object::new(x, y, '#', "scroll of fireball", LIGHT_YELLOW, false);
|
Item::Fireball => {
|
||||||
object.item = Some(Item::Fireball);
|
// Create a fireball scroll
|
||||||
object
|
let mut object = Object::new(x, y, '#', "scroll of fireball", LIGHT_YELLOW, false);
|
||||||
} else {
|
object.item = Some(Item::Fireball);
|
||||||
// Create a confuse scroll (10% chance)
|
object
|
||||||
let mut object = Object::new(x, y, '#', "scroll of confusion", LIGHT_YELLOW, false);
|
}
|
||||||
object.item = Some(Item::Confuse);
|
Item::Confuse => {
|
||||||
object
|
// Create a confuse scroll
|
||||||
|
let mut object = Object::new(x, y, '#', "scroll of confusion", LIGHT_YELLOW, false);
|
||||||
|
object.item = Some(Item::Confuse);
|
||||||
|
object
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
item.always_visible = true;
|
item.always_visible = true;
|
||||||
@ -1315,10 +1433,10 @@ fn new_game(tcod: &mut Tcod) -> (Game, Vec<Object>) {
|
|||||||
let mut player = Object::new(0, 0, '@', "player", WHITE, true);
|
let mut player = Object::new(0, 0, '@', "player", WHITE, true);
|
||||||
player.alive = true;
|
player.alive = true;
|
||||||
player.fighter = Some(Fighter {
|
player.fighter = Some(Fighter {
|
||||||
max_hp: 30,
|
max_hp: 100,
|
||||||
hp: 30,
|
hp: 100,
|
||||||
defense: 2,
|
defense: 1,
|
||||||
power: 5,
|
power: 4,
|
||||||
xp: 0,
|
xp: 0,
|
||||||
on_death: DeathCallback::Player,
|
on_death: DeathCallback::Player,
|
||||||
});
|
});
|
||||||
@ -1328,7 +1446,7 @@ fn new_game(tcod: &mut Tcod) -> (Game, Vec<Object>) {
|
|||||||
|
|
||||||
let mut game = Game {
|
let mut game = Game {
|
||||||
// Generate map
|
// Generate map
|
||||||
map: make_map(&mut objects),
|
map: make_map(&mut objects, 1),
|
||||||
messages: Messages::new(),
|
messages: Messages::new(),
|
||||||
inventory: vec![],
|
inventory: vec![],
|
||||||
dungeon_level: 1,
|
dungeon_level: 1,
|
||||||
@ -1481,7 +1599,7 @@ fn next_level(tcod: &mut Tcod, game: &mut Game, objects: &mut Vec<Object>) {
|
|||||||
RED,
|
RED,
|
||||||
);
|
);
|
||||||
game.dungeon_level += 1;
|
game.dungeon_level += 1;
|
||||||
game.map = make_map(objects);
|
game.map = make_map(objects, game.dungeon_level);
|
||||||
initialise_fov(tcod, &game.map);
|
initialise_fov(tcod, &game.map);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1535,6 +1653,16 @@ fn level_up(tcod: &mut Tcod, game: &mut Game, objects: &mut [Object]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns a value that depends on level. The table specifies what
|
||||||
|
// value occurs after each level, default is 0.
|
||||||
|
fn from_dungeon_level(table: &[Transition], level: u32) -> u32 {
|
||||||
|
table
|
||||||
|
.iter()
|
||||||
|
.rev()
|
||||||
|
.find(|transition| level >= transition.level)
|
||||||
|
.map_or(0, |transition| transition.value)
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
tcod::system::set_fps(FPS);
|
tcod::system::set_fps(FPS);
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user