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










                                  
                  

                   
                    
                    
                  




                     
                     
                      


                      

                      




                     
                
                  
                 
                  
                     
 

                    



























































































































































                                                                               
                                               








                                                              


                       



                                                                 

                                    
 













                                                                     


                                              




















                                                                       











                                                                        

                                                              












                                                                            




















                                                                       
                                          












                                                                  

                                                        







                                                                
                                   



                                                                        


                                                                     

                                                                    









                                                                     


                                                                     



                                                            
 
                                                                        

                                                                     
                                                                    


                                                                       



                                                                          
                         
         





                                                                   
                                                       

                                                                 
                                              

                                             

                                                                      
                                                                     

                                                  


         

                                                                  

                                                
 



                                                       





                                                                      



                                                                      


                                                                       



                       

                                           

                                                                   
                                                                         












                                                                           





                                                            
                                                                    
                                                            

                     
                                     
















                                                                     
                                   
                                              












                                                           

                                                                         

























                                                                
                                   
                                      
                                               
                                                     
                                         
                                     
                                                       













                                                                 
                                           









































                                                                           
                                                                         




















                                                                          
                                                            













                                                  
                                              



                                  
                                               

                                                      
                                                  
         







                                                         
             
                                                                    


                                                                  
                                                                            




                                                                     
                                              


                        
                                                                           



                                                                  

                                                     
 

                                                                                












                                                                    
                                                             


                                                                             
                                                                 
























                                                                                
                                                            










                                                                    
                                            











































                                                                             

                                           
     
                                               

                     
                                    


                                            
                                           

                     
                                                                          





                          




                                                                  
 













                                                                               

                                                                          



                       
                                                                          



                                                                                
                 




















































                                                                       

                                                     
         


                                                                     
                                                                            


























                                                        
                                             

































                                                            
                                             





































                                                                         
                                                      














                                                  
                                                              







































                                                                                
                                         


                                                         
                                                               

















                                                           
                                                               












                                 

                        
                         
                                                                      























                                                             
                                             

















































                                                                 
                         





















                                                                    
                                                      



















                                                                  
                                                        







                                                                  
                                  
                                  





                                                                    
                          













                                                                                
                                                         



                                                                            
                                                                       









































                                                                              
                                                                           





                                               
                                      
 
                        













                                          






                                                                         
                               


                                             


















                                                                 
                               


                                                                        
                                                                        






                                                              










                                          


                                               

                                                      




                                                         
 


                                                                    
                                                                              
             


                                                     
                 
                                                                                  
                                                                                   

                      
             
 



                                                  
                            
                                               
         

                                                      



                                                         


                                                                    






                                                                 
                                                                             
         



                                                                    


                                          
                                                     

















                                              

                                                                       

                                                                     
                                          





                                                                          
                                                           
                                                                   


                                                
                                                                      
                                                           
                                                          






                                                                   
                                                                             

                                     









                              

                                                                    
                                                                            
 

                                                         
     
               
     
 
                              
 
                                       
                                       
     


                                               
         
                                      
 


         












                                                                      
 

                                          
     


                                               
 










                                                                  

         
 
                                             
                                                                             
 
/*
 *  File:       mon-abil.cc
 *  Summary:    Monster abilities.
 *  Written by: Linley Henzell
 */

#include "AppHdr.h"
#include "mon-abil.h"

#include "externs.h"

#include "arena.h"
#include "beam.h"
#include "colour.h"
#include "coordit.h"
#include "directn.h"
#include "fprop.h"
#include "ghost.h"
#include "misc.h"
#include "mon-act.h"
#include "mon-behv.h"
#include "mon-cast.h"
#include "mon-iter.h"
#include "mon-place.h"
#include "terrain.h"
#include "mgen_data.h"
#include "coord.h"
#include "mon-speak.h"
#include "mon-stuff.h"
#include "random.h"
#include "spl-mis.h"
#include "spl-util.h"
#include "state.h"
#include "stuff.h"
#include "env.h"
#include "areas.h"
#include "view.h"
#include "shout.h"
#include "viewchar.h"

#include <algorithm>

bool ugly_thing_mutate(monsters *ugly, bool proximity)
{
    bool success = false;

    std::string src = "";

    unsigned char mon_colour = BLACK;

    if (!proximity)
        success = true;
    else if (one_chance_in(8))
    {
        int you_mutate_chance = 0;
        int mon_mutate_chance = 0;

        for (adjacent_iterator ri(ugly->pos()); ri; ++ri)
        {
            if (you.pos() == *ri)
                you_mutate_chance = get_contamination_level();
            else
            {
                monsters *ugly_near = monster_at(*ri);

                if (!ugly_near
                    || ugly_near->type != MONS_UGLY_THING
                        && ugly_near->type != MONS_VERY_UGLY_THING)
                {
                    continue;
                }

                for (int i = 0; i < 2; ++i)
                {
                    if (coinflip())
                    {
                        mon_mutate_chance++;

                        if (coinflip())
                        {
                            const int ugly_colour =
                                make_low_colour(ugly->colour);
                            const int ugly_near_colour =
                                make_low_colour(ugly_near->colour);

                            if (ugly_colour != ugly_near_colour)
                                mon_colour = ugly_near_colour;
                        }
                    }

                    if (ugly_near->type != MONS_VERY_UGLY_THING)
                        break;
                }
            }
        }

        you_mutate_chance = std::min(16, you_mutate_chance);
        mon_mutate_chance = std::min(16, mon_mutate_chance);

        if (!one_chance_in(you_mutate_chance + mon_mutate_chance + 1))
        {
            const bool proximity_you =
                (you_mutate_chance  > mon_mutate_chance) ? true :
                (you_mutate_chance == mon_mutate_chance) ? coinflip()
                                                         : false;

            src = proximity_you ? " from you" : " from its kin";

            success = true;
        }
    }

    if (success)
    {
        simple_monster_message(ugly,
            make_stringf(" basks in the mutagenic energy%s and changes!",
                         src.c_str()).c_str());

        ugly->uglything_mutate(mon_colour);

        return (true);
    }

    return (false);
}

