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


                                                 
                                                          

   
                   
 
                   


                  
                  
                    
 
                     
                   

                   
                    
                     
                    
                    
                    
                  
                     

                   
                   

                    
                    
                 
                 
                     
                      
                     
                  
                   
                     
                     
                     
                  
                
                 



                           
 













                                          









                                                                        
                                       






                                                    
                                                                   
 


                                                
                                                       

 
                                                       
                                                   
                                                 
                                                   
                                                  

                          

                              




                                                                
                                            

                                                                  

                                                                    
     
                            


                                                             



                                                            
 

                        
 


                



                                               
                                                      

                                                          



              





                                                                     
                                           
 
                                    



                                    

 
                                   
 
                                                                      



                                                                  

 




                                                

                                                                       


                                               
 


























                                                          


                                                                           



                   







                                                




                                                               














































                                                                         
 





                     


                                            
                                              




                                                 
                                                       




                                            
                                                





                                                 



                                   
                                              





                              
                               

 

                                                    



                                     
                                        

                      







                                                                    





                                                                              

                                                           




                                                          

 









                                                     
                                                                        
            
 
                      
                                    

                                                                 


 




                                          











                                                      
                             


                                                 
 
                             

 















                                                                     




                                                








                                                       
                    



                                               
                                                               
                    
                        






                                         

 

                                     
 
                                                        



                       











                                                               

                         
                                                                     


                     
                                   

                                


                


                                                                
                                        
                                


                

                                                 
                                                           

                             
                                    



                                                                      

 
                                                      
 
                       
               
 


                                              


                                                       
                       
                                                   

                                                           
                                         
                                                                 
                                          
               
                                                   

                      

                                                              


                                                             
                                                 

                     

                                                            


                                                            
                                             

                     

                                                            
                                                            




                                                            


                                                   
                                                          
                 
             
      





                                                  
                          

 




                                                            




                                                


                                              
                                                 


                               

                                                   
                                          





                                                                
 
                                                        



                                                             
 








                                                                       
 











                                                                          
                                                        




















                                                                         
                 
                                                                
 

                                                                
     

                                      
 
                                      

                                  
                        
                              
                                                        

                                          
                                                            

                                

                                 

             
 




                                                           



                



                                                                      
                       

                                                   
                  

 





                                                         
 



                           
                                                                              



                            
                                                                   


                     
                                               

                          


                

                                                             






                                            
                     

                                 















                                                            
                                                                              







                                                                 
                                             




                         





                                                              





                                                        
 
                
                    
                           
 
                                                                              

                     

                              
                                                     


                     
                                           
                








                                                              
                                                                     

                     

                                                         
                                                           
 




                                              
                                               









                                                       
                                                                     

                     
 
                                                           
                                                        




                                                 
                              










                                                            
 
                                  

                 






                                                           
 
                    
                           



                 
                            
 
                     

 
                             
 
                        

 
                                                                
 

                                         
 


                              
 





                                                           
 





                                                           
 

               
 
                    
 




                                                   
 


                                                        
 

                              

 




                                        




                                        




                                              

                                       
                                                                                














































                                                                  

                       
                    
                  
                     
                        
                  
                          


                                 

 
                                                       
 
                                   
                                                                       
                                

 
                                       
 
                              

                                                         

 

                                                  

                                                             
 


                                                         
                                                                                  
         
                                                          




                  







                                                             

                                                            






                                                               








































































































































































































                                                                               








                                                             
                                                                                  


                                                               

                                                          

                                                              

                                                          





                  





                                                         
                                                                                  

                                                     
                                                                     


                                       

                                              
     
                                                





















                                                                          

                                                          
                                                         
                                                           
                                                                      

 



                                             
                                         

                                










                                            
                                                             
 
                                      

                                              

                                              
 
                                                         












                                                           

                                      
                                                         

                                  
                                                     






























                                                                            
                      
     

                                                            

                                                          

                                                        

     
                             

                              
                          

 



                                                
                                                           




































                                                                

                         

                                   



                                      

                                        
     
 
                      


                                                          

                                                          
     
 
                      
                          




                                       
                                                         








                                          
 
                      
     
                                                             
                                              

                                                              
     
 
                      
                          

 









































































































                                                                              














                                                           














                                                                             



                                                    


                                                        




                             










                                                                      



















































                                                                                




























































                                                                              










                                                           
                                                                                    





                                        

                                               



                           
                                                                            







                                                              
                                                                        






                                                           
                                       



                                                            





                                                            

 


                                                                          




















                                                                       
                                               



                                        
                                         







                                               


          
                  
                                                                      
                                                                         
                                                           

                                             
                                                                           

                                                                            



           

                    
                      


                  

                    
                     
                 

                     
                              
             



                                                



                      
                  
                        

                         
 
                             
 
                                       
                               
                                      
 



















                                                                         
 





                                                                        
                                                 



                 


























                                                    




                                              




                                               
                                      
 


                                                                 
                        
                         
                     

                         

 
                                    








                                                                            



                                                           
     



                                                                         
     
 
                      
                       
                   

                       





                    



                                                                  


                                             
                   
               



                       

                                                           
                                 



                                                    
                                       

 




                                                               
                                             



                                                                          



                                                      

                                                              
                                        
                               
                               

                                     
                     
                             
                        

 
                                     
 



                                                                         

                                                                                 
                                          
                                       
                                       

                                       
                    
                           
                      


                        
                                                    
 
                                       
                                                    


                              
                                            

                   
                                             
                                     




                             

 


                                            
                         
                     

                         
                                

 

                                           
                            
 
                                 





                                      
     
                          

                          

        





                                           
                              





                                       
                                          

                                                


                        
                                                                            
 
                         
                            
 

                               
                        

                 


                                                                          
                                                                              
                        
     
                                         
                                      
        




                                                          
                                              

                                                           


                    
                                           
 
                                                                          



                             
                                                           

 




                                                                     

                                           




                                           


                 


























































                                                                            







                                                               








                                                       
                                       
 

                                                      


                                    
 
            
              
                            
 








                                                 
























                                                           


                                   
                                       



                                                                     
                                      




                                                                    
                   
                                                                    







                                                                    
                          
                

 


                                                     
                                                    



























                                                        

                                  
                                  

 












                                                                       
 










































































                                                                     
 




                                                                   







































                                                                           





                                





                                    
 


                                       



                 

                       
                              




















                                                                        
                              





















                                                                      
                             























                                                                     
                                                     













                                                           
                                               
                       

 
                              
 
                          
                




                     




                                                                
                              
     







                                                                 





                                                             





                                                             




                                                  
                                                      
 
                               

 
                                                                  
 
                               

 
                                                                    
 
                                        
 







                                                                              


                     





                                                                     


                     



















                                                                              
     

                                    

     



















































                                                                               

 
                                 
 
                            

 
                                   
 













                                                         

 
                                                           
 









                                               

 
                                   
 


































                                                                   

 



                                                                   
                               


 
                                         
 

                       
 
                         
 
                                                                    

 
                                                       

                      
                   
 



                                                         
                                                       
         
                      
 

                                                        


         

                                   



                           
                                   

                              
 


                                                                    


                  
                                           
 

                                               
 
                                       

 

                                                                      

                                                         


                                                        

                                                         
 
                              

 

                       
                 

 











                                                                     
























                                                                            





























                                                                             





                                                                 




                                
                                                                      
 







                                                             
                        
                                 








                                                           


                                                              































                                                                          

                                                                   
 











                                                           
                                                              
                                                                    
                                                                
                                                          
 











                                                                  



                                                 















                                                                  



















                                                                      







                                                    
















                                                                       
                                                                       























                                                                              
                                                               

                            

                                
                                  
                
             






                                                                               


             



                                                              









                                                                             




                                                              
 
                                                                                 
                                                            
                                                                                         
                                                               
                                                                                       
                                                             

                                                                   
                                                                                     




                                                                               
                                                                                   



                                                                

         
                             
 
                           
                              
                                
                              
                                                                   
             















                                                                         
                                
         
                                                          
 
                                              
             
                                                               
                                                      

                              
 
                                      
                                          
                                         
         


                                   








                                                       


                                                                     
             
                                                                         
             


                                                                         


             
                                    




                  
                                                               






                                                 
            
     
                                 

                               
 


                               

 





                                                                       
 












                                                 



                                                                     
                                                               

      
                                                            
                                                    



                                                       

                                                         

      
                                         

             
                                               

                                 
                                                      
                                           



                                        

                   



                                                                    

     
                   
 




                                                               
 
                                                           










                                                                  










                                                        
                                     






                                             
                   
                                                         







                                                                             
 
                                                            

 





















                                                                      
                                                                         

 









                                                                           
  


                                                         
 

































                                                                             
                              








                                                         
                                                                               

















                                                    
                                                         


                    
                                            
                                                
 

                          
 



                                        
                                               

                                
                            

                                     
                                                             

                                     
                                                             

                                     
                                                               





















                                        


                                        
                                                                            
                                      
 
                                           
                                      
 




                                     

                                                    
 

                                             








                                                                      







                                                               
                                                        








                                   
 

                  
 
                                        
 
                                                 



                             
 
                                     

 
                                                                  


                  
                                              
                      

                
         
                               


                          
                            
     
 


                   














                                                                      

                   

 











                                                                    






















                                                                    
                     

                        



                                   
                              


                        

                              


                     
                               



                      
                               


                     
                
                               

                      






                                    

                    






                      






                    










                                                                              
                           








































                                                   








                                                                         




                                                            
                                










                                                                    



                                              















                                                                           


















                                                               
                                                            
 






                                                   

                                                                             
             
                                                                   

                                




                                  
                                

                                     
     
                               

                                                               
     
                                

                                     
     
 









                                                
             
                                                                 

                                



                                        































































































                                                                                  

                                           


                                                     
 
                         
                                                

                        
                                     



                                  
                                            



                                    

                                     


                                                 



                                     
 

                       
                      



                                          

                  

                               
                                                   
                                    
                                                   
                                   

                                  
 


                        
                            
     
                                                           

                                       
                                                                               

                          
                                 



                        

























                                                                     





                                                                  
                                                           



                                                            


                                                              
     





                                                                        










                                                                     
 



                                                                   
                                                                  



                   



                                   















                                            


               











                                                                  
                                                           



                                          

                                      





                                                              
                                                                      
 
                       
 


                                                



                                                                     
                                                                 



                  



                                                                         






                                                                              










                                                      
                                                                        
                                   





                              
 
                                                                          

              
                                                                                 



                              


                                                                          







                                                         
                                                                           





                                      
                                                                          

             
                                        



                                 
                                      









                                                                          

















                                                                           
                  
 
                                                            

                                                           

                                            
                          
                    

                                                           
     

           
                                               


                                                                     
                                     
                                 




                                      
     
                

 






                                            
                                                                          





                                  




                                









                                                                   
                                               


























                                                                          


                              
                                            







                                                                   




                        


                 
                                                        



                     



















                                                                 

                                             






                                                             
                                               





















                                                                      
                                
























                                                
 

                                                                  

                                                         

                                                        
 







                                                                   






                                                                  
 
                





                                                                   








                                                         
 
                 

 









                                                                       
                                                               







                                                  







                                  


                 
                                                        



                     
                                                        



                     
                                                        



                             

                                      
                              

 
                                        
 
                  

 
                                     
 
                  

 




                                    






                                                                          
                                                  


                                                        





                                      
 


                                                       
                                                                     









                                       
/*
 *  File:       mapdef.cc
 *  Summary:    Support code for Crawl des files.
 *  Created by: dshaligram on Wed Nov 22 08:41:20 2006 UTC
 */

#include "AppHdr.h"

#include <iostream>
#include <cstdarg>
#include <cstdio>
#include <cctype>
#include <cstdlib>
#include <algorithm>

#include "artefact.h"
#include "branch.h"
#include "colour.h"
#include "coord.h"
#include "coordit.h"
#include "describe.h"
#include "directn.h"
#include "dungeon.h"
#include "exclude.h"
#include "files.h"
#include "initfile.h"
#include "invent.h"
#include "items.h"
#include "l_defs.h"
#include "libutil.h"
#include "mapdef.h"
#include "mapmark.h"
#include "maps.h"
#include "misc.h"
#include "mon-cast.h"
#include "mon-place.h"
#include "mon-util.h"
#include "place.h"
#include "random.h"
#include "religion.h"
#include "spl-util.h"
#include "spl-book.h"
#include "stuff.h"
#include "env.h"
#include "tags.h"
#ifdef USE_TILE
#include "tiledef-dngn.h"
#include "tiledef-player.h"
#endif

static const char *map_section_names[] = {
    "",
    "north",
    "south",
    "east",
    "west",
    "northwest",
    "northeast",
    "southwest",
    "southeast",
    "encompass",
    "float",
};

// atoi that rejects strings containing non-numeric trailing characters.
// returns defval for invalid input.
template <typename V>
static V strict_aton(const char *s, V defval = 0)
{
    char *end;
    const V res = strtol(s, &end, 10);
    return (!*s || *end) ? defval : res;
}

const char *map_section_name(int msect)
{
    if (msect < 0 || msect >= MAP_NUM_SECTION_TYPES)
        return "";

    return map_section_names[msect];
}

static int find_weight(std::string &s, int defweight = TAG_UNFOUND)
{
    int weight = strip_number_tag(s, "weight:");
    if (weight == TAG_UNFOUND)
        weight = strip_number_tag(s, "w:");
    return (weight == TAG_UNFOUND? defweight : weight);
}

std::string mapdef_split_key_item(const std::string &s,
                                  std::string *key,
                                  int *separator,
                                  std::string *arg,
                                  int key_max_len)
{
    std::string::size_type
        norm = s.find("=", 1),
        fixe = s.find(":", 1);

    const std::string::size_type sep = norm < fixe? norm : fixe;
    if (sep == std::string::npos)
        return ("malformed declaration - must use = or :");

    *key = trimmed_string(s.substr(0, sep));
    std::string substitute    = trimmed_string(s.substr(sep + 1));

    if (key->empty()
        || (key_max_len != -1 && (int) key->length() > key_max_len))
    {
        return make_stringf(
            "selector '%s' must be <= %d characters in '%s'",
            key->c_str(), key_max_len, s.c_str());
    }

    if (substitute.empty())
        return make_stringf("no substitute defined in '%s'",
                            s.c_str());

    *arg = substitute;
    *separator = s[sep];

    return ("");
}

///////////////////////////////////////////////
// level_range
//

level_range::level_range(branch_type br, int s, int d)
    : level_type(LEVEL_DUNGEON), branch(br), shallowest(),
      deepest(), deny(false)
{
    set(s, d);
}

level_range::level_range(const raw_range &r)
    : branch(r.branch), shallowest(r.shallowest), deepest(r.deepest),
      deny(r.deny)
{
}

void level_range::write(writer& outf) const
{
    marshallShort(outf, level_type);
    marshallShort(outf, branch);
    marshallShort(outf, shallowest);
    marshallShort(outf, deepest);
    marshallByte(outf, deny);
}

void level_range::read(reader& inf)
{
    level_type = static_cast<level_area_type>( unmarshallShort(inf) );
    branch     = static_cast<branch_type>( unmarshallShort(inf) );
    shallowest = unmarshallShort(inf);
    deepest    = unmarshallShort(inf);
    deny       = unmarshallByte(inf);
}

std::string level_range::str_depth_range() const
{
    if (shallowest == -1)
        return (":??");

    if (deepest >= branches[branch].depth)
        return (shallowest == 1? "" : make_stringf("%d-", shallowest));

    if (shallowest == deepest)
        return make_stringf(":%d", shallowest);

    return make_stringf(":%d-%d", shallowest, deepest);
}

std::string level_range::describe() const
{
    return make_stringf("%s%s%s",
                        deny? "!" : "",
                        branch == NUM_BRANCHES? "Any" :
                        branches[branch].abbrevname,
                        str_depth_range().c_str());
}

level_range::operator raw_range () const
{
    raw_range r;
    r.branch     = branch;
    r.shallowest = shallowest;
    r.deepest    = deepest;
    r.deny       = deny;
    return (r);
}

void level_range::set(const std::string &br, int s, int d)
    throw (std::string)
{
    if (br == "any" || br == "Any")
        branch = NUM_BRANCHES;
    else if ((branch = str_to_branch(br)) == NUM_BRANCHES
             && (level_type = str_to_level_area_type(br)) == LEVEL_DUNGEON)
        throw make_stringf("Unknown branch: '%s'", br.c_str());

    shallowest = s;
    deepest    = d;

    if (branch != NUM_BRANCHES)
    {
        if (shallowest == -1)
            shallowest = branches[branch].depth;
        if (deepest == -1)
            deepest = branches[branch].depth;
    }

    if (deepest < shallowest)
        throw make_stringf("Level-range %s:%d-%d is malformed",
                           br.c_str(), s, d);
}

