/* * File: overmap.cc * Idea: allow player to make notes about levels. I don't know how * to do this (I expect it will require some kind of dynamic * memory management thing). - LH * Summary: Records location of stairs etc * Written by: Linley Henzell * * Modified for Crawl Reference by $Author$ on $Date$ * * Change History (most recent first): * * <3> 30/7/00 MV Made Over-map full-screen * <2> 8/10/99 BCR Changed Linley's macros * to an enum in overmap.h * <1> 29/8/99 LRH Created */ #include "AppHdr.h" #include "overmap.h" #include #include #include #include #include "externs.h" #include "branch.h" #include "cio.h" #include "dgnevent.h" #include "directn.h" #include "dungeon.h" #include "files.h" #include "menu.h" #include "misc.h" #include "religion.h" #include "shopping.h" #include "state.h" #include "stuff.h" #include "terrain.h" #include "view.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 annotation_map_type; stair_map_type stair_level; shop_map_type shops_present; altar_map_type altars_present; portal_map_type portals_present; annotation_map_type level_annotations; 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, int x, int y ) { // Tell the world first. dungeon_events.fire_position_event(DET_PLAYER_IN_LOS, coord_def(x, y)); // Don't record in temporary terrain if (you.level_type != LEVEL_DUNGEON) return; const coord_def pos(x, y); const god_type god = grid_altar_god(which_thing); if (god != GOD_NO_GOD) seen_altar( god, pos ); else if (grid_is_branch_stairs( which_thing )) seen_staircase( which_thing, pos ); else seen_other_thing( which_thing, pos ); } 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); } 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)"; disp += EOL; seen_anything = true; } ++branchcount; snprintf(buffer, sizeof buffer, "%-6s: %-7s", branches[branch].abbrevname, stair_level[branch].describe(false, true).c_str()); disp += buffer; if ( (branchcount % 4) == 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)"; 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 ) { 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); if ( cur_god == you.religion ) continue; 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; } 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() ) { disp += "\nPortals:\n"; seen_anything = true; } for (int cur_portal = PORTAL_NONE; cur_portal < NUM_PORTALS; ++cur_portal) { last_id.depth = 10000; std::map::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"; } 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_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 (grid_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) { return (unnotice_portal(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(); } 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 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 coord_def pos(you.x_pos, you.y_pos); const level_pos curpos(level_id::current(), 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 ) { if ( you.level_type != LEVEL_DUNGEON ) // can't record in abyss or pan. return; level_pos where(level_id::current(), pos); switch ( which_thing ) { case DNGN_ENTER_SHOP: shops_present[where] = static_cast(get_shop(pos.x, pos.y)->type); break; default: const portal_type portal = feature_to_portal(which_thing); if ( portal != PORTAL_NONE ) portals_present[where] = portal; break; } } // end seen_other_thing() //////////////////////////////////////////////////////////////////////// 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); } std::string get_level_annotation(level_id li) { annotation_map_type::const_iterator i = level_annotations.find(li); if (i == level_annotations.end()) return ""; return (i->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 (is_stair(grd[you.x_pos][you.y_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).length() > 0) { mpr("Current level annotation is:", MSGCH_PROMPT); mpr(get_level_annotation(li).c_str() ); } mpr( "Set level annotation to what? ", MSGCH_PROMPT ); char buf[77]; if (cancelable_get_line( buf, sizeof(buf) )) return; if (strlen(buf) == 0) { if (get_level_annotation(li).length() > 0) { if (!yesno("Really clear the annotation?")) return; } else { canned_msg(MSG_OK); return; } } set_level_annotation(buf, li); }