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

                          
                                             

                                                         


                   
 

               
                
                  
                  
                    

                     
                    
                
                  
                 
                   
                     

                     
                  
                 
                  

                    
                 
                    
                          
                     
                    
                   
                     
                    
                     

                     
                  

                    
                      
                   
                     




                     
                       
                         
                        

                           
 
                     

                       







                                   

                                    

                        




                                            

                         
 
                                        
 




                                          
 


                                                              
 


                                           
 


                                                           
 


                                                         
 


                                                                   
 


                                                             
 
                                         





                                                                        




                                                                        
















                  
                                     






                  
                                                


















                                            

                               




                               


                  















                          
                                 
 






                                                                  
 

                                    

                   

            
                                






                 






                                      
                                                                                  

               

                     


                 
                          

                        
                   






                                                                
                                                      
                                             
                  

                                             
                                            
                                            
 
                                        









                                                                                 
                   



               
                                                            
                        
                                                        
                                                              







                                                             






                
                                                                  

                                              
 


                                                                         
                                        
     

                                 

                                  

                                                                 
 





                                                                             
 




                                                                            

                                                                    
             




                                                           

                                                                             
                                                                         

                                 










                                                                              




                                                      
                                                                     
                            
                                                                     



                                            
                                                                    
                            
                                                                    





                                              
                                                                      
                                
                                                                      



                                              
                                                                      
                                
                                                                      



                         

                                                 
                                                      
                                        
                                                      
                                       
                                                      
                                       
                                                      












                                                                 
                                                              


                                                                              
                                                              


                                                                              
                                                              


                                                                              
                                                              

                                                                              


             

                                            
 
















                                                                             

 


                              


                                                                       
 

                                                                         








                                                                               

                    
                                             







                                                              




                                                                                                                                









                                                        

                   
 



                                                                       
                                                           




                                                                









                                                                               


                                              


                                                                  



                                      
                               
                                          
         
                                                  
                                           

                                                                     
         
                                       

                                          
                                              
             


                                                      


             
                     
         



                                                                   


                                        







                                                                              

                                                 
             


                                                          
                                    
                 
                                                     
                                                                  


                          

                                                            
                              

                                                                       
                                                 
             
      
         
                                               
         
                                                                               
             


                                                          
                                                       
             


                   
                      


     
                       
 
                                    
 


                                                 
 


                                                                    
 




                                   
 


                            





                                                                                     
 
                                                                 
 
 


































                                                                  
 
 
                                                    
 








                                                                     
                 

                                                           
                                              






                                                                             
                

                                                           
                                              






                                                                             
                   

                                                          
                                                   




                                                                            
             

                                                           
                                             




                                                                            
              

                                                          
                                              























                                                                          
             

                                                           
                                             






                                                                            
              

                                                         
                                              

                                                                           
                                           
                                                           


                                             


                                                          
                                       







                                                                            












                                                                                  
 
                           
                                            

                                                

 





                                                             



                                 
                                    

                                 



                                     


                                             
                                 
                            

 
                                                                         
 
                                 
     
                                 
                        
                        

                           
                                 



                         
                                 



                         
                                 

      
                              




                                                                           

                                      

     
                                                



                                                                  





                                                                                



                                                                     
                                                                   
                                                                           



                                                                                
                                            

                           
 
                                                          









                                               
                                                       


     





                                                                   
                                                                  
 
                  
 

                                           
                               
 


                                                                  
 
                                                      
                                                                           

 
                                                                                   
 
                                              
                                              
 
                                          
                                     


                                           
                                                    
                                                 



                                                                        
                                                          




                                                                              
                                                                
         
                                                               
                                                       
                                                                     
                                                             



                                                                    


                           
                                             

                               
                                                   
 
                         






                                           

                           
                                         

                           





                                                
     
                                           

                          
                                 
     
                                              

                          
                                     
     
                                                  




                              
                                                            

                          




                                                           

                                
                                                   
 
                                                                
                                        
 


                                                          
                                                                      
                                                  



                                                                      
                                             

                                                           
                                                              
                                                                 
                                                       



                                                           
                                                   








                                                                   
                                   
     




                                                          
                                                            
                                                 
                                                               
                                                   
                                                            
                                                 
                                                             
                                                   
                                                        
     

 
                                                                    




                                          
                                                               

 
                                  
 


                       
 

                        

                 

                                                     

                                                

                                                      
 
                                      
 
                                             
             
                                                         
                          
                                             
                    
                                                             
             
                                            
             
                                  
             
                                             
             
                                             

             
                                          
 


                      
                                                       
                                                                                
                                                                                   



                                                

                                                                          

     











                                                                   
                                  
     

 








                                                
                             

                            


                                         
                
     

                        

     




                      
                      






                                         




                                                                       


                                                           









                                                            
                                                         



                                        



                                            













                                                                  





                                                   

                         
                                               
                         
                                     




                                                                       
                                                           
                                                                      
         











                               


















                                                                      

























                                                                



                                                                       



                                                                       
                                         










                                               
                               






                                               
                               
 
                                               

 




                                                            



                                                 






                                                                 
                                                     
                                                          














                                  
                                                





                                      

                                                                            

     
                  


























































































                                                                         






                                                                  

                     
                       
 









                                                         





                                                                    







































                                                              






                                                               



                         
                       









                                                         





                                                               


























                                                              



                                                                     















                                                                       










                                                                 
                                                 




                                                







                                                                  


                                                                


                   

                                                  

                                         


                                    
                                                          


                                            
                                                       



                                                          


                                                            
                 




           




                                                         

                                        
                                                     
                                     


                            
                             
         


                                               
                                                
                                            
 


                                                


                          

                                                        




                                                          
                                                                  








                                                                      
                                                           








                                         

                                                   




                              
         





                                                   


                                         

                                                      
             




                                   
                                       
                                                                 



                                 
             
         





















                                                  

                                   
     
                                                
                                  

     


                                                                        









                                                                             
 
                                    















                                                                              
                                                  













                                                                       

                          



                                                              
                                                   

                                         
 


                              
                           







                                                                           
     

                                 
     
                       


                                                
     









                                                                 
                       
 
                                            
                       
                                            
                       
 



                                                               

                                            





                                                
                                                                 
                                                   
 

                                                                      


                                        
                                        
                                    
                                        








                                         
                                                                                  
                                                         
 

                                          
 

                                       

                                                    

                                                                           


                 
                                                   
         
                                                                     
                                        
                                         
                         
             
                                            


                                              


        
                                                                    
                                       
 




                                                                 
                                    
         




                                                       
         
 

     
                                                           
                                      
     














                                                    




                                            
                  

 











































                                                     

                                                   

                      
                           

                        
                      























                                                            
                       



                                                 
                       
                        
                       
                             


                                    

                      
                         
     
                                       


                                           
                                                                      


                                      








                                                         









                                                               
                       
     
                  

 









                                                                            
               



                              


                                                             
                  


                               
                   




                                    
                   
 


                              

                  
                  


                 






                                 

                                                                    
                                             


                                             
                                           
                       





                                   
                     




                             

                       
                         








                                           
                                     
                                                  

 
                                                           

                    
                                 
                                    
 
                   

 
                                                                
 
                                                





                                

                         
                   
      



                              




                        
 
                                                 

               


                                           

                      
                        
                      



                                                 

                                         





                                                                 


                                                                      



                                             
                              
                                                
         
                                                      






                                                                             
                                                         

                                                                

                                                 


                                                                    









                                                                             
         
                                               
                                              
                                                   

                                                           




                                                                           
     

 
                                                                               
 
                      
                                 
                              
 
                                                   

 
                                    
 

                       
                         
 
                                         



                                                                    
                                



                                
                                    






                                               
             


                                                
                                                                        
                                                     
                                                                  
             
                
                                                     


         
          
                                



                                
                                    





                                               
                                                    




                                                    

                                                 
                                                                      
                    
                                                               
 
                                                  
                                                    
             
                                                  
                                                            


                                                   
                                                              

                                              
                                                  

                          
             
                                                        
                                                    

                                                      
             


                                    
                                        




                                                    

                                 






                                    

                                                                        




                                  

                                                                       


                                   
                                                                  

             
                                                                    
                                                                

                                             
                                                                       

                                               
                                                






                                                  
                                          



                                                           
                                                                 

                                                           

                             
                       
      

     


                                   






                                                                  
                   

 
                                                                         
 
                                                                

                                         

                                










                                                                               

                                



                                                

                                                                              





                                                       
                                                               
                                                       

                                                             







                                                                             

                            
                                                                      
     






                              

















                                                            




                                                                
 
                                                                          
                                                    
 




                                                                            
                                                                              





                                         
                                         
                                























                                                     
             
                                                 

                                         
             
                
             
                                         

                                
         
                            
         
                                              
                            





















                                                                               



                                                                            








                                                   
                                  


                                                                  
                          










                                                                             
                  

 

                                                       





                                                          

                                          






                                                                          
 

                                      

 

                                                       
                              
                       
 

                                                                
                       





                                                                               
                                                                            
 
                                            
     
                                                





                                                               

                                                                


                      





                                                                
                             

                           









                                        
                          

























                                                             
                             

                           


                                              
                          




























                                                                            
                                   
                                             


                                                       
                                      
                                             

                                                       








                                                             



                                                             

                                                         



                                                                                

                                                     
















                                              
                                                           
                                                  
                                                             
                                                     
                                                                     






                                                          
                                   
                            
                                                           



                                                   

                                                        


                                          
                                                               





                                    
                                                           




                                            
                                                           

                                                 
                                                                      







                                                        
                                                           
                                                    
                                          












                                                            
                                                         













                                                                   
                                                      





                                                              
                                      


                                                

     
                  

 



                                                      




                            








                                                               


                                                       
                       


                                                                
                       
 


                                                            
                       
     
 
                                    
                                                                          



                                         






                                                  
                          

                       
                      
                                    





                                                               
                  

 
                                 
                
                  

                     

                      










                           
                                       






                                                                  






                                                                                  
                                                                        
                                                                        
                                                                       
                                                                       










                                                                           






                       
                              
 

                        
 
                                              
                                                  

                                                           
                                         
 


                                                                              
         
 



                                                 
                                         



                                                            
 

                                                                           
 



                                                   
 


                                     




                        
 


                       

 
                          



                                               

                         
                   
      

 
                                                  












                                                 















                                          










                                                           

               

                         
                   
      




                                                                        
                      

                   













                       
               


                                                   


                        













                                                            
                                                 












                                                     
                                   



                                             
                                            



                                                   
                                   
         
                 


                                                              

                                                 









                                                            
                       

                                              
                  



                         
                                             




                                   
                                                 





















                                      



                            

                  
 
                       


                                   
                                  



























                                                       




                      


                           






















                                                   
                                                  








                                   
                                               














                                       
                                 







                                        
                                                     
                
                 
























                                              
     

                                             

                      

                             
      

                    





                                  
                        



                       
                        







                                             



                      


                 





                                  
                         
 


                                      
                                                                          

                                           
 
                                            
                                            
 











                                                                       



                        
                                     



























                                                                             
                       

                                                
                  

 
                                                                               







                                                                         





                                                                             
                                                            
                 






                                                            
                       

                                             
                  

 




                                                

                            


                                         
                 

                                
 








                                                                   



                                                                          
                              




                          

                  
                   



                                                          
                                        














                                            


                             



                                                   



                                                                              





                              

 





                                                     



                                                                             

 

                                                         
                                                       







                          


                                                       

 




                                             
















                                                            
              






























































                                                                          


                                                                    

                                                                   


                                                   

                        

                                     
 
                   

                                 
                                                        
                                              
 









                                                                               










                                                       
                                                                            




                       
                                                                        
                                                      


                                 
                                                                     






                                                                                


            


                                                    
 
                             
 
                                              


                                                                      
 




                                                                            
                                                               
                                                              
                                                                       



                                                                                  
                 





                                           
 

                                                                        
                                                                        
                         

                                                                     



























                                                                             
                                              
 

                                                        
                                                                     
                                                             


                



                                                                 



                                                                    
                                     
         









                                                                 



                         


                                      


                        
                    

                       

                                     






                        

                                     




                                                       
                                   
 











                                                                       
 
                                                           

                                                






                                                  

                                                                       
                                
                                                             
                                  

                           





                            

                                                                  




                                   



                                                                               



                                                                        

                                                                            
                                                 
                                                                                
 
                                                                    





                                                        
                      



                                      

                                                       





                                                       

                         
                   
      

 





                                                 
                                                                      




                

                                        
































                                     


                                       



                    






























                                                                




                                                                           




                                                                  


















                                                           


                                          
                             
                                         
                           


                                            
                              
                                         
                           





                                           

















                                                           



                                                     
                    

                                                                      
                                                             




                                                   
                                                                                 

                                                     












                                                                            
                                                      
















































                                                                  
                                                            










                                                     


                                                                               



















                                                                                                



                                                                                                
                                                        
                                                         
                                                                           
                                                  

                                                                            


                                                                          

     









                                                           

                        
                             


                                             

                                                         

                                             






                                              
     





















                                                                       
                                                                                  













                                                                        
                                                  
                                          
     
               






                 
                 








                  




                                                



















                                                                                     




                                                                    


                                                                                                                                            
                                                                                                                                            
                                                                                                                                       

     









                                                   
                                    



                                                     
                              
















                                                                                    
                                













































                                                                          




                                                              
                           
                                               


































                                                                         
                                                                






                                                                          





                                               




















                                                     








                             
                                                
 
                                                   
                                                                    
                                                               
                       
 
                                                                
                       
 


                                                          
                                                                     
                                                                         
                                                                
 
                  

 

                                                        
                                                        
 
                                                                 

                                     
                                        


              
                                            
         
                                               






                                                               
 







                                                 
                                            


                                            
                                  




                                
                                              
                                                                  
                                                                   
                                                    
 
                                                                
 

                                         
 
                                                
 

                                           

                                      















                                                          
                                        
     
                                         
 
                              




                                                             

                                                                
                                                       
 
                                                       
                                                      

                                                                 
                       

                                                                            
                       

                                                 
                       

                                                       
 
                                          
                                                                
                                                                           

                                                               

                                                  
                                                                 
                                     



                                                               
 
                  

 

                                                                          
 
                        




                                                             
                                                                    






                                                           
                                                                    






                                                            
                                                                    





                                                            
                                                                    
     













                                                                            
 
                  








                                                                       
                                                                        
                                                                              
                                                                    

                   



                                    
                                     
                                       
 

      
/*
 *  File:       tilereg.cc
 *  Summary:    Region system implementations
 *
 *  Created by: ennewalker on Sat Jan 5 01:33:53 2008 UTC
 */

#include "AppHdr.h"

#ifdef USE_TILE

#include "cio.h"
#include "cloud.h"
#include "coord.h"
#include "coordit.h"
#include "debug.h"
#include "describe.h"
#include "directn.h"
#include "env.h"
#include "files.h"
#include "food.h"
#include "invent.h"
#include "item_use.h"
#include "itemname.h"
#include "itemprop.h"
#include "items.h"
#include "jobs.h"
#include "macro.h"
#include "message.h"
#include "misc.h"
#include "menu.h"
#include "newgame.h"
#include "map_knowledge.h"
#include "mon-util.h"
#include "options.h"
#include "player.h"
#include "religion.h"
#include "species.h"
#include "spl-book.h"
#include "spl-cast.h"
#include "spl-util.h"
#include "stash.h"
#include "stuff.h"
#include "terrain.h"
#include "transform.h"
#include "travel.h"
#include "viewgeom.h"

#include "tilereg.h"
#include "tiles.h"
#include "tilefont.h"
#include "tilesdl.h"
#include "tilemcache.h"
#include "tiledef-dngn.h"
#include "tiledef-gui.h"
#include "tiledef-main.h"
#include "tiledef-player.h"

#include <sys/stat.h>
#include <SDL_opengl.h>

/* These aren't defined on Win32 */
#ifndef S_IWUSR
#define S_IWUSR (unsigned int)-1
#endif
#ifndef S_IRUSR
#define S_IRUSR (unsigned int)-1
#endif

coord_def Region::NO_CURSOR(-1, -1);

int TextRegion::print_x;
int TextRegion::print_y;
TextRegion *TextRegion::text_mode = NULL;
int TextRegion::text_col = 0;

TextRegion *TextRegion::cursor_region= NULL;
int TextRegion::cursor_flag = 0;
int TextRegion::cursor_x;
int TextRegion::cursor_y;

const VColour map_colours[MAX_MAP_COL] =
{
    VColour(  0,   0,   0, 255), // BLACK
    VColour(128, 128, 128, 255), // DKGREY
    VColour(160, 160, 160, 255), // MDGREY
    VColour(192, 192, 192, 255), // LTGREY
    VColour(255, 255, 255, 255), // WHITE

    VColour(  0,  64, 255, 255), // BLUE  (actually cyan-blue)
    VColour(128, 128, 255, 255), // LTBLUE
    VColour(  0,  32, 128, 255), // DKBLUE (maybe too dark)

    VColour(  0, 255,   0, 255), // GREEN
    VColour(128, 255, 128, 255), // LTGREEN
    VColour(  0, 128,   0, 255), // DKGREEN

    VColour(  0, 255, 255, 255), // CYAN
    VColour( 64, 255, 255, 255), // LTCYAN (maybe too pale)
    VColour(  0, 128, 128, 255), // DKCYAN

    VColour(255,   0,   0, 255), // RED
    VColour(255, 128, 128, 255), // LTRED (actually pink)
    VColour(128,   0,   0, 255), // DKRED

    VColour(192,   0, 255, 255), // MAGENTA (actually blue-magenta)
    VColour(255, 128, 255, 255), // LTMAGENTA
    VColour( 96,   0, 128, 255), // DKMAGENTA

    VColour(255, 255,   0, 255), // YELLOW
    VColour(255, 255,  64, 255), // LTYELLOW (maybe too pale)
    VColour(128, 128,   0, 255), // DKYELLOW

    VColour(165,  91,   0, 255), // BROWN
};

const int dir_dx[9] = {-1, 0, 1, -1, 0, 1, -1, 0, 1};
const int dir_dy[9] = {1, 1, 1, 0, 0, 0, -1, -1, -1};

const int cmd_normal[9] = {'b', 'j', 'n', 'h', '.', 'l', 'y', 'k', 'u'};
const int cmd_shift[9]  = {'B', 'J', 'N', 'H', '5', 'L', 'Y', 'K', 'U'};
const int cmd_ctrl[9]   = {CONTROL('B'), CONTROL('J'), CONTROL('N'),
                           CONTROL('H'), 'X', CONTROL('L'),
                           CONTROL('Y'), CONTROL('K'), CONTROL('U')};
const int cmd_dir[9]    = {'1', '2', '3', '4', '5', '6', '7', '8', '9'};

Region::Region() :
    ox(0),
    oy(0),
    dx(0),
    dy(0),
    mx(0),
    my(0),
    wx(0),
    wy(0),
    sx(0),
    sy(0),
    ex(0),
    ey(0)
{
}

void Region::resize(int _mx, int _my)
{
    mx = _mx;
    my = _my;

    recalculate();
}

void Region::place(int _sx, int _sy, int margin)
{
    sx = _sx;
    sy = _sy;
    ox = margin;
    oy = margin;

    recalculate();
}

void Region::resize_to_fit(int _wx, int _wy)
{
    if (_wx < 0 || _wy < 0)
    {
        mx = wx = my = wy = 0;
        ey = sy;
        ex = sy;
        return;
    }

    int inner_x = _wx - 2 * ox;
    int inner_y = _wy - 2 * oy;

    mx = dx ? inner_x / dx : 0;
    my = dy ? inner_y / dy : 0;

    recalculate();

    ex = sx + _wx;
    ey = sy + _wy;
}

void Region::recalculate()
{
    wx = ox * 2 + mx * dx;
    wy = oy * 2 + my * dy;
    ex = sx + wx;
    ey = sy + wy;

    on_resize();
}

Region::~Region()
{
}

bool Region::inside(int x, int y)
{
    return (x >= sx && y >= sy && x <= ex && y <= ey);
}

