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



                                                


                   
 

                 

                  

                    
                          
                   
      
 
                    
                     
                    
                
                 
                  
                       
                    
                   
                     
                      
                  
                   
                  
                 
                    
 
                                     
                                         
                                         
                                  
                                     
                                          
                                          
 
                                      
 


                                       
                                                         


                                                                      
 

                                                                          
 

                                        
 














                                                                       
                                                                     
 
                                                                    



                                                                   
                                                                           
                                                                        
                                                             
                                 

                        
                                                           
                                                        
 
                                 
                                      

      


                                                                   

                                                                             
 
 
                                     
                                              
                                        
 
                
 
                                             
                     
                               
 
                                                                   





                                                                       
                                    

                     

                                                                          
                                                        
                                                                  
 
                                     
                                  
     
                      

 
                                         
 
                 


                                        



                                          
                                                        

                       

                


                        
                                                    

                       
 
                                      
                       
 






                                                                    





                                                                    
 



                                                                            
 

                      
 

                                             

                               
                  
































































































                                                                               
 
                                                                  
                  

 

                                                                      
 














                                                                   

 








                                                                      
                             

                                     
                                 
                                   
                                      



                       
                                                 





                                                    


                                                



                      

                                                                  



                                                                

                                   



                                     








                                                                         







                                                                       

                                                     












































                                                                          
         
                                



                                                           
                     
         
 

                                                   
                                






                                                                           



                               
                                          
                                                    
                                              
 

                                  

                                    
 
                          
 
                                                      
                                   
     
                               
     
                                                     
                                   
     
                               
     
 


                                                                

                                    
     
                                     
     
                                                 

                                    
     
                                     
     
 


                                                                         
                                                        

                                  
                                         






                                                                       


                                      

     


                           
                                                          
     
                                                    
                                               


                       
 
                       
                      

                  

 
                                  
                     
                               
                         
 
                                                   
                          
 


                            
 
                    

 

                                                                           
 





                                                               
 





                                                                         
                                                                          
                                                              


                                                              

 


                                                                  
                      



                                                    
                                                        


                                                            
                               
 
                  

 


                                                                  
 





                                                            









                                                                                
                                       




















                                                                                  







                     
 


                                         
 
                      
     

                                                
 
                                                                  
     
                                                                         
     
 



                                                                        
 
                                                      

                                                                           
     
                                                          
                                                




























                                                                    

                                             
                                            
                                                  

                                                            













                                                                          
                                                  

                                                            


                                                                     
                                   










                                                       
                        










                                                                 
      
 
 


                                                                 

 
                                                        
 











                                                             


                                                                         
 










                                                                
 


                                                                        
 

                      
 







                                                       
 



                                                               
         







                                                                
             


                                            
             
         




                                  
 

                                                                            
 











                                                                     
                                                                    

                                                                         


                                                     
 



                          
 
                       
     



                                                                          

                                                                
         
                                          






                                                                          
 
                                                            
                              
         

     


                                             
                      
     
 
                           
 
                                                     

 
                                                                      




                                                                    
                                                          
                                                                          
 
                                                                             

 
                                                                              




                                                       
                                                         
                                                   

                                   
                                                

 




                          
                                             


                                       
                                                       

 

                                                                             
 




                                               



                               
                                  
                               









                                     
                              

                 
                                 





                                                                            



                                 
 
















                                                                         

                                         
                       


                                         
 
                                      













                                                     
                                                    
     





                                                        
 

                                                           
     
 

                                                    
                                                                   

                   
                                                               

                                           



                                              




                                                           
     
               







                                                                      
 




                                                          
 







                                                                            

                                                  




                                

                                            
                                  
               




                                                                             

                                          

                                                                     

                                          

                                    
               




                                                                              

                                          

                                                                     


                                              

                                    
               






                                                                              
 
                                                   
 




                                           
                                            
 

                                                          
               

                                
 


                          

                                      
                                                                 
 
                       
 


                             
 
                                     

                
 
                                                   
                                                 

 
                                      
 

                                                   
 



                                                           


                                                    
                           

                        

 
                                        
 
                     
 

                           
 


                       





                                                                 
                                                                               

         





                                                       
                                                           



                                                         
 




                                      




                                                 











                                                                           
 


                          



                                                   





                                                               
                                   
     

                                                               

                                   
                                    





                                                            

     







                                     





                                                                    
                                                                







                                                     
                                       
























                                                             
/*
 *  File:       maps.cc
 *  Summary:    Functions used to create vaults.
 *  Written by: Linley Henzell
 */

#include "AppHdr.h"

#include "maps.h"

#include <cstring>
#include <cstdlib>
#include <algorithm>

#ifndef TARGET_COMPILER_VC
#include <unistd.h>
#endif

#include "coordit.h"
#include "dbg-maps.h"
#include "dungeon.h"
#include "env.h"
#include "enum.h"
#include "files.h"
#include "flood_find.h"
#include "message.h"
#include "mapdef.h"
#include "mon-util.h"
#include "mon-place.h"
#include "coord.h"
#include "random.h"
#include "state.h"
#include "tags.h"
#include "terrain.h"

