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












                                            
                
                  
                    
                     
                           
                         
                      






                     
                  


                                                        
                                                                   





                                                              
                                                                         
     



                                                                        


                                         








                                                                   



                                                                   














                                                                 
















                                                                   
                        
                                      
                                     
                                         




                                                              
                                                                



                                                                     
                                                                         







                                                                       
                                      

















                                                
                                                                      
                                                        



















                                                                          
                                                 






























                                                                              
                          
                                         




                                                   
                                                         















                                                                      
                                  




                                                                      
                                                      





                                                                        
                                                             







                                                               
                                                     










                                                     
                                                 





























                                                               
                                                       

                                    

                                                                               
                 
                                         
                 










                                            









                                                                          





                                                            


                     





























































































                                                                                
                                         
                                                    



                          
                                                                  















































                                                                                
                                                                   

                                                           
                                                                             
















                                                              
                                                          


                           
                                                           

                                             
                                             











                                                                     
                                                           








































                                                                              
                                                                      






















                                                                      


                                                                           
                                      
                 



































                                                                      
                      

                       
                       
     



                                                        





                                      
                                             
                                             
                                           
                                                 









                                            
                                          
                                         






















                                                                        
                                                                   



                                                     
                                                                   
  
                                                                   





                                                                  

                                         



                                                         
                                               







                                          
                                                   















































                                                                       
                                                                        




































                                                                         
                                                   




                              
                                                                


                                                             

























                                                               
                                             




                                                                
                                                  






                                                                  
                                                      























                                                                         
                                







                                       
                                                       

                               
                                     








                                                                    
                            




                                                    
                                                      






















                                                                      
                                  











                                                            
                                                      





                                                             
                                            
























                                                                          
/*
 *  File:       mon-behv.h
 *  Summary:    Monster behaviour functions.
 *  Written by: Linley Henzell
 */

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

#include "externs.h"

#include "coord.h"
#include "coordit.h"
#include "env.h"
#include "fprop.h"
#include "exclude.h"
#include "mon-iter.h"
#include "mon-movetarget.h"
#include "mon-pathfind.h"
#include "mon-stuff.h"
#include "mon-util.h"
#include "random.h"
#include "state.h"
#include "terrain.h"
#include "traps.h"
#include "tutorial.h"
#include "view.h"
#include "shout.h"

static void _set_nearest_monster_foe(monsters *monster);

static void _guess_invis_foe_pos(monsters *mon, bool strict = true)
{
    const actor* foe          = mon->get_foe();
    const int    guess_radius = mons_sense_invis(mon) ? 3 : 2;

    std::vector<coord_def> possibilities;

    for (radius_iterator ri(mon->pos(), guess_radius, C_ROUND); ri; ++ri)
    {
        // NOTE: This depends on mon_see_cell() ignoring clouds,
        // so that cells hidden by opaque clouds are included
        // as a possibility for the foe's location.
        if (!strict || foe->is_habitable(*ri) && mon->mon_see_cell(*ri))
            possibilities.push_back(*ri);
    }

    // If being strict (monster must see possible cell, foe must be
    // able to live there) gives no possibilites, then find *some*
    // cell near the foe.
    if (strict && possibilities.empty())
    {
        _guess_invis_foe_pos(mon, false);
        return;
    }

    if (!possibilities.empty())
        mon->target = possibilities[random2(possibilities.size())];
}

