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






                                      


                 
                  


                    
                     
                    
                    
                


                    
                     


                    
                 

                     
                     
                      
                      
                      
                     
                     
                    
                   

                     
                    
                    
                    

                     
                  



















































































































                                                                               
                                                   















































                                                                             
                                       
     
                                        


                                                                                          
                                              



                                                      
                                                      

                                      
                                                          






































                                                                                
                                            
















                                                                  
                               












                                                                  
                                                      




















                                                                       






                                                    
                                                                   
         
                                                
 

                                                     

                                                     

 


















                                                                     
                           










                                                                    













                                                                        

                                            
                     
 
                                                               
         
                                                       


                                                       
                                                            



                                                                  
                                                                              

                                                         


                                                                          


                                                                           
                                                               


                                                          
 


                                                                 
 
                                                                          
 
                                                                      







                                                                           






                                                            
                                                                       
 


                                                                                  









                                                                              
                                               

















































                                                                          
                                                  




                               
                                                             






                                                
                                                               






                                                                          
                                           



                                              
                                                                   




                                                       


                                                    
                                 





                                                                                       



























                                                                         
                            
 


                                                   




                                                                              
                                    













                                                             
                                               














                                       
                               
         
                                                   
 
                                     
                             
 














                                                                            
                              



                                            




                                
             
                                                 

                                                         

                              


                                           

                                                    



                                                                   
                                                            




                                                                      
                                                                    
 
                                                    
                              

                              

         
 













                                                        
                
                                                       













                                                              


                                                     
                             




                                                 
                                        






















































































                                                                              
                                                                   
















































































































                                                                                      
                                                                        



                   
                                                                                  











                                            
                          





                                                
                              



                                          
                 
















                                                                                   
                   



                                                                                     
                                      


                                                                                
                                










                                                                    




                                                                 

 





                                                                          


                                                      
 
                                               
                                    








                                                               

                                                               




                                                                      

                                                        
                                                                                   
     
                       

     

                                                 
 

                                                    

                                          
     
                                                  


                            

                                                                           












                                                                     

                            

























                                                                               
                                           



                                              
                                                                   


                                    

                                  







                                                               
                                                              

                              








                                                                      
                                                           

                              














                                                                            
             
                                                          
 


                                  








                                                             
                             




















                                                                           
                                                       





                                                                   
                             
                 
                                                                
                                                 




                                                            















                                                         






                            
                            

                   


                          







                                                                       



                                          
                   
                                                     
                                        



                             
                                                            
                                        


                        

                                                            








                       


























                                                                            

                   


                                                              
 









                                                         
 

                                 
 











                                                          
 



                  
 
 


                                                          
 
                                                                                  
 

                                 
 


                                         
     











                                                               
 

                                  
 

                                              
 
                                                    
     

                                            





                               
 
                                                                               
 



                                                                      
 















                                                                   

















                                                                         
                                        
                                               
                                             



                                                                     
                          

                                                       

     
                   
     
                                                      
     



                  







                                                                        
                                                                            







                                                   

                                                       
         

                                                            














                                                                
                                                      










                                                                        

                                                                    

                   
                                                                         
                                                




                                               
                               


                                                       











                                                                            
                         

                                                
                                      






                                                                       
                                                 
                                 
                                        












                                      
                                                                   






                                                   
                                                                   











                                                                            
                                   


                                              
                                                   

                                        
                                            



                                                                 
                                                                               





                                                                        
                                              




                                         
                                                                         
 
                                        

                                             
                          



                                      
      




                                  
                                              


                                                                       




                

                                    

                  
                                               
 
/*
 *  File:       godabil.cc
 *  Summary:    God-granted abilities.
 */

#include "AppHdr.h"

#include <queue>

#include "beam.h"
#include "cloud.h"
#include "colour.h"
#include "coord.h"
#include "coordit.h"
#include "database.h"
#include "directn.h"
#include "effects.h"
#include "env.h"
#include "files.h"
#include "godabil.h"
#include "invent.h"
#include "itemprop.h"
#include "items.h"
#include "kills.h"
#include "message.h"
#include "misc.h"
#include "mon-act.h"
#include "mon-behv.h"
#include "mon-iter.h"
#include "mon-place.h"
#include "mgen_data.h"
#include "mon-stuff.h"
#include "mon-util.h"
#include "mutation.h"
#include "options.h"
#include "random.h"
#include "religion.h"
#include "shopping.h"
#include "spells1.h"
#include "spells3.h"
#include "spells4.h"
#include "spl-book.h"
#include "spl-util.h"
#include "state.h"
#include "stuff.h"
#include "terrain.h"
#include "view.h"

bool yred_injury_mirror(bool actual)
{
    return (you.religion == GOD_YREDELEMNUL && !player_under_penance()
            && you.piety >= piety_breakpoint(1)
            && (!actual || you.duration[DUR_PRAYER]));
}

bool beogh_water_walk()
{
    return (you.religion == GOD_BEOGH && !player_under_penance()
            && you.piety >= piety_breakpoint(4));
}

bool jiyva_grant_jelly(bool actual)
{
    return (you.religion == GOD_JIYVA && !player_under_penance()
            && you.piety >= piety_breakpoint(2)
            && (!actual || you.duration[DUR_PRAYER]));
}

bool jiyva_remove_bad_mutation()
{
    if (!how_mutated())
    {
        mpr("You have no bad mutations to be cured!");
        return (false);
    }

    // Ensure that only bad mutations are removed.
    if (!delete_mutation(RANDOM_BAD_MUTATION, true, false, true, true))
    {
        canned_msg(MSG_NOTHING_HAPPENS);
        return (false);
    }

    mpr("You feel cleansed.");
    return (true);
}

bool vehumet_supports_spell(spell_type spell)
{
    if (spell_typematch(spell, SPTYP_CONJURATION | SPTYP_SUMMONING))
        return (true);

    if (spell == SPELL_SHATTER
        || spell == SPELL_FRAGMENTATION
        || spell == SPELL_SANDBLAST)
    {
        return (true);
    }

    return (false);
}