level_range level_range::parse(std::string s) throw (std::string)
{
    level_range lr;
    trim_string(s);

    if (s[0] == '!')
    {
        lr.deny = true;
        s = trimmed_string(s.substr(1));
    }

    std::string::size_type cpos = s.find(':');
    if (cpos == std::string::npos)
        parse_partial(lr, s);
    else
    {
        std::string branch = trimmed_string(s.substr(0, cpos));
        std::string depth  = trimmed_string(s.substr(cpos + 1));
        parse_depth_range(depth, &lr.shallowest, &lr.deepest);

        lr.set(branch, lr.shallowest, lr.deepest);
    }

    return (lr);
}

void level_range::parse_partial(level_range &lr, const std::string &s)
    throw (std::string)
{
    if (isdigit(s[0]))
    {
        lr.branch = NUM_BRANCHES;
        parse_depth_range(s, &lr.shallowest, &lr.deepest);
    }
    else
        lr.set(s, 1, 100);
}

void level_range::parse_depth_range(const std::string &s, int *l, int *h)
    throw (std::string)
{
    if (s == "*")
    {
        *l = 1;
        *h = 100;
        return;
    }

    if (s == "$")
    {
        *l = *h = -1;
        return;
    }

    std::string::size_type hy = s.find('-');
    if (hy == std::string::npos)
    {
        *l = *h = strict_aton<int>(s.c_str());
        if (!*l)
            throw std::string("Bad depth: ") + s;
    }
    else
    {
        *l = strict_aton<int>(s.substr(0, hy).c_str());

        std::string tail = s.substr(hy + 1);
        if (tail.empty())
            *h = 100;
        else
            *h = strict_aton<int>(tail.c_str());

        if (!*l || !*h || *l > *h)
            throw std::string("Bad depth: ") + s;
    }
}

void level_range::set(int s, int d)
{
    shallowest = s;
    deepest    = d;
    if (deepest == -1 || deepest < shallowest)
        deepest = shallowest;
}

void level_range::reset()
{
    deepest = shallowest = -1;
    level_type = LEVEL_DUNGEON;
}

bool level_range::matches(const level_id &lid) const
{
    // Level types must always match.
    if (lid.level_type != level_type)
        return (false);

    if (lid.level_type != LEVEL_DUNGEON)
        return (true);

    if (branch == NUM_BRANCHES)
        return (matches(absdungeon_depth(lid.branch, lid.depth)));
    else
        return (branch == lid.branch
                && lid.depth >= shallowest && lid.depth <= deepest);
}

bool level_range::matches(int x) const
{
    // [ds] The level ranges used by the game are zero-based, adjust for that.
    ++x;
    return (x >= shallowest && x <= deepest);
}

bool level_range::operator == (const level_range &lr) const
{
    return (deny == lr.deny && level_type == lr.level_type
            && (level_type != LEVEL_DUNGEON
                || (shallowest == lr.shallowest
                    && deepest == lr.deepest
                    && branch == lr.branch)));
}

bool level_range::valid() const
{
    return (shallowest > 0 && deepest >= shallowest);
}

int level_range::span() const
{
    return (deepest - shallowest);
}

////////////////////////////////////////////////////////////////////////
// map_lines

map_lines::map_lines()
    : markers(), lines(), overlay(),
      map_width(0), solid_north(false), solid_east(false),
      solid_south(false), solid_west(false), solid_checked(false)
{
}

map_lines::map_lines(const map_lines &map)
{
    init_from(map);
}

rectangle_iterator map_lines::get_iter() const
{
    ASSERT(width() > 0);
    ASSERT(height() > 0);

    coord_def tl(0, 0);
    coord_def br(width() - 1, height() - 1);
    return rectangle_iterator(tl, br);
}

char map_lines::operator () (const coord_def &c) const
{
    return (lines[c.y][c.x]);
}

char& map_lines::operator () (const coord_def &c)
{
    return (lines[c.y][c.x]);
}

char map_lines::operator () (int x, int y) const
{
    return (lines[y][x]);
}

char& map_lines::operator () (int x, int y)
{
    return (lines[y][x]);
}

bool map_lines::in_bounds(const coord_def &c) const
{
    return (c.x >= 0 && c.y >= 0 && c.x < width() && c.y < height());
}


bool map_lines::in_map(const coord_def &c) const
{
    return (lines[c.y][c.x] != ' ');
}

map_lines &map_lines::operator = (const map_lines &map)
{
    if (this != &map)
        init_from(map);
    return (*this);
}

map_lines::~map_lines()
{
    clear_markers();
}

void map_lines::init_from(const map_lines &map)
{
    // Markers have to be regenerated, they will not be copied.
    clear_markers();
    overlay.reset(NULL);
    lines            = map.lines;
    map_width        = map.map_width;
    solid_north      = map.solid_north;
    solid_east       = map.solid_east;
    solid_south      = map.solid_south;
    solid_west       = map.solid_west;
    solid_checked    = map.solid_checked;
}

template <typename V>
void map_lines::clear_vector(V &vect)
{
    for (int i = 0, vsize = vect.size(); i < vsize; ++i)
        delete vect[i];
    vect.clear();
}

void map_lines::clear_markers()
{
    clear_vector(markers);
}

void map_lines::add_marker(map_marker *marker)
{
    markers.push_back(marker);
}

std::string map_lines::add_feature_marker(const std::string &s)
{
    std::string key, arg;
    int sep = 0;
    std::string err = mapdef_split_key_item(s, &key, &sep, &arg, -1);
    if (!err.empty())
        return (err);

    map_marker_spec spec(key, arg);
    spec.apply_transform(*this);

    return ("");
}

std::string map_lines::add_lua_marker(const std::string &key,
                                      const lua_datum &function)
{
    map_marker_spec spec(key, function);
    spec.apply_transform(*this);
    return ("");
}

void map_lines::apply_markers(const coord_def &c)
{
    for (int i = 0, vsize = markers.size(); i < vsize; ++i)
    {
        markers[i]->pos += c;
        env.markers.add(markers[i]);
    }
    // *not* clear_markers() since we've offloaded marker ownership to
    // the crawl env.
    markers.clear();
}

void map_lines::apply_grid_overlay(const coord_def &c)
{
    if (!overlay.get())
        return;

    for (int y = height() - 1; y >= 0; --y)
        for (int x = width() - 1; x >= 0; --x)
        {
            coord_def gc(c.x + x, c.y + y);

            const int colour = (*overlay)(x, y).colour;
            if (colour)
                dgn_set_grid_colour_at(gc, colour);

            const int property = (*overlay)(x, y).property;
            if (property >= FPROP_BLOODY)
                 // Over-ride whatever property is already there.
                env.pgrid(gc) |= property;
#ifdef USE_TILE
            int floor = (*overlay)(x, y).floortile;
            if (floor)
            {
                if (colour)
                    floor = tile_dngn_coloured(floor, colour);
                int offset = random2(tile_dngn_count(floor));
                env.tile_flv(gc).floor = floor + offset;
            }
            int rock = (*overlay)(x, y).rocktile;
            if (rock)
            {
                if (colour)
                    rock = tile_dngn_coloured(rock, colour);
                int offset = random2(tile_dngn_count(rock));
                env.tile_flv(gc).wall = rock + offset;
            }
            int tile = (*overlay)(x, y).tile;
            if (tile)
            {
                if (colour)
                    tile = tile_dngn_coloured(tile, colour);
                int offset = random2(tile_dngn_count(tile));
                if (grd(gc) == DNGN_FLOOR && !floor)
                    env.tile_flv(gc).floor = tile + offset;
                else if (grd(gc) == DNGN_ROCK_WALL && !rock)
                    env.tile_flv(gc).wall = tile + offset;
                else
                {
                    if ((*overlay)(x, y).no_random)
                        offset = 0;
                    env.tile_flv(gc).feat = tile + offset;
                }
            }
#endif
        }
}

void map_lines::apply_overlays(const coord_def &c)
{
    apply_markers(c);
    apply_grid_overlay(c);
}

const std::vector<std::string> &map_lines::get_lines() const
{
    return (lines);
}

std::vector<std::string> &map_lines::get_lines()
{
    return (lines);
}

void map_lines::add_line(const std::string &s)
{
    lines.push_back(s);
    if (static_cast<int>(s.length()) > map_width)
        map_width = s.length();
}

std::string map_lines::clean_shuffle(std::string s)
{
    return (replace_all_of(s, " \t", ""));
}

std::string map_lines::check_block_shuffle(const std::string &s)
{
    const std::vector<std::string> segs = split_string("/", s);
    const unsigned seglen = segs[0].length();

    for (int i = 1, vsize = segs.size(); i < vsize; ++i)
    {
        if (seglen != segs[i].length())
            return ("block shuffle segment length mismatch");
    }

    return ("");
}

std::string map_lines::check_shuffle(std::string &s)
{
    if (s.find(',') != std::string::npos)
        return ("use / for block shuffle, or multiple SHUFFLE: lines");

    s = clean_shuffle(s);

    if (s.find('/') != std::string::npos)
        return check_block_shuffle(s);

    return ("");
}

std::string map_lines::parse_glyph_replacements(std::string s,
                                                glyph_replacements_t &gly)
{
    s = replace_all_of(s, "\t", " ");
    std::vector<std::string> segs = split_string(" ", s);

    for (int i = 0, vsize = segs.size(); i < vsize; ++i)
    {
        const std::string &is = segs[i];
        if (is.length() > 2 && is[1] == ':')
        {
            const int glych = is[0];
            int weight = atoi( is.substr(2).c_str() );
            if (weight < 1)
                weight = 10;

            gly.push_back( glyph_weighted_replacement_t(glych, weight) );
        }
        else
        {
            for (int c = 0, cs = is.length(); c < cs; ++c)
                gly.push_back( glyph_weighted_replacement_t(is[c], 10) );
        }
    }

    return ("");
}

template<class T>
std::string parse_weighted_str(const std::string &spec, T &list)
{
    std::vector<std::string> speclist = split_string("/", spec);
    for (int i = 0, vsize = speclist.size(); i < vsize; ++i)
    {
        std::string val = speclist[i];
        lowercase(val);

        int weight = find_weight(val);
        if (weight == TAG_UNFOUND)
        {
            weight = 10;
            // :number suffix?
            std::string::size_type cpos = val.find(':');
            if (cpos != std::string::npos)
            {
                weight = atoi(val.substr(cpos + 1).c_str());
                if (weight <= 0)
                    weight = 10;
                val.erase(cpos);
                trim_string(val);
            }
        }

        if (!list.parse(val, weight))
        {
            return make_stringf("bad spec: '%s' in '%s'",
                                val.c_str(), spec.c_str());
        }
    }
    return ("");
}

bool map_colour_list::parse(const std::string &col, int weight)
{
    const int colour = col == "none" ? BLACK : str_to_colour(col, -1);
    if (colour == -1)
        return (false);

    push_back(map_weighted_colour(colour, weight));
    return (true);
}

std::string map_lines::add_colour(const std::string &sub)
{
    std::string s = trimmed_string(sub);

    if (s.empty())
        return ("");

    int sep = 0;
    std::string key;
    std::string substitute;

    std::string err = mapdef_split_key_item(sub, &key, &sep, &substitute, -1);
    if (!err.empty())
        return (err);

    map_colour_list colours;
    err = parse_weighted_str<map_colour_list>(substitute, colours);
    if (!err.empty())
        return (err);

    colour_spec spec(key, sep == ':', colours);
    overlay_colours(spec);

    return ("");
}

bool map_fprop_list::parse(const std::string &fp, int weight)
{
    unsigned long fprop;

    if (fp == "nothing")
        fprop = FPROP_NONE;
    else if (fp.empty())
        return (false);
    else if (str_to_fprop(fp) == FPROP_NONE)
        return false;
    else
        fprop = str_to_fprop(fp);

    push_back(map_weighted_fprop(fprop, weight));
    return true;
}

std::string map_lines::add_fproperty(const std::string &sub)
{
    std::string s = trimmed_string(sub);

    if (s.empty())
        return ("");

    int sep = 0;
    std::string key;
    std::string substitute;

    std::string err = mapdef_split_key_item(sub, &key, &sep, &substitute, -1);
    if (!err.empty())
        return (err);

    map_fprop_list fprops;
    err = parse_weighted_str<map_fprop_list>(substitute, fprops);
    if (!err.empty())
        return (err);

    fprop_spec spec(key, sep == ':', fprops);
    overlay_fprops(spec);

    return ("");
}

bool map_string_list::parse(const std::string &fp, int weight)
{
    push_back(map_weighted_string(fp, weight));
    return (!fp.empty());
}

std::string map_lines::add_subst(const std::string &sub)
{
    std::string s = trimmed_string(sub);

    if (s.empty())
        return ("");

    int sep = 0;
    std::string key;
    std::string substitute;

    std::string err = mapdef_split_key_item(sub, &key, &sep, &substitute, -1);
    if (!err.empty())
        return (err);

    glyph_replacements_t repl;
    err = parse_glyph_replacements(substitute, repl);
    if (!err.empty())
        return (err);

    subst_spec spec(key, sep == ':', repl);
    subst(spec);

    return ("");
}

std::string map_lines::parse_nsubst_spec(const std::string &s,
                                         subst_spec &spec)
{
    std::string key, arg;
    int sep;
    std::string err = mapdef_split_key_item(s, &key, &sep, &arg, -1);
    if (!err.empty())
        return err;
    const int count = key == "*"? -1 : atoi(key.c_str());
    if (!count)
        return make_stringf("Illegal spec: %s", s.c_str());

    glyph_replacements_t repl;
    err = parse_glyph_replacements(arg, repl);
    if (!err.empty())
        return (err);

    spec = subst_spec(count, sep == ':', repl);
    return ("");
}

std::string map_lines::add_nsubst(const std::string &s)
{
    std::vector<subst_spec> substs;

    int sep;
    std::string key, arg;

    std::string err = mapdef_split_key_item(s, &key, &sep, &arg, -1);
    if (!err.empty())
        return (err);

    std::vector<std::string> segs = split_string("/", arg);
    for (int i = 0, vsize = segs.size(); i < vsize; ++i)
    {
        std::string &ns = segs[i];
        if (ns.find('=') == std::string::npos
            && ns.find(':') == std::string::npos)
        {
            if (i < vsize - 1)
                ns = "1=" + ns;
            else
                ns = "*=" + ns;
        }
        subst_spec spec;
        err = parse_nsubst_spec(ns, spec);
        if (!err.empty())
            return (make_stringf("Bad NSUBST spec: %s (%s)",
                                 s.c_str(), err.c_str()));
        substs.push_back(spec);
    }

    nsubst_spec spec(key, substs);
    nsubst(spec);

    return ("");
}

std::string map_lines::add_shuffle(const std::string &raws)
{
    std::string s = raws;
    const std::string err = check_shuffle(s);

    if (err.empty())
        resolve_shuffle(s);

    return (err);
}

int map_lines::width() const
{
    return map_width;
}

int map_lines::height() const
{
    return lines.size();
}

void map_lines::extend(int min_width, int min_height, char fill)
{
    min_width = std::max(1, min_width);
    min_height = std::max(1, min_height);

    bool dirty = false;
    int old_width = width();
    int old_height = height();

    if (static_cast<int>(lines.size()) < min_height)
    {
        dirty = true;
        while (static_cast<int>(lines.size()) < min_height)
            add_line(std::string(min_width, fill));
    }

    if (width() < min_width)
    {
        dirty = true;
        lines[0] += std::string(min_width - width(), fill);
        map_width = std::max(map_width, min_width);
    }

    if (!dirty)
        return;

    normalise(fill);

    // Extend overlay matrix as well.
    if (overlay.get())
    {
        std::auto_ptr<overlay_matrix> new_overlay(
            new overlay_matrix(width(), height()));

        for (int y = 0; y < old_height; ++y)
            for (int x = 0; x < old_width; ++x)
                (*new_overlay)(x, y) = (*overlay)(x, y);

        overlay = new_overlay;
    }
}

coord_def map_lines::size() const
{
    return coord_def(width(), height());
}

int map_lines::glyph(int x, int y) const
{
    return lines[y][x];
}

int map_lines::glyph(const coord_def &c) const
{
    return glyph(c.x, c.y);
}

bool map_lines::is_solid(int gly) const
{
    return (gly == 'x' || gly == 'c' || gly == 'b' || gly == 'v' || gly == 't');
}

void map_lines::check_borders()
{
    if (solid_checked)
        return;

    const int wide = width(), high = height();

    solid_north = solid_south = true;
    for (int x = 0; x < wide && (solid_north || solid_south); ++x)
    {
        if (solid_north && !is_solid(glyph(x, 0)))
            solid_north = false;
        if (solid_south && !is_solid(glyph(x, high - 1)))
            solid_south = false;
    }

    solid_east = solid_west = true;
    for (int y = 0; y < high && (solid_east || solid_west); ++y)
    {
        if (solid_west && !is_solid(glyph(0, y)))
            solid_west = false;
        if (solid_east && !is_solid(glyph(wide - 1, y)))
            solid_east = false;
    }

    solid_checked = true;
}

