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



                              


                   
 



                    
                  


                    
                    

                     
                   
                     
                
                   
                     
                  
                  
                     
                  
                  

                     
                  
                    
                  
                 
                    
                     
                     
                 
                  
                   
                     
                     
                    
                    
                     

                     
                  
                  
                
                    
                      
                     
                 
                     
 
                               

                                  
 
                                 
                    
                       

      



                   


                  
 



                 






                         




                                                             

                     
      
 

                     
      

  

                                                 
                                                                             
 
                   





                                              
                                                







                                                        







                                                                  

                                             
                                                            
                                                                         

                  
 


                            
                       
                                      
     

                                                  
         





                                  

                                                     
                                                             
                  























                                                                      







                                               
                                                                           
                                         


        
                                                                          







                            
                     
                                
 









                                                                              



                                                                             
                                                                     

                                                                         
 


                                                           
 
                                          
                                                                             

                                                               
                          
















                                                                 













                                                      
                             


                                                    


                               


                              



                                       
         
                   
     


                       


                 
                                                                    
 
                                        

                     
                       
                          
                         
                          
                          
                             
                           
        

                            
 

                                                             
                  
                                    
                                            
                             
 
                      
                                     
                                                     
                                       

                                        
                                     










                                         
                                                                   
 
                                          
                                                                         


                                                                             
     
                                
                                          
     

                                 

 
                              

                           






                                        
                                                                         
                                 
               
 
                                                               
 




                                                            
                                
     
                           


               
                                             
 
 
                                
 





                                          
                           
                                              
 
                  
                          
                                   
        
     

                                         
                                       
     
 
                        
     
                           


               

                                                   
                                                       


                                



                                          
                            
                                                   
 
                          
              
                           
     
                                                      

                            
         

                                 
         

                                         

     
                              
                                               

                                           
                                                    


                                                                      

                                                                        


                                                                 
 
                                
                                                      
 
 
                                 
 
                                                            
 
                        
     
                                                       


               
                      
                        
     
                                                                               
                                                
 
                                   
 
                                         
                                                    

                                                   
         
                               

                   
                                
         
                                      

                                                             
                                               





                                                   
                                       
                             



                                        


         



                                                              
 
                                                                 
 
                                                                          
 
                               
 
                        
     
                            


               
                                              
                         
     
                                                   


               
                                     
                                 
                                                     
     
                                       
         





                                                            

         
 




                                                            
 
                                           


                                                                        
 
 
                  
 
                            
                                                    
     
                                           
                                           









                                                    



                                                          
                      
 
                                   






                                                                          
         
                                                            
                                            

                                                                             
         
                                  
                                       
            
                               
 
                                   
                                                          

                                                          
     
 


                         

                            

                                                   
                                              
                                              
 

                     
 




                                                      
 





                                                                           
         
                                                               

                                                                        
                                                              
         
            
                               
 
                                   
                                                          

                                                          
     
 








                                                             
                                         
                       


                        
                                                                 
                                     

                                                                         



                                                                   
                                         

                                                     
            
                                         
                                                   

     
                                                  
 
                                
                                





                                                                    
                   


                                     
 
                              
                                 
                                                   


                                                           
                                                             
                                         

                                                                     
         
            
                                  
 
                                   
                                                       

                                                      

     
                                                        
                             
 
                                             
 
                          
                   
     

                                                     
                              








                                                
     
        
     
                                                            
                                     
                                                                    
                                      


                               
                                                   
 

                                                  
 
                                                       
 






                      
                                      
                                                        
                                                         






                                                                 

                                       

                                                         
                                                      
                
                                                          
      
                                     
         
                                                            

                                                          
                                                                
                                                                              

                                                                
                                                          
                                      


                                                   
                                          
                                      
                                                 
                                                 

                                                                     
                                        
                                                           
                                                                 
                                                             
                                                    
                                               

      
 
                                      
                                        


                                                      
 
 
 




                                                              
                                                                  
                                                                      



                                
 
                                                                             
                                                         
                                                   

                                                         
 


                                           

                                                                  
         


                                                            




                                           
                                
                                                
                                      


                               
                                                                  



                                                                            



                         








                          
                                       
                                        
                                        
                                        
                                        



                                        


                      
                                                                      
 

                                                          
 
                           
                       
 

                                                             
                                                   

 

                                                                    
                                                                          
 

                                                          
 
                           
                       
 

                                               
 

                                   

                                                                 
                                                       
     
                                       
 
 


                            


                                                          
         

                                  


                                 




                          
 


                                                    
 







                                                                      

         



                                  
 
                           
                                                                          
                
                                                                              
 







                                                                        
 











                                     

                                                                     
 

                                                                          

                                
                             
                                     
                             
 

                                                                             

                                                            
                             
                                                                 
                              







                                                                    
                                          
 
                         

 
                                                                         




                                          
                                                               









                                                                  
                                                 





                                         
                      

 
                                               


                                   
                                                   

                                                      





                                             
                                                  













                                                               









                                                                         

 
                                                              
 

                                                                            
 
 
                                                            
 




                                          
                                                                   








                             
 
 
                                                           



                                                                 

















                                                               
                                                              
 


                                              



                                                           




                                                          









                                                          








                                                              

                                                         
                                                           







                                                               
                                                            



                                                               






                                                                             
     
 
 


                                                                
                                         





                                               
                               
     
                                                                   
                                     
     
                                    
     
                                                                        
                                     
     
                                 
     

                                                                        
     
        
     

                                                                    
     






                    
                                                      
         

                                                                       
         


     








                                                                             









                                                  
                                                  










                                               







                                                                      




                                             
                                             








                                                       
                                                      








                                                      




























                                                                             

                                                              
 
                      
                                    
 

                                               
                                                   
 

                                                    
     

                                   

                                                                             
         







                                                     
 
                                           








                                                              

                                                                        
                                                                       
                                       

                                                                        
         


                               

                                                      












                                               
                                                       

                          
             
                
                                                   
 
                                         
                           
         


                                                

                                                       

                              
                                                         
                                                                              








                                                                                  

                                                                                   



                                              

                                                                                   


                                                                                     
                                                                 
                 

                                                
             
 

                                                  
                                            
         
     
 

                     

                                         

                        
                     

                           


                  

                                                                               


                         
                      






                                                                      
                                                                          


                                                                   
                                                         




                                                                        




                                                                      
 














                                                                  

     














                                                             

                                                                       
             
                                                                 





                                                 
 
                  

 
                                                                    
 

                 
             
                      
 
                             
                                               
                                                                            
                                                          




                                 

                         

                                     



                                     
                                

                                
                                
                                
 
               

             

                                                                             


                                     
                          

              


                                     
              



                               



                                 



                              

                             


                                                                           

                           

                           

                                  

                              
              











                                  

              
            
                                    
                      


                       


                           
     


                                                                    
 



                                                                    
                                        

                           
 



                                        
 
                          
         
                                                              
                                           

                           

     

                                                             
 


                           
                             
                                            



                                                     
                          
                               




                                      
                                               

                                                      
                        
                                   
                            
                                      
        
                                                 
 


                           



                             

                                                       
                                
         

                                

                                                    
                                
         

            
                                          

                                           






                                  


                                                             


            


                                                                          
                                   



                                  
                                                  
 





                                                                  
                                             




                           

                                                    
 
                                                             
                          
               
                                                        



             

                                                                 
                                              
                                     
                                     
                                  




                                                                   
                             


                       



                                                                    
 

                                                                             
                                   
                                          
                                     
 
                                                                      
                                             
 















                                                                




                                                                        
         

                                                                      
            



                                                                         










                                                                          
                                            
         
























                                                                 



                                                            

                                    


                                                     
                                           
             
                                 
                            
             





                   
                                     


               
             





                                             

              

             
                           
                          


                             
                                                                

                        

                             

                      
 
                          
 
                  



















                                  
                         
                  
     
              

 






















                                                                           
                                                 






                                                                         
     

                                                                         
     
 




                                                                            


                                                                         
 
                                
 




                                                              
                             
 

                                                                              
                                                      






                                                                           
 
                    
     
                                                       
                                                  
                                    




                                     

                                                    

                                        
                                      
                                  
                                          
                                                
                                     
                                      
                                            
                                            
                             


                                        
                                             
                                         
                                             


                           
                                    


                                                
                                                   




                                                    
                                         

                                        

                                                        
                                         



                                                

                                                   
                                             
 

                                                                      
                                           

                                                          
                                                             
                                         
 

                                                                    
     
 
                                               
                                                                          
 
                    
     
                                                        
         
                                                                  


                                                                                
                          
















                                                                         
                    

                         
                                    
                                         

                                                                              
 
                                      

                                                                     
 
                                   

                       
     
 
               

                                        
                    

                             

 
                                                  
 
                                                                           

                                                       
     
                                  
                                           


                                                      

 

                         
                                                     

 
                           
 



                                                                   

                                                         
                                                            
 

                                         
                                                                           



                                        





                                                                          
 
 




                                                      

                                                                 


                                                               



                                                      











                                                           














                                                                               
                                             
















































































                                                                               
                                                         
 


                            

                                                     











                                                                                                            




                                                         

                                                    


                       











                                                                           


                                                           
                                              


                       







                                                                            
 

                       
                                                           

                                                                   
                                              

                       


                                                    
                                             
 










                                                                                          
                                                                                                          






















                                                                                                    








                                                                                 
                       
              


                                             



                                                                                  


                                                                                  


                                                                        

                                                                               


                       







                                                                               

                                                                              









                                                                                       

                       





                                                                            


                       




                                                                                       
                                                                     







                                                                                


                       
                                                        

                                             





                                                                    

                                                                           

                                                                              




                                                                                      











                                                                                     
     



                                                                           







                                                                                     

                                                                   



                                                                               



                                                                               













                                                                          

                                                                      
 







                                                                   
 

                       
                                    
                                             
















                                                                             

 
                                                               
 









































                                                                                                          

                                                            











                                                                             

                                                      







                                                                              

                       

                                                 
                                             
 













                                                                                               

                       
                                                       



                                                                        

                                                                     
                                                 
 
 
                                                     
 





                                                                   
                               
                                           
        
                                     
 
                                                                    







                         
             

                                               
                
                                

                                                                   
 
                         
                                                        
                                                                  
                                                           
                                                                     
                                                                  

                                                                   
                                                                  

                                                         


                                                          
                                                                        


                                                                        
                                                                       
                                                                            
                                                           
                                                                    
                                                                    



                                                                 
                                                                               
                                                                  

                                                                     
                                                                          

                                                                 

                                   
                         
                                                                    
                                                              







                                                                               
                                                        
                                                      
                                                                           





                                                                             
                                                              
                                                                       
                                                                    
                                                                       

                                                                         

                                                                     
                                                        
                                                                    





                                                                       
                                                           
                                                                   
                                                                        
                           


                                                                      
                                                                
                                   
 



                                                                      
 
      
/*
 *  File:       command.cc
 *  Summary:    Misc commands.
 *  Written by: Linley Henzell
 */

#include "AppHdr.h"

#include "command.h"

#include <stdio.h>
#include <string.h>
#include <sstream>
#include <ctype.h>

#include "externs.h"
#include "options.h"

#include "abl-show.h"
#include "branch.h"
#include "chardump.h"
#include "cio.h"
#include "colour.h"
#include "database.h"
#include "debug.h"
#include "decks.h"
#include "describe.h"
#include "files.h"
#include "ghost.h"
#include "invent.h"
#include "itemname.h"
#include "items.h"
#include "libutil.h"
#include "macro.h"
#include "menu.h"
#include "message.h"
#include "mon-pick.h"
#include "mon-util.h"
#include "ouch.h"
#include "place.h"
#include "player.h"
#include "religion.h"
#include "showsymb.h"
#include "skills2.h"
#include "species.h"
#include "spl-book.h"
#include "spl-cast.h"
#include "spl-util.h"
#include "state.h"
#include "stuff.h"
#include "env.h"
#include "terrain.h"
#include "transform.h"
#include "tutorial.h"
#include "view.h"
#include "viewchar.h"

static void _adjust_item(void);
static void _adjust_spells(void);
static void _adjust_ability(void);

