2022-01-21 15:55:13 -05:00
|
|
|
//! Functionality that is common to all the currently generated maps.
|
2022-01-19 10:15:51 -05:00
|
|
|
use std::collections::{HashMap, HashSet};
|
2022-01-05 16:44:44 -05:00
|
|
|
|
2022-02-04 14:09:48 -05:00
|
|
|
use ::bracket_lib::prelude::*;
|
2022-01-05 16:44:44 -05:00
|
|
|
use ::serde::{Deserialize, Serialize};
|
|
|
|
use ::specs::prelude::*;
|
|
|
|
|
2022-01-06 09:34:17 -05:00
|
|
|
use crate::components::{OtherLevelPosition, Position, Viewshed};
|
2022-01-05 16:44:44 -05:00
|
|
|
use crate::map::{Map, TileType};
|
|
|
|
use crate::map_builders::level_builder;
|
2022-01-19 14:35:13 -05:00
|
|
|
use crate::raws;
|
2022-02-03 14:59:35 -05:00
|
|
|
use crate::rng::roll_dice;
|
2022-01-05 16:44:44 -05:00
|
|
|
|
|
|
|
#[derive(Default, Serialize, Deserialize, Clone)]
|
|
|
|
pub struct MasterDungeonMap {
|
|
|
|
maps: HashMap<i32, Map>,
|
2022-01-19 10:15:51 -05:00
|
|
|
pub identified_items: HashSet<String>,
|
|
|
|
pub scroll_mappings: HashMap<String, String>,
|
2022-01-19 14:35:13 -05:00
|
|
|
pub potion_mappings: HashMap<String, String>,
|
2022-01-05 16:44:44 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
impl MasterDungeonMap {
|
|
|
|
pub fn new() -> MasterDungeonMap {
|
2022-01-19 10:15:51 -05:00
|
|
|
let mut dm = MasterDungeonMap {
|
2022-01-05 16:44:44 -05:00
|
|
|
maps: HashMap::new(),
|
2022-01-19 10:15:51 -05:00
|
|
|
identified_items: HashSet::new(),
|
|
|
|
scroll_mappings: HashMap::new(),
|
2022-01-19 14:35:13 -05:00
|
|
|
potion_mappings: HashMap::new(),
|
2022-01-19 10:15:51 -05:00
|
|
|
};
|
|
|
|
|
2022-01-19 14:35:13 -05:00
|
|
|
for scroll_tag in raws::get_scroll_tags().iter() {
|
2022-02-03 14:59:35 -05:00
|
|
|
let masked_name = make_scroll_name();
|
2022-01-19 10:15:51 -05:00
|
|
|
dm.scroll_mappings
|
|
|
|
.insert(scroll_tag.to_string(), masked_name);
|
2022-01-05 16:44:44 -05:00
|
|
|
}
|
2022-01-19 10:15:51 -05:00
|
|
|
|
2022-01-19 14:35:13 -05:00
|
|
|
let mut used_potion_names = HashSet::new();
|
|
|
|
for potion_tag in raws::get_potion_tags().iter() {
|
2022-02-03 14:59:35 -05:00
|
|
|
let masked_name = make_potion_name(&mut used_potion_names);
|
2022-01-19 14:35:13 -05:00
|
|
|
dm.potion_mappings
|
|
|
|
.insert(potion_tag.to_string(), masked_name);
|
|
|
|
}
|
|
|
|
|
2022-01-19 10:15:51 -05:00
|
|
|
dm
|
2022-01-05 16:44:44 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn store_map(&mut self, map: &Map) {
|
|
|
|
self.maps.insert(map.depth, map.clone());
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn get_map(&self, depth: i32) -> Option<Map> {
|
|
|
|
if self.maps.contains_key(&depth) {
|
2022-01-12 10:45:13 -05:00
|
|
|
Some(self.maps[&depth].clone())
|
2022-01-05 16:44:44 -05:00
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-03 14:59:35 -05:00
|
|
|
fn make_scroll_name() -> String {
|
|
|
|
let length = 4 + roll_dice(1, 4);
|
2022-01-19 10:15:51 -05:00
|
|
|
let mut name = "Scroll of ".to_string();
|
|
|
|
|
|
|
|
for i in 0..length {
|
|
|
|
if i % 2 == 0 {
|
2022-02-03 14:59:35 -05:00
|
|
|
name += match roll_dice(1, 5) {
|
2022-01-19 10:15:51 -05:00
|
|
|
1 => "a",
|
|
|
|
2 => "e",
|
|
|
|
3 => "i",
|
|
|
|
4 => "o",
|
|
|
|
_ => "u",
|
|
|
|
}
|
|
|
|
} else {
|
2022-02-03 14:59:35 -05:00
|
|
|
name += match roll_dice(1, 21) {
|
2022-01-19 10:15:51 -05:00
|
|
|
1 => "b",
|
|
|
|
2 => "c",
|
|
|
|
3 => "d",
|
|
|
|
4 => "f",
|
|
|
|
5 => "g",
|
|
|
|
6 => "h",
|
|
|
|
7 => "j",
|
|
|
|
8 => "k",
|
|
|
|
9 => "l",
|
|
|
|
10 => "m",
|
|
|
|
11 => "n",
|
|
|
|
12 => "p",
|
|
|
|
13 => "q",
|
|
|
|
14 => "r",
|
|
|
|
15 => "s",
|
|
|
|
16 => "t",
|
|
|
|
17 => "v",
|
|
|
|
18 => "w",
|
|
|
|
19 => "x",
|
|
|
|
20 => "y",
|
|
|
|
_ => "z",
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
name
|
|
|
|
}
|
|
|
|
|
2022-01-19 14:35:13 -05:00
|
|
|
const POTION_COLORS: &[&str] = &[
|
|
|
|
"Red", "Orange", "Yellow", "Green", "Brown", "Indigo", "Violet",
|
|
|
|
];
|
|
|
|
const POTION_ADJECTIVES: &[&str] = &[
|
|
|
|
"Swirling",
|
|
|
|
"Effervescent",
|
|
|
|
"Slimey",
|
|
|
|
"Oiley",
|
|
|
|
"Viscous",
|
|
|
|
"Smelly",
|
|
|
|
"Glowing",
|
|
|
|
];
|
|
|
|
|
2022-02-03 14:59:35 -05:00
|
|
|
fn make_potion_name(used_names: &mut HashSet<String>) -> String {
|
2022-01-19 14:35:13 -05:00
|
|
|
loop {
|
2022-02-03 14:59:35 -05:00
|
|
|
let mut name = POTION_ADJECTIVES[roll_dice(1, POTION_ADJECTIVES.len() as i32) as usize - 1]
|
2022-01-19 14:35:13 -05:00
|
|
|
.to_string();
|
|
|
|
name += " ";
|
2022-02-03 14:59:35 -05:00
|
|
|
name += POTION_COLORS[roll_dice(1, POTION_COLORS.len() as i32) as usize - 1];
|
2022-01-19 14:35:13 -05:00
|
|
|
name += " Potion";
|
|
|
|
|
|
|
|
if !used_names.contains(&name) {
|
|
|
|
used_names.insert(name.clone());
|
|
|
|
|
|
|
|
return name;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-06 09:34:17 -05:00
|
|
|
pub fn level_transition(ecs: &mut World, new_depth: i32, offset: i32) -> Option<Vec<Map>> {
|
2022-01-05 16:44:44 -05:00
|
|
|
// Obtain the master dungeon map
|
|
|
|
let dungeon_master = ecs.read_resource::<MasterDungeonMap>();
|
|
|
|
|
|
|
|
// Do we already have a map?
|
|
|
|
if dungeon_master.get_map(new_depth).is_some() {
|
|
|
|
std::mem::drop(dungeon_master);
|
2022-01-06 09:34:17 -05:00
|
|
|
transition_to_existing_map(ecs, new_depth, offset);
|
2022-01-05 16:44:44 -05:00
|
|
|
|
|
|
|
None
|
|
|
|
} else {
|
|
|
|
std::mem::drop(dungeon_master);
|
|
|
|
|
|
|
|
Some(transition_to_new_map(ecs, new_depth))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn transition_to_new_map(ecs: &mut World, new_depth: i32) -> Vec<Map> {
|
2022-02-03 14:59:35 -05:00
|
|
|
let mut builder = level_builder(new_depth, 80, 50);
|
|
|
|
builder.build_map();
|
2022-01-05 16:44:44 -05:00
|
|
|
|
|
|
|
if new_depth > 1 {
|
|
|
|
if let Some(pos) = &builder.build_data.starting_position {
|
|
|
|
let up_idx = builder.build_data.map.xy_idx(pos.x, pos.y);
|
|
|
|
builder.build_data.map.tiles[up_idx] = TileType::UpStairs;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let mapgen_history = builder.build_data.history.clone();
|
|
|
|
let player_start;
|
|
|
|
{
|
|
|
|
let mut worldmap_resource = ecs.write_resource::<Map>();
|
|
|
|
*worldmap_resource = builder.build_data.map.clone();
|
|
|
|
player_start = *builder.build_data.starting_position.as_mut().unwrap();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Spawn bad guys
|
|
|
|
builder.spawn_entities(ecs);
|
|
|
|
|
|
|
|
// Place the player and update resources
|
|
|
|
let (player_x, player_y) = (player_start.x, player_start.y);
|
|
|
|
let mut player_position = ecs.write_resource::<Point>();
|
|
|
|
*player_position = player_start.into();
|
|
|
|
|
|
|
|
let mut position_components = ecs.write_storage::<Position>();
|
|
|
|
let player_entity = ecs.fetch::<Entity>();
|
|
|
|
if let Some(player_pos_comp) = position_components.get_mut(*player_entity) {
|
|
|
|
player_pos_comp.x = player_x;
|
|
|
|
player_pos_comp.y = player_y;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Mark the player's visiblity as dirty
|
|
|
|
let mut viewshed_components = ecs.write_storage::<Viewshed>();
|
|
|
|
if let Some(vs) = viewshed_components.get_mut(*player_entity) {
|
|
|
|
vs.dirty = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Store the newly minted map
|
|
|
|
let mut dungeon_master = ecs.write_resource::<MasterDungeonMap>();
|
|
|
|
dungeon_master.store_map(&builder.build_data.map);
|
|
|
|
|
|
|
|
mapgen_history
|
|
|
|
}
|
|
|
|
|
2022-01-06 09:34:17 -05:00
|
|
|
fn transition_to_existing_map(ecs: &mut World, new_depth: i32, offset: i32) {
|
2022-01-05 16:44:44 -05:00
|
|
|
let dungeon_master = ecs.write_resource::<MasterDungeonMap>();
|
|
|
|
let map = dungeon_master.get_map(new_depth).unwrap();
|
|
|
|
let mut worldmap_resource = ecs.write_resource::<Map>();
|
|
|
|
let player_entity = ecs.fetch::<Entity>();
|
|
|
|
|
|
|
|
// Find the down stairs and place the player
|
|
|
|
let w = map.width;
|
2022-01-06 09:34:17 -05:00
|
|
|
let stair_type = if offset < 0 {
|
|
|
|
TileType::DownStairs
|
|
|
|
} else {
|
|
|
|
TileType::UpStairs
|
|
|
|
};
|
2022-01-05 16:44:44 -05:00
|
|
|
for (idx, tt) in map.tiles.iter().enumerate() {
|
2022-01-06 09:34:17 -05:00
|
|
|
if *tt == stair_type {
|
2022-01-05 16:44:44 -05:00
|
|
|
let mut player_position = ecs.write_resource::<Point>();
|
|
|
|
*player_position = Point::new(idx as i32 % w, idx as i32 / w);
|
|
|
|
let mut position_components = ecs.write_storage::<Position>();
|
|
|
|
if let Some(player_pos_comp) = position_components.get_mut(*player_entity) {
|
|
|
|
player_pos_comp.x = idx as i32 % w;
|
|
|
|
player_pos_comp.y = idx as i32 / w;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
*worldmap_resource = map;
|
|
|
|
|
|
|
|
// Mark the player's visibility as dirty
|
|
|
|
let mut viewshed_components = ecs.write_storage::<Viewshed>();
|
|
|
|
if let Some(vs) = viewshed_components.get_mut(*player_entity) {
|
|
|
|
vs.dirty = true;
|
|
|
|
}
|
|
|
|
}
|
2022-01-06 09:34:17 -05:00
|
|
|
|
|
|
|
pub fn freeze_level_entities(ecs: &mut World) {
|
|
|
|
// Obtain ECS access
|
|
|
|
let entities = ecs.entities();
|
|
|
|
let mut positions = ecs.write_storage::<Position>();
|
|
|
|
let mut other_level_positions = ecs.write_storage::<OtherLevelPosition>();
|
|
|
|
let player_entity = ecs.fetch::<Entity>();
|
|
|
|
let map_depth = ecs.fetch::<Map>().depth;
|
|
|
|
|
|
|
|
// Find positions and make OtherLevelPosition
|
|
|
|
let mut pos_to_delete: Vec<Entity> = Vec::new();
|
|
|
|
for (entity, pos) in (&entities, &positions).join() {
|
|
|
|
if entity != *player_entity {
|
|
|
|
other_level_positions
|
|
|
|
.insert(
|
|
|
|
entity,
|
|
|
|
OtherLevelPosition {
|
|
|
|
x: pos.x,
|
|
|
|
y: pos.y,
|
|
|
|
depth: map_depth,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
.expect("Failed to insert OtherLevelPosition");
|
|
|
|
|
|
|
|
pos_to_delete.push(entity);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove positions
|
|
|
|
for p in pos_to_delete.iter() {
|
|
|
|
positions.remove(*p);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn thaw_level_entities(ecs: &mut World) {
|
|
|
|
// Obtain ECS access
|
|
|
|
let entities = ecs.entities();
|
|
|
|
let mut positions = ecs.write_storage::<Position>();
|
|
|
|
let mut other_level_positions = ecs.write_storage::<OtherLevelPosition>();
|
|
|
|
let player_entity = ecs.fetch::<Entity>();
|
|
|
|
let map_depth = ecs.fetch::<Map>().depth;
|
|
|
|
|
|
|
|
// Find OtherLevelPosition
|
|
|
|
let mut pos_to_delete: Vec<Entity> = Vec::new();
|
|
|
|
for (entity, pos) in (&entities, &other_level_positions).join() {
|
|
|
|
if entity != *player_entity && pos.depth == map_depth {
|
|
|
|
positions
|
|
|
|
.insert(entity, Position { x: pos.x, y: pos.y })
|
|
|
|
.expect("Failed to insert Position");
|
|
|
|
pos_to_delete.push(entity);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove positions
|
|
|
|
for p in pos_to_delete.iter() {
|
|
|
|
other_level_positions.remove(*p);
|
|
|
|
}
|
|
|
|
}
|