/*
* File: overmap.cc
* Summary: Records location of stairs etc
* Written by: Linley Henzell
*/
#include "AppHdr.h"
#include "overmap.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <algorithm>
#include "externs.h"
#include "branch.h"
#include "cio.h"
#include "colour.h"
#include "coord.h"
#include "dgnevent.h"
#include "directn.h"
#include "dungeon.h"
#include "map_knowledge.h"
#include "feature.h"
#include "files.h"
#include "menu.h"
#include "religion.h"
#include "shopping.h"
#include "state.h"
#include "stuff.h"
#include "env.h"
#include "terrain.h"
#include "travel.h"
typedef std::map<branch_type, level_id> stair_map_type;
typedef std::map<level_pos, shop_type> shop_map_type;
typedef std::map<level_pos, god_type> altar_map_type;
typedef std::map<level_pos, portal_type> portal_map_type;
typedef std::map<level_pos, std::string> portal_vault_map_type;
typedef std::map<level_pos, std::string> portal_note_map_type;
// NOTE: The value for colours here is char rather than unsigned char
// because g++ needs it to be char for marshallMap in tags.cc to
// compile properly.
typedef std::map<level_pos, char> portal_vault_colour_map_type;
typedef std::map<level_id, std::string> annotation_map_type;
stair_map_type stair_level;
shop_map_type shops_present;
altar_map_type altars_present;
portal_map_type portals_present;
portal_vault_map_type portal_vaults_present;
portal_note_map_type portal_vault_notes;
portal_vault_colour_map_type portal_vault_colours;
annotation_map_type level_annotations;
annotation_map_type level_exclusions;
static void _seen_altar( god_type god, const coord_def& pos );
static void _seen_staircase(dungeon_feature_type which_staircase,
const coord_def& pos);
static void _seen_other_thing(dungeon_feature_type which_thing,
const coord_def& pos);
void seen_notable_thing(dungeon_feature_type which_thing, const coord_def& pos)
{
// Tell the world first.
dungeon_events.fire_position_event(DET_PLAYER_IN_LOS, pos);
// Don't record in temporary terrain
if (you.level_type != LEVEL_DUNGEON)
return;
const god_type god = feat_altar_god(which_thing);
if (god != GOD_NO_GOD)
_seen_altar( god, pos );
else if (feat_is_branch_stairs( which_thing ))
_seen_staircase( which_thing, pos );
else
_seen_other_thing( which_thing, pos );
}
bool move_notable_thing(const coord_def& orig, const coord_def& dest)
{
ASSERT(in_bounds(orig) && in_bounds(dest));
ASSERT(orig != dest);
ASSERT(!is_notable_terrain(grd(dest)));
if (!is_notable_terrain(grd(orig)))
return (false);
level_pos pos1(level_id::current(), orig);
level_pos pos2(level_id::current(), dest);
shops_present[pos2] = shops_present[pos1];
altars_present[pos2] = altars_present[pos1];
portals_present[pos2] = portals_present[pos1];
portal_vaults_present[pos2] = portal_vaults_present[pos1];
portal_vault_notes[pos2] = portal_vault_notes[pos1];
portal_vault_colours[pos2] = portal_vault_colours[pos1];
unnotice_feature(pos1);
return (true);
}
static dungeon_feature_type portal_to_feature(portal_type p)
{
switch (p)
{
case PORTAL_LABYRINTH: return DNGN_ENTER_LABYRINTH;
case PORTAL_HELL: return DNGN_ENTER_HELL;
case PORTAL_ABYSS: return DNGN_ENTER_ABYSS;
case PORTAL_PANDEMONIUM: return DNGN_ENTER_PANDEMONIUM;
default: return DNGN_FLOOR;
}
}
static const char* portaltype_to_string(portal_type p)
{
switch ( p )
{
case PORTAL_LABYRINTH: return "<cyan>Labyrinth:</cyan>";
case PORTAL_HELL: return "<red>Hell:</red>";
case PORTAL_ABYSS: return "<magenta>Abyss:</magenta>";
case PORTAL_PANDEMONIUM: return "<blue>Pan:</blue>";
default: return "<lightred>Buggy:</lightred>";
}
}
static std::string shoptype_to_string(shop_type s)
{
switch ( s )
{
case SHOP_WEAPON: return "(";
case SHOP_WEAPON_ANTIQUE: return "<yellow>(</yellow>";
case SHOP_ARMOUR: return "[";
case SHOP_ARMOUR_ANTIQUE: return "<yellow>[</yellow>";
case SHOP_GENERAL: return "*";
case SHOP_GENERAL_ANTIQUE: return "<yellow>*</yellow>";
case SHOP_JEWELLERY: return "=";
case SHOP_WAND: return "/";
case SHOP_BOOK: return "+";
case SHOP_FOOD: return "%";
case SHOP_DISTILLERY: return "!";
case SHOP_SCROLL: return "?";
default: return "x";
}
}
static altar_map_type get_notable_altars(const altar_map_type &altars)
{
altar_map_type notable_altars;
for ( altar_map_type::const_iterator na_iter = altars.begin();
na_iter != altars.end(); ++na_iter )
{
if (na_iter->first.id != BRANCH_ECUMENICAL_TEMPLE)
notable_altars[na_iter->first] = na_iter->second;
}
return (notable_altars);
}
inline static std::string place_desc(const level_pos &pos)
{
return "[" + pos.id.describe(false, true) + "] ";
}
inline static std::string altar_description(god_type god)
{
return feature_description( altar_for_god(god) );
}
inline static std::string portal_description(portal_type portal)
{
return feature_description( portal_to_feature(portal) );
}
bool overmap_knows_portal(dungeon_feature_type portal)
{
for ( portal_map_type::const_iterator pl_iter = portals_present.begin();
pl_iter != portals_present.end(); ++pl_iter )
{
if (portal_to_feature(pl_iter->second) == portal)
return (true);
}
return (false);
}
int overmap_knows_num_portals(dungeon_feature_type portal)
{
int num = 0;
for ( portal_map_type::const_iterator pl_iter = portals_present.begin();
pl_iter != portals_present.end(); ++pl_iter )
{
if (portal_to_feature(pl_iter->second) == portal)
num++;
}
return (num);
}
static std::string _portals_description_string()
{
std::string disp;
level_id last_id;
for (int cur_portal = PORTAL_NONE; cur_portal < NUM_PORTALS; ++cur_portal)
{
last_id.depth = 10000;
portal_map_type::const_iterator ci_portals;
for ( ci_portals = portals_present.begin();
ci_portals != portals_present.end();
++ci_portals )
{
// one line per region should be enough, they're all of
// the form D:XX, except for labyrinth portals, of which
// you would need 11 (at least) to have a problem.
if ( ci_portals->second == cur_portal )
{
if ( last_id.depth == 10000 )
disp += portaltype_to_string(ci_portals->second);
if ( ci_portals->first.id == last_id )
disp += '*';
else
{
disp += ' ';
disp += ci_portals->first.id.describe(false, true);
}
last_id = ci_portals->first.id;
}
}
if ( last_id.depth != 10000 )
disp += "\n";
}
return disp;
}
static std::string _portal_vaults_description_string()
{
// Collect all the different portal vault entrance names and then
// display them in alphabetical order.
std::set<std::string> vault_names_set;
std::vector<std::string> vault_names_vec;
portal_vault_map_type::const_iterator ci_portals;
for (ci_portals = portal_vaults_present.begin();
ci_portals != portal_vaults_present.end(); ++ci_portals)
{
vault_names_set.insert(ci_portals->second);
}
for (std::set<std::string>::iterator i = vault_names_set.begin();
i != vault_names_set.end(); ++i)
{
vault_names_vec.push_back(*i);
}
std::sort(vault_names_vec.begin(), vault_names_vec.end() );
std::string disp;
level_id last_id;
for (unsigned int i = 0; i < vault_names_vec.size(); i++)
{
last_id.depth = 10000;
for (ci_portals = portal_vaults_present.begin();
ci_portals != portal_vaults_present.end(); ++ci_portals)
{
// one line per region should be enough, they're all of
// the form D:XX, except for labyrinth portals, of which
// you would need 11 (at least) to have a problem.
if (ci_portals->second == vault_names_vec[i])
{
if (last_id.depth == 10000)
{
unsigned char col =
(unsigned char) portal_vault_colours[ci_portals->first];
disp += '<';
disp += colour_to_str(col) + '>';
disp += vault_names_vec[i];
disp += "</";
disp += colour_to_str(col) + '>';
disp += ':';
}
const level_id lid = ci_portals->first.id;
const level_pos where = ci_portals->first;
if (lid == last_id)
{
if (!portal_vault_notes[where].empty())
{
disp += " (";
disp += portal_vault_notes[where];
disp += ")";
}
else
disp += '*';
}
else
{
disp += ' ';
disp += lid.describe(false, true);
if (!portal_vault_notes[where].empty())
{
disp += " (";
disp += portal_vault_notes[where];
disp += ") ";
}
}
last_id = lid;
}
}
if (last_id.depth != 10000)
disp += "\n";
}
return disp;
}
std::string overview_description_string()
{
char buffer[100];
std::string disp;
bool seen_anything = false;
disp += " <white>Overview of the Dungeon</white>\n" ;
// print branches
int branchcount = 0;
for (int i = 0; i < NUM_BRANCHES; ++i)
{
const branch_type branch = branches[i].id;
if (stair_level.find(branch) != stair_level.end())
{
if (!branchcount)
{
disp += "\n<green>Branches:</green>";
if (crawl_state.need_save || !crawl_state.updating_scores)
{
disp += " (use <white>G</white> to reach them and "
"<white>?/B</white> for more information)";
}
disp += EOL;
seen_anything = true;
}
++branchcount;
level_id lid(branches[i].id, 0);
lid = find_deepest_explored(lid);
// account for the space of the depth. Fortunately it's only an issue
// for the Lair which has a short name anyway.
snprintf(buffer, sizeof buffer,
branches[i].depth < 10 ? "<yellow>%-6s</yellow> <darkgrey>(%d/%d)</darkgrey>: %-7s" :
lid.depth < 10 ? "<yellow>%-5s</yellow> <darkgrey>(%d/%d)</darkgrey>: %-7s" :
"<yellow>%-4s</yellow> <darkgrey>(%d/%d)</darkgrey>: %-7s",
branches[branch].abbrevname,
lid.depth,
branches[i].depth,
stair_level[branch].describe(false, true).c_str());
disp += buffer;
if ( (branchcount % 3) == 0 )
disp += "\n";
else
disp += " ";
}
}
if (branchcount && (branchcount % 4))
disp += "\n";
// remove unworthy altars from the list we show the user. Yeah,
// one more round of map iteration.
const altar_map_type notable_altars = get_notable_altars(altars_present);
// print altars
// we loop through everything a dozen times, oh well
if (!notable_altars.empty())
{
disp += "\n<green>Altars:</green>";
if (crawl_state.need_save || !crawl_state.updating_scores)
{
disp += " (use <white>Ctrl-F \"altar\"</white> to reach them and "
"<white>?/G</white> for information about gods)";
}
disp += EOL;
seen_anything = true;
}
level_id last_id;
std::map<level_pos, god_type>::const_iterator ci_altar;
for (int cur_god = GOD_NO_GOD; cur_god < NUM_GODS; ++cur_god)
{
if (cur_god == you.religion)
continue;
last_id.depth = 10000; // fake depth to be sure we don't match
// GOD_NO_GOD becomes your god
int real_god = (cur_god == GOD_NO_GOD ? you.religion : cur_god);
for (ci_altar = notable_altars.begin();
ci_altar != notable_altars.end(); ++ci_altar)
{
if (ci_altar->second == real_god)
{
if (last_id.depth == 10000)
{
disp += god_name( ci_altar->second, false );
disp += ": ";
disp += ci_altar->first.id.describe(false, true);
}
else
{
if (last_id == ci_altar->first.id)
disp += '*';
else
{
disp += ", ";
disp += ci_altar->first.id.describe(false, true);
}
}
last_id = ci_altar->first.id;
}
}
if (last_id.depth != 10000)
disp += "\n";
}
// print shops
if (!shops_present.empty())
{
disp +="\n<green>Shops:</green>";
if (crawl_state.need_save || !crawl_state.updating_scores)
disp += " (use <white>Ctrl-F \"shop\"</white> to reach them)";
disp += EOL;
seen_anything = true;
}
last_id.depth = 10000;
std::map<level_pos, shop_type>::const_iterator ci_shops;
// There are at most 5 shops per level, plus 7 chars for the level
// name, plus 4 for the spacing; that makes a total of 17
// characters per shop.
const int maxcolumn = get_number_of_cols() - 17;
int column_count = 0;
for (ci_shops = shops_present.begin();
ci_shops != shops_present.end(); ++ci_shops)
{
if (ci_shops->first.id != last_id)
{
if (column_count > maxcolumn)
{
disp += "\n";
column_count = 0;
}
else if (column_count != 0)
{
disp += " ";
column_count += 2;
}
disp += "<brown>";
const std::string loc = ci_shops->first.id.describe(false, true);
disp += loc;
column_count += loc.length();
disp += "</brown>";
disp += ": ";
column_count += 2;
last_id = ci_shops->first.id;
}
disp += shoptype_to_string(ci_shops->second);
++column_count;
}
if (!shops_present.empty())
disp += "\n";
// print portals
if (!portals_present.empty() || !portal_vaults_present.empty())
{
disp += "\n<green>Portals:</green>\n";
seen_anything = true;
}
disp += _portals_description_string();
disp += _portal_vaults_description_string();
if (!seen_anything)
{
if (crawl_state.need_save || !crawl_state.updating_scores)
disp += "You haven't discovered anything interesting yet.";
else
disp += "You didn't discover anything interesting.";
}
bool notes_exist = false;
bool has_notes[NUM_BRANCHES];
for (int i = 0; i < NUM_BRANCHES; ++i)
{
Branch branch = branches[i];
has_notes[i] = false;
for (int depth = 1; depth <= branch.depth; depth++)
{
const level_id li(branch.id, depth);
if (get_level_annotation(li).length() > 0)
{
notes_exist = true;
has_notes[i] = true;
break;
}
}
}
if (notes_exist)
{
disp += "\n\n <white>Level Annotations</white>\n" ;
for (int i = 0; i < NUM_BRANCHES; ++i)
{
if (!has_notes[i])
continue;
Branch branch = branches[i];
disp += "\n<yellow>";
disp += branch.shortname;
disp += "</yellow>\n";
for (int depth = 1; depth <= branch.depth; depth++)
{
const level_id li(branch.id, depth);
if (get_level_annotation(li).length() > 0)
{
char depth_str[3];
sprintf(depth_str, "%2d", depth);
disp += "<white>";
disp += depth_str;
disp += ":</white> ";
disp += get_level_annotation(li);
disp += "\n";
}
}
}
}
return disp.substr(0, disp.find_last_not_of('\n')+1);
}
template <typename Z, typename Key>
inline static bool _find_erase(Z &map, const Key &k)
{
if (map.find(k) != map.end())
{
map.erase(k);
return (true);
}
return (false);
}
static bool _unnotice_portal(const level_pos &pos)
{
return _find_erase(portals_present, pos);
}
static bool _unnotice_portal_vault(const level_pos &pos)
{
(void) _find_erase(portal_vault_colours, pos);
(void) _find_erase(portal_vault_notes, pos);
return _find_erase(portal_vaults_present, pos);
}
static bool _unnotice_altar(const level_pos &pos)
{
return _find_erase(altars_present, pos);
}
static bool _unnotice_shop(const level_pos &pos)
{
return _find_erase(shops_present, pos);
}
static bool _unnotice_stair(const level_pos &pos)
{
const dungeon_feature_type feat = grd(pos.pos);
if (feat_is_branch_stairs(feat))
{
for (int i = 0; i < NUM_BRANCHES; ++i)
{
if (branches[i].entry_stairs == feat)
{
const branch_type br = static_cast<branch_type>(i);
return (_find_erase(stair_level, br));
}
}
}
return (false);
}
bool unnotice_feature(const level_pos &pos)
{
shopping_list.forget_pos(pos);
return (_unnotice_portal(pos)
|| _unnotice_portal_vault(pos)
|| _unnotice_altar(pos)
|| _unnotice_shop(pos)
|| _unnotice_stair(pos));
}
void display_overmap()
{
std::string disp = overview_description_string();
linebreak_string(disp, get_number_of_cols() - 5, get_number_of_cols() - 1);
formatted_scroller(MF_EASY_EXIT | MF_ANYPRINTABLE | MF_NOSELECT,
disp).show();
redraw_screen();
}
static void _seen_staircase( dungeon_feature_type which_staircase,
const coord_def& pos )
{
// which_staircase holds the grid value of the stair, must be converted
// Only handles stairs, not gates or arches
// Don't worry about:
// - stairs returning to dungeon - predictable
// - entrances to the hells - always in vestibule
int i;
for (i = 0; i < NUM_BRANCHES; ++i)
{
if (branches[i].entry_stairs == which_staircase)
{
stair_level[branches[i].id] = level_id::current();
break;
}
}
ASSERT( i != NUM_BRANCHES );
}
// If player has seen an altar; record it.
static void _seen_altar( god_type god, const coord_def& pos )
{
// Can't record in Abyss or Pan.
if (you.level_type != LEVEL_DUNGEON)
return;
level_pos where(level_id::current(), pos);
altars_present[where] = god;
}
void unnotice_altar()
{
const level_pos curpos(level_id::current(), you.pos());
// Hmm, what happens when erasing a nonexistent key directly?
if (altars_present.find(curpos) != altars_present.end())
altars_present.erase(curpos);
}
portal_type feature_to_portal( unsigned char feat )
{
switch (feat)
{
case DNGN_ENTER_LABYRINTH: return PORTAL_LABYRINTH;
case DNGN_ENTER_HELL: return PORTAL_HELL;
case DNGN_ENTER_ABYSS: return PORTAL_ABYSS;
case DNGN_ENTER_PANDEMONIUM: return PORTAL_PANDEMONIUM;
default: return PORTAL_NONE;
}
}
// If player has seen any other thing; record it.
void _seen_other_thing( dungeon_feature_type which_thing, const coord_def& pos )
{
level_pos where(level_id::current(), pos);
switch (which_thing)
{
case DNGN_ENTER_SHOP:
shops_present[where] = static_cast<shop_type>(get_shop(pos)->type);
break;
case DNGN_ENTER_PORTAL_VAULT:
{
std::string portal_name;
portal_name = env.markers.property_at(pos, MAT_ANY, "overmap");
if (portal_name.empty())
portal_name = env.markers.property_at(pos, MAT_ANY, "dstname");
if (portal_name.empty())
portal_name = env.markers.property_at(pos, MAT_ANY, "dst");
if (portal_name.empty())
portal_name = "buggy vault portal";
portal_name = replace_all(portal_name, "_", " ");
portal_vaults_present[where] = uppercase_first(portal_name);
unsigned char col;
if (env.grid_colours(pos) != BLACK)
col = env.grid_colours(pos);
else
col = get_feature_def(which_thing).colour;
portal_vault_colours[where] = (char) element_colour(col, true);
portal_vault_notes[where] =
env.markers.property_at(pos, MAT_ANY, "overmap_note");
break;
}
default:
const portal_type portal = feature_to_portal(which_thing);
if (portal != PORTAL_NONE)
portals_present[where] = portal;
break;
}
}
////////////////////////////////////////////////////////////////////////
void set_level_annotation(std::string str, level_id li)
{
if (str.empty())
{
clear_level_annotation(li);
return;
}
level_annotations[li] = str;
}
void clear_level_annotation(level_id li)
{
level_annotations.erase(li);
}
void set_level_exclusion_annotation(std::string str, level_id li)
{
if (str.empty())
{
clear_level_exclusion_annotation(li);
return;
}
level_exclusions[li] = str;
}
void clear_level_exclusion_annotation(level_id li)
{
level_exclusions.erase(li);
}
std::string get_level_annotation(level_id li, bool skip_excl)
{
annotation_map_type::const_iterator i = level_annotations.find(li);
if (skip_excl)
{
if (i == level_annotations.end())
return "";
return (i->second);
}
annotation_map_type::const_iterator j = level_exclusions.find(li);
if (i == level_annotations.end() && j == level_exclusions.end())
return "";
if (i == level_annotations.end())
return (j->second);
if (j == level_exclusions.end())
return (i->second);
return (i->second + ", " + j->second);
}
bool level_annotation_has(std::string find, level_id li)
{
std::string str = get_level_annotation(li);
return (str.find(find) != std::string::npos);
}
void annotate_level()
{
level_id li = level_id::current();
level_id li2 = level_id::current();
if (feat_is_stair(grd(you.pos())))
{
li2 = level_id::get_next_level_id(you.pos());
if (li2.level_type != LEVEL_DUNGEON || li2.depth <= 0)
li2 = level_id::current();
}
if (you.level_type != LEVEL_DUNGEON && li2.level_type != LEVEL_DUNGEON)
{
mpr("You can't annotate this level.");
return;
}
if (you.level_type != LEVEL_DUNGEON)
li = li2;
else if (li2 != level_id::current())
{
if (yesno("Annotate level on other end of current stairs?", true, 'n'))
li = li2;
}
if (!get_level_annotation(li).empty())
{
mpr("Current level annotation is:", MSGCH_PROMPT);
mpr(get_level_annotation(li, true).c_str() );
}
mpr("Set level annotation to what (using ! forces prompt)? ",
MSGCH_PROMPT);
char buf[77];
if (cancelable_get_line( buf, sizeof(buf) ))
return;
if (buf[0] == 0)
{
if (get_level_annotation(li, true).length() > 0)
{
if (!yesno("Really clear the annotation?", false, 'n'))
return;
}
else
{
canned_msg(MSG_OK);
return;
}
}
set_level_annotation(buf, li);
}