// Returns false if the invocation fails (no spellbooks in sight, etc.).
bool trog_burn_spellbooks()
{
    if (you.religion != GOD_TROG)
        return (false);

    god_acting gdact;

    for (stack_iterator si(you.pos()); si; ++si)
    {
        if (si->base_type == OBJ_BOOKS
            && si->sub_type != BOOK_MANUAL
            && si->sub_type != BOOK_DESTRUCTION)
        {
            mpr("Burning your own feet might not be such a smart idea!");
            return (false);
        }
    }

    int totalpiety = 0;

    for (radius_iterator ri(you.pos(), LOS_RADIUS, true, true, true); ri; ++ri)
    {
        // If a grid is blocked, books lying there will be ignored.
        // Allow bombing of monsters.
        const unsigned short cloud = env.cgrid(*ri);
        if (feat_is_solid(grd(*ri))
            || cloud != EMPTY_CLOUD && env.cloud[cloud].type != CLOUD_FIRE)
        {
            continue;
        }

        int count = 0;
        int rarity = 0;
        for (stack_iterator si(*ri); si; ++si)
        {
            if (si->base_type != OBJ_BOOKS
                || si->sub_type == BOOK_MANUAL
                || si->sub_type == BOOK_DESTRUCTION)
            {
                continue;
            }

            // Ignore {!D} inscribed books.
            if (!check_warning_inscriptions(*si, OPER_DESTROY))
            {
                mpr("Won't ignite {!D} inscribed book.");
                continue;
            }

            rarity += book_rarity(si->sub_type);
            // Piety increases by 2 for books never cracked open, else 1.
            // Conversely, rarity influences the duration of the pyre.
            if (!item_type_known(*si))
                totalpiety += 2;
            else
                totalpiety++;

            dprf("Burned book rarity: %d", rarity);
            destroy_item(si.link());
            count++;
        }

        if (count)
        {
            if (cloud != EMPTY_CLOUD)
            {
                // Reinforce the cloud.
                mpr("The fire roars with new energy!");
                const int extra_dur = count + random2(rarity / 2);
                env.cloud[cloud].decay += extra_dur * 5;
                env.cloud[cloud].set_whose(KC_YOU);
                continue;
            }

            const int duration = std::min(4 + count + random2(rarity/2), 23);
            place_cloud(CLOUD_FIRE, *ri, duration, KC_YOU);

            mprf(MSGCH_GOD, "The book%s burst%s into flames.",
                 count == 1 ? ""  : "s",
                 count == 1 ? "s" : "");
        }
    }

    if (!totalpiety)
    {
         mpr("You cannot see a spellbook to ignite!");
         return (false);
    }
    else
    {
         simple_god_message(" is delighted!", GOD_TROG);
         gain_piety(totalpiety);
    }

    return (true);
}

static bool _is_yred_enslaved_soul(const monsters* mon)
{
    return (mon->alive() && mons_enslaved_soul(mon));
}

static bool _yred_enslaved_souls_on_level_disappear()
{
    bool success = false;

    for (monster_iterator mi; mi; ++mi)
    {
        if (_is_yred_enslaved_soul(*mi))
        {
#ifdef DEBUG_DIAGNOSTICS
            mprf(MSGCH_DIAGNOSTICS, "Undead soul disappearing: %s on level %d, branch %d",
                 mi->name(DESC_PLAIN).c_str(),
                 static_cast<int>(you.your_level),
                 static_cast<int>(you.where_are_you));
#endif

            simple_monster_message(*mi, " is freed.");

            // The monster disappears.
            monster_die(*mi, KILL_DISMISSED, NON_MONSTER);

            success = true;
        }
    }

    return (success);
}

static bool _yred_souls_disappear()
{
    return (apply_to_all_dungeons(_yred_enslaved_souls_on_level_disappear));
}

void yred_make_enslaved_soul(monsters *mon, bool force_hostile,
                             bool quiet, bool unrestricted)
{
    if (!unrestricted)
        _yred_souls_disappear();

    const int type = mon->type;
    monster_type soul_type = mons_species(type);
    const std::string whose =
        you.can_see(mon) ? apostrophise(mon->name(DESC_CAP_THE))
                         : mon->pronoun(PRONOUN_CAP_POSSESSIVE);
    const bool twisted =
        !unrestricted ? !x_chance_in_y(you.skills[SK_INVOCATIONS] * 20 / 9 + 20,
                                       100)
                      : false;
    int corps = -1;

    // If the monster's held in a net, get it out.
    mons_clear_trapping_net(mon);

    const monsters orig = *mon;

    if (twisted)
    {
        mon->type = mons_zombie_size(soul_type) == Z_BIG ?
            MONS_ABOMINATION_LARGE : MONS_ABOMINATION_SMALL;
        mon->base_monster = MONS_NO_MONSTER;
    }
    else
    {
        // Drop the monster's corpse, so that it can be properly
        // re-equipped below.
        corps = place_monster_corpse(mon, true, true);
    }

    // Drop the monster's equipment.
    monster_drop_ething(mon);

    // Recreate the monster as an abomination, or as itself before
    // turning it into a spectral thing below.
    define_monster(*mon);

    mon->colour = ETC_UNHOLY;

    mon->flags |= MF_NO_REWARD;
    mon->flags |= MF_ENSLAVED_SOUL;

    if (twisted)
        // Mark abominations as undead.
        mon->flags |= MF_HONORARY_UNDEAD;
    else if (corps != -1)
    {
        // Turn the monster into a spectral thing, minus the usual
        // adjustments for zombified monsters.
        mon->type = MONS_SPECTRAL_THING;
        mon->base_monster = soul_type;

        // Re-equip the spectral thing.
        equip_undead(mon->pos(), corps, mon->mindex(),
                     mon->base_monster);

        // Destroy the monster's corpse, as it's no longer needed.
        destroy_item(corps);
    }

    name_zombie(mon, &orig);

    mons_make_god_gift(mon, GOD_YREDELEMNUL);

    mon->attitude = !force_hostile ? ATT_FRIENDLY : ATT_HOSTILE;
    behaviour_event(mon, ME_ALERT, !force_hostile ? MHITNOT : MHITYOU);

    if (!quiet)
    {
        mprf("%s soul %s, and %s.", whose.c_str(),
             twisted        ? "becomes twisted" : "remains intact",
             !force_hostile ? "is now yours"    : "fights you");
    }
}