static const char *features[] = {
#ifdef CLUA_BINDINGS
    "Lua user scripts",
#endif

#ifdef USE_TILE
    "Tile support",
#endif

#ifdef WIZARD
    "Wizard mode",
#endif

#ifdef DEBUG
    "Debug mode",
#endif

#if defined(REGEX_POSIX)
    "POSIX regexps",
#elif defined(REGEX_PCRE)
    "PCRE regexps",
#else
    "Glob patterns",
#endif

#if defined(SOUND_PLAY_COMMAND) || defined(WINMM_PLAY_SOUNDS)
    "Sound support",
#endif

#ifdef DGL_MILESTONES
    "Milestones",
#endif

#ifdef UNICODE_GLYPHS
    "Unicode glyphs",
#endif
};

static std::string _get_version_information(void)
{
    std::string result  = "This is <w>" CRAWL " " + Version::Long() + "</w>";

    result += "\n";

    return (result);
}

static std::string _get_version_features(void)
{
    std::string result  = "<w>Features</w>" EOL;
                result += "--------" EOL;

    for (unsigned int i = 0; i < ARRAYSZ(features); i++)
    {
        result += " * ";
        result += features[i];
        result += EOL;
    }

    return (result);
}

static void _add_file_to_scroller(FILE* fp, formatted_scroller& m,
                                  int first_hotkey  = 0,
                                  bool auto_hotkeys = false);

static std::string _get_version_changes(void)
{
    // Attempts to print "Highlights" of the latest version.
    FILE* fp = fopen(datafile_path("changelog.txt", false).c_str(), "r");
    if (!fp)
        return "";

    std::string result = "";
    std::string help;
    char buf[200];
    bool start = false;
    while (fgets(buf, sizeof buf, fp))
    {
        // Remove trailing spaces.
        for (int i = strlen(buf) - 1; i >= 0; i++)
        {
            if (isspace( buf[i] ))
                buf[i] = 0;
            else
                break;
        }
        help = buf;

        // Give up if you encounter an older version.
        if (help.find("Stone Soup 0.5") != std::string::npos)
            break;

        if (help.find("Highlights") != std::string::npos)
        {
            // Highlight the Highlights, so to speak.
            std::string text  = "<w>";
                        text += buf;
                        text += "</w>";
                        text += EOL;
            result += text;
            // And start printing from now on.
            start = true;
        }
        else if (!start)
            continue;
        else if (buf[0] == 0)
        {
            // Stop reading and copying text with the first empty line
            // following the Highlights section.
            break;
        }
        else
        {
            result += buf;
            result += EOL;
        }
    }
    fclose(fp);

    // Did we ever get to print the Highlights?
    if (start)
    {
        result += EOL;
        result += "For a more complete list of changes, see changelog.txt "
                  "in the /docs folder.";
    }
    else
    {
        result += "For a list of changes, see changelog.txt in the /docs "
                  "folder.";
    }

    result += "\n\n";

    return (result);
}

//#define DEBUG_FILES
static void _print_version(void)
{
    formatted_scroller cmd_version;

    // Set flags.
    int flags = MF_NOSELECT | MF_ALWAYS_SHOW_MORE | MF_NOWRAP | MF_EASY_EXIT;
    cmd_version.set_flags(flags, false);
    cmd_version.set_tag("version");

    // FIXME: Allow for hiding Page down when at the end of the listing, ditto
    // for page up at start of listing.
    cmd_version.set_more( formatted_string::parse_string(
#ifdef USE_TILE
                              "<cyan>[ +/L-click : Page down.   - : Page up."
                              "           Esc/R-click exits.]"));
#else
                              "<cyan>[ + : Page down.   - : Page up."
                              "                           Esc exits.]"));
#endif

    cmd_version.add_text(_get_version_information(), true);
    cmd_version.add_text(_get_version_features(), true);
    cmd_version.add_text(_get_version_changes(), true);

    std::string fname = "key_changes.txt";
    // Read in information about changes in comparison to the latest version.
    FILE* fp = fopen(datafile_path(fname, false).c_str(), "r");

#if defined(TARGET_OS_DOS)
    if (!fp)
    {
 #ifdef DEBUG_FILES
        mprf(MSGCH_DIAGNOSTICS, "File '%s' could not be opened.",
             fname.c_str());
 #endif
        if (get_dos_compatible_file_name(&fname))
        {
 #ifdef DEBUG_FILES
            mprf(MSGCH_DIAGNOSTICS,
                 "Attempting to open file '%s'", fname.c_str());
 #endif
            fp = fopen(datafile_path(fname, false).c_str(), "r");
        }
    }
#endif

    if (fp)
    {
        char buf[200];
        bool first = true;
        while (fgets(buf, sizeof buf, fp))
        {
            // Remove trailing spaces.
            for (int i = strlen(buf) - 1; i >= 0; i++)
            {
                if (isspace( buf[i] ))
                    buf[i] = 0;
                else
                    break;
            }
            std::string text;
            if (first)
            {
                // Highlight the first line (title).
                text  = "<w>";
                text += buf;
                text += "</w>";
                first = false;
            }
            else
                text = buf;

            text += EOL;
            cmd_version.add_text(text);
        }
        fclose(fp);
    }

    cmd_version.show();
}

void adjust(void)
{
    mpr("Adjust (i)tems, (s)pells, or (a)bilities? ", MSGCH_PROMPT);

    const int keyin = tolower(get_ch());

    if (keyin == 'i')
        _adjust_item();
    else if (keyin == 's')
        _adjust_spells();
    else if (keyin == 'a')
        _adjust_ability();
    else if (keyin == ESCAPE)
        canned_msg(MSG_OK);
    else
        canned_msg(MSG_HUH);
}

void swap_inv_slots(int from_slot, int to_slot, bool verbose)
{
    // Swap items.
    item_def tmp = you.inv[to_slot];
    you.inv[to_slot]   = you.inv[from_slot];
    you.inv[from_slot] = tmp;

    // Slot switching.
    tmp.slot = you.inv[to_slot].slot;
    you.inv[to_slot].slot  = you.inv[from_slot].slot;
    you.inv[from_slot].slot = tmp.slot;

    you.inv[from_slot].link = from_slot;
    you.inv[to_slot].link  = to_slot;

    for (int i = 0; i < NUM_EQUIP; i++)
    {
        if (you.equip[i] == from_slot)
            you.equip[i] = to_slot;
        else if (you.equip[i] == to_slot)
            you.equip[i] = from_slot;
    }

    if (verbose)
    {
        mpr( you.inv[to_slot].name(DESC_INVENTORY_EQUIP).c_str() );

        if (you.inv[from_slot].is_valid())
            mpr( you.inv[from_slot].name(DESC_INVENTORY_EQUIP).c_str() );
    }

    if (to_slot == you.equip[EQ_WEAPON] || from_slot == you.equip[EQ_WEAPON])
    {
        you.wield_change = true;
        you.m_quiver->on_weapon_changed();
    }
    else // just to make sure
        you.redraw_quiver = true;
}

static void _adjust_item(void)
{
    int from_slot, to_slot;

    if (inv_count() < 1)
    {
        canned_msg(MSG_NOTHING_CARRIED);
        return;
    }

    from_slot = prompt_invent_item("Adjust which item?", MT_INVLIST, -1);
    if (prompt_failed(from_slot))
        return;

    mpr(you.inv[from_slot].name(DESC_INVENTORY_EQUIP).c_str());

    to_slot = prompt_invent_item("Adjust to which letter? ",
                                 MT_INVLIST,
                                 -1,
                                 false,
                                 false);
    if (to_slot == PROMPT_ABORT)
    {
        canned_msg(MSG_OK);
        return;
    }

    swap_inv_slots(from_slot, to_slot, true);
}

static void _adjust_spells(void)
{
    if (!you.spell_no)
    {
        mpr("You don't know any spells.");
        return;
    }

    // Select starting slot
    mpr("Adjust which spell? ", MSGCH_PROMPT);

    int keyin = 0;
    if (Options.auto_list)
        keyin = list_spells(false);
    else
    {
        keyin = get_ch();
        if (keyin == '?' || keyin == '*')
            keyin = list_spells(false);
    }

    if (!isalpha(keyin))
    {
        canned_msg(MSG_OK);
        return;
    }

    const int input_1 = keyin;
    const int index_1 = letter_to_index( input_1 );
    spell_type spell  = get_spell_by_letter( input_1 );

    if (spell == SPELL_NO_SPELL)
    {
        mpr("You don't know that spell.");
        return;
    }

    // Print targeted spell.
    mprf( "%c - %s", keyin, spell_title( spell ) );

    // Select target slot.
    keyin = 0;
    while (!isalpha(keyin))
    {
        mpr("Adjust to which letter? ", MSGCH_PROMPT);
        keyin = get_ch();
        if (keyin == ESCAPE)
        {
            canned_msg( MSG_OK );
            return;
        }
        if (keyin == '?' || keyin == '*')
            keyin = list_spells();
    }

    const int input_2 = keyin;
    const int index_2 = letter_to_index(keyin);

    // swap references in the letter table:
    const int tmp = you.spell_letter_table[index_2];
    you.spell_letter_table[index_2] = you.spell_letter_table[index_1];
    you.spell_letter_table[index_1] = tmp;

    // print out spell in new slot
    mprf("%c - %s", input_2, spell_title(get_spell_by_letter(input_2)));

    // print out other spell if one was involved (now at input_1)
    spell = get_spell_by_letter( input_1 );

    if (spell != SPELL_NO_SPELL)
        mprf("%c - %s", input_1, spell_title(spell) );
}

static void _adjust_ability(void)
{
    const std::vector<talent> talents = your_talents(false);

    if (talents.empty())
    {
        mpr("You don't currently have any abilities.");
        return;
    }

    int selected = -1;
    while (selected < 0)
    {
        msg::streams(MSGCH_PROMPT) << "Adjust which ability? (? or * to list) "
                                   << std::endl;

        const int keyin = get_ch();

        if (keyin == '?' || keyin == '*')
            selected = choose_ability_menu(talents);
        else if (keyin == ESCAPE || keyin == ' '
                 || keyin == '\r' || keyin == '\n')
        {
            canned_msg(MSG_OK);
            return;
        }
        else if (isalpha(keyin))
        {
            // Try to find the hotkey.
            for (unsigned int i = 0; i < talents.size(); ++i)
            {
                if (talents[i].hotkey == keyin)
                {
                    selected = static_cast<int>(i);
                    break;
                }
            }

            // If we can't, cancel out.
            if (selected < 0)
            {
                mpr("No such ability.");
                return;
            }
        }
    }

    msg::stream << static_cast<char>(talents[selected].hotkey)
                << " - "
                << ability_name(talents[selected].which)
                << std::endl;

    const int index1 = letter_to_index(talents[selected].hotkey);

    msg::streams(MSGCH_PROMPT) << "Adjust to which letter? " << std::endl;

    const int keyin = get_ch();

    if (!isalpha(keyin))
    {
        canned_msg(MSG_HUH);
        return;
    }

    const int index2 = letter_to_index(keyin);
    if (index1 == index2)
    {
        mpr("That would be singularly pointless.");
        return;
    }

    // See if we moved something out.
    bool printed_message = false;
    for (unsigned int i = 0; i < talents.size(); ++i)
    {
        if (talents[i].hotkey == keyin)
        {
            msg::stream << "Swapping with: "
                        << static_cast<char>(keyin) << " - "
                        << ability_name(talents[i].which)
                        << std::endl;
            printed_message = true;
            break;
        }
    }

    if (!printed_message)
        msg::stream << "Moving to: "
                    << static_cast<char>(keyin) << " - "
                    << ability_name(talents[selected].which)
                    << std::endl;

    // Swap references in the letter table.
    ability_type tmp = you.ability_letter_table[index2];
    you.ability_letter_table[index2] = you.ability_letter_table[index1];
    you.ability_letter_table[index1] = tmp;
}

void list_armour()
{
    std::ostringstream estr;
    for (int i = EQ_CLOAK; i <= EQ_BODY_ARMOUR; i++)
    {
        const int armour_id = you.equip[i];
        int       colour    = MSGCOL_BLACK;

        estr.str("");
        estr.clear();

        estr << ((i == EQ_CLOAK)       ? "Cloak  " :
                 (i == EQ_HELMET)      ? "Helmet " :
                 (i == EQ_GLOVES)      ? "Gloves " :
                 (i == EQ_SHIELD)      ? "Shield " :
                 (i == EQ_BODY_ARMOUR) ? "Armour " :
                 (i == EQ_BOOTS) ?
                 ((you.species == SP_CENTAUR
                   || you.species == SP_NAGA) ? "Barding"
                                              : "Boots  ")
                                 : "unknown")
             << " : ";

        if (!you_can_wear(i, true))
            estr << "    (unavailable)";
        else if (armour_id != -1 && !you_tran_can_wear(you.inv[armour_id])
                 || !you_tran_can_wear(i))
        {
            estr << "    (currently unavailable)";
        }
        else if (armour_id != -1)
        {
            estr << you.inv[armour_id].name(DESC_INVENTORY);
            colour = menu_colour(estr.str(),
                                 menu_colour_item_prefix(you.inv[armour_id]),
                                 "equip");
        }
        else if (!you_can_wear(i))
            estr << "    (restricted)";
        else
            estr << "    none";

        if (colour == MSGCOL_BLACK)
            colour = menu_colour(estr.str(), "", "equip");

        mpr( estr.str().c_str(), MSGCH_EQUIPMENT, colour);
    }
}