bool map_lines::solid_borders(map_section_type border)
{
    check_borders();
    switch (border)
    {
    case MAP_NORTH: return solid_north;
    case MAP_SOUTH: return solid_south;
    case MAP_EAST:  return solid_east;
    case MAP_WEST:  return solid_west;
    case MAP_NORTHEAST: return solid_north && solid_east;
    case MAP_NORTHWEST: return solid_north && solid_west;
    case MAP_SOUTHEAST: return solid_south && solid_east;
    case MAP_SOUTHWEST: return solid_south && solid_west;
    default: return (false);
    }
}

void map_lines::clear()
{
    clear_markers();
    lines.clear();
    keyspecs.clear();
    overlay.reset(NULL);
    map_width = 0;
    solid_checked = false;

    // First non-legal character.
    next_keyspec_idx = 256;
}

void map_lines::subst(std::string &s, subst_spec &spec)
{
    std::string::size_type pos = 0;
    while ((pos = s.find_first_of(spec.key, pos)) != std::string::npos)
        s[pos++] = spec.value();
}

void map_lines::subst(subst_spec &spec)
{
    ASSERT(!spec.key.empty());
    for (int y = 0, ysize = lines.size(); y < ysize; ++y)
        subst(lines[y], spec);
}

void map_lines::overlay_colours(colour_spec &spec)
{
    if (!overlay.get())
        overlay.reset(new overlay_matrix(width(), height()));

    for (int y = 0, ysize = lines.size(); y < ysize; ++y)
    {
        std::string::size_type pos = 0;
        while ((pos = lines[y].find_first_of(spec.key, pos)) != std::string::npos)
        {
            (*overlay)(pos, y).colour = spec.get_colour();
            ++pos;
        }
    }
}

void map_lines::overlay_fprops(fprop_spec &spec)
{
    if (!overlay.get())
        overlay.reset(new overlay_matrix(width(), height()));

    for (int y = 0, ysize = lines.size(); y < ysize; ++y)
    {
        std::string::size_type pos = 0;
        while ((pos = lines[y].find_first_of(spec.key, pos))
               != std::string::npos)
        {
            (*overlay)(pos, y).property |= spec.get_property();
            ++pos;
        }
    }
}

void map_lines::fill_mask_matrix(const std::string &glyphs,
                                 const coord_def &tl,
                                 const coord_def &br,
                                 Matrix<bool> &flags)
{
    ASSERT(tl.x >= 0);
    ASSERT(tl.y >= 0);
    ASSERT(br.x < width());
    ASSERT(br.y < height());
    ASSERT(tl.x <= br.x);
    ASSERT(tl.y <= br.y);

    for (int y = tl.y; y <= br.y; ++y)
        for (int x = tl.x; x <= br.x; ++x)
        {
            int ox = x - tl.x;
            int oy = y - tl.y;
            flags(ox, oy) = (strchr(glyphs.c_str(), (*this)(x, y)) != NULL);
        }
}

void map_lines::merge_subvault(const coord_def &mtl, const coord_def &mbr,
                               const Matrix<bool> &mask, const map_def &vmap)
{
    const map_lines &vlines = vmap.map;

    // If vault is bigger than the mask region (mtl, mbr), then it gets
    // randomly centered.  (vtl, vbr) stores the vault's region.
    coord_def vtl = mtl;
    coord_def vbr = mbr;

    int width_diff = (mbr.x - mtl.x + 1) - vlines.width();
    int height_diff = (mbr.y - mtl.y + 1) - vlines.height();

    // Adjust vault coords with a random offset.
    int ox = random2(width_diff + 1);
    int oy = random2(height_diff + 1);
    vtl.x += ox;
    vtl.y += oy;
    vbr.x -= (width_diff - ox);
    vbr.y -= (height_diff - oy);

    if (!overlay.get())
        overlay.reset(new overlay_matrix(width(), height()));

    // Clear any markers in the vault's grid
    for (size_t i = 0; i < markers.size(); ++i)
    {
        map_marker *mm = markers[i];
        if (mm->pos.x >= vtl.x && mm->pos.x <= vbr.x
            && mm->pos.y >= vtl.y && mm->pos.y <= vbr.y)
        {
            // Erase this marker.
            markers[i] = markers[markers.size() - 1];
            markers.resize(markers.size() - 1);
            i--;
        }
    }

    // Copy markers and update locations.
    for (size_t i = 0; i < vlines.markers.size(); ++i)
    {
        map_marker *mm = vlines.markers[i];
        coord_def mapc = mm->pos + vtl;
        coord_def maskc = mapc - mtl;

        if (!mask(maskc.x, maskc.y))
            continue;

        map_marker *clone = mm->clone();
        clone->pos = mapc;
        add_marker(clone);
    }

    // Cache keyspecs we've already pushed into the extended keyspec space.
    // If !ksmap[key], then we haven't seen the 'key' glyph before.
    keyspec_map ksmap(0);

    for (int y = mtl.y; y <= mbr.y; ++y)
        for (int x = mtl.x; x <= mbr.x; ++x)
        {
            int mx = x - mtl.x;
            int my = y - mtl.y;
            if (!mask(mx, my))
                continue;

            // Outside subvault?
            if (x < vtl.x || x > vbr.x || y < vtl.y || y > vbr.y)
                continue;

            int vx = x - vtl.x;
            int vy = y - vtl.y;
            coord_def vc(vx, vy);

            char c = vlines(vc);
            if (c == ' ')
                continue;

            // Merge keyspecs.
            // Push vault keyspecs into extended keyspecs.
            // Push MONS/ITEM into KMONS/KITEM keyspecs.
            // Generate KFEAT keyspecs for any normal glyphs.
            int idx;
            if (ksmap[c])
            {
                // Already generated this keyed_pmapspec.
                idx = ksmap[c];
            }
            else
            {
                idx = next_keyspec_idx++;
                ASSERT(idx > 0);

                if (c != SUBVAULT_GLYPH)
                    ksmap[c] = idx;

                const keyed_mapspec *kspec = vlines.mapspec_at(vc);

                // If c is a SUBVAULT_GLYPH, it came from a sub-subvault.
                // Sub-subvaults should always have mapspecs at this point.
                ASSERT(c != SUBVAULT_GLYPH || kspec);

                if (kspec)
                {
                    // Copy vault keyspec into the extended keyspecs.
                    keyspecs[idx] = *kspec;
                    keyspecs[idx].key_glyph = SUBVAULT_GLYPH;
                }
                else if (map_def::valid_monster_array_glyph(c))
                {
                    // Translate monster array into keyed_mapspec
                    keyed_mapspec &km = keyspecs[idx];
                    km.key_glyph = SUBVAULT_GLYPH;

                    km.feat.feats.clear();
                    feature_spec spec(-1);
                    spec.glyph = '.';
                    km.feat.feats.insert(km.feat.feats.begin(), spec);

                    int slot = map_def::monster_array_glyph_to_slot(c);
                    km.mons.set_from_slot(vmap.mons, slot);
                }
                else if (map_def::valid_item_array_glyph(c))
                {
                    // Translate item array into keyed_mapspec
                    keyed_mapspec &km = keyspecs[idx];
                    km.key_glyph = SUBVAULT_GLYPH;

                    km.feat.feats.clear();
                    feature_spec spec(-1);
                    spec.glyph = '.';
                    km.feat.feats.insert(km.feat.feats.begin(), spec);

                    int slot = map_def::item_array_glyph_to_slot(c);
                    km.item.set_from_slot(vmap.items, slot);
                }
                else
                {
                    // Normal glyph.  Turn into a feature keyspec.
                    // This is valid for non-array items and monsters
                    // as well, e.g. '$' and '8'.
                    keyed_mapspec &km = keyspecs[idx];
                    km.key_glyph = SUBVAULT_GLYPH;
                    km.feat.feats.clear();

                    feature_spec spec(-1);
                    spec.glyph = c;
                    km.feat.feats.insert(km.feat.feats.begin(), spec);
                }
            }

            // Finally, handle merging the cell itself.

            // Glyph becomes SUBVAULT_GLYPH.  (The old glyph gets merged into a
            // keyspec, above).  This is so that the glyphs that are included
            // from a subvault are immutable by the parent vault.  Otherwise,
            // latent transformations (like KMONS or KITEM) from the parent
            // vault might confusingly modify a glyph from the subvault.
            //
            // NOTE: It'd be possible to allow subvaults to be modified by the
            // parent vault, but KMONS/KITEM/KFEAT/MONS/ITEM would have to
            // apply immediately instead of latently.  They would also then
            // need to be stored per-coord, rather than per-glyph.
            (*this)(x, y) = SUBVAULT_GLYPH;

            // Merge overlays
            if (vlines.overlay.get())
            {
                (*overlay)(x, y) = (*vlines.overlay)(vx, vy);
            }
            else
            {
                // Erase any existing overlay, as the vault's doesn't exist.
                (*overlay)(x, y) = overlay_def();
            }

            // Set keyspec index for this subvault.
            (*overlay)(x, y).keyspec_idx = idx;
        }
}

#ifdef USE_TILE
void map_lines::overlay_tiles(tile_spec &spec)
{
    if (!overlay.get())
        overlay.reset(new overlay_matrix(width(), height()));

    for (int y = 0, ysize = lines.size(); y < ysize; ++y)
    {
        std::string::size_type pos = 0;
        while ((pos = lines[y].find_first_of(spec.key, pos)) != std::string::npos)
        {
            if (spec.floor)
                (*overlay)(pos, y).floortile = spec.get_tile();
            else if (spec.feat)
                (*overlay)(pos, y).tile = spec.get_tile();
            else
                (*overlay)(pos, y).rocktile = spec.get_tile();

            (*overlay)(pos, y).no_random = spec.no_random;
            ++pos;
        }
    }
}
#endif

void map_lines::nsubst(nsubst_spec &spec)
{
    std::vector<coord_def> positions;
    for (int y = 0, ysize = lines.size(); y < ysize; ++y)
    {
        std::string::size_type pos = 0;
        while ((pos = lines[y].find_first_of(spec.key, pos)) != std::string::npos)
            positions.push_back(coord_def(pos++, y));
    }
    std::random_shuffle(positions.begin(), positions.end(), random2);

    int pcount = 0;
    const int psize = positions.size();
    for (int i = 0, vsize = spec.specs.size();
         i < vsize && pcount < psize; ++i)
    {
        const int nsubsts = spec.specs[i].count;
        pcount += apply_nsubst(positions, pcount, nsubsts, spec.specs[i]);
    }
}

int map_lines::apply_nsubst(std::vector<coord_def> &pos,
                             int start, int nsub,
                             subst_spec &spec)
{
    if (nsub == -1)
        nsub = pos.size();
    const int end = std::min(start + nsub, (int) pos.size());
    int substituted = 0;
    for (int i = start; i < end; ++i)
    {
        const int val = spec.value();
        const coord_def &c = pos[i];
        lines[c.y][c.x] = val;
        ++substituted;
    }
    return (substituted);
}

std::string map_lines::block_shuffle(const std::string &s)
{
    std::vector<std::string> segs = split_string("/", s);
    std::random_shuffle(segs.begin(), segs.end(), random2);
    return (comma_separated_line(segs.begin(), segs.end(), "/", "/"));
}

std::string map_lines::shuffle(std::string s)
{
    std::string result;

    if (s.find('/') != std::string::npos)
        return block_shuffle(s);

    // Inefficient brute-force shuffle.
    while (!s.empty())
    {
        const int c = random2( s.length() );
        result += s[c];
        s.erase(c, 1);
    }

    return (result);
}

void map_lines::resolve_shuffle(const std::string &shufflage)
{
    std::string toshuffle = shufflage;
    std::string shuffled = shuffle(toshuffle);

    if (toshuffle.empty() || shuffled.empty())
        return;

    for (int i = 0, vsize = lines.size(); i < vsize; ++i)
    {
        std::string &s = lines[i];

        for (int j = 0, len = s.length(); j < len; ++j)
        {
            const char c = s[j];
            std::string::size_type pos = toshuffle.find(c);
            if (pos != std::string::npos)
                s[j] = shuffled[pos];
        }
    }
}

void map_lines::normalise(char fillch)
{
    for (int i = 0, vsize = lines.size(); i < vsize; ++i)
    {
        std::string &s = lines[i];
        if (static_cast<int>(s.length()) < map_width)
            s += std::string( map_width - s.length(), fillch );
    }
}

// Should never be attempted if the map has a defined orientation, or if one
// of the dimensions is greater than the lesser of GXM,GYM.
void map_lines::rotate(bool clockwise)
{
    std::vector<std::string> newlines;

    // normalise() first for convenience.
    normalise();

    const int xs = clockwise? 0 : map_width - 1,
              xe = clockwise? map_width : -1,
              xi = clockwise? 1 : -1;

    const int ys = clockwise? (int) lines.size() - 1 : 0,
              ye = clockwise? -1 : (int) lines.size(),
              yi = clockwise? -1 : 1;

    for (int i = xs; i != xe; i += xi)
    {
        std::string line;

        for (int j = ys; j != ye; j += yi)
            line += lines[j][i];

        newlines.push_back(line);
    }

    if (overlay.get())
    {
        std::auto_ptr<overlay_matrix> new_overlay(
            new overlay_matrix( lines.size(), map_width ) );
        for (int i = xs, y = 0; i != xe; i += xi, ++y)
            for (int j = ys, x = 0; j != ye; j += yi, ++x)
                (*new_overlay)(x, y) = (*overlay)(i, j);
        overlay = new_overlay;
    }

    map_width = lines.size();
    lines     = newlines;
    rotate_markers(clockwise);
    solid_checked = false;
}

void map_lines::translate_marker(
    void (map_lines::*xform)(map_marker *, int),
    int par)
{
    for (int i = 0, vsize = markers.size(); i < vsize; ++i)
        (this->*xform)(markers[i], par);
}

void map_lines::vmirror_marker(map_marker *marker, int)
{
    marker->pos.y = height() - 1 - marker->pos.y;
}

void map_lines::hmirror_marker(map_marker *marker, int)
{
    marker->pos.x = width() - 1 - marker->pos.x;
}

void map_lines::rotate_marker(map_marker *marker, int clockwise)
{
    const coord_def c = marker->pos;
    if (clockwise)
        marker->pos = coord_def(width() - 1 - c.y, c.x);
    else
        marker->pos = coord_def(c.y, height() - 1 - c.x);
}

void map_lines::vmirror_markers()
{
    translate_marker(&map_lines::vmirror_marker);
}

void map_lines::hmirror_markers()
{
    translate_marker(&map_lines::hmirror_marker);
}

void map_lines::rotate_markers(bool clock)
{
    translate_marker(&map_lines::rotate_marker, clock);
}

void map_lines::vmirror()
{
    const int vsize = lines.size();
    const int midpoint = vsize / 2;

    for (int i = 0; i < midpoint; ++i)
    {
        std::string temp = lines[i];
        lines[i] = lines[vsize - 1 - i];
        lines[vsize - 1 - i] = temp;
    }

    if (overlay.get())
    {
        for (int i = 0; i < midpoint; ++i)
            for (int j = 0, wide = width(); j < wide; ++j)
                std::swap( (*overlay)(j, i),
                           (*overlay)(j, vsize - 1 - i) );
    }

    vmirror_markers();
    solid_checked = false;
}

void map_lines::hmirror()
{
    const int midpoint = map_width / 2;
    for (int i = 0, vsize = lines.size(); i < vsize; ++i)
    {
        std::string &s = lines[i];
        for (int j = 0; j < midpoint; ++j)
        {
            int c = s[j];
            s[j] = s[map_width - 1 - j];
            s[map_width - 1 - j] = c;
        }
    }

    if (overlay.get())
    {
        for (int i = 0, vsize = lines.size(); i < vsize; ++i)
            for (int j = 0; j < midpoint; ++j)
                std::swap( (*overlay)(j, i),
                           (*overlay)(map_width - 1 - j, i) );
    }

    hmirror_markers();
    solid_checked = false;
}

keyed_mapspec *map_lines::mapspec_for_key(int key)
{
    keyed_specs::iterator i = keyspecs.find(key);
    return i != keyspecs.end()? &i->second : NULL;
}

const keyed_mapspec *map_lines::mapspec_for_key(int key) const
{
    keyed_specs::const_iterator i = keyspecs.find(key);
    return i != keyspecs.end()? &i->second : NULL;
}

keyed_mapspec *map_lines::mapspec_at(const coord_def &c)
{
    int key = (*this)(c);

    if (key == SUBVAULT_GLYPH)
    {
        // Any subvault should create the overlay.
        ASSERT(overlay.get());
        if (!overlay.get())
            return NULL;

        key = (*overlay)(c.x, c.y).keyspec_idx;
        ASSERT(key);
        if (!key)
            return NULL;
    }

    return (mapspec_for_key(key));
}

const keyed_mapspec *map_lines::mapspec_at(const coord_def &c) const
{
    int key = (*this)(c);

    if (key == SUBVAULT_GLYPH)
    {
        // Any subvault should create the overlay and set the keyspec idx.
        ASSERT(overlay.get());
        if (!overlay.get())
            return NULL;

        key = (*overlay)(c.x, c.y).keyspec_idx;
        ASSERT(key);
        if (!key)
            return NULL;
    }

    return (mapspec_for_key(key));
}

