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






                                               
 
                     
                     
                
                 
                    
                  
                     
                    
                 
                     
                     


                   
                  

                    
                    
                 
                     
                      
                  
                    
                  



                     
                
                 

                    
                   
                     
                     



                  
                  

                    


                        
                                        
                                        
 


                                                                

                                                            

 






                                                                      
                                                                  
                                                               


                                     









                                                                 



                                                 
                                               


                             


                                                                               





                                   









                                                                           

                           
                                                       


                                        
                                 
     
                   

 
                                        
 
                                                       




                                        

                                                      
                                                 

         







                                                

 













                                                         

                                 




               
                                                
 
                                   



                                                
                                               

 


                                                                         








                                                     

                         









                                                                















                                                                           





                                          
                             
                            


                                                    




                                    
















                                                                       

 
                                                             












                                                           



                                                     

                                  

                                                                 

                          
     
                   

 









                                                       
 


                   




                                                        












                                    
                             


                                
                                     


     







                                                          
                                                                            








                                                                    
                                                             




                       

                    

                     




                                
                           
                                
 
                                                               
                       




                                                                 
                                                  

                                  






                                                                            
                                         


                            
                   


                                                                 
                              
                                    
                                                   

            
                                               
                                        


                                      
 
                                                   
                          
 
                                                                         




                                                                              
                               



                                                                  





                                                                              

                              
 
                                         














                                                                              
                                                      









                                                                            

                                 
 
                                                                           
                                                  
                                 




                             



















                                                                            



                                                               

                                               
                            





















                                                                          

 
                                

       


                                                             
                                                           
     
                                 










                              
                      
                                 
                                            
                            
         
                                                   
                                                                   
         
                     







                                                                         
                       


                                                   









                                                                         








                                    
                       



                                  




                                       
                                                 






                                                                     

                

 
                                                                     
 
                                                               

                   
                                                                         

                                     
                           
                                                                      
 




                                
                          
 


                            
                                                                 

                             
                   








                                             
 



                             

                                                 
                                           




                            







                                               



                                                          
                                                        
                       



                                               

                                                                                







                                                   
                                              
         

                                                                           
 







                                             









                                                        
 


                         


                                                             




                         












                                                                

                                           






                                                             


                                    




                                          

                    
















                                                                        

                                                       

         


                                                    


                                          



                                                             
 
                            
                                                



                                 
                                        



                                              
                                                                                








                                                 
                                                                             

                        
                                              
         


                                                                           





                                                               
                                                                                


                                                            
 






                                                  

                                

 
                                    

                                     
                                              
 

                          
 

                             
 
                                                                              
                      




                                                                           
                                     

 
                             

                      
                                     
 

                            
 



                                                                   
 
                                              












                                                                   
                                  















                                                                               
                                                      



                            









                                                               

                                                 
                                                                          
                                                                   
 
                                  

                                                         
                                                                                           




                                                               
 
                                                 
 
                                                                          
                                                                   
 
                                             
     
                                                                             
                          
 




                                                                       
 
                                  

                                                         




                                                            

                                                 
                                                                          
                                                                   
 

                                                 
 
                                  
                                                         

 







                                                         
                            


     
















                                                                           

                                               

                                                             

                   
                                                                          
                                   

                                           
                                     

                           










                                                                               
                                   





                                
                          
 


                            
                                                                               














                                                                         
     
                   






                                         
                                                        
                                                         

                                                             
                                 
                       
 


                                          



                                               
                                                     

                                                                           
 
                               
                                                       










                                                        
                                  


















                                                           

                          
     
 

                                  



                                                           
                                          
                          







                                                   
                                                 










                                                            

                                

 
                                       
 
                                  



                             

                                   
 
                                              
 
                                


                                               

                                                     


     
                                
 
                                    
 
                             


                          
                             
 
                                         
 
                                 


                                       

                                                     









                                                               



                                   
 

 
                                    
 
                   



                                             







                                                       



                                                         

                           

                        

                                     

                                                          



                                                           
                                                 

                                  
 


                  














                                                       

                                              
                                                 
     

                                  

                      
                                            
                          












                                                                                
                      
     
                   




                                             
                                 

















                                         
                                                   


                                                              
                                                          












                                          

                               
                                                         


     
                                     
 
                                            

 

                                            
                                        



                                                  
                              

 
                                              
 
                                    


                   
                                                            
                                            








                                        
                                                        
 


                                                            





                                                                 
                                     




                                       
                                                 

                                
                                                           
         
                                 




                                   








                                                      

                                                               


                                   

                                    
                                                 
                                       
 
                         
     
                                                   
                                             
                                                 
                                                                
                                             
         
                                                                  




                    
                                           

                                      
                                                  
 
                       

                                       
                                                            



                                         
 
                                                
                                                 
                              

 
                                    
 
                                    
 
                      
 
                      


                                  
                    
                       
                                         

     
                    
                                     


                                   
                     
                              










                                                                    






                                                          



                                                

                               

                                           









                                             
                           
                     
     
                   

 
                                                      
 
                        








                                                
                       














                                                        
 
                               
                       


















                                                                  

                                                                  
         
                                             



         
                                           

                                               

                                     
 


                                                    
                               
                                               

                                                    
                                                         
                                         
                                

 
                                    


                                                                         

                                              

                                                                


                                                       
                                     




                                   
                     

                                    






                                                                         
               

                                             
                                                                     
                                                                         
         
                                                                       
                         
 

                                                          
                                        
                                                              
                                                        


                                               
                                       

             

                                        
         

                                   
                       





                                        








                                                                   
                                     







                                                                         


                                                                            
                          

 



















                                                        





                                                   

                                                       

                                                               


                                            

                                                   
         
            
                           








                                                   

                                   

                                           


                                                            

                                                               


                                            

                                                   
         
            
                           


     

                                   
                  
 

                           






















                                                
 




                                                      




                                  
                                                           
         

                                                     
         




                                               
 


                                            
     
 






                                             
                                      
 

                                                  



                                                               
                          


                



                                           
                                                               





                                                                          
               

     



                               

                         

                                                                             


            




                                                                         

                   

                                     






                                                  
                                                         

                                         
                                                           



                                                   
                                        
                                                 



                                                            
 

                                           




                                   


                                            

                                 


                    
                                    
                           









                                  
                      
                                 
                                     
                                                     

                                               
 
                                                                      
                                                                                                          
                                                                             


     
 

                                          
                   

                                          
                       
     
 


                                  

                                                                 
                                              
                           

                        
                       
 

                                              
                                          
                               
                                        
                                                
                                               
                                
 
                                                        



                                      
                                                                          


                                                                          
                                      
     
 



                                                           


                                                                 
 
                                             
                                                            

                                                                             
                        
 

                                                
 









                                           
                                                 
                          
 

                                                                     
         
                                      




                                                                 
                                                         
                                                                            


                                
                                                          
                                                                             

             
                                                       

                                
                                                  
                                            
                               






                     
                                                                      
     
                                        
                                                                 
                                      
                                    
     
                   
 


                                   













                                                                         

















































































































































                                                                
/*
 *  File:       stash.cc
 *  Summary:    Classes tracking player stashes
 *  Written by: Darshan Shaligram
 */

#include "AppHdr.h"

#include "artefact.h"
#include "chardump.h"
#include "cio.h"
#include "clua.h"
#include "command.h"
#include "coord.h"
#include "describe.h"
#include "directn.h"
#include "food.h"
#include "itemname.h"
#include "itemprop.h"
#include "files.h"
#include "invent.h"
#include "items.h"
#include "kills.h"
#include "libutil.h"
#include "menu.h"
#include "message.h"
#include "misc.h"
#include "mon-util.h"
#include "mon-stuff.h"
#include "notes.h"
#include "options.h"
#include "place.h"
#include "shopping.h"
#include "spl-book.h"
#include "stash.h"
#include "stuff.h"
#include "env.h"
#include "tags.h"
#include "terrain.h"
#include "traps.h"
#include "travel.h"
#include "tutorial.h"
#include "viewgeom.h"