bool Region::mouse_pos(int mouse_x, int mouse_y, int &cx, int &cy)
{
    int x = mouse_x - ox - sx;
    int y = mouse_y - oy - sy;

    bool valid = (x >= 0 && y >= 0);

    ASSERT(dx > 0);
    ASSERT(dy > 0);
    x /= dx;
    y /= dy;
    valid &= (x < mx && y < my);

    cx = x;
    cy = y;

    return valid;
}

void Region::set_transform()
{
    glLoadIdentity();
    glTranslatef(sx + ox, sy + oy, 0);
    glScalef(dx, dy, 1);
}

TileRegion::TileRegion(ImageManager* im, FTFont *tag_font, int tile_x, int tile_y)
{
    ASSERT(im);
    ASSERT(tag_font);

    m_image = im;
    dx = tile_x;
    dy = tile_y;
    m_tag_font = tag_font;

    // To quite Valgrind
    m_dirty = true;
}

TileRegion::~TileRegion()
{
}

DungeonRegion::DungeonRegion(ImageManager* im, FTFont *tag_font,
                             int tile_x, int tile_y) :
    TileRegion(im, tag_font, tile_x, tile_y),
    m_cx_to_gx(0),
    m_cy_to_gy(0),
    m_buf_dngn(&im->m_textures[TEX_DUNGEON]),
    m_buf_doll(&im->m_textures[TEX_PLAYER]),
    m_buf_main(&im->m_textures[TEX_DEFAULT])
{
    for (int i = 0; i < CURSOR_MAX; i++)
        m_cursor[i] = NO_CURSOR;
}

DungeonRegion::~DungeonRegion()
{
}

void DungeonRegion::load_dungeon(unsigned int* tileb, int cx_to_gx, int cy_to_gy)
{
    m_tileb.clear();
    m_dirty = true;

    if (!tileb)
        return;

    int len = 2 * crawl_view.viewsz.x * crawl_view.viewsz.y;
    m_tileb.resize(len);
    // TODO enne - move this function into dungeonregion
    tile_finish_dngn(tileb, cx_to_gx + mx/2, cy_to_gy + my/2);
    memcpy(&m_tileb[0], tileb, sizeof(unsigned int) * len);

    m_cx_to_gx = cx_to_gx;
    m_cy_to_gy = cy_to_gy;

    place_cursor(CURSOR_TUTORIAL, m_cursor[CURSOR_TUTORIAL]);
}

enum wave_type
{
    WV_NONE = 0,
    WV_SHALLOW,
    WV_DEEP
};

void DungeonRegion::pack_background(unsigned int bg, int x, int y)
{
    unsigned int bg_idx = bg & TILE_FLAG_MASK;

    if (bg_idx >= TILE_DNGN_WAX_WALL)
    {
        tile_flavour &flv = env.tile_flv[x + m_cx_to_gx][y + m_cy_to_gy];
        m_buf_dngn.add(flv.floor, x, y);
    }
    m_buf_dngn.add(bg_idx, x, y);

    if (bg_idx > TILE_DNGN_UNSEEN)
    {
        if (bg & TILE_FLAG_WAS_SECRET)
            m_buf_dngn.add(TILE_DNGN_DETECTED_SECRET_DOOR, x, y);

        if (bg & TILE_FLAG_BLOOD)
        {
            tile_flavour &flv = env.tile_flv[x + m_cx_to_gx][y + m_cy_to_gy];
            int offset = flv.special % tile_dngn_count(TILE_BLOOD);
            m_buf_dngn.add(TILE_BLOOD + offset, x, y);
        }

        if (player_in_branch(BRANCH_SHOALS))
        {
            // Add wave tiles on floor adjacent to shallow water.
            const coord_def pos = coord_def(x + m_cx_to_gx, y + m_cy_to_gy);
            const dungeon_feature_type feat = env.map_knowledge(pos).feat();
            if (feat == DNGN_FLOOR || feat == DNGN_UNDISCOVERED_TRAP
                || feat == DNGN_SHALLOW_WATER)
            {
                wave_type north = WV_NONE, south = WV_NONE,
                          east = WV_NONE, west = WV_NONE,
                          ne = WV_NONE, nw = WV_NONE,
                          se = WV_NONE, sw = WV_NONE;

                for (radius_iterator ri(pos, 1, true, false, true); ri; ++ri)
                {
                    if (!is_terrain_seen(*ri) && !is_terrain_mapped(*ri))
                        continue;

                    bool shallow = false;
                    if (env.map_knowledge(*ri).feat() == DNGN_SHALLOW_WATER)
                    {
                        // Adjacent shallow water is only interesting for
                        // floor cells.
                        if (feat == DNGN_SHALLOW_WATER)
                            continue;

                        shallow = true;
                    }
                    else if (env.map_knowledge(*ri).feat() != DNGN_DEEP_WATER)
                        continue;

                    if (ri->x == pos.x) // orthogonals
                    {
                        if (ri->y < pos.y)
                            north = (shallow ? WV_SHALLOW : WV_DEEP);
                        else
                            south = (shallow ? WV_SHALLOW : WV_DEEP);
                    }
                    else if (ri->y == pos.y)
                    {
                        if (ri->x < pos.x)
                            west = (shallow ? WV_SHALLOW : WV_DEEP);
                        else
                            east = (shallow ? WV_SHALLOW : WV_DEEP);
                    }
                    else // diagonals
                    {
                        if (ri->x < pos.x)
                        {
                            if (ri->y < pos.y)
                                nw = (shallow ? WV_SHALLOW : WV_DEEP);
                            else
                                sw = (shallow ? WV_SHALLOW : WV_DEEP);
                        }
                        else
                        {
                            if (ri->y < pos.y)
                                ne = (shallow ? WV_SHALLOW : WV_DEEP);
                            else
                                se = (shallow ? WV_SHALLOW : WV_DEEP);
                        }
                    }
                }

                // First check for shallow water.
                if (north == WV_SHALLOW)
                    m_buf_dngn.add(TILE_WAVE_N, x, y);
                if (south == WV_SHALLOW)
                    m_buf_dngn.add(TILE_WAVE_S, x, y);
                if (east == WV_SHALLOW)
                    m_buf_dngn.add(TILE_WAVE_E, x, y);
                if (west == WV_SHALLOW)
                    m_buf_dngn.add(TILE_WAVE_W, x, y);

                // Then check for deep water, overwriting shallow
                // corner waves, if necessary.
                if (north == WV_DEEP)
                    m_buf_dngn.add(TILE_WAVE_DEEP_N, x, y);
                if (south == WV_DEEP)
                    m_buf_dngn.add(TILE_WAVE_DEEP_S, x, y);
                if (east == WV_DEEP)
                    m_buf_dngn.add(TILE_WAVE_DEEP_E, x, y);
                if (west == WV_DEEP)
                    m_buf_dngn.add(TILE_WAVE_DEEP_W, x, y);

                if (ne == WV_SHALLOW && !north && !east)
                    m_buf_dngn.add(TILE_WAVE_CORNER_NE, x, y);
                else if (ne == WV_DEEP && north != WV_DEEP && east != WV_DEEP)
                    m_buf_dngn.add(TILE_WAVE_DEEP_CORNER_NE, x, y);
                if (nw == WV_SHALLOW && !north && !west)
                    m_buf_dngn.add(TILE_WAVE_CORNER_NW, x, y);
                else if (nw == WV_DEEP && north != WV_DEEP && west != WV_DEEP)
                    m_buf_dngn.add(TILE_WAVE_DEEP_CORNER_NW, x, y);
                if (se == WV_SHALLOW && !south && !east)
                    m_buf_dngn.add(TILE_WAVE_CORNER_SE, x, y);
                else if (se == WV_DEEP && south != WV_DEEP && east != WV_DEEP)
                    m_buf_dngn.add(TILE_WAVE_DEEP_CORNER_SE, x, y);
                if (sw == WV_SHALLOW && !south && !west)
                    m_buf_dngn.add(TILE_WAVE_CORNER_SW, x, y);
                else if (sw == WV_DEEP && south != WV_DEEP && west != WV_DEEP)
                    m_buf_dngn.add(TILE_WAVE_DEEP_CORNER_SW, x, y);
            }
        }

        if (bg & TILE_FLAG_HALO)
            m_buf_dngn.add(TILE_HALO, x, y);

        if (!(bg & TILE_FLAG_UNSEEN))
        {
            if (bg & TILE_FLAG_SANCTUARY)
                m_buf_dngn.add(TILE_SANCTUARY, x, y);

            // Apply the travel exclusion under the foreground if the cell is
            // visible.  It will be applied later if the cell is unseen.
            if (bg & TILE_FLAG_EXCL_CTR)
                m_buf_dngn.add(TILE_TRAVEL_EXCLUSION_CENTRE_BG, x, y);
            else if (bg & TILE_FLAG_TRAV_EXCL)
                m_buf_dngn.add(TILE_TRAVEL_EXCLUSION_BG, x, y);
        }
        if (bg & TILE_FLAG_RAY)
            m_buf_dngn.add(TILE_RAY, x, y);
        else if (bg & TILE_FLAG_RAY_OOR)
            m_buf_dngn.add(TILE_RAY_OUT_OF_RANGE, x, y);
    }
}

static dolls_data player_doll;
static int gender = -1;

// Saves player doll definitions into dolls.txt.
// Returns true if successful, else false.
static bool _save_doll_data(int mode, int num, const dolls_data* dolls)
{
    // Save mode, num, and all dolls into dolls.txt.
    std::string dollsTxtString = datafile_path("dolls.txt", false, true);

    struct stat stFileInfo;
    stat(dollsTxtString.c_str(), &stFileInfo);

    // Write into the current directory instead if we didn't find the file
    // or don't have write permissions.
    const char *dollsTxt = (dollsTxtString.c_str()[0] == 0
                              || !(stFileInfo.st_mode & S_IWUSR)) ? "dolls.txt"
                            : dollsTxtString.c_str();

    FILE *fp = NULL;
    if ((fp = fopen(dollsTxt, "w+")) != NULL)
    {
        fprintf(fp, "MODE=%s\n",
                    (mode == TILEP_MODE_EQUIP)   ? "EQUIP" :
                    (mode == TILEP_MODE_LOADING) ? "LOADING"
                                                 : "DEFAULT");

        fprintf(fp, "NUM=%02d\n", num == -1 ? 0 : num);

        // Print some explanatory comments. May contain no spaces!
        fprintf(fp, "#Legend:\n");
        fprintf(fp, "#***:equipment/123:index/000:none\n");
        fprintf(fp, "#Shadow/Base/Cloak/Boots/Legs/Body/Gloves/Weapon/Shield/Hair/Beard/Helmet/Halo/Enchant/DrcHead/DrcWing\n");
        fprintf(fp, "#Sh:Bse:Clk:Bts:Leg:Bdy:Glv:Wpn:Shd:Hai:Brd:Hlm:Hal:Enc:Drc:Wng\n");
        char fbuf[80];
        for (unsigned int i = 0; i < NUM_MAX_DOLLS; ++i)
        {
            tilep_print_parts(fbuf, dolls[i]);
            fprintf(fp, "%s\n", fbuf);
        }
        fclose(fp);

        return (true);
    }

    return (false);
}

// Loads player doll definitions from (by default) dolls.txt.
// Returns true if file found, else false.
static bool _load_doll_data(const char *fn, dolls_data *dolls, int max,
                            tile_doll_mode *mode, int *cur)
{
    char fbuf[1024];
    FILE *fp  = NULL;

    std::string dollsTxtString = datafile_path(fn, false, true);

    struct stat stFileInfo;
    stat(dollsTxtString.c_str(), &stFileInfo);

    // Try to read from the current directory instead if we didn't find the
    // file or don't have reading permissions.
    const char *dollsTxt = (dollsTxtString.c_str()[0] == 0
                              || !(stFileInfo.st_mode & S_IRUSR)) ? "dolls.txt"
                            : dollsTxtString.c_str();


    if ( (fp = fopen(dollsTxt, "r")) == NULL )
    {
        // File doesn't exist. By default, use equipment settings.
        *mode = TILEP_MODE_EQUIP;
        return (false);
    }
    else
    {
        memset(fbuf, 0, sizeof(fbuf));
        // Read mode from file.
        if (fscanf(fp, "%s", fbuf) != EOF)
        {
            if (strcmp(fbuf, "MODE=DEFAULT") == 0)
                *mode = TILEP_MODE_DEFAULT;
            else if (strcmp(fbuf, "MODE=EQUIP") == 0)
                *mode = TILEP_MODE_EQUIP; // Nothing else to be done.
        }
        // Read current doll from file.
        if (fscanf(fp, "%s", fbuf) != EOF)
        {
            if (strncmp(fbuf, "NUM=", 4) == 0)
            {
                sscanf(fbuf, "NUM=%d", cur);
                if (*cur < 0 || *cur >= NUM_MAX_DOLLS)
                    *cur = 0;
            }
        }

        if (max == 1)
        {
            // Load only one doll, either the one defined by NUM or
            // use the default/equipment setting.
            if (*mode != TILEP_MODE_LOADING)
            {
                if (gender == -1)
                    gender = coinflip();

                if (*mode == TILEP_MODE_DEFAULT)
                    tilep_job_default(you.char_class, gender, dolls[0].parts);

                // If we don't need to load a doll, return now.
                fclose(fp);
                return (true);
            }

            int count = 0;
            while (fscanf(fp, "%s", fbuf) != EOF)
            {
                if (fbuf[0] == '#') // Skip comment lines.
                    continue;

                if (*cur == count++)
                {
                    tilep_scan_parts(fbuf, dolls[0]);
                    gender = get_gender_from_tile(dolls[0].parts);
                    break;
                }
            }
#if 0
            // Probably segfaults within the tile edit menu.
            if (*cur >= count)
            {
                mprf(MSGCH_WARN, "Doll %d could not be found in '%s'.",
                                 *cur, dollsTxt);
            }
#endif
        }
        else // Load up to max dolls from file.
        {
            for (int count = 0; count < max && fscanf(fp, "%s", fbuf) != EOF; )
            {
                if (fbuf[0] == '#') // Skip comment lines.
                    continue;

                tilep_scan_parts(fbuf, dolls[count++]);
            }
        }

        fclose(fp);
        return (true);
    }
}

void init_player_doll()
{
    dolls_data dolls[NUM_MAX_DOLLS];

    for (int i = 0; i < NUM_MAX_DOLLS; i++)
        for (int j = 0; j < TILEP_PART_MAX; j++)
            dolls[i].parts[j] = TILEP_SHOW_EQUIP;

    tile_doll_mode mode = TILEP_MODE_LOADING;
    int cur = 0;
    _load_doll_data("dolls.txt", dolls, NUM_MAX_DOLLS, &mode, &cur);

    if (mode == TILEP_MODE_LOADING)
    {
        player_doll = dolls[cur];
        return;
    }

    if (gender == -1)
        gender = coinflip();

    for (int i = 0; i < TILEP_PART_MAX; i++)
        player_doll.parts[i] = TILEP_SHOW_EQUIP;
    tilep_race_default(you.species, gender, you.experience_level, player_doll.parts);

    if (mode == TILEP_MODE_EQUIP)
        return;

    tilep_job_default(you.char_class, gender, player_doll.parts);
}

static int _get_random_doll_part(int p)
{
    ASSERT(p >= 0 && p <= TILEP_PART_MAX);
    return (tile_player_part_start[p]
            + random2(tile_player_part_count[p]));
}

static void _fill_doll_part(dolls_data &doll, int p)
{
    ASSERT(p >= 0 && p <= TILEP_PART_MAX);
    doll.parts[p] = _get_random_doll_part(p);
}

static void _create_random_doll(dolls_data &rdoll)
{
    // All dolls roll for these.
    _fill_doll_part(rdoll, TILEP_PART_BODY);
    _fill_doll_part(rdoll, TILEP_PART_HAND1);
    _fill_doll_part(rdoll, TILEP_PART_LEG);
    _fill_doll_part(rdoll, TILEP_PART_BOOTS);
    _fill_doll_part(rdoll, TILEP_PART_HAIR);

    // The following only are rolled with 50% chance.
    if (coinflip())
        _fill_doll_part(rdoll, TILEP_PART_CLOAK);
    if (coinflip())
        _fill_doll_part(rdoll, TILEP_PART_ARM);
    if (coinflip())
        _fill_doll_part(rdoll, TILEP_PART_HAND2);
    if (coinflip())
        _fill_doll_part(rdoll, TILEP_PART_HELM);

    // Only male dolls get a chance at a beard.
    if (rdoll.parts[TILEP_PART_BASE] % 2 == 1 && one_chance_in(4))
        _fill_doll_part(rdoll, TILEP_PART_BEARD);
}

static void _fill_doll_equipment(dolls_data &result)
{
    // Base tile.
    if (result.parts[TILEP_PART_BASE] == TILEP_SHOW_EQUIP)
    {
        if (gender == -1)
            gender = get_gender_from_tile(player_doll.parts);
        tilep_race_default(you.species, gender, you.experience_level,
                           result.parts);
    }

    // Main hand.
    if (result.parts[TILEP_PART_HAND1] == TILEP_SHOW_EQUIP)
    {
        const int item = you.equip[EQ_WEAPON];
        if (you.attribute[ATTR_TRANSFORMATION] == TRAN_BLADE_HANDS)
            result.parts[TILEP_PART_HAND1] = TILEP_HAND1_BLADEHAND;
        else if (item == -1)
            result.parts[TILEP_PART_HAND1] = 0;
        else
            result.parts[TILEP_PART_HAND1] = tilep_equ_weapon(you.inv[item]);
    }
    // Off hand.
    if (result.parts[TILEP_PART_HAND2] == TILEP_SHOW_EQUIP)
    {
        const int item = you.equip[EQ_SHIELD];
        if (you.attribute[ATTR_TRANSFORMATION] == TRAN_BLADE_HANDS)
            result.parts[TILEP_PART_HAND2] = TILEP_HAND2_BLADEHAND;
        else if (item == -1)
            result.parts[TILEP_PART_HAND2] = 0;
        else
            result.parts[TILEP_PART_HAND2] = tilep_equ_shield(you.inv[item]);
    }
    // Body armour.
    if (result.parts[TILEP_PART_BODY] == TILEP_SHOW_EQUIP)
    {
        const int item = you.equip[EQ_BODY_ARMOUR];
        if (item == -1)
            result.parts[TILEP_PART_BODY] = 0;
        else
            result.parts[TILEP_PART_BODY] = tilep_equ_armour(you.inv[item]);
    }
    // Cloak.
    if (result.parts[TILEP_PART_CLOAK] == TILEP_SHOW_EQUIP)
    {
        const int item = you.equip[EQ_CLOAK];
        if (item == -1)
            result.parts[TILEP_PART_CLOAK] = 0;
        else
            result.parts[TILEP_PART_CLOAK] = tilep_equ_cloak(you.inv[item]);
    }
    // Helmet.
    if (result.parts[TILEP_PART_HELM] == TILEP_SHOW_EQUIP)
    {
        const int item = you.equip[EQ_HELMET];
        if (item != -1)
        {
            result.parts[TILEP_PART_HELM] = tilep_equ_helm(you.inv[item]);
        }
        else if (player_mutation_level(MUT_HORNS) > 0)
        {
            switch (player_mutation_level(MUT_HORNS))
            {
                case 1:
                    result.parts[TILEP_PART_HELM] = TILEP_HELM_HORNS1;
                    break;
                case 2:
                    result.parts[TILEP_PART_HELM] = TILEP_HELM_HORNS2;
                    break;
                case 3:
                    result.parts[TILEP_PART_HELM] = TILEP_HELM_HORNS3;
                    break;
            }
        }
        else
        {
            result.parts[TILEP_PART_HELM] = 0;
        }
    }
    // Boots.
    if (result.parts[TILEP_PART_BOOTS] == TILEP_SHOW_EQUIP)
    {
        const int item = you.equip[EQ_BOOTS];
        if (item != -1)
            result.parts[TILEP_PART_BOOTS] = tilep_equ_boots(you.inv[item]);
        else if (player_mutation_level(MUT_HOOVES))
            result.parts[TILEP_PART_BOOTS] = TILEP_BOOTS_HOOVES;
        else
            result.parts[TILEP_PART_BOOTS] = 0;
    }
    // Gloves.
    if (result.parts[TILEP_PART_ARM] == TILEP_SHOW_EQUIP)
    {
        const int item = you.equip[EQ_GLOVES];
        if (item != -1)
            result.parts[TILEP_PART_ARM] = tilep_equ_gloves(you.inv[item]);
        else if (you.has_claws(false) >= 3)
            result.parts[TILEP_PART_ARM] = TILEP_ARM_CLAWS;
        else
            result.parts[TILEP_PART_ARM] = 0;
    }
    // Halo.
    if (result.parts[TILEP_PART_HALO] == TILEP_SHOW_EQUIP)
    {
        const bool halo = you.haloed();
        result.parts[TILEP_PART_HALO] = halo ? TILEP_HALO_TSO : 0;
    }
    // Enchantments.
    if (result.parts[TILEP_PART_ENCH] == TILEP_SHOW_EQUIP)
    {
        result.parts[TILEP_PART_ENCH] =
            (you.duration[DUR_LIQUID_FLAMES] ? TILEP_ENCH_STICKY_FLAME : 0);
    }
    // Draconian head/wings
    if (player_genus(GENPC_DRACONIAN))
    {
        int base = 0;
        int head = 0;
        int wing = 0;
        tilep_draconian_init(you.species, you.experience_level, base, head, wing);

        if (result.parts[TILEP_PART_DRCHEAD] == TILEP_SHOW_EQUIP)
            result.parts[TILEP_PART_DRCHEAD] = head;
        if (result.parts[TILEP_PART_DRCWING] == TILEP_SHOW_EQUIP)
            result.parts[TILEP_PART_DRCWING] = wing;
    }

    // Various other slots.
    for (int i = 0; i < TILEP_PART_MAX; i++)
        if (result.parts[i] == TILEP_SHOW_EQUIP)
            result.parts[i] = 0;
}