// Inflict any enchantments the parent slime has on its offspring,
// leaving durations unchanged, I guess. -cao
static void _split_ench_durations(monsters *initial_slime, monsters *split_off)
{
    mon_enchant_list::iterator i;

    for (i = initial_slime->enchantments.begin();
         i != initial_slime->enchantments.end(); ++i)
    {
        split_off->add_ench(i->second);
    }

}

// What to do about any enchantments these two slimes may have?  For
// now, we are averaging the durations. -cao
static void _merge_ench_durations(monsters *initial_slime, monsters *merge_to)
{
    mon_enchant_list::iterator i;

    int initial_count = initial_slime->number;
    int merge_to_count = merge_to->number;
    int total_count = initial_count + merge_to_count;

    for (i = initial_slime->enchantments.begin();
         i != initial_slime->enchantments.end(); ++i)
    {
        // Does the other slime have this enchantment as well?
        mon_enchant temp = merge_to->get_ench(i->first);
        // If not, use duration 0 for their part of the average.
        int duration = temp.ench == ENCH_NONE ? 0 : temp.duration;

        i->second.duration = (i->second.duration * initial_count
                              + duration * merge_to_count)/total_count;

        if (!i->second.duration)
            i->second.duration = 1;

        merge_to->add_ench(i->second);
    }

    for (i = merge_to->enchantments.begin();
         i != merge_to->enchantments.end(); ++i)
    {
        if (initial_slime->enchantments.find(i->first)
                != initial_slime->enchantments.end()
            && i->second.duration > 1)
        {
            i->second.duration = (merge_to_count * i->second.duration)
                                  / total_count;

            merge_to->update_ench(i->second);
        }
    }
}

// Calculate slime creature hp based on how many are merged.
static void _stats_from_blob_count(monsters *slime, float hp_per_blob)
{
    slime->max_hit_points = (int)(slime->number * hp_per_blob);
    slime->hit_points = slime->max_hit_points;
}

// Create a new slime creature at 'target', and split 'thing''s hp and
// merge count with the new monster.
static bool _do_split(monsters *thing, coord_def & target)
{
    // Create a new slime.
    int slime_idx = create_monster(mgen_data(MONS_SLIME_CREATURE,
                                             thing->behaviour,
                                             0,
                                             0,
                                             0,
                                             target,
                                             thing->foe,
                                             MG_FORCE_PLACE));

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

    monsters *new_slime = &env.mons[slime_idx];

    if (!new_slime)
        return (false);

    // Inflict the new slime with any enchantments on the parent.
    _split_ench_durations(thing, new_slime);
    new_slime->attitude = thing->attitude;
    new_slime->flags = thing->flags;
    new_slime->props = thing->props;
    // XXX copy summoner info

    if (you.can_see(thing))
        mprf("%s splits.", thing->name(DESC_CAP_A).c_str());

    int split_off = thing->number / 2;
    float hp_per_blob = thing->max_hit_points / float(thing->number);

    thing->number -= split_off;
    new_slime->number = split_off;

    new_slime->hit_dice = thing->hit_dice;

    _stats_from_blob_count(thing, hp_per_blob);
    _stats_from_blob_count(new_slime, hp_per_blob);

    if (crawl_state.arena)
        arena_split_monster(thing, new_slime);

    return (true);
}

// Actually merge two slime creature, pooling their hp, etc.
// initial_slime is the one that gets killed off by this process.
static bool _do_merge(monsters *initial_slime, monsters *merge_to)
{
    // Combine enchantment durations.
    _merge_ench_durations(initial_slime, merge_to);

    merge_to->number += initial_slime->number;
    merge_to->max_hit_points += initial_slime->max_hit_points;
    merge_to->hit_points += initial_slime->max_hit_points;

    // Merge monster flags (mostly so that MF_CREATED_NEUTRAL, etc. are
    // passed on if the merged slime subsequently splits.  Hopefully
    // this won't do anything weird.
    merge_to->flags |= initial_slime->flags;

    // Merging costs the combined slime some energy.
    monsterentry* entry = get_monster_data(merge_to->type);

    // We want to find out if merge_to will move next time it has a turn
    // (assuming for the sake of argument the next delay is 10). The
    // purpose of subtracting energy from merge_to is to make it lose a
    // turn after the merge takes place, if it's already going to lose
    // a turn we don't need to do anything.
    merge_to->speed_increment += entry->speed;
    bool can_move = merge_to->has_action_energy();
    merge_to->speed_increment -= entry->speed;

    if(can_move)
    {
        merge_to->speed_increment -= entry->energy_usage.move;

        // This is dumb.  With that said, the idea is that if 2 slimes merge
        // you can gain a space by moving away the turn after (maybe this is
        // too nice but there will probably be a lot of complaints about the
        // damage on higher level slimes).  So we subtracted some energy
        // above, but if merge_to hasn't moved yet this turn, that will just
        // cancel its turn in this round of world_reacts().  So we are going
        // to see if merge_to has gone already by checking its mindex (this
        // works because handle_monsters just iterates over env.mons in
        // ascending order).
        if (initial_slime->mindex() < merge_to->mindex())
            merge_to->speed_increment -= entry->energy_usage.move;
    }

    // Overwrite the state of the slime getting merged into, because it
    // might have been resting or something.
    merge_to->behaviour = initial_slime->behaviour;
    merge_to->foe = initial_slime->foe;

    behaviour_event(merge_to, ME_EVAL);

    // Messaging.
    if (you.can_see(merge_to))
    {
        if (you.can_see(initial_slime))
        {
            mprf("Two slime creatures merge to form %s.",
                 merge_to->name(DESC_NOCAP_A).c_str());
        }
        else
        {
            mprf("A slime creature suddenly becomes %s.",
                 merge_to->name(DESC_NOCAP_A).c_str());
        }

        flash_view_delay(LIGHTGREEN, 150);
    }
    else if (you.can_see(initial_slime))
        mpr("A slime creature suddenly disappears!");

    // Have to 'kill' the slime doing the merging.
    monster_die(initial_slime, KILL_MISC, NON_MONSTER, true);

    return (true);
}