static int write_vault(map_def &mdef,
                       vault_placement &,
                       bool check_place);
static int apply_vault_definition(
                        map_def &def,
                        vault_placement &,
                        bool check_place);

static bool resolve_map(map_def &def);

// Globals: Use unwind_var to modify!

// Checks whether a map place is valid.
map_place_check_t map_place_valid = map_safe_vault_place;

// If non-empty, any floating vault's @ exit must land on these point.
point_vector map_anchor_points;

//////////////////////////////////////////////////////////////////////////
// New style vault definitions

typedef std::vector<map_def> map_vector;
static map_vector vdefs;

// Parameter array that vault code can use.
string_vector map_parameters;

dgn_map_parameters::dgn_map_parameters(const std::string &astring)
    : mpar(map_parameters)
{
    map_parameters.push_back(astring);
}

dgn_map_parameters::dgn_map_parameters(const string_vector &parameters)
    : mpar(map_parameters)
{
    map_parameters = parameters;
}

/* ******************** BEGIN PUBLIC FUNCTIONS ******************* */

// Remember (!!!) - if a member of the monster array isn't specified
// within a vault subroutine, assume that it holds a random monster
// -- only in cases of explicit monsters assignment have I listed
// out random monster insertion {dlb}

// Make sure that vault_n, where n is a number, is a vault which can be put
// anywhere, while other vault names are for specific level ranges, etc.
int vault_main( vault_placement &place, const map_def *vault,
                bool check_place)
{
#ifdef DEBUG_DIAGNOSTICS
    mprf(MSGCH_DIAGNOSTICS, "Generating level: %s (%d,%d)",
         vault->name.c_str(), place.pos.x, place.pos.y);

    if (crawl_state.map_stat_gen)
        mapgen_report_map_try(*vault);
#endif

    // Return value of MAP_NONE forces dungeon.cc to regenerate the
    // level, except for branch entry vaults where dungeon.cc just
    // rejects the vault and places a vanilla entry.

    return (write_vault( const_cast<map_def&>(*vault), place, check_place ));
}

static int write_vault(map_def &mdef,
                       vault_placement &place,
                       bool check_place)
{
    mdef.load();

    // Copy the map so we can monkey with it.
    place.map = mdef;
    place.map.original = &mdef;

    // Try so many times to place the map. This will always succeed
    // unless there are conflicting map placements in 'avoid', or there
    // is a map validate Lua hook that keeps rejecting the map.
    int tries = 25;

    while (tries-- > 0)
    {
        if (!resolve_map(place.map))
            continue;

        // Must set size here, or minivaults will not be placed correctly.
        place.size = place.map.size();
        place.orient = apply_vault_definition(place.map,
                                              place, check_place);

        if (place.orient != MAP_NONE)
            return (place.orient);
    }
    return (MAP_NONE);
}

static bool resolve_map_lua(map_def &map)
{
    map.reinit();
    std::string err = map.run_lua(true);
    if (!err.empty())
    {
#ifdef DEBUG_DIAGNOSTICS
        if (crawl_state.map_stat_gen)
            mapgen_report_error(map, err);
#endif
        mprf(MSGCH_ERROR, "Lua error: %s", err.c_str());
        return (false);
    }

    map.fixup();
    err = map.resolve();
    if (!err.empty())
    {
        mprf(MSGCH_ERROR, "Error: %s", err.c_str());
        return (false);
    }

    if (!map.test_lua_validate(false))
        return (false);

    return (true);
}

// Mirror the map if appropriate, resolve substitutable symbols (?),
static bool resolve_map(map_def &map)
{
    if (!resolve_map_lua(map))
        return (false);

    // Don't bother flipping or rotating 1x1 subvaults.
    // This just cuts down on level generation message spam.
    if (map.map.width() == map.map.height() && map.map.width() == 1)
        return (true);

    // Mirroring is possible for any map that does not explicitly forbid it.
    // Note that mirroring also flips the orientation.
    if (coinflip())
        map.hmirror();

    if (coinflip())
        map.vmirror();

    // The map may also refuse to be rotated.
    if (coinflip())
        map.rotate(coinflip());

    return (true);
}

