summaryrefslogblamecommitdiffstats
path: root/crawl-ref/source/overmap.cc
blob: 744281238388768f3d871343eaae73c8f8ae1756 (plain) (tree)
1
2
3
4
5
6
7
8

                          

                                              


                   
 




                    
                    


                    
                   
                

                   
                     
                    
                    
                          
                    
                  
                 
                     
                     
                  
                  
                
                    
                   
 



                                                         
                                                               
                                                              



                                                                     
                                                            




                                
                                            
                                        
                                                  
                                      
                                     
 




                                                                 
 
                                                                               
 
                            
                                                               
 


                                        
 
                                                     
                          
                                
                                                  
                                            
        
                                              

 























                                                                     
                                                            
 
              
     




                                                           


     
                                                      
 
                
     




                                                                  
     
 
 
                                                  


                












                                                           

     
 





                                                                      
                                                          



















                                                                










                                                                            












                                                                            











































                                                                              

                                                                 















                                                                     

                                                                     



                                                                    
                                                         
             
                                           










                                                                                


                                                             
                                   









                                                           


                                







                                                           
                 
                              

             
                                   




                         
                                         
 


                               
 
                                                                            
 

                        
                                          
     
                                                  
                                                          
         
                             
             

                                                                          



                                                                       
                            

                                     
 
                          








                                                                                                     
                                                 

                                       

                                                                        
                                         
                             



                              
                                         
                     



                                                                             
 

                                                        
                                
     

                                                                  



                                                                              
                    

                             
 
                     
                                                           
                                                                 
     


                                    
                                                                       
 

                                                                        
 

                                                          
         
                                             
             
                                           
                 


                                                                     


                    
                                                      





                                                                         
                 
                                             
             
         
                                   
                         
     
 


                               



                                                                          



                                                            
 
                                                                      




                                                             

                                                     
     
                                          
         
                                         
             


                                 
                                       

                             
                                  
             
                              




                                                                             
                               
 
                         

                              

                                         

                                                     
     

                               
 
                    
                                                                   
     
                                              

                             

                                                
 
                       





                                                                       
 
















































                                                                              
                                 




                 
                                                         

 
                                   
                                                    
 







                                 
                                                  
 
                                             

 
                                                        
 


                                                   

 
                                                 
 
                                            

 
                                                
 
                                           

 
                                                 

                                                   
                                    





                                                                   
                                                      







                                           
                                  




                                          

 


                                                     
                                                                               

                                                                    
                    

 

                                                                  






                                                                           
          
                                      
     
                                                        



                                                              
     
                                
 
 

                                                             
 

                                        
               
 
                                              

                                
 

                     
                                                           




                                                                 
                                                   
 
                 
     




                                                           
     

 

                                                                                
 
                                              
 
                        
     
                         
                                                                           
              




                                 
                                                                       
















                                                                           


                                                                  


              

                                                                  
                                  

                                            
     
 


                                                                        
                                                       
 
                    












                                        
















                                                                 


                                                                       










                                                                      

                  

                                     
                                    


                                          

 
                                                        










                                                 
                                      
















                                                                           
                                                                               


                     
                                          

                                                          
                                                     

     

                                                                 

                 

                                                
 
                    
     
                                                        
         
                                                                   










                                  
/*
 *  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);
}