bool fedhas_passthrough_class(const monster_type mc)
{
    return (you.religion == GOD_FEDHAS
            && mons_class_is_plant(mc)
            && mons_class_is_stationary(mc));
}

// Fedhas allows worshipers to walk on top of stationary plants and
// fungi.
bool fedhas_passthrough(const monsters * target)
{
    return (target
            && fedhas_passthrough_class(target->type)
            && (target->type != MONS_OKLOB_PLANT
                || target->attitude != ATT_HOSTILE));
}

// Fedhas worshipers can shoot through non-hostile plants, can a
// particular beam go through a particular monster?
bool fedhas_shoot_through(const bolt & beam, const monsters * victim)
{
    actor * originator = beam.agent();
    if (!victim || !originator)
        return (false);

    bool origin_worships_fedhas;
    mon_attitude_type origin_attitude;
    if (originator->atype() == ACT_PLAYER)
    {
        origin_worships_fedhas = you.religion == GOD_FEDHAS;
        origin_attitude = ATT_FRIENDLY;
    }
    else
    {
        monsters * temp = dynamic_cast<monsters *> (originator);
        if (!temp)
            return (false);
        origin_worships_fedhas = temp->god == GOD_FEDHAS;
        origin_attitude = temp->attitude;
    }

    return (origin_worships_fedhas
            && fedhas_protects(victim)
            && !beam.is_enchantment()
            && !(beam.is_explosion && beam.in_explosion_phase)
            && (mons_atts_aligned(victim->attitude, origin_attitude)
                || victim->neutral() ));
}

// Turns corpses in LOS into skeletons and grows toadstools on them.
// Can also turn zombies into skeletons and destroy ghoul-type monsters.
// Returns the number of corpses consumed.
int fungal_bloom()
{
    int seen_mushrooms  = 0;
    int seen_corpses    = 0;

    int processed_count = 0;
    bool kills = false;

    for (radius_iterator i(you.pos(), LOS_RADIUS); i; ++i)
    {
        monsters * target = monster_at(*i);
        if (target && target->is_summoned())
            continue;

        if (target && target->mons_species() != MONS_TOADSTOOL)
        {
            switch (mons_genus(target->mons_species()))
            {
            case MONS_ZOMBIE_SMALL:
                // Maybe turn a zombie into a skeleton.
                if (mons_skeleton(mons_zombie_base(target)))
                {
                    processed_count++;

                    monster_type skele_type = MONS_SKELETON_LARGE;
                    if (mons_zombie_size(mons_zombie_base(target)) == Z_SMALL)
                        skele_type = MONS_SKELETON_SMALL;

                    // Killing and replacing the zombie since upgrade_type
                    // doesn't get skeleton speed right (and doesn't
                    // reduce the victim's HP). This is awkward. -cao
                    mgen_data mg(skele_type, target->behaviour, NULL, 0, 0,
                                 target->pos(),
                                 target->foe,
                                 MG_FORCE_BEH | MG_FORCE_PLACE,
                                 target->god,
                                 mons_zombie_base(target),
                                 target->number);

                    unsigned monster_flags = target->flags;
                    int current_hp = target->hit_points;
                    mon_enchant_list ench = target->enchantments;

                    simple_monster_message(target, "'s flesh rots away.");

                    monster_die(target, KILL_MISC, NON_MONSTER, true);
                    int monster = create_monster(mg);
                    env.mons[monster].flags = monster_flags;
                    env.mons[monster].enchantments = ench;

                    if (env.mons[monster].hit_points > current_hp)
                        env.mons[monster].hit_points = current_hp;

                    behaviour_event(&env.mons[monster], ME_ALERT, MHITYOU);

                    continue;
                }
                // Else fall through and destroy the zombie.
                // Ghoul-type monsters are always destroyed.
            case MONS_GHOUL:
            {
                simple_monster_message(target, " rots away and dies.");

                coord_def pos = target->pos();
                int colour    = target->colour;
                int corpse    = monster_die(target, KILL_MISC, NON_MONSTER, true);
                kills = true;

                // If a corpse didn't drop, create a toadstool.
                // If one did drop, we will create toadstools from it as usual
                // later on.
                if (corpse < 0)
                {
                    const int mushroom = create_monster(
                                mgen_data(MONS_TOADSTOOL,
                                          BEH_FRIENDLY,
                                          &you,
                                          0,
                                          0,
                                          pos,
                                          MHITNOT,
                                          MG_FORCE_PLACE,
                                          GOD_NO_GOD,
                                          MONS_NO_MONSTER,
                                          0,
                                          colour),
                                          false);

                    if (mushroom != -1)
                        seen_mushrooms++;

                    processed_count++;

                    continue;
                }
                break;
            }

            default:
                continue;
            }
        }

        for (stack_iterator j(*i); j; ++j)
        {
            bool corpse_on_pos = false;
            if (j->base_type == OBJ_CORPSES && j->sub_type == CORPSE_BODY)
            {
                corpse_on_pos  = true;
                int trial_prob = mushroom_prob(*j);

                processed_count++;
                int target_count = 1 + binomial_generator(20, trial_prob);

                int seen_per;
                spawn_corpse_mushrooms(*j, target_count, seen_per,
                                       BEH_FRIENDLY, true);

                seen_mushrooms += seen_per;

                // Either turn this corpse into a skeleton or destroy it.
                if (mons_skeleton(j->plus))
                    turn_corpse_into_skeleton(*j);
                else
                    destroy_item(j->index());
            }

            if (corpse_on_pos && you.see_cell(*i))
                seen_corpses++;
        }
    }

    if (seen_mushrooms > 0)
        mushroom_spawn_message(seen_mushrooms, seen_corpses);

    if (kills)
        mprf("That felt like a moral victory.");

    return (processed_count);
}