// Writes equipment information into per-character doll file.
void save_doll_file(FILE *dollf)
{
    ASSERT(dollf);

    dolls_data result = player_doll;
    _fill_doll_equipment(result);

    // Write into file.
    char fbuf[80];
    tilep_print_parts(fbuf, result);
    fprintf(dollf, "%s\n", fbuf);

    if (you.attribute[ATTR_HELD] > 0)
        fprintf(dollf, "net\n");
}

void DungeonRegion::pack_player(int x, int y)
{
    dolls_data result = player_doll;
    _fill_doll_equipment(result);
    pack_doll(result, x, y);
}

void pack_doll_buf(TileBuffer& buf, const dolls_data &doll, int x, int y)
{
    int p_order[TILEP_PART_MAX] =
    {
        TILEP_PART_SHADOW,  //  0
        TILEP_PART_HALO,
        TILEP_PART_ENCH,
        TILEP_PART_DRCWING,
        TILEP_PART_CLOAK,
        TILEP_PART_BASE,    //  5
        TILEP_PART_BOOTS,
        TILEP_PART_LEG,
        TILEP_PART_BODY,
        TILEP_PART_ARM,
        TILEP_PART_HAND1,   // 10
        TILEP_PART_HAND2,
        TILEP_PART_HAIR,
        TILEP_PART_BEARD,
        TILEP_PART_HELM,
        TILEP_PART_DRCHEAD  // 15
    };

    int flags[TILEP_PART_MAX];
    tilep_calc_flags(doll.parts, flags);

    // For skirts, boots go under the leg armour.  For pants, they go over.
    if (doll.parts[TILEP_PART_LEG] < TILEP_LEG_SKIRT_OFS)
    {
        p_order[6] = TILEP_PART_BOOTS;
        p_order[7] = TILEP_PART_LEG;
    }

    // Special case bardings from being cut off.
    bool is_naga = (doll.parts[TILEP_PART_BASE] >= TILEP_BASE_NAGA
                    && doll.parts[TILEP_PART_BASE]
                       < tilep_species_to_base_tile(SP_NAGA + 1));

    if (doll.parts[TILEP_PART_BOOTS] >= TILEP_BOOTS_NAGA_BARDING
        && doll.parts[TILEP_PART_BOOTS] <= TILEP_BOOTS_NAGA_BARDING_RED)
    {
        flags[TILEP_PART_BOOTS] = is_naga ? TILEP_FLAG_NORMAL : TILEP_FLAG_HIDE;
    }

    bool is_cent = (doll.parts[TILEP_PART_BASE] >= TILEP_BASE_CENTAUR
                    && doll.parts[TILEP_PART_BASE]
                       < tilep_species_to_base_tile(SP_CENTAUR + 1));

    if (doll.parts[TILEP_PART_BOOTS] >= TILEP_BOOTS_CENTAUR_BARDING
        && doll.parts[TILEP_PART_BOOTS] <= TILEP_BOOTS_CENTAUR_BARDING_RED)
    {
        flags[TILEP_PART_BOOTS] = is_cent ? TILEP_FLAG_NORMAL : TILEP_FLAG_HIDE;
    }

    for (int i = 0; i < TILEP_PART_MAX; i++)
    {
        int p = p_order[i];

        if (!doll.parts[p] || flags[p] == TILEP_FLAG_HIDE)
            continue;

        int ymax = TILE_Y;

        if (flags[p] == TILEP_FLAG_CUT_CENTAUR
            || flags[p] == TILEP_FLAG_CUT_NAGA)
        {
            ymax = 18;
        }

        buf.add(doll.parts[p], x, y, 0, 0, true, ymax);
    }
}

void DungeonRegion::pack_doll(const dolls_data &doll, int x, int y)
{
    pack_doll_buf(m_buf_doll, doll, x, y);
}


void DungeonRegion::pack_mcache(mcache_entry *entry, int x, int y)
{
    ASSERT(entry);

    const dolls_data *doll = entry->doll();
    if (doll)
        pack_doll(*doll, x, y);

    tile_draw_info dinfo[3];
    unsigned int draw_info_count = entry->info(&dinfo[0]);
    ASSERT(draw_info_count <= sizeof(dinfo) / (sizeof(dinfo[0])));

    for (unsigned int i = 0; i < draw_info_count; i++)
        m_buf_doll.add(dinfo[i].idx, x, y, dinfo[i].ofs_x, dinfo[i].ofs_y);
}

void DungeonRegion::pack_foreground(unsigned int bg, unsigned int fg, int x, int y)
{
    unsigned int fg_idx = fg & TILE_FLAG_MASK;
    unsigned int bg_idx = bg & TILE_FLAG_MASK;

    if (fg_idx && fg_idx <= TILE_MAIN_MAX)
        m_buf_main.add(fg_idx, x, y);

    if (fg_idx && !(fg & TILE_FLAG_FLYING))
    {
        if (tile_dngn_equal(TILE_DNGN_LAVA, bg_idx))
            m_buf_main.add(TILE_MASK_LAVA, x, y);
        else if (tile_dngn_equal(TILE_DNGN_SHALLOW_WATER, bg_idx)
                 || tile_dngn_equal(TILE_DNGN_SHALLOW_WATER_DISTURBANCE,
                                    bg_idx))
        {
            m_buf_main.add(TILE_MASK_SHALLOW_WATER, x, y);
        }
        else if (tile_dngn_equal(TILE_DNGN_SHALLOW_WATER_MURKY, bg_idx)
                 || tile_dngn_equal(TILE_DNGN_SHALLOW_WATER_MURKY_DISTURBANCE,
                                    bg_idx))
        {
            m_buf_main.add(TILE_MASK_SHALLOW_WATER_MURKY, x, y);
        }
        else if (tile_dngn_equal(TILE_DNGN_DEEP_WATER, bg_idx))
            m_buf_main.add(TILE_MASK_DEEP_WATER, x, y);
        else if (tile_dngn_equal(TILE_DNGN_DEEP_WATER_MURKY, bg_idx))
            m_buf_main.add(TILE_MASK_DEEP_WATER_MURKY, x, y);
        else if (tile_dngn_equal(TILE_SHOALS_DEEP_WATER, bg_idx))
            m_buf_main.add(TILE_MASK_DEEP_WATER_SHOALS, x, y);
        else if (tile_dngn_equal(TILE_SHOALS_SHALLOW_WATER, bg_idx))
            m_buf_main.add(TILE_MASK_SHALLOW_WATER_SHOALS, x, y);
    }

    if (fg & TILE_FLAG_NET)
        m_buf_doll.add(TILEP_TRAP_NET, x, y);

    if (fg & TILE_FLAG_S_UNDER)
        m_buf_main.add(TILE_SOMETHING_UNDER, x, y);

    int status_shift = 0;
    if (fg & TILE_FLAG_BERSERK)
    {
        m_buf_main.add(TILE_BERSERK, x, y);
        status_shift += 10;
    }

    // Pet mark
    if (fg & TILE_FLAG_PET)
    {
        m_buf_main.add(TILE_HEART, x, y);
        status_shift += 10;
    }
    else if (fg & TILE_FLAG_GD_NEUTRAL)
    {
        m_buf_main.add(TILE_GOOD_NEUTRAL, x, y);
        status_shift += 8;
    }
    else if (fg & TILE_FLAG_NEUTRAL)
    {
        m_buf_main.add(TILE_NEUTRAL, x, y);
        status_shift += 8;
    }
    else if (fg & TILE_FLAG_STAB)
    {
        m_buf_main.add(TILE_STAB_BRAND, x, y);
        status_shift += 8;
    }
    else if (fg & TILE_FLAG_MAY_STAB)
    {
        m_buf_main.add(TILE_MAY_STAB_BRAND, x, y);
        status_shift += 5;
    }

    if (fg & TILE_FLAG_POISON)
    {
        m_buf_main.add(TILE_POISON, x, y, -status_shift, 0);
        status_shift += 5;
    }
    if (fg & TILE_FLAG_FLAME)
    {
        m_buf_main.add(TILE_FLAME, x, y, -status_shift, 0);
        status_shift += 5;
    }

    if (fg & TILE_FLAG_ANIM_WEP)
        m_buf_main.add(TILE_ANIMATED_WEAPON, x, y);

    if (bg & TILE_FLAG_UNSEEN && (bg != TILE_FLAG_UNSEEN || fg))
        m_buf_main.add(TILE_MESH, x, y);

    if (bg & TILE_FLAG_OOR && (bg != TILE_FLAG_OOR || fg))
        m_buf_main.add(TILE_OOR_MESH, x, y);

    if (bg & TILE_FLAG_MM_UNSEEN && (bg != TILE_FLAG_MM_UNSEEN || fg))
        m_buf_main.add(TILE_MAGIC_MAP_MESH, x, y);

    // Don't let the "new stair" icon cover up any existing icons, but
    // draw it otherwise.
    if (bg & TILE_FLAG_NEW_STAIR && status_shift == 0)
        m_buf_main.add(TILE_NEW_STAIR, x, y);

    if (bg & TILE_FLAG_EXCL_CTR && (bg & TILE_FLAG_UNSEEN))
        m_buf_main.add(TILE_TRAVEL_EXCLUSION_CENTRE_FG, x, y);
    else if (bg & TILE_FLAG_TRAV_EXCL && (bg & TILE_FLAG_UNSEEN))
        m_buf_main.add(TILE_TRAVEL_EXCLUSION_FG, x, y);

    // Tutorial cursor takes precedence over other cursors.
    if (bg & TILE_FLAG_TUT_CURSOR)
    {
        m_buf_main.add(TILE_TUTORIAL_CURSOR, x, y);
    }
    else if (bg & TILE_FLAG_CURSOR)
    {
        int type = ((bg & TILE_FLAG_CURSOR) == TILE_FLAG_CURSOR1) ?
            TILE_CURSOR : TILE_CURSOR2;

        if ((bg & TILE_FLAG_CURSOR) == TILE_FLAG_CURSOR3)
           type = TILE_CURSOR3;

        m_buf_main.add(type, x, y);
    }

    if (fg & TILE_FLAG_MDAM_MASK)
    {
        unsigned int mdam_flag = fg & TILE_FLAG_MDAM_MASK;
        if (mdam_flag == TILE_FLAG_MDAM_LIGHT)
            m_buf_main.add(TILE_MDAM_LIGHTLY_DAMAGED, x, y);
        else if (mdam_flag == TILE_FLAG_MDAM_MOD)
            m_buf_main.add(TILE_MDAM_MODERATELY_DAMAGED, x, y);
        else if (mdam_flag == TILE_FLAG_MDAM_HEAVY)
            m_buf_main.add(TILE_MDAM_HEAVILY_DAMAGED, x, y);
        else if (mdam_flag == TILE_FLAG_MDAM_SEV)
            m_buf_main.add(TILE_MDAM_SEVERELY_DAMAGED, x, y);
        else if (mdam_flag == TILE_FLAG_MDAM_ADEAD)
            m_buf_main.add(TILE_MDAM_ALMOST_DEAD, x, y);
    }
}

void DungeonRegion::pack_cursor(cursor_type type, unsigned int tile)
{
    const coord_def &gc = m_cursor[type];
    if (gc == NO_CURSOR || !on_screen(gc))
        return;

    m_buf_main.add(tile, gc.x - m_cx_to_gx, gc.y - m_cy_to_gy);
}

void DungeonRegion::pack_buffers()
{
    m_buf_dngn.clear();
    m_buf_doll.clear();
    m_buf_main.clear();

    if (m_tileb.empty())
        return;

    int tile = 0;
    for (int y = 0; y < crawl_view.viewsz.y; ++y)
        for (int x = 0; x < crawl_view.viewsz.x; ++x)
        {
            unsigned int bg = m_tileb[tile + 1];
            unsigned int fg = m_tileb[tile];
            unsigned int fg_idx = fg & TILE_FLAG_MASK;

            pack_background(bg, x, y);

            if (fg_idx >= TILEP_MCACHE_START)
            {
                mcache_entry *entry = mcache.get(fg_idx);
                if (entry)
                    pack_mcache(entry, x, y);
                else
                    m_buf_doll.add(TILEP_MONS_UNKNOWN, x, y);
            }
            else if (fg_idx == TILEP_PLAYER)
            {
                pack_player(x, y);
            }
            else if (fg_idx >= TILE_MAIN_MAX)
            {
                m_buf_doll.add(fg_idx, x, y);
            }

            pack_foreground(bg, fg, x, y);

            tile += 2;
        }

    pack_cursor(CURSOR_TUTORIAL, TILE_TUTORIAL_CURSOR);
    pack_cursor(CURSOR_MOUSE, you.see_cell(m_cursor[CURSOR_MOUSE]) ? TILE_CURSOR
                                                                   : TILE_CURSOR2);

    if (m_cursor[CURSOR_TUTORIAL] != NO_CURSOR
        && on_screen(m_cursor[CURSOR_TUTORIAL]))
    {
        m_buf_main.add(TILE_TUTORIAL_CURSOR, m_cursor[CURSOR_TUTORIAL].x,
                                             m_cursor[CURSOR_TUTORIAL].y);
    }

    for (unsigned int i = 0; i < m_overlays.size(); i++)
    {
        // overlays must be from the main image and must be in LOS.
        if (!crawl_view.in_grid_los(m_overlays[i].gc))
            continue;

        int idx = m_overlays[i].idx;
        if (idx >= TILE_MAIN_MAX)
            continue;

        int x = m_overlays[i].gc.x - m_cx_to_gx;
        int y = m_overlays[i].gc.y - m_cy_to_gy;
        m_buf_main.add(idx, x, y);
    }
}

struct tag_def
{
    tag_def() { text = NULL; left = right = 0; }

    const char* text;
    char left, right;
    char type;
};

// #define DEBUG_TILES_REDRAW
void DungeonRegion::render()
{
#ifdef DEBUG_TILES_REDRAW
    cprintf("rendering DungeonRegion\n");
#endif
    if (m_dirty)
    {
        pack_buffers();
        m_dirty = false;
    }

    set_transform();
    m_buf_dngn.draw();
    m_buf_doll.draw();
    m_buf_main.draw();

    if (you.berserk())
    {
        ShapeBuffer buff;
        VColour red_film(130, 0, 0, 100);
        buff.add(0, 0, mx, my, red_film);
        buff.draw();
    }

    FixedArray<tag_def, ENV_SHOW_DIAMETER, ENV_SHOW_DIAMETER> tag_show;

    int total_tags = 0;

    for (int t = TAG_MAX - 1; t >= 0; t--)
    {
        for (unsigned int i = 0; i < m_tags[t].size(); i++)
        {
            if (!crawl_view.in_grid_los(m_tags[t][i].gc))
                continue;

            const coord_def ep = grid2show(m_tags[t][i].gc);

            if (tag_show(ep).text)
                continue;

            const char *str = m_tags[t][i].tag.c_str();

            int width    = m_tag_font->string_width(str);
            tag_def &def = tag_show(ep);

            const int buffer = 2;

            def.left  = -width / 2 - buffer;
            def.right =  width / 2 + buffer;
            def.text  = str;
            def.type  = t;

            total_tags++;
        }

        if (total_tags)
            break;
    }

    if (!total_tags)
        return;

    // Draw text tags.
    // TODO enne - be more intelligent about not covering stuff up
    for (int y = 0; y < ENV_SHOW_DIAMETER; y++)
        for (int x = 0; x < ENV_SHOW_DIAMETER; x++)
        {
            coord_def ep(x, y);
            tag_def &def = tag_show(ep);

            if (!def.text)
                continue;

            const coord_def gc = show2grid(ep);
            coord_def pc;
            to_screen_coords(gc, pc);
            // center this coord, which is at the top left of gc's cell
            pc.x += dx / 2;

            const coord_def min_pos(sx, sy);
            const coord_def max_pos(ex, ey);
            m_tag_font->render_string(pc.x, pc.y, def.text,
                                      min_pos, max_pos, WHITE, false);
        }
}

void DungeonRegion::clear()
{
    m_tileb.clear();
}

void DungeonRegion::on_resize()
{
    // TODO enne
}

static int _adjacent_cmd(const coord_def &gc, const MouseEvent &event)
{
    coord_def dir = gc - you.pos();
    for (int i = 0; i < 9; i++)
    {
        if (dir_dx[i] != dir.x || dir_dy[i] != dir.y)
            continue;

        if (event.mod & MOD_SHIFT)
            return cmd_shift[i];
        else if (event.mod & MOD_CTRL)
            return cmd_ctrl[i];
        else
            return cmd_normal[i];
    }

    return 0;
}

static int _click_travel(const coord_def &gc, MouseEvent &event)
{
    if (!in_bounds(gc))
        return 0;

    int cmd = _adjacent_cmd(gc, event);
    if (cmd)
        return cmd;

    if (i_feel_safe())
    {
        start_travel(gc);
        return CK_MOUSE_CMD;
    }

    // If not safe, then take one step towards the click.
    travel_pathfind tp;
    tp.set_src_dst(you.pos(), gc);
    const coord_def dest = tp.pathfind(RMODE_TRAVEL);

    if (!dest.x && !dest.y)
        return 0;

    return _adjacent_cmd(dest, event);
}

// FIXME: If the player is targeted, the game asks the player to target
// something with the mouse, then targets the player anyways and treats
// mouse click as if it hadn't come during targeting (moves the player
// to the clicked cell, whatever).
static void _add_targeting_commands(const coord_def& pos)
{
    // Force targetting cursor back onto center to start off on a clean
    // slate.
    macro_buf_add_cmd(CMD_TARGET_CENTER);

    const coord_def delta = pos - you.pos();

    command_type cmd;

    if (delta.x < 0)
        cmd = CMD_TARGET_LEFT;
    else
        cmd = CMD_TARGET_RIGHT;

    for (int i = 0; i < std::abs(delta.x); i++)
        macro_buf_add_cmd(cmd);

    if (delta.y < 0)
        cmd = CMD_TARGET_UP;
    else
        cmd = CMD_TARGET_DOWN;

    for (int i = 0; i < std::abs(delta.y); i++)
        macro_buf_add_cmd(cmd);

    macro_buf_add_cmd(CMD_TARGET_MOUSE_SELECT);
}