std::string map_lines::add_key_field(
    const std::string &s,
    std::string (keyed_mapspec::*set_field)(const std::string &s, bool fixed),
    void (keyed_mapspec::*copy_field)(const keyed_mapspec &spec))
{
    int separator = 0;
    std::string key, arg;

    std::string err = mapdef_split_key_item(s, &key, &separator, &arg, -1);
    if (!err.empty())
        return (err);

    keyed_mapspec &kmbase = keyspecs[key[0]];
    kmbase.key_glyph = key[0];
    err = ((kmbase.*set_field)(arg, separator == ':'));
    if (!err.empty())
        return (err);

    size_t len = key.length();
    for (size_t i = 1; i < len; i++)
    {
        keyed_mapspec &km = keyspecs[key[i]];
        km.key_glyph = key[i];
        ((km.*copy_field)(kmbase));
    }

    return (err);
}

std::string map_lines::add_key_item(const std::string &s)
{
    return add_key_field(s, &keyed_mapspec::set_item,
                         &keyed_mapspec::copy_item);
}

std::string map_lines::add_key_feat(const std::string &s)
{
    return add_key_field(s, &keyed_mapspec::set_feat,
                         &keyed_mapspec::copy_feat);
}

std::string map_lines::add_key_mons(const std::string &s)
{
    return add_key_field(s, &keyed_mapspec::set_mons,
                         &keyed_mapspec::copy_mons);
}

std::string map_lines::add_key_mask(const std::string &s)
{
    return add_key_field(s, &keyed_mapspec::set_mask,
                         &keyed_mapspec::copy_mask);
}


std::vector<coord_def> map_lines::find_glyph(int gly) const
{
    std::vector<coord_def> points;
    for (int y = height() - 1; y >= 0; --y)
    {
        for (int x = width() - 1; x >= 0; --x)
        {
            const coord_def c(x, y);
            if ((*this)(c) == gly)
                points.push_back(c);
        }
    }
    return (points);
}

std::vector<coord_def> map_lines::find_glyph(const std::string &glyphs) const
{
    std::vector<coord_def> points;
    for (int y = height() - 1; y >= 0; --y)
    {
        for (int x = width() - 1; x >= 0; --x)
        {
            const coord_def c(x, y);
            if (glyphs.find((*this)(c)) != std::string::npos)
                points.push_back(c);
        }
    }
    return (points);
}

coord_def map_lines::find_first_glyph(int gly) const
{
    for (int y = 0, h = height(); y < h; ++y)
    {
        std::string::size_type pos = lines[y].find(gly);
        if (pos != std::string::npos)
            return coord_def(pos, y);
    }

    return coord_def(-1, -1);
}

coord_def map_lines::find_first_glyph(const std::string &glyphs) const
{
    for (int y = 0, h = height(); y < h; ++y)
    {
        std::string::size_type pos = lines[y].find_first_of(glyphs);
        if (pos != std::string::npos)
            return coord_def(pos, y);
    }
    return coord_def(-1, -1);
}

bool map_lines::find_bounds(int gly, coord_def &tl, coord_def &br) const
{
    tl = coord_def(width(), height());
    br = coord_def(-1, -1);

    if (width() == 0 || height() == 0)
        return false;

    for (rectangle_iterator ri(get_iter()); ri; ++ri)
    {
        const coord_def mc = *ri;
        if ((*this)(mc) != gly)
            continue;

        tl.x = std::min(tl.x, mc.x);
        tl.y = std::min(tl.y, mc.y);
        br.x = std::max(br.x, mc.x);
        br.y = std::max(br.y, mc.y);
    }

    return (br.x >= 0);
}

bool map_lines::find_bounds(const char *str, coord_def &tl, coord_def &br) const
{
    tl = coord_def(width(), height());
    br = coord_def(-1, -1);

    if (width() == 0 || height() == 0)
        return false;

    for (rectangle_iterator ri(get_iter()); ri; ++ri)
    {
        ASSERT(ri);
        const coord_def &mc = *ri;
        const size_t len = strlen(str);
        for (size_t i = 0; i < len; ++i)
        {
            if ((*this)(mc) == str[i])
            {
                tl.x = std::min(tl.x, mc.x);
                tl.y = std::min(tl.y, mc.y);
                br.x = std::max(br.x, mc.x);
                br.y = std::max(br.y, mc.y);
                break;
            }
        }
    }

    return (br.x >= 0);
}

bool map_lines::fill_zone(travel_distance_grid_t &tpd, const coord_def &start,
                          const coord_def &tl, const coord_def &br, int zone,
                          const char *wanted, const char *passable) const
{
    // This is the map_lines equivalent of _dgn_fill_zone.
    // It's unfortunately extremely similar, but not close enough to combine.

    bool ret = false;
    std::list<coord_def> points[2];
    int cur = 0;

    for (points[cur].push_back(start); !points[cur].empty(); )
    {
        for (std::list<coord_def>::const_iterator i = points[cur].begin();
             i != points[cur].end(); ++i)
        {
            const coord_def &c(*i);

            tpd[c.x][c.y] = zone;

            ret |= (wanted && strchr(wanted, (*this)(c)) != NULL);

            for (int yi = -1; yi <= 1; ++yi)
                for (int xi = -1; xi <= 1; ++xi)
                {
                    if (!xi && !yi)
                        continue;

                    const coord_def cp(c.x + xi, c.y + yi);
                    if (cp.x < tl.x || cp.x > br.x
                        || cp.y < tl.y || cp.y > br.y
                        || !in_bounds(cp) || tpd[cp.x][cp.y]
                        || passable && !strchr(passable, (*this)(cp)))
                    {
                        continue;
                    }

                    tpd[cp.x][cp.y] = zone;
                    points[!cur].push_back(cp);
                }
        }

        points[cur].clear();
        cur = !cur;
    }
    return (ret);
}

int map_lines::count_feature_in_box(const coord_def &tl, const coord_def &br,
                                    const char *feat) const
{
    int result = 0;
    for (rectangle_iterator ri(tl, br); ri; ++ri)
    {
        if (strchr(feat, (*this)(*ri)))
            result++;
    }

    return (result);
}

#ifdef USE_TILE
bool map_tile_list::parse(const std::string &s, int weight)
{
    unsigned int idx = 0;
    if (s != "none" && !tile_dngn_index(s.c_str(), idx))
        return false;

    push_back(map_weighted_tile((int)idx, weight));
    return true;
}

std::string map_lines::add_tile(const std::string &sub, bool is_floor, bool is_feat)
{
    std::string s = trimmed_string(sub);

    if (s.empty())
        return ("");

    bool no_random = strip_tag(s, "no_random");

    int sep = 0;
    std::string key;
    std::string substitute;

    std::string err = mapdef_split_key_item(s, &key, &sep, &substitute, -1);
    if (!err.empty())
        return (err);

    map_tile_list list;
    err = parse_weighted_str<map_tile_list>(substitute, list);
    if (!err.empty())
        return (err);

    tile_spec spec(key, sep == ':', no_random, is_floor, is_feat, list);
    overlay_tiles(spec);

    return ("");
}

std::string map_lines::add_rocktile(const std::string &sub)
{
    return add_tile(sub, false, false);
}

std::string map_lines::add_floortile(const std::string &sub)
{
    return add_tile(sub, true, false);
}

std::string map_lines::add_spec_tile(const std::string &sub)
{
    return add_tile(sub, false, true);
}

//////////////////////////////////////////////////////////////////////////
// tile_spec

int tile_spec::get_tile()
{
    if (chose_fixed)
        return fixed_tile;

    int chosen = 0;
    int cweight = 0;
    for (int i = 0, size = tiles.size(); i < size; ++i)
        if (x_chance_in_y(tiles[i].second, cweight += tiles[i].second))
            chosen = tiles[i].first;

    if (fix)
    {
        chose_fixed = true;
        fixed_tile = chosen;
    }
    return (chosen);
}

#endif

///////////////////////////////////////////////
// dlua_set_map

dlua_set_map::dlua_set_map(map_def *map)
{
    dlua.callfn("dgn_set_map", "m", map);
}

dlua_set_map::~dlua_set_map()
{
    dlua.callfn("dgn_set_map", 0, 0);
}

///////////////////////////////////////////////
// map_def
//

map_def::map_def()
    : name(), tags(), place(), depths(), orient(), chance(), weight(),
      weight_depth_mult(), weight_depth_div(), welcome_messages(), map(),
      mons(), items(), random_mons(), prelude("dlprelude"),
      mapchunk("dlmapchunk"), main("dlmain"),
      validate("dlvalidate"), veto("dlveto"),
      rock_colour(BLACK), floor_colour(BLACK), rock_tile(0), floor_tile(0),
      border_fill_type(DNGN_ROCK_WALL), index_only(false), cache_offset(0L),
      validating_map_flag(false)
{
    init();
}

void map_def::init()
{
    orient = MAP_NONE;
    name.clear();
    tags.clear();
    place.clear();
    depths.clear();
    prelude.clear();
    mapchunk.clear();
    main.clear();
    validate.clear();
    veto.clear();
    place_loaded_from.clear();
    reinit();

    // Subvault mask set and cleared externally.
    // It should *not* be in reinit.
    svmask = NULL;
}

void map_def::reinit()
{
    items.clear();
    random_mons.clear();
    level_flags.clear();
    branch_flags.clear();

    welcome_messages.clear();

    rock_colour = floor_colour = BLACK;
    rock_tile = floor_tile = 0;
    border_fill_type = DNGN_ROCK_WALL;

    // Chance of using this level. Nonzero chance should be used
    // sparingly. When selecting vaults for a place, first those
    // vaults with chance > 0 are considered, in the order they were
    // loaded (which is arbitrary). If random2(100) < chance, the
    // vault is picked, and all other vaults are ignored for that
    // random selection. weight is ignored if the vault is chosen
    // based on its chance.
    chance = 0;

    // If multiple alternative vaults have a chance, the order in which
    // they're tested is based on chance_priority: higher priority vaults
    // are checked first. Vaults with the same priority are tested in
    // unspecified order.
    chance_priority = 0;

    // Weight for this map. When selecting a map, if no map with a
    // nonzero chance is picked, one of the other eligible vaults is
    // picked with a probability of weight / (sum of weights of all
    // eligible vaults).
    weight = 10;

    // How to modify weight based on absolte dungeon depth.  This
    // needs to be done in the C++ code since the map's lua code doesnt'
    // get called again each time the depth changes.
    weight_depth_mult = 0;
    weight_depth_div  = 1;

    // Clearing the map also zaps map transforms.
    map.clear();
    mons.clear();
}

bool map_def::valid_item_array_glyph(int gly)
{
    return (gly >= 'd' && gly <= 'k');
}

int map_def::item_array_glyph_to_slot(int gly)
{
    ASSERT(map_def::valid_item_array_glyph(gly));
    return (gly - 'd');
}

bool map_def::valid_monster_glyph(int gly)
{
    return (gly >= '0' && gly <= '9');
}

bool map_def::valid_monster_array_glyph(int gly)
{
    return (gly >= '1' && gly <= '7');
}

int map_def::monster_array_glyph_to_slot(int gly)
{
    ASSERT(map_def::valid_monster_array_glyph(gly));
    return (gly - '1');
}

bool map_def::in_map(const coord_def &c) const
{
    return map.in_map(c);
}

int map_def::glyph_at(const coord_def &c) const
{
    return map(c);
}

void map_def::write_full(writer& outf)
{
    cache_offset = outf.tell();
    marshallShort(outf, MAP_CACHE_VERSION);   // Level indicator.
    marshallString4(outf, name);
    prelude.write(outf);
    mapchunk.write(outf);
    main.write(outf);
    validate.write(outf);
    veto.write(outf);
}

void map_def::read_full(reader& inf)
{
    // There's a potential race-condition here:
    // - If someone modifies a .des file while there are games in progress,
    // - a new Crawl process will overwrite the .dsc.
    // - older Crawl processes trying to reading the new .dsc will be hosed.
    // We could try to recover from the condition (by locking and
    // reloading the index), but it's easier to save the game at this
    // point and let the player reload.

    const short fp_version = unmarshallShort(inf);
    std::string fp_name;
    unmarshallString4(inf, fp_name);
    if (fp_version != MAP_CACHE_VERSION || fp_name != name)
    {
        save_game(true,
                  make_stringf("Level file cache for %s is out-of-sync! "
                               "Please reload your game.",
                               file.c_str()).c_str());
    }

    prelude.read(inf);
    mapchunk.read(inf);
    main.read(inf);
    validate.read(inf);
    veto.read(inf);
}

void map_def::load()
{
    if (!index_only)
        return;

    const std::string descache_base = get_descache_path(file, "");
    file_lock deslock(descache_base + ".lk", "rb", false);
    const std::string loadfile = descache_base + ".dsc";
    FILE *fp = fopen(loadfile.c_str(), "rb");
    fseek(fp, cache_offset, SEEK_SET);
    reader inf(fp);
    read_full(inf);
    fclose(fp);

    index_only = false;
}

std::vector<coord_def> map_def::find_glyph(int glyph) const
{
    return map.find_glyph(glyph);
}

coord_def map_def::find_first_glyph(int glyph) const
{
    return map.find_first_glyph(glyph);
}

coord_def map_def::find_first_glyph(const std::string &s) const
{
    return map.find_first_glyph(s);
}

void map_def::write_index(writer& outf) const
{
    if (!cache_offset)
        end(1, false, "Map %s: can't write index - cache offset not set!",
            name.c_str());
    marshallString4(outf, name);
    marshallString4(outf, place_loaded_from.filename);
    marshallLong(outf, place_loaded_from.lineno);
    marshallShort(outf, orient);
    // XXX: This is a hack. See the comment in l_dgn.cc.
    marshallShort(outf, static_cast<short>(border_fill_type));
    marshallLong(outf, chance_priority);
    marshallLong(outf, chance);
    marshallLong(outf, weight);
    marshallLong(outf, cache_offset);
    marshallString4(outf, tags);
    place.save(outf);
    write_depth_ranges(outf);
    prelude.write(outf);
}

void map_def::read_index(reader& inf)
{
    unmarshallString4(inf, name);
    unmarshallString4(inf, place_loaded_from.filename);
    place_loaded_from.lineno   = unmarshallLong(inf);
    orient       = static_cast<map_section_type>( unmarshallShort(inf) );
    // XXX: Hack. See the comment in l_dgn.cc.
    border_fill_type = static_cast<dungeon_feature_type>( unmarshallShort(inf) );
    chance_priority = unmarshallLong(inf);
    chance       = unmarshallLong(inf);
    weight       = unmarshallLong(inf);
    cache_offset = unmarshallLong(inf);
    unmarshallString4(inf, tags);
    place.load(inf);
    read_depth_ranges(inf);
    prelude.read(inf);
    index_only   = true;
}

void map_def::write_depth_ranges(writer& outf) const
{
    marshallShort(outf, depths.size());
    for (int i = 0, sz = depths.size(); i < sz; ++i)
        depths[i].write(outf);
}

void map_def::read_depth_ranges(reader& inf)
{
    depths.clear();
    const int nranges = unmarshallShort(inf);
    for (int i = 0; i < nranges; ++i)
    {
        level_range lr;
        lr.read(inf);
        depths.push_back(lr);
    }
}

void map_def::set_file(const std::string &s)
{
    prelude.set_file(s);
    mapchunk.set_file(s);
    main.set_file(s);
    validate.set_file(s);
    veto.set_file(s);
    file = get_base_filename(s);
}

std::string map_def::run_lua(bool run_main)
{
    dlua_set_map mset(this);

    int err = prelude.load(dlua);
    if (err == -1000)
        lua_pushnil(dlua);
    else if (err)
        return (prelude.orig_error());

    if (!run_main)
    {
        lua_pushnil(dlua);
        lua_pushnil(dlua);
    }
    else
    {
        err = mapchunk.load(dlua);
        if (err == -1000)
            lua_pushnil(dlua);
        else if (err)
            return (mapchunk.orig_error());

        err = main.load(dlua);
        if (err == -1000)
            lua_pushnil(dlua);
        else if (err)
            return (main.orig_error());
    }

    if (!dlua.callfn("dgn_run_map", 3, 0))
        return rewrite_chunk_errors(dlua.error);

    return (dlua.error);
}

bool map_def::test_lua_boolchunk(dlua_chunk &chunk, bool defval, bool croak)
{
    bool result = defval;
    dlua_set_map mset(this);

    int err = chunk.load(dlua);
    if (err == -1000)
        return (result);
    else if (err)
    {
        if (croak)
            end(1, false, "Lua error: %s", validate.orig_error().c_str());
        else
            mprf(MSGCH_ERROR, "Lua error: %s", validate.orig_error().c_str());
        return (result);
    }
    if (dlua.callfn("dgn_run_map", 1, 1))
        dlua.fnreturns(">b", &result);
    else
    {
        if (croak)
            end(1, false, "Lua error: %s",
                rewrite_chunk_errors(dlua.error).c_str());
        else
            mprf(MSGCH_ERROR, "Lua error: %s",
                 rewrite_chunk_errors(dlua.error).c_str());
    }
    return (result);
}