#include <cctype>
#include <cstdio>
#include <fstream>
#include <sstream>
#include <algorithm>

// Global
StashTracker StashTrack;

#define ST_MAJOR_VER ((unsigned char) 4)
#define ST_MINOR_VER ((unsigned char) 8)

void stash_init_new_level()
{
    // If there's an existing stash level for Pan, blow it away.
    StashTrack.remove_level( level_id(LEVEL_PANDEMONIUM) );
    StashTrack.remove_level( level_id(LEVEL_PORTAL_VAULT) );
}

std::string userdef_annotate_item(const char *s, const item_def *item,
                                  bool exclusive)
{
#ifdef CLUA_BINDINGS
    if (exclusive)
        lua_set_exclusive_item(item);
    std::string ann;
    if (!clua.callfn(s, "u>s", item, &ann) && !clua.error.empty())
        mprf(MSGCH_ERROR, "Lua error: %s", clua.error.c_str());
    if (exclusive)
        lua_set_exclusive_item(NULL);
    return (ann);
#else
    return ("");
#endif
}

std::string stash_annotate_item(const char *s,
                                const item_def *item,
                                bool exclusive = false)
{
    std::string text = userdef_annotate_item(s, item, exclusive);
    if (item->base_type == OBJ_BOOKS
            && item_type_known(*item)
            && item->sub_type != BOOK_MANUAL
            && item->sub_type != BOOK_DESTRUCTION
        || count_staff_spells(*item, true) > 1)
    {
        formatted_string fs;
        item_def dup = *item;
        spellbook_contents(dup, item->base_type == OBJ_BOOKS ? RBOOK_READ_SPELL
                                                             : RBOOK_USE_STAFF,
                           &fs);
        text += EOL;
        text += fs.tostring(2, -2);
    }
    return text;
}

void maybe_update_stashes()
{
    if (Options.stash_tracking && !crawl_state.arena)
    {
        StashTrack.update_visible_stashes(
            Options.stash_tracking == STM_ALL ? StashTracker::ST_AGGRESSIVE
                                              : StashTracker::ST_PASSIVE);
    }
}

bool is_stash(int x, int y)
{
    LevelStashes *ls = StashTrack.find_current_level();
    if (ls)
    {
        Stash *s = ls->find_stash(x, y);
        return (s && s->enabled);
    }
    return (false);
}

std::string get_stash_desc(int x, int y)
{
    LevelStashes *ls = StashTrack.find_current_level();
    if (ls)
    {
        Stash *s = ls->find_stash(x, y);
        if (s)
        {
            const std::string desc = s->description();
            if (!desc.empty())
                return ("[Stash: " + desc + "]");
        }
    }
    return "";
}

void describe_stash(int x, int y)
{
    std::string desc = get_stash_desc(x, y);
    if (!desc.empty())
        mpr(desc.c_str(), MSGCH_EXAMINE_FILTER);
}


std::vector<item_def> Stash::get_items() const
{
    return items;
}

std::vector<item_def> item_list_in_stash( coord_def pos )
{
    std::vector<item_def> ret;

    LevelStashes *ls = StashTrack.find_current_level();
    if (ls)
    {
        Stash *s = ls->find_stash(pos.x, pos.y);
        if (s)
            ret = s->get_items();
    }

    return ret;
}

static void _fully_identify_item(item_def *item)
{
    if (!item || !item->is_valid())
        return;

    set_ident_flags( *item, ISFLAG_IDENT_MASK );
    if (item->base_type != OBJ_WEAPONS)
        set_ident_type( *item, ID_KNOWN_TYPE );
}

// ----------------------------------------------------------------------
// Stash
// ----------------------------------------------------------------------

bool Stash::aggressive_verify = true;
std::vector<item_def> Stash::filters;

Stash::Stash(int xp, int yp) : enabled(true), items()
{
    // First, fix what square we're interested in
    if (xp == -1)
    {
        xp = you.pos().x;
        yp = you.pos().y;
    }
    x = (unsigned char) xp;
    y = (unsigned char) yp;
    abspos = GXM * (int) y + x;

    update();
}

bool Stash::are_items_same(const item_def &a, const item_def &b)
{
    const bool same = a.base_type == b.base_type
        && a.sub_type == b.sub_type
        && a.plus == b.plus
        && a.plus2 == b.plus2
        && a.special == b.special
        && a.colour == b.colour
        && a.flags == b.flags
        && a.quantity == b.quantity;

    // Account for rotting meat when comparing items.
    return (same
            || (a.base_type == b.base_type
                && (a.base_type == OBJ_CORPSES
                    || (a.base_type == OBJ_FOOD && a.sub_type == FOOD_CHUNK
                        && b.sub_type == FOOD_CHUNK))
                && a.plus == b.plus));
}

void Stash::filter(const std::string &str)
{
    std::string base = str;

    unsigned char subc = 255;
    std::string   subs = "";
    std::string::size_type cpos = base.find(":", 0);
    if (cpos != std::string::npos)
    {
        subc = atoi(subs.c_str());

        base = base.substr(0, cpos);
    }

    const int base_num = atoi(base.c_str());
    if (base_num == 0 && base != "0" || subc == 0 && subs != "0")
    {
        item_types_pair pair = item_types_by_name(str);
        if (pair.base_type == OBJ_UNASSIGNED)
        {
            Options.report_error("Invalid stash filter '" + str + "'");
            return;
        }
        filter(pair.base_type, pair.sub_type);
    }
    else
    {
        const object_class_type basec =
            static_cast<object_class_type>(base_num);
        filter(basec, subc);
    }
}

void Stash::filter(object_class_type base, unsigned char sub)
{
    item_def item;
    item.base_type = base;
    item.sub_type  = sub;

    filters.push_back(item);
}

bool Stash::is_filtered(const item_def &item)
{
    for (int i = 0, count = filters.size(); i < count; ++i)
    {
        const item_def &filter = filters[i];
        if (item.base_type == filter.base_type
            && (filter.sub_type == 255
                || item.sub_type == filter.sub_type))
        {
            if (is_artefact(item))
                return (false);
            if (filter.sub_type != 255 && !item_type_known(item))
                return (false);
            return (true);
        }
    }
    return (false);
}

bool Stash::unverified() const
{
    return (!verified);
}

bool Stash::pickup_eligible() const
{
    for (int i = 0, size = items.size(); i < size; ++i)
        if (item_needs_autopickup(items[i]))
            return (true);

    return (false);
}

bool Stash::is_boring_feature(dungeon_feature_type feat)
{
    switch (feat)
    {
    // Discard spammy dungeon features.
    case DNGN_SHALLOW_WATER:
    case DNGN_DEEP_WATER:
    case DNGN_LAVA:
    case DNGN_OPEN_DOOR:
    case DNGN_STONE_STAIRS_DOWN_I:
    case DNGN_STONE_STAIRS_DOWN_II:
    case DNGN_STONE_STAIRS_DOWN_III:
    case DNGN_STONE_STAIRS_UP_I:
    case DNGN_STONE_STAIRS_UP_II:
    case DNGN_STONE_STAIRS_UP_III:
    case DNGN_ESCAPE_HATCH_DOWN:
    case DNGN_ESCAPE_HATCH_UP:
    case DNGN_ENTER_SHOP:
    case DNGN_ABANDONED_SHOP:
    case DNGN_UNDISCOVERED_TRAP:
        return (true);
    default:
        return (feat_is_solid(feat));
    }
}