static const bool _is_appropriate_spell(spell_type spell,
                                        const actor* target)
{
    ASSERT(is_valid_spell(spell));

    // TODO: Implement tiles Evaporate interface.
    if (spell == SPELL_EVAPORATE)
        return (false);

    const unsigned int flags    = get_spell_flags(spell);
    const bool         targeted = flags & SPFLAG_TARGETTING_MASK;

    // We don't handle grid targeted spells yet.
    if (flags & SPFLAG_GRID)
        return (false);

    // Monst spells are blocked by transparent walls.
    if (targeted && !you.see_cell_no_trans(target->pos()))
    {
        switch(spell)
        {
        case SPELL_HELLFIRE_BURST:
        case SPELL_SMITING:
        case SPELL_HAUNT:
        case SPELL_FIRE_STORM:
        case SPELL_AIRSTRIKE:
            break;

        default:
            return (false);
        }
    }

    const bool helpful = flags & SPFLAG_HELPFUL;

    if (target->atype() == ACT_PLAYER)
    {
        if (flags & SPFLAG_NOT_SELF)
            return (false);

        return ((flags & (SPFLAG_HELPFUL | SPFLAG_ESCAPE | SPFLAG_RECOVERY))
                || !targeted);
    }

    if (!targeted)
        return (false);

    if (flags & SPFLAG_NEUTRAL)
        return (false);

    bool friendly = dynamic_cast<const monsters*>(target)->wont_attack();

    return (friendly == helpful);
}

static const bool _is_appropriate_evokable(const item_def& item,
                                           const actor* target)
{
    if (!item_is_evokable(item, false, true))
        return (false);

    // Only wands for now.
    if (item.base_type != OBJ_WANDS)
        return (false);

    // Aren't yet any wands that can go through transparent walls.
    if (!you.see_cell_no_trans(target->pos()))
        return (false);

    // We don't know what it is, so it *might* be appropriate.
    if (!item_type_known(item))
        return (true);

    // Random effects are always (in)apropriate for all targets.
    if (item.sub_type == WAND_RANDOM_EFFECTS)
        return (true);

    spell_type spell = zap_type_to_spell(item.zap());
    if (spell == SPELL_TELEPORT_OTHER && target->atype() == ACT_PLAYER)
        spell = SPELL_TELEPORT_SELF;

    return(_is_appropriate_spell(spell, target));
}

static const bool _have_appropriate_evokable(const actor* target)
{
    for (int i = 0; i < ENDOFPACK; i++)
    {
        item_def &item(you.inv[i]);

        if (!item.is_valid())
            continue;

        if (_is_appropriate_evokable(item, target))
            return (true);
   }

    return (false);
}

static item_def* _get_evokable_item(const actor* target)
{
    std::vector<const item_def*> list;

    for (int i = 0; i < ENDOFPACK; i++)
    {
        item_def &item(you.inv[i]);

        if (!item.is_valid())
            continue;

        if (_is_appropriate_evokable(item, target))
            list.push_back(&item);
    }

    ASSERT(!list.empty());

    InvMenu menu(MF_SINGLESELECT | MF_ANYPRINTABLE
                 | MF_ALLOW_FORMATTING | MF_SELECT_BY_PAGE);
    menu.set_type(MT_ANY);
    menu.set_title("Wand to zap?");
    menu.load_items(list);
    menu.show();
    std::vector<SelItem> sel = menu.get_selitems();

    update_screen();
    redraw_screen();

    if (sel.empty())
        return (NULL);

    return ( const_cast<item_def*>(sel[0].item) );
}

static bool _evoke_item_on_target(actor* target)
{
    item_def* item;
    {
        // Prevent the inventory letter from being recorded twice.
        pause_all_key_recorders pause;

        item = _get_evokable_item(target);
    }

    if (item == NULL)
        return (false);

    if (item->base_type == OBJ_WANDS)
    {
        if (item->plus2 == ZAPCOUNT_EMPTY
            || item_type_known(*item) && item->plus <= 0)
        {
            mpr("That wand is empty.");
            return (false);
        }
    }

    macro_buf_add_cmd(CMD_EVOKE);
    macro_buf_add(index_to_letter(item->link)); // Inventory letter.
    _add_targeting_commands(target->pos());
    return (true);
}

static bool _spell_in_range(spell_type spell, actor* target)
{
    if (!(get_spell_flags(spell) & SPFLAG_TARGETTING_MASK))
        return (true);

    int range = calc_spell_range(spell);

    switch(spell)
    {
    case SPELL_EVAPORATE:
    case SPELL_MEPHITIC_CLOUD:
    case SPELL_FIREBALL:
    case SPELL_FREEZING_CLOUD:
    case SPELL_POISONOUS_CLOUD:
        // Increase range by one due to cloud radius.
        range++;
        break;
    default:
        break;
    }

    return (range >= grid_distance(you.pos(), target->pos()));
}

static actor* _spell_target = NULL;

static bool _spell_selector(spell_type spell, bool &grey)
{
    if (!_spell_in_range(spell, _spell_target))
        grey = true;

    return (_is_appropriate_spell(spell, _spell_target));
}

// TODO: Cast spells which target a particular cell.
static bool _cast_spell_on_target(actor* target)
{
    ASSERT(_spell_target == NULL);
    _spell_target = target;

    int letter;
    {
        // Prevent the spell letter from being recorded twice.
        pause_all_key_recorders pause;

        letter = list_spells(true, false, -1, _spell_selector);
    }

    _spell_target = NULL;

    if (letter == 0)
        return (false);

    const spell_type spell = get_spell_by_letter(letter);

    ASSERT(is_valid_spell(spell));
    ASSERT(_is_appropriate_spell(spell, target));

    if (!_spell_in_range(spell, target))
    {
        mprf("%s is out of range for that spell.",
             target->name(DESC_CAP_THE).c_str());
        return (true);
    }

    if (spell_mana(spell) > you.magic_points)
    {
        mpr( "You don't have enough mana to cast that spell.");
        return (true);
    }

    macro_buf_add_cmd(CMD_CAST_SPELL);
    macro_buf_add(letter);

    if (get_spell_flags(spell) & SPFLAG_TARGETTING_MASK)
        _add_targeting_commands(target->pos());
    return (true);
}

static const bool _have_appropriate_spell(const actor* target)
{
    for (size_t i = 0; i < you.spells.size(); i++)
    {
        spell_type spell = you.spells[i];

        if (!is_valid_spell(spell))
            continue;

        if (_is_appropriate_spell(spell, target))
            return (true);
    }

    return (false);
}

static bool _handle_distant_monster(monsters* mon, MouseEvent &event)
{
    const coord_def gc = mon->pos();

    // Handle firing quivered items.
    if ((event.mod & MOD_SHIFT) && you.m_quiver->get_fire_item() != -1)
    {
        macro_buf_add_cmd(CMD_FIRE);
        _add_targeting_commands(mon->pos());
        return (true);
    }

    // Handle evoking items at monster.
    if ((event.mod & MOD_ALT) && _have_appropriate_evokable(mon))
        return _evoke_item_on_target(mon);

    // Handle casting spells at monster.
    if ((event.mod & MOD_CTRL) && _have_appropriate_spell(mon))
        return _cast_spell_on_target(mon);

    // Handle weapons of reaching.
    if (!mon->wont_attack() && you.see_cell_no_trans(mon->pos()))
    {
        const item_def* weapon = you.weapon();
        const coord_def delta  = you.pos() - mon->pos();
        const int       x_dist = std::abs(delta.x);
        const int       y_dist = std::abs(delta.y);

        if (weapon && get_weapon_brand(*weapon) == SPWPN_REACHING
            && std::max(x_dist, y_dist) == 2)
        {
            macro_buf_add_cmd(CMD_EVOKE_WIELDED);
            _add_targeting_commands(mon->pos());
            return (true);
        }
    }

    return (false);
}

static bool _handle_zap_player(MouseEvent &event)
{
    if ((event.mod & MOD_ALT) && _have_appropriate_evokable(&you))
        return _evoke_item_on_target(&you);

    if ((event.mod & MOD_CTRL) && _have_appropriate_spell(&you))
        return _cast_spell_on_target(&you);

    return (false);
}

int DungeonRegion::handle_mouse(MouseEvent &event)
{
    tiles.clear_text_tags(TAG_CELL_DESC);

    if (!inside(event.px, event.py))
        return 0;

    if (mouse_control::current_mode() == MOUSE_MODE_NORMAL
        && event.event == MouseEvent::PRESS
        && event.button == MouseEvent::LEFT)
    {
        you.last_clicked_grid = m_cursor[CURSOR_MOUSE];
        return CK_MOUSE_CLICK;
    }

    if (mouse_control::current_mode() == MOUSE_MODE_NORMAL
        || mouse_control::current_mode() == MOUSE_MODE_MACRO
        || mouse_control::current_mode() == MOUSE_MODE_MORE)
    {
        return 0;
    }

    int cx;
    int cy;

    bool on_map = mouse_pos(event.px, event.py, cx, cy);

    const coord_def gc(cx + m_cx_to_gx, cy + m_cy_to_gy);
    tiles.place_cursor(CURSOR_MOUSE, gc);

    if (event.event == MouseEvent::MOVE)
    {
        std::string desc = get_terse_square_desc(gc);
        // Suppress floor description
        if (desc == "floor")
            desc = "";

        if (you.see_cell(gc))
        {
            const int cloudidx = env.cgrid(gc);
            if (cloudidx != EMPTY_CLOUD)
            {
                std::string terrain_desc = desc;
                desc = cloud_name(cloudidx);

                if (!terrain_desc.empty())
                    desc += "\n" + terrain_desc;
            }
        }

        if (!desc.empty())
            tiles.add_text_tag(TAG_CELL_DESC, desc, gc);
    }

    if (!on_map)
        return 0;

    if (mouse_control::current_mode() == MOUSE_MODE_TARGET
        || mouse_control::current_mode() == MOUSE_MODE_TARGET_PATH
        || mouse_control::current_mode() == MOUSE_MODE_TARGET_DIR)
    {
        if (event.event == MouseEvent::MOVE)
        {
            return CK_MOUSE_MOVE;
        }
        else if (event.event == MouseEvent::PRESS
                 && event.button == MouseEvent::LEFT && on_screen(gc))
        {
            you.last_clicked_grid = m_cursor[CURSOR_MOUSE];
            return CK_MOUSE_CLICK;
        }

        return 0;
    }

    if (event.event != MouseEvent::PRESS)
        return 0;

    you.last_clicked_grid = m_cursor[CURSOR_MOUSE];

    if (you.pos() == gc)
    {
        switch (event.button)
        {
        case MouseEvent::LEFT:
        {
            if ((event.mod & (MOD_CTRL | MOD_ALT)))
            {
                if (_handle_zap_player(event))
                    return 0;
            }

            if (!(event.mod & MOD_SHIFT))
                return 'g';

            const dungeon_feature_type feat = grd(gc);
            switch (feat_stair_direction(feat))
            {
            case CMD_GO_DOWNSTAIRS:
                return ('>');
            case CMD_GO_UPSTAIRS:
                return ('<');
            default:
                if (feat_is_altar(feat)
                    && player_can_join_god(feat_altar_god(feat)))
                {
                    return ('p');
                }
                return 0;
            }
        }
        case MouseEvent::RIGHT:
            if (!(event.mod & MOD_SHIFT))
                return '%'; // Character overview.
            if (you.religion != GOD_NO_GOD)
                return '^'; // Religion screen.

            // fall through...
        default:
            return 0;
        }

    }
    // else not on player...
    if (event.button == MouseEvent::RIGHT)
    {
        full_describe_square(gc);
        return CK_MOUSE_CMD;
    }

    if (event.button != MouseEvent::LEFT)
        return 0;

    monsters* mon = monster_at(gc);
    if (mon && you.can_see(mon))
    {
        if (_handle_distant_monster(mon, event))
            return (CK_MOUSE_CMD);
    }

    // Don't move if we've tried to fire/cast/evoke when there's nothing
    // available.
    if (event.mod & (MOD_SHIFT | MOD_CTRL | MOD_ALT))
    {
        // Ctrl-Click on adjacent open doors closes them.
        if ((event.mod & MOD_CTRL) && grd(gc) == DNGN_OPEN_DOOR
            && adjacent(you.pos(), gc) && (mon == NULL || !you.can_see(mon)))
        {
            return _click_travel(gc, event);
        }
        else
            return (CK_MOUSE_CMD);
    }

    return _click_travel(gc, event);
}

void DungeonRegion::to_screen_coords(const coord_def &gc, coord_def &pc) const
{
    int cx = gc.x - m_cx_to_gx;
    int cy = gc.y - m_cy_to_gy;

    pc.x = sx + ox + cx * dx;
    pc.y = sy + oy + cy * dy;
}

bool DungeonRegion::on_screen(const coord_def &gc) const
{
    int x = gc.x - m_cx_to_gx;
    int y = gc.y - m_cy_to_gy;

    return (x >= 0 && x < mx && y >= 0 && y < my);
}

// Returns the index into m_tileb for the foreground tile.
// This value may not be valid.  Check on_screen() first.
// Add one to the return value to get the background tile idx.
int DungeonRegion::get_buffer_index(const coord_def &gc)
{
    int x = gc.x - m_cx_to_gx;
    int y = gc.y - m_cy_to_gy;
    return 2 * (x + y * mx);
}

void DungeonRegion::place_cursor(cursor_type type, const coord_def &gc)
{
    coord_def result = gc;

    // If we're only looking for a direction, put the mouse
    // cursor next to the player to let them know that their
    // spell/wand will only go one square.
    if (mouse_control::current_mode() == MOUSE_MODE_TARGET_DIR
        && type == CURSOR_MOUSE && gc != NO_CURSOR)
    {
        coord_def delta = gc - you.pos();

        int ax = abs(delta.x);
        int ay = abs(delta.y);

        result = you.pos();
        if (1000 * ay < 414 * ax)
            result += (delta.x > 0) ? coord_def(1, 0) : coord_def(-1, 0);
        else if (1000 * ax < 414 * ay)
            result += (delta.y > 0) ? coord_def(0, 1) : coord_def(0, -1);
        else if (delta.x > 0)
            result += (delta.y > 0) ? coord_def(1, 1) : coord_def(1, -1);
        else if (delta.x < 0)
            result += (delta.y > 0) ? coord_def(-1, 1) : coord_def(-1, -1);
    }

    if (m_cursor[type] != result)
    {
        m_dirty = true;
        m_cursor[type] = result;
        if (type == CURSOR_MOUSE)
            you.last_clicked_grid = coord_def();
    }
}

bool DungeonRegion::update_tip_text(std::string& tip)
{
    // TODO enne - it would be really nice to use the tutorial
    // descriptions here for features, monsters, etc...
    // Unfortunately, that would require quite a bit of rewriting
    // and some parsing of formatting to get that to work.

    if (mouse_control::current_mode() != MOUSE_MODE_COMMAND)
        return (false);

    if (m_cursor[CURSOR_MOUSE] == NO_CURSOR)
        return (false);
    if (!map_bounds(m_cursor[CURSOR_MOUSE]))
        return (false);

    const bool have_reach = you.weapon()
        && get_weapon_brand(*(you.weapon())) == SPWPN_REACHING;
    const int  attack_dist = have_reach ? 2 : 1;

    if (m_cursor[CURSOR_MOUSE] == you.pos())
    {
        tip = you.your_name;
        tip += " (";
        tip += get_species_abbrev(you.species);
        tip += get_class_abbrev(you.char_class);
        tip += ")";

        if (you.visible_igrd(m_cursor[CURSOR_MOUSE]) != NON_ITEM)
            tip += "\n[L-Click] Pick up items (g)";

        const dungeon_feature_type feat = grd(m_cursor[CURSOR_MOUSE]);
        const command_type dir = feat_stair_direction(feat);
        if (dir != CMD_NO_CMD)
        {
            tip += "\n[Shift-L-Click] ";
            if (feat == DNGN_ENTER_SHOP)
                tip += "enter shop";
            else if (feat_is_gate(feat))
                tip += "enter gate";
            else
                tip += "use stairs";

            if (dir == CMD_GO_DOWNSTAIRS)
                tip += " (>)";
            else
                tip += " (<)";
        }
        else if (feat_is_altar(feat) && player_can_join_god(feat_altar_god(feat)))
            tip += "\n[Shift-L-Click] pray on altar (p)";

        // Character overview.
        tip += "\n[R-Click] Overview (%)";

        // Religion.
        if (you.religion != GOD_NO_GOD)
            tip += "\n[Shift-R-Click] Religion (^)";
    }
    else if (abs(m_cursor[CURSOR_MOUSE].x - you.pos().x) <= attack_dist
             && abs(m_cursor[CURSOR_MOUSE].y - you.pos().y) <= attack_dist)
    {
        tip = "";

        if (!cell_is_solid(m_cursor[CURSOR_MOUSE]))
        {
            const monsters *mon = monster_at(m_cursor[CURSOR_MOUSE]);
            if (!mon || mon->friendly())
                tip = "[L-Click] Move\n";
            else if (mon)
            {
                tip = mon->name(DESC_CAP_A);
                tip += "\n[L-Click] Attack\n";
            }
        }
    }
    else
    {
        if (i_feel_safe() && !cell_is_solid(m_cursor[CURSOR_MOUSE]))
            tip = "[L-Click] Travel\n";

    }

    if (m_cursor[CURSOR_MOUSE] != you.pos())
    {
        const monsters* mon = monster_at(m_cursor[CURSOR_MOUSE]);
        if (mon && you.can_see(mon))
        {
            if (you.see_cell_no_trans(mon->pos())
                && you.m_quiver->get_fire_item() != -1)
            {
                tip += "[Shift-L-Click] Fire\n";
            }
        }

    }

    const actor* target = actor_at(m_cursor[CURSOR_MOUSE]);
    if (target && you.can_see(target))
    {
        std::string str = "";

        if (_have_appropriate_spell(target))
            str += "[Ctrl-L-Click] Cast spell\n";

        if (_have_appropriate_evokable(target))
            str += "[Alt-L-Click] Zap wand\n";

        if (!str.empty())
        {
            if (m_cursor[CURSOR_MOUSE] == you.pos())
                tip += "\n";

            tip += str;
        }
    }

    if (m_cursor[CURSOR_MOUSE] != you.pos())
        tip += "[R-Click] Describe";

    return (true);
}

class alt_desc_proc
{
public:
    alt_desc_proc(int _w, int _h) { w = _w; h = _h; }

    int width() { return w; }
    int height() { return h; }
    void nextline()
    {
        ostr << "\n";
    }
    void print(const std::string &str)
    {
        ostr << str;
    }

    static int count_newlines(const std::string &str)
    {
        int count = 0;
        for (size_t i = 0; i < str.size(); i++)
        {
            if (str[i] == '\n')
                count++;
        }
        return count;
    }

    // Remove trailing newlines.
    static void trim(std::string &str)
    {
        int idx = str.size();
        while (--idx >= 0)
        {
            if (str[idx] != '\n')
                break;
        }
        str.resize(idx + 1);
    }

    // rfind consecutive newlines and truncate.
    static bool chop(std::string &str)
    {
        int loc = -1;
        for (size_t i = 1; i < str.size(); i++)
            if (str[i] == '\n' && str[i-1] == '\n')
                loc = i;

        if (loc == -1)
            return (false);

        str.resize(loc);
        return (true);
    }

    void get_string(std::string &str)
    {
        str = replace_all(ostr.str(), "\n\n\n\n", "\n\n");
        str = replace_all(str, "\n\n\n", "\n\n");

        trim(str);
        while (count_newlines(str) > h)
        {
            if (!chop(str))
                break;
        }
    }

protected:
    int w;
    int h;
    std::ostringstream ostr;
};

bool DungeonRegion::update_alt_text(std::string &alt)
{
    if (mouse_control::current_mode() != MOUSE_MODE_COMMAND)
        return (false);

    const coord_def &gc = m_cursor[CURSOR_MOUSE];

    if (gc == NO_CURSOR)
        return (false);
    if (!map_bounds(gc))
        return (false);
    if (!is_terrain_seen(gc))
        return (false);
    if (you.last_clicked_grid == gc)
        return (false);

    describe_info inf;
    if (you.see_cell(gc))
    {
        get_square_desc(gc, inf, true);
        const int cloudidx = env.cgrid(gc);
        if (cloudidx != EMPTY_CLOUD)
        {
            inf.prefix = "There is a cloud of " + cloud_name(cloudidx)
                         + " here.$$";
        }
    }
    else if (grd(gc) != DNGN_FLOOR)
        get_feature_desc(gc, inf);
    else
    {
        // For plain floor, output the stash description.
        std::string stash = get_stash_desc(gc.x, gc.y);
        if (!stash.empty())
            inf.body << "$" << stash;
    }

    alt_desc_proc proc(crawl_view.msgsz.x, crawl_view.msgsz.y);
    process_description<alt_desc_proc>(proc, inf);

    proc.get_string(alt);

    // Suppress floor description
    if (alt == "Floor.")
    {
        alt.clear();
        return (false);
    }
    return (true);
}