bool resolve_subvault(map_def &map)
{
    ASSERT(map.is_subvault());
    if (!map.is_subvault())
        return false;

    if (!resolve_map_lua(map))
        return false;

    int width = map.subvault_width();
    int height = map.subvault_height();

    bool can_rot = (map.map.width() <= height && map.map.height() <= width);
    bool must_rot = (map.map.width() > width || map.map.height() > height);

    // Too big, whether or not it is rotated.
    if (must_rot && !can_rot)
        return false;

    if (must_rot || can_rot && coinflip())
    {
        bool dir = coinflip();
        map.rotate(dir);

        // Update post-rotation dimensions.
        width = map.subvault_width();
        height = map.subvault_height();
    }

    // Inexact fits with dimensions flipped will get rotated above.
    bool exact_fit = (map.map.height() == height && map.map.width() == width);
    if (!exact_fit)
    {
        if (coinflip())
            map.hmirror();

        if (coinflip())
            map.vmirror();

        // The map may have refused to have been rotated, so verify dimensions.
        bool valid = (map.map.width() <= width && map.map.height() <= height);
        return (valid);
    }

    // Don't bother flipping or rotating 1x1 subvaults.
    // This just cuts down on level generation message spam.
    if (exact_fit && width == height && width == 1)
        return (true);

    // Count original mismatches.  If mirroring the map causes more cells to
    // not be written, then don't mirror.  This allows oddly shaped subvaults
    // to be fit correctly into a parent vault that specifies the exact same
    // shape.
    const coord_def svplace(0, 0);

    // 0 - normal
    // 1 - flip horz
    // 2 - flip vert + horz
    // 3 - flip vert
    int mismatch[4];

    // Mirror map in all directions to find best fit.
    mismatch[0] = map.subvault_mismatch_count(svplace);
    map.hmirror();
    mismatch[1] = map.subvault_mismatch_count(svplace);
    map.vmirror();
    mismatch[2] = map.subvault_mismatch_count(svplace);
    map.hmirror();
    mismatch[3] = map.subvault_mismatch_count(svplace);

    int min_mismatch = std::min(mismatch[0], mismatch[1]);
    min_mismatch = std::min(min_mismatch, mismatch[2]);
    min_mismatch = std::min(min_mismatch, mismatch[3]);

    // Pick a mirror combination with the minimum number of mismatches.
    int idx = random2(4);
    while (mismatch[idx] != min_mismatch)
        idx = (idx + 1) % 4;

    // Flip the map (currently vmirror'd) to the correct orientation.
    if (idx == 0)
    {
        map.vmirror();
    }
    else if (idx == 1)
    {
        map.vmirror();
        map.hmirror();
    }
    else if (idx == 2)
    {
        map.hmirror();
    }

    ASSERT(map.subvault_mismatch_count(svplace) == min_mismatch);

    // We already know this is an exact fit, so this is a success.
    return (true);
}

void fit_region_into_map_bounds(coord_def &pos, const coord_def &size,
                                int margin)
{
    const int X_1(X_BOUND_1 + margin);
    const int X_2(X_BOUND_2 - margin);
    const int Y_1(Y_BOUND_1 + margin);
    const int Y_2(Y_BOUND_2 - margin);

    ASSERT(size.x <= (X_2 - X_1 + 1) && size.y <= (Y_2 - Y_1 + 1));

    if (pos.x < X_1)
        pos.x = X_1;
    if (pos.y < Y_1)
        pos.y = Y_1;
    if (pos.x + size.x - 1 > X_2)
        pos.x = X_2 - size.x + 1;
    if (pos.y + size.y - 1 > Y_2)
        pos.y = Y_2 - size.y + 1;
}

// Used for placement of vaults.
static bool _may_overwrite_feature(const dungeon_feature_type grid,
                                   bool water_ok, bool wall_ok = true)
{
    // Deep water grids may be overwritten if water_ok == true.
    if (grid == DNGN_DEEP_WATER)
        return (water_ok);

    // Handle all other non-LOS blocking grids here.
    if (!feat_is_opaque(grid)
        && grid != DNGN_FLOOR
        && grid != DNGN_SHALLOW_WATER
        && grid != DNGN_OPEN_DOOR
        && grid != DNGN_SECRET_DOOR
        && !feat_is_closed_door(grid))
    {
        return (false);
    }

    if (feat_is_wall(grid) || grid == DNGN_TREES)
        return (wall_ok);

    // Otherwise, feel free to clobber this feature.
    return (true);
}

bool map_safe_vault_place(const map_def &map,
                          const coord_def &c,
                          const coord_def &size)
{
    if (size.zero())
        return (true);

    const bool water_ok =
        map.has_tag("water_ok") || player_in_branch(BRANCH_SWAMP);
    const std::vector<std::string> &lines = map.map.get_lines();

    for (rectangle_iterator ri(c, c + size - 1); ri; ++ri)
    {
        const coord_def cp(*ri);
        const coord_def dp(cp - c);

        if (lines[dp.y][dp.x] == ' ')
            continue;

        // Also check adjacent squares for collisions, because being next
        // to another vault may block off one of this vault's exits.
        for (int y = -1; y <= 1; ++y)
            for (int x = -1; x <= 1; ++x)
            {
                const coord_def vp(x + cp.x, y + cp.y);
                if (map_bounds(vp) && (dgn_Map_Mask(vp) & MMT_VAULT))
                    return (false);
            }

        const dungeon_feature_type dfeat = grd(cp);

        // Don't overwrite features other than floor, rock wall, doors,
        // nor water, if !water_ok.
        if (!_may_overwrite_feature(dfeat, water_ok))
            return (false);

        // Don't overwrite monsters or items, either!
        if (monster_at(cp) || igrd(cp) != NON_ITEM)
            return (false);
    }

    return (true);
}