//---------------------------------------------------------------
//
// handle_behaviour
//
// 1. Evaluates current AI state
// 2. Sets monster target x,y based on current foe
//
// XXX: Monsters of I_NORMAL or above should select a new target
// if their current target is another monster which is sitting in
// a wall and is immune to most attacks while in a wall, unless
// the monster has a spell or special/nearby ability which isn't
// affected by the wall.
//---------------------------------------------------------------
void handle_behaviour(monsters *mon)
{
    // Test spawners should always be BEH_SEEK against a foe, since
    // their only purpose is to spew out monsters for testing
    // purposes.
    if (mon->type == MONS_TEST_SPAWNER)
    {
        for (monster_iterator mi; mi; ++mi)
        {
            if (mon->attitude != mi->attitude)
            {
                mon->foe       = mi->mindex();
                mon->target    = mi->pos();
                mon->behaviour = BEH_SEEK;
                return;
            }
        }
    }

    bool changed = true;
    bool isFriendly = mon->friendly();
    bool isNeutral  = mon->neutral();
    bool wontAttack = mon->wont_attack();

    // Whether the player is in LOS of the monster and can see
    // or has guessed the player's location.
    bool proxPlayer = mons_near(mon) && !crawl_state.arena;

    bool trans_wall_block = you.trans_wall_blocking(mon->pos());

#ifdef WIZARD
    // If stealth is greater than actually possible (wizmode level)
    // pretend the player isn't there, but only for hostile monsters.
    if (proxPlayer && you.skills[SK_STEALTH] > 27 && !mon->wont_attack())
        proxPlayer = false;
#endif
    bool proxFoe;
    bool isHurt     = (mon->hit_points <= mon->max_hit_points / 4 - 1);
    bool isHealthy  = (mon->hit_points > mon->max_hit_points / 2);
    bool isSmart    = (mons_intel(mon) > I_ANIMAL);
    bool isScared   = mon->has_ench(ENCH_FEAR);
    bool isMobile   = !mons_is_stationary(mon);
    bool isPacified = mon->pacified();
    bool patrolling = mon->is_patrolling();
    static std::vector<level_exit> e;
    static int                     e_index = -1;

    // Check for confusion -- early out.
    if (mon->has_ench(ENCH_CONFUSION))
    {
        set_random_target(mon);
        return;
    }

    if (mons_is_fleeing_sanctuary(mon)
        && mons_is_fleeing(mon)
        && is_sanctuary(you.pos()))
    {
        return;
    }

    // Make sure monsters are not targetting the player in arena mode.
    ASSERT(!(crawl_state.arena && mon->foe == MHITYOU));

    if (mons_wall_shielded(mon) && cell_is_solid(mon->pos()))
    {
        // Monster is safe, so its behaviour can be simplified to fleeing.
        if (mon->behaviour == BEH_CORNERED || mon->behaviour == BEH_PANIC
            || isScared)
        {
            mon->behaviour = BEH_FLEE;
        }
    }

    const dungeon_feature_type can_move =
        (mons_amphibious(mon)) ? DNGN_DEEP_WATER : DNGN_SHALLOW_WATER;

    // Validate current target exists.
    if (mon->foe != MHITNOT && mon->foe != MHITYOU)
    {
        const monsters& foe_monster = menv[mon->foe];
        if (!foe_monster.alive())
            mon->foe = MHITNOT;
        if (foe_monster.friendly() == isFriendly)
            mon->foe = MHITNOT;
    }

    // Change proxPlayer depending on invisibility and standing
    // in shallow water.
    if (proxPlayer && !you.visible_to(mon))
    {
        proxPlayer = false;

        const int intel = mons_intel(mon);
        // Sometimes, if a player is right next to a monster, they will 'see'.
        if (grid_distance(you.pos(), mon->pos()) == 1
            && one_chance_in(3))
        {
            proxPlayer = true;
        }

        // [dshaligram] Very smart monsters have a chance of clueing in to
        // invisible players in various ways.
        if (intel == I_NORMAL && one_chance_in(13)
                 || intel == I_HIGH && one_chance_in(6))
        {
            proxPlayer = true;
        }
    }

    // Set friendly target, if they don't already have one.
    // Berserking allies ignore your commands!
    if (isFriendly
        && you.pet_target != MHITNOT
        && (mon->foe == MHITNOT || mon->foe == MHITYOU)
        && !mon->berserk()
        && mon->type != MONS_GIANT_SPORE)
    {
        mon->foe = you.pet_target;
    }

    // Instead, berserkers attack nearest monsters.
    if ((mon->berserk() || mon->type == MONS_GIANT_SPORE)
        && (mon->foe == MHITNOT || isFriendly && mon->foe == MHITYOU))
    {
        // Intelligent monsters prefer to attack the player,
        // even when berserking.
        if (!isFriendly && proxPlayer && mons_intel(mon) >= I_NORMAL)
            mon->foe = MHITYOU;
        else
            _set_nearest_monster_foe(mon);
    }

    // Pacified monsters leaving the level prefer not to attack.
    // Others choose the nearest foe.
    if (!isPacified && mon->foe == MHITNOT)
        _set_nearest_monster_foe(mon);

    // Monsters do not attack themselves. {dlb}
    if (mon->foe == mon->mindex())
        mon->foe = MHITNOT;

    // Friendly and good neutral monsters do not attack other friendly
    // and good neutral monsters.
    if (mon->foe != MHITNOT && mon->foe != MHITYOU
        && wontAttack && menv[mon->foe].wont_attack())
    {
        mon->foe = MHITNOT;
    }

    // Neutral monsters prefer not to attack players, or other neutrals.
    if (isNeutral && mon->foe != MHITNOT
        && (mon->foe == MHITYOU || menv[mon->foe].neutral()))
    {
        mon->foe = MHITNOT;
    }

    // Unfriendly monsters fighting other monsters will usually
    // target the player, if they're healthy.
    if (!isFriendly && !isNeutral
        && mon->foe != MHITYOU && mon->foe != MHITNOT
        && proxPlayer && !mon->berserk() && isHealthy
        && !one_chance_in(3))
    {
        mon->foe = MHITYOU;
    }

    // Validate current target again.
    if (mon->foe != MHITNOT && mon->foe != MHITYOU)
    {
        const monsters& foe_monster = menv[mon->foe];
        if (!foe_monster.alive())
            mon->foe = MHITNOT;
        if (foe_monster.friendly() == isFriendly)
            mon->foe = MHITNOT;
    }

    while (changed)
    {
        actor* afoe = mon->get_foe();
        proxFoe = afoe && mon->can_see(afoe);

        coord_def foepos = coord_def(0,0);
        if (afoe)
            foepos = afoe->pos();

        if (mon->foe == MHITYOU)
            proxFoe = proxPlayer;   // Take invis into account.

        // Track changes to state; attitude never changes here.
        beh_type new_beh       = mon->behaviour;
        unsigned short new_foe = mon->foe;

        // Take care of monster state changes.
        switch (mon->behaviour)
        {
        case BEH_SLEEP:
            // default sleep state
            mon->target = mon->pos();
            new_foe = MHITNOT;
            break;

        case BEH_LURK:
        case BEH_SEEK:
            // No foe?  Then wander or seek the player.
            if (mon->foe == MHITNOT)
            {
                if (crawl_state.arena || !proxPlayer || isNeutral || patrolling
                    || mon->type == MONS_GIANT_SPORE)
                {
                    new_beh = BEH_WANDER;
                }
                else
                {
                    new_foe = MHITYOU;
                    mon->target = you.pos();
                }
                break;
            }

            // Foe gone out of LOS?
            if (!proxFoe)
            {
                // Maybe the foe is just invisible.
                if (mon->target.origin() && afoe && mon->near_foe())
                {
                    _guess_invis_foe_pos(mon);
                    if (mon->target.origin())
                    {
                        // Having a seeking mon with a foe who's target is
                        // (0, 0) can lead to asserts, so lets try to
                        // avoid that.
                        _set_nearest_monster_foe(mon);
                        if (mon->foe == MHITNOT)
                        {
                            new_beh = BEH_WANDER;
                            break;
                        }
                        mon->target = mon->get_foe()->pos();
                    }
                }

                if (mon->travel_target == MTRAV_SIREN)
                    mon->travel_target = MTRAV_NONE;

                if (mon->foe == MHITYOU && mon->is_travelling()
                    && mon->travel_target == MTRAV_PLAYER)
                {
                    // We've got a target, so we'll continue on our way.
#ifdef DEBUG_PATHFIND
                    mpr("Player out of LoS... start wandering.");
#endif
                    new_beh = BEH_WANDER;
                    break;
                }

                if (isFriendly)
                {
                    if (patrolling || crawl_state.arena)
                    {
                        new_foe = MHITNOT;
                        new_beh = BEH_WANDER;
                    }
                    else
                    {
                        new_foe = MHITYOU;
                        mon->target = foepos;
                    }
                    break;
                }

                ASSERT(mon->foe != MHITNOT);
                if (mon->foe_memory > 0)
                {
                    // If we've arrived at our target x,y
                    // do a stealth check.  If the foe
                    // fails, monster will then start
                    // tracking foe's CURRENT position,
                    // but only for a few moves (smell and
                    // intuition only go so far).

                    if (mon->pos() == mon->target)
                    {
                        if (mon->foe == MHITYOU)
                        {
                            if (one_chance_in(you.skills[SK_STEALTH]/3))
                                mon->target = you.pos();
                            else
                                mon->foe_memory = 0;
                        }
                        else
                        {
                            if (coinflip())     // XXX: cheesy!
                                mon->target = menv[mon->foe].pos();
                            else
                                mon->foe_memory = 0;
                        }
                    }

                    // Either keep chasing, or start wandering.
                    if (mon->foe_memory < 2)
                    {
                        mon->foe_memory = 0;
                        new_beh = BEH_WANDER;
                    }
                    break;
                }

                ASSERT(mon->foe_memory == 0);
                // Hack: smarter monsters will tend to pursue the player longer.
                switch (mons_intel(mon))
                {
                case I_HIGH:
                    mon->foe_memory = 100 + random2(200);
                    break;
                case I_NORMAL:
                    mon->foe_memory = 50 + random2(100);
                    break;
                case I_ANIMAL:
                case I_INSECT:
                    mon->foe_memory = 25 + random2(75);
                    break;
                case I_PLANT:
                    mon->foe_memory = 10 + random2(50);
                    break;
                }
                break;  // switch/case BEH_SEEK
            }

            ASSERT(proxFoe && mon->foe != MHITNOT);
            // Monster can see foe: continue 'tracking'
            // by updating target x,y.
            if (mon->foe == MHITYOU)
            {
                // The foe is the player.
                if (mon->type == MONS_SIREN
                    && you.beheld_by(mon)
                    && find_siren_water_target(mon))
                {
                    break;
                }

                if (try_pathfind(mon, can_move, trans_wall_block))
                    break;

                // Whew. If we arrived here, path finding didn't yield anything
                // (or wasn't even attempted) and we need to set our target
                // the traditional way.

                // Sometimes, your friends will wander a bit.
                if (isFriendly && one_chance_in(8))
                {
                    set_random_target(mon);
                    mon->foe = MHITNOT;
                    new_beh  = BEH_WANDER;
                }
                else
                {
                    mon->target = you.pos();
                }
            }
            else
            {
                // We have a foe but it's not the player.
                mon->target = menv[mon->foe].pos();
            }

            // Smart monsters, zombified monsters other than spectral
            // things, plants, and nonliving monsters cannot flee.
            if (isHurt && !isSmart && isMobile
                && (!mons_is_zombified(mon) || mon->type == MONS_SPECTRAL_THING)
                && mon->holiness() != MH_PLANT
                && mon->holiness() != MH_NONLIVING)
            {
                new_beh = BEH_FLEE;
            }
            break;

        case BEH_WANDER:
            if (isPacified)
            {
                // If a pacified monster isn't travelling toward
                // someplace from which it can leave the level, make it
                // start doing so.  If there's no such place, either
                // search the level for such a place again, or travel
                // randomly.
                if (mon->travel_target != MTRAV_PATROL)
                {
                    new_foe = MHITNOT;
                    mon->travel_path.clear();

                    e_index = mons_find_nearest_level_exit(mon, e);

                    if (e_index == -1 || one_chance_in(20))
                        e_index = mons_find_nearest_level_exit(mon, e, true);

                    if (e_index != -1)
                    {
                        mon->travel_target = MTRAV_PATROL;
                        patrolling = true;
                        mon->patrol_point = e[e_index].target;
                        mon->target = e[e_index].target;
                    }
                    else
                    {
                        mon->travel_target = MTRAV_NONE;
                        patrolling = false;
                        mon->patrol_point.reset();
                        set_random_target(mon);
                    }
                }

                if (pacified_leave_level(mon, e, e_index))
                    return;
            }

            if (mon->strict_neutral() && mons_is_slime(mon)
                && you.religion == GOD_JIYVA)
            {
                set_random_slime_target(mon);
            }

            // Is our foe in LOS?
            // Batty monsters don't automatically reseek so that
            // they'll flitter away, we'll reset them just before
            // they get movement in handle_monsters() instead. -- bwr
            if (proxFoe && !mons_is_batty(mon))
            {
                new_beh = BEH_SEEK;
                break;
            }

            check_wander_target(mon, isPacified, can_move);

            // During their wanderings, monsters will eventually relax
            // their guard (stupid ones will do so faster, smart
            // monsters have longer memories).  Pacified monsters will
            // also eventually switch the place from which they want to
            // leave the level, in case their current choice is blocked.
            if (!proxFoe && mon->foe != MHITNOT
                   && one_chance_in(isSmart ? 60 : 20)
                || isPacified && one_chance_in(isSmart ? 40 : 120))
            {
                new_foe = MHITNOT;
                if (mon->is_travelling() && mon->travel_target != MTRAV_PATROL
                    || isPacified)
                {
#ifdef DEBUG_PATHFIND
                    mpr("It's been too long! Stop travelling.");
#endif
                    mon->travel_path.clear();
                    mon->travel_target = MTRAV_NONE;

                    if (isPacified && e_index != -1)
                        e[e_index].unreachable = true;
                }
            }
            break;

        case BEH_FLEE:
            // Check for healed.
            if (isHealthy && !isScared)
                new_beh = BEH_SEEK;

            // Smart monsters flee until they can flee no more...
            // possible to get a 'CORNERED' event, at which point
            // we can jump back to WANDER if the foe isn't present.

            if (isFriendly)
            {
                // Special-cased below so that it will flee *towards* you.
                if (mon->foe == MHITYOU)
                    mon->target = you.pos();
            }
            else if (mons_wall_shielded(mon) && find_wall_target(mon))
                ; // Wall target found.
            else if (proxFoe)
            {
                // Special-cased below so that it will flee *from* the
                // correct position.
                mon->target = foepos;
            }
            break;

        case BEH_CORNERED:
            // Plants and nonliving monsters cannot fight back.
            if (mon->holiness() == MH_PLANT
                || mon->holiness() == MH_NONLIVING)
            {
                break;
            }

            if (isHealthy)
                new_beh = BEH_SEEK;

            // Foe gone out of LOS?
            if (!proxFoe)
            {
                if ((isFriendly || proxPlayer) && !isNeutral && !patrolling
                    && !crawl_state.arena)
                {
                    new_foe = MHITYOU;
                }
                else
                    new_beh = BEH_WANDER;
            }
            else
            {
                mon->target = foepos;
            }
            break;

        default:
            return;     // uh oh
        }

        changed = (new_beh != mon->behaviour || new_foe != mon->foe);
        mon->behaviour = new_beh;

        if (mon->foe != new_foe)
            mon->foe_memory = 0;

        mon->foe = new_foe;
    }

    if (mon->travel_target == MTRAV_WALL && cell_is_solid(mon->pos()))
    {
        if (mon->behaviour == BEH_FLEE)
        {
            // Monster is safe, so stay put.
            mon->target = mon->pos();
            mon->foe = MHITNOT;
        }
    }
}