void DungeonRegion::clear_text_tags(text_tag_type type)
{
    m_tags[type].clear();
}

void DungeonRegion::add_text_tag(text_tag_type type, const std::string &tag,
                                 const coord_def &gc)
{
    TextTag t;
    t.tag = tag;
    t.gc  = gc;

    m_tags[type].push_back(t);
}

void DungeonRegion::add_overlay(const coord_def &gc, int idx)
{
    tile_overlay over;
    over.gc  = gc;
    over.idx = idx;

    m_overlays.push_back(over);
    m_dirty = true;
}

void DungeonRegion::clear_overlays()
{
    m_overlays.clear();
    m_dirty = true;
}

InventoryTile::InventoryTile()
{
    tile     = 0;
    idx      = -1;
    quantity = -1;
    key      = 0;
    flag     = 0;
    special  = 0;
}

bool InventoryTile::empty() const
{
    return (idx == -1);
}

InventoryRegion::InventoryRegion(ImageManager* im, FTFont *tag_font,
                                 int tile_x, int tile_y) :
    TileRegion(im, tag_font, tile_x, tile_y),
    m_flavour(NULL),
    m_buf_dngn(&im->m_textures[TEX_DUNGEON]),
    m_buf_main(&im->m_textures[TEX_DEFAULT]),
    m_buf_spells(&im->m_textures[TEX_GUI]),
    m_cursor(NO_CURSOR)
{
}

InventoryRegion::~InventoryRegion()
{
    delete[] m_flavour;
    m_flavour = NULL;
}

void InventoryRegion::clear()
{
    m_items.clear();
    m_buf_dngn.clear();
    m_buf_main.clear();
    m_buf_spells.clear();
}

void InventoryRegion::on_resize()
{
    delete[] m_flavour;
    if (mx * my <= 0)
        return;

    m_flavour = new unsigned char[mx * my];
    for (int i = 0; i < mx * my; ++i)
        m_flavour[i] = random2((unsigned char)~0);
}

void InventoryRegion::update(int num, InventoryTile *items)
{
    m_items.clear();
    for (int i = 0; i < num; i++)
        m_items.push_back(items[i]);

    m_dirty = true;
}

void InventoryRegion::update_slot(int slot, InventoryTile &desc)
{
    while (m_items.size() <= (unsigned int)slot)
    {
        InventoryTile temp;
        m_items.push_back(temp);
    }

    m_items[slot] = desc;
#if 0
    // Not needed? (jpeg)
    m_dirty = true;
#endif
}

void InventoryRegion::render()
{
    if (m_dirty)
    {
        pack_buffers();
        m_dirty = false;
    }

    if (m_buf_dngn.empty() && m_buf_main.empty())
        return;

#ifdef DEBUG_TILES_REDRAW
    cprintf("rendering InventoryRegion\n");
#endif
    set_transform();
    m_buf_dngn.draw();
    m_buf_spells.draw();
    m_buf_main.draw();

    if (m_cursor != NO_CURSOR)
    {
        unsigned int curs_index = cursor_index();
        if (curs_index >= m_items.size())
            return;
        int idx = m_items[curs_index].idx;
        if (idx == -1)
            return;

        bool floor = m_items[curs_index].flag & TILEI_FLAG_FLOOR;

        // Always draw the description in the inventory header. (jpeg)
        int x = sx + ox + dx / 2;
        int y = sy + oy;

        const coord_def min_pos(sx, sy - dy);
        const coord_def max_pos(ex, ey);

        std::string desc = "";
        if (Options.tile_display != TDSP_INVENT)
        {
            const spell_type spell = (spell_type) idx;
            if (spell == NUM_SPELLS)
            {
                snprintf(info, INFO_SIZE, "Memorise spells (%d spell levels "
                                          "available)",
                         player_spell_levels());
                desc = info;
            }
            else if (Options.tile_display == TDSP_SPELLS)
            {
                snprintf(info, INFO_SIZE, "%d MP    %s    (%s)",
                         spell_difficulty(spell),
                         spell_title(spell),
                         failure_rate_to_string(spell_fail(spell)));
                desc = info;
            }
            else // if (Options.tile_display == TDSP_MEMORISE)
            {
                snprintf(info, INFO_SIZE, "%s    (%s)    %d/%d spell slot%s",
                         spell_title(spell),
                         failure_rate_to_string(spell_fail(spell)),
                         spell_levels_required(spell),
                         player_spell_levels(),
                         spell_levels_required(spell) > 1 ? "s" : "");
                desc = info;
            }
        }
        else if (floor && mitm[idx].is_valid())
            desc = mitm[idx].name(DESC_PLAIN);
        else if (!floor && you.inv[idx].is_valid())
            desc = you.inv[idx].name(DESC_INVENTORY_EQUIP);

        if (!desc.empty())
        {
            m_tag_font->render_string(x, y, desc.c_str(),
                                      min_pos, max_pos, WHITE, false, 200);
        }
    }
}

void InventoryRegion::add_quad_char(char c, int x, int y, int ofs_x, int ofs_y)
{
    int num = c - '0';
    assert(num >= 0 && num <= 9);
    int idx = TILE_NUM0 + num;

    m_buf_main.add(idx, x, y, ofs_x, ofs_y, false);
}

void InventoryRegion::pack_buffers()
{
    m_buf_dngn.clear();
    m_buf_main.clear();
    m_buf_spells.clear();

    // Ensure the cursor has been placed.
    place_cursor(m_cursor);

    // Pack base separately, as it comes from a different texture...
    unsigned int i = 0;
    for (int y = 0; y < my; y++)
    {
        if (i >= m_items.size())
            break;

        for (int x = 0; x < mx; x++)
        {
            if (i >= m_items.size())
                break;

            InventoryTile &item = m_items[i++];

            if (item.flag & TILEI_FLAG_FLOOR)
            {
                if (i >= (unsigned int) mx * my)
                    break;

                int num_floor = tile_dngn_count(env.tile_default.floor);
                m_buf_dngn.add(env.tile_default.floor
                                + m_flavour[i] % num_floor, x, y);
            }
            else
                m_buf_dngn.add(TILE_ITEM_SLOT, x, y);
        }
    }

    i = 0;
    for (int y = 0; y < my; y++)
    {
        if (i >= m_items.size())
            break;

        for (int x = 0; x < mx; x++)
        {
            if (i >= m_items.size())
                break;

            InventoryTile &item = m_items[i++];

            if (Options.tile_display != TDSP_INVENT)
            {
                if (item.flag & TILEI_FLAG_MELDED)
                    m_buf_main.add(TILE_MESH, x, y);
            }
            else if (item.flag & TILEI_FLAG_EQUIP)
            {
                if (item.flag & TILEI_FLAG_CURSE)
                    m_buf_main.add(TILE_ITEM_SLOT_EQUIP_CURSED, x, y);
                else
                    m_buf_main.add(TILE_ITEM_SLOT_EQUIP, x, y);

                if (item.flag & TILEI_FLAG_MELDED)
                    m_buf_main.add(TILE_MESH, x, y);
            }
            else if (item.flag & TILEI_FLAG_CURSE)
                m_buf_main.add(TILE_ITEM_SLOT_CURSED, x, y);

            // TODO enne - need better graphic here
            if (item.flag & TILEI_FLAG_SELECT)
                m_buf_main.add(TILE_ITEM_SLOT_SELECTED, x, y);

            if (item.flag & TILEI_FLAG_CURSOR)
                m_buf_main.add(TILE_CURSOR, x, y);

            if (item.tile)
            {
                if (Options.tile_display == TDSP_INVENT)
                    m_buf_main.add(item.tile, x, y);
                else
                    m_buf_spells.add(item.tile, x, y);
            }

            if (item.quantity != -1)
            {
                int num = item.quantity;
                // If you have that many, who cares.
                if (num > 999)
                    num = 999;

                const int offset_amount = TILE_X/4;
                int offset_x = 3;
                int offset_y = 1;

                int help = num;
                int c100 = help/100;
                help -= c100*100;

                if (c100)
                {
                    add_quad_char('0' + c100, x, y, offset_x, offset_y);
                    offset_x += offset_amount;
                }

                int c10 = help/10;
                if (c10 || c100)
                {
                    add_quad_char('0' + c10, x, y, offset_x, offset_y);
                    offset_x += offset_amount;
                }

                int c1 = help % 10;
                add_quad_char('0' + c1, x, y, offset_x, offset_y);
            }

            if (Options.tile_display == TDSP_INVENT && item.special)
                m_buf_main.add(item.special, x, y, 0, 0, false);

            if (item.flag & TILEI_FLAG_TRIED)
                m_buf_main.add(TILE_TRIED, x, y, 0, TILE_Y / 2, false);

            if (item.flag & TILEI_FLAG_INVALID)
                m_buf_main.add(TILE_MESH, x, y);
        }
    }
}

unsigned int InventoryRegion::cursor_index() const
{
    ASSERT(m_cursor != NO_CURSOR);
    return (m_cursor.x + m_cursor.y * mx);
}

void InventoryRegion::place_cursor(const coord_def &cursor)
{
    if (m_cursor != NO_CURSOR && cursor_index() < m_items.size())
    {
        m_items[cursor_index()].flag &= ~TILEI_FLAG_CURSOR;
#if 0
        // Not needed? (jpeg)
        m_dirty = true;
#endif
    }

    if (m_cursor != cursor)
        you.last_clicked_item = -1;

    m_cursor = cursor;

    if (m_cursor == NO_CURSOR || cursor_index() >= m_items.size())
        return;

    // Add cursor to new location
    m_items[cursor_index()].flag |= TILEI_FLAG_CURSOR;
    m_dirty = true;
}

int InventoryRegion::handle_spells_mouse(MouseEvent &event, int item_idx)
{
    const spell_type spell = (spell_type) m_items[item_idx].idx;
    if (event.button == MouseEvent::LEFT)
    {
        if (spell == NUM_SPELLS)
        {
            if (can_learn_spell() && has_spells_to_memorise(false))
            {
                Options.tile_display = TDSP_MEMORISE;
                tiles.update_inventory();
            }
            else
            {
                // FIXME: Doesn't work. The message still disappears instantly!
                you.last_clicked_item = item_idx;
                tiles.set_need_redraw();
            }
            return CK_MOUSE_CMD;
        }
        if (Options.tile_display == TDSP_SPELLS)
        {
            you.last_clicked_item = item_idx;
            tiles.set_need_redraw();
            // Use Z rather than z, seeing how there are no mouseclick macros.
            if (!cast_a_spell(false, spell))
                flush_input_buffer( FLUSH_ON_FAILURE );
        }
        else if (Options.tile_display == TDSP_MEMORISE)
        {
            you.last_clicked_item = item_idx;
            tiles.set_need_redraw();
            if (!learn_spell(spell, m_items[item_idx].special))
                flush_input_buffer( FLUSH_ON_FAILURE );
            else if (!can_learn_spell(true)
                     || !has_spells_to_memorise(true, spell))
            {
                // Jump back to spells list. (Really, this should only happen
                // if there aren't any other spells to memorise, but this
                // doesn't work for some reason.)
                Options.tile_display = TDSP_SPELLS;
                tiles.update_inventory();
            }
        }
        return CK_MOUSE_CMD;
    }
    else if (spell != NUM_SPELLS && event.button == MouseEvent::RIGHT)
    {
        describe_spell(spell);
        redraw_screen();
        return CK_MOUSE_CMD;
    }
    return 0;
}

int InventoryRegion::handle_mouse(MouseEvent &event)
{
    int cx, cy;
    if (!mouse_pos(event.px, event.py, cx, cy))
    {
        place_cursor(NO_CURSOR);
        return 0;
    }

    const coord_def cursor(cx, cy);
    place_cursor(cursor);

    if (mouse_control::current_mode() != MOUSE_MODE_COMMAND)
        return 0;

    if (event.event != MouseEvent::PRESS)
        return 0;

    unsigned int item_idx = cursor_index();
    if (item_idx >= m_items.size() || m_items[item_idx].empty())
        return 0;

    int idx = m_items[item_idx].idx;

    if (m_items[item_idx].key == 0 && Options.tile_display != TDSP_INVENT)
        return handle_spells_mouse(event, item_idx);

    bool on_floor = m_items[item_idx].flag & TILEI_FLAG_FLOOR;

    ASSERT(idx >= 0);

    // TODO enne - this is all really only valid for the on-screen inventory
    // Do we subclass InventoryRegion for the onscreen and offscreen versions?
    char key = m_items[item_idx].key;
    if (key)
        return key;

    if (event.button == MouseEvent::LEFT)
    {
        you.last_clicked_item = item_idx;
        tiles.set_need_redraw();
        if (on_floor)
        {
            if (event.mod & MOD_SHIFT)
                tile_item_use_floor(idx);
            else
                tile_item_pickup(idx);
        }
        else
        {
            if (event.mod & MOD_SHIFT)
                tile_item_drop(idx);
            else if (event.mod & MOD_CTRL)
                tile_item_use_secondary(idx);
            else
                tile_item_use(idx);
        }
        // TODO enne - need to redraw inventory here?
        return CK_MOUSE_CMD;
    }
    else if (event.button == MouseEvent::RIGHT)
    {
        if (on_floor)
        {
            if (event.mod & MOD_SHIFT)
            {
                you.last_clicked_item = item_idx;
                tiles.set_need_redraw();
                tile_item_eat_floor(idx);
            }
            else
            {
                describe_item(mitm[idx]);
                redraw_screen();
            }
        }
        else // in inventory
        {
            describe_item(you.inv[idx], true);
            redraw_screen();
        }
        return CK_MOUSE_CMD;
    }

    return 0;
}

// NOTE: Assumes the item is equipped in the first place!
static bool _is_true_equipped_item(item_def item)
{
    // Weapons and staves are only truly equipped if wielded.
    if (item.link == you.equip[EQ_WEAPON])
        return (item.base_type == OBJ_WEAPONS || item.base_type == OBJ_STAVES);

    // Cursed armour and rings are only truly equipped if *not* wielded.
    return (item.link != you.equip[EQ_WEAPON]);
}

// Returns whether there's any action you can take with an item in inventory
// apart from dropping it.
static bool _can_use_item(const item_def &item, bool equipped)
{
    // There's nothing you can do with an empty box if you can't unwield it.
    if (!equipped && item.sub_type == MISC_EMPTY_EBONY_CASKET)
        return (false);

    // Vampires can drain corpses.
    if (item.base_type == OBJ_CORPSES)
    {
        return (you.species == SP_VAMPIRE
                && item.sub_type != CORPSE_SKELETON
                && !food_is_rotten(item)
                && mons_has_blood(item.plus));
    }

    if (equipped && item.cursed())
    {
        // Misc. items/rods can always be evoked, cursed or not.
        if (item.base_type == OBJ_MISCELLANY || item_is_rod(item))
            return (true);

        // You can't unwield/fire a wielded cursed weapon/staff
        // but cursed armour and rings can be unwielded without problems.
        return (!_is_true_equipped_item(item));
    }

    // Mummies can't do anything with food or potions.
    if (you.species == SP_MUMMY)
        return (item.base_type != OBJ_POTIONS && item.base_type != OBJ_FOOD);

    // In all other cases you can use the item in some way.
    return (true);
}

void _update_spell_tip_text(std::string& tip, int flag)
{
    if (Options.tile_display == TDSP_SPELLS)
    {
        if (flag & TILEI_FLAG_MELDED)
            tip = "You cannot cast this spell right now.";
        else
            tip = "[L-Click] Cast (z)";

        tip += "\n[R-Click] Describe (I)";
    }
    else if (Options.tile_display == TDSP_MEMORISE)
    {
        if (flag & TILEI_FLAG_MELDED)
            tip = "You don't have enough slots for this spell right now.";
        else
            tip = "[L-Click] Memorise (M)";

        tip += "\n[R-Click] Describe";
    }
}