bool map_def::test_lua_validate(bool croak)
{
    return validate.empty() || test_lua_boolchunk(validate, false, croak);
}

bool map_def::test_lua_veto()
{
    return !veto.empty() && test_lua_boolchunk(veto, true);
}

std::string map_def::rewrite_chunk_errors(const std::string &s) const
{
    std::string res = s;
    if (prelude.rewrite_chunk_errors(res))
        return (res);
    if (mapchunk.rewrite_chunk_errors(res))
        return (res);
    if (main.rewrite_chunk_errors(res))
        return (res);
    if (validate.rewrite_chunk_errors(res))
        return (res);
    veto.rewrite_chunk_errors(res);
    return (res);
}

std::string map_def::validate_temple_map()
{
    std::vector<coord_def> altars = find_glyph('B');

    if (has_tag_prefix("temple_overflow_"))
    {
        std::vector<std::string> tag_list = get_tags();
        std::string              temple_tag;

        for (unsigned int i = 0; i < tag_list.size(); i++)
        {
            if (starts_with(tag_list[i], "temple_overflow_"))
            {
                temple_tag = tag_list[i];
                break;
            }
        }

        if (temple_tag.empty())
            return make_stringf("Unkown temple tag.");

        temple_tag = strip_tag_prefix(temple_tag, "temple_overflow_");

        if (temple_tag.empty())
            return ("Malformed temple_overflow_ tag");

        int num = atoi(temple_tag.c_str());

        if (num == 0)
        {
            temple_tag = replace_all(temple_tag, "_", " ");

            god_type god = string_to_god(temple_tag.c_str(), true);

            if (god == GOD_NO_GOD)
            {
                return make_stringf("Invalid god name '%s'",
                                    temple_tag.c_str());
            }

            // Assume that specialized single-god temples are set up
            // properly.
            return("");
        }
        else
        {
            if ( ( (unsigned long) num ) != altars.size() )
            {
                return make_stringf("Temple should contain %lu altars, but "
                                    "has %d.", altars.size(), num);
            }
        }
    }

    if (altars.empty())
        return ("Temple vault must contain at least one altar.");

    // TODO: check for substitutions and shuffles

    std::vector<coord_def> b_glyphs = map.find_glyph('B');
    for (std::vector<coord_def>::iterator i = b_glyphs.begin();
        i != b_glyphs.end(); ++i)
    {
        const keyed_mapspec *spec = map.mapspec_at(*i);
        if (spec != NULL && spec->feat.feats.size() > 0)
            return ("Can't change feat 'B' in temple (KFEAT)");
    }

    std::vector<god_type> god_list = temple_god_list();

    if (altars.size() > god_list.size())
        return ("Temple vault has too many altars");

    return ("");
}

std::string map_def::validate_map_def()
{
    unwind_bool valid_flag(validating_map_flag, true);

    std::string err = run_lua(true);
    if (!err.empty())
        return (err);

    fixup();
    resolve();
    test_lua_validate(true);

    if ((place.branch == BRANCH_ECUMENICAL_TEMPLE
         && place.level_type == LEVEL_DUNGEON)
        || has_tag_prefix("temple_overflow_"))
    {
        err = validate_temple_map();
        if (!err.empty())
            return err;
    }

    if (orient == MAP_FLOAT || is_minivault())
    {
        if (map.width() > GXM - MAPGEN_BORDER * 2
            || map.height() > GYM - MAPGEN_BORDER * 2)
        {
            return make_stringf(
                     "%s is too big: %dx%d - max %dx%d",
                     is_minivault()? "Minivault" : "Float",
                     map.width(), map.height(),
                     GXM - MAPGEN_BORDER * 2,
                     GYM - MAPGEN_BORDER * 2);
        }
    }
    else
    {
        if (map.width() > GXM
            || map.height() > GYM)
        {
            return make_stringf(
                     "Map is too big: %dx%d - max %dx%d",
                     map.width(), map.height(),
                     GXM, GYM);
        }
    }

    switch (orient)
    {
    case MAP_NORTH: case MAP_SOUTH:
        if (map.height() > GYM * 2 / 3)
            return make_stringf("Map too large - height %d (max %d)",
                                map.height(), GYM * 2 / 3);
        break;
    case MAP_EAST: case MAP_WEST:
        if (map.width() > GXM * 2 / 3)
            return make_stringf("Map too large - width %d (max %d)",
                                map.width(), GXM * 2 / 3);
        break;
    case MAP_NORTHEAST: case MAP_SOUTHEAST:
    case MAP_NORTHWEST: case MAP_SOUTHWEST:
    case MAP_FLOAT:
        if (map.width() > GXM * 2 / 3 || map.height() > GYM * 2 / 3)
            return make_stringf("Map too large - %dx%d (max %dx%d)",
                                map.width(), map.height(),
                                GXM * 2 / 3, GYM * 2 / 3);
        break;
    default:
        break;
    }

    dlua_set_map dl(this);
    return ("");
}

bool map_def::is_usable_in(const level_id &lid) const
{
    bool any_matched = false;
    for (int i = 0, sz = depths.size(); i < sz; ++i)
    {
        const level_range &lr = depths[i];
        if (lr.matches(lid))
        {
            if (lr.deny)
                return (false);
            any_matched = true;
        }
    }
    return (any_matched);
}

void map_def::add_depth(const level_range &range)
{
    depths.push_back(range);
}

void map_def::add_depths(depth_ranges::const_iterator s,
                         depth_ranges::const_iterator e)
{
    depths.insert(depths.end(), s, e);
}

bool map_def::has_depth() const
{
    return (!depths.empty());
}

bool map_def::is_minivault() const
{
    return (has_tag("minivault"));
}

// Tries to dock a floating vault - push it to one edge of the level.
// Docking will only succeed if two contiguous edges are all x/c/b/v
// (other walls prevent docking). If the vault's width is > GXM*2/3,
// it's also eligible for north/south docking, and if the height >
// GYM*2/3, it's eligible for east/west docking. Although docking is
// similar to setting the orientation, it doesn't affect 'orient'.
coord_def map_def::float_dock()
{
    const map_section_type orients[] =
        { MAP_NORTH, MAP_SOUTH, MAP_EAST, MAP_WEST,
          MAP_NORTHEAST, MAP_SOUTHEAST, MAP_NORTHWEST, MAP_SOUTHWEST };
    map_section_type which_orient = MAP_NONE;
    int norients = 0;

    for (unsigned i = 0; i < sizeof(orients) / sizeof(*orients); ++i)
    {
        if (map.solid_borders(orients[i]) && can_dock(orients[i])
            && one_chance_in(++norients))
        {
            which_orient = orients[i];
        }
    }

    if (which_orient == MAP_NONE || which_orient == MAP_FLOAT)
        return coord_def(-1, -1);

#ifdef DEBUG_DIAGNOSTICS
    mprf(MSGCH_DIAGNOSTICS, "Docking floating vault to %s",
         map_section_name(which_orient));
#endif
    return dock_pos(which_orient);
}

coord_def map_def::dock_pos(map_section_type norient) const
{
    const int minborder = 6;

    switch (norient)
    {
    case MAP_NORTH:
        return coord_def( (GXM - map.width()) / 2, minborder );
    case MAP_SOUTH:
        return coord_def( (GXM - map.width()) / 2,
                          GYM - minborder - map.height() );
    case MAP_EAST:
        return coord_def( GXM - minborder - map.width(),
                          (GYM - map.height()) / 2 );
    case MAP_WEST:
        return coord_def( minborder,
                          (GYM - map.height()) / 2 );
    case MAP_NORTHEAST:
        return coord_def( GXM - minborder - map.width(), minborder );
    case MAP_NORTHWEST:
        return coord_def( minborder, minborder );
    case MAP_SOUTHEAST:
        return coord_def( GXM - minborder - map.width(),
                          GYM - minborder - map.height() );
    case MAP_SOUTHWEST:
        return coord_def( minborder,
                          GYM - minborder - map.height() );
    default:
        return coord_def(-1, -1);
    }
}

bool map_def::can_dock(map_section_type norient) const
{
    switch (norient)
    {
    case MAP_NORTH: case MAP_SOUTH:
        return map.width() > GXM * 2 / 3;
    case MAP_EAST: case MAP_WEST:
        return map.height() > GYM * 2 / 3;
    default:
        return (true);
    }
}

coord_def map_def::float_random_place() const
{
    // Try to leave enough around the float for roomification.
    int minhborder = MAPGEN_BORDER + 11,
        minvborder = minhborder;

    if (GXM - 2 * minhborder < map.width())
        minhborder = (GXM - map.width()) / 2 - 1;

    if (GYM - 2 * minvborder < map.height())
        minvborder = (GYM - map.height()) / 2 - 1;

    return coord_def(
        random_range(minhborder, GXM - minhborder - map.width()),
        random_range(minvborder, GYM - minvborder - map.height()));
}

point_vector map_def::anchor_points() const
{
    point_vector points;
    for (int y = 0, cheight = map.height(); y < cheight; ++y)
        for (int x = 0, cwidth = map.width(); x < cwidth; ++x)
            if (map.glyph(x, y) == '@')
                points.push_back(coord_def(x, y));
    return (points);
}

coord_def map_def::float_aligned_place() const
{
    const point_vector our_anchors = anchor_points();
    const coord_def fail(-1, -1);

#ifdef DEBUG_DIAGNOSTICS
    mprf(MSGCH_DIAGNOSTICS,
         "Aligning floating vault with %lu points vs %lu reference points",
         our_anchors.size(), map_anchor_points.size());
#endif

    // Mismatch in the number of points we have to align, bail.
    if (our_anchors.size() != map_anchor_points.size())
        return (fail);

    // Align first point of both vectors, then check that the others match.
    const coord_def pos = map_anchor_points[0] - our_anchors[0];

    for (int i = 1, psize = map_anchor_points.size(); i < psize; ++i)
        if (pos + our_anchors[i] != map_anchor_points[i])
            return (fail);

    // Looking good, check bounds.
    if (!map_bounds(pos) || !map_bounds(pos + size() - 1))
        return (fail);

    // Go us!
    return (pos);
}

coord_def map_def::float_place()
{
    ASSERT(orient == MAP_FLOAT);

    coord_def pos(-1, -1);

    if (!map_anchor_points.empty())
        pos = float_aligned_place();
    else
    {
        if (coinflip())
            pos = float_dock();

        if (pos.x == -1)
            pos = float_random_place();
    }

    return (pos);
}

void map_def::hmirror()
{
    if (has_tag("no_hmirror"))
        return;

#ifdef DEBUG_DIAGNOSTICS
    mprf(MSGCH_DIAGNOSTICS, "Mirroring %s horizontally.", name.c_str());
#endif
    map.hmirror();

    switch (orient)
    {
    case MAP_EAST:      orient = MAP_WEST; break;
    case MAP_NORTHEAST: orient = MAP_NORTHWEST; break;
    case MAP_SOUTHEAST: orient = MAP_SOUTHWEST; break;
    case MAP_WEST:      orient = MAP_EAST; break;
    case MAP_NORTHWEST: orient = MAP_NORTHEAST; break;
    case MAP_SOUTHWEST: orient = MAP_SOUTHEAST; break;
    default: break;
    }
}

void map_def::vmirror()
{
    if (has_tag("no_vmirror"))
        return;

#ifdef DEBUG_DIAGNOSTICS
    mprf(MSGCH_DIAGNOSTICS, "Mirroring %s vertically.", name.c_str());
#endif
    map.vmirror();

    switch (orient)
    {
    case MAP_NORTH:     orient = MAP_SOUTH; break;
    case MAP_NORTHEAST: orient = MAP_SOUTHEAST; break;
    case MAP_NORTHWEST: orient = MAP_SOUTHWEST; break;

    case MAP_SOUTH:     orient = MAP_NORTH; break;
    case MAP_SOUTHEAST: orient = MAP_NORTHEAST; break;
    case MAP_SOUTHWEST: orient = MAP_NORTHWEST; break;
    default: break;
    }
}

void map_def::rotate(bool clock)
{
    if (has_tag("no_rotate"))
        return;

#define GMINM ((GXM) < (GYM)? (GXM) : (GYM))
    // Make sure the largest dimension fits in the smaller map bound.
    if (map.width() <= GMINM && map.height() <= GMINM)
    {
#ifdef DEBUG_DIAGNOSTICS
        mprf(MSGCH_DIAGNOSTICS, "Rotating %s %sclockwise.",
                name.c_str(),
                !clock? "anti-" : "");
#endif
        map.rotate(clock);

        // Orientation shifts for clockwise rotation:
        const map_section_type clockrotate_orients[][2] = {
            { MAP_NORTH,        MAP_EAST        },
            { MAP_NORTHEAST,    MAP_SOUTHEAST   },
            { MAP_EAST,         MAP_SOUTH       },
            { MAP_SOUTHEAST,    MAP_SOUTHWEST   },
            { MAP_SOUTH,        MAP_WEST        },
            { MAP_SOUTHWEST,    MAP_NORTHWEST   },
            { MAP_WEST,         MAP_NORTH       },
            { MAP_NORTHWEST,    MAP_NORTHEAST   },
        };
        const int nrots = sizeof(clockrotate_orients)
                            / sizeof(*clockrotate_orients);

        const int refindex = !clock;
        for (int i = 0; i < nrots; ++i)
            if (orient == clockrotate_orients[i][refindex])
            {
                orient = clockrotate_orients[i][!refindex];
                break;
            }
    }
}

void map_def::normalise()
{
    // Pad out lines that are shorter than max.
    map.normalise(' ');
}

std::string map_def::resolve()
{
    dlua_set_map dl(this);
    return ("");
}

void map_def::fixup()
{
    normalise();

    // Fixup minivaults into floating vaults tagged "minivault".
    if (orient == MAP_NONE)
    {
        orient = MAP_FLOAT;
        tags += " minivault ";
    }
}

bool map_def::has_tag(const std::string &tagwanted) const
{
    return !tags.empty() && !tagwanted.empty()
        && tags.find(" " + tagwanted + " ") != std::string::npos;
}

bool map_def::has_tag_prefix(const std::string &prefix) const
{
    return !tags.empty() && !prefix.empty()
        && tags.find(" " + prefix) != std::string::npos;
}

bool map_def::has_tag_suffix(const std::string &suffix) const
{
    return !tags.empty() && !suffix.empty()
        && tags.find(suffix + " ") != std::string::npos;
}

std::vector<std::string> map_def::get_tags() const
{
    return split_string(" ", tags);
}

keyed_mapspec *map_def::mapspec_at(const coord_def &c)
{
    return (map.mapspec_at(c));
}

const keyed_mapspec *map_def::mapspec_at(const coord_def &c) const
{
    return (map.mapspec_at(c));
}

std::string map_def::subvault_from_tagstring(const std::string &sub)
{
    std::string s = trimmed_string(sub);

    if (s.empty())
        return ("");

    int sep = 0;
    std::string key;
    std::string substitute;

    std::string err = mapdef_split_key_item(sub, &key, &sep, &substitute, -1);
    if (!err.empty())
        return (err);

    // Randomly picking a different vault per-glyph is not supported.
    if (sep != ':')
        return ("SUBVAULT does not support '='.  Use ':' instead.");

    map_string_list vlist;
    err = parse_weighted_str<map_string_list>(substitute, vlist);
    if (!err.empty())
        return (err);

    bool fix = false;
    string_spec spec(key, fix, vlist);

    // Although it's unfortunate to not be able to validate subvaults except a
    // run-time, this allows subvaults to reference maps by tag that may not
    // have been loaded yet.
    if (!is_validating())
        err = apply_subvault(spec);

    if (!err.empty())
        return (err);

    return ("");
}

std::string map_def::apply_subvault(string_spec &spec)
{
    // Find bounding box for key glyphs
    coord_def tl, br;
    if (!map.find_bounds(spec.key.c_str(), tl, br))
    {
        // No glyphs, so do nothing.
        return ("");
    }

    int vwidth = br.x - tl.x + 1;
    int vheight = br.y - tl.y + 1;
    Matrix<bool> flags(vwidth, vheight);
    map.fill_mask_matrix(spec.key, tl, br, flags);

    // Backup pre-subvault unique tags and names.
    const std::set<std::string> uniq_tags  = you.uniq_map_tags;
    const std::set<std::string> uniq_names = you.uniq_map_names;

    const int max_tries = 100;
    int ntries = 0;

    std::string tag = spec.get_property();
    while (++ntries <= max_tries)
    {
        // Each iteration, restore tags and names.  This is because this vault
        // may successfully load a subvault (registering its tag and name), but
        // then itself fail.
        you.uniq_map_tags = uniq_tags;
        you.uniq_map_names = uniq_names;

        const map_def *orig = random_map_for_tag(tag);
        if (!orig)
            return (make_stringf("No vault found for tag '%s'", tag.c_str()));

        map_def vault = *orig;

        vault.load();

        // Temporarily set the subvault mask so this subvault can know
        // that it is being generated as a subvault.
        vault.svmask = &flags;

        if (!resolve_subvault(vault))
            continue;

        ASSERT(vault.map.width() <= vwidth);
        ASSERT(vault.map.height() <= vheight);

        map.merge_subvault(tl, br, flags, vault);

        dgn_register_vault(vault);

        return ("");
    }

    // Failure, restore original unique tags and names.
    you.uniq_map_tags = uniq_tags;
    you.uniq_map_names = uniq_names;

    return (make_stringf("Could not fit '%s' in (%d,%d) to (%d, %d).",
                         tag.c_str(), tl.x, tl.y, br.x, br.y));
}