static bool _grid_has_mimic_item(const coord_def& pos)
{
    const monsters *mon = monster_at(pos);
    return (mon && mons_is_unknown_mimic(mon));
}

static bool _grid_has_perceived_item(const coord_def& pos)
{
    return (you.visible_igrd(pos) != NON_ITEM || _grid_has_mimic_item(pos));
}

static bool _grid_has_perceived_multiple_items(const coord_def& pos)
{
    int count = 0;

    if (_grid_has_mimic_item(pos))
        ++count;

    for (stack_iterator si(pos, true); si && count < 2; ++si)
        ++count;

    return (count > 1);
}

void Stash::update()
{
    coord_def p(x,y);
    feat = grd(p);
    trap = NUM_TRAPS;

    if (is_boring_feature(feat))
        feat = DNGN_FLOOR;

    if (feat_is_trap(feat))
        trap = get_trap_type(p);

    // If this is your position, you know what's on this square
    if (p == you.pos())
    {
        // Zap existing items
        items.clear();

        // Now, grab all items on that square and fill our vector
        for (stack_iterator si(p, true); si; ++si)
            if (!is_filtered(*si))
                add_item(*si);

        verified = true;
    }
    // If this is not your position, the only thing we can do is verify that
    // what the player sees on the square is the first item in this vector.
    else
    {
        if (!_grid_has_perceived_item(p))
        {
            items.clear();
            verified = true;
            return;
        }

        // There's something on this square. Take a squint at it.
        const item_def *pitem;
        if (_grid_has_mimic_item(p))
            pitem = &get_mimic_item(monster_at(p));
        else
        {
            pitem = &mitm[you.visible_igrd(p)];
            tutorial_first_item(*pitem);
        }

        const item_def& item = *pitem;

        if (!_grid_has_perceived_multiple_items(p))
            items.clear();

        // We knew of nothing on this square, so we'll assume this is the
        // only item here, but mark it as unverified unless we can see nothing
        // under the item.
        if (items.size() == 0)
        {
            if (!is_filtered(item))
                add_item(item);
            // Note that we could be lying here, since we can have
            // a verified falsehood (if there's a mimic.)
            verified = !_grid_has_perceived_multiple_items(p);
            return;
        }

        // There's more than one item in this pile. As long as the top item is
        // not filtered, we can check to see if it matches what we think the
        // top item is.

        if (is_filtered(item))
            return;

        const item_def &first = items[0];
        // Compare these items
        if (!are_items_same(first, item))
        {
            if (aggressive_verify)
            {
                // See if 'item' matches any of the items we have. If it does,
                // we'll just make that the first item and leave 'verified'
                // unchanged.

                // Start from 1 because we've already checked items[0]
                for (int i = 1, count = items.size(); i < count; ++i)
                {
                    if (are_items_same(items[i], item))
                    {
                        // Found it. Swap it to the front of the vector.
                        std::swap(items[i], items[0]);

                        // We don't set verified to true. If this stash was
                        // already unverified, it remains so.
                        return;
                    }
                }
            }

            // If this is unverified, forget last item on stack. This isn't
            // terribly clever, but it prevents the vector swelling forever.
            if (!verified)
                items.pop_back();

            // Items are different. We'll put this item in the front of our
            // vector, and mark this as unverified
            add_item(item, true);
            verified = false;
        }
    }
}

static bool _is_rottable(const item_def &item)
{
    return (item.base_type == OBJ_CORPSES
            || (item.base_type == OBJ_FOOD && item.sub_type == FOOD_CHUNK));
}

static short _min_rot(const item_def &item)
{
    if (item.base_type == OBJ_FOOD)
        return 0;

    if (item.base_type == OBJ_CORPSES && item.sub_type == CORPSE_SKELETON)
        return 0;

    if (!mons_skeleton(item.plus))
        return 0;
    else
        return -200;
}

// Returns the item name for a given item, with any appropriate
// stash-tracking pre/suffixes.
std::string Stash::stash_item_name(const item_def &item)
{
    std::string name = item.name(DESC_NOCAP_A);

    if (!_is_rottable(item))
        return name;

    if (item.plus2 <= _min_rot(item))
    {
        name += " (gone by now)";
        return name;
    }

    // Skeletons show no signs of rotting before they're gone
    if (item.base_type == OBJ_CORPSES && item.sub_type == CORPSE_SKELETON)
        return name;

    // Item was already seen to be rotten
    if (item.special < 100)
        return name;

    if (item.plus2 <= 0)
        name += " (skeletalised by now)";
    else if (item.plus2 < 100)
        name += " (rotten by now)";

    return name;
}

class StashMenu : public InvMenu
{
public:
    StashMenu() : InvMenu(MF_SINGLESELECT), can_travel(false)
    {
        set_type(MT_PICKUP);
        set_tag("stash");       // override "inventory" tag
    }
    unsigned char getkey() const;
public:
    bool can_travel;
protected:
    void draw_title();
    bool process_key(int key);
};

void StashMenu::draw_title()
{
    if (title)
    {
        cgotoxy(1, 1);
        textcolor(title->colour);
        cprintf( "%s", title->text.c_str());
        if (title->quantity)
        {
            cprintf(", %d item%s", title->quantity,
                                   title->quantity == 1? "" : "s");
        }
        cprintf(")");

        if (action_cycle == Menu::CYCLE_TOGGLE)
        {
            cprintf("  [a-z: %s  ?/!: %s]",
                    menu_action == ACT_EXAMINE ? "examine" : "shopping",
                    menu_action == ACT_EXAMINE ? "shopping" : "examine");
        }

        if (can_travel)
        {
            if (action_cycle == Menu::CYCLE_TOGGLE)
            {
                // XXX: This won't fit in the title, so it goes into the
                // footer/-more-.  Not ideal, but I don't know where else
                // to put it.
                std::string str = "<w>[ENTER: travel]</w>";
                set_more(formatted_string::parse_string(str));
                flags |= MF_ALWAYS_SHOW_MORE;
            }
            else
                cprintf("  [ENTER: travel]");
        }
    }
}

bool StashMenu::process_key(int key)
{
    if (key == CK_ENTER)
    {
        // Travel activates.
        lastch = 1;
        return (false);
    }
    return Menu::process_key(key);
}

unsigned char StashMenu::getkey() const
{
    return (lastch);
}

static MenuEntry *stash_menu_fixup(MenuEntry *me)
{
    const item_def *item = static_cast<const item_def *>( me->data );
    if (item->base_type == OBJ_GOLD)
    {
        me->quantity = 0;
        me->colour   = DARKGREY;
    }

    return (me);
}

bool Stash::show_menu(const level_pos &prefix, bool can_travel) const
{
    const std::string prefix_str = short_place_name(prefix.id);
    StashMenu menu;

    MenuEntry *mtitle = new MenuEntry("Stash (" + prefix_str, MEL_TITLE);
    menu.can_travel   = can_travel;
    mtitle->quantity  = items.size();
    menu.set_title(mtitle);
    menu.load_items( InvMenu::xlat_itemvect(items), stash_menu_fixup);

    std::vector<MenuEntry*> sel;
    while (true)
    {
        sel = menu.show();
        if (menu.getkey() == 1)
            return (true);

        if (sel.size() != 1)
            break;

        item_def *item = static_cast<item_def *>( sel[0]->data );
        describe_item(*item);
    }
    return (false);
}