// Slime creatures can split but not merge under these conditions.
static bool _unoccupied_slime(monsters *thing)
{
     return (thing->asleep() || mons_is_wandering(thing)
             || thing->foe == MHITNOT);
}

// Slime creatures cannot split or merge under these conditions.
static bool _disabled_slime(monsters *thing)
{
    return (!thing
            || mons_is_fleeing(thing)
            || mons_is_confused(thing)
            || thing->paralysed());
}

// See if there are any appropriate adjacent slime creatures for 'thing'
// to merge with.  If so, carry out the merge.
//
// A slime creature will merge if there is an adjacent slime, merging
// onto that slime would reduce the distance to the original slime's
// target, and there are no empty squares that would also reduce the
// distance to the target.
static bool _slime_merge(monsters *thing)
{
    if (!thing || _disabled_slime(thing) || _unoccupied_slime(thing))
        return (false);

    int max_slime_merge = 5;
    int compass_idx[8] = {0, 1, 2, 3, 4, 5, 6, 7};
    std::random_shuffle(compass_idx, compass_idx + 8);
    coord_def origin = thing->pos();

    int target_distance = grid_distance(thing->target, thing->pos());

    monsters * merge_target = NULL;
    // Check for adjacent slime creatures.
    for (int i = 0; i < 8; ++i)
    {
        coord_def target = origin + Compass[compass_idx[i]];

        // If this square won't reduce the distance to our target, don't
        // look for a potential merge, and don't allow this square to
        // prevent a merge if empty.
        if (grid_distance(thing->target, target) >= target_distance)
            continue;

        // Don't merge if there is an open square that reduces distance
        // to target, even if we found a possible slime to merge with.
        if (!actor_at(target)
            && mons_class_can_pass(MONS_SLIME_CREATURE, env.grid(target)))
        {
            return false;
        }

        // Is there a slime creature on this square we can consider
        // merging with?
        monsters *other_thing = monster_at(target);
        if (!merge_target
            && other_thing
            && other_thing->type == MONS_SLIME_CREATURE
            && other_thing->attitude == thing->attitude
            && other_thing->is_summoned() == thing->is_summoned()
            && !other_thing->is_shapeshifter()
            && !_disabled_slime(other_thing))
        {
            // We can potentially merge if doing so won't take us over
            // the merge cap.
            int new_blob_count = other_thing->number + thing->number;
            if (new_blob_count <= max_slime_merge)
                merge_target = other_thing;
        }
    }

    // We found a merge target and didn't find an open square that
    // would reduce distance to target, so we can actually merge.
    if (merge_target)
        return (_do_merge(thing, merge_target));

    // No adjacent slime creatures we could merge with.
    return (false);
}

static bool _slime_can_spawn(const coord_def target)
{
    return (mons_class_can_pass(MONS_SLIME_CREATURE, env.grid(target))
            && !actor_at(target));
}

// See if slime creature 'thing' can split, and carry out the split if
// we can find a square to place the new slime creature on.
static bool _slime_split(monsters *thing)
{
    if (!thing || thing->number <= 1
        || coinflip() // Don't make splitting quite so reliable. (jpeg)
        || _disabled_slime(thing))
    {
        return (false);
    }

    const coord_def origin  = thing->pos();

    const actor* foe        = thing->get_foe();
    const bool has_foe      = (foe != NULL && thing->can_see(foe));
    const coord_def foe_pos = (has_foe ? foe->position : coord_def(0,0));
    const int old_dist      = (has_foe ? distance(origin, foe_pos) : 0);

    if (has_foe && old_dist > 1)
    {
        // If we're not already adjacent to the foe, check whether we can
        // move any closer. If so, do that rather than splitting.
        for (radius_iterator ri(origin, 1, true, false, true); ri; ++ri)
            if (_slime_can_spawn(*ri) && distance(*ri, foe_pos) < old_dist)
                return (false);
    }

    int compass_idx[] = {0, 1, 2, 3, 4, 5, 6, 7};
    std::random_shuffle(compass_idx, compass_idx + 8);

    // Anywhere we can place an offspring?
    for (int i = 0; i < 8; ++i)
    {
        coord_def target = origin + Compass[compass_idx[i]];

        // Don't split if this increases the distance to the target.
        if (has_foe && distance(target, foe_pos) > old_dist)
            continue;

        if (_slime_can_spawn(target))
        {
            // This can fail if placing a new monster fails.  That
            // probably means we have too many monsters on the level,
            // so just return in that case.
            return (_do_split(thing, target));
        }
    }

   // No free squares.
   return (false);
}

// See if a given slime creature can split or merge.
bool slime_split_merge(monsters *thing)
{
    // No merging/splitting shapeshifters.
    if (!thing
        || thing->is_shapeshifter()
        || thing->type != MONS_SLIME_CREATURE)
    {
        return (false);
    }

    if (_slime_split(thing))
        return (true);

    return (_slime_merge(thing));
}