bool map_def::is_subvault() const
{
    return (svmask != NULL);
}

void map_def::apply_subvault_mask()
{
    if (!svmask)
        return;

    map.clear();
    map.extend(subvault_width(), subvault_height(), ' ');

    for (rectangle_iterator ri(map.get_iter()); ri; ++ri)
    {
        const coord_def mc = *ri;
        if (subvault_cell_valid(mc))
            map(mc) = '.';
        else
            map(mc) = ' ';
    }
}

bool map_def::subvault_cell_valid(const coord_def &c) const
{
    if (!svmask)
        return false;

    if (c.x < 0 || c.x >= subvault_width()
        || c.y < 0 || c.y >= subvault_height())
    {
        return false;
    }

    return (*svmask)(c.x, c.y);
}

int map_def::subvault_width() const
{
    if (!svmask)
        return 0;

    return (svmask->width());
}

int map_def::subvault_height() const
{
    if (!svmask)
        return 0;

    return (svmask->height());
}

int map_def::subvault_mismatch_count(const coord_def &offset) const
{
    int count = 0;
    if (!is_subvault())
        return (count);

    for (rectangle_iterator ri(map.get_iter()); ri; ++ri)
    {
        // Coordinate in the subvault
        const coord_def sc = *ri;
        // Coordinate in the mask
        const coord_def mc = sc + offset;

        bool valid_subvault_cell = (map(sc) != ' ');
        bool valid_mask = (*svmask)(mc.x, mc.y);

        if (valid_subvault_cell && !valid_mask)
            count++;
    }

    return (count);
}

///////////////////////////////////////////////////////////////////
// mons_list
//

mons_list::mons_list() : mons()
{
}

int mons_list::fix_demon(int demon) const
{
    if (demon >= -1)
        return (demon);

    demon = -100 - demon;

    return (summon_any_demon(static_cast<demon_class_type>(demon)));
}

mons_spec mons_list::pick_monster(mons_spec_slot &slot)
{
    int totweight = 0;
    mons_spec pick;

    for (mons_spec_list::iterator i = slot.mlist.begin();
         i != slot.mlist.end(); ++i)
    {
        const int weight = i->genweight;
        if (x_chance_in_y(weight, totweight += weight))
        {
            pick = *i;

            if (pick.mid < 0 && pick.fix_mons)
                pick.mid = i->mid = fix_demon(pick.mid);
        }
    }

    if (pick.mid < 0)
        pick = fix_demon(pick.mid);

    if (slot.fix_slot)
    {
        slot.mlist.clear();
        slot.mlist.push_back(pick);
        slot.fix_slot = false;
    }

    if (pick.mid == MONS_WEAPON_MIMIC && !pick.fix_mons)
        pick.mid = random_range(MONS_GOLD_MIMIC, MONS_POTION_MIMIC);

    return (pick);
}

mons_spec mons_list::get_monster(int index)
{
    if (index < 0 || index >= (int)mons.size())
        return (mons_spec(RANDOM_MONSTER));

    return (pick_monster(mons[index]));
}

mons_spec mons_list::get_monster(int slot_index, int list_index) const
{
    if (slot_index < 0 || slot_index >= (int)mons.size())
        return (mons_spec(RANDOM_MONSTER));

    const mons_spec_list &list = mons[slot_index].mlist;

    if (list_index < 0 || list_index >= (int)list.size())
        return (mons_spec(RANDOM_MONSTER));

    return (list[list_index]);
}

void mons_list::clear()
{
    mons.clear();
}

void mons_list::set_from_slot(const mons_list &list, int slot_index)
{
    clear();

    // Don't set anything if an invalid index.
    // Future calls to get_monster will just return a random monster.
    if (slot_index < 0 || (size_t)slot_index >= list.mons.size())
        return;

    mons.push_back(list.mons[slot_index]);
}

bool mons_list::check_mimic(const std::string &s, int *mid, bool *fix) const
{
    if (s == "mimic")
    {
        *mid = MONS_WEAPON_MIMIC;
        *fix = false;
        return (true);
    }
    else if (s == "gold mimic")
        *mid = MONS_GOLD_MIMIC;
    else if (s == "weapon mimic")
        *mid = MONS_WEAPON_MIMIC;
    else if (s == "armour mimic")
        *mid = MONS_ARMOUR_MIMIC;
    else if (s == "scroll mimic")
        *mid = MONS_SCROLL_MIMIC;
    else if (s == "potion mimic")
        *mid = MONS_POTION_MIMIC;
    else
        return (false);

    *fix = true;
    return (true);
}

void mons_list::parse_mons_spells(mons_spec &spec, const std::string &spells)
{
    spec.explicit_spells = true;
    spec.extra_monster_flags |= MF_SPELLCASTER;
    const std::vector<std::string> spell_names(split_string(";", spells));
    if (spell_names.size() > NUM_MONSTER_SPELL_SLOTS)
    {
        error = make_stringf("Too many monster spells (max %d) in %s",
                             NUM_MONSTER_SPELL_SLOTS,
                             spells.c_str());
        return;
    }
    for (unsigned i = 0, ssize = spell_names.size(); i < ssize; ++i)
    {
        const std::string spname(
            lowercase_string(replace_all_of(spell_names[i], "_", " ")));
        if (spname.empty() || spname == "." || spname == "none"
            || spname == "no spell")
        {
            spec.spells[i] = SPELL_NO_SPELL;
        }
        else
        {
            const spell_type sp(spell_by_name(spname));
            if (sp == SPELL_NO_SPELL)
            {
                error = make_stringf("Unknown spell name: '%s' in '%s'",
                                     spname.c_str(), spells.c_str());
                return;
            }
            if (!is_valid_mon_spell(sp))
            {
                error = make_stringf("Not a monster spell: '%s'",
                                     spname.c_str());
                return;
             }
            spec.spells[i] = sp;
        }
    }
}

mons_list::mons_spec_slot mons_list::parse_mons_spec(std::string spec)
{
    mons_spec_slot slot;

    slot.fix_slot = strip_tag(spec, "fix_slot");

    std::vector<std::string> specs = split_string("/", spec);

    for (int i = 0, ssize = specs.size(); i < ssize; ++i)
    {
        mons_spec mspec;
        std::string s = specs[i];

        std::string spells(strip_tag_prefix(s, "spells:"));
        if (!spells.empty())
        {
            parse_mons_spells(mspec, spells);
            if (!error.empty())
                return (slot);
        }

        std::vector<std::string> parts = split_string(";", s);
        std::string mon_str = parts[0];

        if (parts.size() > 2)
        {
            error = make_stringf("Too many semi-colons for '%s' spec.",
                                 mon_str.c_str());
            return (slot);
        }
        else if (parts.size() == 2)
        {
            // TODO: Allow for a "fix_slot" type tag which will cause
            // all monsters generated from this spec to have the
            // exact same equipment.
            std::string items_str = parts[1];
            items_str = replace_all(items_str, "|", "/");

            std::vector<std::string> segs = split_string(".", items_str);

            if (segs.size() > NUM_MONSTER_SLOTS)
            {
                error = make_stringf("More items than monster item slots "
                                     "for '%s'.", mon_str.c_str());
                return (slot);
            }

            for (int j = 0, isize = segs.size(); j < isize; ++j)
            {
                error = mspec.items.add_item(segs[j], false);
                if (!error.empty())
                    return (slot);
            }
        }

        mspec.genweight = find_weight(mon_str);
        if (mspec.genweight == TAG_UNFOUND || mspec.genweight <= 0)
            mspec.genweight = 10;

        if (strip_tag(mon_str, "priest_spells"))
        {
            mspec.extra_monster_flags &= ~MF_ACTUAL_SPELLS;
            mspec.extra_monster_flags |= MF_PRIEST;
        }

        if (strip_tag(mon_str, "actual_spells"))
        {
            mspec.extra_monster_flags &= ~MF_PRIEST;
            mspec.extra_monster_flags |= MF_ACTUAL_SPELLS;
        }

        mspec.fix_mons       = strip_tag(mon_str, "fix_mons");
        mspec.generate_awake = strip_tag(mon_str, "generate_awake");
        mspec.patrolling     = strip_tag(mon_str, "patrolling");
        mspec.band           = strip_tag(mon_str, "band");

        const std::string att = strip_tag_prefix(mon_str, "att:");
        if (att.empty() || att == "hostile")
            mspec.attitude = ATT_HOSTILE;
        else if (att == "friendly")
            mspec.attitude = ATT_FRIENDLY;
        else if (att == "good_neutral")
            mspec.attitude = ATT_GOOD_NEUTRAL;
        else if (att == "fellow_slime" || att == "strict_neutral")
            mspec.attitude = ATT_STRICT_NEUTRAL;
        else if (att == "neutral")
            mspec.attitude = ATT_NEUTRAL;

        // Useful for summoned monsters.
        if (strip_tag(mon_str, "seen"))
            mspec.extra_monster_flags |= MF_SEEN;

        if (!mon_str.empty() && isdigit(mon_str[0]))
        {
            // Look for space after initial digits.
            std::string::size_type pos =
                mon_str.find_first_not_of("0123456789");
            if (pos != std::string::npos && mon_str[pos] == ' ')
            {
                const std::string mcount = mon_str.substr(0, pos);
                const int count = atoi(mcount.c_str());
                if (count >= 1 && count <= 99)
                    mspec.quantity = count;

                mon_str = mon_str.substr(pos);
            }
        }

        // place:Elf:7 to choose monsters appropriate for that level,
        // for example.
        const std::string place = strip_tag_prefix(mon_str, "place:");
        if (!place.empty())
        {
            try
            {
                mspec.place = level_id::parse_level_id(place);
            }
            catch (const std::string &err)
            {
                error = err;
                return (slot);
            }
        }

        mspec.mlevel = strip_number_tag(mon_str, "lev:");
        if (mspec.mlevel == TAG_UNFOUND)
            mspec.mlevel = 0;

        mspec.hd = strip_number_tag(mon_str, "hd:");
        if (mspec.hd == TAG_UNFOUND)
            mspec.hd = 0;

        mspec.hp = strip_number_tag(mon_str, "hp:");
        if (mspec.hp == TAG_UNFOUND)
            mspec.hp = 0;

        int dur = strip_number_tag(mon_str, "dur:");
        if (dur == TAG_UNFOUND)
            dur = 0;
        else if (dur < 1 || dur > 6)
            dur = 0;

        mspec.abjuration_duration = dur;

        int summon_type = 0;
        std::string s_type = strip_tag_prefix(mon_str, "sum:");
        if (!s_type.empty())
        {
            // In case of spells!
            s_type = replace_all_of(s_type, "_", " ");
            summon_type = static_cast<int>(str_to_summon_type(s_type));
            if (summon_type == SPELL_NO_SPELL)
            {
                error = make_stringf("bad monster summon type: \"%s\"",
                                s_type.c_str());
                return (slot);
            }
            if (mspec.abjuration_duration == 0)
            {
                error = "marked summon with no duration";
                return (slot);
            }
        }

        mspec.summon_type = summon_type;

        std::string non_actor_summoner = strip_tag_prefix(mon_str, "nas:");
        if (!non_actor_summoner.empty())
        {
            non_actor_summoner = replace_all_of(non_actor_summoner, "_", " ");
            mspec.non_actor_summoner = non_actor_summoner;
            if (mspec.abjuration_duration == 0)
            {
                error = "marked summon with no duration";
                return (slot);
            }
        }

        std::string colour = strip_tag_prefix(mon_str, "col:");
        if (!colour.empty())
        {
            if (colour == "any")
                // XXX: Hack
                mspec.colour = -1;
            else
            {
                mspec.colour = str_to_colour(colour, BLACK);
                if (mspec.colour == BLACK)
                {
                    error = make_stringf("bad monster colour \"%s\" in \"%s\"",
                                         colour.c_str(), specs[i].c_str());
                    return (slot);
                }
            }
        }

        std::string tile = strip_tag_prefix(mon_str, "tile:");
#ifdef USE_TILE
        if (!tile.empty())
        {
            unsigned int index;
            if (!tile_player_index(tile.c_str(), index))
            {
                error = make_stringf("bad tile name: \"%s\".", tile.c_str());
                return (slot);
            }
            mspec.props["monster_tile"] = short(index);
        }
#endif

        std::string name = strip_tag_prefix(mon_str, "name:");
        if (!name.empty())
        {
            name = replace_all_of(name, "_", " ");
            mspec.monname = name;

            if (strip_tag(mon_str, "name_suffix") || strip_tag(mon_str, "n_suf"))
                mspec.extra_monster_flags |= MF_NAME_SUFFIX;
            else if (strip_tag(mon_str, "name_adjective") || strip_tag(mon_str, "n_adj"))
                mspec.extra_monster_flags |= MF_NAME_ADJECTIVE;
            else if (strip_tag(mon_str, "name_replace") || strip_tag(mon_str, "n_rpl"))
                mspec.extra_monster_flags |= MF_NAME_REPLACE;

            // We should be able to combine this with name_replace.
            if (strip_tag(mon_str, "name_descriptor") || strip_tag(mon_str, "n_des"))
                mspec.extra_monster_flags |= MF_NAME_DESCRIPTOR;
            // Reasoning for this setting both flags: it does nothing with the
            // description unless NAME_DESCRIPTOR is also set; thus, you end up
            // with bloated vault description lines akin to: "name:blah_blah
            // name_replace name_descrpitor name_definite".
            if (strip_tag(mon_str, "name_definite") || strip_tag(mon_str, "n_the"))
            {
                mspec.extra_monster_flags |= MF_NAME_DEFINITE;
                mspec.extra_monster_flags |= MF_NAME_DESCRIPTOR;
            }
        }

        trim_string(mon_str);

        if (mon_str == "8")
            mspec.mlevel = -8;
        else if (mon_str == "9")
            mspec.mlevel = -9;
        else if (check_mimic(mon_str, &mspec.mid, &mspec.fix_mons))
            ;
        else if (mspec.place.is_valid())
        {
            // For monster specs such as place:Orc:4 zombie, we may
            // have a monster modifier, in which case we set the
            // modifier in monbase.
            const mons_spec nspec = mons_by_name("orc " + mon_str);
            if (nspec.mid != MONS_PROGRAM_BUG)
            {
                // Is this a modified monster?
                if (nspec.monbase != MONS_PROGRAM_BUG
                    && mons_class_is_zombified(nspec.mid))
                {
                    mspec.monbase = static_cast<monster_type>(nspec.mid);
                }
            }
        }
        else if (mon_str != "0")
        {
            const mons_spec nspec = mons_by_name(mon_str);

            if (nspec.mid == MONS_PROGRAM_BUG)
            {
                error = make_stringf("unknown monster: \"%s\"",
                                     mon_str.c_str());
                return (slot);
            }

            mspec.mid     = nspec.mid;
            mspec.monbase = nspec.monbase;
            mspec.number  = nspec.number;
        }

        if (mspec.items.size() > 0)
        {
            monster_type mid = (monster_type)mspec.mid;
            if (mid == RANDOM_DRACONIAN
                || mid == RANDOM_BASE_DRACONIAN
                || mid == RANDOM_NONBASE_DRACONIAN)
            {
                mid = MONS_DRACONIAN;
            }

            if (mid >= NUM_MONSTERS)
            {
                error = "Can't give spec items to a random monster.";
                return (slot);
            }
            else if (mons_class_itemuse(mid) < MONUSE_STARTING_EQUIPMENT)
            {
                if (mid != MONS_DANCING_WEAPON || mspec.items.size() > 1)
                    error = make_stringf("Monster '%s' can't use items.",
                                         mon_str.c_str());
            }
        }

        slot.mlist.push_back(mspec);
    }

    return (slot);
}

std::string mons_list::add_mons(const std::string &s, bool fix)
{
    error.clear();

    mons_spec_slot slotmons = parse_mons_spec(s);
    if (!error.empty())
        return (error);

    if (fix)
    {
        slotmons.fix_slot = true;
        pick_monster(slotmons);
    }

    mons.push_back( slotmons );

    return (error);
}