static int _create_plant(coord_def & target, int hp_adjust = 0)
{
    if (actor_at(target) || !mons_class_can_pass(MONS_PLANT, grd(target)))
        return (0);

    const int plant = create_monster(mgen_data
                                     (MONS_PLANT,
                                      BEH_FRIENDLY,
                                      &you,
                                      0,
                                      0,
                                      target,
                                      MHITNOT,
                                      MG_FORCE_PLACE, GOD_FEDHAS));


    if (plant != -1)
    {
        env.mons[plant].flags |= MF_ATT_CHANGE_ATTEMPT;
        env.mons[plant].max_hit_points += hp_adjust;
        env.mons[plant].hit_points += hp_adjust;

        if (you.see_cell(target))
        {
            if (hp_adjust)
                mpr("A plant grows up from the ground, it is strengthened by Fedhas.");
            else
                mpr("A plant grows up from the ground.");
        }
    }


    return (plant != -1);
}

bool sunlight()
{
    int c_size = 5;
    int x_offset[] = {-1, 0, 0, 0, 1};
    int y_offset[] = { 0,-1, 0, 1, 0};

    dist spelld;

    bolt temp_bolt;

    temp_bolt.colour = YELLOW;
    direction(spelld, DIR_TARGET, TARG_HOSTILE, LOS_RADIUS, false, false,
              false, true, "Select sunlight destination", NULL,
              true);

    if (!spelld.isValid)
        return (false);

    coord_def base = spelld.target;

    int evap_count  = 0;
    int plant_count = 0;
    int processed_count = 0;

    // This is dealt with outside of the main loop.
    int cloud_count = 0;

    // FIXME: Uncomfortable level of code duplication here but the explosion
    // code in bolt subjects the input radius to r*(r+1) for the threshold and
    // since r is an integer we can never get just the 4-connected neighbours.
    // Anyway the bolt code doesn't seem to be well set up to handle the
    // 'occasional plant' gimmick.
    for (int i = 0; i < c_size; ++i)
    {
        coord_def target = base;
        target.x += x_offset[i];
        target.y += y_offset[i];

        if (!in_bounds(target) || feat_is_solid(grd(target)))
            continue;

        temp_bolt.explosion_draw_cell(target);

        actor *victim = actor_at(target);

        // If this is a water square we will evaporate it.
        dungeon_feature_type ftype = grd(target);
        dungeon_feature_type orig_type = ftype;

        switch (ftype)
        {
        case DNGN_SHALLOW_WATER:
            ftype = DNGN_FLOOR;
            break;

        case DNGN_DEEP_WATER:
            ftype = DNGN_SHALLOW_WATER;
            break;

        default:
            break;
        }

        if (orig_type != ftype)
        {
            dungeon_terrain_changed(target, ftype);

            if (you.see_cell(target))
                evap_count++;

            // This is a little awkward but if we evaporated all the way to
            // the dungeon floor that may have given a monster
            // ENCH_AQUATIC_LAND, and if that happened the player should get
            // credit if the monster dies. The enchantment is inflicted via
            // the dungeon_terrain_changed call chain and that doesn't keep
            // track of what caused the terrain change. -cao
            monsters * monster = monster_at(target);
            if (monster && ftype == DNGN_FLOOR
                && monster->has_ench(ENCH_AQUATIC_LAND))
            {
                mon_enchant temp = monster->get_ench(ENCH_AQUATIC_LAND);
                temp.who = KC_YOU;
                monster->add_ench(temp);
            }

            processed_count++;
        }

        monsters *mons = monster_at(target);

        if (victim)
        {
            if (!mons)
                you.backlight();
            else
            {
                backlight_monsters(target, 1, 0);
                behaviour_event(mons, ME_ALERT, MHITYOU);
            }

            processed_count++;
        }
        else if (one_chance_in(100)
                 && ftype >= DNGN_FLOOR_MIN
                 && ftype <= DNGN_FLOOR_MAX
                 && orig_type == DNGN_SHALLOW_WATER)
        {
            // Create a plant.
            const int plant = create_monster(mgen_data(MONS_PLANT,
                                                       BEH_HOSTILE,
                                                       &you,
                                                       0,
                                                       0,
                                                       target,
                                                       MHITNOT,
                                                       MG_FORCE_PLACE,
                                                       GOD_FEDHAS));

            if (plant != -1 && you.see_cell(target))
                plant_count++;

            processed_count++;
        }
    }

    // We damage clousd for a large radius, though.
    for (radius_iterator ai(base, 7); ai; ++ai)
    {
        if (env.cgrid(*ai) != EMPTY_CLOUD)
        {
            const int cloudidx = env.cgrid(*ai);
            if (env.cloud[cloudidx].type == CLOUD_GLOOM)
            {
                cloud_count++;
                delete_cloud(cloudidx);
            }
        }
    }

#ifndef USE_TILE
    // Move the cursor out of the way (it looks weird).
    cgotoxy(base.x, base.y, GOTO_DNGN);
#endif
    delay(200);

    if (plant_count)
    {
        mprf("%s grow%s in the sunlight.",
             (plant_count > 1 ? "Some plants": "A plant"),
             (plant_count > 1 ? "": "s"));
    }

    if (evap_count)
        mprf("Some water evaporates in the bright sunlight.");

    if (cloud_count)
        mprf("Sunlight penetrates the thick gloom.");

    return (processed_count);
}

template<typename T>
bool less_second(const T & left, const T & right)
{
    return (left.second < right.second);
}

typedef std::pair<coord_def, int> point_distance;