static bool _connected_minivault_place(const coord_def &c,
                                       const vault_placement &place)
{
    if (place.size.zero())
        return (true);

    // Must not be completely isolated.
    const bool water_ok = place.map.has_tag("water_ok");
    const std::vector<std::string> &lines = place.map.map.get_lines();

    for (rectangle_iterator ri(c, c + place.size - 1); ri; ++ri)
    {
        const coord_def &ci(*ri);

        if (lines[ci.y - c.y][ci.x - c.x] == ' ')
            continue;

        if (_may_overwrite_feature(grd(ci), water_ok, false))
            return (true);
    }

    return (false);
}

static coord_def _find_minivault_place(
    const vault_placement &place,
    bool check_place)
{
    // [ds] The margin around the edges of the map where the minivault
    // won't be placed. Purely arbitrary as far as I can see.
    const int margin = MAPGEN_BORDER * 2;

    // Find a target area which can be safely overwritten.
    for (int tries = 0; tries < 600; ++tries)
    {
        coord_def v1(random_range( margin, GXM - margin - place.size.x ),
                     random_range( margin, GYM - margin - place.size.y ));

        if (check_place && !map_place_valid(place.map, v1, place.size))
        {
#ifdef DEBUG_MINIVAULT_PLACEMENT
            mprf(MSGCH_DIAGNOSTICS,
                 "Skipping (%d,%d): not safe to place map",
                 v1.x, v1.y);
#endif
            continue;
        }

        if (!_connected_minivault_place(v1, place))
        {
#ifdef DEBUG_MINIVAULT_PLACEMENT
            mprf(MSGCH_DIAGNOSTICS,
                 "Skipping (%d,%d): not a good minivault place (tags: %s)",
                 v1.x, v1.y, place.map.tags.c_str());
#endif
            continue;
        }
        return (v1);
    }
    return (coord_def(-1, -1));
}

static bool apply_vault_grid(map_def &def,
                             vault_placement &place,
                             bool check_place)
{
    const map_lines &ml = def.map;
    const int orient = def.orient;

    const coord_def size(ml.size());

    coord_def start(0, 0);

    if (orient == MAP_SOUTH || orient == MAP_SOUTHEAST
        || orient == MAP_SOUTHWEST)
    {
        start.y = GYM - size.y;
    }
    if (orient == MAP_EAST || orient == MAP_NORTHEAST
        || orient == MAP_SOUTHEAST)
    {
        start.x = GXM - size.x;
    }

    // Handle maps aligned along cardinals that are smaller than
    // the corresponding map dimension.
    if ((orient == MAP_NORTH || orient == MAP_SOUTH
         || orient == MAP_ENCOMPASS)
        && size.x < GXM)
    {
        start.x = (GXM - size.x) / 2;
    }
    if ((orient == MAP_EAST || orient == MAP_WEST
         || orient == MAP_ENCOMPASS)
        && size.y < GYM)
    {
        start.y = (GYM - size.y) / 2;
    }

    // Floating maps can go anywhere, ask the map_def to suggest a place.
    if (orient == MAP_FLOAT)
    {
        const bool minivault = def.has_tag("minivault");
        if (map_bounds(place.pos))
        {
            start = place.pos - size / 2;
            fit_region_into_map_bounds(start, size, minivault ? 2 : 0);
        }
        else if (minivault)
        {
            start = _find_minivault_place(place, check_place);
            if (map_bounds(start))
                fit_region_into_map_bounds(start, size, 2);
        }
        else
            start = def.float_place();
    }

    if (!map_bounds(start))
        return (false);

    if (check_place && !map_place_valid(def, start, size))
    {
        dprf("Bad vault place: (%d,%d) dim (%d,%d)",
             start.x, start.y, size.x, size.y);
        return (false);
    }


    place.pos  = start;
    place.size = size;

    return (true);
}

static int apply_vault_definition(
        map_def &def,
        vault_placement &place,
        bool check_place)
{
    if (!apply_vault_grid(def, place, check_place))
        return (MAP_NONE);

    int orient = def.orient;
    if (orient == MAP_NONE)
        orient = MAP_NORTH;

    return (orient);
}

///////////////////////////////////////////////////////////////////////////
// Map lookups

template <typename I>
static bool map_has_no_tags(const map_def &map, I begin, I end)
{
    for ( ; begin != end; ++begin)
        if (map.has_tag(*begin))
            return (false);

    return (true);
}

static bool vault_unforbidden(const map_def &map)
{
    return (you.uniq_map_names.find(map.name) == you.uniq_map_names.end()
            && Level_Unique_Maps.find(map.name) == Level_Unique_Maps.end()
            && map_has_no_tags(map, you.uniq_map_tags.begin(),
                               you.uniq_map_tags.end())
            && map_has_no_tags(map, Level_Unique_Tags.begin(),
                               Level_Unique_Tags.end()));
}