// Returns true if you resist the siren's call.
static bool _siren_movement_effect(const monsters *monster)
{
    bool do_resist = (you.attribute[ATTR_HELD] || you.check_res_magic(70)
                      || you.cannot_act() || you.asleep());

    if (!do_resist)
    {
        coord_def dir(coord_def(0,0));
        if (monster->pos().x < you.pos().x)
            dir.x = -1;
        else if (monster->pos().x > you.pos().x)
            dir.x = 1;
        if (monster->pos().y < you.pos().y)
            dir.y = -1;
        else if (monster->pos().y > you.pos().y)
            dir.y = 1;

        const coord_def newpos = you.pos() + dir;

        if (!in_bounds(newpos) || is_feat_dangerous(grd(newpos))
            || !you.can_pass_through_feat(grd(newpos)))
        {
            do_resist = true;
        }
        else
        {
            bool swapping = false;
            monsters *mon = monster_at(newpos);
            if (mon)
            {
                coord_def swapdest;
                if (mon->wont_attack()
                    && !mons_is_stationary(mon)
                    && !mons_is_projectile(mon->type)
                    && !mon->cannot_act()
                    && !mon->asleep()
                    && swap_check(mon, swapdest, true))
                {
                    swapping = true;
                }
                else if (!mon->submerged())
                    do_resist = true;
            }

            if (!do_resist)
            {
                const coord_def oldpos = you.pos();
                mprf("The pull of her song draws you forwards.");

                if (swapping)
                {
                    if (monster_at(oldpos))
                    {
                        mprf("Something prevents you from swapping places "
                             "with %s.",
                             mon->name(DESC_NOCAP_THE).c_str());
                        return (do_resist);
                    }

                    int swap_mon = mgrd(newpos);
                    // Pick the monster up.
                    mgrd(newpos) = NON_MONSTER;
                    mon->moveto(oldpos);

                    // Plunk it down.
                    mgrd(mon->pos()) = swap_mon;

                    mprf("You swap places with %s.",
                         mon->name(DESC_NOCAP_THE).c_str());
                }
                move_player_to_grid(newpos, true, true, true);

                if (swapping)
                    mon->apply_location_effects(newpos);
            }
        }
    }

    return (do_resist);
}

static bool _silver_statue_effects(monsters *mons)
{
    actor *foe = mons->get_foe();
    if (foe && mons->can_see(foe) && !one_chance_in(3))
    {
        const std::string msg =
            "'s eyes glow " + weird_glowing_colour() + '.';
        simple_monster_message(mons, msg.c_str(), MSGCH_WARN);

        create_monster(
            mgen_data(
                summon_any_demon((coinflip() ? DEMON_COMMON
                                             : DEMON_LESSER)),
                SAME_ATTITUDE(mons), mons, 5, 0, foe->pos(), mons->foe));
        return (true);
    }
    return (false);
}

static bool _orange_statue_effects(monsters *mons)
{
    actor *foe = mons->get_foe();
    if (foe && mons->can_see(foe) && !one_chance_in(3))
    {
        if (you.can_see(foe))
        {
            if (foe == &you)
                mprf(MSGCH_WARN, "A hostile presence attacks your mind!");
            else if (you.can_see(mons))
                mprf(MSGCH_WARN, "%s fixes %s piercing gaze on %s.",
                     mons->name(DESC_CAP_THE).c_str(),
                     mons->pronoun(PRONOUN_NOCAP_POSSESSIVE).c_str(),
                     foe->name(DESC_NOCAP_THE).c_str());
        }

        MiscastEffect(foe, mons->mindex(), SPTYP_DIVINATION,
                      random2(15), random2(150),
                      "an orange crystal statue");
        return (true);
    }

    return (false);
}

static bool _orc_battle_cry(monsters *chief)
{
    const actor *foe = chief->get_foe();
    int affected = 0;

    if (foe
        && (foe != &you || !chief->friendly())
        && !silenced(chief->pos())
        && chief->can_see(foe)
        && coinflip())
    {
        const int boss_index = chief->mindex();
        const int level = chief->hit_dice > 12? 2 : 1;
        std::vector<monsters*> seen_affected;
        for (monster_iterator mi(chief); mi; ++mi)
        {
            if (*mi != chief
                && mons_species(mi->type) == MONS_ORC
                && mons_aligned(boss_index, mi->mindex())
                && mi->hit_dice < chief->hit_dice
                && !mi->berserk()
                && !mi->has_ench(ENCH_MIGHT)
                && !mi->cannot_move()
                && !mi->confused())
            {
                mon_enchant ench = mi->get_ench(ENCH_BATTLE_FRENZY);
                if (ench.ench == ENCH_NONE || ench.degree < level)
                {
                    const int dur =
                        random_range(12, 20) * speed_to_duration(mi->speed);

                    if (ench.ench != ENCH_NONE)
                    {
                        ench.degree   = level;
                        ench.duration = std::max(ench.duration, dur);
                        mi->update_ench(ench);
                    }
                    else
                    {
                        mi->add_ench(mon_enchant(ENCH_BATTLE_FRENZY, level,
                                                  KC_OTHER, dur));
                    }

                    affected++;
                    if (you.can_see(*mi))
                        seen_affected.push_back(*mi);

                    if (mi->asleep())
                        behaviour_event(*mi, ME_DISTURB, MHITNOT, chief->pos());
                }
            }
        }

        if (affected)
        {
            if (you.can_see(chief) && player_can_hear(chief->pos()))
            {
                mprf(MSGCH_SOUND, "%s roars a battle-cry!",
                     chief->name(DESC_CAP_THE).c_str());
            }

            // The yell happens whether you happen to see it or not.
            noisy(LOS_RADIUS, chief->pos(), chief->mindex());

            // Disabling detailed frenzy announcement because it's so spammy.
            const msg_channel_type channel =
                        chief->friendly() ? MSGCH_MONSTER_ENCHANT
                                                  : MSGCH_FRIEND_ENCHANT;

            if (!seen_affected.empty())
            {
                std::string who;
                if (seen_affected.size() == 1)
                {
                    who = seen_affected[0]->name(DESC_CAP_THE);
                    mprf(channel, "%s goes into a battle-frenzy!", who.c_str());
                }
                else
                {
                    int type = seen_affected[0]->type;
                    for (unsigned int i = 0; i < seen_affected.size(); i++)
                    {
                        if (seen_affected[i]->type != type)
                        {
                            // just mention plain orcs
                            type = MONS_ORC;
                            break;
                        }
                    }
                    who = get_monster_data(type)->name;

                    mprf(channel, "%s %s go into a battle-frenzy!",
                         chief->friendly() ? "Your" : "The",
                         pluralise(who).c_str());
                }
            }
        }
    }
    // Orc battle cry doesn't cost the monster an action.
    return (false);
}