void list_jewellery(void)
{
    std::ostringstream jstr;

    for (int i = EQ_LEFT_RING; i <= EQ_AMULET; i++)
    {
        const int jewellery_id = you.equip[i];
        int       colour       = MSGCOL_BLACK;

        jstr.str("");
        jstr.clear();

        jstr << ((i == EQ_LEFT_RING)  ? "Left ring " :
                 (i == EQ_RIGHT_RING) ? "Right ring" :
                 (i == EQ_AMULET)     ? "Amulet    "
                                      : "unknown   ")
             << " : ";

        if (jewellery_id != -1 && !you_tran_can_wear(you.inv[jewellery_id])
            || !you_tran_can_wear(i))
        {
            jstr << "    (currently unavailable)";
        }
        else if (jewellery_id != -1)
        {
            jstr << you.inv[jewellery_id].name(DESC_INVENTORY);
            std::string
                prefix = menu_colour_item_prefix(you.inv[jewellery_id]);
            colour = menu_colour(jstr.str(), prefix, "equip");
        }
        else
            jstr << "    none";

        if (colour == MSGCOL_BLACK)
            colour = menu_colour(jstr.str(), "", "equip");

        mpr( jstr.str().c_str(), MSGCH_EQUIPMENT, colour);
    }
}

void list_weapons(void)
{
    const int weapon_id = you.equip[EQ_WEAPON];

    // Output the current weapon
    //
    // Yes, this is already on the screen... I'm outputing it
    // for completeness and to avoid confusion.
    std::string wstring = "Current   : ";
    int         colour;

    if (weapon_id != -1)
    {
        wstring += you.inv[weapon_id].name(DESC_INVENTORY_EQUIP);
        colour = menu_colour(wstring,
                             menu_colour_item_prefix(you.inv[weapon_id]),
                             "equip");
    }
    else
    {
        if (you.attribute[ATTR_TRANSFORMATION] == TRAN_BLADE_HANDS)
            wstring += "    blade hands";
        else if (!you_tran_can_wear(EQ_WEAPON))
            wstring += "    (currently unavailable)";
        else
            wstring += "    empty hands";
        colour = menu_colour(wstring, "", "equip");
    }

    mpr(wstring.c_str(), MSGCH_EQUIPMENT, colour);

    // Print out the swap slots.
    for (int i = 0; i <= 1; ++i)
    {
        // We'll avoid repeating the current weapon for these slots,
        // in order to keep things clean.
        if (weapon_id == i)
            continue;

        if (i == 0)
            wstring = "Primary   : ";
        else
            wstring = "Secondary : ";

        colour = MSGCOL_BLACK;
        if (you.inv[i].is_valid()
            && (you.inv[i].base_type == OBJ_WEAPONS
                || you.inv[i].base_type == OBJ_STAVES
                || you.inv[i].base_type == OBJ_MISCELLANY))
        {
            wstring += you.inv[i].name(DESC_INVENTORY_EQUIP);
            colour = menu_colour(wstring,
                                 menu_colour_item_prefix(you.inv[i]),
                                 "equip");
        }
        else
            wstring += "    none";

        if (colour == MSGCOL_BLACK)
            colour = menu_colour(wstring, "", "equip");

        mpr(wstring.c_str(), MSGCH_EQUIPMENT, colour);
    }

    // Now we print out the current default fire weapon.
    wstring = "Firing    : ";

    int slot = you.m_quiver->get_fire_item();

    colour = MSGCOL_BLACK;
    if (slot == -1)
    {
        const item_def* item;
        you.m_quiver->get_desired_item(&item, &slot);
        if (!item->is_valid())
        {
            wstring += "    nothing";
        }
        else
        {
            wstring += "  - ";
            wstring += item->name(DESC_NOCAP_A);
            wstring += " (empty)";
        }
    }
    else
    {
        wstring += you.inv[slot].name(DESC_INVENTORY_EQUIP);
        colour = menu_colour(wstring,
                             menu_colour_item_prefix(you.inv[slot]),
                             "equip");
    }

    if (colour == MSGCOL_BLACK)
        colour = menu_colour(wstring, "", "equip");

    mpr(wstring.c_str(), MSGCH_EQUIPMENT, colour);
}

static bool _cmdhelp_textfilter(const std::string &tag)
{
#ifdef WIZARD
    if (tag == "wiz")
        return (true);
#endif
    return (false);
}

static const char *targetting_help_1 =
    "<h>Examine surroundings ('<w>x</w><h>' in main):\n"
    "<w>Esc</w> : cancel (also <w>Space</w>, <w>x</w>)\n"
    "<w>Dir.</w>: move cursor in that direction\n"
    "<w>.</w> : move to cursor (also <w>Enter</w>, <w>Del</w>)\n"
    "<w>v</w> : describe monster under cursor\n"
    "<w>+</w> : cycle monsters forward (also <w>=</w>)\n"
    "<w>-</w> : cycle monsters backward\n"
    "<w>*</w> : cycle objects forward\n"
    "<w>/</w> : cycle objects backward (also <w>;</w>)\n"
    "<w>^</w> : cycle through traps\n"
    "<w>_</w> : cycle through altars\n"
    "<w><<</w>/<w>></w> : cycle through up/down stairs\n"
    "<w>Tab</w> : cycle through shops and portals\n"
    "<w>Ctrl-F</w> : change monster targetting mode\n"
#ifndef USE_TILE
    "<w>Ctrl-L</w> : toggle targetting via monster list\n"
#endif
    "<w>Ctrl-P</w> : repeat prompt\n"
    " \n"
    "<h>targetting (zapping wands, casting spells, etc.):\n"
    "The keys from examine surroundings also work here.\n"
    "In addition, you can use:\n"
    "<w>Enter</w> : fire at target (<w>Space</w>, <w>Del</w>)\n"
    "<w>.</w> : fire at target and stop there (may hit submerged creatures)\n"
    "<w>!</w> : fire at target, ignoring range\n"
    "<w>@</w> : fire at target and stop there, ignoring range\n"
    "<w>p</w> : fire at Previous target (also <w>f</w>)\n"
    "<w>:</w> : show/hide beam path\n"
    "<w>Shift-Dir</w> : shoot straight-line beam\n"
#ifdef WIZARD
    " \n"
    "<h>Wizard targetting commands:</h>\n"
    "<w>g</w>: give item to monster\n"
    "<w>s</w>: force monster to shout or speak\n"
    "<w>S</w>: make monster a summoned monster\n"
    "<w>F</w>: cycle monster friendly/good neutral/neutral/hostile\n"
    "<w>P</w>: apply divine blessing to monster\n"
    "<w>m</w>: move monster or player\n"
    "<w>M</w>: cause spell miscast for monster or player\n"
    "<w>w</w>: calculate shortest path to any point on the map\n"
    "<w>D</w>: get debugging information about the monster\n"
    "<w>~</w>: polymorph monster to specific type\n"
    "<w>,</w>: bring down the monster to 1hp\n"
#endif
;

static const char *targetting_help_2 =
    "<h>Firing or throwing a missile:\n"
    "<w>(</w> : cycle to next suitable missile.\n"
    "<w>)</w> : cycle to previous suitable missile.\n"
    "<w>i</w> : choose from inventory.\n"
;


// Add the contents of the file fp to the scroller menu m.
// If first_hotkey is nonzero, that will be the hotkey for the
// start of the contents of the file.
// If auto_hotkeys is true, the function will try to identify
// sections and add appropriate hotkeys.
static void _add_file_to_scroller(FILE* fp, formatted_scroller& m,
                                  int first_hotkey, bool auto_hotkeys)
{
    bool next_is_hotkey = false;
    bool is_first = true;
    char buf[200];

    // Bracket with MEL_TITLES, so that you won't scroll into it or above it.
    m.add_entry(new MenuEntry(std::string(), MEL_TITLE));
    for (int i = 0; i < get_number_of_lines(); ++i)
        m.add_entry(new MenuEntry(std::string()));
    m.add_entry(new MenuEntry(std::string(), MEL_TITLE));

    while (fgets(buf, sizeof buf, fp))
    {
        MenuEntry* me = new MenuEntry(buf);
        if (next_is_hotkey && (isupper(buf[0]) || isdigit(buf[0]))
            || is_first && first_hotkey)
        {
            int hotkey = (is_first ? first_hotkey : buf[0]);
            if (!is_first && buf[0] == 'X'
                && strlen(buf) >= 3 && isdigit(buf[2]))
            {
                // X.# is hotkeyed to the #
                hotkey = buf[2];
            }
            me->add_hotkey(hotkey);
            if (isupper(hotkey))
                me->add_hotkey(tolower(hotkey));
            me->level  = MEL_SUBTITLE;
            me->colour = WHITE;
        }
        m.add_entry(me);
        // FIXME: There must be a better way to identify sections!
        next_is_hotkey =
            (auto_hotkeys
                && (strstr(buf, "------------------------------------------"
                                "------------------------------") == buf));
        is_first = false;
    }
}


struct help_file
{
    const char* name;
    int hotkey;
    bool auto_hotkey;
};

help_file help_files[] = {
    { "crawl_manual.txt",  '*', true },
    { "../README.txt",     '!', false },
    { "aptitudes.txt",     '%', false },
    { "quickstart.txt",    '^', false },
    { "macros_guide.txt",  '~', false },
    { "options_guide.txt", '&', false },
#ifdef USE_TILE
    { "tiles_help.txt",    'T', false },
#endif
    { NULL, 0, false }
};

static bool _compare_mon_names(MenuEntry *entry_a, MenuEntry* entry_b)
{
    monsters *a = static_cast<monsters*>( entry_a->data );
    monsters *b = static_cast<monsters*>( entry_b->data );

    if (a->type == b->type)
        return (false);

    std::string a_name = mons_type_name(a->type, DESC_PLAIN);
    std::string b_name = mons_type_name(b->type, DESC_PLAIN);
    return (lowercase(a_name) < lowercase(b_name));
}

// Compare monsters by location-independant level, or by hitdice if
// levels are equal, or by name if both level and hitdice are equal.
static bool _compare_mon_toughness(MenuEntry *entry_a, MenuEntry* entry_b)
{
    monsters *a = static_cast<monsters*>( entry_a->data );
    monsters *b = static_cast<monsters*>( entry_b->data );

    if (a->type == b->type)
        return (false);

    int a_toughness = mons_difficulty(a->type);
    int b_toughness = mons_difficulty(b->type);

    if (a_toughness == b_toughness)
    {
        std::string a_name = mons_type_name(a->type, DESC_PLAIN);
        std::string b_name = mons_type_name(b->type, DESC_PLAIN);
        return (lowercase(a_name) < lowercase(b_name));
    }
    return (a_toughness > b_toughness);
}

class DescMenu : public Menu
{
public:
    DescMenu( int _flags, bool _show_mon, bool _text_only)
        : Menu(_flags, "", _text_only), sort_alpha(true),
          showing_monsters(_show_mon)
        {
            set_highlighter(NULL);

            if (_show_mon)
                toggle_sorting();

            set_prompt();
        }

    bool sort_alpha;
    bool showing_monsters;

    void set_prompt()
        {
            std::string prompt = "Describe which? ";

            if (showing_monsters)
            {
                if (sort_alpha)
                    prompt += "(CTRL-S to sort by monster toughness)";
                else
                    prompt += "(CTRL-S to sort by name)";
            }
            set_title(new MenuEntry(prompt, MEL_TITLE));
        }

    void sort()
        {
            if (!showing_monsters)
                return;

            if (sort_alpha)
                std::sort(items.begin(), items.end(), _compare_mon_names);
            else
                std::sort(items.begin(), items.end(), _compare_mon_toughness);

            for (unsigned int i = 0, size = items.size(); i < size; i++)
            {
                const char letter = index_to_letter(i);

                items[i]->hotkeys.clear();
                items[i]->add_hotkey(letter);
            }
        }