static bool _mons_check_foe(monsters *mon, const coord_def& p,
                            bool friendly, bool neutral)
{
    if (!in_bounds(p))
        return (false);

    if (p == you.pos())
    {
        // The player: We don't return true here because
        // otherwise wandering monsters will always
        // attack the player.
        return (false);
    }

    if (monsters *foe = monster_at(p))
    {
        if (foe != mon
            && mon->can_see(foe)
            && !mons_is_projectile(foe->type)
            && (friendly || !is_sanctuary(p))
            && (foe->friendly() != friendly
                || (neutral && !foe->neutral())))
        {
            return (true);
        }
    }
    return (false);
}

// Choose random nearest monster as a foe.
void _set_nearest_monster_foe(monsters *mon)
{
    const bool friendly = mon->friendly();
    const bool neutral  = mon->neutral();

    for (int k = 1; k <= LOS_RADIUS; ++k)
    {
        std::vector<coord_def> monster_pos;
        for (int i = -k; i <= k; ++i)
            for (int j = -k; j <= k; (abs(i) == k ? j++ : j += 2*k))
            {
                const coord_def p = mon->pos() + coord_def(i, j);
                if (_mons_check_foe(mon, p, friendly, neutral))
                    monster_pos.push_back(p);
            }
        if (monster_pos.empty())
            continue;

        const coord_def mpos = monster_pos[random2(monster_pos.size())];
        if (mpos == you.pos())
            mon->foe = MHITYOU;
        else
            mon->foe = env.mgrid(mpos);
        return;
    }
}