static bool map_matches_layout_type(const map_def &map)
{
    if (dgn_Layout_Type.empty() || !map.has_tag_prefix("layout_"))
        return (true);

    return map.has_tag("layout_" + dgn_Layout_Type);
}

const map_def *find_map_by_name(const std::string &name)
{
    for (unsigned i = 0, size = vdefs.size(); i < size; ++i)
        if (vdefs[i].name == name)
            return (&vdefs[i]);

    return (NULL);
}

std::vector<std::string> find_map_matches(const std::string &name)
{
    std::vector<std::string> matches;

    for (unsigned i = 0, size = vdefs.size(); i < size; ++i)
        if (vdefs[i].name.find(name) != std::string::npos)
            matches.push_back(vdefs[i].name);
    return (matches);
}

std::vector<map_def> find_maps_for_tag (const std::string tag, bool check_depth,
                                          bool check_used)
{
    std::vector<map_def> maps;
    level_id place = level_id::current();

    for (unsigned i = 0, size = vdefs.size(); i < size; ++i)
    {
        map_def mapdef = vdefs[i];
        if (mapdef.has_tag(tag)
            && !mapdef.has_tag("dummy")
            && (!check_depth || !mapdef.has_depth()
                 || mapdef.is_usable_in(place))
            && (!check_used || vault_unforbidden(mapdef)))
        {
                maps.push_back(mapdef);
        }
    }

    return (maps);
}

int weight_map_vector (std::vector<map_def> maps)
{
    int weights = 0;

    for (std::vector<map_def>::iterator mi = maps.begin(); mi != maps.end(); ++mi)
        weights += mi->weight;

    return (weights);
}

struct map_selector {
private:
    enum select_type
    {
        PLACE,
        DEPTH,
        TAG
    };

public:
    bool accept(const map_def &md) const;
    void announce(int vault) const;

    bool valid() const
    {
        return (sel == TAG || place.is_valid());
    }

    static map_selector by_place(const level_id &place, bool mini)
    {
        return map_selector(map_selector::PLACE, place, "", mini, false);
    }

    static map_selector by_depth(const level_id &place, bool mini)
    {
        return map_selector(map_selector::DEPTH, place, "", mini, true);
    }

    static map_selector by_tag(const std::string &tag,
                               bool check_depth,
                               const level_id &place = level_id::current())
    {
        return map_selector(map_selector::TAG, place, tag,
                            false, check_depth);
    }

private:
    map_selector(select_type _typ, const level_id &_pl,
                 const std::string &_tag,
                 bool _mini, bool _check_depth)
        : ignore_chance(false), preserve_dummy(false),
          sel(_typ), place(_pl), tag(_tag),
          mini(_mini), check_depth(_check_depth),
          check_layout(sel == DEPTH && place == level_id::current())
    {
    }

public:
    bool ignore_chance;
    bool preserve_dummy;
    const select_type sel;
    const level_id place;
    const std::string tag;
    const bool mini;
    const bool check_depth;
    const bool check_layout;
};

bool map_selector::accept(const map_def &mapdef) const
{
    switch (sel)
    {
    case PLACE:
        return (mapdef.is_minivault() == mini
                && mapdef.place == place
                && !mapdef.has_tag("layout")
                && !mapdef.has_tag("place_unique")
                && (!mapdef.has_tag_prefix("temple_")
                    || mapdef.has_tag_prefix("uniq_altar_"))
                && map_matches_layout_type(mapdef)
                && vault_unforbidden(mapdef));
    case DEPTH:
        return (mapdef.is_minivault() == mini
                && !mapdef.place.is_valid()
                && mapdef.is_usable_in(place)
                // Some tagged levels cannot be selected by depth. This is
                // the only thing preventing Pandemonium demon vaults from
                // showing up in the main dungeon.
                && !mapdef.has_tag_suffix("entry")
                && !mapdef.has_tag("pan")
                && !mapdef.has_tag("unrand")
                && !mapdef.has_tag("bazaar")
                && !mapdef.has_tag("layout")
                && !mapdef.has_tag("place_unique")
                && (!mapdef.has_tag_prefix("temple_")
                    || mapdef.has_tag_prefix("uniq_altar_"))
                && (!check_layout || map_matches_layout_type(mapdef))
                && vault_unforbidden(mapdef));
    case TAG:
        return (mapdef.has_tag(tag)
                && (!check_depth || !mapdef.has_depth()
                    || mapdef.is_usable_in(place))
                && map_matches_layout_type(mapdef)
                && vault_unforbidden(mapdef));
    default:
        return (false);
    }
}