    void toggle_sorting()
        {
            if (!showing_monsters)
                return;

            sort_alpha = !sort_alpha;

            sort();
            set_prompt();
        }
};

static std::vector<std::string> _get_desc_keys(std::string regex,
                                               db_find_filter filter)
{
    std::vector<std::string> key_matches = getLongDescKeysByRegex(regex,
                                                                  filter);

    if (key_matches.size() == 1)
        return (key_matches);
    else if (key_matches.size() > 52)
        return (key_matches);

    std::vector<std::string> body_matches = getLongDescBodiesByRegex(regex,
                                                                     filter);

    if (key_matches.size() == 0 && body_matches.size() == 0)
        return (key_matches);
    else if (key_matches.size() == 0 && body_matches.size() == 1)
        return (body_matches);

    // Merge key_matches and body_matches, discarding duplicates.
    std::vector<std::string> tmp = key_matches;
    tmp.insert(tmp.end(), body_matches.begin(), body_matches.end());
    std::sort(tmp.begin(), tmp.end());
    std::vector<std::string> all_matches;
    for (unsigned int i = 0, size = tmp.size(); i < size; i++)
        if (i == 0 || all_matches[all_matches.size() - 1] != tmp[i])
            all_matches.push_back(tmp[i]);

    return (all_matches);
}

static std::vector<std::string> _get_monster_keys(unsigned char showchar)
{
    std::vector<std::string> mon_keys;

    for (int i = 0; i < NUM_MONSTERS; i++)
    {
        if (i == MONS_PROGRAM_BUG || mons_global_level(i) == 0)
            continue;

        monsterentry *me = get_monster_data(i);

        if (me == NULL || me->name == NULL || me->name[0] == '\0')
            continue;

        if (me->mc != i)
            continue;

        if (getLongDescription(me->name).empty())
            continue;

        if (me->showchar == showchar)
            mon_keys.push_back(me->name);
    }

    return (mon_keys);
}

static std::vector<std::string> _get_god_keys()
{
    std::vector<std::string> names;

    for (int i = GOD_NO_GOD + 1; i < NUM_GODS; i++)
    {
        god_type which_god = static_cast<god_type>(i);
        names.push_back(god_name(which_god));
    }

    return names;
}

static std::vector<std::string> _get_branch_keys()
{
    std::vector<std::string> names;

    for (int i = BRANCH_MAIN_DUNGEON; i < NUM_BRANCHES; i++)
    {
        branch_type which_branch = static_cast<branch_type>(i);
        Branch     &branch       = branches[which_branch];

        // Skip unimplemented branches
        if (branch.depth < 1 || branch.shortname == NULL)
            continue;

        names.push_back(branch.shortname);
    }
/*
    // Maybe include other level areas, as well.
    for (int i = LEVEL_LABYRINTH; i < NUM_LEVEL_AREA_TYPES; i++)
    {
        names.push_back(place_name(
                            get_packed_place(BRANCH_MAIN_DUNGEON, 1,
                                static_cast<level_area_type>(i)), true));
    }
*/
    return (names);
}

static bool _monster_filter(std::string key, std::string body)
{
    int mon_num = get_monster_by_name(key.c_str(), true);
    return (mon_num == MONS_PROGRAM_BUG || mons_global_level(mon_num) == 0);
}

static bool _spell_filter(std::string key, std::string body)
{
    spell_type spell = spell_by_name(key);

    if (spell == SPELL_NO_SPELL)
        return (true);

    if (get_spell_flags(spell) & (SPFLAG_MONSTER | SPFLAG_TESTING))
    {
#ifdef WIZARD
        return (!you.wizard);
#else
        return (true);
#endif
    }

    return (false);
}

static bool _item_filter(std::string key, std::string body)
{
    return (item_types_by_name(key).base_type == OBJ_UNASSIGNED);
}

static bool _skill_filter(std::string key, std::string body)
{
    key = lowercase_string(key);
    std::string name;
    for (int i = 0; i < NUM_SKILLS; i++)
    {
        // There are a couple of NULL entries in the skill set.
        if (!skill_name(i))
            continue;

        name = lowercase_string(skill_name(i));

        if (name.find(key) != std::string::npos)
            return (false);
    }
    return (true);
}

static bool _feature_filter(std::string key, std::string body)
{
    return (feat_by_desc(key) == DNGN_UNSEEN);
}

static bool _card_filter(std::string key, std::string body)
{
    key = lowercase_string(key);
    std::string name;

    // Every card description contains the keyword "card".
    if (key.find("card") != std::string::npos)
        return (false);

    for (int i = 0; i < NUM_CARDS; ++i)
    {
        name = lowercase_string(card_name((card_type) i));

        if (name.find(key) != std::string::npos)
            return (false);
    }
    return (true);
}

static bool _ability_filter(std::string key, std::string body)
{
    key = lowercase_string(key);
    if (string_matches_ability_name(key))
        return (false);

    return (true);
}

typedef void (*db_keys_recap)(std::vector<std::string>&);

static void _recap_mon_keys(std::vector<std::string> &keys)
{
    for (unsigned int i = 0, size = keys.size(); i < size; i++)
    {
        monster_type type = get_monster_by_name(keys[i], true);
        keys[i] = mons_type_name(type, DESC_PLAIN);
    }
}

static void _recap_feat_keys(std::vector<std::string> &keys)
{
    for (unsigned int i = 0, size = keys.size(); i < size; i++)
    {
        dungeon_feature_type type = feat_by_desc(keys[i]);
        if (type == DNGN_ENTER_SHOP)
            keys[i] = "A shop";
        else
        {
            keys[i] = feature_description(type, NUM_TRAPS, false, DESC_CAP_A,
                                          false);
        }
    }
}

// Extra info on this item wasn't found anywhere else.
static void _append_non_item(std::string &desc, std::string key)
{
    spell_type type = spell_by_name(key);

    if (type == SPELL_NO_SPELL)
        return;

    unsigned int flags = get_spell_flags(type);

    if (flags & SPFLAG_TESTING)
    {
        desc += "$This is a testing spell, only available via the "
                "&Z wizard command.";
    }
    else if (flags & SPFLAG_MONSTER)
    {
        desc += "$This is a monster-only spell, only available via the "
                "&Z wizard command.";
    }
    else if (flags & SPFLAG_CARD)
    {
        desc += "$This is a card-effect spell, unavailable in ordinary "
                "spellbooks.";
    }
    else
    {
        desc += "$Odd, this spell can't be found anywhere.  Please "
                "file a bug report.";
    }

#ifdef WIZARD
    if (!you.wizard)
#else
    if (true)
#endif
    {
        if (flags & (SPFLAG_TESTING | SPFLAG_MONSTER))
        {
            desc += "$$You aren't in wizard mode, so you shouldn't be "
                    "seeing this entry.  Please file a bug report.";
        }
    }
}

// Adds a list of all books/rods that contain a given spell (by name)
// to a description string.
static bool _append_books(std::string &desc, item_def &item, std::string key)
{
    spell_type type = spell_by_name(key, true);

    if (type == SPELL_NO_SPELL)
        return (false);

    desc += "$Type:       ";
    bool already = false;

    for (int i = 0; i <= SPTYP_LAST_EXPONENT; i++)
    {
        if (spell_typematch( type, 1 << i ))
        {
            if (already)
                desc += "/" ;

            desc += spelltype_long_name( 1 << i );
            already = true;
        }
    }
    if (!already)
        desc += "None";

    desc += "$Level:      ";
    char sval[3];
    itoa( spell_difficulty( type ), sval, 10 );
    desc += sval;

    if (you_cannot_memorise(type))
    {
        desc += "$You cannot memorise or cast this spell because you "
                "are a ";
        desc += lowercase_string(species_name(you.species, 0));
        desc += ".";
    }

    set_ident_flags(item, ISFLAG_IDENT_MASK);
    std::vector<std::string> books;
    std::vector<std::string> rods;

    item.base_type = OBJ_BOOKS;
    for (int i = 0; i < NUM_FIXED_BOOKS; i++)
        for (int j = 0; j < 8; j++)
            if (which_spell_in_book(i, j) == type)
            {
                item.sub_type = i;
                books.push_back(item.name(DESC_PLAIN));
            }

    item.base_type = OBJ_STAVES;
    int book;
    for (int i = STAFF_FIRST_ROD; i < NUM_STAVES; i++)
    {
        item.sub_type = i;
        book = item.book_number();

        for (int j = 0; j < 8; j++)
            if (which_spell_in_book(book, j) == type)
                rods.push_back(item.name(DESC_PLAIN));
    }

    if (!books.empty())
    {
        desc += "$$This spell can be found in the following book";
        if (books.size() > 1)
            desc += "s";
        desc += ":$";
        desc += comma_separated_line(books.begin(), books.end(), "$", "$");

        if (!rods.empty())
        {
            desc += "$$... and the following rod";
            if (rods.size() > 1)
                desc += "s";
            desc += ":$";
            desc += comma_separated_line(rods.begin(), rods.end(), "$", "$");
        }
    }
    else // rods-only
    {
        desc += "$$This spell can be found in the following rod";
        if (rods.size() > 1)
            desc += "s";
        desc += ":$";
        desc += comma_separated_line(rods.begin(), rods.end(), "$", "$");
    }

    return (true);
}

static bool _do_description(std::string key, std::string type,
                            std::string footer = "")
{
    describe_info inf;
    inf.quote = getQuoteString(key);

    std::string desc = getLongDescription(key);

    int width = std::min(80, get_number_of_cols());

    god_type which_god = string_to_god(key.c_str());
    if (which_god != GOD_NO_GOD)
    {
        if (is_good_god(which_god))
        {
            inf.suffix = "$$" + god_name(which_god) +
                         " won't accept worship from undead or evil beings.";
        }
        std::string help = get_god_powers(which_god);
        if (!help.empty())
        {
            desc += EOL;
            desc += help;
        }
        desc += EOL;
        desc += get_god_likes(which_god);

        help = get_god_dislikes(which_god);
        if (!help.empty())
        {
            desc += EOL EOL;
            desc += help;
        }
    }
    else
    {
        monster_type mon_num = get_monster_by_name(key, true);
        // Don't attempt to get more information on ghost demon
        // monsters, as the ghost struct has not been initialised, which
        // will cause a crash.  Similarly for zombified monsters, since
        // they require a base monster.
        if (mon_num != MONS_PROGRAM_BUG && !mons_is_ghost_demon(mon_num)
            && !mons_class_is_zombified(mon_num))
        {
            monsters mon;
            mon.type = mon_num;

            if (mons_genus(mon_num) == MONS_DRACONIAN)
            {
                switch (mon_num)
                {
                case MONS_BLACK_DRACONIAN:
                case MONS_MOTTLED_DRACONIAN:
                case MONS_YELLOW_DRACONIAN:
                case MONS_GREEN_DRACONIAN:
                case MONS_PURPLE_DRACONIAN:
                case MONS_RED_DRACONIAN:
                case MONS_WHITE_DRACONIAN:
                case MONS_PALE_DRACONIAN:
                    mon.base_monster = mon_num;
                    break;
                default:
                    mon.base_monster = MONS_NO_MONSTER;
                    break;
                }
            }
            else
                mon.base_monster = MONS_NO_MONSTER;

            describe_monsters(mon, true);
            return (false);
        }
        else
        {
            int thing_created = get_item_slot();
            if (thing_created != NON_ITEM
                && (type == "item" || type == "spell"))
            {
                char name[80];
                strncpy(name, key.c_str(), sizeof(name));
                if (get_item_by_name(&mitm[thing_created], name, OBJ_WEAPONS))
                {
                    append_weapon_stats(desc, mitm[thing_created]);
                    desc += "$";
                }
                else if (get_item_by_name(&mitm[thing_created], name, OBJ_ARMOUR))
                {
                    append_armour_stats(desc, mitm[thing_created]);
                    desc += "$";
                }
                else if (get_item_by_name(&mitm[thing_created], name, OBJ_MISSILES)
                         && mitm[thing_created].sub_type != MI_THROWING_NET)
                {
                    append_missile_info(desc);
                    desc += "$";
                }
                else if (type == "spell"
                         || get_item_by_name(&mitm[thing_created], name, OBJ_BOOKS)
                         || get_item_by_name(&mitm[thing_created], name, OBJ_STAVES))
                {
                    if (!_append_books(desc, mitm[thing_created], key))
                        append_spells(desc, mitm[thing_created]);
                }
                else
                    _append_non_item(desc, key);
            }

            // Now we don't need the item anymore.
            if (thing_created != NON_ITEM)
                destroy_item(thing_created);
        }
    }

    inf.body << desc;

    key = uppercase_first(key);
    linebreak_string2(footer, width - 1);

    inf.footer = footer;
    inf.title  = key;

    print_description(inf);
    return (true);
}