// dfs starting at origin, find the distance from the origin to the targets
// (not leaving LOS, not crossing monsters or solid walls) and store that in
// distances
static void _path_distance(coord_def & origin,
                           std::vector<coord_def> & targets,
                           std::vector<int> & distances)
{
    std::set<unsigned> exclusion;
    std::queue<point_distance> fringe;
    fringe.push(point_distance(origin,0));

    int idx = origin.x + origin.y * X_WIDTH;
    exclusion.insert(idx);

    while (!fringe.empty())
    {
        point_distance current = fringe.front();
        fringe.pop();

        // did we hit a target?
        for (unsigned i = 0; i < targets.size(); ++i)
        {
            if (current.first == targets[i])
            {
                distances[i] = current.second;
                break;
            }
        }

        for (adjacent_iterator adj_it(current.first); adj_it; ++adj_it)
        {
            idx = adj_it->x + adj_it->y * X_WIDTH;
            if (you.see_cell(*adj_it)
                && !feat_is_solid(env.grid(*adj_it))
                && exclusion.insert(idx).second)
            {
                monsters * temp = monster_at(*adj_it);
                if (!temp || (temp->attitude == ATT_HOSTILE
                              && temp->mons_species() != MONS_PLANT
                              && temp->mons_species() != MONS_TOADSTOOL
                              && temp->mons_species() != MONS_FUNGUS
                              && temp->mons_species() != MONS_BALLISTOMYCETE))
                {
                    fringe.push(point_distance(*adj_it, current.second+1));
                }
            }
        }
    }
}

// so we are basically going to compute point to point distance between
// the points of origin and the end points (origins and targets respecitvely)
// We will return a vector consisting of the minimum distances along one
// dimension of the distance matrix.
static void _point_point_distance(std::vector<coord_def> & origins,
                                  std::vector<coord_def> & targets,
                                  bool origin_to_target,
                                  std::vector<int> & distances)
{

    distances.clear();
    // Consider a matrix where the points of origin form the rows and
    // the target points form the column, we want to take the minimum along
    // one of those dimensions.
    if (origin_to_target)
        distances.resize(origins.size(), INT_MAX);
    else
        distances.resize(targets.size(), INT_MAX);

    std::vector<int> current_distances(targets.size(), 0);
    for (unsigned i = 0; i < origins.size(); ++i)
    {
        for (unsigned j = 0; j < current_distances.size(); ++j)
            current_distances[j] = INT_MAX;

        _path_distance(origins[i], targets, current_distances);

        // So we got the distance from a point of origin to one of the
        // targets. What should we do with it?
        if (origin_to_target)
        {
            // The minimum of current_distances is points(i)
            int min_dist = current_distances[0];
            for (unsigned j = 1; j < current_distances.size(); ++j)
                if (current_distances[j] < min_dist)
                    min_dist = current_distances[j];

            distances[i] = min_dist;
        }
        else
        {
            for (unsigned j = 0; j < targets.size(); ++j)
            {
                if (i == 0)
                    distances[j] = current_distances[j];
                else if (current_distances[j] < distances[j])
                    distances[j] = current_distances[j];
            }
        }
    }
}

// So the idea is we want to decide which adjacent tiles are in the most 'danger'
// We claim danger is proportional to the minimum distances from the point to a
// (hostile) monster. This function carries out at most 8 depth-first searches
// to calculate the distances in question. In practice it should be called for
// at most 7 searches since 8 (all adjacent free, > 8 monsters in view) can be
// special cased easily.
bool prioritise_adjacent(const coord_def &target, std::vector<coord_def> & candidates)
{
    radius_iterator los_it(target, LOS_RADIUS, true, true, true);

    std::vector<coord_def> mons_positions;
    // collect hostile monster positions in LOS
    for ( ; los_it; ++los_it)
    {
        monsters *hostile = monster_at(*los_it);

        if (hostile && hostile->attitude == ATT_HOSTILE)
            mons_positions.push_back(hostile->pos());
    }

    if (mons_positions.empty())
    {
        std::random_shuffle(candidates.begin(), candidates.end());
        return (true);
    }

    bool squares_to_monsters = mons_positions.size() > candidates.size();

    std::vector<int> distances;

    // So the idea is we will search from either possible plant locations to
    // monsters or from monsters to possible plant locations, but honestly the
    // implementation is unnecessarily tense and doing plants to monsters all
    // the time would be fine. Yet I'm reluctant to change it because it does
    // work.
    if (squares_to_monsters)
    {
        _point_point_distance(candidates, mons_positions,
                              squares_to_monsters, distances);
    }
    else
    {
        _point_point_distance(mons_positions, candidates,
                              squares_to_monsters, distances);
    }

    std::vector<point_distance> possible_moves(candidates.size());

    for (unsigned i = 0; i < possible_moves.size(); ++i)
    {
        possible_moves[i].first  = candidates[i];
        possible_moves[i].second = distances[i];
    }

    std::sort(possible_moves.begin(), possible_moves.end(),
              less_second<point_distance>);

    for (unsigned i = 0; i < candidates.size(); ++i)
        candidates[i] = possible_moves[i].first;

    return (true);
}

// Prompt the user to select a stack of fruit from their inventory.  The
// user can optionally select only a partial stack of fruit (the count
// variable will store the number of fruit the user wants).  Return the
// index of the item selected in the user's inventory, or a negative
// number if the prompt failed (user cancelled or had no fruit).
int _prompt_for_fruit(int & count, const char * prompt_string)
{
    int rc = prompt_invent_item(prompt_string,
                                MT_INVLIST,
                                OSEL_FRUIT,
                                true,
                                true,
                                true,
                                '\0',
                                -1,
                                &count);

    if (prompt_failed(rc))
        return (rc);

    // Return PROMPT_INAPPROPRIATE if the 'object selected isn't
    // actually fruit.
    if (!is_fruit(you.inv[rc]))
        return (PROMPT_INAPPROPRIATE);

    // Handle it if the user lies about the amount of fruit available.
    if (count > you.inv[rc].quantity)
        count = you.inv[rc].quantity;

    return (rc);
}

bool _prompt_amount(int max, int & selected, const std::string & prompt)
{
    selected = max;
    while (true)
    {
        msg::streams(MSGCH_PROMPT) << prompt <<" (" << max << " max)"<< std::endl;

        unsigned char keyin = get_ch();

        // Cancel
        if (keyin == ESCAPE || keyin == ' ')
        {
            canned_msg( MSG_OK );
            return (false);
        }

        // Default is max
        if (keyin == '\n'  || keyin == '\r')
            return (true);

        // Otherwise they should enter a digit
        if (isdigit(keyin))
        {
            selected = keyin - '0';
            if (selected > 0 && selected <= max)
                return (true);
        }
        // else they entered some garbage?
    }

    return (max);
}