//-----------------------------------------------------------------
//
// behaviour_event
//
// 1. Change any of: monster state, foe, and attitude
// 2. Call handle_behaviour to re-evaluate AI state and target x, y
//
//-----------------------------------------------------------------
void behaviour_event(monsters *mon, mon_event_type event, int src,
                     coord_def src_pos, bool allow_shout)
{
    ASSERT(src >= 0 && src <= MHITYOU);
    ASSERT(!crawl_state.arena || src != MHITYOU);
    ASSERT(in_bounds(src_pos) || src_pos.origin());
    if (mons_is_projectile(mon->type))
        return; // projectiles have no AI

    const beh_type old_behaviour = mon->behaviour;

    bool isSmart          = (mons_intel(mon) > I_ANIMAL);
    bool wontAttack       = mon->wont_attack();
    bool sourceWontAttack = false;
    bool setTarget        = false;
    bool breakCharm       = false;
    bool was_sleeping     = mon->asleep();

    if (src == MHITYOU)
        sourceWontAttack = true;
    else if (src != MHITNOT)
        sourceWontAttack = menv[src].wont_attack();

    if (is_sanctuary(mon->pos()) && mons_is_fleeing_sanctuary(mon))
    {
        mon->behaviour = BEH_FLEE;
        mon->foe       = MHITYOU;
        mon->target    = env.sanctuary_pos;
        return;
    }

    switch (event)
    {
    case ME_DISTURB:
        // Assumes disturbed by noise...
        if (mon->asleep())
        {
            mon->behaviour = BEH_WANDER;

            if (mons_near(mon))
                remove_auto_exclude(mon, true);
        }

        // A bit of code to make Projected Noise actually do
        // something again.  Basically, dumb monsters and
        // monsters who aren't otherwise occupied will at
        // least consider the (apparent) source of the noise
        // interesting for a moment. -- bwr
        if (!isSmart || mon->foe == MHITNOT || mons_is_wandering(mon))
        {
            if (mon->is_patrolling())
                break;

            ASSERT(!src_pos.origin());
            mon->target = src_pos;
        }
        break;

    case ME_WHACK:
    case ME_ANNOY:
        // Will turn monster against <src>, unless they
        // are BOTH friendly or good neutral AND stupid,
        // or else fleeing anyway.  Hitting someone over
        // the head, of course, always triggers this code.
        if (event == ME_WHACK
            || ((wontAttack != sourceWontAttack || isSmart)
                && !mons_is_fleeing(mon) && !mons_is_panicking(mon)))
        {
            // Monster types that you can't gain experience from cannot
            // fight back, so don't bother having them do so.  If you
            // worship Fedhas, create a ring of friendly plants, and try
            // to break out of the ring by killing a plant, you'll get
            // a warning prompt and penance only once.  Without the
            // hostility check, the plant will remain friendly until it
            // dies, and you'll get a warning prompt and penance once
            // *per hit*.  This may not be the best way to address the
            // issue, though. -cao
            if (mons_class_flag(mon->type, M_NO_EXP_GAIN)
                && mon->attitude != ATT_FRIENDLY
                && mon->attitude != ATT_GOOD_NEUTRAL)
            {
                return;
            }

            mon->foe = src;

            if (mon->asleep() && mons_near(mon))
                remove_auto_exclude(mon, true);

            if (!mons_is_cornered(mon))
                mon->behaviour = BEH_SEEK;

            if (src == MHITYOU)
            {
                mon->attitude = ATT_HOSTILE;
                breakCharm    = true;
            }
        }

        // Now set target so that monster can whack back (once) at an
        // invisible foe.
        if (event == ME_WHACK)
            setTarget = true;
        break;

    case ME_ALERT:
        // Allow monsters falling asleep while patrolling (can happen if
        // they're left alone for a long time) to be woken by this event.
        if (mon->friendly() && mon->is_patrolling()
            && !mon->asleep())
        {
            break;
        }

        // Avoid moving friendly giant spores out of BEH_WANDER.
        if (mon->friendly() && mon->type == MONS_GIANT_SPORE)
            break;

        if (mon->asleep() && mons_near(mon))
            remove_auto_exclude(mon, true);

        // Will alert monster to <src> and turn them
        // against them, unless they have a current foe.
        // It won't turn friends hostile either.
        if (!mons_is_fleeing(mon) && !mons_is_panicking(mon)
            && !mons_is_cornered(mon))
        {
            mon->behaviour = BEH_SEEK;
        }

        if (mon->foe == MHITNOT)
            mon->foe = src;

        if (!src_pos.origin()
            && (mon->foe == MHITNOT || mon->foe == src
                || mons_is_wandering(mon)))
        {
            if (mon->is_patrolling())
                break;

            mon->target = src_pos;

            // XXX: Should this be done in _handle_behaviour()?
            if (src == MHITYOU && src_pos == you.pos()
                && !you.see_cell(mon->pos()))
            {
                const dungeon_feature_type can_move =
                    (mons_amphibious(mon)) ? DNGN_DEEP_WATER
                                           : DNGN_SHALLOW_WATER;

                try_pathfind(mon, can_move, true);
            }
        }
        break;

    case ME_SCARE:
        // Stationary monsters can't flee, and berserking monsters
        // are too enraged.
        if (mons_is_stationary(mon) || mon->berserk())
        {
            mon->del_ench(ENCH_FEAR, true, true);
            break;
        }

        // Neither do plants or nonliving beings.
        if (mon->holiness() == MH_PLANT
            || mon->holiness() == MH_NONLIVING)
        {
            mon->del_ench(ENCH_FEAR, true, true);
            break;
        }

        // Assume monsters know where to run from, even if player is
        // invisible.
        mon->behaviour = BEH_FLEE;
        mon->foe       = src;
        mon->target    = src_pos;
        if (src == MHITYOU)
        {
            // Friendly monsters don't become hostile if you read a
            // scroll of fear, but enslaved ones will.
            // Send friendlies off to a random target so they don't cling
            // to you in fear.
            if (mon->friendly())
            {
                breakCharm = true;
                mon->foe   = MHITNOT;
                set_random_target(mon);
            }
            else
                setTarget = true;
        }
        else if (mon->friendly() && !crawl_state.arena)
            mon->foe = MHITYOU;

        if (you.see_cell(mon->pos()))
            learned_something_new(TUT_FLEEING_MONSTER);
        break;

    case ME_CORNERED:
        // Some monsters can't flee.
        if (mon->behaviour != BEH_FLEE && !mon->has_ench(ENCH_FEAR))
            break;

        // Pacified monsters shouldn't change their behaviour.
        if (mon->pacified())
            break;

        // Just set behaviour... foe doesn't change.
        if (!mons_is_cornered(mon))
        {
            if (mon->friendly() && !crawl_state.arena)
            {
                mon->foe = MHITYOU;
                simple_monster_message(mon, " returns to your side!");
            }
            else
                simple_monster_message(mon, " turns to fight!");
        }

        mon->behaviour = BEH_CORNERED;
        break;

    case ME_EVAL:
        break;
    }

    if (setTarget)
    {
        if (src == MHITYOU)
        {
            mon->target = you.pos();
            mon->attitude = ATT_HOSTILE;
        }
        else if (src != MHITNOT)
            mon->target = src_pos;
    }

    // Now, break charms if appropriate.
    if (breakCharm)
        mon->del_ench(ENCH_CHARM);

    // Do any resultant foe or state changes.
    handle_behaviour(mon);
    ASSERT(in_bounds(mon->target) || mon->target.origin());

    // If it woke up and you're its new foe, it might shout.
    if (was_sleeping && !mon->asleep() && allow_shout
        && mon->foe == MHITYOU && !mon->wont_attack())
    {
        handle_monster_shouts(mon);
    }

    const bool wasLurking =
        (old_behaviour == BEH_LURK && !mons_is_lurking(mon));
    const bool isPacified = mon->pacified();

    if ((wasLurking || isPacified)
        && (event == ME_DISTURB || event == ME_ALERT || event == ME_EVAL))
    {
        // Lurking monsters or pacified monsters leaving the level won't
        // stop doing so just because they noticed something.
        mon->behaviour = old_behaviour;
    }
    else if (wasLurking && mon->has_ench(ENCH_SUBMERGED)
             && !mon->del_ench(ENCH_SUBMERGED))
    {
        // The same goes for lurking submerged monsters, if they can't
        // unsubmerge.
        mon->behaviour = BEH_LURK;
    }

    ASSERT(!crawl_state.arena
           || mon->foe != MHITYOU && mon->target != you.pos());
}

void make_mons_stop_fleeing(monsters *mon)
{
    if (mons_is_fleeing(mon))
        behaviour_event(mon, ME_CORNERED);
}