static bool _make_monster_angry(const monsters *mon, monsters *targ)
{
    if (mon->friendly() != targ->friendly())
        return (false);

    // targ is guaranteed to have a foe (needs_berserk checks this).
    // Now targ needs to be closer to *its* foe than mon is (otherwise
    // mon might be in the way).

    coord_def victim;
    if (targ->foe == MHITYOU)
        victim = you.pos();
    else if (targ->foe != MHITNOT)
    {
        const monsters *vmons = &menv[targ->foe];
        if (!vmons->alive())
            return (false);
        victim = vmons->pos();
    }
    else
    {
        // Should be impossible. needs_berserk should find this case.
        ASSERT(false);
        return (false);
    }

    // If mon may be blocking targ from its victim, don't try.
    if (victim.distance_from(targ->pos()) > victim.distance_from(mon->pos()))
        return (false);

    if (you.can_see(mon))
    {
        mprf("%s goads %s on!", mon->name(DESC_CAP_THE).c_str(),
             targ->name(DESC_NOCAP_THE).c_str());
    }

    targ->go_berserk(false);

    return (true);
}

static bool _moth_incite_monsters(const monsters *mon)
{
    if (is_sanctuary(you.pos()) || is_sanctuary(mon->pos()))
        return false;

    int goaded = 0;
    circle_def c(mon->pos(), 3, C_SQUARE);
    for (monster_iterator mi(&c); mi; ++mi)
    {
        if (*mi == mon || !mi->needs_berserk())
            continue;

        if (is_sanctuary(mi->pos()))
            continue;

        // Cannot goad other moths of wrath!
        if (mi->type == MONS_MOTH_OF_WRATH)
            continue;

        if (_make_monster_angry(mon, *mi) && !one_chance_in(3 * ++goaded))
            return (true);
    }

    return (false);
}

static inline void _mons_cast_abil(monsters *monster, bolt &pbolt,
                                   spell_type spell_cast)
{
    mons_cast(monster, pbolt, spell_cast, true, true);
}

