/* * File: overmap.cc * Summary: Records location of stairs etc * Written by: Linley Henzell */ #include "AppHdr.h" #include "overmap.h" #include #include #include #include #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 stair_map_type; typedef std::map shop_map_type; typedef std::map altar_map_type; typedef std::map portal_map_type; typedef std::map portal_vault_map_type; typedef std::map 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 portal_vault_colour_map_type; typedef std::map 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 "Labyrinth:"; case PORTAL_HELL: return "Hell:"; case PORTAL_ABYSS: return "Abyss:"; case PORTAL_PANDEMONIUM: return "Pan:"; default: return "Buggy:"; } } static std::string shoptype_to_string(shop_type s) { switch ( s ) { case SHOP_WEAPON: return "("; case SHOP_WEAPON_ANTIQUE: return "("; case SHOP_ARMOUR: return "["; case SHOP_ARMOUR_ANTIQUE: return "["; case SHOP_GENERAL: return "*"; case SHOP_GENERAL_ANTIQUE: return "*"; 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 vault_names_set; std::vector 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::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 += ':'; } 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 += " Overview of the Dungeon\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 += "\nBranches:"; if (crawl_state.need_save || !crawl_state.updating_scores) { disp += " (use G to reach them and " "?/B 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 ? "%-6s (%d/%d): %-7s" : lid.depth < 10 ? "%-5s (%d/%d): %-7s" : "%-4s (%d/%d): %-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 += "\nAltars:"; if (crawl_state.need_save || !crawl_state.updating_scores) { disp += " (use Ctrl-F \"altar\" to reach them and " "?/G for information about gods)"; } disp += EOL; seen_anything = true; } level_id last_id; std::map::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 +="\nShops:"; if (crawl_state.need_save || !crawl_state.updating_scores) disp += " (use Ctrl-F \"shop\" to reach them)"; disp += EOL; seen_anything = true; } last_id.depth = 10000; std::map::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 += ""; const std::string loc = ci_shops->first.id.describe(false, true); disp += loc; column_count += loc.length(); disp += ""; 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 += "\nPortals:\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 Level Annotations\n" ; for (int i = 0; i < NUM_BRANCHES; ++i) { if (!has_notes[i]) continue; Branch branch = branches[i]; disp += "\n"; disp += branch.shortname; disp += "\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 += ""; disp += depth_str; disp += ": "; disp += get_level_annotation(li); disp += "\n"; } } } } return disp.substr(0, disp.find_last_not_of('\n')+1); } template 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(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(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); }