std::string Stash::description() const
{
    if (!enabled || items.empty())
        return ("");

    const item_def &item = items[0];
    std::string desc = stash_item_name(item);

    size_t sz = items.size();
    if (sz > 1)
    {
        char additionals[50];
        snprintf(additionals, sizeof additionals,
                " (...%ld)",
                 (unsigned long) (sz - 1));
        desc += additionals;
    }
    return (desc);
}

std::string Stash::feature_description() const
{
    if (feat == DNGN_FLOOR)
        return ("");

    return (::feature_description(feat, trap));
}

bool Stash::matches_search(const std::string &prefix,
                           const base_pattern &search,
                           stash_search_result &res) const
{
    if (!enabled || items.empty() && feat == DNGN_FLOOR)
        return (false);

    for (unsigned i = 0; i < items.size(); ++i)
    {
        const item_def &item = items[i];
        std::string s   = stash_item_name(item);
        std::string ann = stash_annotate_item(STASH_LUA_SEARCH_ANNOTATE, &item);
        if (search.matches(prefix + " " + ann + s))
        {
            if (!res.count++)
                res.match = s;
            res.matches += item.quantity;
            continue;
        }

        if (is_dumpable_artefact(item, false))
        {
            std::string desc =
                munge_description(get_item_description(item, false, true));

            if (search.matches(desc))
            {
                if (!res.count++)
                    res.match = s;
                res.matches += item.quantity;
            }
        }
    }

    if (!res.matches && feat != DNGN_FLOOR)
    {
        const std::string fdesc = feature_description();
        if (!fdesc.empty() && search.matches(fdesc))
        {
            res.match = fdesc;
            res.matches = 1;
        }
    }

    if (res.matches)
    {
        res.stash = this;
        // XXX pos.pos looks lame. Lameness is not solicited.
        res.pos.pos.x = x;
        res.pos.pos.y = y;
    }

    return !!res.matches;
}

void Stash::_update_corpses(long rot_time)
{
    for (int i = items.size() - 1; i >= 0; i--)
    {
        item_def &item = items[i];

        if (!_is_rottable(item))
            continue;

        long new_rot = static_cast<long>(item.plus2) - rot_time;

        if (new_rot <= _min_rot(item))
        {
            items.erase(items.begin() + i);
            continue;
        }
        item.plus2 = static_cast<short>(new_rot);
    }
}

void Stash::add_item(const item_def &item, bool add_to_front)
{
    if (_is_rottable(item))
        StashTrack.update_corpses();

    if (add_to_front)
        items.insert(items.begin(), item);
    else
        items.push_back(item);

    seen_item(item);

    if (!_is_rottable(item))
        return;

    // item.special remains unchanged in the stash, to show how fresh it
    // was when last seen.  It's plus2 that's decayed over time.
    if (add_to_front)
    {
        item_def &it = items.front();
        it.plus2     = it.special;
    }
    else
    {
        item_def &it = items.back();
        it.plus2     = it.special;
    }
}

void Stash::write(std::ostream &os, int refx, int refy,
                  std::string place, bool identify)
    const
{
    if (!enabled || (items.size() == 0 && verified))
        return;

    bool note_status = notes_are_active();
    activate_notes(false);

    os << "(" << ((int) x - refx) << ", " << ((int) y - refy)
       << (place.length()? ", " + place : "")
       << ")"
       << std::endl;

    char buf[ITEMNAME_SIZE];
    for (int i = 0; i < (int) items.size(); ++i)
    {
        item_def item = items[i];

        if (identify)
            _fully_identify_item(&item);

        std::string s = stash_item_name(item);
        strncpy(buf, s.c_str(), sizeof buf);

        std::string ann = userdef_annotate_item(STASH_LUA_DUMP_ANNOTATE, &item);

        if (!ann.empty())
        {
            trim_string(ann);
            ann = " " + ann;
        }

        os << "  " << buf
           << (!ann.empty()? ann : std::string())
           << (!verified && (items.size() > 1 || i) ? " (still there?)" : "")
           << std::endl;

        if (is_dumpable_artefact(item, false))
        {
            std::string desc =
                munge_description(get_item_description(item, false, true));

            // Kill leading and trailing whitespace
            desc.erase(desc.find_last_not_of(" \n\t") + 1);
            desc.erase(0, desc.find_first_not_of(" \n\t"));
            // If string is not-empty, pad out to a neat indent
            if (desc.length())
            {
                // Walk backwards and prepend indenting spaces to \n characters.
                for (int j = desc.length() - 1; j >= 0; --j)
                    if (desc[j] == '\n')
                        desc.insert(j + 1, " ");

                os << "    " << desc << std::endl;
            }
        }
    }

    if (items.size() <= 1 && !verified)
        os << "  (unseen)" << std::endl;

    activate_notes(note_status);
}

void Stash::save(writer& outf) const
{
    // How many items on this square?
    marshallShort(outf, (short) items.size());

    marshallByte(outf, x);
    marshallByte(outf, y);

    marshallByte(outf, feat);
    marshallByte(outf, trap);

    // Note: Enabled save value is inverted logic, so that it defaults to true
    marshallByte(outf,
        (unsigned char) ((verified? 1 : 0) | (!enabled? 2 : 0)) );

    // And dump the items individually. We don't bother saving fields we're
    // not interested in (and don't anticipate being interested in).
    for (unsigned i = 0; i < items.size(); ++i)
        marshallItem(outf, items[i]);
}

void Stash::load(reader& inf)
{
    // How many items?
    int count = unmarshallShort(inf);

    x = unmarshallByte(inf);
    y = unmarshallByte(inf);

    feat =  static_cast<dungeon_feature_type>(
                static_cast<unsigned char>( unmarshallByte(inf) ));
    trap =  static_cast<trap_type>(
                static_cast<unsigned char>( unmarshallByte(inf) ));

    unsigned char flags = unmarshallByte(inf);
    verified = (flags & 1) != 0;

    // Note: Enabled save value is inverted so it defaults to true.
    enabled  = (flags & 2) == 0;

    abspos = GXM * (int) y + x;

    // Zap out item vector, in case it's in use (however unlikely)
    items.clear();
    // Read in the items
    for (int i = 0; i < count; ++i)
    {
        item_def item;
        unmarshallItem(inf, item);

        items.push_back(item);
    }
}

std::ostream &operator << (std::ostream &os, const Stash &s)
{
    s.write(os);
    return os;
}

ShopInfo::ShopInfo(int xp, int yp) : x(xp), y(yp), name(), shoptype(-1),
                                     visited(false), items()
{
    // Most of our initialization will be done externally; this class is really
    // a mildly glorified struct.
    const shop_struct *sh = get_shop(coord_def(x, y));
    if (sh)
        shoptype = sh->type;
}

void ShopInfo::add_item(const item_def &sitem, unsigned price)
{
    shop_item it;
    it.item  = sitem;
    it.price = price;
    items.push_back(it);
}

std::string ShopInfo::shop_item_name(const shop_item &si) const
{
    const unsigned long oldflags = si.item.flags;

    if (shoptype_identifies_stock(static_cast<shop_type>(this->shoptype)))
        const_cast<shop_item&>(si).item.flags |= ISFLAG_IDENT_MASK;

    if (oldflags != si.item.flags)
        const_cast<shop_item&>(si).item.flags = oldflags;

    return make_stringf("%s (%u gold)", Stash::stash_item_name(si.item).c_str(), si.price);
}