// Reads all questions from database/FAQ.txt, outputs them in the form of
// a selectable menu and prints the corresponding answer for a chosen question.
static bool _handle_FAQ()
{
    clrscr();
    viewwindow(false);

    std::vector<std::string> question_keys = getAllFAQKeys();
    if (question_keys.empty())
    {
        mpr("No questions found in FAQ! Please submit a bug report!");
        return (false);
    }
    Menu FAQmenu(MF_SINGLESELECT | MF_ANYPRINTABLE | MF_ALLOW_FORMATTING);
    MenuEntry *title = new MenuEntry("Frequently Asked Questions");
    title->colour = YELLOW;
    FAQmenu.set_title(title);
    const int width = std::min(80, get_number_of_cols());

    for (unsigned int i = 0, size = question_keys.size(); i < size; i++)
    {
        const char letter = index_to_letter(i);

        std::string question = getFAQ_Question(question_keys[i]);
        // Wraparound if the question is longer than fits into a line.
        linebreak_string2(question, width - 4);
        std::vector<formatted_string> fss;
        formatted_string::parse_string_to_multiple(question, fss);

        MenuEntry *me;
        for (unsigned int j = 0; j < fss.size(); j++)
        {
            if (j == 0)
            {
                me = new MenuEntry(question, MEL_ITEM, 1, letter);
                me->data = (void*) &question_keys[i];
            }
            else
            {
                question = "    " + fss[j].tostring();
                me = new MenuEntry(question, MEL_ITEM, 1);
            }
            FAQmenu.add_entry(me);
        }
    }

    while (true)
    {
        std::vector<MenuEntry*> sel = FAQmenu.show();
        redraw_screen();
        if (sel.empty())
            return (false);
        else
        {
            ASSERT(sel.size() == 1);
            ASSERT(sel[0]->hotkeys.size() == 1);

            std::string key = *((std::string*) sel[0]->data);
            std::string answer = getFAQ_Answer(key);
            if (answer.empty())
            {
                answer = "No answer found in the FAQ! Please submit a "
                         "bug report!";
            }
            answer = "Q: " + getFAQ_Question(key) + EOL + answer;
            linebreak_string2(answer, width - 1);
            print_description(answer);
            if (getch() == 0)
                getch();
        }
    }

    return (true);
}

static bool _find_description(bool &again, std::string& error_inout)
{
    again = true;

    clrscr();
    viewwindow(false);

    if (!error_inout.empty())
        mpr(error_inout.c_str(), MSGCH_PROMPT);
    mpr("Describe a (M)onster, (S)pell, s(K)ill, (I)tem, (F)eature, (G)od, "
        "(A)bility, (B)ranch, or (C)ard? ", MSGCH_PROMPT);
    int ch;
    {
        cursor_control con(true);
        ch = toupper(getch());
    }
    std::string    type;
    std::string    extra;
    db_find_filter filter     = NULL;
    db_keys_recap  recap      = NULL;
    bool           want_regex = true;
    bool           want_sort  = true;

    bool doing_mons     = false;
    bool doing_items    = false;
    bool doing_gods     = false;
    bool doing_branches = false;
    bool doing_features = false;
    bool doing_spells   = false;

    switch (ch)
    {
    case 'M':
        type       = "monster";
        extra      = "  Enter a single letter to list monsters displayed by "
                     "that symbol.";
        filter     = _monster_filter;
        recap      = _recap_mon_keys;
        doing_mons = true;
        break;
    case 'S':
        type         = "spell";
        filter       = _spell_filter;
        doing_spells = true;
        break;
    case 'K':
        type   = "skill";
        filter = _skill_filter;
        break;
    case 'A':
        type   = "ability";
        filter = _ability_filter;
        break;
    case 'C':
        type   = "card";
        filter = _card_filter;
        break;
    case 'I':
        type        = "item";
        extra       = "  Enter a single letter to list items displayed by "
                      "that symbol.";
        filter      = _item_filter;
        doing_items = true;
        break;
    case 'F':
        type   = "feature";
        filter = _feature_filter;
        recap  = _recap_feat_keys;

        doing_features = true;
        break;
    case 'G':
        type       = "god";
        filter     = NULL;
        want_regex = false;
        doing_gods = true;
        break;
    case 'B':
        type           = "branch";
        filter         = NULL;
        want_regex     = false;
        want_sort      = false;
        doing_branches = true;
        break;

    default:
        error_inout = "Okay, then.";
        again = false;
        return (false);
    }

    std::string regex = "";

    if (want_regex)
    {
        mprf(MSGCH_PROMPT,
             "Describe a %s; partial names and regexps are fine.%s",
             type.c_str(), extra.c_str());

        mpr("Describe what? ", MSGCH_PROMPT);
        char buf[80];
        if (cancelable_get_line(buf, sizeof(buf)) || buf[0] == '\0')
        {
            error_inout = "Okay, then.";
            return (false);
        }

        if (strlen(buf) == 1)
            regex = buf;
        else
            regex = trimmed_string(buf);

        if (regex.empty())
        {
            error_inout = "Description must contain at least "
                          "one non-space.";
            return (false);
        }
    }

    bool by_mon_symbol  = (doing_mons  && regex.size() == 1);
    bool by_item_symbol = (doing_items && regex.size() == 1);

    if (by_mon_symbol)
        want_regex = false;

    bool exact_match = false;
    if (want_regex && !(*filter)(regex, ""))
    {
        // Try to get an exact match first.
        std::string desc = getLongDescription(regex);

        if (!desc.empty())
            exact_match = true;
    }

    std::vector<std::string> key_list;

    if (by_mon_symbol)
        key_list = _get_monster_keys(regex[0]);
    else if (by_item_symbol)
        key_list = item_name_list_for_glyph(regex[0]);
    else if (doing_gods)
        key_list = _get_god_keys();
    else if (doing_branches)
        key_list = _get_branch_keys();
    else
        key_list = _get_desc_keys(regex, filter);

    if (recap != NULL)
        (*recap)(key_list);

    if (key_list.size() == 0)
    {
        if (by_mon_symbol)
        {
            error_inout  = "No monsters with symbol '";
            error_inout += regex;
            error_inout += "'.";
        }
        else if (by_item_symbol)
        {
            error_inout  = "No items with symbol '";
            error_inout += regex;
            error_inout += "'.";
        }
        else
        {
            error_inout  = "No matching ";
            error_inout += pluralise(type);
            error_inout += ".";
        }
        return (false);
    }
    else if (key_list.size() > 52)
    {
        if (by_mon_symbol)
        {
            error_inout  = "Too many monsters with symbol '";
            error_inout += regex;
            error_inout += "' to display";
        }
        else
        {
            std::ostringstream os;
            os << "Too many matching " << type << "s (" << key_list.size()
               << ") to display.";
            error_inout = os.str();
        }
        return (false);
    }
    else if (key_list.size() == 1)
        return _do_description(key_list[0], type);

    if (exact_match)
    {
        std::string footer = "This entry is an exact match for '";
        footer += regex;
        footer += "'.  To see non-exact matches, press space.";

        _do_description(regex, type, footer);

        if (getch() != ' ')
            return (false);
    }

    if (want_sort)
        std::sort(key_list.begin(), key_list.end());

    // For tiles builds use a tiles menu to display monsters.
    const bool text_only =
#ifdef USE_TILE
        !(doing_mons || doing_features || doing_spells);
#else
        true;
#endif

    DescMenu desc_menu(MF_SINGLESELECT | MF_ANYPRINTABLE |
                       MF_ALWAYS_SHOW_MORE | MF_ALLOW_FORMATTING,
                       doing_mons, text_only);
    desc_menu.set_tag("description");
    std::list<monsters> monster_list;
    std::list<item_def> item_list;
    for (unsigned int i = 0, size = key_list.size(); i < size; i++)
    {
        const char  letter = index_to_letter(i);
        std::string str    = uppercase_first(key_list[i]);

        MenuEntry *me = NULL;

        if (doing_mons)
        {
            // Create and store fake monsters, so the menu code will
            // have something valid to refer to.
            monsters     fake_mon;
            monster_type m_type = get_monster_by_name(str, true);

            // NOTE: Initializing the demon_ghost part of (very) ugly
            // things and player ghosts is taken care of in define_monster().
            fake_mon.type = m_type;
            fake_mon.props["fake"] = true;
            define_monster(fake_mon);

            // FIXME: This doesn't generate proper draconian monsters.
            monster_list.push_back(fake_mon);

#ifndef USE_TILE
            int colour = mons_class_colour(m_type);
            if (colour == BLACK)
                colour = LIGHTGREY;

            std::string prefix = "(<";
            prefix += colour_to_str(colour);
            prefix += ">";
            prefix += stringize_glyph(mons_char(fake_mon.type));
            prefix += "</";
            prefix += colour_to_str(colour);
            prefix += ">) ";

            str = prefix + str;
#endif

            // NOTE: MonsterMenuEntry::get_tiles() takes care of setting
            // up a fake weapon when displaying a fake dancing weapon's
            // tile.
            me = new MonsterMenuEntry(str, &(monster_list.back()),
                                      letter);
        }
        else if (doing_features)
            me = new FeatureMenuEntry(str, feat_by_desc(str), letter);
        else
        {
            me = new MenuEntry(uppercase_first(key_list[i]), MEL_ITEM, 1,
                               letter);

#ifdef USE_TILE
            if (doing_spells)
            {
                spell_type spell = spell_by_name(str);
                if (spell != SPELL_NO_SPELL)
                    me->add_tile(tile_def(tileidx_spell(spell), TEX_GUI));
            }
#else
            UNUSED(doing_spells);
#endif

            me->data = (void*) &key_list[i];
        }

        desc_menu.add_entry(me);
    }

    desc_menu.sort();

    while (true)
    {
        std::vector<MenuEntry*> sel = desc_menu.show();
        redraw_screen();
        if ( sel.empty() )
        {
            if (doing_mons && desc_menu.getkey() == CONTROL('S'))
                desc_menu.toggle_sorting();
            else
                return (false);
        }
        else
        {
            ASSERT(sel.size() == 1);
            ASSERT(sel[0]->hotkeys.size() == 1);

            std::string key;

            if (doing_mons)
            {
                monsters* mon = (monsters*) sel[0]->data;
                key = mons_type_name(mon->type, DESC_PLAIN);
            }
            else if (doing_features)
                key = sel[0]->text;
            else
                key = *((std::string*) sel[0]->data);

            if (_do_description(key, type))
            {
                if (getch() == 0)
                    getch();
            }
        }
    }

    return (false);
}

static int _keyhelp_keyfilter(int ch)
{
    switch (ch)
    {
    case ':':
        // If the game has begun, show notes.
        if (crawl_state.need_save)
        {
            display_notes();
            return -1;
        }
        break;

    case '/':
    {
        bool again = false;
        std::string error;
        do
        {
            // resets 'again'
            if (_find_description(again, error) && getch() == 0)
                getch();

            if (again)
                mesclr(true);
        }
        while (again);

        viewwindow(false);

        return -1;
    }

    case 'q':
    case 'Q':
    {
        bool again;
        do
        {
            // resets 'again'
            again = _handle_FAQ();
            if (again)
                mesclr(true);
        }
        while (again);

        return -1;
    }

    case 'v':
    case 'V':
        _print_version();
        return -1;
    }
    return ch;
}

///////////////////////////////////////////////////////////////////////////
// Manual menu highlighter.

class help_highlighter : public MenuHighlighter
{
public:
    help_highlighter();
    int entry_colour(const MenuEntry *entry) const;
private:
    text_pattern pattern;
    std::string get_species_key() const;
};

help_highlighter::help_highlighter()
    : pattern(get_species_key())
{
}

int help_highlighter::entry_colour(const MenuEntry *entry) const
{
    return !pattern.empty() && pattern.matches(entry->text)? WHITE : -1;
}