std::string mons_list::set_mons(int index, const std::string &s)
{
    error.clear();

    if (index < 0)
        return (error = make_stringf("Index out of range: %d", index));

    mons_spec_slot slotmons = parse_mons_spec(s);
    if (!error.empty())
        return (error);

    if (index >= (int) mons.size())
    {
        mons.reserve(index + 1);
        mons.resize(index + 1, mons_spec_slot());
    }
    mons[index] = slotmons;
    return (error);
}

void mons_list::get_zombie_type(std::string s, mons_spec &spec) const
{
    static const char *zombie_types[] =
    {
        " zombie", " skeleton", " simulacrum", " spectre", NULL
    };

    // This order must match zombie_types, indexed from one.
    static const monster_type zombie_montypes[][2] =
    {     // small               // large
        { MONS_PROGRAM_BUG,      MONS_PROGRAM_BUG },
        { MONS_ZOMBIE_SMALL,     MONS_ZOMBIE_LARGE },
        { MONS_SKELETON_SMALL,   MONS_SKELETON_LARGE },
        { MONS_SIMULACRUM_SMALL, MONS_SIMULACRUM_LARGE },
        { MONS_SPECTRAL_THING,   MONS_SPECTRAL_THING },
    };

    int mod = ends_with(s, zombie_types);
    if (!mod)
    {
        const std::string spectre("spectral ");
        if (s.find(spectre) == 0)
        {
            mod = ends_with(" spectre", zombie_types);
            s = s.substr(spectre.length());
        }
        else
        {
            spec.mid = MONS_PROGRAM_BUG;
            return;
        }
    }
    else
    {
        s = s.substr(0, s.length() - strlen(zombie_types[mod - 1]));
    }

    trim_string(s);

    mons_spec base_monster = mons_by_name(s);
    if (base_monster.mid < 0)
        base_monster.mid = MONS_PROGRAM_BUG;
    spec.monbase = static_cast<monster_type>(base_monster.mid);
    spec.number = base_monster.number;

    const int zombie_size = mons_zombie_size(spec.monbase);
    if (!zombie_size)
    {
        spec.mid = MONS_PROGRAM_BUG;
        return;
    }

    spec.mid = zombie_montypes[mod][zombie_size - 1];
}

mons_spec mons_list::get_hydra_spec(const std::string &name) const
{
    int         nheads = -1;
    std::string prefix = name.substr(0, name.find("-"));

    nheads = atoi(prefix.c_str());
    if (nheads != 0)
        ;
    else if (prefix == "0")
        nheads = 0;
    else
    {
        // Might be "two-headed hydra" type string.
        for (int i = 0; i <= 20; ++i)
            if (number_in_words(i) == prefix)
            {
                nheads = i;
                break;
            }
    }

    if (nheads < 1)
        nheads = MONS_PROGRAM_BUG;  // What can I say? :P
    else if (nheads > 20)
    {
#if DEBUG || DEBUG_DIAGNOSTICS
        mprf(MSGCH_DIAGNOSTICS, "Hydra spec wants %d heads, clamping to 20.",
             nheads);
#endif
        nheads = 20;
    }

    return (mons_spec(MONS_HYDRA, MONS_NO_MONSTER, nheads));
}

mons_spec mons_list::get_slime_spec(const std::string &name) const
{
    std::string prefix = name.substr(0, name.find(" slime creature"));

    int slime_size = 1;

    if (prefix == "large")
        slime_size = 2;
    else if (prefix == "very large")
        slime_size = 3;
    else if (prefix == "enormous")
        slime_size = 4;
    else if (prefix == "titanic")
        slime_size = 5;
    else
    {
#if DEBUG || DEBUG_DIAGNOSTICS
        mprf(MSGCH_DIAGNOSTICS, "Slime spec wants invalid size '%s'",
             prefix.c_str());
#endif
     }

    return (mons_spec(MONS_SLIME_CREATURE, MONS_NO_MONSTER, slime_size));
}

// Handle draconians specified as:
// Exactly as in mon-data.h:
//    yellow draconian or draconian knight - the monster specified.
//
// Others:
//    any draconian => any random draconain
//    any base draconian => any unspecialised coloured draconian.
//    any nonbase draconian => any specialised coloured draconian.
//    any <colour> draconian => any draconian of the colour.
//    any nonbase <colour> draconian => any specialised drac of the colour.
//
mons_spec mons_list::drac_monspec(std::string name) const
{
    mons_spec spec;

    spec.mid = get_monster_by_name(name, true);

    // Check if it's a simple drac name, we're done.
    if (spec.mid != MONS_PROGRAM_BUG)
        return (spec);

    spec.mid = RANDOM_DRACONIAN;

    // Request for any draconian?
    if (starts_with(name, "any "))
        // Strip "any "
        name = name.substr(4);

    if (starts_with(name, "base "))
    {
        // Base dracs need no further work.
        return (RANDOM_BASE_DRACONIAN);
    }
    else if (starts_with(name, "nonbase "))
    {
        spec.mid = RANDOM_NONBASE_DRACONIAN;
        name = name.substr(8);
    }

    trim_string(name);

    // Match "any draconian"
    if (name == "draconian")
        return (spec);

    // Check for recognition again to match any (nonbase) <colour> draconian.
    const monster_type colour = get_monster_by_name(name, true);
    if (colour != MONS_PROGRAM_BUG)
    {
        spec.monbase = colour;
        return (spec);
    }

    // Only legal possibility left is <colour> boss drac.
    std::string::size_type wordend = name.find(' ');
    if (wordend == std::string::npos)
        return (MONS_PROGRAM_BUG);

    std::string scolour = name.substr(0, wordend);
    if ((spec.monbase = draconian_colour_by_name(scolour)) == MONS_PROGRAM_BUG)
        return (MONS_PROGRAM_BUG);

    name = trimmed_string(name.substr(wordend + 1));
    spec.mid = get_monster_by_name(name, true);

    // We should have a non-base draconian here.
    if (spec.mid == MONS_PROGRAM_BUG
        || mons_genus(spec.mid) != MONS_DRACONIAN
        || spec.mid == MONS_DRACONIAN
        || (spec.mid >= MONS_BLACK_DRACONIAN
            && spec.mid <= MONS_PALE_DRACONIAN))
    {
        return (MONS_PROGRAM_BUG);
    }

    return (spec);
}

mons_spec mons_list::mons_by_name(std::string name) const
{
    lowercase(name);

    name = replace_all_of( name, "_", " " );
    name = replace_all( name, "random", "any" );

    if (name == "nothing")
        return (-1);

    // Special casery:
    if (name == "pandemonium demon")
        return (MONS_PANDEMONIUM_DEMON);

    if (name == "any" || name == "any monster")
        return (RANDOM_MONSTER);

    if (name == "any demon")
        return (-100 - DEMON_RANDOM);

    if (name == "any lesser demon" || name == "lesser demon")
        return (-100 - DEMON_LESSER);

    if (name == "any common demon" || name == "common demon")
        return (-100 - DEMON_COMMON);

    if (name == "any greater demon" || name == "greater demon")
        return (-100 - DEMON_GREATER);

    if (name == "small zombie")
        return (MONS_ZOMBIE_SMALL);
    if (name == "large zombie")
        return (MONS_ZOMBIE_LARGE);

    if (name == "small skeleton")
        return (MONS_SKELETON_SMALL);
    if (name == "large skeleton")
        return (MONS_SKELETON_LARGE);

    if (name == "spectral thing")
        return (MONS_SPECTRAL_THING);

    if (name == "small simulacrum")
        return (MONS_SIMULACRUM_SMALL);
    if (name == "large simulacrum")
        return (MONS_SIMULACRUM_LARGE);

    if (name == "small abomination")
        return (MONS_ABOMINATION_SMALL);
    if (name == "large abomination")
        return (MONS_ABOMINATION_LARGE);

    if (ends_with(name, "-headed hydra") && !starts_with(name, "spectral "))
        return (get_hydra_spec(name));

    if (ends_with(name, " slime creature"))
        return (get_slime_spec(name));

    mons_spec spec;
    get_zombie_type(name, spec);
    if (spec.mid != MONS_PROGRAM_BUG)
        return (spec);

    if (name.find("draconian") != std::string::npos)
        return drac_monspec(name);

    return (get_monster_by_name(name, true));
}

//////////////////////////////////////////////////////////////////////
// item_list

void item_list::clear()
{
    items.clear();
}

item_spec item_list::pick_item(item_spec_slot &slot)
{
    int cumulative = 0;
    item_spec spec;
    for (item_spec_list::const_iterator i = slot.ilist.begin();
         i != slot.ilist.end(); ++i)
    {
        const int weight = i->genweight;
        if (x_chance_in_y(weight, cumulative += weight))
            spec = *i;
    }

    if (slot.fix_slot)
    {
        slot.ilist.clear();
        slot.ilist.push_back(spec);
        slot.fix_slot = false;
    }

    return (spec);
}

item_spec item_list::get_item(int index)
{
    if (index < 0 || index >= (int) items.size())
    {
        const item_spec none;
        return (none);
    }

    return (pick_item(items[index]));
}

std::string item_list::add_item(const std::string &spec, bool fix)
{
    error.clear();

    item_spec_slot sp = parse_item_spec(spec);
    if (error.empty())
    {
        if (fix)
        {
            sp.fix_slot = true;
            pick_item(sp);
        }

        items.push_back(sp);
    }

    return (error);
}

std::string item_list::set_item(int index, const std::string &spec)
{
    error.clear();
    if (index < 0)
        return (error = make_stringf("Index %d out of range", index));
    item_spec_slot sp = parse_item_spec(spec);
    if (error.empty())
    {
        if (index >= (int) items.size())
        {
            items.reserve(index + 1);
            items.resize(index + 1, item_spec_slot());
        }
        items.push_back(sp);
    }

    return (error);
}

void item_list::set_from_slot(const item_list &list, int slot_index)
{
    clear();

    // Don't set anything if an invalid index.
    // Future calls to get_item will just return no item.
    if (slot_index < 0 || (size_t)slot_index >= list.items.size())
        return;

    items.push_back(list.items[slot_index]);
}

// TODO: More checking for innapropriate combinations, like the holy
// wrath brand on a demonic weapon or the running ego on a helmet.
static int str_to_ego(item_spec &spec, std::string ego_str)
{
    const char* armour_egos[] = {
        "running",
        "fire_resistance",
        "cold_resistance",
        "poison_resistance",
        "see_invisible",
        "darkness",
        "strength",
        "dexterity",
        "intelligence",
        "ponderousness",
        "levitation",
        "magic_resistance",
        "protection",
        "stealth",
        "resistance",
        "positive_energy",
        "archmagi",
        "preservation",
        "reflection",
        "spirit shield",
        "archery",
        NULL
    };

    const char* weapon_brands[] = {
        "flaming",        // 1
        "freezing",
        "holy_wrath",
        "electrocution",
        "orc_slaying",    // 5
        "dragon_slaying",
        "venom",
        "protection",
        "draining",
        "speed",          // 10
        "vorpal",
        "flame",
        "frost",
        "vampiricism",
        "pain",           // 15
        "distortion",
        "reaching",
        "returning",
        "chaos",
        "confuse",        // 20
        "penetration",
        "reaping",
        NULL
    };

    const char* missile_brands[] = {
        "flame",
        "ice",
        "poisoned",
        "curare",
        "returning",
        "chaos",
        "penetration",
        "reaping",
        "dispersal",
        "exploding",
        "steel",
        "silver",
        "electric",
        "paralysis",
        "slow",
        "sleep",
        "confusion",
        "sickness",
        "wrath",
        NULL
    };

    const char** name_lists[3] = {armour_egos, weapon_brands, missile_brands};

    int armour_order[3]  = {0, 1, 2};
    int weapon_order[3]  = {1, 0, 2};
    int missile_order[3] = {2, 0, 1};

    int *order;

    switch (spec.base_type)
    {
    case OBJ_ARMOUR:
        order = armour_order;
        break;

    case OBJ_WEAPONS:
        order = weapon_order;
        break;

    case OBJ_MISSILES:
        order = missile_order;
        break;

    default:
        DEBUGSTR("Bad base_type for ego'd item.");
        return 0;
    }

    const char** allowed = name_lists[order[0]];

    for (int i = 0; allowed[i] != NULL; i++)
    {
        if (ego_str == allowed[i])
            return (i + 1);
    }

    // Incompatible or non-existant ego type
    for (int i = 1; i <= 2; i++)
    {
        const char** list = name_lists[order[i]];

        for (int j = 0; list[j] != NULL; j++)
            if (ego_str == list[j])
                // Ego incompatible with base type.
                return (-1);
    }

    // Non-existant ego
    return 0;
}

int item_list::parse_acquirement_source(const std::string &source)
{
    const std::string god_name(replace_all_of(source, "_", " "));
    const god_type god(string_to_god(god_name.c_str(), true));
    if (god == GOD_NO_GOD)
        error = make_stringf("unknown god name: '%s'", god_name.c_str());
    return (god);
}