std::string ShopInfo::shop_item_desc(const shop_item &si) const
{
    std::string desc;

    const unsigned long oldflags = si.item.flags;

    if (shoptype_identifies_stock(static_cast<shop_type>(this->shoptype)))
        const_cast<shop_item&>(si).item.flags |= ISFLAG_IDENT_MASK;

    if (is_dumpable_artefact(si.item, false))
    {
        desc = munge_description(get_item_description(si.item, false, true));
        trim_string(desc);

        // Walk backwards and prepend indenting spaces to \n characters
        for (int i = desc.length() - 1; i >= 0; --i)
            if (desc[i] == '\n')
                desc.insert(i + 1, " ");
    }

    if (oldflags != si.item.flags)
        const_cast<shop_item&>(si).item.flags = oldflags;

    return desc;
}

void ShopInfo::describe_shop_item(const shop_item &si) const
{
    const unsigned long oldflags = si.item.flags;

    if (shoptype_identifies_stock(static_cast<shop_type>(this->shoptype)))
        const_cast<shop_item&>(si).item.flags |= ISFLAG_IDENT_MASK;

    item_def it = static_cast<item_def>(si.item);
    describe_item( it );

    if (oldflags != si.item.flags)
        const_cast<shop_item&>(si).item.flags = oldflags;
}

class ShopItemEntry : public InvEntry
{
public:
    ShopItemEntry(const ShopInfo::shop_item &it,
                  const std::string &item_name,
                  menu_letter hotkey) : InvEntry(it.item)
    {
        text = item_name;
        hotkeys[0] = hotkey;
    }
};

void ShopInfo::fill_out_menu(StashMenu &menu, const level_pos &place) const
{
    menu.clear();

    menu_letter hotkey;
    for (int i = 0, count = items.size(); i < count; ++i)
    {
        ShopItemEntry *me = new ShopItemEntry(items[i],
                                              shop_item_name(items[i]),
                                              hotkey++);
        if (shopping_list.is_on_list(items[i].item, &place))
            me->colour = LIGHTCYAN;
        menu.add_entry(me);
    }
}

bool ShopInfo::show_menu(const level_pos &place,
                         bool can_travel) const
{
    const std::string place_str = short_place_name(place.id);

    StashMenu menu;

    MenuEntry *mtitle = new MenuEntry(name + " (" + place_str, MEL_TITLE);
    menu.can_travel   = can_travel;
    menu.action_cycle = Menu::CYCLE_TOGGLE;
    menu.menu_action  = Menu::ACT_EXAMINE;
    mtitle->quantity  = items.size();
    menu.set_title(mtitle);

    if (items.empty())
    {
        MenuEntry *me = new MenuEntry(
                visited? "  (Shop is empty)" : "  (Shop contents are unknown)",
                MEL_ITEM,
                0,
                0);
        me->colour = DARKGREY;
        menu.add_entry(me);
    }
    else
        fill_out_menu(menu, place);

    std::vector<MenuEntry*> sel;
    while (true)
    {
        sel = menu.show();
        if (menu.getkey() == 1)
            return (true);

        if (sel.size() != 1)
            break;

        const shop_item *item = static_cast<const shop_item *>( sel[0]->data );
        if (menu.menu_action == Menu::ACT_EXAMINE)
            describe_shop_item(*item);
        else
        {
            if (shopping_list.is_on_list(item->item, &place))
                shopping_list.del_thing(item->item, &place);
            else
                shopping_list.add_thing(item->item, item->price, &place);

            // If the shop has identical items (like stacks of food in a
            // food shop) then adding/removing one to the shopping list
            // will have the same effect on the others, so the other
            // identical items will need to be re-coloured.
            fill_out_menu(menu, place);
        }
    }
    return (false);
}

std::string ShopInfo::description() const
{
    return (name);
}

bool ShopInfo::matches_search(const std::string &prefix,
                              const base_pattern &search,
                              stash_search_result &res) const
{
    if (items.empty() && visited)
        return (false);

    bool note_status = notes_are_active();
    activate_notes(false);

    bool match = false;

    for (unsigned i = 0; i < items.size(); ++i)
    {
        std::string sname = shop_item_name(items[i]);
        std::string ann   = stash_annotate_item( STASH_LUA_SEARCH_ANNOTATE,
                                                  &items[i].item, true );

        bool thismatch = false;
        if (search.matches(prefix + " " + ann + sname))
            thismatch = true;
        else
        {
            std::string desc = shop_item_desc(items[i]);
            if (search.matches(desc))
                thismatch = true;
        }

        if (thismatch)
        {
            if (!res.count++)
                res.match = sname;
            res.matches++;
        }
    }

    if (!res.matches)
    {
        std::string shoptitle = prefix + " {shop} " + name;
        if (!visited && items.empty())
            shoptitle += "*";
        if (search.matches(shoptitle))
        {
            match = true;
            res.match = name;
        }
    }

    if (match || res.matches)
    {
        res.shop = this;
        res.pos.pos.x = x;
        res.pos.pos.y = y;
    }

    activate_notes(note_status);
    return (match || res.matches);
}

void ShopInfo::write(std::ostream &os, bool identify) const
{
    bool note_status = notes_are_active();
    activate_notes(false);
    os << "[Shop] " << name << std::endl;
    if (items.size() > 0)
    {
        for (unsigned i = 0; i < items.size(); ++i)
        {
            shop_item item = items[i];

            if (identify)
                _fully_identify_item(&item.item);

            os << "  " << shop_item_name(item) << std::endl;
            std::string desc = shop_item_desc(item);
            if (desc.length() > 0)
                os << "    " << desc << std::endl;
        }
    }
    else if (visited)
        os << "  (Shop is empty)" << std::endl;
    else
        os << "  (Shop contents are unknown)" << std::endl;

    activate_notes(note_status);
}

void ShopInfo::save(writer& outf) const
{
    marshallShort(outf, shoptype);

    int mangledx = (short) x;
    if (!visited)
        mangledx |= 1024;
    marshallShort(outf, mangledx);
    marshallShort(outf, (short) y);

    marshallShort(outf, (short) items.size());

    marshallString4(outf, name);

    for (unsigned i = 0; i < items.size(); ++i)
    {
        marshallItem(outf, items[i].item);
        marshallShort(outf, (short) items[i].price );
    }
}

void ShopInfo::load(reader& inf)
{
    shoptype = unmarshallShort(inf);

    x = unmarshallShort(inf);
    visited = !(x & 1024);
    x &= 0xFF;

    y = unmarshallShort(inf);

    int itemcount = unmarshallShort(inf);

    unmarshallString4(inf, name);
    for (int i = 0; i < itemcount; ++i)
    {
        shop_item item;
        unmarshallItem(inf, item.item);
        item.price = (unsigned) unmarshallShort(inf);
        items.push_back(item);
    }
}

std::ostream &operator << (std::ostream &os, const ShopInfo &s)
{
    s.write(os);
    return os;
}

LevelStashes::LevelStashes()
    : m_place(level_id::current()),
      m_stashes(),
      m_shops()
{
}

level_id LevelStashes::where() const
{
    return m_place;
}

Stash *LevelStashes::find_stash(int x, int y)
{
    if (x == -1 || y == -1)
    {
        x = you.pos().x;
        y = you.pos().y;
    }
    const int abspos = (GXM * y) + x;
    stashes_t::iterator st = m_stashes.find(abspos);
    return (st == m_stashes.end()? NULL : &st->second);
}

const Stash *LevelStashes::find_stash(int x, int y) const
{
    if (x == -1 || y == -1)
    {
        x = you.pos().x;
        y = you.pos().y;
    }
    const int abspos = (GXM * y) + x;
    stashes_t::const_iterator st = m_stashes.find(abspos);
    return (st == m_stashes.end()? NULL : &st->second);
}

const ShopInfo *LevelStashes::find_shop(int x, int y) const
{
    for (unsigned i = 0; i < m_shops.size(); ++i)
        if (m_shops[i].isAt(x, y))
            return (&m_shops[i]);

    return (NULL);
}