// To highlight species in aptitudes list. ('?%')
std::string help_highlighter::get_species_key() const
{
    if (player_genus(GENPC_DRACONIAN) && you.experience_level < 7)
        return "";

    std::string result = species_name(you.species, you.experience_level);
    if (player_genus(GENPC_DRACONIAN))
    {
        strip_tag(result,
                  species_name(you.species, you.experience_level, true));
    }

    result += "  ";
    return (result);
}
////////////////////////////////////////////////////////////////////////////

static int _show_keyhelp_menu(const std::vector<formatted_string> &lines,
                              bool with_manual, bool easy_exit = false,
                              int hotkey = 0)
{
    formatted_scroller cmd_help;

    // Set flags, and use easy exit if necessary.
    int flags = MF_NOSELECT | MF_ALWAYS_SHOW_MORE | MF_NOWRAP;
    if (easy_exit)
        flags |= MF_EASY_EXIT;
    cmd_help.set_flags(flags, false);
    cmd_help.set_tag("help");

    // FIXME: Allow for hiding Page down when at the end of the listing, ditto
    // for page up at start of listing.
    cmd_help.set_more( formatted_string::parse_string(
#ifdef USE_TILE
                            "<cyan>[ +/L-click : Page down.   - : Page up."
                            "           Esc/R-click exits.]"));
#else
                            "<cyan>[ + : Page down.   - : Page up."
                            "                           Esc exits.]"));
#endif

    if (with_manual)
    {
        cmd_help.set_highlighter(new help_highlighter);
        cmd_help.f_keyfilter = _keyhelp_keyfilter;
        column_composer cols(2, 40);

        cols.add_formatted(
            0,
            "<h>Dungeon Crawl Help\n"
            "\n"
            "Press one of the following keys to\n"
            "obtain more information on a certain\n"
            "aspect of Dungeon Crawl.\n"

            "<w>?</w>: List of keys\n"
            "<w>!</w>: Read Me!\n"
            "<w>^</w>: Quickstart Guide\n"
            "<w>:</w>: Browse character notes\n"
            "<w>~</w>: Macros help\n"
            "<w>&</w>: Options help\n"
            "<w>%</w>: Table of aptitudes\n"
            "<w>/</w>: Lookup description\n"
            "<w>Q</w>: FAQ\n"
#ifdef USE_TILE
            "<w>T</w>: Tiles key help\n"
#endif
            "<w>V</w>: Version information\n"
            "<w>Home</w>: This screen\n",
            true, true, _cmdhelp_textfilter);

        cols.add_formatted(
            1,
            "<h>Manual Contents\n\n"
            "<w>*</w>       Table of contents\n"
            "<w>A</w>.      Overview\n"
            "<w>B</w>.      Starting Screen\n"
            "<w>C</w>.      Attributes and Stats\n"
            "<w>D</w>.      Dungeon Exploration\n"
            "<w>E</w>.      Experience and Skills\n"
            "<w>F</w>.      Monsters\n"
            "<w>G</w>.      Items\n"
            "<w>H</w>.      Spellcasting\n"
            "<w>I</w>.      targetting\n"
            "<w>J</w>.      Religion\n"
            "<w>K</w>.      Mutations\n"
            "<w>L</w>.      Licence, Contact, History\n"
            "<w>M</w>.      Keymaps, Macros, Options\n"
            "<w>N</w>.      Philosophy\n"
            "<w>1</w>.      List of Species\n"
            "<w>2</w>.      List of Classes\n"
            "<w>3</w>.      List of Skills\n"
            "<w>4</w>.      Keys and Commands\n"
            "<w>5</w>.      List of Enchantments\n"
            "<w>6</w>.      Inscriptions\n",
            true, true, _cmdhelp_textfilter);

        std::vector<formatted_string> blines = cols.formatted_lines();
        unsigned i;
        for (i = 0; i < blines.size(); ++i)
            cmd_help.add_item_formatted_string(blines[i]);

        while (static_cast<int>(++i) < get_number_of_lines())
            cmd_help.add_item_string("");

        // unscrollable
        cmd_help.add_entry(new MenuEntry(std::string(), MEL_TITLE));
    }

    for (unsigned i = 0; i < lines.size(); ++i)
        cmd_help.add_item_formatted_string(lines[i], (i == 0 ? '?' : 0) );

    if (with_manual)
    {
        for (int i = 0; help_files[i].name != NULL; ++i)
        {
            // Attempt to open this file, skip it if unsuccessful.
            std::string fname = canonicalise_file_separator(help_files[i].name);
            FILE* fp = fopen(datafile_path(fname, false).c_str(), "r");

#if defined(TARGET_OS_DOS)
            if (!fp)
            {
 #ifdef DEBUG_FILES
                mprf(MSGCH_DIAGNOSTICS, "File '%s' could not be opened.",
                     help_files[i].name);
 #endif
                if (get_dos_compatible_file_name(&fname))
                {
 #ifdef DEBUG_FILES
                    mprf(MSGCH_DIAGNOSTICS,
                         "Attempting to open file '%s'", fname.c_str());
 #endif
                    fp = fopen(datafile_path(fname, false).c_str(), "r");
                }
            }
#endif

            if (!fp)
                continue;

            // Put in a separator...
            cmd_help.add_item_string("");
            cmd_help.add_item_string(std::string(get_number_of_cols()-1,'='));
            cmd_help.add_item_string("");

            // ...and the file itself.
            _add_file_to_scroller(fp, cmd_help, help_files[i].hotkey,
                                  help_files[i].auto_hotkey);

            // Done with this file.
            fclose(fp);
        }
    }

    if (hotkey)
        cmd_help.jump_to_hotkey(hotkey);

    cmd_help.show();

    return cmd_help.getkey();
}

void show_specific_help( const std::string &help )
{
    std::vector<std::string> lines = split_string("\n", help, false, true);
    std::vector<formatted_string> formatted_lines;
    for (int i = 0, size = lines.size(); i < size; ++i)
    {
        formatted_lines.push_back(
            formatted_string::parse_string(
                lines[i], true, _cmdhelp_textfilter));
    }
    _show_keyhelp_menu(formatted_lines, false, true);
}

void show_levelmap_help()
{
    show_specific_help( getHelpString("level-map") );
}

void show_targetting_help()
{
    column_composer cols(2, 41);
    // Page size is number of lines - one line for --more-- prompt.
    cols.set_pagesize(get_number_of_lines() - 1);

    cols.add_formatted(0, targetting_help_1, true, true);
    cols.add_formatted(1, targetting_help_2, true, true);
    _show_keyhelp_menu(cols.formatted_lines(), false, true);
}
void show_interlevel_travel_branch_help()
{
    show_specific_help( getHelpString("interlevel-travel.branch.prompt") );
}

void show_interlevel_travel_depth_help()
{
    show_specific_help( getHelpString("interlevel-travel.depth.prompt") );
}

void show_stash_search_help()
{
    show_specific_help( getHelpString("stash-search.prompt") );
}

void show_butchering_help()
{
    show_specific_help( getHelpString("butchering") );
}

static void _add_command(column_composer &cols, const int column,
                         const command_type cmd,
                         const std::string desc,
                         const unsigned int space_to_colon = 7)
{
    std::string command_name = command_to_string(cmd);
    if (strcmp(command_name.c_str(), "<") == 0)
        command_name += "<";

    const int cmd_len = command_name.length();
    std::string line = "<w>" + command_name + "</w>";
    for (unsigned int i = cmd_len; i < space_to_colon; ++i)
        line += " ";
    line += ": " + desc;

    cols.add_formatted(
            column,
            line.c_str(),
            false, true, _cmdhelp_textfilter);
}

static void _insert_commands(std::string &desc, std::vector<command_type> cmds)
{
    for (unsigned int i = 0; i < cmds.size(); ++i)
    {
        const std::string::size_type found = desc.find("%");
        if (found == std::string::npos)
            break;

        std::string command_name = command_to_string(cmds[i]);
        if (strcmp(command_name.c_str(), "<") == 0)
            command_name += "<";

        desc.replace(found, 1, command_name);
    }
    desc += "\n";
    desc = replace_all(desc, "percent", "%");
}

static void _insert_commands(std::string &desc, const int first, ...)
{
    std::vector<command_type> cmd_vector;
    cmd_vector.push_back((command_type) first);

    va_list args;
    va_start(args, first);
    int nargs = 10;

    while (nargs-- > 0)
    {
        int value = va_arg(args, int);
        if (!value)
            break;

        cmd_vector.push_back((command_type) value);
    }
    ASSERT(nargs > 0);
    va_end(args);

    _insert_commands(desc, cmd_vector);
}

static void _add_insert_commands(column_composer &cols, const int column,
                                 const unsigned int space_to_colon,
                                 const std::string &desc, const int first, ...)
{
    const command_type cmd = (command_type) first;

    va_list args;
    va_start(args, first);
    int nargs = 10;

    std::vector<command_type> cmd_vector;
    while (nargs-- > 0)
    {
        int value = va_arg(args, int);
        if (!value)
            break;

        cmd_vector.push_back((command_type) value);
    }
    ASSERT(nargs > 0);
    va_end(args);

    std::string line = desc;
    _insert_commands(line, cmd_vector);
    _add_command(cols, column, cmd, line, space_to_colon);
}

static void _insert_commands(column_composer &cols, const int column,
                             const std::string desc, const int first, ...)
{
    std::vector<command_type> cmd_vector;
    cmd_vector.push_back((command_type) first);

    va_list args;
    va_start(args, first);
    int nargs = 10;

    while (nargs-- > 0)
    {
        int value = va_arg(args, int);
        if (!value)
            break;

        cmd_vector.push_back((command_type) value);
    }
    ASSERT(nargs > 0);
    va_end(args);

    std::string line = desc;
    _insert_commands(line, cmd_vector);
    cols.add_formatted(
            column,
            line.c_str(),
            false, true, _cmdhelp_textfilter);
}