//---------------------------------------------------------------
//
// mon_special_ability
//
//---------------------------------------------------------------
bool mon_special_ability(monsters *monster, bolt & beem)
{
    bool used = false;

    const monster_type mclass = (mons_genus( monster->type ) == MONS_DRACONIAN)
                                  ? draco_subspecies( monster )
                                  : static_cast<monster_type>( monster->type );

    // Slime creatures can split while out of sight.
    if ((!mons_near(monster) || monster->asleep() || monster->submerged())
        && monster->type != MONS_SLIME_CREATURE)
    {
        return (false);
    }

    const msg_channel_type spl = (monster->friendly() ? MSGCH_FRIEND_SPELL
                                                         : MSGCH_MONSTER_SPELL);

    spell_type spell = SPELL_NO_SPELL;

    circle_def c;
    switch (mclass)
    {
    case MONS_UGLY_THING:
    case MONS_VERY_UGLY_THING:
        // A (very) ugly thing's proximity to you if you're glowing, or
        // to others of its kind, can mutate it into a different (very)
        // ugly thing.
        used = ugly_thing_mutate(monster, true);
        break;

    case MONS_SLIME_CREATURE:
        // Slime creatures may split or merge depending on the
        // situation.
        used = slime_split_merge(monster);
        if (!monster->alive())
            return (true);
        break;

    case MONS_ORC_KNIGHT:
    case MONS_ORC_WARLORD:
    case MONS_SAINT_ROKA:
        if (is_sanctuary(monster->pos()))
            break;

        used = _orc_battle_cry(monster);
        break;

    case MONS_ORANGE_STATUE:
        if (player_or_mon_in_sanct(monster))
            break;

        used = _orange_statue_effects(monster);
        break;

    case MONS_SILVER_STATUE:
        if (player_or_mon_in_sanct(monster))
            break;

        used = _silver_statue_effects(monster);
        break;

    case MONS_BALL_LIGHTNING:
        if (is_sanctuary(monster->pos()))
            break;

        if (monster->attitude == ATT_HOSTILE
            && distance(you.pos(), monster->pos()) <= 5)
        {
            monster->hit_points = -1;
            used = true;
            break;
        }

        c = circle_def(monster->pos(), 4, C_CIRCLE);
        for (monster_iterator targ(&c); targ; ++targ)
        {
            if (mons_atts_aligned(monster->attitude, targ->attitude))
                continue;

            if (monster->can_see(*targ) && !feat_is_solid(grd(targ->pos())))
            {
                monster->hit_points = -1;
                used = true;
                break;
            }
        }
        break;

    case MONS_LAVA_SNAKE:
        if (monster->has_ench(ENCH_CONFUSION))
            break;

        if (!you.visible_to(monster))
            break;

        if (coinflip())
            break;

        // Setup tracer.
        beem.name        = "glob of lava";
        beem.aux_source  = "glob of lava";
        beem.range       = 6;
        beem.damage      = dice_def(3, 10);
        beem.hit         = 20;
        beem.colour      = RED;
        beem.type        = dchar_glyph(DCHAR_FIRED_ZAP);
        beem.flavour     = BEAM_LAVA;
        beem.beam_source = monster->mindex();
        beem.thrower     = KILL_MON;

        // Fire tracer.
        fire_tracer(monster, beem);

        // Good idea?
        if (mons_should_fire(beem))
        {
            make_mons_stop_fleeing(monster);
            simple_monster_message(monster, " spits lava!");
            beem.fire();
            used = true;
        }
        break;

    case MONS_ELECTRIC_EEL:
        if (monster->has_ench(ENCH_CONFUSION))
            break;

        if (!you.visible_to(monster))
            break;

        if (coinflip())
            break;

        // Setup tracer.
        beem.name        = "bolt of electricity";
        beem.aux_source  = "bolt of electricity";
        beem.range       = 8;
        beem.damage      = dice_def( 3, 6 );
        beem.hit         = 50;
        beem.colour      = LIGHTCYAN;
        beem.type        = dchar_glyph(DCHAR_FIRED_ZAP);
        beem.flavour     = BEAM_ELECTRICITY;
        beem.beam_source = monster->mindex();
        beem.thrower     = KILL_MON;
        beem.is_beam     = true;

        // Fire tracer.
        fire_tracer(monster, beem);

        // Good idea?
        if (mons_should_fire(beem))
        {
            make_mons_stop_fleeing(monster);
            simple_monster_message(monster,
                                   " shoots out a bolt of electricity!");
            beem.fire();
            used = true;
        }
        break;

    case MONS_ACID_BLOB:
    case MONS_OKLOB_PLANT:
    case MONS_YELLOW_DRACONIAN:
        if (monster->has_ench(ENCH_CONFUSION))
            break;

        if (player_or_mon_in_sanct(monster))
            break;

        if (one_chance_in(3))
        {
            spell = SPELL_ACID_SPLASH;
            setup_mons_cast(monster, beem, spell);

            // Fire tracer.
            fire_tracer(monster, beem);

            // Good idea?
            if (mons_should_fire(beem))
            {
                make_mons_stop_fleeing(monster);
                _mons_cast_abil(monster, beem, spell);
                used = true;
            }
        }
        break;

    case MONS_MOTH_OF_WRATH:
        if (one_chance_in(3))
            used = _moth_incite_monsters(monster);
        break;

    case MONS_SNORG:
        if (monster->has_ench(ENCH_CONFUSION))
            break;

        if (monster->foe == MHITNOT
            || monster->foe == MHITYOU && monster->friendly())
        {
            break;
        }

        // There's a 5% chance of Snorg spontaneously going berserk that
        // increases to 20% once he is wounded.
        if (monster->hit_points == monster->max_hit_points && !one_chance_in(4))
            break;

        if (one_chance_in(5))
            monster->go_berserk(true);
        break;

    case MONS_PIT_FIEND:
        if (one_chance_in(3))
            break;
        // deliberate fall through
    case MONS_FIEND:
        if (monster->has_ench(ENCH_CONFUSION))
            break;

        if (player_or_mon_in_sanct(monster))
            break;

        // Friendly fiends won't use torment, preferring hellfire
        // (right now there is no way a monster can predict how
        // badly they'll damage the player with torment) -- GDL

        // Well, I guess you could allow it if the player is torment
        // resistant, but there's a very good reason torment resistant
        // players can't cast Torment themselves, and allowing your
        // allies to cast it would just introduce harmless Torment
        // through the backdoor. Thus, shouldn't happen. (jpeg)
        if (one_chance_in(4))
        {
            spell_type spell_cast = SPELL_NO_SPELL;

            switch (random2(4))
            {
            case 0:
                if (!monster->friendly())
                {
                    make_mons_stop_fleeing(monster);
                    spell_cast = SPELL_SYMBOL_OF_TORMENT;
                    _mons_cast_abil(monster, beem, spell_cast);
                    used = true;
                    break;
                }
                // deliberate fallthrough -- see above
            case 1:
            case 2:
            case 3:
                spell_cast = SPELL_HELLFIRE;
                setup_mons_cast(monster, beem, spell_cast);

                // Fire tracer.
                fire_tracer(monster, beem);

                // Good idea?
                if (mons_should_fire(beem))
                {
                    make_mons_stop_fleeing(monster);

                    _mons_cast_abil(monster, beem, spell_cast);
                    used = true;
                }
                break;
            }
        }
        break;

    case MONS_IMP:
    case MONS_PHANTOM:
    case MONS_INSUBSTANTIAL_WISP:
    case MONS_BLINK_FROG:
    case MONS_KILLER_KLOWN:
    case MONS_PRINCE_RIBBIT:
    case MONS_MARA:
    case MONS_MARA_FAKE:
    case MONS_GOLDEN_EYE:
        if (one_chance_in(7) || monster->caught() && one_chance_in(3))
            used = monster_blink(monster);
        break;

    case MONS_MANTICORE:
        if (monster->has_ench(ENCH_CONFUSION))
            break;

        if (!you.visible_to(monster))
            break;

        // The fewer spikes the manticore has left, the less
        // likely it will use them.
        if (random2(16) >= static_cast<int>(monster->number))
            break;

        // Do the throwing right here, since the beam is so
        // easy to set up and doesn't involve inventory.

        // Set up the beam.
        beem.name        = "volley of spikes";
        beem.aux_source  = "volley of spikes";
        beem.range       = 6;
        beem.hit         = 14;
        beem.damage      = dice_def( 2, 10 );
        beem.beam_source = monster->mindex();
        beem.type        = dchar_glyph(DCHAR_FIRED_MISSILE);
        beem.colour      = LIGHTGREY;
        beem.flavour     = BEAM_MISSILE;
        beem.thrower     = KILL_MON;
        beem.is_beam     = false;

        // Fire tracer.
        fire_tracer(monster, beem);

        // Good idea?
        if (mons_should_fire(beem))
        {
            make_mons_stop_fleeing(monster);
            simple_monster_message(monster, " flicks its tail!");
            beem.fire();
            used = true;
            // Decrement # of volleys left.
            monster->number--;
        }
        break;

    case MONS_PLAYER_GHOST:
    {
        const ghost_demon &ghost = *(monster->ghost);

        if (ghost.species < SP_RED_DRACONIAN
            || ghost.species == SP_GREY_DRACONIAN
            || ghost.species >= SP_BASE_DRACONIAN
            || ghost.xl < 7
            || one_chance_in(ghost.xl - 5))
        {
            break;
        }
    }
    // Intentional fallthrough

    case MONS_WHITE_DRACONIAN:
    case MONS_RED_DRACONIAN:
        spell = SPELL_DRACONIAN_BREATH;
    // Intentional fallthrough

    case MONS_ICE_DRAGON:
        if (spell == SPELL_NO_SPELL)
            spell = SPELL_COLD_BREATH;
    // Intentional fallthrough

    // Dragon breath weapons:
    case MONS_DRAGON:
    case MONS_HELL_HOUND:
    case MONS_LINDWURM:
    case MONS_FIRE_DRAKE:
    case MONS_XTAHUA:
        if (spell == SPELL_NO_SPELL)
            spell = SPELL_FIRE_BREATH;

        if (monster->has_ench(ENCH_CONFUSION))
            break;

        if (!you.visible_to(monster))
            break;

        if (monster->type != MONS_HELL_HOUND && x_chance_in_y(3, 13)
            || one_chance_in(10))
        {
            setup_mons_cast(monster, beem, spell);

            // Fire tracer.
            fire_tracer(monster, beem);

            // Good idea?
            if (mons_should_fire(beem))
            {
                make_mons_stop_fleeing(monster);
                _mons_cast_abil(monster, beem, spell);
                used = true;
            }
        }
        break;

    case MONS_MERMAID:
    case MONS_SIREN:
    {
        // Don't behold observer in the arena.
        if (crawl_state.arena)
            break;

        // Don't behold player already half down or up the stairs.
        if (!you.delay_queue.empty())
        {
            delay_queue_item delay = you.delay_queue.front();

            if (delay.type == DELAY_ASCENDING_STAIRS
                || delay.type == DELAY_DESCENDING_STAIRS)
            {
                dprf("Taking stairs, don't mesmerise.");
                break;
            }
        }

        // Won't sing if either of you silenced, or it's friendly,
        // confused, fleeing, or leaving the level.
        if (monster->has_ench(ENCH_CONFUSION)
            || mons_is_fleeing(monster)
            || monster->pacified()
            || monster->friendly()
            || !player_can_hear(monster->pos()))
        {
            break;
        }

        // Don't even try on berserkers. Mermaids know their limits.
        if (you.berserk())
            break;

        // Reduce probability because of spamminess.
        if (you.species == SP_MERFOLK && !one_chance_in(4))
            break;

        // A wounded invisible mermaid is less likely to give away her position.
        if (monster->invisible()
            && monster->hit_points <= monster->max_hit_points / 2
            && !one_chance_in(3))
        {
            break;
        }

        bool already_mesmerised = you.beheld_by(monster);

        if (one_chance_in(5)
            || monster->foe == MHITYOU && !already_mesmerised && coinflip())
        {
            noisy(LOS_RADIUS, monster->pos(), monster->mindex(), true);

            bool did_resist = false;
            if (you.can_see(monster))
            {
                simple_monster_message(monster,
                    make_stringf(" chants %s song.",
                    already_mesmerised ? "her luring" : "a haunting").c_str(),
                    spl);

                if (monster->type == MONS_SIREN)
                {
                    if (_siren_movement_effect(monster))
                    {
                        canned_msg(MSG_YOU_RESIST); // flavour only
                        did_resist = true;
                    }
                }
            }
            else
            {
                // If you're already mesmerised by an invisible mermaid she
                // can still prolong the enchantment; otherwise you "resist".
                if (already_mesmerised)
                    mpr("You hear a luring song.", MSGCH_SOUND);
                else
                {
                    if (one_chance_in(4)) // reduce spamminess
                    {
                        if (coinflip())
                            mpr("You hear a haunting song.", MSGCH_SOUND);
                        else
                            mpr("You hear an eerie melody.", MSGCH_SOUND);

                        canned_msg(MSG_YOU_RESIST); // flavour only
                    }
                    break;
                }
            }

            // Once mesmerised by a particular monster, you cannot resist
            // anymore.
            if (!already_mesmerised
                && (you.species == SP_MERFOLK || you.check_res_magic(100)))
            {
                if (!did_resist)
                    canned_msg(MSG_YOU_RESIST);
                break;
            }

            you.add_beholder(monster);

            used = true;
        }
        break;
    }

    default:
        break;
    }

    if (used)
        monster->lose_energy(EUT_SPECIAL);

    return (used);
}