int _collect_fruit(std::vector<std::pair<int,int>  > & available_fruit)
{
    int total=0;

    for (int i = 0; i < ENDOFPACK; i++)
    {
        if (you.inv[i].is_valid()
            && is_fruit(you.inv[i]) )
        {
            total += you.inv[i].quantity;
            available_fruit.push_back(std::pair<int,int> (you.inv[i].quantity, i));
        }
    }

    return (total);
}

bool _less_first(const std::pair<int, int> & left, const std::pair<int, int> & right)
{
    return (left.first < right.first);
}
void _decrease_amount(std::vector<std::pair<int, int> > & available, int amount)
{
    int total_decrease = amount;
    for (unsigned i=0; i < available.size() && amount > 0; i++)
    {

        int decrease_amount = available[i].first;
        if (decrease_amount > amount)
        {
            decrease_amount = amount;
        }
        amount -= decrease_amount;
        dec_inv_item_quantity(available[i].second, decrease_amount);
    }
    if (total_decrease > 1)
        mprf("%d pieces of fruit are consumed!", total_decrease);
    else
        mpr("A piece of fruit is consumed!");

}

// Create a ring or partial ring around the caster.  The user is
// prompted to select a stack of fruit, and then plants are placed on open
// squares adjacent to the user.  Of course, one piece of fruit is
// consumed per plant, so a complete ring may not be formed.
bool plant_ring_from_fruit()
{
    // How much fruit is available?
    std::vector<std::pair<int, int> > collected_fruit;
    int total_fruit = _collect_fruit(collected_fruit);

    // How many adjacent open spaces are there?
    std::vector<coord_def> adjacent;
    for (adjacent_iterator adj_it(you.pos()); adj_it; ++adj_it)
    {
        if (mons_class_can_pass(MONS_PLANT, env.grid(*adj_it))
            && !actor_at(*adj_it))
        {
            adjacent.push_back(*adj_it);
        }
    }

    int max_use = std::min(total_fruit, int(adjacent.size()) );

    // Don't prompt if we can't do anything (due to having no fruit or
    // no squares to place plants on).
    if (max_use == 0)
        return (false);

    // And how many plants does the user want to create?
    int target_count;
    if (!_prompt_amount(max_use, target_count, "How many plants will you create?"))
    {
        return (false);
    }

    if ((int)adjacent.size() > target_count)
        prioritise_adjacent(you.pos(), adjacent);

    int hp_adjust = you.skills[SK_INVOCATIONS] * 10;

    int created_count = 0;
    for (int i = 0; i < target_count; ++i)
    {
        if (_create_plant(adjacent[i], hp_adjust))
            created_count++;
    }

    std::sort(collected_fruit.begin(), collected_fruit.end(), _less_first);
    _decrease_amount(collected_fruit, created_count);

    return (created_count);
}

// Create a circle of water around the target, with a radius of
// approximately 2.  This turns normal floor tiles into shallow water
// and turns (unoccupied) shallow water into deep water.  There is a
// chance of spawning plants or fungus on unoccupied dry floor tiles
// outside of the rainfall area.  Return the number of plants/fungi
// created.
int rain(const coord_def &target)
{
    int spawned_count = 0;
    int processed_count = 0;

    for (radius_iterator rad(target, LOS_RADIUS, true, true, true); rad; ++rad)
    {
        // Adjust the shape of the rainfall slightly to make it look
        // nicer.  I want a threshold of 2.5 on the euclidean distance,
        // so a threshold of 6 prior to the sqrt is close enough.
        int rain_thresh = 6;
        coord_def local = *rad - target;

        dungeon_feature_type ftype = grd(*rad);

        if (local.abs() > rain_thresh)
        {
            // Maybe spawn a plant on (dry, open) squares that are in
            // LOS but outside the rainfall area.  In open space, there
            // are 213 squares in LOS, and we are going to drop water on
            // (25-4) of those, so if we want x plants to spawn on
            // average in open space, the trial probability should be
            // x/192.
            if (x_chance_in_y(5, 192)
                && !actor_at(*rad)
                && ftype >= DNGN_FLOOR_MIN
                && ftype <= DNGN_FLOOR_MAX)
            {
                const int plant = create_monster(mgen_data
                                     (coinflip() ? MONS_PLANT : MONS_FUNGUS,
                                      BEH_GOOD_NEUTRAL,
                                      &you,
                                      0,
                                      0,
                                      *rad,
                                      MHITNOT,
                                      MG_FORCE_PLACE, GOD_FEDHAS));

                if (plant != -1)
                    spawned_count++;

                processed_count++;
            }

            continue;
        }

        // Turn regular floor squares only into shallow water.
        if (ftype >= DNGN_FLOOR_MIN && ftype <= DNGN_FLOOR_MAX)
        {
            dungeon_terrain_changed(*rad, DNGN_SHALLOW_WATER);

            processed_count++;
        }
        // We can also turn shallow water into deep water, but we're
        // just going to skip cases where there is something on the
        // shallow water.  Destroying items will probably be annoying,
        // and insta-killing monsters is clearly out of the question.
        else if (!actor_at(*rad)
                 && igrd(*rad) == NON_ITEM
                 && ftype == DNGN_SHALLOW_WATER)
        {
            dungeon_terrain_changed(*rad, DNGN_DEEP_WATER);

            processed_count++;
        }

        if (ftype >= DNGN_MINMOVE)
        {
            // Maybe place a raincloud.
            //
            // The rainfall area is 20 (5*5 - 4 (corners) - 1 (center));
            // the expected number of clouds generated by a fixed chance
            // per tile is 20 * p = expected.  Say an Invocations skill
            // of 27 gives expected 5 clouds.
            int max_expected = 5;
            int expected = div_rand_round(max_expected
                                          * you.skills[SK_INVOCATIONS], 27);

            if (x_chance_in_y(expected, 20))
            {
                place_cloud(CLOUD_RAIN, *rad, 10, KC_YOU);

                processed_count++;
            }
        }
    }

    if (spawned_count > 0)
    {
        mprf("%s grow%s in the rain.",
             (spawned_count > 1 ? "Some plants" : "A plant"),
             (spawned_count > 1 ? "" : "s"));
    }

    return (processed_count);
}