static void _add_formatted_keyhelp(column_composer &cols)
{
    cols.add_formatted(
            0,
            "<h>Movement:\n"
            "To move in a direction or to attack, \n"
            "use the numpad (try Numlock off and \n"
            "on) or vi keys:\n",
            true, true, _cmdhelp_textfilter);

    _insert_commands(cols, 0, "                 <w>1 2 3      % % %",
                     CMD_MOVE_UP_LEFT, CMD_MOVE_UP, CMD_MOVE_UP_RIGHT, 0);
    _insert_commands(cols, 0, "                  \\|/        \\|/", 0);
    _insert_commands(cols, 0, "                 <w>4</w>-<w>5</w>-<w>6</w>      <w>%</w>-<w>%</w>-<w>%</w>",
                     CMD_MOVE_LEFT, CMD_MOVE_NOWHERE, CMD_MOVE_RIGHT, 0);
    _insert_commands(cols, 0, "                  /|\\        /|\\", 0);
    _insert_commands(cols, 0, "                 <w>7 8 9      % % %",
                     CMD_MOVE_DOWN_LEFT, CMD_MOVE_DOWN, CMD_MOVE_DOWN_RIGHT, 0);
/*
            "                 <w>1 2 3      y k u\n"
            "                  \\|/        \\|/\n"
            "                 <w>4</w>-<w>5</w>-<w>6</w>"
                     "      <w>h</w>-<w>.</w>-<w>l</w>\n"
            "                  /|\\        /|\\\n"
            "                 <w>7 8 9      b j n\n"
*/

    cols.add_formatted(
            0,
            "<h>Rest/Search:\n",
            true, true, _cmdhelp_textfilter);

    _add_command(cols, 0, CMD_SEARCH, "wait a turn; searches adjacent", 2);
    cols.add_formatted(
            0,
            "    squares (also <w>numpad-5</w>, <w>.</w>, <w>Del</w>)\n",
            false, true, _cmdhelp_textfilter);

    _add_command(cols, 0, CMD_REST, "rest and long search; stops when", 2);
    cols.add_formatted(
            0,
            "    Health or Magic become full,\n"
            "    something is detected, or after\n"
            "    100 turns over (<w>Shift-numpad-5</w>)\n",
            false, true, _cmdhelp_textfilter);

    cols.add_formatted(
            0,
            "<h>Extended Movement:\n",
            true, true, _cmdhelp_textfilter);

    _add_command(cols, 0, CMD_EXPLORE, "auto-explore");
    _add_command(cols, 0, CMD_INTERLEVEL_TRAVEL, "interlevel travel");
    _add_command(cols, 0, CMD_SEARCH_STASHES, "Find items");
    _add_command(cols, 0, CMD_FIX_WAYPOINT, "set Waypoint");
    _add_command(cols, 0, CMD_FORGET_STASH, "Exclude square from searches");

    cols.add_formatted(
            0,
            "<w>/ Dir.</w>, <w>Shift-Dir.</w>: long walk\n"
            "<w>* Dir.</w>, <w>Ctrl-Dir.</w> : open/close door, \n"
            "         untrap, attack without move\n",
            false, true, _cmdhelp_textfilter);

    cols.add_formatted(
            0,
            "\n"
            "<h>Item types (and common commands)\n",
            true, true, _cmdhelp_textfilter);

    _add_insert_commands(cols, 0, 2, "use special Ability (<w>%!</w> for help)",
                         CMD_USE_ABILITY, CMD_USE_ABILITY, 0);

    _insert_commands(cols, 0, "<cyan>)</cyan> : hand weapons (<w>%</w>ield)",
                     CMD_WIELD_WEAPON, 0);
    _insert_commands(cols, 0, "<brown>(</brown> : missiles (<w>%</w>uiver, "
                              "<w>%</w>ire, <w>%</w>/<w>%</w> cycle)",
                     CMD_QUIVER_ITEM, CMD_FIRE, CMD_CYCLE_QUIVER_FORWARD,
                     CMD_CYCLE_QUIVER_BACKWARD, 0);
    _insert_commands(cols, 0, "<cyan>[</cyan> : armour (<w>%</w>ear and <w>%</w>ake off)",
                     CMD_WEAR_ARMOUR, CMD_REMOVE_ARMOUR, 0);
    _insert_commands(cols, 0, "<brown>percent</brown> : corpses and food (<w>%</w>hop up and <w>%</w>at)",
                     CMD_BUTCHER, CMD_EAT, 0);
    _insert_commands(cols, 0, "<w>?</w> : scrolls (<w>%</w>ead)",
                     CMD_READ, 0);
    _insert_commands(cols, 0, "<magenta>!</magenta> : potions (<w>%</w>uaff)",
                     CMD_QUAFF, 0);
    _insert_commands(cols, 0, "<blue>=</blue> : rings (<w>%</w>ut on and <w>%</w>emove)",
                     CMD_WEAR_JEWELLERY, CMD_REMOVE_JEWELLERY, 0);
    _insert_commands(cols, 0, "<red>\"</red> : amulets (<w>%</w>ut on and <w>%</w>emove)",
                     CMD_WEAR_JEWELLERY, CMD_REMOVE_JEWELLERY, 0);
    _insert_commands(cols, 0, "<lightgrey>/</lightgrey> : wands (e<w>%</w>oke)",
                     CMD_EVOKE, 0);

    std::string item_types = "<lightcyan>";
    item_types += static_cast<char>(get_item_symbol(SHOW_ITEM_BOOK));
    item_types +=
        "</lightcyan> : books (<w>%</w>ead, <w>%</w>emorise, <w>%</w>ap, <w>%</w>ap)";
    _insert_commands(cols, 0, item_types,
                     CMD_READ, CMD_MEMORISE_SPELL, CMD_CAST_SPELL,
                     CMD_FORCE_CAST_SPELL, 0);
    _insert_commands(cols, 0, "<brown>\\</brown> : staves and rods (<w>%</w>ield and e<w>%</w>oke)",
                     CMD_WIELD_WEAPON, CMD_EVOKE_WIELDED, 0);
    _insert_commands(cols, 0, "<lightgreen>}</lightgreen> : miscellaneous items (e<w>%</w>oke)",
                     CMD_EVOKE, 0);
    _insert_commands(cols, 0, "<yellow>$</yellow> : gold (<w>%</w> counts gold)",
                     CMD_LIST_GOLD, 0);

    cols.add_formatted(
            0,
            "<lightmagenta>0</lightmagenta> : the Orb of Zot\n"
            "    Carry it to the surface and win!\n",
            false, true, _cmdhelp_textfilter);

    cols.add_formatted(
            0,
            "<h>Other Gameplay Actions:\n",
            true, true, _cmdhelp_textfilter);

    _add_insert_commands(cols, 0, 2, "use special Ability (<w>%!</w> for help)",
                         CMD_USE_ABILITY, CMD_USE_ABILITY, 0);
    _add_insert_commands(cols, 0, 2, "Pray (<w>%</w> and <w>%!</w> for help)",
                         CMD_PRAY, CMD_DISPLAY_RELIGION, CMD_DISPLAY_RELIGION, 0);
    _add_command(cols, 0, CMD_CAST_SPELL, "cast spell, abort without targets", 2);
    _add_command(cols, 0, CMD_FORCE_CAST_SPELL, "cast spell, no matter what", 2);
    _add_command(cols, 0, CMD_DISPLAY_SPELLS, "list all spells", 2);

    _add_insert_commands(cols, 0, 2, "tell allies (<w>%t</w> to shout)",
                         CMD_SHOUT, CMD_SHOUT, 0);
    _add_command(cols, 0, CMD_PREV_CMD_AGAIN, "re-do previous command", 2);
    _add_command(cols, 0, CMD_REPEAT_CMD, "repeat next command # of times", 2);

    cols.add_formatted(
            0,
            "<h>Non-Gameplay Commands / Info\n",
            true, true, _cmdhelp_textfilter);

    _add_command(cols, 0, CMD_REPLAY_MESSAGES, "show Previous messages");
    _add_command(cols, 0, CMD_REDRAW_SCREEN, "Redraw screen");
    _add_command(cols, 0, CMD_CLEAR_MAP, "Clear main and level maps");
    _add_command(cols, 0, CMD_ANNOTATE_LEVEL, "annotate the dungeon level", 2);
    _add_command(cols, 0, CMD_CHARACTER_DUMP, "dump character to file", 2);
    _add_insert_commands(cols, 0, 2, "add note (use <w>%:</w> to read notes)",
                         CMD_MAKE_NOTE, CMD_DISPLAY_COMMANDS, 0);
    _add_command(cols, 0, CMD_MACRO_ADD, "add macro (also <w>Ctrl-D</w>)", 2);
    _add_command(cols, 0, CMD_ADJUST_INVENTORY, "reassign inventory/spell letters", 2);
// No online play for tiles, so this replacement is reasonable. (jpeg)
#ifdef USE_TILE
    _add_command(cols, 0, CMD_TOGGLE_SPELL_DISPLAY, "toggle inventory/spells", 2);
    _add_command(cols, 0, CMD_EDIT_PLAYER_TILE, "edit player doll", 2);
#else
    _add_command(cols, 0, CMD_READ_MESSAGES, "read messages (online play only)", 2);
#endif

    cols.add_formatted(
            1,
            "<h>Game Saving and Quitting:\n",
            true, true, _cmdhelp_textfilter);

    _add_command(cols, 1, CMD_SAVE_GAME, "Save game and exit");
    _add_command(cols, 1, CMD_SAVE_GAME_NOW, "Save and exit without query");
    _add_command(cols, 1, CMD_QUIT, "Quit without saving");

    cols.add_formatted(
            1,
            "<h>Player Character Information:\n",
            true, true, _cmdhelp_textfilter);

    _add_command(cols, 1, CMD_DISPLAY_CHARACTER_STATUS, "display character status", 2);
    _add_command(cols, 1, CMD_DISPLAY_SKILLS, "show skill screen", 2);
    _add_command(cols, 1, CMD_RESISTS_SCREEN, "show resistances", 2);
    _add_command(cols, 1, CMD_DISPLAY_RELIGION, "show religion screen", 2);
    _add_command(cols, 1, CMD_DISPLAY_MUTATIONS, "show Abilities/mutations", 2);
    _add_command(cols, 1, CMD_DISPLAY_KNOWN_OBJECTS, "show item knowledge", 2);
    _add_command(cols, 1, CMD_LIST_ARMOUR, "display worn armour", 2);
    _add_command(cols, 1, CMD_LIST_WEAPONS, "display current weapons", 2);
    _add_command(cols, 1, CMD_LIST_JEWELLERY, "display worn jewellery", 2);
    _add_command(cols, 1, CMD_LIST_GOLD, "display gold in possession", 2);
    _add_command(cols, 1, CMD_EXPERIENCE_CHECK, "display experience info", 2);

    cols.add_formatted(
            1,
            "<h>Dungeon Interaction and Information:\n",
            true, true, _cmdhelp_textfilter);

    _insert_commands(cols, 1, "<w>%</w>/<w>%</w> : Open/Close door",
                     CMD_OPEN_DOOR, CMD_CLOSE_DOOR, 0);
    _insert_commands(cols, 1, "<w>%</w>/<w>%</w> : use staircase",
                     CMD_GO_UPSTAIRS, CMD_GO_DOWNSTAIRS, 0);


    _add_command(cols, 1, CMD_INSPECT_FLOOR, "examine occupied tile");
    _add_command(cols, 1, CMD_LOOK_AROUND, "eXamine surroundings/targets");
    _add_insert_commands(cols, 1, 7, "eXamine level map (<w>%?</w> for help)",
                         CMD_DISPLAY_MAP, CMD_DISPLAY_MAP, 0);
    _add_command(cols, 1, CMD_FULL_VIEW, "list monsters, items, features in view");
    _add_command(cols, 1, CMD_DISPLAY_OVERMAP, "show dungeon Overview");
    _add_command(cols, 1, CMD_TOGGLE_AUTOPICKUP, "toggle auto-pickup");
    _add_command(cols, 1, CMD_TOGGLE_FRIENDLY_PICKUP, "change ally pickup behaviour");

    cols.add_formatted(
            1,
            "<h>Item Interaction (inventory):\n",
            true, true, _cmdhelp_textfilter);

    _add_command(cols, 1, CMD_DISPLAY_INVENTORY, "show Inventory list", 2);
    _add_command(cols, 1, CMD_LIST_EQUIPMENT, "show inventory of equipped items", 2);
    _add_command(cols, 1, CMD_INSCRIBE_ITEM, "inscribe item", 2);
    _add_command(cols, 1, CMD_FIRE, "Fire next appropriate item", 2);
    _add_command(cols, 1, CMD_THROW_ITEM_NO_QUIVER, "select an item and Fire it", 2);
    _add_command(cols, 1, CMD_QUIVER_ITEM, "select item slot to be quivered", 2);

    {
        std::string interact = (you.species == SP_VAMPIRE ? "Drain corpses"
                                                          : "Eat food");
        interact += " (tries floor first)\n";
        _add_command(cols, 1, CMD_EAT, interact, 2);
    }

    _add_command(cols, 1, CMD_QUAFF, "Quaff a potion", 2);
    _add_command(cols, 1, CMD_READ, "Read a scroll or book", 2);
    _add_command(cols, 1, CMD_MEMORISE_SPELL, "Memorise a spell from a book", 2);
    _add_command(cols, 1, CMD_WIELD_WEAPON, "Wield an item ( <w>-</w> for none)", 2);
    _add_command(cols, 1, CMD_WEAPON_SWAP, "wield item a, or switch to b", 2);

    _insert_commands(cols, 1, "    (use <w>%</w> to assign slots)",
                     CMD_ADJUST_INVENTORY, 0);

    _add_command(cols, 1, CMD_EVOKE_WIELDED, "eVoke power of wielded item", 2);
    _add_command(cols, 1, CMD_EVOKE, "eVoke wand", 2);

    _insert_commands(cols, 1, "<w>%</w>/<w>%</w> : Wear or Take off armour",
                     CMD_WEAR_ARMOUR, CMD_REMOVE_ARMOUR, 0);
    _insert_commands(cols, 1, "<w>%</w>/<w>%</w> : Put on or Remove jewellery",
                     CMD_WEAR_JEWELLERY, CMD_REMOVE_JEWELLERY, 0);

    cols.add_formatted(
            1,
            "<h>Item Interaction (floor):\n",
            true, true, _cmdhelp_textfilter);

    _add_command(cols, 1, CMD_PICKUP, "pick up items (also <w>g</w>)", 2);

    cols.add_formatted(
            1,
            "    (press twice for pick up menu)\n",
            false, true, _cmdhelp_textfilter);

    _add_command(cols, 1, CMD_DROP, "Drop an item", 2);
    _insert_commands(cols, 1, "<w>%#</w>: Drop exact number of items",
                     CMD_DROP, 0);

    {
        std::string interact = "Chop up a corpse";
        if (you.species == SP_VAMPIRE && you.experience_level >= 6)
            interact += " or bottle its blood";
        _add_command(cols, 1, CMD_BUTCHER, interact, 2);
    }

    _add_command(cols, 1, CMD_EAT, "Eat food from floor", 2);

    cols.add_formatted(
            1,
            "<h>Additional help:\n",
            true, true, _cmdhelp_textfilter);

    std::string text =
            "Many commands have context sensitive "
            "help, among them <w>%</w>, <w>%</w>, <w>%</w> (or any "
            "form of targetting), <w>%</w>, and <w>%</w>.\n"
            "You can read descriptions of your "
            "current spells (<w>%</w>), skills (<w>%?</w>) and "
            "abilities (<w>%!</w>).";
    _insert_commands(text, CMD_DISPLAY_MAP, CMD_LOOK_AROUND, CMD_FIRE,
                     CMD_SEARCH_STASHES, CMD_INTERLEVEL_TRAVEL,
                     CMD_DISPLAY_SPELLS, CMD_DISPLAY_SKILLS, CMD_USE_ABILITY,
                     0);
    linebreak_string2(text, 40);

    cols.add_formatted(
            1, text,
            false, true, _cmdhelp_textfilter);
}