void map_selector::announce(int vault) const
{
#ifdef DEBUG_DIAGNOSTICS
    if (vault != -1)
    {
        const char *format =
            sel == PLACE? "[PLACE] Found map %s for %s" :
            sel == DEPTH? "[DEPTH] Found random map %s for %s" :
                          "[TAG] Found map %s tagged '%s'";

        mprf(MSGCH_DIAGNOSTICS, format,
             vdefs[vault].name.c_str(),
             sel == TAG? tag.c_str() : place.describe().c_str());
    }
#endif
}

static bool _compare_vault_chance_priority(int a, int b)
{
    return (vdefs[b].chance_priority < vdefs[a].chance_priority);
}

static std::string _vault_chance_tag(const map_def &map)
{
    if (map.has_tag_prefix("chance_"))
    {
        const std::vector<std::string> tags = map.get_tags();
        for (int i = 0, size = tags.size(); i < size; ++i)
        {
            if (tags[i].find("chance_") == 0)
                return (tags[i]);
        }
    }
    return ("");
}

typedef std::vector<unsigned> vault_indices;

static vault_indices _eligible_maps_for_selector(const map_selector &sel)
{
    vault_indices eligible;

    if (sel.valid())
    {
        for (unsigned i = 0, size = vdefs.size(); i < size; ++i)
            if (sel.accept(vdefs[i]))
                eligible.push_back(i);
    }

    return (eligible);
}

static const map_def *_random_map_by_selector(const map_selector &sel);
static const map_def *_random_map_in_list(const map_selector &sel,
                                          const vault_indices &filtered)
{
    int mapindex = -1;
    int rollsize = 0;

    // First build a list of vaults that could be used:
    vault_indices eligible;

    // Vaults that are eligible and have >0 chance.
    vault_indices chance;

    typedef std::set<std::string> tag_set;
    tag_set chance_tags;

    for (unsigned f = 0, size = filtered.size(); f < size; ++f)
    {
        const int i = filtered[f];
        if (!sel.ignore_chance && vdefs[i].chance > 0)
        {
            // There may be several alternatives for a portal
            // vault that want to be governed by one common
            // CHANCE. In this case each vault will use a
            // CHANCE, and a common chance_xxx tag. Pick the
            // first such vault for the chance roll. Note that
            // at this point we ignore chance_priority.
            const std::string tag = _vault_chance_tag(vdefs[i]);
            if (chance_tags.find(tag) == chance_tags.end())
            {
                if (!tag.empty())
                    chance_tags.insert(tag);
                chance.push_back(i);
            }
        }
        else
        {
            eligible.push_back(i);
        }
    }

    // Sort chances by priority, high priority first.
    std::sort(chance.begin(), chance.end(), _compare_vault_chance_priority);

    // Check CHANCEs.
    for (vault_indices::const_iterator i = chance.begin();
         i != chance.end(); ++i)
    {
        const map_def &map(vdefs[*i]);
        if (random2(CHANCE_ROLL) < map.chance)
        {
            const std::string chance_tag = _vault_chance_tag(map);
            // If this map has a chance_ tag, convert the search into
            // a lookup for that tag.
            if (!chance_tag.empty())
            {
                map_selector msel = map_selector::by_tag(chance_tag,
                                                         sel.check_depth,
                                                         sel.place);
                msel.ignore_chance = true;
                return _random_map_by_selector(msel);
            }

            mapindex = *i;
            break;
        }
    }

    if (mapindex == -1)
    {
        int absdepth = 0;
        if (sel.place.level_type == LEVEL_DUNGEON && sel.place.is_valid())
            absdepth = sel.place.absdepth();

        for (vault_indices::const_iterator i = eligible.begin();
             i != eligible.end(); ++i)
        {
            const map_def &map(vdefs[*i]);
            const int weight = map.weight
                + absdepth * map.weight_depth_mult / map.weight_depth_div;

            if (weight <= 0)
                continue;

            rollsize += weight;

            if (rollsize && x_chance_in_y(weight, rollsize))
                mapindex = *i;
        }
    }

    if (!sel.preserve_dummy && mapindex != -1
        && vdefs[mapindex].has_tag("dummy"))
    {
        mapindex = -1;
    }

    sel.announce(mapindex);

    return (mapindex == -1? NULL : &vdefs[mapindex]);
}

static const map_def *_random_map_by_selector(const map_selector &sel)
{
    const vault_indices filtered = _eligible_maps_for_selector(sel);
    return _random_map_in_list(sel, filtered);
}

// Returns a map for which PLACE: matches the given place.
const map_def *random_map_for_place(const level_id &place, bool minivault)
{
    return _random_map_by_selector(map_selector::by_place(place, minivault));
}

const map_def *random_map_in_depth(const level_id &place, bool want_minivault)
{
    return _random_map_by_selector(
        map_selector::by_depth(place, want_minivault));
}

const map_def *random_map_for_tag(const std::string &tag,
                                  bool check_depth)
{
    return _random_map_by_selector(
        map_selector::by_tag(tag, check_depth));
}

int map_count()
{
    return (vdefs.size());
}