item_spec item_list::parse_single_spec(std::string s)
{
    item_spec result;

    // If there's a colon, this must be a generation weight.
    int weight = find_weight(s);
    if (weight != TAG_UNFOUND)
    {
        result.genweight = weight;
        if (result.genweight <= 0)
        {
            error = make_stringf("Bad item generation weight: '%d'",
                                 result.genweight);
            return (result);
        }
    }

    const int qty = strip_number_tag(s, "q:");
    if (qty != TAG_UNFOUND)
        result.qty = qty;

    const std::string acquirement_source = strip_tag_prefix(s, "acquire:");
    if (!acquirement_source.empty() || strip_tag(s, "acquire"))
    {
        if (!acquirement_source.empty())
            result.acquirement_source =
                parse_acquirement_source(acquirement_source);
        // If requesting acquirement, must specify item base type or
        // "any".
        result.level = ISPEC_ACQUIREMENT;
        if (s == "any")
            result.base_type = OBJ_RANDOM;
        else
            parse_random_by_class(s, result);
        return (result);
    }

    std::string ego_str  = strip_tag_prefix(s, "ego:");
    std::string race_str = strip_tag_prefix(s, "race:");
    lowercase(ego_str);
    lowercase(race_str);

    if (race_str == "elven")
        result.race = MAKE_ITEM_ELVEN;
    else if (race_str == "dwarven")
        result.race = MAKE_ITEM_DWARVEN;
    else if (race_str == "orcish")
        result.race = MAKE_ITEM_ORCISH;
    else if (race_str == "none" || race_str == "no_race")
        result.race = MAKE_ITEM_NO_RACE;
    else if (!race_str.empty())
    {
        error = make_stringf("Bad race: %s", race_str.c_str());
        return (result);
    }

    std::string unrand_str = strip_tag_prefix(s, "unrand:");

    if (strip_tag(s, "good_item"))
        result.level = MAKE_GOOD_ITEM;
    else
    {
        int number = strip_number_tag(s, "level:");
        if (number != TAG_UNFOUND)
        {
            if (number <= 0 && number != ISPEC_GOOD && number != ISPEC_SUPERB
                && number != ISPEC_DAMAGED && number != ISPEC_BAD)
            {
                error = make_stringf("Bad item level: %d", number);
                return (result);
            }

            result.level = number;
        }
    }

    if (strip_tag(s, "damaged"))
    {
        result.level = ISPEC_DAMAGED;
    }
    if (strip_tag(s, "cursed"))
    {
        result.level = ISPEC_BAD; // damaged + cursed, actually
    }
    if (strip_tag(s, "randart"))
    {
        result.level = ISPEC_RANDART;
    }

    if (strip_tag(s, "no_uniq"))
        result.allow_uniques = 0;
    if (strip_tag(s, "allow_uniq"))
        result.allow_uniques = 1;
    else
    {
        int uniq = strip_number_tag(s, "uniq:");
        if (uniq != TAG_UNFOUND)
        {
            if (uniq <= 0)
            {
                error = make_stringf("Bad uniq level: %d", uniq);
                return (result);
            }
            result.allow_uniques = uniq;
        }
    }

    // XXX: This is nice-ish now, but could probably do with being improved.
    if (strip_tag(s, "randbook"))
    {
        result.props["make_book_theme_randart"] = true;
        // make_book_theme_randart requires the following properties:
        // disc: <first discipline>, disc2: <optional second discipline>
        // numspells: <total number of spells>, slevels: <maximum levels>
        // spell: <include this spell>, owner:<name of owner>
        // None of these are required, but if you don't intend on using any
        // of them, use "any fixed theme book" instead.
        short disc1 = 0;
        short disc2 = 0;

        std::string st_disc1 = strip_tag_prefix(s, "disc:");
        if (!st_disc1.empty())
        {
            disc1 = school_by_name(st_disc1);
            if (disc1 == SPTYP_NONE)
            {
                error = make_stringf("Bad spell school: %s", st_disc1.c_str());
                return (result);
            }
        }

        std::string st_disc2 = strip_tag_prefix(s, "disc2:");
        if (!st_disc2.empty())
        {
            disc2 = school_by_name(st_disc2);
            if (disc2 == SPTYP_NONE)
            {
                error = make_stringf("Bad spell school: %s", st_disc2.c_str());
                return (result);
            }
        }

        if (disc1 != 0 && disc2 != 0)
        {
            if (disciplines_conflict(disc1, disc2))
            {
                error = make_stringf("Bad combination of spell schools: %s & %s.",
                                    st_disc1.c_str(), st_disc2.c_str());
                return (result);
            }
        }

        if (disc1 == 0 && disc2 != 0)
        {
            // Don't fail, just quietly swap. Any errors in disc1's syntax will
            // have been caught above, anyway.
            disc1 = disc2;
            disc2 = 0;
        }

        short num_spells = strip_number_tag(s, "numspells:");
        if (num_spells == TAG_UNFOUND)
            num_spells = -1;
        else if (num_spells <= 0 || num_spells > SPELLBOOK_SIZE)
        {
            error = make_stringf("Bad spellbook size: %d", num_spells);
            return (result);
        }

        short slevels = strip_number_tag(s, "slevels:");
        if (slevels == TAG_UNFOUND)
            slevels = -1;
        else if (slevels == 0)
        {
            error = make_stringf("Bad slevels: %d.", slevels);
            return (result);
        }

        const std::string spell = replace_all_of(strip_tag_prefix(s, "spell:"),
                                                "_", " ");
        if (!spell.empty() && spell_by_name(spell) == SPELL_NO_SPELL)
        {
            error = make_stringf("Bad spell: %s", spell.c_str());
            return (result);
        }

        const std::string owner = replace_all_of(strip_tag_prefix(s, "owner:"),
                                                "_", " ");
        result.props["randbook_disc1"] = disc1;
        result.props["randbook_disc2"] = disc2;
        result.props["randbook_num_spells"] = num_spells;
        result.props["randbook_slevels"] = slevels;
        result.props["randbook_spell"] = spell;
        result.props["randbook_owner"] = owner;

        result.base_type = OBJ_BOOKS;
        // This is changed in make_book_theme_randart.
        result.sub_type = BOOK_MINOR_MAGIC_I;
        result.plus = -1;

        return (result);
    }

    // Clean up after any tag brain damage.
    trim_string(s);

    if (!ego_str.empty())
        error = "Can't set an ego for random items.";

    // Completely random?
    if (s == "random" || s == "any" || s == "%")
        return (result);

    if (s == "*" || s == "star_item")
    {
        result.level = ISPEC_GOOD;
        return (result);
    }
    else if (s == "|" || s == "superb_item")
    {
        result.level = ISPEC_SUPERB;
        return (result);
    }
    else if (s == "$" || s == "gold")
    {
        if (!ego_str.empty())
            error = "Can't set an ego for gold.";

        result.base_type = OBJ_GOLD;
        result.sub_type = OBJ_RANDOM;
        return (result);
    }

    if (s == "nothing")
    {
        error.clear();
        result.base_type = OBJ_UNASSIGNED;
        return (result);
    }

    error.clear();

    // Check for "any objclass"
    if (s.find("any ") == 0)
        parse_random_by_class(s.substr(4), result);
    else if (s.find("random ") == 0)
        parse_random_by_class(s.substr(7), result);
    // Check for actual item names.
    else
        parse_raw_name(s, result);

    if (!error.empty())
        return (result);

    if (!unrand_str.empty())
    {
        result.ego = get_unrandart_num(unrand_str.c_str());
        if (result.ego == SPWPN_NORMAL)
        {
            error = make_stringf("Unknown unrand art: %s", unrand_str.c_str());
            return result;
        }
        result.ego = -result.ego;
        return result;
    }

    if (ego_str.empty())
        return (result);

    if (result.base_type != OBJ_WEAPONS
        && result.base_type != OBJ_MISSILES
        && result.base_type != OBJ_ARMOUR)
    {
        error = "An ego can only be applied to a weapon, missile or "
            "armour.";
        return (result);
    }

    if (ego_str == "none")
    {
        result.ego = -1;
        return (result);
    }

    const int ego = str_to_ego(result, ego_str);

    if (ego == 0)
    {
        error = make_stringf("No such ego as: %s", ego_str.c_str());
        return (result);
    }
    else if (ego == -1)
    {
        error = make_stringf("Ego '%s' is invalid for item '%s'.",
                             ego_str.c_str(), s.c_str());
        return (result);
    }
    else if (result.sub_type == OBJ_RANDOM)
    {
        // it will be assigned among appropriate ones later
    }
    else if (result.base_type == OBJ_WEAPONS
                && !is_weapon_brand_ok(result.sub_type, ego)
             || result.base_type == OBJ_ARMOUR
                && !is_armour_brand_ok(result.sub_type, ego)
             || result.base_type == OBJ_MISSILES
                && !is_missile_brand_ok(result.sub_type, ego))
    {
        error = make_stringf("Ego '%s' is incompatible with item '%s'.",
                             ego_str.c_str(), s.c_str());
        return (result);
    }
    result.ego = ego;

    return (result);
}

void item_list::parse_random_by_class(std::string c, item_spec &spec)
{
    trim_string(c);
    if (c == "?" || c.empty())
    {
        error = make_stringf("Bad item class: '%s'", c.c_str());
        return;
    }

    for (int type = OBJ_WEAPONS; type < NUM_OBJECT_CLASSES; ++type)
    {
        if (c == item_class_name(type, true))
        {
            spec.base_type = static_cast<object_class_type>(type);
            return;
        }
    }

    // Random manual?
    if (c == "manual")
    {
        spec.base_type = OBJ_BOOKS;
        spec.sub_type  = BOOK_MANUAL;
        spec.plus      = -1;
        return;
    }
    else if (c == "fixed theme book")
    {
        spec.base_type = OBJ_BOOKS;
        spec.sub_type  = BOOK_RANDART_THEME;
        spec.plus      = -1;
        return;
    }
    else if (c == "fixed level book")
    {
        spec.base_type = OBJ_BOOKS;
        spec.sub_type  = BOOK_RANDART_LEVEL;
        spec.plus      = -1;
        return;
    }

    error = make_stringf("Bad item class: '%s'", c.c_str());
}

void item_list::parse_raw_name(std::string name, item_spec &spec)
{
    trim_string(name);
    if (name.empty())
    {
        error = make_stringf("Bad item name: '%s'", name.c_str());
        return ;
    }

    item_def parsed = find_item_type(OBJ_UNASSIGNED, name);
    if (parsed.sub_type != OBJ_RANDOM)
    {
        spec.base_type = parsed.base_type;
        spec.sub_type  = parsed.sub_type;
        spec.plus      = parsed.plus;
        spec.plus2     = parsed.plus2;
        return;
    }

    error = make_stringf("Bad item name: '%s'", name.c_str());
}

item_list::item_spec_slot item_list::parse_item_spec(std::string spec)
{
    // lowercase(spec);

    item_spec_slot list;

    list.fix_slot = strip_tag(spec, "fix_slot");
    std::vector<std::string> specifiers = split_string( "/", spec );

    for (unsigned i = 0; i < specifiers.size() && error.empty(); ++i)
    {
        list.ilist.push_back( parse_single_spec(specifiers[i]) );
    }

    return (list);
}

/////////////////////////////////////////////////////////////////////////
// subst_spec

subst_spec::subst_spec(std::string _k, bool _f, const glyph_replacements_t &g)
    : key(_k), count(-1), fix(_f), frozen_value(0), repl(g)
{
}

subst_spec::subst_spec(int _count, bool dofix, const glyph_replacements_t &g)
    : key(""), count(_count), fix(dofix), frozen_value(0), repl(g)
{
}

int subst_spec::value()
{
    if (frozen_value)
        return (frozen_value);

    int cumulative = 0;
    int chosen = 0;
    for (int i = 0, size = repl.size(); i < size; ++i)
        if (x_chance_in_y(repl[i].second, cumulative += repl[i].second))
            chosen = repl[i].first;

    if (fix)
        frozen_value = chosen;

    return (chosen);
}

//////////////////////////////////////////////////////////////////////////
// nsubst_spec

nsubst_spec::nsubst_spec(std::string _key, const std::vector<subst_spec> &_specs)
    : key(_key), specs(_specs)
{
}

//////////////////////////////////////////////////////////////////////////
// colour_spec

int colour_spec::get_colour()
{
    if (fixed_colour != BLACK)
        return (fixed_colour);

    int chosen = BLACK;
    int cweight = 0;
    for (int i = 0, size = colours.size(); i < size; ++i)
        if (x_chance_in_y(colours[i].second, cweight += colours[i].second))
            chosen = colours[i].first;
    if (fix)
        fixed_colour = chosen;
    return (chosen);
}

//////////////////////////////////////////////////////////////////////////
// fprop_spec

unsigned long fprop_spec::get_property()
{
    if (fixed_prop != FPROP_NONE)
        return (fixed_prop);

    unsigned long chosen = FPROP_NONE;
    int cweight = 0;
    for (int i = 0, size = fprops.size(); i < size; ++i)
        if (x_chance_in_y(fprops[i].second, cweight += fprops[i].second))
            chosen = fprops[i].first;
    if (fix)
        fixed_prop = chosen;
    return (chosen);
}

//////////////////////////////////////////////////////////////////////////
// string_spec

std::string string_spec::get_property()
{
    if (!fixed_str.empty())
        return fixed_str;

    std::string chosen = "";
    int cweight = 0;
    for (int i = 0, size = strlist.size(); i < size; ++i)
        if (x_chance_in_y(strlist[i].second, cweight += strlist[i].second))
            chosen = strlist[i].first;
    if (fix)
        fixed_str = chosen;
    return (chosen);
}

//////////////////////////////////////////////////////////////////////////
// map_marker_spec

std::string map_marker_spec::apply_transform(map_lines &map)
{
    std::vector<coord_def> positions = map.find_glyph(key);

    // Markers with no key are not an error.
    if (positions.empty())
        return ("");

    for (int i = 0, size = positions.size(); i < size; ++i)
    {
        try
        {
            map_marker *mark = create_marker();
            if (!mark)
                return make_stringf("Unable to parse marker from %s",
                                    marker.c_str());
            mark->pos = positions[i];
            map.add_marker(mark);
        }
        catch (const std::string &err)
        {
            return (err);
        }
    }
    return ("");
}

map_marker *map_marker_spec::create_marker()
{
    return lua_fn.get()
        ? new map_lua_marker(*lua_fn.get())
        : map_marker::parse_marker(marker);
}

//////////////////////////////////////////////////////////////////////////
// map_flags
map_flags::map_flags()
    : flags_set(0), flags_unset(0)
{
}

void map_flags::clear()
{
    flags_set = flags_unset = 0;
}

typedef std::map<std::string, unsigned long> flag_map;

map_flags map_flags::parse(const std::string flag_list[],
                           const std::string &s) throw(std::string)
{
    map_flags mf;

    const std::vector<std::string> segs = split_string("/", s);

    flag_map flag_vals;
    for (int i = 0; !flag_list[i].empty(); i++)
        flag_vals[flag_list[i]] = 1 << i;

    for (int i = 0, size = segs.size(); i < size; i++)
    {
        std::string flag   = segs[i];
        bool        negate = false;

        if (flag[0] == '!')
        {
            flag   = flag.substr(1);
            negate = true;
        }

        flag_map::const_iterator val = flag_vals.find(flag);
        if (val == flag_vals.end())
            throw make_stringf("Unknown flag: '%s'", flag.c_str());

        if (negate)
            mf.flags_unset |= val->second;
        else
            mf.flags_set |= val->second;
    }

    return mf;
}

//////////////////////////////////////////////////////////////////////////
// keyed_mapspec

keyed_mapspec::keyed_mapspec()
    :  key_glyph(-1), feat(), item(), mons()
{
}

std::string keyed_mapspec::set_feat(const std::string &s, bool fix)
{
    err.clear();
    parse_features(s);
    feat.fix_slot = fix;

    // Fix this feature.
    if (fix)
        get_feat();

    return (err);
}

void keyed_mapspec::copy_feat(const keyed_mapspec &spec)
{
    feat = spec.feat;
}

void keyed_mapspec::parse_features(const std::string &s)
{
    feat.feats.clear();
    std::vector<std::string> specs = split_string("/", s);
    for (int i = 0, size = specs.size(); i < size; ++i)
    {
        const std::string &spec = specs[i];

        feature_spec_list feats = parse_feature(spec);
        if (!err.empty())
            return;
        feat.feats.insert( feat.feats.end(),
                           feats.begin(),
                           feats.end() );
    }
}

feature_spec keyed_mapspec::parse_trap(std::string s, int weight)
{
    strip_tag(s, "trap");
    const bool known = strip_tag(s, "known");

    trim_string(s);
    lowercase(s);

    const int trap = str_to_trap(s);
    if (trap == -1)
        err = make_stringf("bad trap name: '%s'", s.c_str());

    feature_spec fspec(known ? 1 : -1, weight);
    fspec.trap = trap;
    return (fspec);
}

feature_spec keyed_mapspec::parse_shop(std::string s, int weight)
{
    strip_tag(s, "shop");
    trim_string(s);
    lowercase(s);

    const int shop = str_to_shoptype(s);
    if (shop == -1)
        err = make_stringf("bad shop type: '%s'", s.c_str());

    feature_spec fspec(-1, weight);
    fspec.shop = shop;
    return (fspec);
}

feature_spec_list keyed_mapspec::parse_feature(const std::string &str)
{
    std::string s = str;
    int weight = find_weight(s);
    if (weight == TAG_UNFOUND || weight <= 0)
        weight = 10;
    trim_string(s);

    feature_spec_list list;
    if (s.length() == 1)
    {
        feature_spec fsp(-1, weight);
        fsp.glyph = s[0];
        list.push_back( fsp );
        return (list);
    }

    if (s.find("trap") != std::string::npos)
    {
        list.push_back( parse_trap(s, weight) );
        return (list);
    }

    if (s.find("shop") != std::string::npos
        || s.find("store") != std::string::npos)
    {
        list.push_back( parse_shop(s, weight) );
        return (list);
    }

    const dungeon_feature_type ftype = dungeon_feature_by_name(s);
    if (ftype == DNGN_UNSEEN)
        err = make_stringf("no features matching \"%s\"",
                           str.c_str());
    else
        list.push_back( feature_spec( ftype, weight ) );

    return (list);
}

std::string keyed_mapspec::set_mons(const std::string &s, bool fix)
{
    err.clear();
    mons.clear();

    std::vector<std::string> segments = split_string(",", s);
    for (int i = 0, size = segments.size(); i < size; ++i)
    {
        const std::string error = mons.add_mons(segments[i], fix);
        if (!error.empty())
            return (error);
    }

    return ("");
}

std::string keyed_mapspec::set_item(const std::string &s, bool fix)
{
    err.clear();
    item.clear();

    std::vector<std::string> segs = split_string(",", s);

    for (int i = 0, size = segs.size(); i < size; ++i)
    {
        err = item.add_item(segs[i], fix);
        if (!err.empty())
            return (err);
    }

    return (err);
}

std::string keyed_mapspec::set_mask(const std::string &s, bool garbage)
{
    UNUSED(garbage);

    err.clear();

    try
    {
        static std::string flag_list[] =
            {"vault", "no_item_gen", "no_monster_gen", "no_pool_fixup",
             "no_secret_doors", "no_wall_fixup", "opaque", ""};
        map_mask = map_flags::parse(flag_list, s);
    }
    catch (const std::string &error)
    {
        err = error;
        return (err);
    }

    // If not also a KFEAT...
    if (feat.feats.size() == 0)
    {
        feature_spec fsp(-1, 10);
        fsp.glyph = key_glyph;
        feat.feats.push_back(fsp);
    }

    return (err);
}

void keyed_mapspec::copy_mons(const keyed_mapspec &spec)
{
    mons = spec.mons;
}

void keyed_mapspec::copy_item(const keyed_mapspec &spec)
{
    item = spec.item;
}

void keyed_mapspec::copy_mask(const keyed_mapspec &spec)
{
    map_mask = spec.map_mask;
}

feature_spec keyed_mapspec::get_feat()
{
    return feat.get_feat('.');
}

mons_list &keyed_mapspec::get_monsters()
{
    return (mons);
}

item_list &keyed_mapspec::get_items()
{
    return (item);
}

map_flags &keyed_mapspec::get_mask()
{
    return (map_mask);
}

//////////////////////////////////////////////////////////////////////////
// feature_slot

feature_slot::feature_slot() : feats(), fix_slot(false)
{
}

feature_spec feature_slot::get_feat(int def_glyph)
{
    int tweight = 0;
    feature_spec chosen_feat = feature_spec(DNGN_FLOOR);

    if (def_glyph != -1)
    {
        chosen_feat.feat = -1;
        chosen_feat.glyph = def_glyph;
    }

    for (int i = 0, size = feats.size(); i < size; ++i)
    {
        const feature_spec &feat = feats[i];
        if (x_chance_in_y(feat.genweight, tweight += feat.genweight))
            chosen_feat = feat;
    }

    if (fix_slot)
    {
        feats.clear();
        feats.push_back( chosen_feat );
    }
    return (chosen_feat);
}