bool LevelStashes::shop_needs_visit(int x, int y) const
{
    const ShopInfo *shop = find_shop(x, y);
    return (shop && !shop->is_visited());
}

bool LevelStashes::needs_visit(int x, int y) const
{
    const Stash *s = find_stash(x, y);
    if (s && (s->unverified() || s->pickup_eligible()))
        return (true);

    return (shop_needs_visit(x, y));
}

ShopInfo &LevelStashes::get_shop(int x, int y)
{
    for (unsigned i = 0; i < m_shops.size(); ++i)
    {
        if (m_shops[i].isAt(x, y))
            return m_shops[i];
    }
    ShopInfo si(x, y);
    si.set_name(shop_name(coord_def(x, y)));
    m_shops.push_back(si);
    return get_shop(x, y);
}

// Updates the stash at (x,y). Returns true if there was a stash at (x,y), false
// otherwise.
bool LevelStashes::update_stash(int x, int y)
{
    Stash *s = find_stash(x, y);
    if (s)
    {
        s->update();
        if (s->empty())
            kill_stash(*s);
        return (true);
    }
    return (false);
}

// Removes a Stash from the level.
void LevelStashes::kill_stash(const Stash &s)
{
    m_stashes.erase(s.abs_pos());
}

void LevelStashes::no_stash(int x, int y)
{
    Stash *s = find_stash(x, y);
    bool en = false;
    if (s)
    {
        en = s->enabled = !s->enabled;
        s->update();
        if (s->empty())
            kill_stash(*s);
    }
    else
    {
        Stash newStash(x, y);
        newStash.enabled = false;

        m_stashes[ newStash.abs_pos() ] = newStash;
    }

    mpr(en? "I'll no longer ignore what I see on this square."
          : "Ok, I'll ignore what I see on this square.");
}

void LevelStashes::add_stash(int x, int y)
{
    Stash *s = find_stash(x, y);
    if (s)
    {
        s->update();
        if (s->empty())
            kill_stash(*s);
    }
    else
    {
        Stash new_stash(x, y);
        if (!new_stash.empty())
            m_stashes[ new_stash.abs_pos() ] = new_stash;
    }
}

bool LevelStashes::is_current() const
{
    return (m_place == level_id::current());
}

std::string LevelStashes::level_name() const
{
    return m_place.describe(true, true);
}

std::string LevelStashes::short_level_name() const
{
    return m_place.describe();
}

int LevelStashes::_num_enabled_stashes() const
{
    int rawcount = m_stashes.size();
    if (!rawcount)
        return (0);

    for (stashes_t::const_iterator iter = m_stashes.begin();
            iter != m_stashes.end(); iter++)
    {
        if (!iter->second.enabled)
            --rawcount;
    }
    return rawcount;
}

void LevelStashes::get_matching_stashes(
        const base_pattern &search,
        std::vector<stash_search_result> &results) const
{
    std::string lplace = "{" + m_place.describe() + "}";
    for (stashes_t::const_iterator iter = m_stashes.begin();
            iter != m_stashes.end(); iter++)
    {
        if (iter->second.enabled)
        {
            stash_search_result res;
            if (iter->second.matches_search(lplace, search, res))
            {
                res.pos.id = m_place;
                results.push_back(res);
            }
        }
    }

    for (unsigned i = 0; i < m_shops.size(); ++i)
    {
        stash_search_result res;
        if (m_shops[i].matches_search(lplace, search, res))
        {
            res.pos.id = m_place;
            results.push_back(res);
        }
    }
}

void LevelStashes::_update_corpses(long rot_time)
{
    for (stashes_t::iterator iter = m_stashes.begin();
            iter != m_stashes.end(); iter++)
    {
        iter->second._update_corpses(rot_time);
    }
}

void LevelStashes::write(std::ostream &os, bool identify) const
{
    if (visible_stash_count() == 0)
        return;

    os << level_name() << std::endl;

    for (unsigned i = 0; i < m_shops.size(); ++i)
        m_shops[i].write(os, identify);

    if (m_stashes.size())
    {
        const Stash &s = m_stashes.begin()->second;
        int refx = s.getX(), refy = s.getY();
        std::string levname = short_level_name();
        for (stashes_t::const_iterator iter = m_stashes.begin();
             iter != m_stashes.end(); iter++)
        {
            iter->second.write(os, refx, refy, levname, identify);
        }
    }
    os << std::endl;
}

void LevelStashes::save(writer& outf) const
{
    // How many stashes on this level?
    marshallShort(outf, (short) m_stashes.size());

    m_place.save(outf);

    // And write the individual stashes
    for (stashes_t::const_iterator iter = m_stashes.begin();
         iter != m_stashes.end(); iter++)
    {
        iter->second.save(outf);
    }

    marshallShort(outf, (short) m_shops.size());
    for (unsigned i = 0; i < m_shops.size(); ++i)
        m_shops[i].save(outf);
}

void LevelStashes::load(reader& inf)
{
    int size = unmarshallShort(inf);

    m_place.load(inf);

    m_stashes.clear();
    for (int i = 0; i < size; ++i)
    {
        Stash s;
        s.load(inf);
        if (!s.empty())
            m_stashes[ s.abs_pos() ] = s;
    }

    m_shops.clear();
    int shopc = unmarshallShort(inf);
    for (int i = 0; i < shopc; ++i)
    {
        ShopInfo si(0, 0);
        si.load(inf);
        m_shops.push_back(si);
    }
}

std::ostream &operator << (std::ostream &os, const LevelStashes &ls)
{
    ls.write(os);
    return os;
}

LevelStashes &StashTracker::get_current_level()
{
    return (levels[level_id::current()]);
}

LevelStashes *StashTracker::find_level(const level_id &id)
{
    stash_levels_t::iterator i = levels.find(id);
    return (i != levels.end()? &i->second : NULL);
}

LevelStashes *StashTracker::find_current_level()
{
    if (is_level_untrackable())
        return (NULL);

    return find_level(level_id::current());
}


bool StashTracker::update_stash(int x, int y)
{
    LevelStashes *lev = find_current_level();
    if (lev)
    {
        bool res = lev->update_stash(x, y);
        if (!lev->stash_count())
            remove_level();
        return (res);
    }
    return (false);
}

void StashTracker::remove_level(const level_id &place)
{
    levels.erase(place);
}

void StashTracker::no_stash(int x, int y)
{
    if (is_level_untrackable())
        return ;
    LevelStashes &current = get_current_level();
    current.no_stash(x, y);
    if (!current.stash_count())
        remove_level();
}

void StashTracker::add_stash(int x, int y, bool verbose)
{
    if (is_level_untrackable())
        return ;
    LevelStashes &current = get_current_level();
    current.add_stash(x, y);

    if (verbose)
    {
        Stash *s = current.find_stash(x, y);
        if (s && s->enabled)
            mpr("Added stash.");
    }

    if (!current.stash_count())
        remove_level();
}

void StashTracker::dump(const char *filename, bool identify) const
{
    std::ofstream outf(filename);
    if (outf)
    {
        write(outf, identify);
        outf.close();
    }
}

void StashTracker::write(std::ostream &os, bool identify) const
{
    os << you.your_name << std::endl << std::endl;
    if (!levels.size())
        os << "  You have no stashes." << std::endl;
    else
    {
        for (stash_levels_t::const_iterator iter = levels.begin();
             iter != levels.end(); iter++)
        {
            iter->second.write(os, identify);
        }
    }
}