// Destroy corpses in the player's LOS (first corpse on a stack only)
// and make 1 giant spore per corpse.  Spores are given the input as
// their starting behavior; the function returns the number of corpses
// processed.
int corpse_spores(beh_type behavior)
{
    int count = 0;
    for (radius_iterator rad(you.pos(), LOS_RADIUS, true, true, true); rad;
         ++rad)
    {
        for (stack_iterator stack_it(*rad); stack_it; ++stack_it)
        {
            if (stack_it->base_type == OBJ_CORPSES
                && stack_it->sub_type == CORPSE_BODY)
            {
                count++;

                int rc = create_monster(mgen_data(MONS_GIANT_SPORE,
                                                  behavior,
                                                  &you,
                                                  0,
                                                  0,
                                                  *rad,
                                                  MHITNOT,
                                                  MG_FORCE_PLACE));

                if (rc != -1)
                {
                    env.mons[rc].flags |= MF_ATT_CHANGE_ATTEMPT;
                    if (behavior == BEH_FRIENDLY)
                    {
                        env.mons[rc].behaviour = BEH_WANDER;
                        env.mons[rc].foe = MHITNOT;
                    }
                }

                if (mons_skeleton(stack_it->plus))
                    turn_corpse_into_skeleton(*stack_it);
                else
                    destroy_item(stack_it->index());

                break;
            }
        }
    }

    return (count);
}

struct monster_conversion
{
    monster_conversion()
    {
        base_monster = NULL;
        piety_cost = 0;
        fruit_cost = 0;

    }
    monsters * base_monster;
    int piety_cost;
    int fruit_cost;
    monster_type new_type;
};


// Given a monster (which should be a plant/fungus), see if
// evolve_flora() can upgrade it, and set up a monster_conversion
// structure for it.  Return true (and fill in possible_monster) if the
// monster can be upgraded, and return false otherwise.
bool _possible_evolution(monsters * input,
                         monster_conversion & possible_monster)
{
    possible_monster.base_monster = input;
    switch (input->mons_species())
    {
    case MONS_PLANT:
    case MONS_BUSH:
        possible_monster.new_type = MONS_OKLOB_PLANT;
        possible_monster.fruit_cost = 1;
        break;

    case MONS_FUNGUS:
    case MONS_BALLISTOMYCETE:
        possible_monster.new_type = MONS_WANDERING_MUSHROOM;
        possible_monster.piety_cost = 1;
        break;

    case MONS_TOADSTOOL:
        possible_monster.new_type = MONS_WANDERING_MUSHROOM;
        possible_monster.piety_cost = 2;
        break;

    default:
        return (false);
    }

    return (true);
}

void _collect_adjacent_monsters(std::vector<monster_conversion> & available,
                                const coord_def &  center)
{
    for (adjacent_iterator adjacent(center, false); adjacent; ++adjacent)
    {
        monsters * candidate = monster_at(*adjacent);
        monster_conversion monster_upgrade;
        if (candidate && _possible_evolution(candidate, monster_upgrade))
            available.push_back(monster_upgrade);
    }
}

void _cost_summary(int oklob_count, int wandering_count, int total_in_range)
{
    mesclr(true);
    if (oklob_count)
    {
        std::string str = (oklob_count > 1 ? "ts" : "t");
        mprf("Upgrading %d plan%s to oklob plan%s (%d fruit)",
              oklob_count, str.c_str(), str.c_str(), oklob_count);
    }

    if (wandering_count)
        mprf("Upgrading %d fungi to wandering mushroo%s (piety cost)",
             wandering_count, (wandering_count > 1 ? "ms" : "m "));
}

bool evolve_flora()
{
    // Collect adjacent monsters
    std::vector<monster_conversion> available_monsters;
    _collect_adjacent_monsters(available_monsters, you.pos());

    // No monsters in range can be upgraded.
    if (available_monsters.empty() )
    {
        mpr("No flora in range can be evolved.");
        return (false);
    }

    // What are the total costs of all adjacent upgrades?
    int piety_cost = 0;
    int fruit_cost = 0;

    int oklob_generation = 0;
    int wandering_generation = 0;

    for (unsigned i=0; i < available_monsters.size(); i++)
    {
        piety_cost += available_monsters[i].piety_cost;
        fruit_cost += available_monsters[i].fruit_cost;
        switch(available_monsters[i].new_type)
        {
        case MONS_OKLOB_PLANT:
            oklob_generation++;
            break;
        case MONS_WANDERING_MUSHROOM:
            wandering_generation++;
            break;

        default:
            break;
        }
    }


    std::vector<std::pair<int, int> > collected_fruit;
    int total_fruit = _collect_fruit(collected_fruit);
    int useable_fruit = std::min(total_fruit, fruit_cost);

    _cost_summary(useable_fruit, wandering_generation, available_monsters.size());

    crawl_state.darken_range = 1;
    viewwindow(false, false);

    int target_fruit = useable_fruit;
    // Ask the user how many fruit to use
    if (useable_fruit > 1)
    {
        if(!_prompt_amount(useable_fruit, target_fruit,
                           "How many oklobs will you create?"))
        {
            crawl_state.darken_range = -1;
            viewwindow(false,false);
            return (false);
        }
    }
    else
    {
        delay(500);
    }

    crawl_state.darken_range = -1;
    viewwindow(false, false);

    int fruit_used = target_fruit;
    int reduction = fruit_cost - target_fruit;

    if (int(available_monsters.size()) <= reduction)
    {
        mprf("Not enough fruit available.");
        return false;
    }

    int plants_evolved = 0;
    int toadstools_evolved = 0;
    int fungi_evolved = 0;


    std::random_shuffle(available_monsters.begin(), available_monsters.end() );

    for (unsigned i=0; i < available_monsters.size(); i++)
    {
        monsters * current_plant = available_monsters[i].base_monster;
        monster_conversion current_target = available_monsters[i];

        if (current_target.new_type == MONS_OKLOB_PLANT)
        {
            if (target_fruit)
                target_fruit--;
            else
                continue;
        }
        else if (you.piety > current_target.piety_cost)
        {
            lose_piety(current_target.piety_cost);
        }
        // This would wipe out our remaining piety, so don't do it.
        else
        {
            continue;
        }

        switch (current_plant->mons_species())
        {
        case MONS_PLANT:
            plants_evolved++;
            break;

        case MONS_FUNGUS:
        case MONS_BALLISTOMYCETE:
            fungi_evolved++;
            break;

        case MONS_TOADSTOOL:
            toadstools_evolved++;
            break;
        };

        current_plant->upgrade_type(current_target.new_type, true, true);
        current_plant->god = GOD_FEDHAS;
        current_plant->attitude = ATT_FRIENDLY;
        current_plant->flags |= MF_NO_REWARD;
        current_plant->flags |= MF_ATT_CHANGE_ATTEMPT;

        // Try to remove slowly dying in case we are upgrading a
        // toadstool, and spore production in case we are upgrading a
        // ballistomycete.
        current_plant->del_ench(ENCH_SLOWLY_DYING);
        current_plant->del_ench(ENCH_SPORE_PRODUCTION);
    }

    if (fruit_used)
    {
        _decrease_amount(collected_fruit, fruit_used);
    }

    return (true);
}