bool InventoryRegion::update_tip_text(std::string& tip)
{
    if (m_cursor == NO_CURSOR)
        return (false);

    unsigned int item_idx = cursor_index();
    if (item_idx >= m_items.size() || m_items[item_idx].empty())
        return (false);

    int idx = m_items[item_idx].idx;

    // TODO enne - consider subclassing this class, rather than depending
    // on "key" to determine if this is the crt inventory or the on screen one.
    bool display_actions = (m_items[item_idx].key == 0
                    && mouse_control::current_mode() == MOUSE_MODE_COMMAND);

    if (Options.tile_display != TDSP_INVENT)
    {
        if (m_items[item_idx].idx == NUM_SPELLS)
        {
            if (m_items[item_idx].flag & TILEI_FLAG_MELDED)
                tip = "You cannot learn any spells right now.";
            else
                tip = "Memorise spells (M)";
        }
        else
            _update_spell_tip_text(tip, m_items[item_idx].flag);
        return (true);
    }

    // TODO enne - should the command keys here respect keymaps?

    if (m_items[item_idx].flag & TILEI_FLAG_FLOOR)
    {
        const item_def &item = mitm[idx];

        if (!item.is_valid())
            return (false);

        tip = "";
        if (m_items[item_idx].key)
        {
            tip = m_items[item_idx].key;
            tip += " - ";
        }

        tip += item.name(DESC_NOCAP_A);

        if (!display_actions)
            return (true);

        tip += "\n[L-Click] Pick up (g)";
        if (item.base_type == OBJ_CORPSES
            && item.sub_type != CORPSE_SKELETON
            && !food_is_rotten(item))
        {
            tip += "\n[Shift-L-Click] ";
            if (can_bottle_blood_from_corpse(item.plus))
                tip += "Bottle blood";
            else
                tip += "Chop up";
            tip += " (c)";

            if (you.species == SP_VAMPIRE)
                tip += "\n\n[Shift-R-Click] Drink blood (e)";
        }
        else if (item.base_type == OBJ_FOOD
                 && you.is_undead != US_UNDEAD
                 && you.species != SP_VAMPIRE)
        {
            tip += "\n[Shift-R-Click] Eat (e)";
        }
    }
    else
    {
        const item_def &item = you.inv[idx];
        if (!item.is_valid())
            return (false);

        tip = item.name(DESC_INVENTORY_EQUIP);

        if (!display_actions)
            return (true);

        int type = item.base_type;
        const bool equipped = m_items[item_idx].flag & TILEI_FLAG_EQUIP;
        bool wielded = (you.equip[EQ_WEAPON] == idx);

        const int EQUIP_OFFSET = NUM_OBJECT_CLASSES;

        if (_can_use_item(item, equipped))
        {
            tip += "\n[L-Click] ";
            if (equipped)
            {
                if (wielded && type != OBJ_MISCELLANY && !item_is_rod(item))
                {
                    if (type == OBJ_JEWELLERY || type == OBJ_ARMOUR
                        || type == OBJ_WEAPONS || type == OBJ_STAVES)
                    {
                        type = OBJ_WEAPONS + EQUIP_OFFSET;
                    }
                }
                else
                    type += EQUIP_OFFSET;
            }

            switch (type)
            {
            // first equipable categories
            case OBJ_WEAPONS:
            case OBJ_STAVES:
                tip += "Wield (w)";
                if (is_throwable(&you, item))
                    tip += "\n[Ctrl-L-Click] Fire (f)";
                break;
            case OBJ_WEAPONS + EQUIP_OFFSET:
                tip += "Unwield (w-)";
                if (is_throwable(&you, item))
                    tip += "\n[Ctrl-L-Click] Fire (f)";
                break;
            case OBJ_MISCELLANY:
                if (item.sub_type >= MISC_DECK_OF_ESCAPE
                    && item.sub_type <= MISC_DECK_OF_DEFENCE)
                {
                    tip += "Wield (w)";
                    break;
                }
                tip += "Evoke (V)";
                break;
            case OBJ_MISCELLANY + EQUIP_OFFSET:
                if (item.sub_type >= MISC_DECK_OF_ESCAPE
                    && item.sub_type <= MISC_DECK_OF_DEFENCE)
                {
                    tip += "Draw a card (v)\n";
                    tip += "[Ctrl-L-Click] Unwield (w-)";
                    break;
                }
                // else fall-through
            case OBJ_STAVES + EQUIP_OFFSET: // rods - other staves handled above
                tip += "Evoke (v)\n";
                tip += "[Ctrl-L-Click] Unwield (w-)";
                break;
            case OBJ_ARMOUR:
                tip += "Wear (W)";
                break;
            case OBJ_ARMOUR + EQUIP_OFFSET:
                tip += "Take off (T)";
                break;
            case OBJ_JEWELLERY:
                tip += "Put on (P)";
                break;
            case OBJ_JEWELLERY + EQUIP_OFFSET:
                tip += "Remove (R)";
                break;
            case OBJ_MISSILES:
                tip += "Fire (f)";

                if (wielded)
                    tip += "\n[Ctrl-L-Click] Unwield (w-)";
                else if (item.sub_type == MI_STONE
                            && you.has_spell(SPELL_SANDBLAST)
                         || item.sub_type == MI_ARROW
                            && you.has_spell(SPELL_STICKS_TO_SNAKES))
                {
                    // For Sandblast and Sticks to Snakes,
                    // respectively.
                    tip += "\n[Ctrl-L-Click] Wield (w)";
                }
                break;
            case OBJ_WANDS:
                tip += "Evoke (V)";
                if (wielded)
                    tip += "\n[Ctrl-L-Click] Unwield (w-)";
                break;
            case OBJ_BOOKS:
                if (item_type_known(item)
                    && item.sub_type != BOOK_MANUAL
                    && item.sub_type != BOOK_DESTRUCTION
                    && you.skills[SK_SPELLCASTING] > 0)
                {
                    tip += "Memorise (M)";
                    if (wielded)
                        tip += "\n[Ctrl-L-Click] Unwield (w-)";
                    break;
                }
                // else fall-through
            case OBJ_SCROLLS:
                tip += "Read (r)";
                if (wielded)
                    tip += "\n[Ctrl-L-Click] Unwield (w-)";
                break;
            case OBJ_POTIONS:
                tip += "Quaff (q)";
                // For Sublimation of Blood.
                if (wielded)
                    tip += "\n[Ctrl-L-Click] Unwield (w-)";
                else if (item_type_known(item)
                         && is_blood_potion(item)
                         && you.has_spell(SPELL_SUBLIMATION_OF_BLOOD))
                {
                    tip += "\n[Ctrl-L-Click] Wield (w)";
                }
                break;
            case OBJ_FOOD:
                tip += "Eat (e)";
                // For Sublimation of Blood.
                if (wielded)
                    tip += "\n[Ctrl-L-Click] Unwield (w-)";
                else if (item.sub_type == FOOD_CHUNK
                         && you.has_spell(
                                SPELL_SUBLIMATION_OF_BLOOD))
                {
                    tip += "\n[Ctrl-L-Click] Wield (w)";
                }
                break;
            case OBJ_CORPSES:
                if (you.species == SP_VAMPIRE)
                    tip += "Drink blood (e)";

                if (wielded)
                {
                    if (you.species == SP_VAMPIRE)
                        tip += EOL;
                    tip += "[Ctrl-L-Click] Unwield (w-)";
                }
                break;
            default:
                tip += "Use";
            }
        }

        // For Boneshards.
        // Special handling since skeletons have no primary action.
        if (item.base_type == OBJ_CORPSES
            && item.sub_type == CORPSE_SKELETON)
        {
            if (wielded)
                tip += "\n[Ctrl-L-Click] Unwield";
            else if (you.has_spell(SPELL_BONE_SHARDS))
                tip += "\n[Ctrl-L-Click] Wield (w)";
        }

        tip += "\n[R-Click] Info";
        // Has to be non-equipped or non-cursed to drop.
        if (!equipped || !_is_true_equipped_item(you.inv[idx])
            || !you.inv[idx].cursed())
        {
            tip += "\n[Shift-L-Click] Drop (d)";
        }
    }

    return (true);
}

void _update_spell_alt_text(std::string &alt, int idx)
{
    const spell_type spell = (spell_type) idx;

    if (spell == NUM_SPELLS)
    {
        alt.clear();
        return;
    }
    describe_info inf;
    get_spell_desc(spell, inf);

    alt_desc_proc proc(crawl_view.msgsz.x, crawl_view.msgsz.y);
    process_description<alt_desc_proc>(proc, inf);

    proc.get_string(alt);
}

bool InventoryRegion::update_alt_text(std::string &alt)
{
    if (m_cursor == NO_CURSOR)
        return (false);

    unsigned int item_idx = cursor_index();
    if (item_idx >= m_items.size() || m_items[item_idx].empty())
        return (false);

    if (you.last_clicked_item >= 0
        && item_idx == (unsigned int) you.last_clicked_item)
    {
        return (false);
    }

    int idx = m_items[item_idx].idx;
    if (m_items[item_idx].key == 0 && Options.tile_display != TDSP_INVENT)
    {
        _update_spell_alt_text(alt, idx);
        return (true);
    }

    const item_def *item;
    if (m_items[item_idx].flag & TILEI_FLAG_FLOOR)
        item = &mitm[idx];
    else
        item = &you.inv[idx];

    if (!item->is_valid())
        return (false);

    describe_info inf;
    get_item_desc(*item, inf, true);

    alt_desc_proc proc(crawl_view.msgsz.x, crawl_view.msgsz.y);
    process_description<alt_desc_proc>(proc, inf);

    proc.get_string(alt);

    return (true);
}

MapRegion::MapRegion(int pixsz) :
    m_buf(NULL),
    m_dirty(true),
    m_far_view(false)
{
    ASSERT(pixsz > 0);

    dx = pixsz;
    dy = pixsz;
    clear();
    init_colours();
}

void MapRegion::on_resize()
{
    delete[] m_buf;

    int size = mx * my;
    m_buf    = new unsigned char[size];
    memset(m_buf, 0, sizeof(unsigned char) * size);
}

void MapRegion::init_colours()
{
    // TODO enne - the options array for colours should be
    // tied to the map feature enumeration to avoid this function.
    m_colours[MF_UNSEEN]        = (map_colour)Options.tile_unseen_col;
    m_colours[MF_FLOOR]         = (map_colour)Options.tile_floor_col;
    m_colours[MF_WALL]          = (map_colour)Options.tile_wall_col;
    m_colours[MF_MAP_FLOOR]     = (map_colour)Options.tile_floor_col; // TODO enne
    m_colours[MF_MAP_WALL]      = (map_colour)Options.tile_mapped_wall_col;
    m_colours[MF_DOOR]          = (map_colour)Options.tile_door_col;
    m_colours[MF_ITEM]          = (map_colour)Options.tile_item_col;
    m_colours[MF_MONS_FRIENDLY] = (map_colour)Options.tile_friendly_col;
    m_colours[MF_MONS_PEACEFUL] = (map_colour)Options.tile_peaceful_col;
    m_colours[MF_MONS_NEUTRAL]  = (map_colour)Options.tile_neutral_col;
    m_colours[MF_MONS_HOSTILE]  = (map_colour)Options.tile_monster_col;
    m_colours[MF_MONS_NO_EXP]   = (map_colour)Options.tile_plant_col;
    m_colours[MF_STAIR_UP]      = (map_colour)Options.tile_upstairs_col;
    m_colours[MF_STAIR_DOWN]    = (map_colour)Options.tile_downstairs_col;
    m_colours[MF_STAIR_BRANCH]  = (map_colour)Options.tile_feature_col;
    m_colours[MF_FEATURE]       = (map_colour)Options.tile_feature_col;
    m_colours[MF_WATER]         = (map_colour)Options.tile_water_col;
    m_colours[MF_LAVA]          = (map_colour)Options.tile_lava_col;
    m_colours[MF_TRAP]          = (map_colour)Options.tile_trap_col;
    m_colours[MF_EXCL_ROOT]     = (map_colour)Options.tile_excl_centre_col;
    m_colours[MF_EXCL]          = (map_colour)Options.tile_excluded_col;
    m_colours[MF_PLAYER]        = (map_colour)Options.tile_player_col;
}

MapRegion::~MapRegion()
{
    delete[] m_buf;
}

void MapRegion::pack_buffers()
{
    m_buf_map.clear();
    m_buf_lines.clear();

    for (int x = m_min_gx; x <= m_max_gx; x++)
        for (int y = m_min_gy; y <= m_max_gy; y++)
        {
            map_feature f = (map_feature)m_buf[x + y * mx];
            map_colour c  = m_colours[f];

            float pos_x = x - m_min_gx;
            float pos_y = y - m_min_gy;
            m_buf_map.add(pos_x, pos_y, pos_x + 1, pos_y + 1, map_colours[c]);
        }

    // Draw window box.
    if (m_win_start.x == -1 && m_win_end.x == -1)
        return;

    int c = (int)Options.tile_window_col;
    float pos_sx = (m_win_start.x - m_min_gx);
    float pos_sy = (m_win_start.y - m_min_gy);
    float pos_ex = (m_win_end.x - m_min_gx) + 1 / (float)dx;
    float pos_ey = (m_win_end.y - m_min_gy) + 1 / (float)dy;

    m_buf_lines.add_square(pos_sx, pos_sy, pos_ex, pos_ey, map_colours[c]);
}

void MapRegion::render()
{
    if (m_min_gx > m_max_gx || m_min_gy > m_max_gy)
        return;

#ifdef DEBUG_TILES_REDRAW
    cprintf("rendering MapRegion\n");
#endif
    if (m_dirty)
    {
        pack_buffers();
        m_dirty = false;
    }

    set_transform();
    m_buf_map.draw();
    m_buf_lines.draw();
}

void MapRegion::recenter()
{
    // adjust offsets to center map
    ox = (wx - dx * (m_max_gx - m_min_gx)) / 2;
    oy = (wy - dy * (m_max_gy - m_min_gy)) / 2;
#if 0
    // Not needed? (jpeg)
    m_dirty = true;
#endif
}

void MapRegion::set(int gx, int gy, map_feature f)
{
    ASSERT((unsigned int)f <= (unsigned char)~0);
    m_buf[gx + gy * mx] = f;

    if (f == MF_UNSEEN)
        return;

    // Get map extents
    m_min_gx = std::min(m_min_gx, gx);
    m_max_gx = std::max(m_max_gx, gx);
    m_min_gy = std::min(m_min_gy, gy);
    m_max_gy = std::max(m_max_gy, gy);

    recenter();
}

void MapRegion::update_bounds()
{
    int min_gx = m_min_gx;
    int max_gx = m_max_gx;
    int min_gy = m_min_gy;
    int max_gy = m_max_gy;

    m_min_gx = GXM;
    m_max_gx = 0;
    m_min_gy = GYM;
    m_max_gy = 0;

    for (int x = min_gx; x <= max_gx; x++)
        for (int y = min_gy; y <= max_gy; y++)
        {
            map_feature f = (map_feature)m_buf[x + y * mx];
            if (f == MF_UNSEEN)
                continue;

            m_min_gx = std::min(m_min_gx, x);
            m_max_gx = std::max(m_max_gx, x);
            m_min_gy = std::min(m_min_gy, y);
            m_max_gy = std::max(m_max_gy, y);
        }

    recenter();
#if 0
    // Not needed? (jpeg)
    m_dirty = true;
#endif
}

void MapRegion::set_window(const coord_def &start, const coord_def &end)
{
    m_win_start = start;
    m_win_end   = end;

    m_dirty = true;
}

void MapRegion::clear()
{
    m_min_gx = GXM;
    m_max_gx = 0;
    m_min_gy = GYM;
    m_max_gy = 0;

    m_win_start.x = -1;
    m_win_start.y = -1;
    m_win_end.x = -1;
    m_win_end.y = -1;

    recenter();

    if (m_buf)
        memset(m_buf, 0, sizeof(*m_buf) * mx * my);

    m_buf_map.clear();
    m_buf_lines.clear();
}

int MapRegion::handle_mouse(MouseEvent &event)
{
    if (mouse_control::current_mode() != MOUSE_MODE_COMMAND)
        return 0;

    int cx;
    int cy;
    if (!mouse_pos(event.px, event.py, cx, cy))
    {
        if (m_far_view)
        {
            m_far_view = false;
            tiles.load_dungeon(crawl_view.vgrdc);
            return 0;
        }
        return 0;
    }

    const coord_def gc(m_min_gx + cx, m_min_gy + cy);

    tiles.place_cursor(CURSOR_MOUSE, gc);

    switch (event.event)
    {
    case MouseEvent::MOVE:
        if (m_far_view)
            tiles.load_dungeon(gc);
        return 0;
    case MouseEvent::PRESS:
        if (event.button == MouseEvent::LEFT)
        {
            return _click_travel(gc, event);
        }
        else if (event.button == MouseEvent::RIGHT)
        {
            m_far_view = true;
            tiles.load_dungeon(gc);
        }
        return 0;
    case MouseEvent::RELEASE:
        if ((event.button == MouseEvent::RIGHT) && m_far_view)
        {
            m_far_view = false;
            tiles.load_dungeon(crawl_view.vgrdc);
        }
        return 0;
    default:
        return 0;
    }
}

bool MapRegion::update_tip_text(std::string& tip)
{
    if (mouse_control::current_mode() != MOUSE_MODE_COMMAND)
        return (false);

    tip = "[L-Click] Travel / [R-Click] View";
    return (true);
}

void TextRegion::scroll()
{
    for (int idx = 0; idx < mx*(my-1); idx++)
    {
        cbuf[idx] = cbuf[idx + mx];
        abuf[idx] = abuf[idx + mx];
    }

    for (int idx = mx*(my-1); idx < mx*my; idx++)
    {
        cbuf[idx] = ' ';
        abuf[idx] = 0;
    }

    if (print_y > 0)
        print_y -= 1;
    if (cursor_y > 0)
        cursor_y -= 1;
}

TextRegion::TextRegion(FTFont *font) :
    cbuf(NULL),
    abuf(NULL),
    cx_ofs(0),
    cy_ofs(0),
    m_font(font)
{
    ASSERT(font);

    dx = m_font->char_width();
    dy = m_font->char_height();
}

void TextRegion::on_resize()
{
    delete[] cbuf;
    delete[] abuf;

    int size = mx * my;
    cbuf = new unsigned char[size];
    abuf = new unsigned char[size];

    for (int i = 0; i < size; i++)
    {
        cbuf[i] = ' ';
        abuf[i] = 0;
    }
}

TextRegion::~TextRegion()
{
    delete[] cbuf;
    delete[] abuf;
}

void TextRegion::adjust_region(int *x1, int *x2, int y)
{
    *x2 = *x2 + 1;
}

void TextRegion::addstr(char *buffer)
{
    char buf2[1024];
    int len = strlen(buffer);

    int j = 0;

    for (int i = 0; i < len + 1; i++)
    {
        char c = buffer[i];
        bool newline = false;

        if (c == '\r')
            continue;

        if (c == '\n')
        {
            c = 0;
            newline = true;
        }
        buf2[j] = c;
        j++;

        if (c == 0)
        {
            if (j-1 != 0)
                addstr_aux(buf2, j - 1); // draw it
            if (newline)
            {
                print_x = cx_ofs;
                print_y++;
                j = 0;

                if (print_y - cy_ofs == my)
                    scroll();
            }
        }
    }
    if (cursor_flag)
        cgotoxy(print_x+1, print_y+1);
}

void TextRegion::addstr_aux(char *buffer, int len)
{
    int x = print_x - cx_ofs;
    int y = print_y - cy_ofs;
    int adrs = y * mx;
    int head = x;
    int tail = x + len - 1;

    adjust_region(&head, &tail, y);

    for (int i = 0; i < len && x + i < mx; i++)
    {
        cbuf[adrs+x+i] = buffer[i];
        abuf[adrs+x+i] = text_col;
    }
    print_x += len;
}

void TextRegion::clear_to_end_of_line()
{
    int cx = print_x - cx_ofs;
    int cy = print_y - cy_ofs;
    int col = text_col;
    int adrs = cy * mx;

    ASSERT(adrs + mx - 1 < mx * my);
    for (int i = cx; i < mx; i++)
    {
        cbuf[adrs+i] = ' ';
        abuf[adrs+i] = col;
    }
}

void TextRegion::putch(unsigned char ch)
{
    // special case: check for '0' char: map to space
    if (ch == 0)
        ch = ' ';
    addstr_aux((char *)&ch, 1);
}

void TextRegion::writeWChar(unsigned char *ch)
{
    addstr_aux((char *)ch, 2);
}

void TextRegion::textcolor(int color)
{
    text_col = color;
}

void TextRegion::textbackground(int col)
{
    textcolor(col*16 + (text_col & 0xf));
}

void TextRegion::cgotoxy(int x, int y)
{
    ASSERT(x >= 1);
    ASSERT(y >= 1);
    print_x = x-1;
    print_y = y-1;

#if 0
    if (cursor_region != NULL && cursor_flag)
    {
        cursor_x = -1;
        cursor_y = -1;
        cursor_region = NULL;
    }
#endif
    if (cursor_flag)
    {
        cursor_x = print_x;
        cursor_y = print_y;
        cursor_region = text_mode;
    }
}

int TextRegion::wherex()
{
    return print_x + 1;
}

int TextRegion::wherey()
{
    return print_y + 1;
}

void TextRegion::_setcursortype(int curstype)
{
    cursor_flag = curstype;
    if (cursor_region != NULL)
    {
        cursor_x = -1;
        cursor_y = -1;
    }

    if (curstype)
    {
        cursor_x = print_x;
        cursor_y = print_y;
        cursor_region = text_mode;
    }
}

void TextRegion::render()
{
#ifdef DEBUG_TILES_REDRAW
    cprintf("rendering TextRegion\n");
#endif
    if (this == TextRegion::cursor_region && cursor_x > 0 && cursor_y > 0)
    {
        int idx = cursor_x + mx * cursor_y;

        unsigned char char_back = cbuf[idx];
        unsigned char col_back  = abuf[idx];

        cbuf[idx] = '_';
        abuf[idx] = WHITE;

        m_font->render_textblock(sx + ox, sy + oy, cbuf, abuf, mx, my);

        cbuf[idx] = char_back;
        abuf[idx] = col_back;
    }
    else
    {
        m_font->render_textblock(sx + ox, sy + oy, cbuf, abuf, mx, my);
    }
}

void TextRegion::clear()
{
    for (int i = 0; i < mx * my; i++)
    {
        cbuf[i] = ' ';
        abuf[i] = 0;
    }
}

StatRegion::StatRegion(FTFont *font) : TextRegion(font)
{
}

int StatRegion::handle_mouse(MouseEvent &event)
{
    if (mouse_control::current_mode() != MOUSE_MODE_COMMAND)
        return 0;

    if (!inside(event.px, event.py))
        return 0;

    if (event.event != MouseEvent::PRESS || event.button != MouseEvent::LEFT)
        return 0;

    // Resting
    return '5';
}

bool StatRegion::update_tip_text(std::string& tip)
{
    if (mouse_control::current_mode() != MOUSE_MODE_COMMAND)
        return (false);

    tip = "[L-Click] Rest / Search for a while";
    return (true);
}

MessageRegion::MessageRegion(FTFont *font) : TextRegion(font), m_overlay(false)
{
}

int MessageRegion::handle_mouse(MouseEvent &event)
{
    // TODO enne - mouse scrolling here should mouse scroll up through
    // the message history in the message pane, without going to the CRT.

    if (!inside(event.px, event.py))
        return 0;

    if (event.event != MouseEvent::PRESS || event.button != MouseEvent::LEFT)
        return 0;

    if (mouse_control::current_mode() != MOUSE_MODE_COMMAND)
        return 0;

    return CONTROL('P');
}