// Combines code using in Confusing Eye, Giant Eye and Eye of Draining to
// reduce clutter.
bool _eyeball_will_use_ability (monsters *monster)
{
    return (coinflip()
        && !mons_is_wandering(monster)
        && !mons_is_fleeing(monster)
        && !monster->pacified()
        && !player_or_mon_in_sanct(monster));
}

//---------------------------------------------------------------
//
// mon_nearby_ability
//
// Gives monsters a chance to use a special ability when they're
// next to the player.
//
//---------------------------------------------------------------
void mon_nearby_ability(monsters *monster)
{
    actor *foe = monster->get_foe();
    if (!foe
        || !monster->can_see(foe)
        || monster->asleep()
        || monster->submerged())
    {
        return;
    }

    maybe_mons_speaks(monster);

    if (monster_can_submerge(monster, grd(monster->pos()))
        && !monster->caught()             // No submerging while caught.
        && !you.beheld_by(monster) // No submerging if player entranced.
        && !mons_is_lurking(monster)  // Handled elsewhere.
        && monster->wants_submerge())
    {
        monsterentry* entry = get_monster_data(monster->type);

        monster->add_ench(ENCH_SUBMERGED);
        monster->speed_increment -= ENERGY_SUBMERGE(entry);
        return;
    }

    switch (monster->type)
    {
    case MONS_SPATIAL_VORTEX:
    case MONS_KILLER_KLOWN:
        // Choose random colour.
        monster->colour = random_colour();
        break;

    case MONS_GOLDEN_EYE:
        if (_eyeball_will_use_ability(monster))
        {
            const bool can_see = you.can_see(monster);
            if (can_see && you.can_see(foe))
                mprf("%s blinks at %s.",
                     monster->name(DESC_CAP_THE).c_str(),
                     foe->name(DESC_NOCAP_THE).c_str());

            int confuse_power = 2 + random2(3);

            if (foe->atype() == ACT_PLAYER && !can_see)
                mpr("You feel you are being watched by something.");

            if (foe->check_res_magic((monster->hit_dice * 5) * confuse_power))
            {
                if (foe->atype() == ACT_PLAYER)
                    canned_msg(MSG_YOU_RESIST);
                else if (foe->atype() == ACT_MONSTER)
                {
                    const monsters *foe_mons = dynamic_cast<const monsters*>(foe);
                    simple_monster_message(foe_mons, mons_resist_string(foe_mons));
                }
                break;
            }

            foe->confuse(monster, 2 + random2(3));
        }
        break;

    case MONS_GIANT_EYEBALL:
        if (_eyeball_will_use_ability(monster))
        {
            const bool can_see = you.can_see(monster);
            if (can_see && you.can_see(foe))
                mprf("%s stares at %s.",
                     monster->name(DESC_CAP_THE).c_str(),
                     foe->name(DESC_NOCAP_THE).c_str());

            if (foe->atype() == ACT_PLAYER && !can_see)
                mpr("You feel you are being watched by something.");

            // Subtly different from old paralysis behaviour, but
            // it'll do.
            foe->paralyse(monster, 2 + random2(3));
        }
        break;

    case MONS_EYE_OF_DRAINING:
        if (_eyeball_will_use_ability(monster) && foe->atype() == ACT_PLAYER)
        {
            if (you.can_see(monster))
                simple_monster_message(monster, " stares at you.");
            else
                mpr("You feel you are being watched by something.");

            dec_mp(5 + random2avg(13, 3));

            monster->heal(10, true); // heh heh {dlb}
        }
        break;

    case MONS_AIR_ELEMENTAL:
        if (one_chance_in(5))
            monster->add_ench(ENCH_SUBMERGED);
        break;

    case MONS_PANDEMONIUM_DEMON:
        if (monster->ghost->cycle_colours)
            monster->colour = random_colour();
        break;

    default:
        break;
    }
}