void StashTracker::save(writer& outf) const
{
    // Write version info first - major + minor
    marshallByte(outf, ST_MAJOR_VER);
    marshallByte(outf, ST_MINOR_VER);

    // Time of last corpse update.
    marshallFloat(outf, (float) last_corpse_update);

    // How many levels have we?
    marshallShort(outf, (short) levels.size());

    // And ask each level to write itself to the tag
    stash_levels_t::const_iterator iter = levels.begin();
    for ( ; iter != levels.end(); iter++)
        iter->second.save(outf);
}

void StashTracker::load(reader& inf)
{
    // Check version. Compatibility isn't important, since stash-tracking
    // is non-critical.
    unsigned char major = unmarshallByte(inf),
                  minor = unmarshallByte(inf);
    if (major != ST_MAJOR_VER || minor != ST_MINOR_VER) return ;

    // Time of last corpse update.
    last_corpse_update = (double) unmarshallFloat(inf);

    int count = unmarshallShort(inf);

    levels.clear();
    for (int i = 0; i < count; ++i)
    {
        LevelStashes st;
        st.load(inf);
        if (st.stash_count())
            levels[st.where()] = st;
    }
}

void StashTracker::update_visible_stashes(
                                    StashTracker::stash_update_mode mode)
{
    if (is_level_untrackable())
        return;

    LevelStashes *lev = find_current_level();
    for (int cy = crawl_view.glos1.y; cy <= crawl_view.glos2.y; ++cy)
        for (int cx = crawl_view.glos1.x; cx <= crawl_view.glos2.x; ++cx)
        {
            if (!in_bounds(cx, cy) || !you.see_cell(coord_def(cx, cy)))
                continue;

            const dungeon_feature_type grid = grd[cx][cy];
            if ((!lev || !lev->update_stash(cx, cy))
                && mode == ST_AGGRESSIVE
                && (_grid_has_perceived_item(coord_def(cx,cy))
                    || !Stash::is_boring_feature(grid)))
            {
                if (!lev)
                    lev = &get_current_level();
                lev->add_stash(cx, cy);
            }

            if (grid == DNGN_ENTER_SHOP)
                get_shop(cx, cy);
        }

    if (lev && !lev->stash_count())
        remove_level();
}

#define SEARCH_SPAM_THRESHOLD 400
static std::string lastsearch;
static input_history search_history(15);

void StashTracker::show_stash_search_prompt()
{
    std::vector<std::string> opts;
    if (!lastsearch.empty())
        opts.push_back(
            make_stringf("Enter for \"%s\"", lastsearch.c_str()) );
    if (level_type_is_stash_trackable(you.level_type)
        && lastsearch != ".")
    {
        opts.push_back("? for help");
    }

    std::string prompt_qual =
        comma_separated_line(opts.begin(), opts.end(), ", or ", ", or ");

    if (!prompt_qual.empty())
        prompt_qual = " [" + prompt_qual + "]";

    mprf(MSGCH_PROMPT, "Search for what%s?", prompt_qual.c_str());
    // Push the cursor down to the next line. Newline on the prompt will not
    // do the trick on DOS.
    mpr("", MSGCH_PROMPT);
}

class stash_search_reader : public line_reader
{
public:
    stash_search_reader(char *buf, size_t sz,
                        int wcol = get_number_of_cols())
        : line_reader(buf, sz, wcol)
    {
    }
protected:
    int process_key(int ch)
    {
        if (ch == '?' && !pos)
        {
            *buffer = 0;
            return (ch);
        }
        return line_reader::process_key(ch);
    }
};

// helper for search_stashes
struct compare_by_distance
{
    bool operator()(const stash_search_result& lhs,
                    const stash_search_result& rhs)
    {
        if (lhs.player_distance != rhs.player_distance)
        {
            // Sort by increasing distance
            return (lhs.player_distance < rhs.player_distance);
        }
        else if (lhs.matches != rhs.matches)
        {
            // Then by decreasing number of matches
            return (lhs.matches > rhs.matches);
        }
        else
            return (false);
    }
};

// helper for search_stashes
struct compare_by_name
{
    bool operator()(const stash_search_result& lhs,
                    const stash_search_result& rhs)
    {
        if (lhs.match != rhs.match)
        {
            // Sort by name
            return (lhs.match < rhs.match);
        }
        else if (lhs.player_distance != rhs.player_distance)
        {
            // Then sort by increasing distance
            return (lhs.player_distance < rhs.player_distance);
        }
        else if (lhs.matches != rhs.matches)
        {
            // Then by decreasing number of matches
            return (lhs.matches > rhs.matches);
        }
        else
            return (false);
    }
};

void StashTracker::search_stashes()
{
    char buf[400];

    this->update_corpses();

    stash_search_reader reader(buf, sizeof buf);

    bool validline = false;
    while (true)
    {
        show_stash_search_prompt();

        int ret = reader.read_line();
        if (!ret)
        {
            validline = true;
            break;
        }
        else if (ret == '?')
        {
            show_stash_search_help();
            redraw_screen();
        }
        else
        {
            break;
        }
    }

    mesclr();
    if (!validline || (!*buf && !lastsearch.length()))
        return;

    std::string csearch = *buf? buf : lastsearch;
    std::string help = lastsearch;
    lastsearch = csearch;

    if (csearch == ".")
    {
        if (!level_type_is_stash_trackable(you.level_type))
        {
            mpr("Cannot track items on this level.");
            return;
        }
#if defined(REGEX_PCRE) || defined(REGEX_POSIX)
#define RE_ESCAPE "\\"
#else
#define RE_ESCAPE ""
#endif

        csearch = (RE_ESCAPE "{")
            + level_id::current().describe()
            + (RE_ESCAPE "}");
    }

    std::vector<stash_search_result> results;

    base_pattern *search = NULL;

    text_pattern tpat( csearch, true );
    search = &tpat;

    lua_text_pattern ltpat( csearch );

    if (lua_text_pattern::is_lua_pattern(csearch))
        search = &ltpat;

    if (!search->valid())
    {
        mpr("Your search expression is invalid.", MSGCH_PLAIN);
        lastsearch = help;
        return ;
    }

    get_matching_stashes(*search, results);

    if (results.empty())
    {
        mpr("Can't find anything matching that.", MSGCH_PLAIN);
        return;
    }

    if (results.size() > SEARCH_SPAM_THRESHOLD)
    {
        mpr("Too many matches; use a more specific search.", MSGCH_PLAIN);
        return;
    }

    bool sort_by_dist = true;
    while (true)
    {
        const char* sort_style;
        if (sort_by_dist)
        {
            std::sort(results.begin(), results.end(), compare_by_distance());
            sort_style = "by dist";
        }
        else
        {
            std::sort(results.begin(), results.end(), compare_by_name());
            sort_style = "by name";
        }

        const bool again = display_search_results(results, sort_style);
        if (!again)
            break;
        sort_by_dist = !sort_by_dist;
    }
}

void StashTracker::get_matching_stashes(
        const base_pattern &search,
        std::vector<stash_search_result> &results)
    const
{
    stash_levels_t::const_iterator iter = levels.begin();
    for ( ; iter != levels.end(); iter++)
    {
        iter->second.get_matching_stashes(search, results);
        if (results.size() > SEARCH_SPAM_THRESHOLD)
            return;
    }

    level_id curr = level_id::current();
    for (unsigned i = 0; i < results.size(); ++i)
    {
        int ldist = level_distance(curr, results[i].pos.id);
        if (ldist == -1)
            ldist = 1000;

        results[i].player_distance = ldist;
    }
}