int map_count_for_tag(const std::string &tag,
                      bool check_depth)
{
    return _eligible_maps_for_selector(
        map_selector::by_tag(tag, check_depth)).size();
}

/////////////////////////////////////////////////////////////////////////////
// Reading maps from .des files.

// All global preludes.
std::vector<dlua_chunk> global_preludes;

// Map-specific prelude.
dlua_chunk lc_global_prelude("global_prelude");
std::string lc_desfile;
map_def     lc_map;
level_range lc_range;
depth_ranges lc_default_depths;
bool lc_run_global_prelude = true;
map_load_info_t lc_loaded_maps;

std::set<std::string> map_files_read;

extern int yylineno;

void reset_map_parser()
{
    lc_map.init();
    lc_range.reset();
    lc_default_depths.clear();
    lc_global_prelude.clear();

    yylineno = 1;
    lc_run_global_prelude = true;
}

////////////////////////////////////////////////////////////////////////////

static bool checked_des_index_dir = false;

static void check_des_index_dir()
{
    if (checked_des_index_dir)
        return;

    std::string desdir = get_savedir_path("des");
    if (!check_dir("Data file cache", desdir, true))
        end(1, true, "Can't create data file cache: %s", desdir.c_str());

    checked_des_index_dir = true;
}

std::string get_descache_path(const std::string &file,
                              const std::string &ext)
{
    const std::string basename =
        change_file_extension(get_base_filename(file), ext);
    return get_savedir_path("des/" + basename);
}

static bool verify_file_version(const std::string &file)
{
    FILE *fp = fopen(file.c_str(), "rb");
    if (!fp)
        return (false);
    reader inf(fp);
    const long ver = unmarshallLong(inf);
    fclose(fp);

    return (ver == MAP_CACHE_VERSION);
}

static bool verify_map_index(const std::string &base)
{
    return verify_file_version(base + ".idx");
}

static bool verify_map_full(const std::string &base)
{
    return verify_file_version(base + ".dsc");
}

static bool load_map_index(const std::string &base)
{
    // If there's a global prelude, load that first.
    {
        FILE *fp = fopen((base + ".lux").c_str(), "rb");
        if (fp)
        {
            reader inf(fp);
            lc_global_prelude.read(inf);
            fclose(fp);

            global_preludes.push_back( lc_global_prelude );
        }
    }

    FILE* fp = fopen((base + ".idx").c_str(), "rb");
    if (!fp)
        end(1, true, "Unable to read %s", (base + ".idx").c_str());
    reader inf(fp);

    // Discard version (it's been checked by verify_map_index).
    (void) unmarshallLong(inf);
    const int nmaps = unmarshallShort(inf);
    const int nexist = vdefs.size();
    vdefs.resize( nexist + nmaps, map_def() );
    for (int i = 0; i < nmaps; ++i)
    {
        map_def &vdef(vdefs[nexist + i]);
        vdef.read_index(inf);
        vdef.set_file(base);
        lc_loaded_maps[vdef.name] = vdef.place_loaded_from;
        vdef.place_loaded_from.clear();
    }
    fclose(fp);

    return (true);
}

static bool load_map_cache(const std::string &filename)
{
    check_des_index_dir();
    const std::string descache_base = get_descache_path(filename, "");

    file_lock deslock(descache_base + ".lk", "rb", false);

    if (is_newer(filename, descache_base + ".idx")
        || is_newer(filename, descache_base + ".dsc"))
        return (false);

    if (!verify_map_index(descache_base) || !verify_map_full(descache_base))
        return (false);

    return load_map_index(descache_base);
}

static void write_map_prelude(const std::string &filebase)
{
    const std::string luafile = filebase + ".lux";
    if (lc_global_prelude.empty())
    {
        unlink(luafile.c_str());
        return;
    }

    FILE *fp = fopen(luafile.c_str(), "wb");
    writer outf(fp);
    lc_global_prelude.write(outf);
    fclose(fp);
}

static void write_map_full(const std::string &filebase, size_t vs, size_t ve)
{
    const std::string cfile = filebase + ".dsc";
    FILE *fp = fopen(cfile.c_str(), "wb");
    if (!fp)
        end(1, true, "Unable to open %s for writing", cfile.c_str());

    writer outf(fp);
    marshallLong(outf, MAP_CACHE_VERSION);
    for (size_t i = vs; i < ve; ++i)
        vdefs[i].write_full(outf);
    fclose(fp);
}

static void write_map_index(const std::string &filebase, size_t vs, size_t ve)
{
    const std::string cfile = filebase + ".idx";
    FILE *fp = fopen(cfile.c_str(), "wb");
    if (!fp)
        end(1, true, "Unable to open %s for writing", cfile.c_str());

    writer outf(fp);
    marshallLong(outf, MAP_CACHE_VERSION);
    marshallShort(outf, ve > vs? ve - vs : 0);
    for (size_t i = vs; i < ve; ++i)
        vdefs[i].write_index(outf);
    fclose(fp);
}