bool MessageRegion::update_tip_text(std::string& tip)
{
    if (mouse_control::current_mode() != MOUSE_MODE_COMMAND)
        return (false);

    tip = "[L-Click] Browse message history";
    return (true);
}

void MessageRegion::set_overlay(bool is_overlay)
{
    m_overlay = is_overlay;
}

void MessageRegion::render()
{
#ifdef DEBUG_TILES_REDRAW
    cprintf("rendering MessageRegion\n");
#endif
    int idx = -1;
    unsigned char char_back = 0;
    unsigned char col_back = 0;

    if (!m_overlay && !m_alt_text.empty())
    {
        coord_def min_pos(sx, sy);
        coord_def max_pos(ex, ey);
        m_font->render_string(sx + ox, sy + oy, m_alt_text.c_str(),
                              min_pos, max_pos, WHITE, false);
        return;
    }

    if (this == TextRegion::cursor_region && cursor_x > 0 && cursor_y > 0)
    {
        idx = cursor_x + mx * cursor_y;
        char_back = cbuf[idx];
        col_back  = abuf[idx];

        cbuf[idx] = '_';
        abuf[idx] = WHITE;
    }

    if (m_overlay)
    {
        int height;
        bool found = false;
        for (height = my; height > 0; height--)
        {
            unsigned char *buf = &cbuf[mx * (height - 1)];
            for (int x = 0; x < mx; x++)
            {
                if (buf[x] != ' ')
                {
                    found = true;
                    break;
                }
            }

            if (found)
                break;
        }

        if (height > 0)
        {
            height *= m_font->char_height();

            glLoadIdentity();

            ShapeBuffer buff;
            VColour col(100, 100, 100, 100);
            buff.add(sx, sy, ex, sy + height, col);
            buff.draw();
        }
    }

    m_font->render_textblock(sx + ox, sy + oy, cbuf, abuf, mx, my, m_overlay);

    if (idx >= 0)
    {
        cbuf[idx] = char_back;
        abuf[idx] = col_back;
    }
}

CRTRegion::CRTRegion(FTFont *font) : TextRegion(font)
{
}

int CRTRegion::handle_mouse(MouseEvent &event)
{
    if (event.event != MouseEvent::PRESS || event.button != MouseEvent::LEFT)
        return 0;

    return CK_MOUSE_CLICK;
}

MenuRegion::MenuRegion(ImageManager *im, FTFont *entry) :
    m_image(im), m_font_entry(entry), m_mouse_idx(-1),
    m_max_columns(1), m_dirty(false), m_font_buf(entry)
{
    ASSERT(m_image);
    ASSERT(m_font_entry);

    dx = 1;
    dy = 1;

    m_entries.resize(128);

    for (int i = 0; i < TEX_MAX; i++)
        m_tile_buf[i].set_tex(&m_image->m_textures[i]);
}

void MenuRegion::set_num_columns(int columns)
{
    m_max_columns = std::max(1, columns);
}

int MenuRegion::mouse_entry(int x, int y)
{
    if (m_dirty)
        place_entries();

    for (unsigned int i = 0; i < m_entries.size(); i++)
    {
        if (!m_entries[i].valid)
            continue;

        if (x >= m_entries[i].sx && x <= m_entries[i].ex
            && y >= m_entries[i].sy && y <= m_entries[i].ey)
        {
            return i;
        }
    }

    return -1;
}

int MenuRegion::handle_mouse(MouseEvent &event)
{
    m_mouse_idx = -1;

    int x, y;
    if (!mouse_pos(event.px, event.py, x, y))
        return 0;

    if (event.event == MouseEvent::MOVE)
    {
        int old_idx = m_mouse_idx;
        m_mouse_idx = mouse_entry(x, y);
        if (old_idx == m_mouse_idx)
            return 0;

        const VColour mouse_colour(160, 160, 160, 255);

        m_line_buf.clear();
        if (!m_entries[m_mouse_idx].heading && m_entries[m_mouse_idx].key)
        {
            m_line_buf.add_square(m_entries[m_mouse_idx].sx-1,
                                  m_entries[m_mouse_idx].sy,
                                  m_entries[m_mouse_idx].ex+1,
                                  m_entries[m_mouse_idx].ey+1,
                                  mouse_colour);
        }

        return 0;
    }

    if (event.event == MouseEvent::PRESS)
    {
        switch (event.button)
        {
        case MouseEvent::LEFT:
        {
            int entry = mouse_entry(x, y);
            if (entry == -1)
                return 0;
            return m_entries[entry].key;
        }
#if 0
        // TODO enne - these events are wonky on OS X.
        // TODO enne - fix menus so that mouse wheeling doesn't easy exit
        case MouseEvent::SCROLL_UP:
            return CK_UP;
        case MouseEvent::SCROLL_DOWN:
            return CK_DOWN;
#endif
        default:
            return 0;
        }
    }

    return 0;
}

void MenuRegion::place_entries()
{
    m_dirty = false;

    const int heading_indent  = 10;
    const int tile_indent     = 20;
    const int text_indent     = (Options.tile_menu_icons ? 58 : 20);
    const int max_tile_height = (Options.tile_menu_icons ? 32 : 0);
    const int entry_buffer    = 1;
    const VColour selected_colour(50, 50, 10, 255);

    m_font_buf.clear();
    m_shape_buf.clear();
    m_line_buf.clear();
    for (int t = 0; t < TEX_MAX; t++)
        m_tile_buf[t].clear();

    int column = 0;
    if (!Options.tile_menu_icons)
        set_num_columns(1);
    const int max_columns  = std::min(2, m_max_columns);
    const int column_width = mx / max_columns;

    int lines = count_linebreaks(m_more);
    int more_height = (lines + 1) * m_font_entry->char_height();
    m_font_buf.add(m_more, sx + ox, ey - oy - more_height);

    int height = 0;
    int end_height = my - more_height;

    const int max_entry_height = std::max((int)m_font_entry->char_height() * 2,
                                          max_tile_height);

    for (unsigned int i = 0; i < m_entries.size(); i++)
    {
        if (!m_entries[i].valid)
        {
            m_entries[i].sx = 0;
            m_entries[i].sy = 0;
            m_entries[i].ex = 0;
            m_entries[i].ey = 0;
            continue;
        }

        if (height + max_entry_height > end_height && column <= max_columns)
        {
            height = 0;
            column++;
        }

        int text_width  = m_font_entry->string_width(m_entries[i].text);
        int text_height = m_font_entry->char_height();

        if (m_entries[i].heading)
        {
            m_entries[i].sx = heading_indent + column * column_width;
            m_entries[i].ex = m_entries[i].sx + text_width;
            m_entries[i].sy = height;
            m_entries[i].ey = m_entries[i].sy + text_height;

            m_font_buf.add(m_entries[i].text, m_entries[i].sx, m_entries[i].sy);

            height += text_height;
        }
        else
        {
            m_entries[i].sy = height;
            int entry_start = column * column_width;
            int text_sx = text_indent + entry_start;

            int entry_height;

            if (m_entries[i].tiles.size() > 0)
            {
                m_entries[i].sx = entry_start + tile_indent;
                entry_height = std::max(max_tile_height, text_height);

                for (unsigned int t = 0; t < m_entries[i].tiles.size(); t++)
                {
                    // NOTE: This is not perfect. Tiles will be drawn
                    // sorted by texture first, e.g. you can never draw
                    // a dungeon tile over a monster tile.
                    int tile      = m_entries[i].tiles[t].tile;
                    TextureID tex = m_entries[i].tiles[t].tex;
                    m_tile_buf[tex].add_unscaled(tile, m_entries[i].sx,
                                                 m_entries[i].sy,
                                                 m_entries[i].tiles[t].ymax);
//                     m_tile_buf[tex].add(tile, m_entries[i].sx, m_entries[i].sy,
//                                         0, 0, true, TILE_Y);
                }
            }
            else
            {
                m_entries[i].sx = text_sx;
                entry_height = text_height;
            }

            int text_sy = m_entries[i].sy;
            text_sy += (entry_height - m_font_entry->char_height()) / 2;
            // Split menu entries that don't fit into a single line into
            // two lines.
            if (Options.tile_menu_icons
                && text_sx + text_width > entry_start + column_width)
            {
                // [enne] - Ugh, hack.  Maybe MenuEntry could specify the
                // presence and length of this substring?
                std::string unfm = m_entries[i].text.tostring();
                bool let = (unfm[1] >= 'a' && unfm[1] <= 'z'
                            || unfm[1] >= 'A' && unfm[1] <= 'Z');
                bool plus = (unfm[3] == '-' || unfm[3] == '+');

                formatted_string text;
                if (let && plus && unfm[0] == ' ' && unfm[2] == ' '
                    && unfm[4] == ' ')
                {
                    formatted_string header = m_entries[i].text.substr(0, 5);
                    m_font_buf.add(header, text_sx, text_sy);
                    text_sx += m_font_entry->string_width(header);
                    text = m_entries[i].text.substr(5);
                }
                else
                {
                    text += m_entries[i].text;
                }

                int w = entry_start + column_width - text_sx;
                int h = m_font_entry->char_height() * 2;
                formatted_string split = m_font_entry->split(text, w, h);

                int string_height = m_font_entry->string_height(split);
                if (string_height > entry_height)
                    text_sy = m_entries[i].sy;

                m_font_buf.add(split, text_sx, text_sy);

                entry_height = std::max(entry_height, string_height);
                m_entries[i].ex = entry_start + column_width;
            }
            else
            {
                if (max_columns == 1)
                    m_entries[i].ex = text_sx + text_width;
                else
                    m_entries[i].ex = entry_start + column_width;
                m_font_buf.add(m_entries[i].text, text_sx, text_sy);
            }

            m_entries[i].ey = m_entries[i].sy + entry_height;
            height = m_entries[i].ey;
        }

        if (m_entries[i].selected)
        {
            m_shape_buf.add(m_entries[i].sx-1, m_entries[i].sy-1,
                            m_entries[i].ex+1, m_entries[i].ey+1,
                            selected_colour);
        }

        height += entry_buffer;
    }
}

void MenuRegion::render()
{
#ifdef DEBUG_TILES_REDRAW
    cprintf("rendering MenuRegion\n");
#endif
    if (m_dirty)
        place_entries();

    set_transform();
    m_shape_buf.draw();
    m_line_buf.draw();
    for (int i = 0; i < TEX_MAX; i++)
        m_tile_buf[i].draw();
    m_font_buf.draw();
}

void MenuRegion::clear()
{
    m_shape_buf.clear();
    m_line_buf.clear();
    for (int i = 0; i < TEX_MAX; i++)
        m_tile_buf[i].clear();
    m_font_buf.clear();

    m_more.clear();

    for (unsigned int i = 0; i < m_entries.size(); i++)
        m_entries[i].valid = false;

    m_mouse_idx = -1;
}

void MenuRegion::set_entry(int idx, const std::string &str, int colour,
                           const MenuEntry *me)
{
    if (idx >= (int)m_entries.size())
    {
        int new_size = m_entries.size();
        while (idx >= new_size)
            new_size *= 2;
        m_entries.resize(new_size);

        // Quiet valgrind warning about unitialized memory.
        for (int i = idx + 1; i < new_size; i++)
            m_entries[i].valid = false;
    }

    MenuRegionEntry &e = m_entries[idx];
    e.valid = true;
    e.text.clear();
    e.text.textcolor(colour);
    e.text += formatted_string::parse_string(str);

    e.heading  = (me->level == MEL_TITLE || me->level == MEL_SUBTITLE);
    e.selected = me->selected();
    e.key      = me->hotkeys.size() > 0 ? me->hotkeys[0] : 0;
    e.sx = e.sy = e.ex = e.ey = 0;
    e.tiles.clear();
    me->get_tiles(e.tiles);

    m_dirty = true;
}

void MenuRegion::on_resize()
{
    // Probably needed, even though for me nothing went wrong when
    // I commented it out. (jpeg)
    m_dirty = true;
}

int MenuRegion::maxpagesize() const
{
    // TODO enne - this is a conservative guess.
    // It would be better to make menus use a dynamic number of items per page,
    // but it'd require a lot more refactoring of menu.cc to handle that.

    const int lines = count_linebreaks(m_more);
    const int more_height = (lines + 1) * m_font_entry->char_height();

    // Similar to the definition of max_entry_height in place_entries().
    // HACK: Increasing height by 1 to make sure all items actually fit
    //       on the page, though this introduces a few too many empty lines.
    const int div = (Options.tile_menu_icons ? 32
                                             : m_font_entry->char_height() + 1);

    const int pagesize = ((my - more_height) / div) * m_max_columns;

    // Upper limit for inventory menus. (jpeg)
    // Non-inventory menus only have one column and need
    // *really* big screens to cover more than 52 lines.
    if (pagesize > 52)
        return (52);
    return (pagesize);
}

void MenuRegion::set_offset(int lines)
{
    oy = (lines - 1) * m_font_entry->char_height() + 4;
    my = wy - oy;
}

void MenuRegion::set_more(const formatted_string &more)
{
    m_more.clear();
    m_more += more;
#if 0
    // Not needed? (jpeg)
    m_dirty = true;
#endif
}

TitleRegion::TitleRegion(int width, int height) :
    m_buf(&m_img, GL_QUADS)
{
    sx = sy = 0;
    dx = dy = 1;

    if (!m_img.load_texture("title.png", GenericTexture::MIPMAP_NONE))
        return;

    // Center
    wx = width;
    wy = height;
    ox = (wx - m_img.orig_width()) / 2;
    oy = (wy - m_img.orig_height()) / 2;

    {
        PTVert &v = m_buf.get_next();
        v.pos_x = 0;
        v.pos_y = 0;
        v.tex_x = 0;
        v.tex_y = 0;
    }
    {
        PTVert &v = m_buf.get_next();
        v.pos_x = 0;
        v.pos_y = m_img.height();
        v.tex_x = 0;
        v.tex_y = 1;
    }
    {
        PTVert &v = m_buf.get_next();
        v.pos_x = m_img.width();
        v.pos_y = m_img.height();
        v.tex_x = 1;
        v.tex_y = 1;
    }
    {
        PTVert &v = m_buf.get_next();
        v.pos_x = m_img.width();
        v.pos_y = 0;
        v.tex_x = 1;
        v.tex_y = 0;
    }
}

void TitleRegion::render()
{
#ifdef DEBUG_TILES_REDRAW
    cprintf("rendering TitleRegion\n");
#endif
    set_transform();
    m_buf.draw();
}

void TitleRegion::run()
{
    mouse_control mc(MOUSE_MODE_MORE);
    getch();
}

DollEditRegion::DollEditRegion(ImageManager *im, FTFont *font) :
    m_font_buf(font)
{
    sx = sy = 0;
    dx = dy = 32;
    mx = my = 1;

    m_font = font;

    m_doll_idx = 0;
    m_cat_idx = TILEP_PART_BASE;
    m_copy_valid = false;

    m_tile_buf.set_tex(&im->m_textures[TEX_PLAYER]);
    m_cur_buf.set_tex(&im->m_textures[TEX_PLAYER]);
}

void DollEditRegion::clear()
{
    m_shape_buf.clear();
    m_tile_buf.clear();
    m_cur_buf.clear();
    m_font_buf.clear();
}

// FIXME: Very hacky!
// Returns the starting tile for the next species in the tiles list, or the
// shadow tile if it's the last species.
static int _get_next_species_tile()
{
    int sp = you.species;
    if (player_genus(GENPC_DRACONIAN) && you.experience_level < 7)
        sp = SP_BASE_DRACONIAN;

    switch (sp)
    {
    case SP_HUMAN:
        return TILEP_BASE_ELF;
    case SP_HIGH_ELF:
    case SP_SLUDGE_ELF:
        return TILEP_BASE_DEEP_ELF;
    case SP_DEEP_ELF:
        return TILEP_BASE_DWARF;
    case SP_MOUNTAIN_DWARF:
    case SP_HALFLING:
    case SP_HILL_ORC:
    case SP_KOBOLD:
    case SP_MUMMY:
    case SP_NAGA:
    case SP_OGRE:
        return tilep_species_to_base_tile(you.species + 1);
    case SP_TROLL:
        return TILEP_BASE_DRACONIAN;
    case SP_BASE_DRACONIAN:
        return TILEP_BASE_DRACONIAN_BLACK;
    case SP_BLACK_DRACONIAN:
        return TILEP_BASE_DRACONIAN_GOLD;
    case SP_YELLOW_DRACONIAN:
        return TILEP_BASE_DRACONIAN_GREY;
    case SP_GREY_DRACONIAN:
        return TILEP_BASE_DRACONIAN_GREEN;
    case SP_GREEN_DRACONIAN:
        return TILEP_BASE_DRACONIAN_MOTTLED;
    case SP_MOTTLED_DRACONIAN:
        return TILEP_BASE_DRACONIAN_PALE;
    case SP_PALE_DRACONIAN:
        return TILEP_BASE_DRACONIAN_PURPLE;
    case SP_PURPLE_DRACONIAN:
        return TILEP_BASE_DRACONIAN_RED;
    case SP_RED_DRACONIAN:
        return TILEP_BASE_DRACONIAN_WHITE;
    case SP_WHITE_DRACONIAN:
        return TILEP_BASE_CENTAUR;
    case SP_CENTAUR:
    case SP_DEMIGOD:
    case SP_SPRIGGAN:
    case SP_MINOTAUR:
    case SP_DEMONSPAWN:
    case SP_GHOUL:
    case SP_KENKU:
    case SP_MERFOLK:
    case SP_VAMPIRE:
        return tilep_species_to_base_tile(you.species + 1);
    case SP_DEEP_DWARF:
        return TILEP_SHADOW_SHADOW;
    default:
        return TILEP_BASE_HUMAN;
    }
}

static int _get_next_part(int cat, int part, int inc)
{
    // Can't increment or decrement on show equip.
    if (part == TILEP_SHOW_EQUIP)
        return part;

    // Increment max_part by 1 to include the special value of "none".
    // (Except for the base, for which "none" is disallowed.)
    int max_part = tile_player_part_count[cat] + 1;
    int offset   = tile_player_part_start[cat];

    if (cat == TILEP_PART_BASE)
    {
        offset   = tilep_species_to_base_tile(you.species, you.experience_level);
        max_part = _get_next_species_tile() - offset;
    }

    ASSERT(inc > -max_part);

    // Translate the "none" value into something we can do modulo math with.
    if (part == 0)
    {
        part = offset;
        inc--;
    }

    // Valid part numbers are in the range [offset, offset + max_part - 1].
    int ret = (part + max_part + inc - offset) % (max_part);

    if (cat != TILEP_PART_BASE && ret == max_part - 1)
    {
        // "none" value.
        return 0;
    }
    else
    {
        // Otherwise, valid part number.
        return ret + offset;
    }
}