bool ponderousify_armour()
{
    int item_slot = -1;
    do
    {
        if (item_slot == -1)
        {
            item_slot = prompt_invent_item("Make which item ponderous?",
                            MT_INVLIST, OSEL_PONDER_ARM, true, true, false);
        }

        if (prompt_failed(item_slot))
            return (false);

        item_def& arm(you.inv[item_slot]);

        if (!is_enchantable_armour(arm, true, true)
            || get_armour_ego_type(arm) != SPARM_NORMAL
            || get_armour_slot(arm) != EQ_BODY_ARMOUR)
        {
            mpr("This armour can't be made ponderous. "
                "Choose a different one, or Esc to abort.");
            if (Options.auto_list)
                more();

            item_slot = -1;
            mpr("You can't enchant that."); //does not appear
            continue;
        }

        //make item desc runed if desc was vanilla?

        set_item_ego_type(arm, OBJ_ARMOUR, SPARM_PONDEROUSNESS);

        you.redraw_armour_class = true;
        you.redraw_evasion = true;

        simple_god_message(" says: Use this wisely!");

        return (true);
    }
    while (true);

    return true;
}

static int _slouch_monsters(coord_def where, int pow, int, actor* agent)
{
    monsters* mon = monster_at(where);
    if (mon == NULL || mons_is_stationary(mon) || mon->cannot_move()
        || mons_is_projectile(mon->type))
        return (0);

    int dmg = (mon->speed - 1000/player_movement_speed()/player_speed());
    dmg = (dmg > 0 ? roll_dice(dmg*4, 3)/2 : 0);

    mon->hurt(agent, dmg, BEAM_MMISSILE, true);
    return (1);
}

int cheibriados_slouch(int pow)
{
    return (apply_area_visible(_slouch_monsters, pow));
}

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

static int _lugonu_warp_monster(coord_def where, int pow, int, actor *)
{
    if (!in_bounds(where))
        return (0);

    monsters* mon = monster_at(where);
    if (mon == NULL)
        return (0);

    if (!mon->friendly())
        behaviour_event(mon, ME_ANNOY, MHITYOU);

    if (mon->check_res_magic(pow * 2))
    {
        mprf("%s %s.",
             mon->name(DESC_CAP_THE).c_str(), mons_resist_string(mon));
        return (1);
    }

    const int damage = 1 + random2(pow / 6);
    if (mons_genus(mon->type) == MONS_BLINK_FROG)
        mon->heal(damage, false);
    else if (!mon->check_res_magic(pow))
    {
        mon->hurt(&you, damage);
        if (!mon->alive())
            return (1);
    }

    mon->blink();

    return (1);
}

static void _lugonu_warp_area(int pow)
{
    apply_area_around_square(_lugonu_warp_monster, you.pos(), pow);
}

void lugonu_bends_space()
{
    const int pow = 4 + skill_bump(SK_INVOCATIONS);
    const bool area_warp = random2(pow) > 9;

    mprf("Space bends %saround you!", area_warp ? "sharply " : "");

    if (area_warp)
        _lugonu_warp_area(pow);

    random_blink(false, true);

    const int damage = roll_dice(1, 4);
    ouch(damage, NON_MONSTER, KILLED_BY_WILD_MAGIC, "a spatial distortion");
}

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

void cheibriados_time_bend(int pow)
{
    mpr("The flow of time bends around you.");

    for (adjacent_iterator ai(you.pos()); ai; ++ai)
    {
        monsters* mon = monster_at(*ai);
        if (mon && !mons_is_stationary(mon))
        {
            if (roll_dice(mon->hit_dice, 3) > random2avg(pow, 2))
            {
                mprf("%s %s.",
                     mon->name(DESC_CAP_THE).c_str(), mons_resist_string(mon));
                continue;
            }

            simple_god_message(
                make_stringf(" rebukes %s.",
                             mon->name(DESC_NOCAP_THE).c_str()).c_str(),
                             GOD_CHEIBRIADOS);
            do_slow_monster(mon, KC_YOU);
        }
    }
}

void cheibriados_time_step(int pow) // pow is the number of turns to skip
{
    const coord_def old_pos = you.pos();

    mpr("You step out of the flow of time.");
    flash_view(LIGHTBLUE);
    you.moveto(coord_def(0, 0));
    you.duration[DUR_TIME_STEP] = pow;

    you.time_taken = 10;
    do
    {
        run_environment_effects();
        handle_monsters();
        manage_clouds();
    }
    while (--you.duration[DUR_TIME_STEP] > 0);
    // Update corpses, etc.  This does also shift monsters, but only by
    // a tiny bit.
    update_level(pow * 10);

#ifndef USE_TILE
    delay(1000);
#endif

    you.moveto(old_pos);
    you.duration[DUR_TIME_STEP] = 0;

    flash_view(0);
    mpr("You return to the normal time flow.");
}