// When giant spores move maybe place a ballistomycete on the they move
// off of.
void ballisto_on_move(monsters * monster, const coord_def & position)
{
    if (monster->type == MONS_GIANT_SPORE)
    {
        // The number field is used as a cooldown timer for this behavior.
        if (monster->number <= 0)
        {
            if (one_chance_in(4))
            {
                beh_type attitude = SAME_ATTITUDE(monster);
                if (!crawl_state.arena && attitude == BEH_FRIENDLY)
                {
                    attitude = BEH_GOOD_NEUTRAL;
                }
                int rc = create_monster(mgen_data(MONS_BALLISTOMYCETE,
                                                  attitude,
                                                  monster,
                                                  0,
                                                  0,
                                                  position,
                                                  MHITNOT,
                                                  MG_FORCE_PLACE));

                if (rc != -1 && you.can_see(&env.mons[rc]))
                    mprf("A ballistomycete grows in the wake of the spore.");

                monster->number = 40;
            }
        }
        else
        {
            monster->number--;
        }

    }
}

// If 'monster' is a ballistomycete or spore activate some number of
// ballistomycetes on the level.
void activate_ballistomycetes( monsters * monster, const coord_def & origin)
{
    if (!monster || monster->type != MONS_BALLISTOMYCETE
                    && monster->type != MONS_GIANT_SPORE)
    {
        return;
    }

    bool found_others = false;

    std::vector<monsters *> candidates;
    for (monster_iterator mi; mi; ++mi)
    {
        if (mi->mindex() != monster->mindex()
            && mi->alive()
            && mi->type == MONS_BALLISTOMYCETE)
        {
            candidates.push_back(*mi);

        }
    }

    if (candidates.empty())
        return;

    // If a spore or inactive ballisto died we will only activate one
    // other ballisto. If it was an active ballisto we will distribute
    // its count to others on the level.
    int activation_count = 1;
    if (monster ->type == MONS_BALLISTOMYCETE)
    {
        activation_count += monster->number;
    }

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

    int index = 0;
    for (int i=0; i<activation_count; ++i)
    {
        index = i % candidates.size();

        monsters * spawner = candidates[index];

        spawner->number++;
        found_others = true;

        // Change color and start the spore production timer if we
        // are moving from 0 to 1.
        if (spawner->number == 1)
        {
            spawner->colour = LIGHTRED;
            // Reset the spore production timer.
            spawner->del_ench(ENCH_SPORE_PRODUCTION, false);
            spawner->add_ench(ENCH_SPORE_PRODUCTION);
        }
    }

    if (you.see_cell(origin) && found_others)
        mprf("You feel the ballistomycetes will spawn a replacement spore.");
}