void DollEditRegion::render()
{
#ifdef DEBUG_TILES_REDRAW
    cprintf("rendering DollEditRegion\n");
#endif
    VColour grey(128, 128, 128, 255);

    m_cur_buf.clear();
    m_tile_buf.clear();
    m_shape_buf.clear();
    m_font_buf.clear();

    // Max items to show at once.
    const int max_show = 9;

    // Layout options (units are in 32x32 squares)
    const int left_gutter = 2;
    const int item_line = 2;
    const int edit_doll_line = 5;
    const int doll_line = 8;
    const int info_offset =
        left_gutter + std::max(max_show, (int)NUM_MAX_DOLLS) + 1;

    const int center_x = left_gutter + max_show / 2;

    // Pack current doll separately so it can be drawn repeatedly.
    {
        dolls_data temp = m_dolls[m_doll_idx];
        _fill_doll_equipment(temp);
        pack_doll_buf(m_cur_buf, temp, 0, 0);
    }

    // Draw set of dolls.
    for (int i = 0; i < NUM_MAX_DOLLS; i++)
    {
        int x = left_gutter + i;
        int y = doll_line;

        if (m_mode == TILEP_MODE_LOADING && m_doll_idx == i)
            m_tile_buf.add(TILEP_CURSOR, x, y);

        dolls_data temp = m_dolls[i];
        _fill_doll_equipment(temp);
        pack_doll_buf(m_tile_buf, temp, x, y);

        m_shape_buf.add(x, y, x + 1, y + 1, grey);
    }

    // Draw current category of parts.
    int max_part = tile_player_part_count[m_cat_idx];
    if (m_cat_idx == TILEP_PART_BASE)
        max_part = _get_next_species_tile() - tilep_species_to_base_tile() - 1;

    int show = std::min(max_show, max_part);
    int half_show = show / 2;
    for (int i = -half_show; i <= show - half_show; i++)
    {
        int x = center_x + i;
        int y = item_line;

        if (i == 0)
            m_tile_buf.add(TILEP_CURSOR, x, y);

        int part = _get_next_part(m_cat_idx, m_part_idx, i);
        ASSERT(part != TILEP_SHOW_EQUIP);
        if (part)
            m_tile_buf.add(part, x, y);

        m_shape_buf.add(x, y, x + 1, y + 1, grey);
    }

    m_shape_buf.add(left_gutter, edit_doll_line, left_gutter + 2, edit_doll_line + 2, grey);
    m_shape_buf.add(left_gutter + 3, edit_doll_line, left_gutter + 4, edit_doll_line + 1, grey);
    m_shape_buf.add(left_gutter + 5, edit_doll_line, left_gutter + 6, edit_doll_line + 1, grey);
    m_shape_buf.add(left_gutter + 7, edit_doll_line, left_gutter + 8, edit_doll_line + 1, grey);
    {
        // Describe the three middle tiles.
        float tile_name_x = (left_gutter + 2.7) * 32.0f;
        float tile_name_y = (edit_doll_line + 1) * 32.0f;
        m_font_buf.add("Custom", VColour::white, tile_name_x, tile_name_y);
        tile_name_x = (left_gutter + 4.7) * 32.0f;
        tile_name_y = (edit_doll_line + 1) * 32.0f;
        m_font_buf.add("Default", VColour::white, tile_name_x, tile_name_y);
        tile_name_x = (left_gutter + 7) * 32.0f;
        tile_name_y = (edit_doll_line + 1) * 32.0f;
        m_font_buf.add("Equip", VColour::white, tile_name_x, tile_name_y);
    }


    set_transform();
    m_shape_buf.draw();
    m_tile_buf.draw();

    glLoadIdentity();
    glTranslatef(32 * left_gutter, 32 * edit_doll_line, 0);
    glScalef(64, 64, 1);
    m_cur_buf.draw();

    {
        dolls_data temp;
        temp = m_job_default;
        _fill_doll_equipment(temp);
        pack_doll_buf(m_cur_buf, temp, 2, 0);

        for (unsigned int i = 0; i < TILEP_PART_MAX; ++i)
            temp.parts[i] = TILEP_SHOW_EQUIP;
        _fill_doll_equipment(temp);
        pack_doll_buf(m_cur_buf, temp, 4, 0);

        if (m_mode == TILEP_MODE_LOADING)
            m_cur_buf.add(TILEP_CURSOR, 0, 0);
        else if (m_mode == TILEP_MODE_DEFAULT)
            m_cur_buf.add(TILEP_CURSOR, 2, 0);
        else if (m_mode == TILEP_MODE_EQUIP)
            m_cur_buf.add(TILEP_CURSOR, 4, 0);
    }
    glLoadIdentity();
    glTranslatef(32 * (left_gutter + 3), 32 * edit_doll_line, 0);
    glScalef(32, 32, 1);
    m_cur_buf.draw();

    // Add text.
    const char *part_name = "(none)";
    if (m_part_idx == TILEP_SHOW_EQUIP)
        part_name = "(show equip)";
    else if (m_part_idx)
        part_name = tile_player_name(m_part_idx);

    glLoadIdentity();
    glTranslatef(0, 0, 0);
    glScalef(1, 1, 1);

    std::string item_str = part_name;
    float item_name_x = left_gutter * 32.0f;
    float item_name_y = (item_line + 1) * 32.0f;
    m_font_buf.add(item_str, VColour::white, item_name_x, item_name_y);

    std::string doll_name;
    doll_name = make_stringf("Doll index %d / %d", m_doll_idx, NUM_MAX_DOLLS - 1);
    float doll_name_x = left_gutter * 32.0f;
    float doll_name_y = (doll_line + 1) * 32.0f;
    m_font_buf.add(doll_name, VColour::white, doll_name_x, doll_name_y);

    const char *mode_name[TILEP_MODE_MAX] =
    {
        "Current Equipment",
        "Custom Doll",
        "Job Defaults"
    };
    doll_name = make_stringf("Doll Mode: %s", mode_name[m_mode]);
    doll_name_y += m_font->char_height() * 2.0f;
    m_font_buf.add(doll_name, VColour::white, doll_name_x, doll_name_y);

    // FIXME - this should be generated in rltiles
    const char *cat_name[TILEP_PART_MAX] =
    {
        "Base",
        "Shadow",
        "Halo",
        "Ench",
        "Cloak",
        "Boots",
        "Legs",
        "Body",
        "Gloves",
        "LHand",
        "RHand",
        "Hair",
        "Beard",
        "Helm",
        "DrcWing",
        "DrcHead"
    };

    // Add current doll information:
    std::string info_str;
    float info_x = info_offset * 32.0f;
    float info_y = 0.0f + m_font->char_height();

    for (int i = 0 ; i < TILEP_PART_MAX; i++)
    {
        int part = m_dolls[m_doll_idx].parts[i];
        int disp = part;
        if (disp)
            disp = disp - tile_player_part_start[i] + 1;
        int maxp = tile_player_part_count[i];

        const char *sel = (m_cat_idx == i) ? "->" : "  ";

        if (part == TILEP_SHOW_EQUIP)
            info_str = make_stringf("%2s%9s: (show equip)", sel, cat_name[i]);
        else if (!part)
            info_str = make_stringf("%2s%9s: (none)", sel, cat_name[i]);
        else
            info_str = make_stringf("%2s%9s: %3d/%3d", sel, cat_name[i], disp, maxp);
        m_font_buf.add(info_str, VColour::white, info_x, info_y);
        info_y += m_font->char_height();
    }

    // List the most important commands. (Hopefully the rest will be
    // self-explanatory.)
    {
        const int height = m_font->char_height();
        const float start_y = doll_name_y + height * 3;
        m_font_buf.add("Change parts       left/right              Confirm choice      Enter", VColour::white, 0.0f, start_y);
        m_font_buf.add("Change category    up/down                 Copy doll           Ctrl-C", VColour::white, 0.0f, start_y + height * 1);
        m_font_buf.add("Change doll        0-9, Shift + arrows     Paste copied doll   Ctrl-V", VColour::white, 0.0f, start_y + height * 2);
        m_font_buf.add("Change doll mode   m                       Randomise doll      Ctrl-R", VColour::white, 0.0f, start_y + height * 3);
        m_font_buf.add("Quit menu          q, Escape, Ctrl-S       Toggle equipment    *", VColour::white, 0.0f, start_y + height * 4);
    }

    m_font_buf.draw();
}

int DollEditRegion::handle_mouse(MouseEvent &event)
{
    return 0;
}

void DollEditRegion::run()
{
    // Initialise equipment setting.
    dolls_data equip_doll;
    for (unsigned int i = 0; i < TILEP_PART_MAX; ++i)
         equip_doll.parts[i] = TILEP_SHOW_EQUIP;

    // Initialise job default.
    m_job_default = equip_doll;
    tilep_race_default(you.species, gender,
                       you.experience_level, m_job_default.parts);
    tilep_job_default(you.char_class, gender, m_job_default.parts);

    // Read predefined dolls from file.
    for (unsigned int i = 0; i < NUM_MAX_DOLLS; ++i)
         m_dolls[i] = equip_doll;

    m_mode = TILEP_MODE_LOADING;
    m_doll_idx = -1;

    if (!_load_doll_data("dolls.txt", m_dolls, NUM_MAX_DOLLS, &m_mode, &m_doll_idx))
    {
        m_doll_idx = 0;
    }

    bool update_part_idx = true;

    command_type cmd;
    do
    {
        if (update_part_idx)
        {
            m_part_idx = m_dolls[m_doll_idx].parts[m_cat_idx];
            if (m_part_idx == TILEP_SHOW_EQUIP)
                m_part_idx = 0;
            update_part_idx = false;
        }

        int key = getchm(KMC_DOLL);
        cmd = key_to_command(key, KMC_DOLL);

        switch (cmd)
        {
        case CMD_DOLL_RANDOMIZE:
            _create_random_doll(m_dolls[m_doll_idx]);
            break;
        case CMD_DOLL_SELECT_NEXT_DOLL:
            m_doll_idx = (m_doll_idx + 1) % NUM_MAX_DOLLS;
            update_part_idx = true;
            break;
        case CMD_DOLL_SELECT_PREV_DOLL:
            m_doll_idx = (m_doll_idx + NUM_MAX_DOLLS - 1) % NUM_MAX_DOLLS;
            update_part_idx = true;
            break;
        case CMD_DOLL_SELECT_NEXT_PART:
            m_cat_idx = (m_cat_idx + 1) % TILEP_PART_MAX;
            update_part_idx = true;
            break;
        case CMD_DOLL_SELECT_PREV_PART:
            m_cat_idx = (m_cat_idx + TILEP_PART_MAX - 1) % TILEP_PART_MAX;
            update_part_idx = true;
            break;
        case CMD_DOLL_CHANGE_PART_NEXT:
            m_part_idx = _get_next_part(m_cat_idx, m_part_idx, 1);
            if (m_dolls[m_doll_idx].parts[m_cat_idx] != TILEP_SHOW_EQUIP)
                m_dolls[m_doll_idx].parts[m_cat_idx] = m_part_idx;
            break;
        case CMD_DOLL_CHANGE_PART_PREV:
            m_part_idx = _get_next_part(m_cat_idx, m_part_idx, -1);
            if (m_dolls[m_doll_idx].parts[m_cat_idx] != TILEP_SHOW_EQUIP)
                m_dolls[m_doll_idx].parts[m_cat_idx] = m_part_idx;
            break;
        case CMD_DOLL_CONFIRM_CHOICE:
            m_dolls[m_doll_idx].parts[m_cat_idx] = m_part_idx;
            if (m_mode != TILEP_MODE_LOADING)
                m_mode = TILEP_MODE_LOADING;
            break;
        case CMD_DOLL_COPY:
            m_doll_copy  = m_dolls[m_doll_idx];
            m_copy_valid = true;
            break;
        case CMD_DOLL_PASTE:
            if (m_copy_valid)
                m_dolls[m_doll_idx] = m_doll_copy;
            break;
        case CMD_DOLL_TAKE_OFF:
            m_part_idx = 0;
            m_dolls[m_doll_idx].parts[m_cat_idx] = 0;
            break;
        case CMD_DOLL_TAKE_OFF_ALL:
            for (int i = 0; i < TILEP_PART_MAX; i++)
            {
                switch (i)
                {
                case TILEP_PART_BASE:
                case TILEP_PART_SHADOW:
                case TILEP_PART_HALO:
                case TILEP_PART_ENCH:
                case TILEP_PART_DRCWING:
                case TILEP_PART_DRCHEAD:
                    break;
                default:
                    m_dolls[m_doll_idx].parts[i] = 0;
                };
            }
            break;
        case CMD_DOLL_TOGGLE_EQUIP:
            if (m_dolls[m_doll_idx].parts[m_cat_idx] == TILEP_SHOW_EQUIP)
                m_dolls[m_doll_idx].parts[m_cat_idx] = m_part_idx;
            else
                m_dolls[m_doll_idx].parts[m_cat_idx] = TILEP_SHOW_EQUIP;
            break;
        case CMD_DOLL_TOGGLE_EQUIP_ALL:
            for (int i = 0; i < TILEP_PART_MAX; i++)
                m_dolls[m_doll_idx].parts[i] = TILEP_SHOW_EQUIP;
            break;
        case CMD_DOLL_CLASS_DEFAULT:
            m_dolls[m_doll_idx] = m_job_default;
            break;
        case CMD_DOLL_CHANGE_MODE:
            m_mode = (tile_doll_mode)(((int)m_mode + 1) % TILEP_MODE_MAX);
        default:
            if (key == '0')
                m_doll_idx = 0;
            else if (key >= '1' && key <= '9')
                m_doll_idx = key - '1' + 1;
            ASSERT(m_doll_idx < NUM_MAX_DOLLS);
            break;
        }
    }
    while (cmd != CMD_DOLL_QUIT);

    _save_doll_data(m_mode, m_doll_idx, &m_dolls[0]);

    // Update player with the current doll.
    switch (m_mode)
    {
    case TILEP_MODE_LOADING:
        player_doll = m_dolls[m_doll_idx];
        break;
    case TILEP_MODE_DEFAULT:
        player_doll = m_job_default;
        break;
    default:
    case TILEP_MODE_EQUIP:
        player_doll = equip_doll;
    }
}

ImageManager::ImageManager()
{
}

ImageManager::~ImageManager()
{
    unload_textures();
}

bool ImageManager::load_textures(bool need_mips)
{
    GenericTexture::MipMapOptions mip = need_mips ?
        GenericTexture::MIPMAP_CREATE : GenericTexture::MIPMAP_NONE;
    if (!m_textures[TEX_DUNGEON].load_texture("dngn.png", mip))
        return (false);

    if (!m_textures[TEX_PLAYER].load_texture("player.png", mip))
        return (false);

    if (!m_textures[TEX_GUI].load_texture("gui.png", mip))
        return (false);

    m_textures[TEX_DUNGEON].set_info(TILE_DNGN_MAX, &tile_dngn_info);
    m_textures[TEX_PLAYER].set_info(TILEP_PLAYER_MAX, &tile_player_info);
    m_textures[TEX_GUI].set_info(TILEG_GUI_MAX, &tile_gui_info);

    return (true);
}

static void _copy_onto(unsigned char *pixels, int width,
                       int height, unsigned char *src,
                       const tile_info &inf, bool blend)
{
    unsigned char *dest = &pixels[4 * (inf.sy * width + inf.sx)];

    size_t dest_row_size = width * 4;
    size_t src_row_size = inf.width * 4;

    if (blend)
    {
        for (int r = 0; r < inf.height; r++)
        {
            for (int c = 0; c < inf.width; c++)
            {
                unsigned char a = src[3];
                unsigned char inv_a = 255 - src[3];
                dest[0] = (src[0] * a + dest[0] * inv_a) / 255;
                dest[1] = (src[1] * a + dest[1] * inv_a) / 255;
                dest[2] = (src[2] * a + dest[2] * inv_a) / 255;
                dest[3] = (src[3] * a + dest[3] * inv_a) / 255;

                dest += 4;
                src += 4;
            }
            dest += dest_row_size - src_row_size;
        }
    }
    else
    {
        for (int r = 0; r < inf.height; r++)
        {
            memcpy(dest, src, src_row_size);

            dest += dest_row_size;
            src += src_row_size;
        }
    }
}

// Copy an image at inf from pixels into dest.
static void _copy_into(unsigned char *dest, unsigned char *pixels,
                       int width, int height, const tile_info &inf,
                       int ofs_x = 0, int ofs_y = 0)
{
    unsigned char *src = &pixels[4 * (inf.sy * width + inf.sx)];

    size_t src_row_size = width * 4;
    size_t dest_row_size = inf.width * 4;

    memset(dest, 0, 4 * inf.width * inf.height);

    int total_ofs_x = inf.offset_x + ofs_x;
    int total_ofs_y = inf.offset_y + ofs_y;
    int src_height  = inf.ey - inf.sy;
    int src_width   = inf.ex - inf.sx;

    if (total_ofs_x < 0)
    {
        src_width += total_ofs_x;
        src -= 4 * total_ofs_x;
        total_ofs_x = 0;
    }
    if (total_ofs_y < 0)
    {
        src_height += total_ofs_y;
        src -= 4 * width * total_ofs_y;
        total_ofs_y = 0;
    }

    dest += total_ofs_x * 4 + total_ofs_y * dest_row_size;

    for (int r = 0; r < src_height; r++)
    {
        memcpy(dest, src, src_width * 4);

        dest += dest_row_size;
        src += src_row_size;
    }
}

// Stores "over" on top of "under" in the location of "over".
static bool _copy_under(unsigned char *pixels, int width,
                        int height, int idx_under, int idx_over,
                        int uofs_x = 0, int uofs_y = 0)
{
    const tile_info &under = tile_main_info(idx_under);
    const tile_info &over  = tile_main_info(idx_over);

    if (over.width != under.width || over.height != under.height)
        return (false);

    if (over.ex - over.sx != over.width || over.ey - over.sy != over.height)
        return (false);

    if (over.offset_x != 0 || over.offset_y != 0)
        return (false);

    size_t image_size = under.width * under.height * 4;

    // Make a copy of the original images.
    unsigned char *under_pixels = new unsigned char[image_size];
    _copy_into(under_pixels, pixels, width, height, under, uofs_x, uofs_y);
    unsigned char *over_pixels = new unsigned char[image_size];
    _copy_into(over_pixels, pixels, width, height, over);

    // Replace the over image with the under image
    _copy_onto(pixels, width, height, under_pixels, over, false);
    // Blend the over image over top.
    _copy_onto(pixels, width, height, over_pixels, over, true);

    delete[] under_pixels;
    delete[] over_pixels;

    return (true);
}

static bool _process_item_image(unsigned char *pixels, unsigned int width,
                                unsigned int height)
{
    bool success = true;
    for (int i = 0; i < NUM_POTIONS; i++)
    {
        int special = you.item_description[IDESC_POTIONS][i];
        int tile0 = TILE_POTION_OFFSET + special % 14;
        int tile1 = TILE_POT_HEALING + i;
        success &= _copy_under(pixels, width, height, tile0, tile1);
    }

    for (int i = 0; i < NUM_WANDS; i++)
    {
        int special = you.item_description[IDESC_WANDS][i];
        int tile0 = TILE_WAND_OFFSET + special % 12;
        int tile1 = TILE_WAND_FLAME + i;
        success &= _copy_under(pixels, width, height, tile0, tile1);
    }

    for (int i = 0; i < STAFF_SMITING; i++)
    {
        int special = you.item_description[IDESC_STAVES][i];
        int tile0 = TILE_STAFF_OFFSET + (special / 4) % 10;
        int tile1 = TILE_STAFF_WIZARDRY + i;
        success &= _copy_under(pixels, width, height, tile0, tile1);
    }
    for (int i = STAFF_SMITING; i < NUM_STAVES; i++)
    {
        int special = you.item_description[IDESC_STAVES][i];
        int tile0 = TILE_ROD_OFFSET + (special / 4) % 10;
        int tile1 = TILE_ROD_SMITING + i - STAFF_SMITING;
        success &= _copy_under(pixels, width, height, tile0, tile1);
    }
    for (int i = RING_FIRST_RING; i < NUM_RINGS; i++)
    {
        int special = you.item_description[IDESC_RINGS][i];
        int tile0 = TILE_RING_NORMAL_OFFSET + special % 13;
        int tile1 = TILE_RING_REGENERATION + i - RING_FIRST_RING;
        success &= _copy_under(pixels, width, height, tile0, tile1, -5, -6);
    }
    for (int i = AMU_FIRST_AMULET; i < NUM_JEWELLERY; i++)
    {
        int special = you.item_description[IDESC_RINGS][i];
        int tile0 = TILE_AMU_NORMAL_OFFSET + special % 13;
        int tile1 = TILE_AMU_RAGE + i - AMU_FIRST_AMULET;
        success &= _copy_under(pixels, width, height, tile0, tile1);
    }

    return (true);
}

bool ImageManager::load_item_texture()
{
    // We need to load images in two passes: one for the title and one
    // for the items.  To handle identifiable items, the texture itself
    // is modified.  So, it cannot be loaded until after the item
    // description table has been initialised.
    GenericTexture::MipMapOptions mip = GenericTexture::MIPMAP_CREATE;
    bool success = m_textures[TEX_DEFAULT].load_texture("main.png", mip,
                                                        &_process_item_image);
    m_textures[TEX_DEFAULT].set_info(TILE_MAIN_MAX, tile_main_info);

    return success;
}

void ImageManager::unload_textures()
{
    for (int i = 0; i < TEX_MAX; i++)
        m_textures[i].unload_texture();
}

#endif