class StashSearchMenu : public Menu
{
public:
    StashSearchMenu(const char* sort_style_)
        : Menu(), can_travel(true),
          request_toggle_sort_method(false),
          sort_style(sort_style_)
    { }

public:
    bool can_travel;
    bool request_toggle_sort_method;
    const char* sort_style;

protected:
    bool process_key(int key);
    void draw_title();
};

void StashSearchMenu::draw_title()
{
    if (title)
    {
        cgotoxy(1, 1);
        textcolor(title->colour);
        cprintf("%d %s%s, sorted %s",
                title->quantity, title->text.c_str(),
                title->quantity > 1? "es" : "",
                sort_style);

        draw_title_suffix(formatted_string::parse_string(make_stringf(
                 "<lightgrey>  [<w>a-z</w>: %s  <w>?</w>/<w>!</w>: change action  <w>/</w>: change sort]",
                 menu_action == ACT_EXECUTE ? "travel" : "examine")), false);
    }
}


bool StashSearchMenu::process_key(int key)
{
    if (key == '/')
    {
        request_toggle_sort_method = true;
        return (false);
    }

    return Menu::process_key(key);
}

// Returns true to request redisplay with a different sort method
bool StashTracker::display_search_results(
    std::vector<stash_search_result> &results,
    const char* sort_style)
{
    if (results.empty())
        return (false);

    bool travelable = can_travel_interlevel();

    StashSearchMenu stashmenu(sort_style);
    stashmenu.set_tag("stash");
    stashmenu.can_travel   = travelable;
    stashmenu.action_cycle = Menu::CYCLE_TOGGLE;
    stashmenu.menu_action  = Menu::ACT_EXECUTE;
    std::string title = "match";

    MenuEntry *mtitle = new MenuEntry(title, MEL_TITLE);
    // Abuse of the quantity field.
    mtitle->quantity = results.size();
    stashmenu.set_title(mtitle);

    // Don't make a menu so tall that we recycle hotkeys on the same page.
    if (results.size() > 52
        && (stashmenu.maxpagesize() > 52 || stashmenu.maxpagesize() == 0))
    {
        stashmenu.set_maxpagesize(52);
    }

    menu_letter hotkey;
    for (unsigned i = 0; i < results.size(); ++i, ++hotkey)
    {
        stash_search_result &res = results[i];
        std::ostringstream matchtitle;
        matchtitle << "[" << short_place_name(res.pos.id) << "] "
                   << res.match;

        if (res.matches > 1 && res.count > 1)
            matchtitle << " (+" << (res.matches - 1) << ")";

        MenuEntry *me = new MenuEntry(matchtitle.str(), MEL_ITEM, 1, hotkey);
        me->data = &res;

        if (res.shop && !res.shop->is_visited())
            me->colour = CYAN;

        stashmenu.add_entry(me);
    }

    stashmenu.set_flags( MF_SINGLESELECT );

    std::vector<MenuEntry*> sel;
    while (true)
    {
        sel = stashmenu.show();

        if (stashmenu.request_toggle_sort_method)
            return (true);

        if (sel.size() == 1
            && stashmenu.menu_action == StashSearchMenu::ACT_EXAMINE)
        {
            stash_search_result *res =
                static_cast<stash_search_result *>(sel[0]->data);

            bool dotravel = false;
            if (res->shop)
            {
                dotravel = res->shop->show_menu(res->pos,
                                                can_travel_to(res->pos.id));
            }
            else if (res->stash)
            {
                dotravel = res->stash->show_menu(res->pos,
                                                 can_travel_to(res->pos.id));
            }

            if (dotravel && can_travel_to(res->pos.id))
            {
                redraw_screen();
                const travel_target lp = res->pos;
                start_translevel_travel(lp);
                return (false);
            }
            continue;
        }
        break;
    }

    redraw_screen();
    if (sel.size() == 1 && stashmenu.menu_action == Menu::ACT_EXECUTE)
    {
        const stash_search_result *res =
                static_cast<stash_search_result *>(sel[0]->data);
        const level_pos lp = res->pos;
        start_translevel_travel(lp);
    }
    return (false);
}

void StashTracker::update_corpses()
{
    if (you.elapsed_time - last_corpse_update < 20.0)
        return;

    const long rot_time = static_cast<long>((you.elapsed_time -
                                             last_corpse_update) / 20.0);

    last_corpse_update = you.elapsed_time;

    for (stash_levels_t::iterator iter = levels.begin();
            iter != levels.end(); iter++)
    {
        iter->second._update_corpses(rot_time);
    }
}

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

ST_ItemIterator::ST_ItemIterator()
{
    m_stash_level_it = StashTrack.levels.begin();
    new_level();
    //(*this)++;
}

ST_ItemIterator::operator bool() const
{
    return (m_item != NULL);
}

const item_def& ST_ItemIterator::operator *() const
{
    return (*m_item);
}

const item_def* ST_ItemIterator::operator->() const
{
    return (m_item);
}

const level_id &ST_ItemIterator::place()
{
    return (m_place);
}

const ShopInfo* ST_ItemIterator::shop()
{
    return (m_shop);
}

const unsigned  ST_ItemIterator::price()
{
    return (m_price);
}

const ST_ItemIterator& ST_ItemIterator::operator ++ ()
{
    m_item = NULL;
    m_shop = NULL;

    const LevelStashes &ls = m_stash_level_it->second;

    if (m_stash_it == ls.m_stashes.end())
    {
        if (m_shop_it == ls.m_shops.end())
        {
            m_stash_level_it++;
            if (m_stash_level_it == StashTrack.levels.end())
                return (*this);

            new_level();
            return (*this);
        }
        m_shop = &(*m_shop_it);

        if (m_shop_item_it != m_shop->items.end())
        {
            const ShopInfo::shop_item &item = *m_shop_item_it++;
            m_item  = &(item.item);
            ASSERT(m_item->is_valid());
            m_price = item.price;
            return (*this);
        }

        m_shop_it++;
        if (m_shop_it != ls.m_shops.end())
            m_shop_item_it = m_shop_it->items.begin();

        ++(*this);
    }
    else
    {
        if (m_stash_item_it != m_stash_it->second.items.end())
        {
            m_item = &(*m_stash_item_it++);
            ASSERT(m_item->is_valid());
            return (*this);
        }

        m_stash_it++;
        if (m_stash_it == ls.m_stashes.end())
        {
            ++(*this);
            return (*this);
        }

        m_stash_item_it = m_stash_it->second.items.begin();
        ++(*this);
    }

    return (*this);
}

void ST_ItemIterator::new_level()
{
    m_item  = NULL;
    m_shop  = NULL;
    m_price = 0;

    if (m_stash_level_it == StashTrack.levels.end())
        return;

    const LevelStashes &ls = m_stash_level_it->second;

    m_place = ls.m_place;

    m_stash_it = ls.m_stashes.begin();
    if (m_stash_it != ls.m_stashes.end())
    {
        m_stash_item_it = m_stash_it->second.items.begin();
        if (m_stash_item_it != m_stash_it->second.items.end())
        {
            m_item = &(*m_stash_item_it++);
            ASSERT(m_item->is_valid());
        }
    }

    m_shop_it = ls.m_shops.begin();
    if (m_shop_it != ls.m_shops.end())
    {
        const ShopInfo &si = *m_shop_it;

        m_shop_item_it = si.items.begin();

        if (m_item == NULL && m_shop_item_it != si.items.end())
        {
            const ShopInfo::shop_item &item = *m_shop_item_it++;
            m_item  = &(item.item);
            ASSERT(m_item->is_valid());
            m_price = item.price;
            m_shop  = &si;
        }
    }
}

ST_ItemIterator ST_ItemIterator::operator ++ (int dummy)
{
    const ST_ItemIterator copy = *this;
    ++(*this);
    return (copy);
}