static void _add_formatted_tutorial_help(column_composer &cols)
{
    cols.add_formatted(
            0, "<h>Item types (and common commands)\n",
            true, true, _cmdhelp_textfilter);

    _insert_commands(cols, 0, "<cyan>)</cyan> : hand weapons (<w>%</w>ield)",
                     CMD_WIELD_WEAPON, 0);
    _insert_commands(cols, 0, "<brown>(</brown> : missiles (<w>%</w>uiver, "
                              "<w>%</w>ire, <w>%</w>/<w>%</w> cycle)",
                     CMD_QUIVER_ITEM, CMD_FIRE, CMD_CYCLE_QUIVER_FORWARD,
                     CMD_CYCLE_QUIVER_BACKWARD, 0);
    _insert_commands(cols, 0, "<cyan>[</cyan> : armour (<w>%</w>ear and <w>%</w>ake off)",
                     CMD_WEAR_ARMOUR, CMD_REMOVE_ARMOUR, 0);
    _insert_commands(cols, 0, "<brown>percent</brown> : corpses and food (<w>%</w>hop up and <w>%</w>at)",
                     CMD_BUTCHER, CMD_EAT, 0);
    _insert_commands(cols, 0, "<w>?</w> : scrolls (<w>%</w>ead)",
                     CMD_READ, 0);
    _insert_commands(cols, 0, "<magenta>!</magenta> : potions (<w>%</w>uaff)",
                     CMD_QUAFF, 0);
    _insert_commands(cols, 0, "<blue>=</blue> : rings (<w>%</w>ut on and <w>%</w>emove)",
                     CMD_WEAR_JEWELLERY, CMD_REMOVE_JEWELLERY, 0);
    _insert_commands(cols, 0, "<red>\"</red> : amulets (<w>%</w>ut on and <w>%</w>emove)",
                     CMD_WEAR_JEWELLERY, CMD_REMOVE_JEWELLERY, 0);
    _insert_commands(cols, 0, "<lightgrey>/</lightgrey> : wands (e<w>%</w>oke)",
                     CMD_EVOKE, 0);

    std::string item_types = "<lightcyan>";
    item_types += static_cast<char>(get_item_symbol(SHOW_ITEM_BOOK));
    item_types +=
        "</lightcyan> : books (<w>%</w>ead, <w>%</w>emorise, <w>%</w>ap, <w>%</w>ap)";
    _insert_commands(cols, 0, item_types,
                     CMD_READ, CMD_MEMORISE_SPELL, CMD_CAST_SPELL,
                     CMD_FORCE_CAST_SPELL, 0);

    item_types = "<brown>";
    item_types += static_cast<char>(get_item_symbol(SHOW_ITEM_STAVE));
    item_types +=
        "</brown> : staves and rods (<w>%</w>ield and e<w>%</w>oke)";
    _insert_commands(cols, 0, item_types,
                     CMD_WIELD_WEAPON, CMD_EVOKE_WIELDED, 0);

    cols.add_formatted(
            0,
            "<h>Movement and attacking\n"
            "Use the <w>numpad</w> for movement (try both\n"
            "Numlock on and off). You can also use\n",
            true, true, _cmdhelp_textfilter);

    _insert_commands(cols, 0, "     <w>%%%%</w> : left, down, up, right and",
                     CMD_MOVE_LEFT, CMD_MOVE_DOWN, CMD_MOVE_UP,
                     CMD_MOVE_RIGHT, 0);
    _insert_commands(cols, 0, "     <w>%%%%</w> : diagonal movement.",
                     CMD_MOVE_UP_LEFT, CMD_MOVE_UP_RIGHT, CMD_MOVE_DOWN_LEFT,
                     CMD_MOVE_DOWN_RIGHT, 0);

    cols.add_formatted(
            0,
            "Walking into a monster will attack it\n"
            "with the wielded weapon or barehanded.\n"
            "For ranged attacks use either\n",
            false, true, _cmdhelp_textfilter);

    _insert_commands(cols, 0, "<w>%</w> to launch missiles (like arrows)",
                     CMD_FIRE, 0);
    _insert_commands(cols, 0, "<w>%</w>/<w>%</w> to cast spells "
                              "(<w>%?</w> lists spells).",
                     CMD_CAST_SPELL, CMD_FORCE_CAST_SPELL, CMD_CAST_SPELL, 0);

    cols.add_formatted(
            1,
            "<h>Additional important commands\n",
            true, true, _cmdhelp_textfilter);

    _add_command(cols, 1, CMD_SAVE_GAME_NOW, "Save the game and exit", 2);
    cols.add_formatted(1, " ", false, true, _cmdhelp_textfilter);
    _add_command(cols, 1, CMD_SEARCH, "search for one turn (also <w>.</w> and <w>Del</w>)", 2);
    _add_command(cols, 1, CMD_REST, "rest full/search longer (<w>Shift-Num 5</w>)", 2);
    _add_command(cols, 1, CMD_DISPLAY_INVENTORY, "list inventory (select item to view it)", 2);
    _add_command(cols, 1, CMD_PICKUP, "pick up item from ground (also <w>g</w>)", 2);
    _add_command(cols, 1, CMD_DROP, "drop item", 2);
    _insert_commands(cols, 0, "<w>%</w> or <w>%</w> : ascend/descend the stairs",
                     CMD_GO_UPSTAIRS, CMD_GO_DOWNSTAIRS, 0);
    cols.add_formatted(1, " ", false, true, _cmdhelp_textfilter);
    _add_command(cols, 1, CMD_REPLAY_MESSAGES, "show previous messages", 2);
    _add_command(cols, 1, CMD_DISPLAY_MAP, "show map of the whole level", 2);
    _add_command(cols, 1, CMD_FULL_VIEW, "list monsters, items, features in sight", 2);

    cols.add_formatted(
            1,
            "<h>Targetting (for spells and missiles)\n"
            "Use <w>+</w> (or <w>=</w>) and <w>-</w> to cycle between\n"
            "hostile monsters. <w>Enter</w> or <w>.</w> or <w>Del</w>\n"
            "all fire at the selected target.\n"
            "If the previous target is still alive\n"
            "and in sight, one of <w>f</w> or <w>p</w> fires at it\n"
            "again (without selecting anything).\n",
            true, true, _cmdhelp_textfilter, 40);
}

void list_commands(int hotkey, bool do_redraw_screen)
{
    // 2 columns, split at column 40.
    column_composer cols(2, 41);

    // Page size is number of lines - one line for --more-- prompt.
    cols.set_pagesize(get_number_of_lines() - 1);

    if (Tutorial.tutorial_left)
        _add_formatted_tutorial_help(cols);
    else
        _add_formatted_keyhelp(cols);

    _show_keyhelp_menu(cols.formatted_lines(), true, false, hotkey);

    if (do_redraw_screen)
    {
        clrscr();
        redraw_screen();
    }
}

#ifdef WIZARD
int list_wizard_commands(bool do_redraw_screen)
{
    // 2 columns
    column_composer cols(2, 44);
    // Page size is number of lines - one line for --more-- prompt.
    cols.set_pagesize(get_number_of_lines());

    cols.add_formatted(0,
                       "<yellow>Player stats</yellow>\n"
                       "<w>A</w>      : set all skills to level\n"
                       "<w>g</w>      : exercise a skill\n"
                       "<w>r</w>      : change character's species\n"
                       "<w>s</w>      : gain 20000 skill points\n"
                       "<w>S</w>      : set skill to level\n"
                       "<w>x</w>      : gain an experience level\n"
                       "<w>Ctrl-L</w> : change experience level\n"
                       "<w>$</w>      : get 1000 gold\n"
                       "<w>]</w>      : get a mutation\n"
                       "<w>^</w>      : gain piety\n"
                       "<w>_</w>      : gain religion\n"
                       "<w>@</w>      : set Str Int Dex\n"
                       "<w>Ctrl-D</w> : change enchantments/durations\n"
                       "\n"
                       "<yellow>Other player related effects</yellow>\n"
                       "<w>c</w>      : card effect\n"
                       "<w>Ctrl-G</w> : save/load ghost (bones file)\n"
                       "<w>h</w>/<w>H</w>    : heal yourself (super-Heal)\n"
                       "<w>Ctrl-H</w> : set hunger state\n"
                       "<w>X</w>      : make Xom do something now\n"
                       "<w>z</w>      : cast spell by number/name\n"
                       "\n"
                       "<yellow>Item related commands</yellow>\n"
                       "<w>a</w>      : acquirement\n"
                       "<w>C</w>      : (un)curse item\n"
                       "<w>i</w>/<w>I</w>    : identify/unidentify inventory\n"
                       "<w>o</w>/<w>%</w>    : create an object\n"
                       "<w>t</w>      : tweak object properties\n"
                       "<w>v</w>      : show gold value of an item\n"
                       "<w>|</w>      : create all predefined artefacts\n"
                       "<w>+</w>      : make randart from item\n"
                       "<w>'</w>      : list items\n",
                       true, true);

    cols.add_formatted(1,
                       "<yellow>Monster related commands</yellow>\n"
                       "<w>D</w>      : detect all monsters\n"
                       "<w>G</w>      : banish all monsters\n"
                       "<w>m</w>/<w>M</w>    : create monster by number/name\n"
                       "<w>\"</w>      : list monsters\n"
                       "\n"
                       "<yellow>Create level features</yellow>\n"
                       "<w>l</w>      : make entrance to labyrinth\n"
                       "<w>L</w>      : place a vault by name\n"
                       "<w>p</w>      : make entrance to pandemonium\n"
                       "<w>P</w>      : make a portal\n"
                       "<w>T</w>      : make a trap\n"
                       "<w><<</w>/<w>></w>    : create up/down staircase\n"
                       "<w>(</w>/<w>)</w>    : make feature by number/name\n"
                       "<w>\\</w>      : make a shop\n"
                       "\n"
                       "<yellow>Other level related commands</yellow>\n"
                       "<w>Ctrl-A</w> : generate new Abyss area\n"
                       "<w>b</w>      : controlled blink\n"
                       "<w>Ctrl-B</w> : controlled teleport\n"
                       "<w>B</w>      : banish yourself to the Abyss\n"
                       "<w>R</w>      : change monster spawn rate\n"
                       "<w>k</w>      : shift section of a labyrinth\n"
                       "<w>u</w>/<w>d</w>    : shift up/down one level\n"
                       "<w>~</w>      : go to a specific level\n"
                       "<w>:</w>      : find branches and overflow\n"
                       "         temples in the dungeon\n"
                       "<w>{</w>      : magic mapping\n"
                       "<w>}</w>      : detect all traps on level\n"
                       "\n"
                       "<yellow>Debugging commands</yellow>\n"
                       "<w>f</w>      : player combat damage stats\n"
                       "<w>F</w>      : combat stats with fsim_kit\n"
                       "<w>Ctrl-F</w> : combat stats (monster vs PC)\n"
                       "<w>Ctrl-I</w> : item generation stats\n"
                       "<w>Ctrl-X</w> : Xom effect stats\n"
                       "<w>O</w>      : measure exploration time\n"
                       "<w>Ctrl-t</w> : enter in-game Lua interpreter\n"
                       "\n"
                       "<yellow>Wizard targetting commands</yellow>\n"
                       "<w>x?</w>     : list targetted commands\n"
                       "\n"
                       "<w>?</w>      : list wizard commands\n",
                       true, true);

    int key = _show_keyhelp_menu(cols.formatted_lines(), false, true);
    if (do_redraw_screen)
        redraw_screen();
    return key;
}
#endif