static void write_map_cache(const std::string &filename, size_t vs, size_t ve)
{
    check_des_index_dir();

    const std::string descache_base = get_descache_path(filename, "");

    file_lock deslock(descache_base + ".lk", "wb");

    write_map_prelude(descache_base);
    write_map_full(descache_base, vs, ve);
    write_map_index(descache_base, vs, ve);
}

static void parse_maps(const std::string &s)
{
    const std::string base = get_base_filename(s);
    if (map_files_read.find(base) != map_files_read.end())
        return;

    map_files_read.insert(base);

    if (load_map_cache(s))
        return;

    FILE *dat = fopen(s.c_str(), "r");
    if (!dat)
        end(1, true, "Failed to open %s for reading", s.c_str());

    reset_map_parser();

    extern int yyparse(void);
    extern FILE *yyin;
    yyin = dat;

    size_t file_start = vdefs.size();
    yyparse();
    fclose(dat);

    global_preludes.push_back( lc_global_prelude );
    write_map_cache(s, file_start, vdefs.size());
}

void read_map(const std::string &file)
{
    parse_maps( lc_desfile = datafile_path(file) );
}

void read_maps()
{
    if (dlua.execfile("clua/loadmaps.lua", true, true))
        end(1, false, "Lua error: %s", dlua.error.c_str());

    // Clean up cached environments.
    dlua.callfn("dgn_flush_map_environments", 0, 0);
    lc_loaded_maps.clear();

    sanity_check_maps();
}

void add_parsed_map( const map_def &md )
{
    map_def map = md;

    map.fixup();
    vdefs.push_back( map );
}

void run_map_preludes()
{
    for (int i = 0, size = global_preludes.size(); i < size; ++i)
    {
        dlua_chunk &chunk = global_preludes[i];
        if (!chunk.empty())
        {
            if (chunk.load_call(dlua, NULL))
                mprf(MSGCH_ERROR, "Lua error: %s", chunk.orig_error().c_str());
        }
    }
    for (int i = 0, size = vdefs.size(); i < size; ++i)
    {
        if (!vdefs[i].prelude.empty())
        {
            std::string err = vdefs[i].run_lua(true);
            if (!err.empty())
                mprf(MSGCH_ERROR, "Lua error (map %s): %s",
                     vdefs[i].name.c_str(), err.c_str());
        }
    }
}

const map_def *map_by_index(int index)
{
    return (&vdefs[index]);
}

void sanity_check_maps()
{
    dlua.execfile("clua/sanity.lua", true, true);
}

///////////////////////////////////////////////////////////////////////////
// Debugging code

#ifdef DEBUG_DIAGNOSTICS

typedef std::pair<std::string, int> weighted_map_name;
typedef std::vector<weighted_map_name> weighted_map_names;

static weighted_map_names mg_find_random_vaults(
    const level_id &place, bool wantmini)
{
    weighted_map_names wms;

    if (!place.is_valid())
        return (wms);

    typedef std::map<std::string, int> map_count_t;

    map_count_t map_counts;

    map_selector sel = map_selector::by_depth(place, wantmini);
    sel.preserve_dummy = true;

    no_messages mx;
    vault_indices filtered = _eligible_maps_for_selector(sel);

    for (int i = 0; i < 10000; ++i)
    {
        const map_def *map(_random_map_in_list(sel, filtered));
        if (!map)
            map_counts["(none)"]++;
        else
            map_counts[map->name]++;
    }

    for (map_count_t::const_iterator i = map_counts.begin();
         i != map_counts.end(); ++i)
    {
        wms.push_back(*i);
    }

    return (wms);
}

static bool weighted_map_more_likely(
    const weighted_map_name &a,
    const weighted_map_name &b)
{
    return (a.second > b.second);
}

static void mg_report_random_vaults(
    FILE *outf, const level_id &place, bool wantmini)
{
    weighted_map_names wms = mg_find_random_vaults(place, wantmini);
    std::sort(wms.begin(), wms.end(), weighted_map_more_likely);
    int weightsum = 0;
    for (int i = 0, size = wms.size(); i < size; ++i)
        weightsum += wms[i].second;

    std::string line;
    for (int i = 0, size = wms.size(); i < size; ++i)
    {
        std::string curr =
            make_stringf("%s (%.2f%%)",
                         wms[i].first.c_str(),
                         100.0 * wms[i].second / weightsum);
        if (i < size - 1)
            curr += ", ";
        if (line.length() + curr.length() > 80u)
        {
            fprintf(outf, "%s\n", line.c_str());
            line.clear();
        }

        line += curr;
    }
    if (!line.empty())
        fprintf(outf, "%s\n", line.c_str());
}

void mg_report_random_maps(FILE *outf, const level_id &place)
{
    fprintf(outf, "---------------- Mini\n");
    mg_report_random_vaults(outf, place, true);
    fprintf(outf, "------------- Regular\n");
    mg_report_random_vaults(outf, place, false);
}

#endif