/*
 *  File:       monstuff.cc
 *  Summary:    Misc monster related functions.
 *  Written by: Linley Henzell
 *
 *  Modified for Crawl Reference by $Author: zelgadis $ on $Date: 2008-12-15 22:58:13 -0500 (Mon, 15 Dec 2008) $
 *
 *  Change History (most recent first):
 *
 *      <8>      7 Aug  2001   MV      Intelligent monsters now pick up gold
 *      <7>     26 Mar  2001   GDL     Fixed monster reaching
 *      <6>     13 Mar  2001   GDL     Rewrite of monster AI
 *      <5>     31 July 2000   GDL     More Manticore fixes.
 *      <4>     29 July 2000   JDJ     Fixed a bunch of places in handle_pickup where MSLOT_WEAPON
 *                                     was being erroneously used.
 *      <3>     25 July 2000   GDL     Fixed Manticores
 *      <2>     11/23/99       LRH     Upgraded monster AI
 *      <1>     -/--/--        LRH     Created
 */

#include "AppHdr.h"
#include "monstuff.h"

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <algorithm>

#ifdef DOS
#include <conio.h>
#endif

#include "externs.h"

#include "beam.h"
#include "cloud.h"
#include "database.h"
#include "debug.h"
#include "delay.h"
#include "describe.h"
#include "dgnevent.h"
#include "fight.h"
#include "files.h"
#include "ghost.h"
#include "hiscores.h"
#include "it_use2.h"
#include "itemname.h"
#include "items.h"
#include "itemprop.h"
#include "Kills.h"
#include "makeitem.h"
#include "message.h"
#include "misc.h"
#include "monplace.h"
#include "monspeak.h"
#include "mon-pick.h"
#include "mon-util.h"
#include "mstuff2.h"
#include "notes.h"
#include "player.h"
#include "randart.h"
#include "religion.h"
#include "spl-cast.h"
#include "spl-util.h"
#include "spells3.h"
#include "spells4.h"
#include "state.h"
#include "stuff.h"
#include "terrain.h"
#include "traps.h"
#include "tutorial.h"
#include "view.h"
#include "stash.h"
#include "xom.h"

static bool _wounded_damaged(int wound_class);
static bool _handle_special_ability(monsters *monster, bolt & beem);
static bool _handle_pickup(monsters *monster);
static void _handle_behaviour(monsters *monster);
static void _set_nearest_monster_foe(monsters *monster);
static void _mons_in_cloud(monsters *monster);
static bool _mon_can_move_to_pos(const monsters *monster, const int count_x,
                                 const int count_y, bool just_check = false);
static bool _is_trap_safe(const monsters *monster, const int trap_x,
                          const int trap_y, bool just_check = false);
static bool _monster_move(monsters *monster);
static bool _plant_spit(monsters *monster, bolt &pbolt);
static spell_type _map_wand_to_mspell(int wand_type);

// [dshaligram] Doesn't need to be extern.
static int mmov_x, mmov_y;

static int compass_x[8] = { -1, 0, 1, 1, 1, 0, -1, -1 };
static int compass_y[8] = { -1, -1, -1, 0, 1, 1, 1, 0 };

static bool immobile_monster[MAX_MONSTERS];

#define FAR_AWAY    1000000         // used in monster_move()

// This function creates an artificial item to represent a mimic's appearance.
// Eventually, mimics could be redone to be more like dancing weapons...
// there'd only be one type and it would look like the item it carries. -- bwr
void get_mimic_item( const monsters *mimic, item_def &item )
{
    ASSERT( mimic != NULL && mons_is_mimic( mimic->type ) );

    item.base_type = OBJ_UNASSIGNED;
    item.sub_type  = 0;
    item.special   = 0;
    item.colour    = 0;
    item.flags     = 0;
    item.quantity  = 1;
    item.plus      = 0;
    item.plus2     = 0;
    item.x         = mimic->x;
    item.y         = mimic->y;
    item.link      = NON_ITEM;

    int prop = 127 * mimic->x + 269 * mimic->y;

    rng_save_excursion exc;
    seed_rng( prop );

    switch (mimic->type)
    {
    case MONS_WEAPON_MIMIC:
        item.base_type = OBJ_WEAPONS;
        item.sub_type = (59 * mimic->x + 79 * mimic->y) % NUM_WEAPONS;

        prop %= 100;

        if (prop < 20)
            make_item_randart(item);
        else if (prop < 50)
            set_equip_desc( item, ISFLAG_GLOWING );
        else if (prop < 80)
            set_equip_desc( item, ISFLAG_RUNED );
        else if (prop < 85)
            set_equip_race( item, ISFLAG_ORCISH );
        else if (prop < 90)
            set_equip_race( item, ISFLAG_DWARVEN );
        else if (prop < 95)
            set_equip_race( item, ISFLAG_ELVEN );
        break;

    case MONS_ARMOUR_MIMIC:
        item.base_type = OBJ_ARMOUR;
        item.sub_type = (59 * mimic->x + 79 * mimic->y) % NUM_ARMOURS;

        prop %= 100;

        if (prop < 20)
            make_item_randart(item);
        else if (prop < 40)
            set_equip_desc(item, ISFLAG_GLOWING);
        else if (prop < 60)
            set_equip_desc(item, ISFLAG_RUNED);
        else if (prop < 80)
            set_equip_desc(item, ISFLAG_EMBROIDERED_SHINY);
        else if (prop < 85)
            set_equip_race(item, ISFLAG_ORCISH);
        else if (prop < 90)
            set_equip_race(item, ISFLAG_DWARVEN);
        else if (prop < 95)
            set_equip_race(item, ISFLAG_ELVEN);
        break;

    case MONS_SCROLL_MIMIC:
        item.base_type = OBJ_SCROLLS;
        item.sub_type = prop % NUM_SCROLLS;
        break;

    case MONS_POTION_MIMIC:
        item.base_type = OBJ_POTIONS;
        item.sub_type = prop % NUM_POTIONS;
        break;

    case MONS_GOLD_MIMIC:
    default:
        item.base_type = OBJ_GOLD;
        item.quantity = 5 + prop % 30;
        break;
    }

    item_colour( item ); // also sets special vals for scrolls/potions
}

// Sets the colour of a mimic to match its description... should be called
// whenever a mimic is created or teleported. -- bwr
int get_mimic_colour( const monsters *mimic )
{
    ASSERT( mimic != NULL && mons_is_mimic( mimic->type ) );

    if (mimic->type == MONS_SCROLL_MIMIC)
        return (LIGHTGREY);
    else if (mimic->type == MONS_GOLD_MIMIC)
        return (YELLOW);

    item_def  item;
    get_mimic_item( mimic, item );

    return (item.colour);
}

// Monster curses a random player inventory item.
bool curse_an_item( bool decay_potions, bool quiet )
{
    int count = 0;
    int item  = ENDOFPACK;

    for (int i = 0; i < ENDOFPACK; i++)
    {
        if (!is_valid_item( you.inv[i] ))
            continue;

        if (you.inv[i].base_type == OBJ_WEAPONS
            || you.inv[i].base_type == OBJ_ARMOUR
            || you.inv[i].base_type == OBJ_JEWELLERY
            || you.inv[i].base_type == OBJ_POTIONS)
        {
            if (item_cursed( you.inv[i] ))
                continue;

            if (you.inv[i].base_type == OBJ_POTIONS
                && (!decay_potions || you.inv[i].sub_type == POT_DECAY))
            {
                continue;
            }

            // Item is valid for cursing, so we'll give it a chance.
            count++;
            if (one_chance_in( count ))
                item = i;
        }
    }

    // Any item to curse?
    if (item == ENDOFPACK)
        return (false);

    // Curse item.
    if (decay_potions && !quiet) // Just for mummies.
        mpr("You feel nervous for a moment...", MSGCH_MONSTER_SPELL);

    if (you.inv[item].base_type == OBJ_POTIONS)
    {
        int amount;
        // Decay at least two of the stack.
        if (you.inv[item].quantity <= 2)
            amount = you.inv[item].quantity;
        else
            amount = 2 + random2(you.inv[item].quantity - 1);

        split_potions_into_decay(item, amount);

        if (item_value(you.inv[item], true) / amount > 2)
            xom_is_stimulated(32 * amount);
    }
    else
    {
        do_curse_item( you.inv[item], false );
    }

    return (true);
}

static void _monster_drop_ething(monsters *monster,
                                 bool mark_item_origins = false,
                                 int owner_id = NON_ITEM)
{
    const bool hostile_grid = grid_destroys_items(grd(monster->pos()));
    const int midx = (int) monster_index(monster);

    bool destroyed = false;

    // Drop weapons & missiles last (ie on top) so others pick up.
    for (int i = NUM_MONSTER_SLOTS - 1; i >= 0; i--)
    {
        int item = monster->inv[i];

        if (item != NON_ITEM)
        {
            const bool summoned_item =
                testbits(mitm[item].flags, ISFLAG_SUMMONED);
            if (hostile_grid || summoned_item)
            {
                item_was_destroyed(mitm[item], midx);
                destroy_item( item );
                if (!summoned_item)
                    destroyed = true;
            }
            else
            {
                if (mons_friendly(monster) && is_valid_item(mitm[item]))
                    mitm[item].flags |= ISFLAG_DROPPED_BY_ALLY;

                move_item_to_grid( &item, monster->x, monster->y );

                if (mark_item_origins && is_valid_item(mitm[item]))
                    origin_set_monster(mitm[item], monster);
            }

            monster->inv[i] = NON_ITEM;
        }
    }

    if (destroyed)
        mprf(MSGCH_SOUND, grid_item_destruction_message(grd(monster->pos())));
}

static void _place_monster_corpse(const monsters *monster)
{
    int corpse_class = mons_species(monster->type);
    if (corpse_class == MONS_DRACONIAN)
        corpse_class = draco_subspecies(monster);

    if (monster->has_ench(ENCH_SHAPESHIFTER))
        corpse_class = MONS_SHAPESHIFTER;
    else if (monster->has_ench(ENCH_GLOWING_SHAPESHIFTER))
        corpse_class = MONS_GLOWING_SHAPESHIFTER;

    // Doesn't leave a corpse.
    if (mons_weight(corpse_class) == 0 || coinflip())
        return;

    int o = get_item_slot();
    if (o == NON_ITEM)
        return;

    mitm[o].flags     = 0;
    mitm[o].base_type = OBJ_CORPSES;
    mitm[o].plus      = corpse_class;
    mitm[o].plus2     = 0;    // butcher work done
    mitm[o].sub_type  = CORPSE_BODY;
    mitm[o].special   = 210;  // rot time
    mitm[o].colour    = mons_class_colour(corpse_class);
    mitm[o].quantity  = 1;
    mitm[o].props[MONSTER_NUMBER] = short(monster->number);

    if (mitm[o].colour == BLACK)
        mitm[o].colour = monster->colour;

    if (grid_destroys_items(grd[monster->x][monster->y]))
    {
        item_was_destroyed(mitm[o]);
        mitm[o].base_type = OBJ_UNASSIGNED;
        return;
    }
    origin_set_monster(mitm[o], monster);

    // Don't care if 'o' is changed, and it shouldn't be (corpses don't stack).
    move_item_to_grid( &o, monster->x, monster->y );
    if (see_grid(monster->x, monster->y))
    {
        const bool poison = (mons_corpse_effect(monster->type) == CE_POISONOUS
                             && player_res_poison() <= 0);
        tutorial_dissection_reminder(!poison);
    }
}                               // end place_monster_corpse()

static void _tutorial_inspect_kill()
{
    if (Options.tutorial_events[TUT_KILLED_MONSTER])
        learned_something_new(TUT_KILLED_MONSTER);
}

#ifdef DGL_MILESTONES
static std::string _milestone_kill_verb(killer_type killer)
{
    return (killer == KILL_RESET ? "banished " : "killed ");
}

static void _check_kill_milestone(const monsters *mons,
                                 killer_type killer, int i)
{
    if (mons->type == MONS_PLAYER_GHOST)
    {
        std::string milestone = _milestone_kill_verb(killer) + "the ghost of ";
        milestone += get_ghost_description(*mons, true);
        milestone += ".";
        mark_milestone("ghost", milestone);
    }
    else if (mons_is_unique(mons->type))
    {
        mark_milestone("unique",
                       _milestone_kill_verb(killer)
                       + mons->name(DESC_NOCAP_THE, true)
                       + ".");
    }
}
#endif // DGL_MILESTONES

static void _give_monster_experience( monsters *victim,
                                     int killer_index, int experience,
                                     bool victim_was_born_friendly )
{
    if (invalid_monster_index(killer_index))
        return;

    monsters *mon = &menv[killer_index];
    if (!mon->alive())
        return;

    if ((!victim_was_born_friendly || !mons_friendly(mon))
        && !mons_aligned(killer_index, monster_index(victim)))
    {
        if (mon->gain_exp(experience))
        {
            if (you.religion == GOD_SHINING_ONE || you.religion == GOD_BEOGH
                || player_under_penance()
                || !one_chance_in(3))
            {
                return;
            }

            // Randomly bless the follower who gained experience.
            if (you.religion == GOD_SHINING_ONE
                    && random2(you.piety) >= piety_breakpoint(0)
                || you.religion == GOD_BEOGH
                    && random2(you.piety) >= piety_breakpoint(2))
            {
                bless_follower(mon);
            }
        }
    }
}

static void _give_adjusted_experience(monsters *monster, killer_type killer,
                                     bool pet_kill, int killer_index,
                                     unsigned int *exp_gain,
                                     unsigned int *avail_gain)
{
    const int experience = exper_value(monster);

    const bool created_friendly =
        testbits(monster->flags, MF_CREATED_FRIENDLY);
    const bool was_neutral = testbits(monster->flags, MF_WAS_NEUTRAL);
    const bool no_xp = monster->has_ench(ENCH_ABJ);
    const bool already_got_half_xp = testbits(monster->flags, MF_GOT_HALF_XP);

    if (created_friendly || was_neutral || no_xp)
        ; // No experience if monster was created friendly or summoned.
    else if (YOU_KILL(killer))
    {
        int old_lev = you.experience_level;
        if (already_got_half_xp)
            gain_exp( experience / 2, exp_gain, avail_gain );
        else
            gain_exp( experience, exp_gain, avail_gain );

        // Give a message for monsters dying out of sight
        if (exp_gain > 0 && !mons_near(monster)
            && you.experience_level == old_lev)
        {
            mpr("You feel a bit more experienced.");
        }
    }
    else if (pet_kill && !already_got_half_xp)
        gain_exp( experience / 2 + 1, exp_gain, avail_gain );

    if (MON_KILL(killer) && !no_xp)
        _give_monster_experience( monster, killer_index, experience,
                                  created_friendly );
}

static bool _is_pet_kill(killer_type killer, int i)
{
    if (!MON_KILL(killer))
        return (false);

    if (i == ANON_FRIENDLY_MONSTER)
        return (true);

    if (invalid_monster_index(i))
        return (false);

    const monsters *m = &menv[i];
    if (mons_friendly(m)) // This includes enslaved monsters.
        return (true);

    // Check if the monster was confused by you or a friendly, which
    // makes casualties to this monster collateral kills.
    const mon_enchant me = m->get_ench(ENCH_CONFUSION);
    return (me.ench == ENCH_CONFUSION
            && (me.who == KC_YOU || me.who == KC_FRIENDLY));
}

// Elyvilon will occasionally (5% chance) protect the life of
// one of your allies.
static bool _ely_protects_ally(monsters *monster)
{
    ASSERT(you.religion == GOD_ELYVILON);

    if (mons_holiness(monster) != MH_NATURAL
         && mons_holiness(monster) != MH_HOLY
        || !mons_friendly(monster)
        || !mons_near(monster)
        || !player_monster_visible(monster) // for simplicity
        || !one_chance_in(20))
    {
        return (false);
    }

    monster->hit_points = 1;
    snprintf(info, INFO_SIZE, " protects %s%s from harm!%s",
             mons_is_unique(monster->type) ? "" : "your ",
             monster->name(DESC_PLAIN).c_str(),
             coinflip() ? "" : "  You feel responsible.");
    simple_god_message(info);
    lose_piety(1);

    return (true);
}

// Elyvilon retribution effect: Heal hostile monsters that were about to
// be killed by you or one of your friends.
static bool _ely_heals_monster(monsters *monster, killer_type killer, int i)
{
    ASSERT(you.religion != GOD_ELYVILON);
    god_type god = GOD_ELYVILON;

    if (!you.penance[god] || !is_evil_god(you.religion))
        return (false);

    const int ely_penance = you.penance[god];

    if (mons_friendly(monster) || !one_chance_in(10))
        return (false);

    if (MON_KILL(killer) && !invalid_monster_index(i))
    {
        monsters *mon = &menv[i];
        if (!mons_friendly(mon) || !one_chance_in(3))
            return (false);

        if (!mons_near(monster))
            return (false);
    }
    else if (!YOU_KILL(killer))
        return (false);

#ifdef DEBUG_DIAGNOSTICS
    mprf(MSGCH_DIAGNOSTICS, "monster hp: %d, max hp: %d",
         monster->hit_points, monster->max_hit_points);
#endif

    monster->hit_points = std::min(1 + random2(ely_penance/3),
                                   monster->max_hit_points);

#ifdef DEBUG_DIAGNOSTICS
    mprf(MSGCH_DIAGNOSTICS, "new hp: %d, ely penance: %d",
         monster->hit_points, ely_penance);
#endif

    snprintf(info, INFO_SIZE, "%s heals %s%s",
             god_name(god, false).c_str(),
             monster->name(DESC_NOCAP_THE).c_str(),
             monster->hit_points * 2 <= monster->max_hit_points ? "." : "!");

    god_speaks(god, info);
    dec_penance(god, 1 + random2(monster->hit_points/2));

    return (true);
}

static bool _monster_avoided_death(monsters *monster, killer_type killer, int i)
{
    if (monster->hit_points < -25
        || monster->hit_points < -monster->max_hit_points
        || monster->max_hit_points <= 0
        || monster->hit_dice < 1)
    {
        return (false);
    }

    // Elyvilon specials
    if (you.religion == GOD_ELYVILON && _ely_protects_ally(monster))
        return (true);

    if (you.religion != GOD_ELYVILON && _ely_heals_monster(monster, killer, i))
        return (true);

    // Beogh special
    bool convert = false;

    if (you.religion == GOD_BEOGH
        && mons_species(monster->type) == MONS_ORC
        && !player_under_penance() && you.piety >= piety_breakpoint(2)
        && mons_near(monster) && !mons_is_summoned(monster))
    {
        if (YOU_KILL(killer))
            convert = true;
        else if (MON_KILL(killer) && !invalid_monster_index(i))
        {
            monsters *mon = &menv[i];
            if (is_follower(mon) && !one_chance_in(3))
                convert = true;
        }
    }

    // Orcs may convert to Beogh under threat of death, either from you
    // or, less often, your followers.  In both cases, the checks are
    // made against your stats.  You're the potential messiah, after all.
    if (convert)
    {
#ifdef DEBUG_DIAGNOSTICS
        mprf(MSGCH_DIAGNOSTICS, "Death convert attempt on %s, HD: %d, "
             "your xl: %d",
             monster->name(DESC_PLAIN).c_str(),
             monster->hit_dice,
             you.experience_level);
#endif
        if (random2(you.piety) >= piety_breakpoint(0)
            && random2(you.experience_level) >= random2(monster->hit_dice)
            // Bias beaten-up-conversion towards the stronger orcs.
            && random2(monster->hit_dice) > 2)
        {
            beogh_convert_orc(monster, true, MON_KILL(killer));
            return (true);
        }
    }

    return (false);
}

static bool _slime_pit_unlock(bool silent)
{
    unset_level_flags(LFLAG_NO_TELE_CONTROL, silent);

    bool in_los = false;
    if (!silent)
    {
        for (int x = 0; x < GXM && !in_los; ++x)
            for (int y = 0; y < GYM; ++y)
                if (grd[x][y] == DNGN_STONE_WALL
                    && see_grid(x, y))
                {
                    in_los = true;
                    break;
                }
    }

    replace_area_wrapper(DNGN_STONE_WALL, DNGN_CLEAR_ROCK_WALL);

    if (!silent)
    {
        if (in_los)
        {
            mpr("Suddenly, all colour oozes out of the stone walls.",
                MSGCH_MONSTER_ENCHANT);
        }
        else
        {
            mpr("You feel a strange vibration for a moment.",
                MSGCH_MONSTER_ENCHANT);
        }
    }

    return (true);
}

static bool _slime_pit_unlock_offlevel()
{
    return _slime_pit_unlock(true);
}
static bool _slime_pit_unlock_onlevel()
{
    return _slime_pit_unlock(false);
}

static void _fire_monster_death_event(monsters *monster,
                                      killer_type killer,
                                      int i)
{
    // Banished monsters aren't technically dead, so no death event
    // for them.
    if (killer != KILL_RESET)
    {
        dungeon_events.fire_event(
            dgn_event(DET_MONSTER_DIED, monster->pos(), 0,
                      monster_index(monster), killer));

        if (monster->type == MONS_ROYAL_JELLY)
        {
            const level_id target(BRANCH_SLIME_PITS, 6);
            if (is_existing_level(target))
            {
                apply_to_level(
                    target,
                    true,
                    target == level_id::current() ? _slime_pit_unlock_onlevel
                                                  : _slime_pit_unlock_offlevel);
            }
        }
    }
}

void monster_die(monsters *monster, killer_type killer, int i, bool silent)
{
    if (monster->type == -1)
        return;

    if (!silent && _monster_avoided_death(monster, killer, i))
        return;

    mons_clear_trapping_net(monster);

    // Update list of monsters beholding player.
    update_beholders(monster, true);

    const int monster_killed = monster_index(monster);
    const bool hard_reset    = testbits(monster->flags, MF_HARD_RESET);
    const bool gives_xp      = !monster->has_ench(ENCH_ABJ);

          bool in_transit    = false;
          bool drop_items    = !hard_reset;

#ifdef DGL_MILESTONES
    _check_kill_milestone(monster, killer, i);
#endif

    // Award experience for suicide if the suicide was caused by the
    // player.
    if (MON_KILL(killer) && monster_killed == i)
    {
        const mon_enchant me = monster->get_ench(ENCH_CONFUSION);
        if (me.ench == ENCH_CONFUSION && me.who == KC_YOU)
            killer = KILL_YOU_CONF;
    }
    else if (MON_KILL(killer) && monster->has_ench(ENCH_CHARM))
        killer = KILL_YOU_CONF; // Well, it was confused in a sense... (jpeg)

    // Take note!
    if (killer != KILL_RESET && killer != KILL_DISMISSED)
    {
        if (MONST_INTERESTING(monster)
            // XXX yucky hack
            || monster->type == MONS_PLAYER_GHOST
            || monster->type == MONS_PANDEMONIUM_DEMON)
        {
            take_note(Note(NOTE_KILL_MONSTER,
                           monster->type, mons_friendly(monster),
                           monster->name(DESC_NOCAP_A, true).c_str()));
        }
    }

    // From time to time Trog gives you a little bonus
    if (killer == KILL_YOU && you.duration[DUR_BERSERKER])
    {
        if (you.religion == GOD_TROG
            && !player_under_penance() && you.piety > random2(1000))
        {
            int bonus = 3 + random2avg( 10, 2 );

            you.duration[DUR_BERSERKER] += bonus;
            you.duration[DUR_MIGHT] += bonus;
            haste_player( bonus );

            mpr("You feel the power of Trog in you as your rage grows.",
                MSGCH_GOD, GOD_TROG);
        }
        else if (wearing_amulet( AMU_RAGE ) && one_chance_in(30))
        {
            int bonus = 2 + random2(4);

            you.duration[DUR_BERSERKER] += bonus;
            you.duration[DUR_MIGHT] += bonus;
            haste_player( bonus );

            mpr("Your amulet glows a violent red.");
        }
    }

    if (you.prev_targ == monster_killed)
    {
        you.prev_targ = MHITNOT;
        crawl_state.cancel_cmd_repeat();
    }

    if (killer == KILL_YOU)
        crawl_state.cancel_cmd_repeat();

    const bool pet_kill = _is_pet_kill(killer, i);

    if (monster->type == MONS_GIANT_SPORE
        || monster->type == MONS_BALL_LIGHTNING)
    {
        if (monster->hit_points < 1 && monster->hit_points > -15)
            return;
    }
    else if (monster->type == MONS_FIRE_VORTEX
             || monster->type == MONS_SPATIAL_VORTEX)
    {
        if (!silent)
        {
            simple_monster_message( monster, " dissipates!",
                                    MSGCH_MONSTER_DAMAGE, MDAM_DEAD );
            silent = true;
        }

        if (monster->type == MONS_FIRE_VORTEX)
        {
            place_cloud(CLOUD_FIRE, monster->x, monster->y, 2 + random2(4),
                        monster->kill_alignment());
        }

        if (killer == KILL_RESET)
            killer = KILL_DISMISSED;
    }
    else if (monster->type == MONS_SIMULACRUM_SMALL
             || monster->type == MONS_SIMULACRUM_LARGE)
    {
        if (!silent)
        {
            simple_monster_message( monster, " vapourises!",
                                    MSGCH_MONSTER_DAMAGE,  MDAM_DEAD );
            silent = true;
        }

        place_cloud(CLOUD_COLD, monster->x, monster->y, 2 + random2(4),
                    monster->kill_alignment());

        if (killer == KILL_RESET)
            killer = KILL_DISMISSED;
    }
    else if (monster->type == MONS_DANCING_WEAPON)
    {
        if (!hard_reset)
        {
            if (killer == KILL_RESET)
                killer = KILL_DISMISSED;
        }

        if (!silent && !hard_reset)
        {
            int w_idx = monster->inv[MSLOT_WEAPON];
            if (w_idx != NON_ITEM && !(mitm[w_idx].flags & ISFLAG_SUMMONED))
            {
                simple_monster_message( monster, " falls from the air.",
                                        MSGCH_MONSTER_DAMAGE, MDAM_DEAD );
                silent = true;
            }
            else
                killer = KILL_RESET;
        }
    }

    bool death_message = (!silent && mons_near(monster)
                          && player_monster_visible(monster));

    switch (killer)
    {
        case KILL_YOU:          // You kill in combat.
        case KILL_YOU_MISSILE:  // You kill by missile or beam.
        case KILL_YOU_CONF:     // You kill by confusion.
        {
            const bool bad_kill    = god_hates_killing(you.religion, monster);
            const bool was_neutral = testbits(monster->flags, MF_WAS_NEUTRAL);
            const bool created_friendly =
                testbits(monster->flags, MF_CREATED_FRIENDLY);

            if (death_message)
            {
                mprf(MSGCH_MONSTER_DAMAGE, MDAM_DEAD, "You %s %s!",
                     _wounded_damaged(monster->type) ? "destroy" : "kill",
                     monster->name(DESC_NOCAP_THE).c_str());

                if ((created_friendly || was_neutral) && gives_xp)
                    mpr("That felt strangely unrewarding.");
            }

            // killing triggers tutorial lesson
            _tutorial_inspect_kill();

            // Prevent summoned creatures from being good kills.
            if (bad_kill || !created_friendly && gives_xp)
            {
                if (mons_holiness(monster) == MH_NATURAL)
                {
                    did_god_conduct(DID_KILL_LIVING,
                                    monster->hit_dice, true, monster);
                }

                if (mons_holiness(monster) == MH_UNDEAD)
                {
                    did_god_conduct(DID_KILL_UNDEAD,
                                    monster->hit_dice, true, monster);
                }

                if (mons_holiness(monster) == MH_DEMONIC)
                {
                    did_god_conduct(DID_KILL_DEMON,
                                    monster->hit_dice, true, monster);
                }

                if (mons_is_evil(monster)
                    && mons_holiness(monster) == MH_NATURAL)
                {
                    did_god_conduct(DID_KILL_NATURAL_EVIL,
                                    monster->hit_dice, true, monster);
                }

                if (mons_is_chaotic(monster))
                {
                    did_god_conduct(DID_KILL_CHAOTIC,
                                    monster->hit_dice, true, monster);
                }

                // jmf: Trog hates wizards.
                if (mons_is_magic_user(monster))
                {
                    did_god_conduct(DID_KILL_WIZARD,
                                    monster->hit_dice, true, monster);
                }

                // Beogh hates priests of other gods.
                if (mons_class_flag(monster->type, M_PRIEST))
                {
                    did_god_conduct(DID_KILL_PRIEST,
                                    monster->hit_dice, true, monster);
                }

                if (mons_is_holy(monster))
                {
                    did_god_conduct(DID_KILL_HOLY, monster->hit_dice,
                                    true, monster);
                }
            }

            // Divine health and mana restoration doesn't happen when
            // killing born-friendly monsters.  The mutation still
            // applies, however.
            if (player_mutation_level(MUT_DEATH_STRENGTH)
                || (!created_friendly && gives_xp
                    && (you.religion == GOD_MAKHLEB
                        || you.religion == GOD_SHINING_ONE
                           && mons_is_evil_or_unholy(monster))
                    && !player_under_penance()
                    && random2(you.piety) >= piety_breakpoint(0)))
            {
                if (you.hp < you.hp_max)
                {
                    mpr("You feel a little better.");
                    inc_hp(monster->hit_dice + random2(monster->hit_dice),
                           false);
                }
            }

            if (!created_friendly && gives_xp
                && (you.religion == GOD_MAKHLEB
                    || you.religion == GOD_VEHUMET
                    || you.religion == GOD_SHINING_ONE
                       && mons_is_evil_or_unholy(monster))
                && !player_under_penance()
                && random2(you.piety) >= piety_breakpoint(0))
            {
                if (you.magic_points < you.max_magic_points)
                {
                    mpr("You feel your power returning.");
                    inc_mp( 1 + random2(monster->hit_dice / 2), false );
                }
            }

            // Randomly bless a follower.
            if (!created_friendly
                && gives_xp
                && (you.religion == GOD_BEOGH
                        && mons_holiness(monster) == MH_NATURAL
                        && random2(you.piety) >= piety_breakpoint(2))
                && !player_under_penance())
            {
                bless_follower();
            }

            if (you.duration[DUR_DEATH_CHANNEL]
                && gives_xp
                && mons_holiness(monster) == MH_NATURAL
                && mons_weight(mons_species(monster->type)))
            {
                const monster_type spectre = mons_species(monster->type);

                // Don't allow 0-headed hydras to become spectral hydras.
                if ((spectre != MONS_HYDRA || monster->number != 0)
                    && create_monster(
                        mgen_data(MONS_SPECTRAL_THING, BEH_FRIENDLY,
                                  0, monster->pos(), you.pet_target,
                                  0, static_cast<god_type>(you.attribute[ATTR_DIVINE_DEATH_CHANNEL]),
                                  spectre, monster->number)) != -1)
                {
                    if (death_message)
                        mpr("A glowing mist starts to gather...");
                }
            }
            break;
        }

        case KILL_MON:          // Monster kills in combat
        case KILL_MON_MISSILE:  // Monster kills by missile or beam
            if (!silent)
            {
                simple_monster_message(monster,
                                       _wounded_damaged(monster->type) ?
                                       " is destroyed!" : " dies!",
                                       MSGCH_MONSTER_DAMAGE,
                                       MDAM_DEAD);
            }

            // No piety loss if god gifts killed by other monsters.
            if (mons_friendly(monster) && !mons_is_god_gift(monster))
            {
                did_god_conduct(DID_FRIEND_DIED, 1 + (monster->hit_dice / 2),
                                true, monster);
            }

            // Trying to prevent summoning abuse here, so we're trying to
            // prevent summoned creatures from being done_good kills. Only
            // affects creatures which were friendly when summoned.
            if (!testbits(monster->flags, MF_CREATED_FRIENDLY) && gives_xp
                && pet_kill)
            {
                bool notice = false;
                const bool anon = (i == ANON_FRIENDLY_MONSTER);

                const mon_holy_type targ_holy = mons_holiness(monster),
                    attacker_holy = anon ? MH_NATURAL : mons_holiness(&menv[i]);

                if (attacker_holy == MH_UNDEAD)
                {
                    if (targ_holy == MH_NATURAL)
                    {
                        // Yes, this is a hack, but it makes sure that confused
                        // monsters doing the kill are not referred to as
                        // "slave", and I think it's okay that Yredelemnul
                        // ignores kills done by confused monsters as opposed
                        // to enslaved or friendly ones. (jpeg)
                        if (mons_friendly(&menv[i]))
                        {
                            notice |=
                                did_god_conduct(DID_LIVING_KILLED_BY_UNDEAD_SLAVE,
                                            monster->hit_dice);
                        }
                        else
                        {
                            notice |=
                                did_god_conduct(DID_LIVING_KILLED_BY_SERVANT,
                                            monster->hit_dice);
                        }
                    }
                }
                else if (you.religion == GOD_SHINING_ONE
                         || you.religion == GOD_VEHUMET
                         || you.religion == GOD_MAKHLEB
                         || you.religion == GOD_LUGONU
                         || !anon && mons_is_god_gift(&menv[i]))
                {
                    // Yes, we are splitting undead pets from the others
                    // as a way to focus Necromancy vs Summoning (ignoring
                    // Summon Wraith here)... at least we're being nice and
                    // putting the natural creature Summons together with
                    // the Demon ones.  Note that Vehumet gets a free
                    // pass here since those followers are assumed to
                    // come from Summoning spells...  the others are
                    // from invocations (Zin, TSO, Makh, Kiku). -- bwr

                    if (targ_holy == MH_NATURAL)
                    {
                        notice |= did_god_conduct( DID_LIVING_KILLED_BY_SERVANT,
                                                   monster->hit_dice );

                        if (mons_is_evil(monster))
                        {
                            notice |=
                                did_god_conduct(
                                        DID_NATURAL_EVIL_KILLED_BY_SERVANT,
                                        monster->hit_dice );
                        }
                    }
                    else if (targ_holy == MH_DEMONIC)
                    {
                        notice |= did_god_conduct( DID_DEMON_KILLED_BY_SERVANT,
                                                   monster->hit_dice );
                    }
                    else if (targ_holy == MH_UNDEAD)
                    {
                        notice |= did_god_conduct( DID_UNDEAD_KILLED_BY_SERVANT,
                                                   monster->hit_dice );
                    }
                }

                // Holy kills are always noticed.
                if (targ_holy == MH_HOLY)
                {
                    notice |= did_god_conduct( DID_HOLY_KILLED_BY_SERVANT,
                                               monster->hit_dice );
                }

                if (you.religion == GOD_VEHUMET
                    && notice
                    && !player_under_penance()
                    && random2(you.piety) >= piety_breakpoint(0))
                {
                    // Vehumet - only for non-undead servants (coding
                    // convenience, no real reason except that Vehumet
                    // prefers demons).
                    if (you.magic_points < you.max_magic_points)
                    {
                        mpr("You feel your power returning.");
                        inc_mp( 1 + random2(monster->hit_dice / 2), false );
                    }
                }

                if (you.religion == GOD_SHINING_ONE
                    && mons_is_evil_or_unholy(monster)
                    && !player_under_penance()
                    && random2(you.piety) >= piety_breakpoint(0)
                    && !invalid_monster_index(i))
                {
                    monsters *mon = &menv[i];

                    // Randomly bless the follower who killed.
                    if (!one_chance_in(3) && mon->alive()
                        && bless_follower(mon))
                    {
                        break;
                    }

                    if (mon->alive() && mon->hit_points < mon->max_hit_points)
                    {
                        simple_monster_message(mon, " looks invigorated.");
                        heal_monster( mon, 1 + random2(monster->hit_dice / 4),
                                      false );
                    }
                }

                // Randomly bless the follower who killed.
                if (you.religion == GOD_BEOGH
                    && mons_holiness(monster) == MH_NATURAL
                    && random2(you.piety) >= piety_breakpoint(2)
                    && !player_under_penance()
                    && !one_chance_in(3)
                    && !invalid_monster_index(i))
                {
                    bless_follower(&menv[i]);
                }

            }
            break;

        // Monster killed by trap/inanimate thing/itself/poison not from you.
        case KILL_MISC:
            if (!silent)
            {
                simple_monster_message(monster,
                                       _wounded_damaged(monster->type) ?
                                       " is destroyed!" : " dies!",
                                       MSGCH_MONSTER_DAMAGE,
                                       MDAM_DEAD);
            }
            break;

        case KILL_RESET:
        // Monster doesn't die, just goes back to wherever it came from
        // This must only be called by monsters running out of time (or
        // abjuration), because it uses the beam variables! Or does it???
            if (!silent)
            {
                simple_monster_message( monster,
                                        " disappears in a puff of smoke!" );
            }

            place_cloud( random_smoke_type(),
                         monster->x, monster->y, 1 + random2(3),
                         monster->kill_alignment() );

            // KILL_RESET monsters no longer lose their whole inventory, only
            // items they were generated with.
            if (!monster->needs_transit())
            {
                // A banished monster that doesn't go on the transit list
                // loses all items.
                if (!mons_is_summoned(monster))
                    monster->destroy_inventory();
                break;
            }

            // Monster goes to the Abyss.
            monster->flags |= MF_BANISHED;
            monster->set_transit( level_id(LEVEL_ABYSS) );
            in_transit = true;
            monster->destroy_inventory();
            // Make monster stop patrolling and/or travelling.
            monster->patrol_point = coord_def(0, 0);
            monster->travel_path.clear();
            monster->travel_target = MTRAV_NONE;
            break;

        case KILL_DISMISSED:
            break;

        default:
            monster->destroy_inventory();
            break;
    }

    if (monster->type == MONS_BORIS && !in_transit)
    {
        // XXX: Actual blood curse effect for Boris? -- bwr

        // Provide the player with an ingame clue to Boris' return.  -- bwr
        std::string msg = getSpeakString("Boris return_speech");
        if (!msg.empty())
        {
            msg = do_mon_str_replacements(msg, monster);
            mpr(msg.c_str(), MSGCH_TALK);
        }

        // Now that Boris is dead, he's a valid target for monster
        // creation again. -- bwr
        you.unique_creatures[ monster->type ] = false;
    }
    else if (!mons_is_summoned(monster))
    {
        if (monster->type == MONS_MUMMY)
        {
            if (YOU_KILL(killer) && killer != KILL_YOU_CONF)
                curse_an_item(true);
        }
        else if (monster->type == MONS_GUARDIAN_MUMMY
                 || monster->type == MONS_GREATER_MUMMY
                 || monster->type == MONS_MUMMY_PRIEST)
        {
            if (YOU_KILL(killer) && killer != KILL_YOU_CONF)
            {
                mpr("You feel extremely nervous for a moment...",
                    MSGCH_MONSTER_SPELL);

                miscast_effect( SPTYP_NECROMANCY,
                                3 + (monster->type == MONS_GREATER_MUMMY) * 8
                                  + (monster->type == MONS_MUMMY_PRIEST) * 5,
                                random2avg(88, 3), 100, "a mummy death curse" );
            }
        }
    }

    if (killer != KILL_RESET && killer != KILL_DISMISSED)
    {
        you.kills->record_kill(monster, killer, pet_kill);

        kill_category kc =
            (killer == KILL_YOU || killer == KILL_YOU_MISSILE) ? KC_YOU :
            (pet_kill)?                                          KC_FRIENDLY :
                                                                 KC_OTHER;

        unsigned int exp_gain = 0, avail_gain = 0;
        _give_adjusted_experience(monster, killer, pet_kill, i,
                                  &exp_gain, &avail_gain);

        PlaceInfo& curr_PlaceInfo = you.get_place_info();
        PlaceInfo  delta;

        delta.mon_kill_num[kc]++;
        delta.mon_kill_exp       += exp_gain;
        delta.mon_kill_exp_avail += avail_gain;

        you.global_info += delta;
        you.global_info.assert_validity();

        curr_PlaceInfo += delta;
        curr_PlaceInfo.assert_validity();

        if (monster->has_ench(ENCH_ABJ))
        {
            if (mons_weight(mons_species(monster->type)))
            {
                simple_monster_message(monster,
                            "'s corpse disappears in a puff of smoke!");

                place_cloud( random_smoke_type(),
                             monster->x, monster->y, 1 + random2(3),
                             monster->kill_alignment() );
            }
        }
        else
        {
            // Have to add case for disintegration effect here? {dlb}
            _place_monster_corpse(monster);
        }
    }

    _fire_monster_death_event(monster, killer, i);

    const coord_def mwhere = monster->pos();
    if (drop_items)
        _monster_drop_ething(monster, YOU_KILL(killer) || pet_kill);
    monster_cleanup(monster);

    // Force redraw for monsters that die.
    if (see_grid(mwhere))
    {
        view_update_at(mwhere);
        update_screen();
    }
}

void monster_cleanup(monsters *monster)
{
    unsigned int monster_killed = monster_index(monster);
    monster->reset();

    for (int dmi = 0; dmi < MAX_MONSTERS; dmi++)
        if (menv[dmi].foe == monster_killed)
            menv[dmi].foe = MHITNOT;

    if (you.pet_target == monster_killed)
        you.pet_target = MHITNOT;
}

static bool _jelly_divide(monsters *parent)
{
    int jex = 0, jey = 0;       // loop variables {dlb}
    bool foundSpot = false;     // to rid code of hideous goto {dlb}
    monsters *child = NULL;     // value determined with loop {dlb}

    if (!mons_class_flag(parent->type, M_SPLITS) || parent->hit_points == 1)
        return (false);

    // First, find a suitable spot for the child {dlb}:
    for (jex = -1; jex < 3; jex++)
    {
        // Loop moves beyond those tiles contiguous to parent {dlb}:
        if (jex > 1)
            return (false);

        for (jey = -1; jey < 2; jey++)
        {
            // 10-50 for now - must take clouds into account:
            if (mgrd[parent->x + jex][parent->y + jey] == NON_MONSTER
                && parent->can_pass_through(parent->x + jex, parent->y + jey)
                && (parent->x + jex != you.x_pos || parent->y + jey != you.y_pos))
            {
                foundSpot = true;
                break;
            }
        }

        if (foundSpot)
            break;
    }   // end of for jex

    int k = 0;                  // must remain outside loop that follows {dlb}

    // Now that we have a spot, find a monster slot {dlb}:
    for (k = 0; k < MAX_MONSTERS; k++)
    {
        child = &menv[k];

        if (child->type == -1)
            break;
        else if (k == MAX_MONSTERS - 1)
            return (false);
    }

    // Handle impact of split on parent {dlb}:
    parent->max_hit_points /= 2;

    if (parent->hit_points > parent->max_hit_points)
        parent->hit_points = parent->max_hit_points;

    parent->init_experience();
    parent->experience = parent->experience * 3 / 5 + 1;

    // Create child {dlb}:
    // This is terribly partial and really requires
    // more thought as to generation ... {dlb}
    *child = *parent;
    child->max_hit_points  = child->hit_points;
    child->speed_increment = 70 + random2(5);
    child->x = parent->x + jex;
    child->y = parent->y + jey;

    mgrd[child->x][child->y] = k;

    if (!simple_monster_message(parent, " splits in two!"))
    {
        if (!silenced(parent->x, parent->y) || !silenced(child->x, child->y))
            mpr("You hear a squelching noise.", MSGCH_SOUND);
    }

    return (true);
}                               // end jelly_divide()

// If you're invis and throw/zap whatever, alerts menv to your position.
void alert_nearby_monsters(void)
{
    monsters *monster = 0;       // NULL {dlb}

    for (int it = 0; it < MAX_MONSTERS; it++)
    {
        monster = &menv[it];

        // Judging from the above comment, this function isn't
        // intended to wake up monsters, so we're only going to
        // alert monsters that aren't sleeping.  For cases where an
        // event should wake up monsters and alert them, I'd suggest
        // calling noisy() before calling this function. -- bwr
        if (monster->type != -1
            && mons_near(monster)
            && !mons_is_sleeping(monster))
        {
            behaviour_event(monster, ME_ALERT, MHITYOU);
        }
    }
}

static bool _valid_morph( monsters *monster, int new_mclass )
{
    const dungeon_feature_type current_tile = grd[monster->x][monster->y];

    // 'morph targets are _always_ "base" classes, not derived ones.
    new_mclass = mons_species(new_mclass);

    // [ds] Non-base draconians are much more trouble than their HD
    // suggests.
    if (mons_genus(new_mclass) == MONS_DRACONIAN
        && new_mclass != MONS_DRACONIAN
        && !player_in_branch(BRANCH_HALL_OF_ZOT)
        && !one_chance_in(10))
    {
        return (false);
    }

    // Various inappropriate polymorph targets.
    if (mons_class_holiness( new_mclass ) != mons_holiness( monster )
        || mons_class_flag( new_mclass, M_UNIQUE)       // no uniques
        || mons_class_flag( new_mclass, M_NO_EXP_GAIN ) // not helpless
        || new_mclass == mons_species( monster->type )  // must be different
        || new_mclass == MONS_PROGRAM_BUG
        || new_mclass == MONS_SHAPESHIFTER
        || new_mclass == MONS_GLOWING_SHAPESHIFTER

        // These require manual setting of mons.base_monster to indicate
        // what they are a skeleton/zombie/simulacrum/spectral thing of,
        // which we currently aren't smart enough to handle.
        || mons_class_is_zombified(new_mclass)

        // These shouldn't happen anyways (demons unaffected + holiness check),
        // but if we ever do have polydemon, these will be needed:
        || new_mclass == MONS_PLAYER_GHOST
        || new_mclass == MONS_PANDEMONIUM_DEMON

        // Monsters still under development
        || new_mclass == MONS_ROCK_WORM
        || new_mclass == MONS_TRAPDOOR_SPIDER

        // Other poly-unsuitable things.
        || new_mclass == MONS_ROYAL_JELLY
        || new_mclass == MONS_ORB_GUARDIAN
        || new_mclass == MONS_ORANGE_STATUE
        || new_mclass == MONS_SILVER_STATUE
        || new_mclass == MONS_ICE_STATUE
        || new_mclass >= MONS_GERYON && new_mclass <= MONS_ERESHKIGAL)
    {
        return (false);
    }

    // Determine if the monster is happy on current tile.
    return (monster_habitable_grid(new_mclass, current_tile));
}

static bool _is_poly_power_unsuitable(
    poly_power_type power,
    int src_pow,
    int tgt_pow,
    int relax)
{
    switch (power)
    {
    case PPT_LESS:
        return (tgt_pow > src_pow - 3 + (relax * 3) / 2)
                || (power == PPT_LESS && (tgt_pow < src_pow - (relax / 2)));
    case PPT_MORE:
        return (tgt_pow < src_pow + 2 - relax)
                || (power == PPT_MORE && (tgt_pow > src_pow + relax));
    default:
    case PPT_SAME:
        return (tgt_pow < src_pow - relax)
                || (tgt_pow > src_pow + (relax * 3) / 2);
    }
}

// if targetc == RANDOM_MONSTER, then relpower indicates the desired
// power of the new monster, relative to the current monster.
// Relaxation still takes effect when needed, no matter what relpower
// says.
bool monster_polymorph(monsters *monster, monster_type targetc,
                       poly_power_type power,
                       bool force_beh)
{
    std::string str_polymon;
    int source_power, target_power, relax;
    int tries = 1000;

    // Used to be mons_power, but that just returns hit_dice
    // for the monster class.  By using the current hit dice
    // the player gets the opportunity to use draining more
    // effectively against shapeshifters. -- bwr
    source_power = monster->hit_dice;
    relax = 1;

    if (targetc == RANDOM_MONSTER)
    {
        do
        {
            // Pick a monster that's guaranteed happy at this grid.
            targetc = random_monster_at_grid(monster->x, monster->y);

            // Valid targets are always base classes ([ds] which is unfortunate
            // in that well-populated monster classes will dominate polymorphs).
            targetc = mons_species(targetc);

            target_power = mons_power(targetc);

            if (one_chance_in(200))
                relax++;

            if (relax > 50)
                return (simple_monster_message( monster, " shudders."));
        }
        while (tries-- && (!_valid_morph(monster, targetc)
                           || _is_poly_power_unsuitable(power, source_power,
                                                        target_power, relax)));
    }

    if (!_valid_morph(monster, targetc))
        return simple_monster_message(monster, " looks momentarily different.");

    // If old monster is visible to the player, and is interesting,
    // then note why the interesting monster went away.
    if (player_monster_visible(monster) && mons_near(monster)
        && MONST_INTERESTING(monster))
    {
        take_note(Note(NOTE_POLY_MONSTER, monster->type, 0,
                       monster->name(DESC_CAP_A, true).c_str()));
    }

    // Messaging.
    bool can_see = you.can_see(monster);

    if (monster->has_ench(ENCH_GLOWING_SHAPESHIFTER, ENCH_SHAPESHIFTER))
        str_polymon = " changes into ";
    else if (targetc == MONS_PULSATING_LUMP)
        str_polymon = " degenerates into ";
    else
        str_polymon = " evaporates and reforms as ";

    if (!can_see)
        str_polymon += "something you cannot see!";
    else
    {
        str_polymon += mons_type_name(targetc, DESC_NOCAP_A);

        if (targetc == MONS_PULSATING_LUMP)
            str_polymon += " of flesh";

        str_polymon += "!";
    }

    bool player_messaged = simple_monster_message(monster, str_polymon.c_str());

    // Even if the monster transforms from one type that can behold the
    // player into a different type which can also behold the player,
    // the polymorph disrupts the beholding process.  Do this before
    // changing monster->type, since unbeholding can only happen while
    // the monster is still a mermaid.
    update_beholders(monster, true);

    // the actual polymorphing:
    const int  old_hp     = monster->hit_points;
    const int  old_hp_max = monster->max_hit_points;
    const bool old_mon_caught = mons_is_caught(monster);
    const char old_ench_countdown = monster->ench_countdown;

    // deal with mons_sec
    monster->type         = targetc;
    monster->base_monster = MONS_PROGRAM_BUG;
    monster->number       = 0;

    mon_enchant abj       = monster->get_ench(ENCH_ABJ);
    mon_enchant charm     = monster->get_ench(ENCH_CHARM);
    mon_enchant neutral   = monster->get_ench(ENCH_NEUTRAL);
    mon_enchant shifter   = monster->get_ench(ENCH_GLOWING_SHAPESHIFTER,
                                              ENCH_SHAPESHIFTER);

    // Note: define_monster() will clear out all enchantments! -- bwr
    define_monster( monster_index(monster) );

    monster->add_ench(abj);
    monster->add_ench(charm);
    monster->add_ench(neutral);
    monster->add_ench(shifter);

    monster->ench_countdown = old_ench_countdown;

    if (mons_class_flag(monster->type, M_INVIS))
        monster->add_ench(ENCH_INVIS);

    if (!player_messaged && mons_near(monster)
        && player_monster_visible(monster))
    {
        mprf("%s appears out of thin air!", monster->name(DESC_CAP_A).c_str());
        player_messaged = true;
    }

    monster->hit_points = monster->max_hit_points
                                * ((old_hp * 100) / old_hp_max) / 100
                                + random2(monster->max_hit_points);

    monster->hit_points = std::min(monster->max_hit_points,
                                   monster->hit_points);

    monster->speed_increment = 67 + random2(6);

    _monster_drop_ething(monster);

    // New monster type might be interesting.
    mark_interesting_monst(monster);

    // If new monster is visible to player, then we've seen it.
    if (player_monster_visible(monster) && mons_near(monster))
        seen_monster(monster);

    if (old_mon_caught)
        check_net_will_hold_monster(monster);

    if (!force_beh)
        player_angers_monster(monster);

    return (player_messaged);
}

bool monster_blink(monsters *monster)
{
    int nx, ny;

    {
        unwind_var<env_show_grid> visible_grid( env.show );
        losight(env.show, grd, monster->x, monster->y, true);

        if (!random_near_space(monster->x, monster->y, nx, ny,
                               false, true,
                               !mons_wont_attack(monster)))
        {
            return (false);
        }
    }

    mons_clear_trapping_net(monster);

    mgrd[monster->x][monster->y] = NON_MONSTER;

    const coord_def oldplace = monster->pos();
    monster->x = nx;
    monster->y = ny;

    mgrd[nx][ny] = monster_index(monster);

    if (player_monster_visible(monster) && mons_near(monster))
        seen_monster(monster);

    monster->check_redraw(oldplace);
    monster->apply_location_effects();

    return (true);
}

// allow_adjacent:  allow target to be adjacent to origin.
// restrict_LOS:    restrict target to be within PLAYER line of sight.
bool random_near_space(int ox, int oy, int &tx, int &ty, bool allow_adjacent,
                       bool restrict_LOS, bool forbid_sanctuary)
{
    // This might involve ray tracing (via num_feats_between()), so
    // cache results to avoid duplicating ray traces.
    FixedArray<bool, 14, 14> tried;
    tried.init(false);

    // Is the monster on the other side of a transparent wall?
    bool trans_wall_block  = trans_wall_blocking(ox, oy);
    bool origin_is_player  = (you.pos() == coord_def(ox, oy));
    int  min_walls_between = 0;

    // Skip ray tracing if possible.
    if (trans_wall_block)
    {
        min_walls_between = num_feats_between(ox, oy, you.x_pos, you.y_pos,
                                              DNGN_CLEAR_ROCK_WALL,
                                              DNGN_CLEAR_PERMAROCK_WALL);
    }

    int tries = 0;
    while (tries++ < 150)
    {
        int dx = random2(14);
        int dy = random2(14);

        tx = ox - 6 + dx;
        ty = oy - 6 + dy;

        // Origin is not 'near'.
        if (tx == ox && ty == oy)
            continue;

        if (tried[dx][dy])
            continue;

        tried[dx][dy] = true;

        if (!in_bounds(tx, ty)
            || restrict_LOS && !see_grid(tx, ty)
            || grd[tx][ty] < DNGN_SHALLOW_WATER
            || mgrd[tx][ty] != NON_MONSTER
            || tx == you.x_pos && ty == you.y_pos
            || !allow_adjacent && distance(ox, oy, tx, ty) <= 2
            || forbid_sanctuary && is_sanctuary(tx, ty))
        {
            continue;
        }

        if (!trans_wall_block && !origin_is_player)
            return (true);

        // If the monster is on a visible square which is on the other
        // side of one or more translucent from the player, then it
        // can only blink through translucent walls if the end point
        // is either not visible to the player, or there are at least
        // as many translucent walls between the player and the end
        // point as between the player and the start point.  However,
        // monsters can still blink through translucent walls to get
        // away from the player, since in the absence of translucent
        // walls monsters can blink to places which are not in either
        // the monster's nor the player's LOS.
        if (!origin_is_player && !see_grid(tx, ty))
            return (true);

        // Player can't randomly pass through translucent walls.
        if (origin_is_player)
        {
            if (see_grid_no_trans(tx, ty))
                return (true);

            continue;
        }

        int walls_passed = num_feats_between(tx, ty, ox, oy,
                                             DNGN_CLEAR_ROCK_WALL,
                                             DNGN_CLEAR_PERMAROCK_WALL,
                                             true, true);
        if (walls_passed == 0)
            return (true);

        // Player can't randomly pass through translucent walls.
        if (origin_is_player)
            continue;

        int walls_between = num_feats_between(tx, ty, you.x_pos, you.y_pos,
                                              DNGN_CLEAR_ROCK_WALL,
                                              DNGN_CLEAR_PERMAROCK_WALL);

        if (walls_between >= min_walls_between)
            return (true);
    }

    return (false);
}

static bool _habitat_okay( const monsters *monster, dungeon_feature_type targ )
{
    return (monster_habitable_grid(monster, targ));
}

// This doesn't really swap places, it just sets the monster's
// position equal to the player (the player has to be moved afterwards).
// It also has a slight problem with the fact that if the player is
// levitating over an inhospitable habitat for the monster the monster
// will be put in a place it normally couldn't go (this could be a
// feature because it prevents insta-killing).  In order to prevent
// that little problem, we go looking for a square for the monster
// to "scatter" to instead... and if we can't find one the monster
// just refuses to be swapped (not a bug, this is intentionally
// avoiding the insta-kill).  Another option is to look a bit
// wider for a vaild square (either by a last attempt blink, or
// by looking at a wider radius)...  insta-killing should be a
// last resort in this function (especially since Tome, Dig, and
// Summoning can be used to set up death traps).  If worse comes
// to worse, at least consider making the Swap spell not work
// when the player is over lava or water (if the player wants to
// swap pets to their death, we can let that go). -- bwr
bool swap_places(monsters *monster)
{
    coord_def loc;
    if (swap_check(monster, loc))
    {
        swap_places(monster, loc);
        return true;
    }
    return false;
}

// Swap monster to this location.  Player is swapped elsewhere.
bool swap_places(monsters *monster, const coord_def &loc)
{
    ASSERT(map_bounds(loc));
    ASSERT(_habitat_okay(monster, grd(loc)));

    mpr("You swap places.");

    mgrd[monster->x][monster->y] = NON_MONSTER;

    monster->x = loc.x;
    monster->y = loc.y;

    mgrd[monster->x][monster->y] = monster_index(monster);

    return true;
}

// Returns true if this is a valid swap for this monster.  If true, then
// the valid location is set in loc.
bool swap_check(monsters *monster, coord_def &loc)
{
    int loc_x = you.x_pos;
    int loc_y = you.y_pos;

    const int mgrid = grd[monster->x][monster->y];

    // Don't move onto dangerous terrain.
    if (is_grid_dangerous(mgrid))
    {
        canned_msg(MSG_UNTHINKING_ACT);
        return (false);
    }

    if (mons_is_caught(monster))
    {
        simple_monster_message(monster, " is held in a net!");
        return (false);
    }

    const bool mon_dest_okay = _habitat_okay( monster, grd[loc_x][loc_y] );
    const bool you_dest_okay =
        !is_grid_dangerous(mgrid)
        || yesno("Do you really want to step there?", false, 'n');

    if (!you_dest_okay)
        return (false);

    bool swap = mon_dest_okay;

    // Choose an appropriate habitat square at random around the target.
    if (!swap)
    {
        int num_found = 0;
        int temp_x, temp_y;

        for (int x = -1; x <= 1; x++)
        {
            temp_x = you.x_pos + x;
            if (temp_x < 0 || temp_x >= GXM)
                continue;

            for (int y = -1; y <= 1; y++)
            {
                if (x == 0 && y == 0)
                    continue;

                temp_y = you.y_pos + y;
                if (temp_y < 0 || temp_y >= GYM)
                    continue;

                if (mgrd[temp_x][temp_y] == NON_MONSTER
                    && _habitat_okay( monster, grd[temp_x][temp_y] ))
                {
                    // Found an appropiate space... check if we
                    // switch the current choice to this one.
                    num_found++;
                    if (one_chance_in(num_found))
                    {
                        loc_x = temp_x;
                        loc_y = temp_y;
                    }
                }
            }
        }

        if (num_found)
            swap = true;
    }

    if (swap)
    {
        loc.x = loc_x;
        loc.y = loc_y;
    }
    else
    {
        // Might not be ideal, but it's better than insta-killing
        // the monster... maybe try for a short blink instead? -- bwr
        simple_monster_message( monster, " resists." );
        // FIXME: AI_HIT_MONSTER isn't ideal.
        interrupt_activity( AI_HIT_MONSTER, monster );
    }

    return (swap);
}                               // end swap_places()

void mons_get_damage_level( const monsters* monster, std::string& desc,
                            mon_dam_level_type& dam_level )
{
    if (monster->hit_points <= monster->max_hit_points / 6)
    {
        desc += "almost ";
        desc += _wounded_damaged(monster->type) ? "destroyed" : "dead";
        dam_level = MDAM_ALMOST_DEAD;
        return;
    }

    if (monster->hit_points <= monster->max_hit_points / 4)
    {
        desc += "severely ";
        dam_level = MDAM_SEVERELY_DAMAGED;
    }
    else if (monster->hit_points <= monster->max_hit_points / 3)
    {
        desc += "heavily ";
        dam_level = MDAM_HEAVILY_DAMAGED;
    }
    else if (monster->hit_points <= (monster->max_hit_points * 3) / 4)
    {
        desc += "moderately ";
        dam_level = MDAM_MODERATELY_DAMAGED;
    }
    else if (monster->hit_points < monster->max_hit_points)
    {
        desc += "lightly ";
        dam_level = MDAM_LIGHTLY_DAMAGED;
    }
    else
    {
        desc += "not ";
        dam_level = MDAM_OKAY;
    }

    desc += _wounded_damaged(monster->type) ? "damaged" : "wounded";
}

void print_wounds(const monsters *monster)
{
    if (monster->type == -1)
        return;

    if (monster->hit_points == monster->max_hit_points
        || monster->hit_points < 1)
    {
        return;
    }

    if (monster_descriptor(monster->type, MDSC_NOMSG_WOUNDS))
        return;

    std::string desc;
    mon_dam_level_type dam_level;
    mons_get_damage_level(monster, desc, dam_level);

    desc.insert(0, " is ");
    desc += ".";
    simple_monster_message(monster, desc.c_str(), MSGCH_MONSTER_DAMAGE,
                           dam_level);
}

// (true == 'damaged') [constructs, undead, etc.]
// and (false == 'wounded') [living creatures, etc.] {dlb}
static bool _wounded_damaged(int monster_type)
{
    // this schema needs to be abstracted into real categories {dlb}:
    const mon_holy_type holy = mons_class_holiness(monster_type);
    if (holy == MH_UNDEAD || holy == MH_NONLIVING || holy == MH_PLANT)
        return (true);

    return (false);
}                               // end _wounded_damaged()

//---------------------------------------------------------------
//
// 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, int event, int src,
                     int src_x, int src_y)
{
    beh_type old_behaviour = mon->behaviour;

    bool isSmart          = (mons_intel(mon->type) > I_ANIMAL);
    bool wontAttack       = mons_wont_attack(mon);
    bool sourceWontAttack = false;
    bool setTarget        = false;
    bool breakCharm       = false;

    if (src == MHITYOU)
        sourceWontAttack = true;
    else if (src != MHITNOT)
        sourceWontAttack = mons_wont_attack( &menv[src] );

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

        // A bit of code to make Project 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;

            mon->target_x = src_x;
            mon->target_y = src_y;
        }
        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)))
        {
            // (Plain) plants and fungi cannot fight back.
            if (mon->type == MONS_FUNGUS || mon->type == MONS_PLANT)
                return;

            mon->foe = src;

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

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

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

    case ME_ALERT:
        if (mons_friendly(mon) && mon->is_patrolling())
            break;

        // 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;
        break;

    case ME_SCARE:
    {
        const bool flee_sanct = !mons_wont_attack(mon)
                                && is_sanctuary(mon->x, mon->y);

        // Stationary monsters can't flee, even from sanctuary.
        if (mons_is_stationary(mon))
        {
            mon->del_ench(ENCH_FEAR, true, true);
            break;
        }

        // Berserking monsters don't flee, unless it's from sanctuary.
        if (mon->has_ench(ENCH_BERSERK) && !flee_sanct)
            break;

        // Neither do plants or nonliving beings, and sanctuary doesn't
        // affect plants.
        if (mons_class_holiness(mon->type) == MH_PLANT
            || (mons_class_holiness(mon->type) == MH_NONLIVING && !flee_sanct))
        {
            mon->del_ench(ENCH_FEAR, true, true);
            break;
        }

        mon->foe = src;
        mon->behaviour = BEH_FLEE;
        // Assume monsters know where to run from, even if player is
        // invisible.
        setTarget = true;
        if (see_grid(mon->x, mon->y))
            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;

        // Don't stop fleeing from sanctuary.
        if (!mons_wont_attack(mon) && is_sanctuary(mon->x, mon->y))
            break;

        // Just set behaviour... foe doesn't change.
        if (!mons_is_cornered(mon))
            simple_monster_message(mon, " turns to fight!");

        mon->behaviour = BEH_CORNERED;
        break;

    case ME_EVAL:
    default:
        break;
    }

    if (setTarget)
    {
        if (src == MHITYOU)
        {
            mon->target_x = you.x_pos;
            mon->target_y = you.y_pos;
            mon->attitude = ATT_HOSTILE;
        }
        else if (src != MHITNOT)
        {
            mon->target_x = menv[src].x;
            mon->target_y = menv[src].y;
        }
    }

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

    // Do any resultant foe or state changes.
    _handle_behaviour(mon);

    const bool wasLurking =
        (old_behaviour == BEH_LURK && !mons_is_lurking(mon));
    const bool isPacified = mons_is_pacified(mon);

    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;
    }
}

static bool _choose_random_patrol_target_grid(monsters *mon)
{
    const int intel = mons_intel(mon->type);

    // Zombies will occasionally just stand around.
    // This does not mean that they don't move every second turn. Rather,
    // once they reach their chosen target, there's a 50% chance they'll
    // just remain there until next turn when this function is called
    // again.
    if (intel == I_PLANT && coinflip())
        return (true);

    const int patrol_x = mon->patrol_point.x;
    const int patrol_y = mon->patrol_point.y;

    // If there's no chance we'll find the patrol point, quit right away.
    if (grid_distance(mon->x, mon->y, patrol_x, patrol_y) > 2 * LOS_RADIUS)
        return (false);

    const bool patrol_seen = mon->mon_see_grid(patrol_x, patrol_y,
                                               habitat2grid(mons_habitat(mon)));

    if (intel == I_PLANT && !patrol_seen)
    {
        // Really stupid monsters won't even try to get back into the
        // patrol zone.
        return (false);
    }

    // While the patrol point is in easy reach, monsters of insect/plant
    // intelligence will only use a range of 5 (distance from the patrol point).
    // Otherwise, try to get back using the full LOS.
    const int  rad      = (intel >= I_ANIMAL || !patrol_seen)? LOS_RADIUS : 5;
    const bool is_smart = (intel >= I_NORMAL);

    monster_los patrol;
    // Set monster to make monster_los respect habitat restrictions.
    patrol.set_monster(mon);
    patrol.set_los_centre(patrol_x, patrol_y);
    patrol.set_los_range(rad);
    patrol.fill_los_field();

    monster_los lm;
    if (is_smart || !patrol_seen)
    {
        // For stupid monsters, don't bother if the patrol point is in sight.
        lm.set_monster(mon);
        lm.fill_los_field();
    }

    int pos_x, pos_y;
    int count_grids = 0;
    bool set_target = false;
    for (int j = -LOS_RADIUS; j < LOS_RADIUS; j++)
        for (int k = -LOS_RADIUS; k < LOS_RADIUS; k++, set_target = false)
        {
            pos_x = patrol_x + j;
            pos_y = patrol_y + k;

            if (!in_bounds(pos_x, pos_y))
                continue;

            // Don't bother for the current position. If everything fails,
            // we'll stay here anyway.
            if (pos_x == mon->x && pos_y == mon->y)
                continue;

            if (!mon->can_pass_through_feat(grd[pos_x][pos_y]))
                continue;

            // Don't bother moving to squares (currently) occupied by a
            // monster. We'll usually be able to find other target squares
            // (and if we're not, we couldn't move anyway), and this avoids
            // monsters trying to move onto a grid occupied by a plant or
            // sleeping monster.
            if (mgrd[pos_x][pos_y] != NON_MONSTER)
                continue;

            if (patrol_seen)
            {
                // If the patrol point can be easily (within LOS) reached
                // from the current position, it suffices if the target is
                // within reach of the patrol point OR the current position:
                // we can easily get there.
                // Only smart monsters will even attempt to move out of the
                // patrol area.
                // NOTE: Either of these can take us into a position where the
                // target cannot be easily reached (e.g. blocked by a wall)
                // and the patrol point is out of sight, too. Such a case
                // will be handled below, though it might take a while until
                // a monster gets out of a deadlock. (5% chance per turn.)
                if (!patrol.in_sight(pos_x, pos_y)
                    && (!is_smart || !lm.in_sight(pos_x, pos_y)))
                {
                    continue;
                }
            }
            else
            {
                // If, however, the patrol point is out of reach, we have to
                // make sure the new target brings us into reach of it.
                // This means that the target must be reachable BOTH from
                // the patrol point AND the current position.
                if (!patrol.in_sight(pos_x, pos_y)
                    || !lm.in_sight(pos_x, pos_y))
                {
                    continue;
                }
                // If this fails for all surrounding squares (probably because
                // we're too far away), we fall back to heading directly for
                // the patrol point.
            }

            if (intel == I_PLANT && pos_x == patrol_x && pos_y == patrol_y)
            {
                // Slightly greater chance to simply head for the centre.
                count_grids += 3;
                if (x_chance_in_y(3, count_grids))
                    set_target = true;
            }
            else if (one_chance_in(++count_grids))
                set_target = true;

            if (set_target)
            {
                mon->target_x = pos_x;
                mon->target_y = pos_y;
            }
        }

    return (count_grids);
}

//#define DEBUG_PATHFIND

// Check all grids in LoS and mark lava and/or water as seen if the
// appropriate grids are encountered, so we later only need to do the
// visibility check for monsters that can't pass a feature potentially in
// the way. We don't care about shallow water as  most monsters can safely
// cross that, and fire elementals alone aren't really worth the extra
// hassle. :)
static void _check_lava_water_in_sight()
{
    // Check the player's vision for lava or deep water,
    // to avoid unnecessary pathfinding later.
    you.lava_in_sight = you.water_in_sight = 0;
    coord_def gp;
    for (gp.y = (you.y_pos - 8); (gp.y <= you.y_pos + 8); gp.y++)
        for (gp.x = (you.x_pos - 8); (gp.x <= you.x_pos + 8); gp.x++)
        {
            if (!in_bounds(gp))
                continue;

            const coord_def ep = gp - you.pos() + coord_def(9, 9);
            if (env.show(ep))
            {
                dungeon_feature_type feat = grd[gp.x][gp.y];
                if (feat == DNGN_LAVA)
                {
                    you.lava_in_sight = 1;
                    if (you.water_in_sight > 0)
                        break;
                }
                else if (feat == DNGN_DEEP_WATER)
                {
                    you.water_in_sight = 1;
                    if (you.lava_in_sight > 0)
                        break;
                }
            }
        }
}

// If a monster can see but not directly reach the target, and then fails to
// find a path to get there, mark all surrounding (in a radius of 2) monsters
// of the same (or greater) movement restrictions as also being unable to
// find a path, so we won't need to calculate again.
// Should there be a direct path to the target for a monster thus marked, it
// will still be able to come nearer (and the mark will then be cleared).
static void _mark_neighbours_target_unreachable(monsters *mon)
{
    // Highly intelligent monsters are perfectly capable of pathfinding
    // and don't need their neighbour's advice.
    const int intel = mons_intel(mon->type);
    if (intel > I_NORMAL)
        return;

    bool flies         = mons_flies(mon);
    bool amphibious    = mons_amphibious(mon);
    habitat_type habit = mons_habitat(mon);

    int x, y;
    monsters *m;
    for (int i = -2; i <= 2; ++i)
        for (int j = -2; j <= 2; ++j)
        {
            if (i == 0 && j == 0)
                continue;

            x = mon->x + i;
            y = mon->y + j;

            if (!in_bounds(x,y))
                continue;

            if (mgrd[x][y] == NON_MONSTER)
                continue;

            // Don't alert monsters out of sight (e.g. on the other side of
            // a wall).
            if (!mon->mon_see_grid(x, y))
                continue;

            m = &menv[mgrd[x][y]];

            // Don't restrict smarter monsters as they might find a path
            // a dumber monster wouldn't.
            if (mons_intel(m->type) > intel)
                continue;

            // Monsters of differing habitats might prefer different routes.
            if (mons_habitat(m) != habit)
                continue;

            // A flying monster has an advantage over a non-flying one.
            if (!flies && mons_flies(m))
                continue;

            // Same for a swimming one, around water.
            if (you.water_in_sight > 0 && !amphibious && mons_amphibious(m))
                continue;

            if (m->travel_target == MTRAV_NONE)
                m->travel_target = MTRAV_UNREACHABLE;
        }
}

static void _find_all_level_exits(std::vector<level_exit> &e)
{
    e.clear();

    for (int y = 0; y < GXM; ++y)
    {
        for (int x = 0; x < GXM; ++x)
        {
            if (in_bounds(x, y))
            {
                const dungeon_feature_type gridc = grd[x][y];

                // All types of stairs.
                if (is_stair(gridc))
                    e.push_back(level_exit(coord_def(x, y), false));

                // Teleportation and shaft traps.
                const trap_type tt = trap_type_at_xy(x, y);
                if (tt == TRAP_TELEPORT || tt == TRAP_SHAFT)
                    e.push_back(level_exit(coord_def(x, y), false));
            }
        }
    }
}

static int _mons_find_nearest_level_exit(const monsters *mon,
                                         std::vector<level_exit> &e,
                                         bool reset = false)
{

    if (e.empty() || reset)
        _find_all_level_exits(e);

    int retval = -1;
    int old_dist = -1;

    for (unsigned int i = 0; i < e.size(); ++i)
    {
        if (e[i].unreachable)
            continue;

        int dist = grid_distance(mon->x, mon->y, e[i].target.x,
                                 e[i].target.y);

        if (old_dist == -1 || old_dist >= dist)
        {
            // Ignore teleportation and shaft traps that the monster
            // shouldn't know about.
            if (!mons_is_native_in_branch(mon)
                && grd(e[i].target) == DNGN_UNDISCOVERED_TRAP)
            {
                continue;
            }

            retval = i;
            old_dist = dist;
        }
    }

    return (retval);
}

// If _mons_find_level_exits() is ever expanded to handle more grid
// types, this should be expanded along with it.
static void _mons_indicate_level_exit(const monsters *mon)
{
    const dungeon_feature_type gridc = grd(mon->pos());

    // All types of stairs.
    if (is_travelable_stair(gridc))
    {
        command_type dir = grid_stair_direction(gridc);
        simple_monster_message(mon,
            make_stringf(" %s the stairs.",
                dir == CMD_GO_UPSTAIRS   ? "goes up" :
                dir == CMD_GO_DOWNSTAIRS ? "goes down"
                                         : "takes").c_str());
    }
    else if (is_gate(gridc))
        simple_monster_message(mon, " passes through the gate.");
}

void make_mons_leave_level(monsters *mon)
{
    if (mons_is_pacified(mon))
    {
        if (mons_near(mon) && player_monster_visible(mon))
            _mons_indicate_level_exit(mon);

        // Pacified monsters leaving the level take their stuff with
        // them.
        mon->flags |= MF_HARD_RESET;
        monster_die(mon, KILL_DISMISSED, 0);
    }
}

//---------------------------------------------------------------
//
// 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.
//---------------------------------------------------------------
static void _handle_behaviour(monsters *mon)
{
    bool changed = true;
    bool isFriendly = mons_friendly(mon);
    bool isNeutral  = mons_neutral(mon);
    bool wontAttack = mons_wont_attack(mon);
    bool proxPlayer = mons_near(mon);
    bool trans_wall_block = trans_wall_blocking(mon->x, mon->y);

#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 && !mons_wont_attack(mon))
        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->type) > I_ANIMAL);
    bool isScared   = mon->has_ench(ENCH_FEAR);
    bool isMobile   = !mons_is_stationary(mon);
    bool isPacified = mons_is_pacified(mon);
    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))
    {
        mon->target_x = 10 + random2(GXM - 10);
        mon->target_y = 10 + random2(GYM - 10);
        return;
    }

    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
        && !menv[mon->foe].alive())
    {
        mon->foe = MHITNOT;
    }

    // Change proxPlayer depending on invisibility and standing
    // in shallow water.
    if (proxPlayer && you.invisible())
    {
        if (!mons_player_visible( mon ))
            proxPlayer = false;
        // Must be able to see each other.
        else if (!see_grid(mon->x, mon->y))
            proxPlayer = false;

        const int intel = mons_intel(mon->type);
        // Now, the corollary to that is that sometimes, if a
        // player is right next to a monster, they will 'see'.
        if (grid_distance( you.x_pos, you.y_pos, mon->x, mon->y ) == 1
            && one_chance_in(3))
        {
            proxPlayer = true;
        }

        // [dshaligram] Very smart monsters have a chance of clueing in to
        // invisible players in various ways.
        else 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->has_ench(ENCH_BERSERK))
    {
        mon->foe = you.pet_target;
    }

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

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

    // Monsters do not attack themselves. {dlb}
    if (mon->foe == monster_index(mon))
        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 && mons_wont_attack(&menv[mon->foe]))
    {
        mon->foe = MHITNOT;
    }

    // Neutral monsters prefer not to attack players, or other neutrals.
    if (isNeutral && mon->foe != MHITNOT
        && (mon->foe == MHITYOU || mons_neutral(&menv[mon->foe])))
    {
        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->has_ench(ENCH_BERSERK)) && isHealthy
        && !one_chance_in(3))
    {
        mon->foe = MHITYOU;
    }

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

    while (changed)
    {
        int foe_x = you.x_pos;
        int foe_y = you.y_pos;

        // Evaluate these each time; they may change.
        if (mon->foe == MHITNOT)
            proxFoe = false;
        else
        {
            if (mon->foe == MHITYOU)
            {
                foe_x   = you.x_pos;
                foe_y   = you.y_pos;
                proxFoe = proxPlayer;   // Take invis into account.
            }
            else
            {
                proxFoe = mons_near(mon, mon->foe);

                if (!mon_can_see_monster(mon, &menv[mon->foe]))
                    proxFoe = false;

                foe_x = menv[mon->foe].x;
                foe_y = menv[mon->foe].y;
            }
        }

        // 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_x = mon->x;
            mon->target_y = mon->y;
            new_foe = MHITNOT;
            break;

        case BEH_LURK:
        case BEH_SEEK:
            // No foe? Then wander or seek the player.
            if (mon->foe == MHITNOT)
            {
                if (!proxPlayer || isNeutral || patrolling)
                    new_beh = BEH_WANDER;
                else
                {
                    new_foe = MHITYOU;
                    mon->target_x = you.x_pos;
                    mon->target_y = you.y_pos;
                }
                break;
            }

            // Foe gone out of LOS?
            if (!proxFoe)
            {
                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)
                    {
                        new_foe = MHITNOT;
                        new_beh = BEH_WANDER;
                    }
                    else
                    {
                        new_foe = MHITYOU;
                        mon->target_x = foe_x;
                        mon->target_y = foe_y;
                    }
                    break;
                }

                if (mon->foe_memory > 0 && mon->foe != MHITNOT)
                {
                    // 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->x == mon->target_x && mon->y == mon->target_y)
                    {
                        if (mon->foe == MHITYOU)
                        {
                            if (one_chance_in(you.skills[SK_STEALTH]/3))
                            {
                                mon->target_x = you.x_pos;
                                mon->target_y = you.y_pos;
                            }
                            else
                                mon->foe_memory = 1;
                        }
                        else
                        {
                            if (coinflip())     // XXX: cheesy!
                            {
                                mon->target_x = menv[mon->foe].x;
                                mon->target_y = menv[mon->foe].y;
                            }
                            else
                                mon->foe_memory = 1;
                        }
                    }

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

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

                mon->foe_memory = memory;
                break;  // switch/case BEH_SEEK
            }

            // Monster can see foe: continue 'tracking'
            // by updating target x,y.
            if (mon->foe == MHITYOU)
            {
                // Just because we can *see* the player, that doesn't mean
                // we can actually get there. To find about that, we first
                // check for transparent walls. If there are transparent
                // walls in the way we'll need pathfinding, no matter what.
                // (Though monsters with a los attack don't need to get any
                // closer to hurt the player.)
                // If no walls are detected, there could still be a river
                // or a pool of lava in the way. So we check whether there
                // is water or lava in LoS (boolean) and if so, try to find
                // a way around it. It's possible that the player can see
                // lava but it actually has no influence on the monster's
                // movement (because it's lying in the opposite direction)
                // but if so, we'll find that out during path finding.
                // In another attempt of optimization, don't bother with
                // path finding if the monster in question has no trouble
                // travelling through water or flying across lava.
                // Also, if no path is found (too far away, perhaps) set a
                // flag, so we don't directly calculate the whole thing again
                // next turn, and even extend that flag to neighbouring
                // monsters of similar movement restrictions.

                bool potentially_blocking = trans_wall_block;

                // Smart monsters that can fire through walls won't use
                // pathfinding, and it's also not necessary if the monster
                // is already adjacent to you.
                if (potentially_blocking && mons_intel(mon->type) >= I_NORMAL
                        && mons_has_los_ability(mon->type)
                    || grid_distance(mon->x, mon->y, you.x_pos, you.y_pos) == 1)
                {
                    potentially_blocking = false;
                }
                else
                {
                    // If we don't already know whether there's water or lava
                    // in LoS of the player, find out now.
                    if (you.lava_in_sight == -1 || you.water_in_sight == -1)
                        _check_lava_water_in_sight();

                    // Flying monsters don't see water/lava as obstacle.
                    // Also don't use pathfinding if the monster can shoot
                    // across the blocking terrain, and is smart enough to
                    // realize that.
                    if (!potentially_blocking && !mons_flies(mon)
                        && (mons_intel(mon->type) < I_NORMAL
                            || !mons_has_ranged_spell(mon)
                               && !mons_has_ranged_attack(mon)))
                    {
                        const habitat_type habit = mons_habitat(mon);
                        if (you.lava_in_sight > 0 && habit != HT_LAVA
                            || you.water_in_sight > 0 && habit != HT_WATER
                               && can_move != DNGN_DEEP_WATER)
                        {
                            potentially_blocking = true;
                        }
                    }
                }

                if (!potentially_blocking
                    || grid_see_grid(mon->x, mon->y, you.x_pos, you.y_pos,
                                     can_move))
                {
                    if (mon->travel_target != MTRAV_PATROL
                        && mon->travel_target != MTRAV_NONE)
                    {
                        if (mon->is_travelling())
                            mon->travel_path.clear();
                        mon->travel_target = MTRAV_NONE;
                    }
                }
                else if (mon->travel_target != MTRAV_UNREACHABLE
                         || one_chance_in(12))
                {
#ifdef DEBUG_PATHFIND
                    mprf("%s: Player out of reach! What now?",
                         mon->name(DESC_PLAIN).c_str());
#endif
                    // If we're already on our way, do nothing.
                    if (mon->is_travelling()
                        && mon->travel_target == MTRAV_PLAYER)
                    {
                        int len = mon->travel_path.size();
                        coord_def targ = mon->travel_path[len - 1];
                        if (grid_see_grid(targ.x, targ.y, you.x_pos, you.y_pos,
                                          can_move))
                        {
                            // Current target still valid?
                            if (mon->x == mon->travel_path[0].x
                                && mon->y == mon->travel_path[0].y)
                            {
                                // Get next waypoint.
                                mon->travel_path.erase(
                                    mon->travel_path.begin() );

                                if (!mon->travel_path.empty())
                                {
                                    mon->target_x = mon->travel_path[0].x;
                                    mon->target_y = mon->travel_path[0].y;
                                    break;
                                }
                            }
                            else if (grid_see_grid(mon->x, mon->y,
                                                   mon->travel_path[0].x,
                                                   mon->travel_path[0].y,
                                                   can_move))
                            {
                                mon->target_x = mon->travel_path[0].x;
                                mon->target_y = mon->travel_path[0].y;
                                break;
                            }
                        }
                    }

                    // Use pathfinding to find a (new) path to the player.
                    const int dist = grid_distance(mon->x, mon->y,
                                                   you.x_pos, you.y_pos);

#ifdef DEBUG_PATHFIND
                    mprf("Need to calculate a path... (dist = %d)", dist);
#endif
                    const bool native = mons_is_native_in_branch(mon);

                    int range = 0;
                    switch (mons_intel(mon->type))
                    {
                    case I_PLANT:
                        range = 2;
                        break;
                    case I_INSECT:
                        range = 3;
                        break;
                    case I_ANIMAL:
                        range = 4;
                        break;
                    case I_NORMAL:
                        range = 8;
                        break;
                    default:
                        // Highly intelligent monsters can find their way
                        // anywhere. (range == 0 means no restriction.)
                        break;
                    }

                    if (range)
                    {
                        if (native)
                            range += 3;
                        else if (mons_class_flag(mon->type, M_BLOOD_SCENT))
                            range++;
                    }

                    if (range > 0 && dist > range)
                    {
                        mon->travel_target = MTRAV_UNREACHABLE;
#ifdef DEBUG_PATHFIND
                        mprf("Distance too great, don't attempt pathfinding! (%s)",
                             mon->name(DESC_PLAIN).c_str());
#endif
                    }
                    else
                    {
#ifdef DEBUG_PATHFIND
                        mprf("Need a path for %s from (%d, %d) to (%d, %d), "
                             "max. dist = %d",
                             mon->name(DESC_PLAIN).c_str(), mon->x, mon->y,
                             you.x_pos, you.y_pos, range);
#endif
                        monster_pathfind mp;
                        if (range > 0)
                            mp.set_range(range);

                        if (mp.start_pathfind(mon, coord_def(you.x_pos,
                                                             you.y_pos)))
                        {
                            mon->travel_path = mp.calc_waypoints();
                            if (!mon->travel_path.empty())
                            {
                                // Okay then, we found a path.  Let's use it!
                                mon->target_x = mon->travel_path[0].x;
                                mon->target_y = mon->travel_path[0].y;
                                mon->travel_target = MTRAV_PLAYER;
                                break;
                            }
                            else
                            {
#ifdef DEBUG_PATHFIND
                                mpr("No path found!");
#endif
                                mon->travel_target = MTRAV_UNREACHABLE;
                                // Pass information on to nearby monsters.
                                _mark_neighbours_target_unreachable(mon);
                            }
                        }
                        else
                        {
#ifdef DEBUG_PATHFIND
                            mpr("No path found!");
#endif
                            mon->travel_target = MTRAV_UNREACHABLE;
                            // Pass information on to nearby monsters.
                            _mark_neighbours_target_unreachable(mon);
                        }
                    }
                }
                // 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))
                {
                    mon->target_x = 10 + random2(GXM - 10);
                    mon->target_y = 10 + random2(GYM - 10);
                    mon->foe = MHITNOT;
                    new_beh  = BEH_WANDER;
                }
                else
                {
                    mon->target_x = you.x_pos;
                    mon->target_y = you.y_pos;
                }
            }
            else
            {
                // We have a foe but it's not the player.
                mon->target_x = menv[mon->foe].x;
                mon->target_y = menv[mon->foe].y;
            }

            // Stupid monsters, plants or nonliving monsters cannot flee.
            if (isHurt && !isSmart && isMobile
                && mons_class_holiness(mon->type) != MH_PLANT
                && mons_class_holiness(mon->type) != 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_x = e[e_index].target.x;
                        mon->target_y = e[e_index].target.y;
                    }
                    else
                    {
                        mon->travel_target = MTRAV_NONE;
                        patrolling = false;
                        mon->patrol_point = coord_def(0, 0);
                        mon->target_x = 10 + random2(GXM - 10);
                        mon->target_y = 10 + random2(GYM - 10);
                    }
                }

                // If a pacified monster is leaving the level, and has
                // reached its goal, handle it here.
                if (e_index != -1
                    && mon->x == e[e_index].target.x
                    && mon->y == e[e_index].target.y)
                {
                    make_mons_leave_level(mon);
                    return;
                }

                // If a pacified monster is far enough away from the
                // player, make it leave the level.
                if (grid_distance(mon->x, mon->y, you.x_pos, you.y_pos)
                        >= LOS_RADIUS * 4)
                {
                    make_mons_leave_level(mon);
                    return;
                }
            }

            // 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;
            }

            // default wander behaviour
            //
            // XXX: This is really dumb wander behaviour... instead of
            // changing the goal square every turn, better would be to
            // have the monster store a direction and have the monster
            // head in that direction for a while, then shift the
            // direction to the left or right.  We're changing this so
            // wandering monsters at least appear to have some sort of
            // attention span.  -- bwr
            if (mon->x == mon->target_x && mon->y == mon->target_y
                || mons_is_batty(mon) || !isPacified && one_chance_in(20))
            {
                bool need_target = true;
                if (mon->is_travelling())
                {
#ifdef DEBUG_PATHFIND
                    mprf("Monster %s reached target (%d, %d)",
                         mon->name(DESC_PLAIN).c_str(),
                         mon->target_x, mon->target_y);
#endif

                    need_target = false;
                    if (mon->x == mon->travel_path[0].x
                        && mon->y == mon->travel_path[0].y)
                    {
#ifdef DEBUG_PATHFIND
                        mpr("Arrived at first waypoint.");
#endif
                        // Hey, we reached our first waypoint!
                        mon->travel_path.erase( mon->travel_path.begin() );
                        if (mon->travel_path.empty())
                        {
#ifdef DEBUG_PATHFIND
                            mpr("We reached the end of our path: stop "
                                "travelling.");
#endif

                            mon->travel_target = MTRAV_NONE;
                            need_target = true;
                        }
                        else
                        {
                            mon->target_x = mon->travel_path[0].x;
                            mon->target_y = mon->travel_path[0].y;
#ifdef DEBUG_PATHFIND
                            mprf("Next waypoint: (%d, %d)",
                                 mon->target_x, mon->target_y);
#endif
                        }
                    }
                    else if (!grid_see_grid(mon->x, mon->y,
                                            mon->travel_path[0].x,
                                            mon->travel_path[0].y, can_move))
                    {
#ifdef DEBUG_PATHFIND
                        mpr("Can't see waypoint grid.");
#endif
                        // Apparently we got sidetracked a bit.
                        // Check the waypoints vector backwards and pick the
                        // first waypoint we can see.

                        // XXX: Note that this might still not be the best
                        // thing to do since another path might be even
                        // *closer* to our actual target now.
                        // Not by much, though, since the original path was
                        // optimal (A*) and the distance between the waypoints
                        // is rather small.

                        int erase = -1;  // Erase how many waypoints?
                        int size = mon->travel_path.size();
                        for (int i = size - 1; i >= 0; --i)
                        {
                            if (grid_see_grid(mon->x, mon->y,
                                              mon->travel_path[i].x,
                                              mon->travel_path[i].y, can_move))
                            {
                                mon->target_x = mon->travel_path[i].x;
                                mon->target_y = mon->travel_path[i].y;
                                erase = i;
                                break;
                            }
                        }

                        if (erase > 0)
                        {
#ifdef DEBUG_PATHFIND
                            mprf("Need to erase %d of %d waypoints.",
                                 erase, mon->travel_path.size());
#endif
                            // Erase all waypoints that came earlier:
                            // we don't need them anymore.
                            while (0 < erase--)
                            {
                                mon->travel_path.erase(
                                    mon->travel_path.begin() );
                            }
                        }
                        else
                        {
                            // We can't reach our old path from our current
                            // position, so calculate a new path instead.

                            monster_pathfind mp;
                            // The last coordinate in the path vector is our
                            // destination.
                            int len = mon->travel_path.size();
                            if (mp.start_pathfind(mon, mon->travel_path[len-1]))
                            {
                                mon->travel_path = mp.calc_waypoints();
                                if (!mon->travel_path.empty())
                                {
                                    mon->target_x = mon->travel_path[0].x;
                                    mon->target_y = mon->travel_path[0].y;
#ifdef DEBUG_PATHFIND
                                    mprf("Next waypoint: (%d, %d)",
                                         mon->target_x, mon->target_y);
#endif
                                }
                                else
                                {
                                    mon->travel_target = MTRAV_NONE;
                                    need_target = true;
                                }
                            }
                            else
                            {
                                // Or just forget about the whole thing.
                                mon->travel_path.clear();
                                mon->travel_target = MTRAV_NONE;
                                need_target = true;
                            }
                        }
                    }
                    // Else, we can see the next waypoint and are making good
                    // progress. Carry on, then!
                }

                // If we still need a target because we're not travelling
                // (any more), check for patrol routes instead.
                if (need_target && patrolling)
                {
                    need_target = false;

                    if (!_choose_random_patrol_target_grid(mon))
                    {
                        // If we couldn't find a target that is within easy
                        // reach of the monster and close to the patrol point,
                        // do one of the following:
                        //  * set current position as new patrol point
                        //  * forget about patrolling
                        //  * head back to patrol point

                        if (mons_intel(mon->type) == I_PLANT)
                        {
                            // Really stupid monsters forget where they're
                            // supposed to be.
                            if (mons_friendly(mon))
                            {
                                // Your ally was told to wait, and wait it will!
                                // (Though possibly not where you told it to.)
                                mon->patrol_point = coord_def(mon->x, mon->y);
                            }
                            else
                            {
                                // Stop patrolling.
                                mon->patrol_point = coord_def(0, 0);
                                mon->travel_target = MTRAV_NONE;
                                need_target = true;
                            }
                        }
                        else
                        {
                            // It's time to head back!
                            // Other than for tracking the player, there's
                            // currently no distinction between smart and
                            // stupid monsters when it comes to travelling
                            // back to the patrol point. This is in part due
                            // to the flavour in e.g. bees finding their way
                            // back to the Hive (and patrolling should really
                            // be restricted to cases like this), and for the
                            // other part it's not all that important because
                            // we calculate the path once and then follow it
                            // home, and the player won't ever see the
                            // orderly fashion the bees will trudge along.
                            // What he will see is them swarming back to the
                            // Hive entrance after some time, and that is what
                            // matters.
                            monster_pathfind mp;
                            if (mp.start_pathfind(mon, mon->patrol_point))
                            {
                                mon->travel_path = mp.calc_waypoints();
                                if (!mon->travel_path.empty())
                                {
                                    mon->target_x = mon->travel_path[0].x;
                                    mon->target_y = mon->travel_path[0].y;
                                    mon->travel_target = MTRAV_PATROL;
                                }
                                else
                                {
                                    // We're so close we don't even need
                                    // a path.
                                    mon->target_x = mon->patrol_point.x;
                                    mon->target_y = mon->patrol_point.y;
                                }
                            }
                            else
                            {
                                // Stop patrolling.
                                mon->patrol_point  = coord_def(0, 0);
                                mon->travel_target = MTRAV_NONE;
                                need_target = true;
                            }
                        }
                    }
                    else
                    {
#ifdef DEBUG_PATHFIND
                        mprf("Monster %s (pp: %d, %d) is now patrolling to (%d, %d)",
                             mon->name(DESC_PLAIN).c_str(),
                             mon->patrol_point.x, mon->patrol_point.y,
                             mon->target_x, mon->target_y);
#endif
                    }
                }

                if (need_target)
                {
                    mon->target_x = 10 + random2(GXM - 10);
                    mon->target_y = 10 + random2(GYM - 10);
                }
            }

            // 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.

            // XXX: If a monster can move through solid grids, then it
            // should preferentially flee towards the nearest solid grid
            // it can move through.  It will be (mostly) safe as soon as
            // it enters the wall, and even if it isn't, once it moves
            // again it will be on the other side of the wall and likely
            // beyond the reach of the player.

            if (isFriendly)
            {
                // Special-cased below so that it will flee *towards* you.
                mon->target_x = you.x_pos;
                mon->target_y = you.y_pos;
            }
            else if (proxFoe)
            {
                // Special-cased below so that it will flee *from* the
                // correct position.
                mon->target_x = foe_x;
                mon->target_y = foe_y;
            }
            break;

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

            if (isHealthy)
                new_beh = BEH_SEEK;

            // Foe gone out of LOS?
            if (!proxFoe)
            {
                if ((isFriendly || proxPlayer) && !isNeutral && !patrolling)
                    new_foe = MHITYOU;
                else
                    new_beh = BEH_WANDER;
            }
            else
            {
                mon->target_x = foe_x;
                mon->target_y = foe_y;
            }
            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;
    }
}

static bool _mons_check_set_foe(monsters *mon, int x, int y,
                                bool friendly, bool neutral)
{
    if (!inside_level_bounds(x, y))
        return (false);

    if (!friendly && !neutral && x == you.x_pos && y == you.y_pos
        && mons_player_visible(mon) && !is_sanctuary(x, y))
    {
        mon->foe = MHITYOU;
        return (true);
    }

    if (mgrd[x][y] != NON_MONSTER)
    {
        monsters *foe = &menv[mgrd[x][y]];

        if (foe != mon
            && mon_can_see_monster(mon, foe)
            && (friendly || !is_sanctuary(x, y))
            && (mons_friendly(foe) != friendly
                || (neutral && !mons_neutral(foe))))
        {
            mon->foe = mgrd[x][y];
            return (true);
        }
    }
    return (false);
}

// Choose nearest monster as a foe.  (Used for berserking monsters.)
void _set_nearest_monster_foe(monsters *mon)
{
    const bool friendly = mons_friendly(mon);
    const bool neutral  = mons_neutral(mon);

    const int mx = mon->x;
    const int my = mon->y;

    for (int k = 1; k <= LOS_RADIUS; ++k)
    {
        for (int x = mx - k; x <= mx + k; ++x)
            if (_mons_check_set_foe(mon, x, my - k, friendly, neutral)
                || _mons_check_set_foe(mon, x, my + k, friendly, neutral))
            {
                return;
            }

        for (int y = my - k + 1; y < my + k; ++y)
            if (_mons_check_set_foe(mon, mx - k, y, friendly, neutral)
                || _mons_check_set_foe(mon, mx + k, y, friendly, neutral))
            {
                return;
            }
    }
}

// The default suitable() function for choose_random_nearby_monster().
bool choose_any_monster(const monsters* mon)
{
    return (true);
}

// Find a nearby monster and return its index, including you as a
// possibility with probability weight.  suitable() should return true
// for the type of monster wanted.
// If prefer_named is true, named monsters (including uniques) are twice
// as likely to get chosen compared to non-named ones.
monsters *choose_random_nearby_monster(int weight,
                                       bool (*suitable)(const monsters* mon),
                                       bool in_sight, bool prefer_named)
{
    return choose_random_monster_on_level(weight, suitable, in_sight, true,
                                          prefer_named);
}

monsters *choose_random_monster_on_level(int weight,
                                         bool (*suitable)(const monsters* mon),
                                         bool in_sight, bool near_by,
                                         bool prefer_named)
{
    monsters *chosen = NULL;
    int mons_count = weight;

    int ystart = 0;
    int xstart = 0;
    int yend = GXM - 1;
    int xend = GYM - 1;

    if (near_by)
    {
        ystart = MAX(0,       you.y_pos - 9);
        xstart = MAX(0,       you.x_pos - 9);
          yend = MIN(GYM - 1, you.y_pos + 9);
          xend = MIN(GXM - 1, you.x_pos + 9);
    }

    // Monster check.
    for ( int y = ystart; y <= yend; ++y )
    {
        for ( int x = xstart; x <= xend; ++x )
        {
            if ( mgrd[x][y] != NON_MONSTER && (!in_sight || see_grid(x,y)) )
            {
                monsters *mon = &menv[mgrd[x][y]];
                if (suitable(mon))
                {
                    // FIXME: if the intent is to favour monsters
                    // named by $DEITY, we should set a flag on the
                    // monster (something like MF_DEITY_PREFERRED) and
                    // use that instead of checking the name, given
                    // that other monsters can also have names.

                    // True, but it's currently only used for orcs, and
                    // Blork and Urug also being preferred to non-named orcs
                    // is fine, I think. Once more gods name followers (and
                    // prefer them) that should be changed, of course. (jpeg)
                    if (prefer_named && mon->is_named())
                    {
                        mons_count += 2;
                        // Named monsters have doubled chances.
                        if (x_chance_in_y(2, mons_count))
                            chosen = mon;
                    }
                    else if (one_chance_in(++mons_count))
                        chosen = mon;
                }
            }
        }
    }

    return chosen;
}

// Note that this function *completely* blocks messaging for monsters
// distant or invisible to the player ... look elsewhere for a function
// permitting output of "It" messages for the invisible {dlb}
// Intentionally avoids info and str_pass now. -- bwr
bool simple_monster_message(const monsters *monster, const char *event,
                            msg_channel_type channel, int param,
                            description_level_type descrip)
{

    if (mons_near( monster )
        && (channel == MSGCH_MONSTER_SPELL || channel == MSGCH_FRIEND_SPELL
            || player_monster_visible(monster)))
    {
        char buff[INFO_SIZE];

        snprintf( buff, sizeof(buff), "%s%s", monster->name(descrip).c_str(),
                  event );

        if (channel == MSGCH_PLAIN && mons_wont_attack(monster))
            channel = MSGCH_FRIEND_ACTION;

        mpr( buff, channel, param );
        return (true);
    }

    return (false);
}                               // end simple_monster_message()

// Altars as well as branch entrances are considered interesting
// for some monster types.
static bool _mon_on_interesting_grid(monsters *mon)
{
    // Patrolling shouldn't happen all the time.
    if (one_chance_in(4))
        return (false);

    const dungeon_feature_type feat = grd[mon->x][mon->y];

    switch (feat)
    {
    // Holy beings will tend to patrol around altars to the good gods.
    case DNGN_ALTAR_ELYVILON:
        if (!one_chance_in(3))
            return (false);
        // else fall through
    case DNGN_ALTAR_ZIN:
    case DNGN_ALTAR_SHINING_ONE:
        return (mons_is_holy(mon));

    // Orcs will tend to patrol around altars to Beogh, and guard the
    // stairway from and to the Orcish Mines.
    case DNGN_ALTAR_BEOGH:
    case DNGN_ENTER_ORCISH_MINES:
    case DNGN_RETURN_FROM_ORCISH_MINES:
        return (mons_is_native_in_branch(mon, BRANCH_ORCISH_MINES));

    // Same for elves and the Elven Halls.
    case DNGN_ENTER_ELVEN_HALLS:
    case DNGN_RETURN_FROM_ELVEN_HALLS:
        return (mons_is_native_in_branch(mon, BRANCH_ELVEN_HALLS));

    // Killer bees always return to their hive.
    case DNGN_ENTER_HIVE:
        return (mons_is_native_in_branch(mon, BRANCH_HIVE));

    default:
        return (false);
    }
}

// If a hostile monster finds itself on a grid of an "interesting" feature,
// while unoccupied, it will remain in that area, and try to return to it
// if it left it for fighting, seeking etc.
static void _maybe_set_patrol_route(monsters *monster)
{
    if (mons_is_wandering(monster)
        && !mons_friendly(monster)
        && !monster->is_patrolling()
        && _mon_on_interesting_grid(monster))
    {
        monster->patrol_point = coord_def(monster->x, monster->y);
    }
}

//---------------------------------------------------------------
//
// handle_movement
//
// Move the monster closer to its target square.
//
//---------------------------------------------------------------
static void _handle_movement(monsters *monster)
{
    int dx, dy;

    _maybe_set_patrol_route(monster);

    // Monsters will try to flee out of a sanctuary.
    if (is_sanctuary(monster->x, monster->y) && !mons_friendly(monster)
        && !mons_is_fleeing(monster)
        && monster->add_ench(mon_enchant(ENCH_FEAR, 0, KC_YOU)))
    {
        behaviour_event(monster, ME_SCARE, MHITNOT, monster->x, monster->y);
    }
    else if (mons_is_fleeing(monster) && inside_level_bounds(env.sanctuary_pos)
             && !is_sanctuary(monster->x, monster->y)
             && monster->target_pos() == env.sanctuary_pos)
    {
        // Once outside there's a chance they'll regain their courage.
        // Nonliving and berserking monsters always stop imediately,
        // since they're only being forced out rather than actually scared.
        if (monster->holiness() == MH_NONLIVING
            || monster->has_ench(ENCH_BERSERK)
            || random2(5) > 2)
        {
            monster->del_ench(ENCH_FEAR);
        }
    }

    // some calculations
    if (monster->type == MONS_BORING_BEETLE && monster->foe == MHITYOU)
    {
        // Boring beetles always move in a straight line in your direction.
        dx = you.x_pos - monster->x;
        dy = you.y_pos - monster->y;
    }
    else
    {
        dx = monster->target_x - monster->x;
        dy = monster->target_y - monster->y;
    }

    // Move the monster.
    mmov_x = (dx > 0) ? 1 : ((dx < 0) ? -1 : 0);
    mmov_y = (dy > 0) ? 1 : ((dy < 0) ? -1 : 0);

    if (mons_is_fleeing(monster)
        && (!mons_friendly(monster)
            || monster->target_x != you.x_pos
            || monster->target_y != you.y_pos))
    {
        mmov_x *= -1;
        mmov_y *= -1;
    }

    // Don't allow monsters to enter a sanctuary
    // or attack you inside a sanctuary even if you're right next to them.
    if (is_sanctuary(monster->x + mmov_x, monster->y + mmov_y)
        && (!is_sanctuary(monster->x, monster->y)
            || monster->x + mmov_x == you.x_pos
               && monster->y + mmov_y == you.y_pos))
    {
        mmov_x = 0;
        mmov_y = 0;
    }

    // Bounds check: don't let fleeing monsters try to run off the map.
    if (monster->target_x + mmov_x < 0 || monster->target_x + mmov_x >= GXM)
        mmov_x = 0;

    if (monster->target_y + mmov_y < 0 || monster->target_y + mmov_y >= GYM)
        mmov_y = 0;

    // now quit if we can't move
    if (mmov_x == 0 && mmov_y == 0)
        return;

    // Reproduced here is some semi-legacy code that makes monsters
    // move somewhat randomly along oblique paths.  It is an exceedingly
    // good idea, given crawl's unique line of sight properties.
    //
    // Added a check so that oblique movement paths aren't used when
    // close to the target square. -- bwr
    if (grid_distance(dx, dy, 0, 0) > 3)
    {
        if (abs(dx) > abs(dy))
        {
            // Sometimes we'll just move parallel the x axis.
            if (coinflip())
                mmov_y = 0;
        }

        if (abs(dy) > abs(dx))
        {
            // Sometimes we'll just move parallel the y axis.
            if (coinflip())
                mmov_x = 0;
        }
    }
}                               // end handle_movement()

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

static bool _is_player_or_mon_sanct(const monsters* monster)
{
    return (is_sanctuary(you.x_pos, you.y_pos)
            || is_sanctuary(monster->x, monster->y));
}

//---------------------------------------------------------------
//
// handle_nearby_ability
//
// Gives monsters a chance to use a special ability when they're
// next to the player.
//
//---------------------------------------------------------------
static void _handle_nearby_ability(monsters *monster)
{
    if (!mons_near(monster)
        || mons_is_sleeping(monster)
        || monster->has_ench(ENCH_SUBMERGED))
    {
        return;
    }

#define MON_SPEAK_CHANCE 21

    if (mons_class_flag(monster->type, M_SPEAKS)
        && (!mons_is_wandering(monster) || monster->attitude == ATT_NEUTRAL)
        && one_chance_in(MON_SPEAK_CHANCE))
    {
        mons_speaks(monster);
    }
    else if (get_mon_shape(monster) >= MON_SHAPE_QUADRUPED)
    {
        // Non-humanoid-ish monsters have a low chance of speaking
        // without the M_SPEAKS flag, to give the dungeon some
        // atmosphere/flavor.
        int chance = MON_SPEAK_CHANCE * 4;

        // Band members are a lot less likely to speak, since there's
        // a lot of them.
        if (testbits(monster->flags, MF_BAND_MEMBER))
            chance *= 10;

        // However, confused and fleeing monsters are more interesting.
        if (mons_is_fleeing(monster))
            chance /= 2;
        if (monster->has_ench(ENCH_CONFUSION))
            chance /= 2;

        if (one_chance_in(chance))
            mons_speaks(monster);
    }
    // Okay then, don't speak.

    if (monster_can_submerge(monster, grd[monster->x][monster->y])
        && !player_beheld_by(monster) // No submerging if player entranced.
        && !mons_is_lurking(monster)  // Handled elsewhere.
        && (one_chance_in(5)
            || grid_distance(monster->x, monster->y,
                             you.x_pos, you.y_pos) > 1
               // FIXME This is better expressed as a
               // function such as
               // monster_has_ranged_attack:
               && monster->type != MONS_ELECTRICAL_EEL
               && monster->type != MONS_LAVA_SNAKE
               && (monster->type != MONS_MERMAID
                   || you.species == SP_MERFOLK)
               // Don't submerge if we just unsubmerged for
               // the sake of shouting.
               && monster->seen_context != "bursts forth shouting"
               && !one_chance_in(20)
            || monster->hit_points <= monster->max_hit_points / 2
            || env.cgrid[monster->x][monster->y] != EMPTY_CLOUD))
    {
        monster->add_ench(ENCH_SUBMERGED);
        update_beholders(monster);
        return;
    }

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

    case MONS_GIANT_EYEBALL:
        if (coinflip() && !mons_friendly(monster)
            && !mons_is_wandering(monster)
            && !mons_is_fleeing(monster)
            && !mons_is_pacified(monster)
            && !_is_player_or_mon_sanct(monster))
        {
            simple_monster_message(monster, " stares at you.");

            int &paralysis(you.duration[DUR_PARALYSIS]);
            if (!paralysis || (paralysis < 10 && one_chance_in(1 + paralysis)))
                paralysis += 2 + random2(3);
        }
        break;

    case MONS_EYE_OF_DRAINING:
        if (coinflip() && !mons_friendly(monster)
            && !mons_is_wandering(monster)
            && !mons_is_fleeing(monster)
            && !mons_is_pacified(monster)
            && !_is_player_or_mon_sanct(monster))
        {
            simple_monster_message(monster, " stares at you.");

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

            heal_monster(monster, 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;
    }
}                               // end handle_nearby_ability()

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

    FixedArray < unsigned int, 19, 19 > show;

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

    if (!mons_near(monster)
        || mons_is_sleeping(monster)
        || monster->has_ench(ENCH_SUBMERGED))
    {
        return (false);
    }

    const msg_channel_type spl = (mons_friendly(monster) ? MSGCH_FRIEND_SPELL
                                                         : MSGCH_MONSTER_SPELL);

    switch (mclass)
    {
    case MONS_ORC_KNIGHT:
    case MONS_ORC_WARLORD:
        if (is_sanctuary(monster->x, monster->y))
            break;

        used = orc_battle_cry(monster);
        break;

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

        used = orange_statue_effects(monster);
        break;

    case MONS_SILVER_STATUE:
        if (_is_player_or_mon_sanct(monster))
        {
            break;
        }
        used = silver_statue_effects(monster);
        break;

    case MONS_BALL_LIGHTNING:
        if (is_sanctuary(monster->x, monster->y))
            break;

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

        for (int i = 0; i < MAX_MONSTERS; i++)
        {
            monsters *targ = &menv[i];

            if (targ->type == -1 || targ->type == NON_MONSTER)
                continue;

            if (distance(monster->x, monster->y, targ->x, targ->y) >= 5)
                continue;

            if (mons_atts_aligned(monster->attitude, targ->attitude))
                continue;

            // Faking LOS by checking the neighbouring square.
            int dx = sgn(targ->x - monster->x);
            int dy = sgn(targ->y - monster->y);

            const int tx = monster->x + dx;
            const int ty = monster->y + dy;

            if (!inside_level_bounds(tx, ty))
                continue;

            if (!grid_is_solid(grd[tx][ty]))
            {
                monster->hit_points = -1;
                used = true;
                break;
            }
        }
        break;

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

        if (!mons_player_visible( monster ))
            break;

        if (coinflip())
            break;

        // Setup tracer.
        beem.name        = "glob of lava";
        beem.aux_source  = "glob of lava";
        beem.range       = 4;
        beem.rangeMax    = 13;
        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_index(monster);
        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!");
            fire_beam(beem);
            used = true;
        }
        break;

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

        if (!mons_player_visible( monster ))
            break;

        if (coinflip())
            break;

        // Setup tracer.
        beem.name        = "bolt of electricity";
        beem.aux_source  = "bolt of electricity";
        beem.range       = 4;
        beem.rangeMax    = 13;
        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_index(monster);
        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!");
            fire_beam(beem);
            used = true;
        }
        break;

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

        if (_is_player_or_mon_sanct(monster))
            break;

        if (one_chance_in(3))
            used = _plant_spit(monster, beem);

        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 && mons_friendly(monster))
        {
            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 (_is_player_or_mon_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 (!mons_friendly(monster))
                {
                    _make_mons_stop_fleeing(monster);
                    spell_cast = SPELL_SYMBOL_OF_TORMENT;
                    mons_cast(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);
                    simple_monster_message( monster, " makes a gesture!", spl );

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

            mmov_x = 0;
            mmov_y = 0;
        }
        break;

    case MONS_IMP:
    case MONS_PHANTOM:
    case MONS_INSUBSTANTIAL_WISP:
    case MONS_BLINK_FROG:
    case MONS_KILLER_KLOWN:
        if (one_chance_in(7) || mons_is_caught(monster) && one_chance_in(3))
        {
            simple_monster_message(monster, " blinks.");
            used = monster_blink(monster);
        }
        break;

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

        if (!mons_player_visible( 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       = 9;
        beem.rangeMax    = 9;
        beem.hit         = 14;
        beem.damage      = dice_def( 2, 10 );
        beem.beam_source = monster_index(monster);
        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!");
            fire_beam(beem);
            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;
        }
    }

    // Dragon breath weapons:
    case MONS_DRAGON:
    case MONS_HELL_HOUND:
    case MONS_ICE_DRAGON:
    case MONS_LINDWURM:
    case MONS_FIREDRAKE:
    case MONS_XTAHUA:
    case MONS_WHITE_DRACONIAN:
    case MONS_RED_DRACONIAN:
        if (monster->has_ench(ENCH_CONFUSION))
            break;

        if (!mons_player_visible( monster ))
            break;

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

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

            // Good idea?
            if (mons_should_fire(beem))
            {
                _make_mons_stop_fleeing(monster);
                simple_monster_message(monster, " breathes.", spl);
                fire_beam(beem);
                mmov_x = 0;
                mmov_y = 0;
                used = true;
            }
        }
        break;

    case MONS_MERMAID:
    {
        // 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)
            {
#ifdef DEBUG_DIAGNOSTICS
                mpr("Taking stairs, don't behold.", MSGCH_DIAGNOSTICS);
#endif
                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)
            || mons_is_pacified(monster)
            || mons_friendly(monster)
            || silenced(monster->x, monster->y)
            || silenced(you.x_pos, you.y_pos))
        {
            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_beheld = player_beheld_by(monster);

        if (one_chance_in(5)
            || monster->foe == MHITYOU && !already_beheld && coinflip())
        {
            int walls = num_feats_between(you.x_pos, you.y_pos,
                                          monster->x, monster->y, DNGN_UNSEEN,
                                          DNGN_MAXWALL);

            if (walls > 0)
            {
                simple_monster_message(monster, " appears to sing, but you "
                                       "can't hear her.");
                break;
            }
            else if (player_monster_visible(monster))
            {
                simple_monster_message(monster,
                    make_stringf(" chants %s song.",
                    already_beheld ? "her luring" : "a haunting").c_str(),
                    spl);
            }
            else
            {
                // If you're already beheld by an invisible mermaid she can
                // still prolong the enchantment; otherwise you "resist".
                if (already_beheld)
                    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 beheld by a particular monster, you cannot resist anymore.
            if (!already_beheld
                && (you.species == SP_MERFOLK || you_resist_magic(100)))
            {
                canned_msg(MSG_YOU_RESIST);
                break;
            }

            if (!you.duration[DUR_BEHELD])
            {
                you.duration[DUR_BEHELD] = 7;
                you.beheld_by.push_back(monster_index(monster));
                mpr("You are beheld!", MSGCH_WARN);
            }
            else
            {
                you.duration[DUR_BEHELD] += 5;
                if (!already_beheld)
                    you.beheld_by.push_back(monster_index(monster));
            }
            used = true;

            if (you.duration[DUR_BEHELD] > 12)
                you.duration[DUR_BEHELD] = 12;
        }
        break;
    }
    default:
        break;
    }

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

    return (used);
}                               // end handle_special_ability()

//---------------------------------------------------------------
//
// _handle_potion
//
// Give the monster a chance to quaff a potion. Returns true if
// the monster imbibed.
//
//---------------------------------------------------------------
static bool _handle_potion(monsters *monster, bolt & beem)
{
    // Yes, there is a logic to this ordering {dlb}:
    if (mons_is_sleeping(monster)
        || monster->inv[MSLOT_POTION] == NON_ITEM
        || !one_chance_in(3))
    {
        return (false);
    }

    bool                    imbibed     = false;
    item_type_id_state_type ident       = ID_UNKNOWN_TYPE;
    bool was_visible = (mons_near(monster) && player_monster_visible(monster));

    const int potion_type = mitm[monster->inv[MSLOT_POTION]].sub_type;
    switch (potion_type)
    {
    case POT_HEALING:
    case POT_HEAL_WOUNDS:
        if (monster->hit_points <= monster->max_hit_points / 2
            && mons_holiness(monster) != MH_UNDEAD
            && mons_holiness(monster) != MH_NONLIVING
            && mons_holiness(monster) != MH_PLANT)
        {
            simple_monster_message(monster, " drinks a potion.");

            if (heal_monster(monster, 5 + random2(7), false))
            {
                simple_monster_message(monster, " is healed!");
                ident = ID_MON_TRIED_TYPE;
            }

            if (mitm[monster->inv[MSLOT_POTION]].sub_type == POT_HEAL_WOUNDS)
            {
                heal_monster(monster, 10 + random2avg(28, 3), false);
            }

            if (potion_type == POT_HEALING)
            {
                monster->del_ench(ENCH_POISON);
                monster->del_ench(ENCH_SICK);
                if (monster->del_ench(ENCH_CONFUSION))
                    ident = ID_KNOWN_TYPE;
                if (monster->del_ench(ENCH_ROT))
                    ident = ID_KNOWN_TYPE;
            }

            imbibed = true;
        }
        break;

    case POT_BLOOD:
    case POT_BLOOD_COAGULATED:
        if (mons_species(monster->type) == MONS_VAMPIRE
            && monster->hit_points <= monster->max_hit_points / 2)
        {
            simple_monster_message(monster, " drinks a potion.");

            if (heal_monster(monster, 10 + random2avg(28, 3), false))
            {
                simple_monster_message(monster, " is healed!");
                ident = ID_MON_TRIED_TYPE;
            }

            imbibed = true;
        }
        break;

    case POT_SPEED:
        // Notice that these are the same odd colours used in
        // mons_ench_f2() {dlb}
        if (monster->has_ench(ENCH_HASTE))
            break;

        beem.flavour = BEAM_HASTE;
        // intentional fall through
    case POT_INVISIBILITY:
        if (mitm[monster->inv[MSLOT_POTION]].sub_type == POT_INVISIBILITY)
        {
            if (monster->has_ench(ENCH_INVIS))
                break;

            beem.flavour = BEAM_INVISIBILITY;
            // Friendly monsters won't go invisible if the player can't
            // see invisible. We're being nice.
            if (mons_friendly(monster) && !player_see_invis(false))
                break;
        }

        // Allow monsters to drink these when player in sight. (jpeg)
        simple_monster_message(monster, " drinks a potion.");
        mons_ench_f2(monster, beem);
        imbibed = true;
        if (beem.obvious_effect)
            ident = ID_KNOWN_TYPE;
        break;
    }

    if (imbibed)
    {
        if (dec_mitm_item_quantity( monster->inv[MSLOT_POTION], 1 ))
            monster->inv[MSLOT_POTION] = NON_ITEM;
        else if (is_blood_potion(mitm[monster->inv[MSLOT_POTION]]))
            remove_oldest_blood_potion(mitm[monster->inv[MSLOT_POTION]]);

        if (ident != ID_UNKNOWN_TYPE && was_visible)
            set_ident_type(OBJ_POTIONS, potion_type, ident);

        monster->lose_energy(EUT_ITEM);
    }

    return (imbibed);
}

static bool _handle_reaching(monsters *monster)
{
    bool       ret = false;
    const int  wpn = monster->inv[MSLOT_WEAPON];

    if (mons_aligned(monster_index(monster), monster->foe))
        return (false);

    if (monster->has_ench(ENCH_SUBMERGED))
        return (false);

    if (wpn != NON_ITEM && get_weapon_brand( mitm[wpn] ) == SPWPN_REACHING )
    {
        if (monster->foe == MHITYOU)
        {
            // This check isn't redundant -- player may be invisible.
            if (monster->target_pos() == you.pos()
                && see_grid_no_trans(monster->pos()))
            {
                int dx = abs(monster->x - you.x_pos);
                int dy = abs(monster->y - you.y_pos);

                if (dx == 2 && dy <= 2 || dy == 2 && dx <= 2)
                {
                    ret = true;
                    monster_attack( monster_index(monster), false );
                }
            }
        }
        else if (monster->foe != MHITNOT)
        {
            int foe_x = menv[monster->foe].x;
            int foe_y = menv[monster->foe].y;
            // Same comments as to invisibility as above.
            if (monster->target_x == foe_x && monster->target_y == foe_y
                && monster->mon_see_grid(foe_x, foe_y, true))
            {
                int dx = abs(monster->x - foe_x);
                int dy = abs(monster->y - foe_y);

                if (dx == 2 && dy <= 2 || dy == 2 && dx <= 2)
                {
                    ret = true;
                    monsters_fight(monster_index(monster), monster->foe, false);
                }
            }
        }
    }

    // Player saw the item reach.
    if (ret && !is_artefact(mitm[wpn]) && mons_near(monster)
        && player_monster_visible(monster))
    {
        set_ident_flags(mitm[wpn], ISFLAG_KNOW_TYPE);
    }

    return ret;
}                               // end handle_reaching()

//---------------------------------------------------------------
//
// handle_scroll
//
// Give the monster a chance to read a scroll. Returns true if
// the monster read something.
//
//---------------------------------------------------------------
static bool _handle_scroll(monsters *monster)
{
    // Yes, there is a logic to this ordering {dlb}:
    if (mons_is_sleeping(monster)
        || monster->has_ench(ENCH_CONFUSION)
        || monster->has_ench(ENCH_SUBMERGED)
        || monster->inv[MSLOT_SCROLL] == NON_ITEM
        || !one_chance_in(3))
    {
        return (false);
    }

    bool                    read        = false;
    item_type_id_state_type ident       = ID_UNKNOWN_TYPE;
    bool                    was_visible =
        mons_near(monster) && player_monster_visible(monster);

    // Notice how few cases are actually accounted for here {dlb}:
    const int scroll_type = mitm[monster->inv[MSLOT_SCROLL]].sub_type;
    switch (scroll_type)
    {
    case SCR_TELEPORTATION:
        if (!monster->has_ench(ENCH_TP))
        {
            if (mons_is_caught(monster) || mons_is_fleeing(monster)
                || mons_is_pacified(monster))
            {
                simple_monster_message(monster, " reads a scroll.");
                monster_teleport(monster, false);
                read  = true;
                ident = ID_KNOWN_TYPE;
            }
        }
        break;

    case SCR_BLINKING:
        if (mons_is_caught(monster) || mons_is_fleeing(monster)
            || mons_is_pacified(monster))
        {
            if (mons_near(monster))
            {
                simple_monster_message(monster, " reads a scroll.");
                simple_monster_message(monster, " blinks!");
                monster_blink(monster);
                read  = true;
                ident = ID_KNOWN_TYPE;
            }
        }
        break;

    case SCR_SUMMONING:
        if (mons_near(monster))
        {
            simple_monster_message(monster, " reads a scroll.");
            create_monster(
                mgen_data(MONS_ABOMINATION_SMALL, SAME_ATTITUDE(monster),
                          2, monster->pos(), monster->foe) );
            read  = true;
            ident = ID_KNOWN_TYPE;
        }
        break;
    }

    if (read)
    {
        if (dec_mitm_item_quantity(monster->inv[MSLOT_SCROLL], 1))
            monster->inv[MSLOT_SCROLL] = NON_ITEM;

        if (ident != ID_UNKNOWN_TYPE && was_visible)
            set_ident_type(OBJ_SCROLLS, scroll_type, ident);

        monster->lose_energy(EUT_ITEM);
    }

    return read;
}

//---------------------------------------------------------------
//
// handle_wand
//
// Give the monster a chance to zap a wand. Returns true if the
// monster zapped.
//
//---------------------------------------------------------------
static bool _handle_wand(monsters *monster, bolt &beem)
{
    // Yes, there is a logic to this ordering {dlb}:
    if (!mons_near(monster)
        || mons_is_sleeping(monster)
        || monster->has_ench(ENCH_SUBMERGED)
        || monster->inv[MSLOT_WAND] == NON_ITEM
        || mitm[monster->inv[MSLOT_WAND]].plus <= 0
        || coinflip())
    {
        return (false);
    }

    bool niceWand    = false;
    bool zap         = false;
    bool was_visible = (mons_near(monster) && player_monster_visible(monster));

    item_def &wand(mitm[monster->inv[MSLOT_WAND]]);

    // map wand type to monster spell type
    const spell_type mzap = _map_wand_to_mspell(wand.sub_type);
    if (mzap == SPELL_NO_SPELL)
        return (false);

    // set up the beam
    int power         = 30 + monster->hit_dice;
    bolt theBeam      = mons_spells(mzap, power);

    beem.name         = theBeam.name;
    beem.beam_source  = monster_index(monster);
    beem.source_x     = monster->x;
    beem.source_y     = monster->y;
    beem.colour       = theBeam.colour;
    beem.range        = theBeam.range;
    beem.rangeMax     = theBeam.rangeMax;
    beem.damage       = theBeam.damage;
    beem.ench_power   = theBeam.ench_power;
    beem.hit          = theBeam.hit;
    beem.type         = theBeam.type;
    beem.flavour      = theBeam.flavour;
    beem.thrower      = theBeam.thrower;
    beem.is_beam      = theBeam.is_beam;
    beem.is_explosion = theBeam.is_explosion;

#if HISCORE_WEAPON_DETAIL
    beem.aux_source =
        wand.name(DESC_QUALNAME, false, true, false, false);
#else
    beem.aux_source =
        wand.name(DESC_QUALNAME, false, true, false, false,
                  ISFLAG_KNOW_CURSE | ISFLAG_KNOW_PLUSES);
#endif

    const int wand_type = wand.sub_type;
    switch (wand_type)
    {
    case WAND_DISINTEGRATION:
        // Dial down damage from wands of disintegration, since
        // disintegration beams can do large amounts of damage.
        beem.damage.size = beem.damage.size * 2 / 3;
        break;

    case WAND_ENSLAVEMENT:
    case WAND_DIGGING:
    case WAND_RANDOM_EFFECTS:
        // These have been deemed "too tricky" at this time {dlb}:
        return (false);

    case WAND_POLYMORPH_OTHER:
         // Monsters can be very trigger happy with wands, reduce this
         // for polymorph.
         if (!one_chance_in(5))
             return (false);
         break;

    // These are wands that monsters will aim at themselves {dlb}:
    case WAND_HASTING:
        if (!monster->has_ench(ENCH_HASTE))
        {
            beem.target_x = monster->x;
            beem.target_y = monster->y;

            niceWand = true;
            break;
        }
        return (false);

    case WAND_HEALING:
        if (monster->hit_points <= monster->max_hit_points / 2)
        {
            beem.target_x = monster->x;
            beem.target_y = monster->y;

            niceWand = true;
            break;
        }
        return (false);

    case WAND_INVISIBILITY:
        if (!monster->has_ench(ENCH_INVIS)
            && !monster->has_ench(ENCH_SUBMERGED)
            && (!mons_friendly(monster) || player_see_invis(false)))
        {
            beem.target_x = monster->x;
            beem.target_y = monster->y;

            niceWand = true;
            break;
        }
        return (false);

    case WAND_TELEPORTATION:
        if (monster->hit_points <= monster->max_hit_points / 2
            || mons_is_caught(monster))
        {
            if (!monster->has_ench(ENCH_TP)
                && !one_chance_in(20))
            {
                beem.target_x = monster->x;
                beem.target_y = monster->y;

                niceWand = true;
                break;
            }
            // This break causes the wand to be tried on the player.
            break;
        }
        return (false);
    }

    // Fire tracer, if necessary.
    if (!niceWand)
    {
        fire_tracer( monster, beem );

        // Good idea?
        zap = mons_should_fire(beem);
    }

    if (niceWand || zap)
    {
        if (!niceWand)
            _make_mons_stop_fleeing(monster);

        if (!simple_monster_message(monster, " zaps a wand."))
        {
            if (!silenced(you.x_pos, you.y_pos))
                mpr("You hear a zap.", MSGCH_SOUND);
        }

        // charge expenditure {dlb}
        wand.plus--;
        beem.is_tracer = false;
        fire_beam(beem);

        if (was_visible)
        {
            if (niceWand || beem.name != "0" || beem.obvious_effect)
                set_ident_type(OBJ_WANDS, wand_type, ID_KNOWN_TYPE);
            else
                set_ident_type(OBJ_WANDS, wand_type, ID_MON_TRIED_TYPE);

            // Increment zap count.
            if (wand.plus2 >= 0)
                wand.plus2++;
        }

        monster->lose_energy(EUT_ITEM);

        return (true);
    }

    return (false);
}

// Returns a suitable breath weapon for the draconian; does not handle all
// draconians, does fire a tracer.
static spell_type _get_draconian_breath_spell( const monsters *monster )
{
    spell_type draco_breath = SPELL_NO_SPELL;

    if (mons_genus( monster->type ) == MONS_DRACONIAN)
    {
        switch (draco_subspecies( monster ))
        {
        case MONS_BLACK_DRACONIAN:
            draco_breath = SPELL_LIGHTNING_BOLT;
            break;

        case MONS_PALE_DRACONIAN:
            draco_breath = SPELL_STEAM_BALL;
            break;

        case MONS_GREEN_DRACONIAN:
            draco_breath = SPELL_POISONOUS_CLOUD;
            break;

        case MONS_PURPLE_DRACONIAN:
            draco_breath = SPELL_ISKENDERUNS_MYSTIC_BLAST;
            break;

        case MONS_MOTTLED_DRACONIAN:
            draco_breath = SPELL_STICKY_FLAME;
            break;

        case MONS_DRACONIAN:
        case MONS_YELLOW_DRACONIAN:     // already handled as ability
        case MONS_RED_DRACONIAN:        // already handled as ability
        case MONS_WHITE_DRACONIAN:      // already handled as ability
        default:
            break;
        }
    }


    if (draco_breath != SPELL_NO_SPELL)
    {
        // [ds] Check line-of-fire here. It won't happen elsewhere.
        bolt beem;
        setup_mons_cast(monster, beem, draco_breath);

        fire_tracer(monster, beem);

        if (!mons_should_fire(beem))
            draco_breath = SPELL_NO_SPELL;
    }

    return (draco_breath);
}

static bool _is_emergency_spell(const monster_spells &msp, int spell)
{
    // If the emergency spell appears early, it's probably not a dedicated
    // escape spell.
    for (int i = 0; i < 5; ++i)
        if (msp[i] == spell)
            return (false);

    return (msp[5] == spell);
}

static const char *_orb_of_fire_glow()
{
    static const char *orb_glows[] =
    {
        " glows yellow.",
        " glows bright magenta.",
        " glows deep purple.",       // Smoke on the Water
        " glows red.",
        " emits a lurid red light.",
    };
    return RANDOM_ELEMENT(orb_glows);
}

static bool _mons_announce_cast(monsters *monster, bool nearby,
                                spell_type spell_cast,
                                spell_type draco_breath)
{
    const msg_channel_type spl = (mons_friendly(monster) ? MSGCH_FRIEND_SPELL
                                                         : MSGCH_MONSTER_SPELL);

    if (nearby)
    {
        // Handle monsters within range of player.
        if (monster->type == MONS_GERYON)
        {
            if (silenced(monster->x, monster->y))
                return (false);

            simple_monster_message( monster, " winds a great silver horn.",
                                    spl );
        }
        else if (mons_is_demon( monster->type ))
        {
            simple_monster_message( monster, " gestures.", spl );
        }
        else
        {
            switch (monster->type)
            {
            default:
                if (spell_cast == draco_breath)
                {
                    if (!simple_monster_message(monster, " breathes.", spl))
                    {
                        if (!silenced(monster->x, monster->y)
                            && !silenced(you.x_pos, you.y_pos))
                        {
                            mpr("You hear a roar.", MSGCH_SOUND);
                        }
                    }
                    break;
                }

                if (silenced(monster->x, monster->y))
                    return (false);

                if (mons_class_flag(monster->type, M_PRIEST))
                {
                    switch (random2(3))
                    {
                    case 0:
                        simple_monster_message(monster, " prays.", spl);
                        break;
                    case 1:
                        simple_monster_message(monster,
                                               " mumbles some strange prayers.",
                                               spl);
                        break;
                    case 2:
                    default:
                        simple_monster_message(monster,
                                               " utters an invocation.", spl);
                        break;
                    }
                }
                else // not a priest
                {
                    switch (random2(3))
                    {
                    case 0:
                        // XXX: could be better, chosen to match the
                        // ones in monspeak.cc... has the problem
                        // that it doesn't suggest a vocal component. -- bwr
                        if (player_monster_visible(monster))
                        {
                            simple_monster_message(monster,
                                                   " gestures wildly.", spl );
                        }
                        break;
                    case 1:
                        simple_monster_message(monster,
                                               " mumbles some strange words.",
                                               spl);
                        break;
                    case 2:
                    default:
                        simple_monster_message(monster, " casts a spell.", spl);
                        break;
                    }
                }
                break;

            case MONS_BALL_LIGHTNING:
                monster->hit_points = -1;
                break;

            case MONS_STEAM_DRAGON:
            case MONS_MOTTLED_DRAGON:
            case MONS_STORM_DRAGON:
            case MONS_GOLDEN_DRAGON:
            case MONS_SHADOW_DRAGON:
            case MONS_SWAMP_DRAGON:
            case MONS_SWAMP_DRAKE:
            case MONS_DEATH_DRAKE:
            case MONS_HELL_HOG:
            case MONS_SERPENT_OF_HELL:
            case MONS_QUICKSILVER_DRAGON:
            case MONS_IRON_DRAGON:
                if (!simple_monster_message(monster, " breathes.", spl))
                {
                    if (!silenced(monster->x, monster->y)
                        && !silenced(you.x_pos, you.y_pos))
                    {
                        mpr("You hear a roar.", MSGCH_SOUND);
                    }
                }
                break;

            case MONS_VAPOUR:
                monster->add_ench(ENCH_SUBMERGED);
                break;

            case MONS_BRAIN_WORM:
            case MONS_ELECTRIC_GOLEM:
            case MONS_ICE_STATUE:
                // These don't show any signs that they're casting a spell.
                break;

            case MONS_GREAT_ORB_OF_EYES:
            case MONS_SHINING_EYE:
            case MONS_EYE_OF_DEVASTATION:
                simple_monster_message(monster, " gazes.", spl);
                break;

            case MONS_GIANT_ORANGE_BRAIN:
                simple_monster_message(monster, " pulsates.", spl);
                break;

            case MONS_NAGA:
            case MONS_NAGA_WARRIOR:
                simple_monster_message(monster, " spits poison.", spl);
                break;

            case MONS_ORB_OF_FIRE:
                simple_monster_message(monster, _orb_of_fire_glow(), spl);
                break;
            }
        }
    }
    else
    {
        // Handle far-away monsters.
        if (monster->type == MONS_GERYON
            && !silenced(you.x_pos, you.y_pos))
        {
            mpr("You hear a weird and mournful sound.", MSGCH_SOUND);
        }
    }

    return (true);
}

//---------------------------------------------------------------
//
// handle_spell
//
// Give the monster a chance to cast a spell. Returns true if
// a spell was cast.
//
//---------------------------------------------------------------
static bool _handle_spell(monsters *monster, bolt &beem)
{
    bool monsterNearby = mons_near(monster);
    bool finalAnswer   = false;   // as in: "Is that your...?" {dlb}
    const spell_type draco_breath = _get_draconian_breath_spell(monster);

    if (is_sanctuary(monster->x, monster->y)
        && !mons_wont_attack(monster))
    {
        return (false);
    }

    // Yes, there is a logic to this ordering {dlb}:
    if (mons_is_sleeping(monster)
        || monster->has_ench(ENCH_SUBMERGED)
        || !mons_class_flag(monster->type, M_SPELLCASTER)
           && draco_breath == SPELL_NO_SPELL)
    {
        return (false);
    }

    if ((mons_class_flag(monster->type, M_ACTUAL_SPELLS)
            || mons_class_flag(monster->type, M_PRIEST))
        && monster->has_ench(ENCH_GLOWING_SHAPESHIFTER, ENCH_SHAPESHIFTER))
    {
        return (false);           //jmf: shapeshifters don't get spells, just
                                  //     physical powers.
    }
    else if (monster->has_ench(ENCH_CONFUSION)
             && !mons_class_flag(monster->type, M_CONFUSED))
    {
        return (false);
    }
    else if (monster->type == MONS_PANDEMONIUM_DEMON
             && !monster->ghost->spellcaster)
    {
        return (false);
    }
    else if (random2(200) > monster->hit_dice + 50
             || (monster->type == MONS_BALL_LIGHTNING && coinflip()))
    {
        return (false);
    }
    else
    {
        spell_type spell_cast = SPELL_NO_SPELL;
        monster_spells hspell_pass(monster->spells);

        if (!mon_enemies_around(monster))
        {
            // Force the casting of dig when the player is not visible -
            // this is EVIL!
            if (monster->has_spell(SPELL_DIG)
                && mons_is_seeking(monster))
            {
                spell_cast = SPELL_DIG;
                finalAnswer = true;
            }
            else if ((monster->has_spell(SPELL_LESSER_HEALING)
                         || monster->has_spell(SPELL_GREATER_HEALING))
                     && monster->hit_points < monster->max_hit_points)
            {
                // The player's out of sight!
                // Quick, let's take a turn to heal ourselves. -- bwr
                spell_cast = monster->has_spell(SPELL_GREATER_HEALING) ?
                             SPELL_GREATER_HEALING : SPELL_LESSER_HEALING;
                finalAnswer = true;
            }
            else if (mons_is_fleeing(monster) || mons_is_pacified(monster))
            {
                // Since the player isn't around, we'll extend the monster's
                // normal choices to include the self-enchant slot.
                int foundcount = 0;
                for (int i = NUM_MONSTER_SPELL_SLOTS - 1; i >= 0; --i)
                {
                    if (ms_useful_fleeing_out_of_sight(monster, hspell_pass[i])
                        && one_chance_in(++foundcount))
                    {
                        spell_cast = hspell_pass[i];
                        finalAnswer = true;
                    }
                }
            }
            else if (monster->foe == MHITYOU && !monsterNearby)
                return (false);
        }

        // Monsters caught in a net try to get away.
        // This is only urgent if enemies are around.
        if (!finalAnswer && mon_enemies_around(monster)
            && mons_is_caught(monster) && one_chance_in(4))
        {
            for (int i = 0; i < NUM_MONSTER_SPELL_SLOTS; i++)
            {
                if (ms_quick_get_away( monster, hspell_pass[i] ))
                {
                    spell_cast = hspell_pass[i];
                    finalAnswer = true;
                    break;
                }
            }
        }

        // Promote the casting of useful spells for low-HP monsters.
        if (!finalAnswer
            && monster->hit_points < monster->max_hit_points / 4
            && !one_chance_in(4))
        {
            // Note: There should always be at least some chance we don't
            // get here... even if the monster is on its last HP.  That
            // way we don't have to worry about monsters infinitely casting
            // Healing on themselves (e.g. orc high priests).
            if ((mons_is_fleeing(monster) || mons_is_pacified(monster))
                && ms_low_hitpoint_cast(monster, hspell_pass[5]))
            {
                spell_cast = hspell_pass[5];
                finalAnswer = true;
            }

            if (!finalAnswer)
            {
                int found_spell = 0;
                for (int i = 0; i < NUM_MONSTER_SPELL_SLOTS; ++i)
                {
                    if (ms_low_hitpoint_cast( monster, hspell_pass[i] )
                        && one_chance_in(++found_spell))
                    {
                        spell_cast = hspell_pass[i];
                        finalAnswer = true;
                    }
                }
            }
        }

        if (!finalAnswer)
        {
            // If nothing found by now, safe friendlies and good
            // neutrals will rarely cast.
            if (mons_wont_attack(monster) && !mon_enemies_around(monster)
                && !one_chance_in(10))
            {
                return (false);
            }

            // Remove healing/invis/haste if we don't need them.
            int num_no_spell = 0;

            for (int i = 0; i < NUM_MONSTER_SPELL_SLOTS; i++)
            {
                if (hspell_pass[i] == SPELL_NO_SPELL)
                    num_no_spell++;
                else if (ms_waste_of_time( monster, hspell_pass[i] )
                         || hspell_pass[i] == SPELL_DIG)
                {
                    // Should monster not have selected dig by now,
                    // it never will.
                    hspell_pass[i] = SPELL_NO_SPELL;
                    num_no_spell++;
                }
            }

            // If no useful spells... cast no spell.
            if (num_no_spell == NUM_MONSTER_SPELL_SLOTS
                && draco_breath == SPELL_NO_SPELL)
            {
                return (false);
            }

            // Up to four tries to pick a spell.
            for (int loopy = 0; loopy < 4; ++loopy)
            {
                bool spellOK = false;

                // Setup spell - monsters that are fleeing or pacified
                // and leaving the level will always try to choose their
                // emergency spell.
                if (mons_is_fleeing(monster) || mons_is_pacified(monster))
                {
                    spell_cast = (one_chance_in(5) ? SPELL_NO_SPELL
                                                   : hspell_pass[5]);

                    // Pacified monsters leaving the level won't choose
                    // emergency spells harmful to the area.
                    if (spell_cast != SPELL_NO_SPELL
                        && mons_is_pacified(monster)
                            && spell_harms_area(spell_cast))
                    {
                        spell_cast = SPELL_NO_SPELL;
                    }
                }
                else
                {
                    // Randomly picking one of the non-emergency spells:
                    spell_cast = hspell_pass[random2(5)];
                }

                if (spell_cast == SPELL_NO_SPELL)
                    continue;

                // Setup the spell.
                setup_mons_cast(monster, beem, spell_cast);

                // beam-type spells requiring tracers
                if (spell_needs_tracer(spell_cast))
                {
                    fire_tracer(monster, beem);
                    // Good idea?
                    if (mons_should_fire(beem))
                        spellOK = true;
                }
                else
                {
                    // All direct-effect/summoning/self-enchantments/etc.
                    spellOK = true;

                    if (ms_direct_nasty(spell_cast)
                             && mons_aligned(monster_index(monster),
                                             monster->foe))
                    {
                        spellOK = false;
                    }
                    else if (monster->foe == MHITYOU
                            || monster->foe == MHITNOT)
                    {
                        // XXX: Note the crude hack so that monsters can
                        // use ME_ALERT to target (we should really have
                        // a measure of time instead of peeking to see
                        // if the player is still there). -- bwr
                        if (!mons_player_visible(monster)
                            && (monster->target_x != you.x_pos
                                || monster->target_y != you.y_pos
                                || coinflip()))
                        {
                            spellOK = false;
                        }
                    }
                    else if (!mon_can_see_monster(monster,
                                                  &menv[monster->foe]))
                    {
                        spellOK = false;
                    }
                    else if (monster->type == MONS_DAEVA)
                    {
                        const monsters *mon = &menv[monster->foe];

                        // Don't allow daevas to make unchivalric magic
                        // attacks, except against appropriate monsters.
                        if (is_unchivalric_attack(monster, mon, mon)
                            && !tso_unchivalric_attack_safe_monster(mon))
                        {
                            spellOK = false;
                        }
                    }
                }

                // If not okay, then maybe we'll cast a defensive spell.
                if (!spellOK)
                {
                    spell_cast = (coinflip() ? hspell_pass[2]
                                             : SPELL_NO_SPELL);
                }

                if (spell_cast != SPELL_NO_SPELL)
                    break;
            }
        }

        // If there's otherwise no ranged attack use the breath weapon.
        // The breath weapon is also occasionally used.
        if (draco_breath != SPELL_NO_SPELL
            && (spell_cast == SPELL_NO_SPELL
                 || !_is_emergency_spell(hspell_pass, spell_cast)
                    && one_chance_in(4))
            && !_is_player_or_mon_sanct(monster))
        {
            spell_cast = draco_breath;
            finalAnswer = true;
        }

        // Should the monster *still* not have a spell, well, too bad {dlb}:
        if (spell_cast == SPELL_NO_SPELL)
            return (false);

        // Try to animate dead: if nothing rises, pretend we didn't cast it.
        if (spell_cast == SPELL_ANIMATE_DEAD
            && !animate_dead(monster, 100, SAME_ATTITUDE(monster),
                             monster->foe, GOD_NO_GOD, false))
        {
            return (false);
        }

        if (!_mons_announce_cast(monster, monsterNearby,
                                 spell_cast, draco_breath))
        {
            return (false);
        }

        // FINALLY! determine primary spell effects {dlb}:
        if (spell_cast == SPELL_BLINK)
        {
            // Why only cast blink if nearby? {dlb}
            if (monsterNearby)
            {
                simple_monster_message(monster, " blinks!");
                monster_blink(monster);

                monster->lose_energy(EUT_SPELL);
            }
            else
                return (false);
        }
        else
        {
            if (spell_needs_foe(spell_cast))
                _make_mons_stop_fleeing(monster);

            mons_cast(monster, beem, spell_cast);
            mmov_x = 0;
            mmov_y = 0;
            monster->lose_energy(EUT_SPELL);
        }
    } // end "if mons_class_flag(monster->type, M_SPELLCASTER) ...

    return (true);
}

// Returns a rough estimate of damage from throwing the wielded weapon.
int mons_thrown_weapon_damage(const item_def *weap)
{
    if (!weap || get_weapon_brand(*weap) != SPWPN_RETURNING)
        return (0);

    return std::max(0, (property(*weap, PWPN_DAMAGE) + weap->plus2 / 2));
}

// Returns a rough estimate of damage from firing/throwing missile.
int mons_missile_damage(const item_def *launch,
                        const item_def *missile)
{
    if (!missile || (!launch && !is_throwable(*missile)))
        return (0);

    const int missile_damage = property(*missile, PWPN_DAMAGE) / 2 + 1;
    const int launch_damage  = launch? property(*launch, PWPN_DAMAGE) : 0;
    return std::max(0, launch_damage + missile_damage);
}

int mons_weapon_damage_rating(const item_def &launcher)
{
    return (property(launcher, PWPN_DAMAGE) + launcher.plus2);
}

// Given the monster's current weapon and alt weapon (either or both of
// which may be NULL), works out whether using missiles or throwing the
// main weapon (with returning brand) is better. If using missiles that
// need a launcher, sets *launcher to the launcher.
//
// If the monster has no ranged weapon attack, returns NON_ITEM.
//
int mons_pick_best_missile(monsters *mons, item_def **launcher,
                           bool ignore_melee)
{
    *launcher = NULL;
    item_def *melee = NULL, *launch = NULL;
    for (int i = MSLOT_WEAPON; i <= MSLOT_ALT_WEAPON; ++i)
    {
        if (item_def *item = mons->mslot_item(static_cast<mon_inv_type>(i)))
        {
            if (is_range_weapon(*item))
                launch = item;
            else if (!ignore_melee)
                melee = item;
        }
    }

    const item_def *missiles = mons->missiles();
    if (launch && missiles && !missiles->launched_by(*launch))
        launch = NULL;

    const int tdam = mons_thrown_weapon_damage(melee);
    const int fdam = mons_missile_damage(launch, missiles);

    if (!tdam && !fdam)
        return (NON_ITEM);
    else if (tdam >= fdam)
        return (melee->index());
    else
    {
        *launcher = launch;
        return (missiles->index());
    }
}

//---------------------------------------------------------------
//
// handle_throw
//
// Give the monster a chance to throw something. Returns true if
// the monster hurled.
//
//---------------------------------------------------------------
static bool _handle_throw(monsters *monster, bolt & beem)
{
    // Yes, there is a logic to this ordering {dlb}:
    if (monster->incapacitated()
        || monster->asleep()
        || monster->submerged())
    {
        return (false);
    }

    // Zombies are always too stupid to do this.
    if (mons_itemuse(monster->type) < MONUSE_OPEN_DOORS)
        return (false);

    const bool archer = mons_class_flag(monster->type, M_ARCHER);
    // Highly-specialised archers are more likely to shoot than talk.
    if (one_chance_in(archer? 9 : 5))
        return (false);

    // Don't allow offscreen throwing for now.
    if (monster->foe == MHITYOU && !mons_near(monster))
        return (false);

    // Monsters won't shoot in melee range, largely for balance reasons.
    // Specialist archers are an exception to this rule.
    if (!archer
        && adjacent( beem.target_x, beem.target_y, monster->x, monster->y ))
    {
        return (false);
    }

    // Greatly lowered chances if the monster is fleeing or pacified and
    // leaving the level.
    if ((mons_is_fleeing(monster) || mons_is_pacified(monster))
        && !one_chance_in(8))
    {
        return (false);
    }

    item_def *launcher = NULL;
    const item_def *weapon = NULL;
    const int mon_item = mons_pick_best_missile(monster, &launcher);

    if (mon_item == NON_ITEM || !is_valid_item(mitm[mon_item]))
        return (false);

    if (_is_player_or_mon_sanct(monster))
        return (false);

    // Throwing a net at a target that is already caught would be
    // completely useless, so bail out.
    if (mitm[mon_item].base_type == OBJ_MISSILES
        && mitm[mon_item].sub_type == MI_THROWING_NET
        && ( beem.target_x == you.x_pos && beem.target_y == you.y_pos
                && you.caught()
             || mgrd[beem.target_x][beem.target_y] != NON_MONSTER
                && mons_is_caught(&menv[mgrd[beem.target_x][beem.target_y]]) ))
    {
        return (false);
    }

    // If the attack needs a launcher that we can't wield, bail out.
    if (launcher)
    {
        weapon = monster->mslot_item(MSLOT_WEAPON);
        if (weapon && weapon != launcher && weapon->cursed())
            return (false);
    }

    // Ok, we'll try it.
    setup_generic_throw( monster, beem );

    // Set fake damage for the tracer.
    beem.damage = dice_def(10, 10);

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

    // Clear fake damage (will be set correctly in mons_throw).
    beem.damage = 0;

    // Good idea?
    if (mons_should_fire( beem ))
    {
        // Monsters shouldn't shoot if fleeing, so let them "turn to attack".
        _make_mons_stop_fleeing(monster);

        if (launcher && launcher != weapon)
            monster->swap_weapons();

        beem.name.clear();
        return (mons_throw( monster, beem, mon_item ));
    }

    return (false);
}                               // end handle_throw()

static bool _handle_monster_spell(monsters *monster, bolt &beem)
{
    // Shapeshifters don't get spells.
    if (!monster->has_ench( ENCH_GLOWING_SHAPESHIFTER,
                            ENCH_SHAPESHIFTER )
        || !mons_class_flag( monster->type, M_ACTUAL_SPELLS ))
    {
        if (_handle_spell(monster, beem))
            return (true);
    }
    return (false);
}

// Give the monster its action energy (aka speed_increment).
static void _monster_add_energy(monsters *monster)
{
    int energy_gained = (monster->speed * you.time_taken) / 10;

    // Slow monsters might get 0 here. Maybe we should factor in
    // *how* slow it is...but a 10-to-1 move ratio seems more than
    // enough.
    if (energy_gained == 0 && monster->speed != 0)
        energy_gained = 1;

    monster->speed_increment += energy_gained;
}

int mons_natural_regen_rate(monsters *monster)
{
    // A HD divider ranging from 3 (at 1 HD) to 1 (at 8 HD).
    int divider =
        std::max(div_rand_round(15 - monster->hit_dice, 4), 1);

    // The undead have a harder time regenerating.
    switch (monster->holiness())
    {
    // The undead don't regenerate easily.
    case MH_UNDEAD:
        divider *= 4;
        break;

    // And golems have it worse.
    case MH_NONLIVING:
        divider *= 5;
        break;

    default:
        break;
    }

    return (std::max(div_rand_round(monster->hit_dice, divider), 1));
}

static inline bool _mons_natural_regen_roll(monsters *monster)
{
    const int regen_rate = mons_natural_regen_rate(monster);
    return (x_chance_in_y(regen_rate, 25));
}

// Do natural regeneration for monster.
static void _monster_regenerate(monsters *monster)
{
    if (monster->has_ench(ENCH_SICK)
        || mons_class_flag(monster->type, M_NO_REGEN))
    {
        return;
    }

    // Non-land creatures out of their element cannot regenerate.
    if (mons_habitat(monster) != HT_LAND
        && !monster_habitable_grid(monster, grd(monster->pos())))
    {
        return;
    }

    if (monster_descriptor(monster->type, MDSC_REGENERATES)
        || (monster->type == MONS_FIRE_ELEMENTAL
            && (grd(monster->pos()) == DNGN_LAVA
                || cloud_type_at(monster->pos()) == CLOUD_FIRE))

        || (monster->type == MONS_WATER_ELEMENTAL
            && grid_is_watery(grd(monster->pos())))

        || (monster->type == MONS_AIR_ELEMENTAL
            && env.cgrid(monster->pos()) == EMPTY_CLOUD
            && one_chance_in(3))

        || _mons_natural_regen_roll(monster))
    {
        heal_monster(monster, 1, false);
    }
}

static bool _swap_monsters(const int mover_idx, const int moved_idx)
{
    monsters* mover = &menv[mover_idx];
    monsters* moved = &menv[moved_idx];

    // Can't swap with a stationary monster.
    if (mons_is_stationary(moved))
        return (false);

    // Swapping is a purposeful action.
    if (mover->confused())
        return (false);

    // Right now just happens in sanctuary.
    if (!is_sanctuary(mover->x, mover->y) || !is_sanctuary(moved->x, moved->y))
        return (false);

    // A friendly or good-neutral monster moving past a fleeing hostile
    // or neutral monster, or vice versa.
    if (mons_wont_attack(mover) == mons_wont_attack(moved)
        || mons_is_fleeing(mover) == mons_is_fleeing(moved))
    {
        return (false);
    }

    // Don't swap places if the player explicitly ordered their pet to
    // attack monsters.
    if ((mons_friendly(mover) || mons_friendly(moved))
        && you.pet_target != MHITYOU && you.pet_target != MHITNOT)
    {
        return (false);
    }

    if (!mover->can_pass_through(moved->x, moved->y)
        || !moved->can_pass_through(mover->x, mover->y))
    {
        return (false);
    }

    if (!monster_habitable_grid(mover, grd[moved->x][moved->y])
        || !monster_habitable_grid(moved, grd[mover->x][mover->y]))
    {
        return (false);
    }

    // Okay, we can do the swap.
    const coord_def mover_pos = mover->pos();
    const coord_def moved_pos = moved->pos();

    mover->x = moved_pos.x;
    mover->y = moved_pos.y;

    moved->x = mover_pos.x;
    moved->y = mover_pos.y;

    mgrd(mover->pos()) = mover_idx;
    mgrd(moved->pos()) = moved_idx;

    if (you.can_see(mover) && you.can_see(moved))
    {
        mprf("%s and %s swap places.", mover->name(DESC_CAP_THE).c_str(),
             moved->name(DESC_NOCAP_THE).c_str());
    }

    return (true);
}

static void _swim_or_move_energy(monsters *mon)
{
    const dungeon_feature_type feat = grd[mon->x][mon->y];

    // FIXME: Replace check with mons_is_swimming()?
    mon->lose_energy( (feat >= DNGN_LAVA && feat <= DNGN_SHALLOW_WATER
                       && !mon->airborne()) ? EUT_SWIM
                                            : EUT_MOVE );
}

#if DEBUG
#    define DEBUG_ENERGY_USE(problem) \
         if (monster->speed_increment == old_energy) \
             mprf(MSGCH_DIAGNOSTICS, \
                  problem " for monster '%s' consumed no energy", \
                  monster->name(DESC_PLAIN).c_str(), true);
#else
#    define DEBUG_ENERGY_USE(problem) ((void) 0)
#endif

static void _handle_monster_move(int i, monsters *monster)
{
    bool brkk = false;
    bolt beem;
    FixedArray <unsigned int, 19, 19> show;

    monster->hit_points = std::min(monster->max_hit_points,
                                   monster->hit_points);

    // Monster just summoned (or just took stairs), skip this action.
    if (testbits( monster->flags, MF_JUST_SUMMONED ))
    {
        monster->flags &= ~MF_JUST_SUMMONED;
        return;
    }

    _monster_add_energy(monster);

    // Handle clouds on nonmoving monsters.
    if (monster->speed == 0
        && env.cgrid[monster->x][monster->y] != EMPTY_CLOUD
        && !monster->has_ench(ENCH_SUBMERGED))
    {
        _mons_in_cloud( monster );
    }

    // Apply monster enchantments once for every normal-speed
    // player turn.
    monster->ench_countdown -= you.time_taken;
    while (monster->ench_countdown < 0)
    {
        monster->ench_countdown += 10;
        monster->apply_enchantments();

        // If the monster *merely* died just break from the loop
        // rather than quit altogether, since we have to deal with
        // giant spores and ball lightning exploding at the end of the
        // function, but do return if the monster's data has been
        // reset, since then the monster type is invalid.
        if (monster->type == -1)
            return;
        else if (monster->hit_points < 1)
            break;
    }

    // Memory is decremented here for a reason -- we only want it
    // decrementing once per monster "move".
    if (monster->foe_memory > 0)
        monster->foe_memory--;

    // Otherwise there are potential problems with summonings.
    if (monster->type == MONS_GLOWING_SHAPESHIFTER)
        monster->add_ench(ENCH_GLOWING_SHAPESHIFTER);

    if (monster->type == MONS_SHAPESHIFTER)
        monster->add_ench(ENCH_SHAPESHIFTER);

    // We reset batty monsters from wander to seek here, instead
    // of in handle_behaviour() since that will be called with
    // every single movement, and we want these monsters to
    // hit and run. -- bwr
    if (monster->foe != MHITNOT && mons_is_wandering(monster)
        && mons_is_batty(monster))
    {
        monster->behaviour = BEH_SEEK;
    }

    monster->check_speed();

    monsterentry* entry = get_monster_data(monster->type);
    if (!entry)
        return;

    int old_energy      = INT_MAX;
    int non_move_energy = std::min(entry->energy_usage.move,
                                   entry->energy_usage.swim);

    while (monster->has_action_energy())
    {
        // The continues & breaks are WRT this.
        if (!monster->alive())
            break;

        if (monster->speed_increment >= old_energy)
        {
#if DEBUG
            if (monster->speed_increment == old_energy)
            {
                mprf(MSGCH_DIAGNOSTICS, "'%s' has same energy as last loop",
                     monster->name(DESC_PLAIN, true).c_str());
            }
            else
            {
                mprf(MSGCH_DIAGNOSTICS, "'%s' has MORE energy than last loop",
                     monster->name(DESC_PLAIN, true).c_str());
            }
#endif
            monster->speed_increment = old_energy - 10;
            old_energy               = monster->speed_increment;
            continue;
        }
        old_energy = monster->speed_increment;

        monster->shield_blocks = 0;

        if (env.cgrid[monster->x][monster->y] != EMPTY_CLOUD)
        {
            if (monster->has_ench(ENCH_SUBMERGED))
            {
                monster->speed_increment -= entry->energy_usage.swim;
                break;
            }

            if (monster->type == -1)
            {
                monster->speed_increment -= entry->energy_usage.move;
                break;  // problem with vortices
            }

            _mons_in_cloud(monster);

            if (monster->type == -1)
            {
                monster->speed_increment = 1;
                break;
            }
        }

        if (monster->type == MONS_TIAMAT && one_chance_in(3))
        {
            int cols[] = { RED, WHITE, DARKGREY, GREEN, MAGENTA };
            int newcol = cols[random2(sizeof(cols) / sizeof(cols[0]))];
            monster->colour = newcol;
        }

        _monster_regenerate(monster);

        if (mons_cannot_act(monster))
        {
            monster->speed_increment -= non_move_energy;
            continue;
        }

        _handle_behaviour(monster);

        // Submerging monsters will hide from clouds.
        if (monster_can_submerge(monster, grd[monster->x][monster->y])
            && env.cgrid[monster->x][monster->y] != EMPTY_CLOUD)
        {
            monster->add_ench(ENCH_SUBMERGED);
        }

        if (monster->speed >= 100)
        {
            monster->speed_increment -= non_move_energy;
            continue;
        }

        if (mons_is_zombified(monster)
            && monster->type != MONS_SPECTRAL_THING)
        {
            monster->max_hit_points = monster->hit_points;
        }

        if (igrd[monster->x][monster->y] != NON_ITEM
            && (mons_itemuse(monster->type) == MONUSE_WEAPONS_ARMOUR
                || mons_itemuse(monster->type) == MONUSE_EATS_ITEMS))
        {
            // Keep neutral and charmed monsters from picking up stuff.
            // Same for friendlies if friendly_pickup is set to "none".
            if (!mons_neutral(monster) && !monster->has_ench(ENCH_CHARM)
                && (!mons_friendly(monster)
                    || you.friendly_pickup > FRIENDLY_PICKUP_NONE))
            {
                if (_handle_pickup(monster))
                {
                    DEBUG_ENERGY_USE("handle_pickup()");
                    continue;
                }
            }
        }

        if (mons_is_lurking(monster))
        {
            // Lurking monsters only stop lurking if their target is right
            // next to them, otherwise they just sit there.
            if (monster->foe != MHITNOT
                && abs(monster->target_x - monster->x) <= 1
                && abs(monster->target_y - monster->y) <= 1)
            {
                if (monster->has_ench(ENCH_SUBMERGED))
                {
                    // Don't unsubmerge if the monster is too damaged or
                    // if the monster is afraid.
                    if (monster->hit_points <= monster->max_hit_points / 2
                        || monster->has_ench(ENCH_FEAR))
                    {
                        monster->speed_increment -= non_move_energy;
                        continue;
                    }

                    if (!monster->del_ench(ENCH_SUBMERGED))
                    {
                        // Couldn't unsubmerge.
                        monster->speed_increment -= non_move_energy;
                        continue;
                    }
                }
                monster->behaviour = BEH_SEEK;
            }
            else
            {
                monster->speed_increment -= non_move_energy;
                continue;
            }
        }

        if (mons_is_caught(monster))
        {
            // Struggling against the net takes time.
            _swim_or_move_energy(monster);
        }
        else if (!mons_is_petrified(monster))
        {
            // Calculates mmov_x, mmov_y based on monster target.
            _handle_movement(monster);

            brkk = false;

            if (mons_is_confused(monster)
                || monster->type == MONS_AIR_ELEMENTAL
                   && monster->has_ench(ENCH_SUBMERGED))
            {
                std::vector<coord_def> moves;

                int pfound = 0;
                for (int yi = -1; yi <= 1; ++yi)
                    for (int xi = -1; xi <= 1; ++xi)
                    {
                        coord_def c = monster->pos() + coord_def(xi, yi);
                        if (in_bounds(c) && monster->can_pass_through(c)
                            && one_chance_in(++pfound))
                        {
                            mmov_x = xi;
                            mmov_y = yi;
                        }
                    }

                if (x_chance_in_y(2, 2 + pfound))
                    mmov_x = mmov_y = 0;

                // Bounds check: don't let confused monsters try to run
                // off the map.
                if (monster->x + mmov_x < 0 || monster->x + mmov_x >= GXM)
                    mmov_x = 0;

                if (monster->y + mmov_y < 0 || monster->y + mmov_y >= GYM)
                    mmov_y = 0;

                if (!monster->can_pass_through(monster->x + mmov_x,
                                               monster->y + mmov_y))
                {
                    mmov_x = mmov_y = 0;
                }

                int enemy = mgrd[monster->x + mmov_x][monster->y + mmov_y];
                if (enemy != NON_MONSTER
                    && !is_sanctuary(monster->x, monster->y)
                    && (mmov_x != 0 || mmov_y != 0))
                {
                    if (monsters_fight(i, enemy))
                    {
                        brkk = true;
                        mmov_x = 0;
                        mmov_y = 0;
                        DEBUG_ENERGY_USE("monsters_fight()");
                    }
                    else
                    {
                        // FIXME: None of these work!
                        // Instead run away!
                        if (monster->add_ench(mon_enchant(ENCH_FEAR)))
                        {
                            behaviour_event(monster, ME_SCARE, MHITNOT,
                                            monster->x + mmov_x,
                                            monster->y + mmov_y);
                        }
                        break;
                    }
                }
            }

            if (brkk)
                continue;
        }
        _handle_nearby_ability( monster );

        beem.target_x = monster->target_x;
        beem.target_y = monster->target_y;

        if (!mons_is_sleeping(monster)
            && !mons_is_wandering(monster)

            // Berserking monsters are limited to running up and
            // hitting their foes.
            && !monster->has_ench(ENCH_BERSERK))
        {
            // Prevents unfriendlies from nuking you from offscreen.
            // How nice!
            if (mons_friendly(monster) || mons_near(monster))
            {
                // [ds] Special abilities shouldn't overwhelm spellcasting
                // in monsters that have both. This aims to give them both
                // roughly the same weight.

                if (coinflip() ? _handle_special_ability(monster, beem)
                                 || _handle_monster_spell(monster, beem)
                               : _handle_monster_spell(monster, beem)
                                 || _handle_special_ability(monster, beem))
                {
                    DEBUG_ENERGY_USE("spell or special");
                    continue;
                }

                if (_handle_potion(monster, beem))
                {
                    DEBUG_ENERGY_USE("_handle_potion()");
                    continue;
                }

                if (_handle_scroll(monster))
                {
                    DEBUG_ENERGY_USE("handle_scroll()");
                    continue;
                }

                if (_handle_wand(monster, beem))
                {
                    DEBUG_ENERGY_USE("handle_wand()");
                    continue;
                }

                if (_handle_reaching(monster))
                {
                    DEBUG_ENERGY_USE("handle_reaching()");
                    continue;
                }
            }

            if (_handle_throw(monster, beem))
            {
                DEBUG_ENERGY_USE("handle_throw()");
                continue;
            }
        }

        if (!mons_is_caught(monster))
        {
            // See if we move into (and fight) an unfriendly monster.
            int targmon = mgrd[monster->x + mmov_x][monster->y + mmov_y];
            if (targmon != NON_MONSTER
                && targmon != i
                && !mons_aligned(i, targmon))
            {
                // Maybe they can swap places?
                if (_swap_monsters(i, targmon))
                {
                    _swim_or_move_energy(monster);
                    continue;
                }
                // Figure out if they fight.
                else if (monsters_fight(i, targmon))
                {
                    if (mons_is_batty(monster))
                    {
                        monster->behaviour = BEH_WANDER;
                        monster->target_x = 10 + random2(GXM - 10);
                        monster->target_y = 10 + random2(GYM - 10);
                        // monster->speed_increment -= monster->speed;
                    }

                    mmov_x = 0;
                    mmov_y = 0;
                    brkk = true;
                    DEBUG_ENERGY_USE("monsters_fight()");
                }
            }

            if (brkk)
                continue;

            if (monster->x + mmov_x == you.x_pos
                && monster->y + mmov_y == you.y_pos)
            {
                bool isFriendly = mons_friendly(monster);
                bool attacked   = false;

                if (!isFriendly)
                {
                    monster_attack(i);
                    attacked = true;

                    if (mons_is_batty(monster))
                    {
                        monster->behaviour = BEH_WANDER;
                        monster->target_x = 10 + random2(GXM - 10);
                        monster->target_y = 10 + random2(GYM - 10);
                    }
                    DEBUG_ENERGY_USE("monster_attack()");
                }

                if ((monster->type == MONS_GIANT_SPORE
                        || monster->type == MONS_BALL_LIGHTNING)
                    && monster->hit_points < 1)
                {
                    // Detach monster from the grid first, so it
                    // doesn't get hit by its own explosion. (GDL)
                    mgrd[monster->x][monster->y] = NON_MONSTER;

                    spore_goes_pop(monster);
                    monster_cleanup(monster);
                    continue;
                }

                if (attacked)
                {
                    mmov_x = 0;
                    mmov_y = 0;
                    continue;   //break;
                }
            }

            if (invalid_monster(monster) || mons_is_stationary(monster))
            {
                if (monster->speed_increment == old_energy)
                    monster->speed_increment -= non_move_energy;
                continue;
            }

            if (mons_cannot_move(monster) || !_monster_move(monster))
                monster->speed_increment -= non_move_energy;
        }
        update_beholders(monster);

        // Reevaluate behaviour, since the monster's
        // surroundings have changed (it may have moved,
        // or died for that matter).  Don't bother for
        // dead monsters.  :)
        if (monster->type != -1)
            _handle_behaviour(monster);

    }                   // end while

    if (monster->type != -1 && monster->hit_points < 1)
    {
        if (monster->type == MONS_GIANT_SPORE
            || monster->type == MONS_BALL_LIGHTNING)
        {
            // Detach monster from the grid first, so it
            // doesn't get hit by its own explosion. (GDL)
            mgrd[monster->x][monster->y] = NON_MONSTER;

            spore_goes_pop(monster);
            monster_cleanup(monster);
            return;
        }
        else
            monster_die(monster, KILL_MISC, 0);
    }
}

//---------------------------------------------------------------
//
// handle_monsters
//
// This is the routine that controls monster AI.
//
//---------------------------------------------------------------
void handle_monsters(void)
{
    // Keep track of monsters that have already moved and don't allow
    // them to move again.
    memset(immobile_monster, 0, sizeof immobile_monster);

    for (int i = 0; i < MAX_MONSTERS; i++)
    {
        monsters *monster = &menv[i];

        if (monster->type == -1 || immobile_monster[i])
            continue;

        const int mx = monster->x, my = monster->y;

        _handle_monster_move(i, monster);

        if (!invalid_monster(monster)
            && (monster->x != mx || monster->y != my))
        {
            immobile_monster[i] = true;
        }

        // If the player got banished, discard pending monster actions.
        if (you.banished)
        {
            // Clear list of beholding monsters.
            if (you.duration[DUR_BEHELD])
            {
                you.beheld_by.clear();
                you.duration[DUR_BEHELD] = 0;
            }
            break;
        }
    }                           // end of for loop

    // Clear any summoning flags so that lower indiced
    // monsters get their actions in the next round.
    for (int i = 0; i < MAX_MONSTERS; i++)
        menv[i].flags &= ~MF_JUST_SUMMONED;
}

static bool _is_item_jelly_edible(const item_def &item)
{
    // Don't eat artefacts (note that unrandarts are randarts).
    if (is_fixed_artefact(item) || is_random_artefact(item))
        return (false);

    // Shouldn't eat stone things
    //   - but what about wands and rings?
    if (item.base_type == OBJ_MISSILES
        && (item.sub_type == MI_STONE || item.sub_type == MI_LARGE_ROCK))
    {
        return (false);
    }

    // Don't eat special game items.
    if (item.base_type == OBJ_ORBS
        || (item.base_type == OBJ_MISCELLANY
            && (item.sub_type == MISC_RUNE_OF_ZOT
                || item.sub_type == MISC_HORN_OF_GERYON)))
    {
        return (false);
    }

    return (true);
}

//---------------------------------------------------------------
//
// handle_pickup
//
// Returns false if monster doesn't spend any time picking something up.
//
//---------------------------------------------------------------
static bool _handle_pickup(monsters *monster)
{
    // single calculation permissible {dlb}
    bool monsterNearby = mons_near(monster);
    int  item = NON_ITEM;

    if (mons_is_sleeping(monster)
        || monster->has_ench(ENCH_SUBMERGED))
    {
        return (false);
    }

    if (mons_itemuse(monster->type) == MONUSE_EATS_ITEMS)
    {
        // Friendly jellies won't eat.
        if (monster->attitude != ATT_HOSTILE)
            return (false);

        int  midx       = monster_index(monster);
        int  hps_gained = 0;
        int  max_eat    = roll_dice( 1, 10 );
        int  eaten      = 0;
        bool eaten_net  = false;

        for (item = igrd[monster->x][monster->y];
             item != NON_ITEM && eaten < max_eat && hps_gained < 50;
             item = mitm[item].link)
        {
            int quant = mitm[item].quantity;

            if (!_is_item_jelly_edible(mitm[item]))
                continue;

#if DEBUG_DIAGNOSTICS || DEBUG_EATERS
            mprf(MSGCH_DIAGNOSTICS,
                 "%s eating %s", monster->name(DESC_PLAIN, true).c_str(),
                 mitm[item].name(DESC_PLAIN).c_str());
#endif

            if (mitm[igrd[monster->x][monster->y]].base_type != OBJ_GOLD)
            {
                if (quant > max_eat - eaten)
                    quant = max_eat - eaten;

                hps_gained += (quant * item_mass( mitm[item] )) / 20 + quant;
                eaten += quant;

                if (mons_is_caught(monster)
                    && mitm[item].base_type == OBJ_MISSILES
                    && mitm[item].sub_type == MI_THROWING_NET
                    && item_is_stationary(mitm[item]))
                {
                    monster->del_ench(ENCH_HELD, true);
                    eaten_net = true;
                }
            }
            else
            {
                // shouldn't be much trouble to digest a huge pile of gold!
                if (quant > 500)
                    quant = 500 + roll_dice( 2, (quant - 500) / 2 );

                hps_gained += quant / 10 + 1;
                eaten++;
            }

            if (quant >= mitm[item].quantity)
                item_was_destroyed(mitm[item], midx);

            dec_mitm_item_quantity( item, quant );
        }

        if (eaten)
        {
            if (hps_gained < 1)
                hps_gained = 1;
            else if (hps_gained > 50)
                hps_gained = 50;

            // This is done manually instead of using heal_monster(),
            // because that function doesn't work quite this way.  -- bwr
            monster->hit_points += hps_gained;

            if (monster->max_hit_points < monster->hit_points)
                monster->max_hit_points = monster->hit_points;

            if (!silenced(you.x_pos, you.y_pos)
                && !silenced(monster->x, monster->y))
            {
                mprf(MSGCH_SOUND, "You hear a%s slurping noise.",
                     monsterNearby ? "" : " distant");
            }
            if (eaten_net)
                simple_monster_message(monster, " devours the net!");

            if (mons_class_flag( monster->type, M_SPLITS ))
            {
                const int reqd = (monster->hit_dice <= 6)
                                            ? 50 : monster->hit_dice * 8;

                if (monster->hit_points >= reqd)
                    _jelly_divide(monster);
            }
        }

        return (false);
    }                           // end "if jellies"

    // Note: Monsters only look at stuff near the top of stacks.
    // XXX: Need to put in something so that monster picks up multiple items
    // (eg ammunition) identical to those it's carrying.
    // Monsters may now pick up several items in the same turn, though with
    // reducing chances. (jpeg)
    bool success = false;
    for (item = igrd[monster->x][monster->y]; item != NON_ITEM; )
    {
        item_def &topickup = mitm[item];
        item = topickup.link;
        if (monster->pickup_item(topickup, monsterNearby))
            success = true;
        if (coinflip())
            break;
    }
    return (success);
}                               // end handle_pickup()

static void _jelly_grows(monsters *monster)
{
    if (!silenced(you.x_pos, you.y_pos)
        && !silenced(monster->x, monster->y))
    {
        mprf(MSGCH_SOUND, "You hear a%s slurping noise.",
             mons_near(monster) ? "" : " distant");
    }

    monster->hit_points += 5;

    // note here, that this makes jellies "grow" {dlb}:
    if (monster->hit_points > monster->max_hit_points)
        monster->max_hit_points = monster->hit_points;

    if (mons_class_flag( monster->type, M_SPLITS ))
    {
        // and here is where the jelly might divide {dlb}
        const int reqd = (monster->hit_dice < 6) ? 50
                                                 : monster->hit_dice * 8;

        if (monster->hit_points >= reqd)
            _jelly_divide(monster);
    }
}

static bool _mons_can_displace(const monsters *mpusher, const monsters *mpushee)
{
    if (invalid_monster(mpusher) || invalid_monster(mpushee))
        return (false);

    const int ipushee = monster_index(mpushee);
    if (invalid_monster_index(ipushee))
        return (false);

    if (immobile_monster[ipushee])
        return (false);

    // Confused monsters can't be pushed past, sleeping monsters
    // can't push. Note that sleeping monsters can't be pushed
    // past, either, but they may be woken up by a crowd trying to
    // elbow past them, and the wake-up check happens downstream.
    if (mons_is_confused(mpusher)      || mons_is_confused(mpushee)
        || mons_cannot_move(mpusher)   || mons_cannot_move(mpushee)
        || mons_is_stationary(mpusher) || mons_is_stationary(mpushee)
        || mons_is_sleeping(mpusher))
    {
        return (false);
    }

    // Batty monsters are unpushable.
    if (mons_is_batty(mpusher) || mons_is_batty(mpushee))
        return (false);

    if (!monster_shover(mpusher))
        return (false);

    if (!monster_senior(mpusher, mpushee))
        return (false);

    return (true);
}

static bool _monster_swaps_places( monsters *mon, int mx, int my )
{
    if (!mx && !my)
        return (false);

    int targmon = mgrd[mon->x + mx][mon->y + my];
    if (targmon == MHITNOT || targmon == MHITYOU)
        return (false);

    monsters *m2 = &menv[targmon];
    if (!_mons_can_displace(mon, m2))
        return (false);

    if (mons_is_sleeping(m2))
    {
        if (one_chance_in(2))
        {
#ifdef DEBUG_DIAGNOSTICS
            mprf(MSGCH_DIAGNOSTICS,
                 "Alerting monster %s at (%d,%d)",
                 m2->name(DESC_PLAIN).c_str(), m2->x, m2->y);
#endif
            behaviour_event( m2, ME_ALERT, MHITNOT );
        }
        return (false);
    }

    // Check that both monsters will be happy at their proposed new locations.
    const int cx = mon->x, cy = mon->y,
              nx = mon->x + mx, ny = mon->y + my;

    if (!_habitat_okay(mon, grd[nx][ny])
        || !_habitat_okay(m2, grd[cx][cy]))
    {
        return (false);
    }

    // Okay, do the swap!
    _swim_or_move_energy(mon);

    mon->x = nx;
    mon->y = ny;
    mgrd[nx][ny] = monster_index(mon);

    m2->x  = cx;
    m2->y  = cy;
    const int m2i = monster_index(m2);
    ASSERT(m2i >= 0 && m2i < MAX_MONSTERS);
    mgrd[cx][cy] = m2i;
    immobile_monster[m2i] = true;

    mon->check_redraw(coord_def(cx, cy));
    mon->apply_location_effects();
    m2->apply_location_effects();

    return (false);
}

static bool _do_move_monster(monsters *monster, int xi, int yi)
{
    const int fx = monster->x + xi,
              fy = monster->y + yi;

    if (!in_bounds(fx, fy))
        return (false);

    if (fx == you.x_pos && fy == you.y_pos)
    {
        monster_attack( monster_index(monster) );
        return (true);
    }

    if (!xi && !yi)
    {
        const int mx = monster_index(monster);
        monsters_fight( mx, mx );
        return (true);
    }

    if (mgrd[fx][fy] != NON_MONSTER)
    {
        monsters_fight( monster_index(monster), mgrd[fx][fy] );
        return (true);
    }

    if (!xi && !yi)
        return (false);

    // This appears to be the real one, ie where the movement occurs:
    _swim_or_move_energy(monster);

    mgrd[monster->x][monster->y] = NON_MONSTER;

    monster->x = fx;
    monster->y = fy;

    mgrd[monster->x][monster->y] = monster_index(monster);

    monster->check_redraw(monster->pos() - coord_def(xi, yi));
    monster->apply_location_effects();

    return (true);
}

void mons_check_pool(monsters *mons, killer_type killer, int killnum)
{
    // Levitating/flying monsters don't make contact with the terrain.
    if (mons->airborne())
        return;

    dungeon_feature_type grid = grd(mons->pos());
    if ((grid == DNGN_LAVA || grid == DNGN_DEEP_WATER)
        && !monster_habitable_grid(mons, grid))
    {
        const bool message = mons_near(mons);

        // Don't worry about invisibility - you should be able to
        // see if something has fallen into the lava.
        if (message)
        {
            mprf("%s falls into the %s!",
                 mons->name(DESC_CAP_THE).c_str(),
                 (grid == DNGN_LAVA ? "lava" : "water"));
        }

        if (grid == DNGN_LAVA && mons_res_fire(mons) >= 2)
            grid = DNGN_DEEP_WATER;

        // Even fire resistant monsters perish in lava, but undead can survive
        // deep water.
        if (grid == DNGN_LAVA || mons->can_drown())
        {
            if (message)
            {
                if (grid == DNGN_LAVA)
                {
                    simple_monster_message( mons, " is incinerated.",
                                            MSGCH_MONSTER_DAMAGE, MDAM_DEAD);
                }
                else if (mons_genus(mons->type) == MONS_MUMMY)
                {
                    simple_monster_message( mons, " falls apart.",
                                            MSGCH_MONSTER_DAMAGE, MDAM_DEAD);
                }
                else if (mons_is_zombified(mons))
                {
                    simple_monster_message( mons, " sinks like a rock.",
                                            MSGCH_MONSTER_DAMAGE, MDAM_DEAD);
                }
                else
                {
                    simple_monster_message( mons, " drowns.",
                                            MSGCH_MONSTER_DAMAGE, MDAM_DEAD);
                }
            }

            if (killer == KILL_NONE)
            {
                // Self-kill.
                killer  = KILL_MON;
                killnum = monster_index(mons);
            }
            monster_die(mons, killer, killnum, true);
        }
    }
}

// Randomize potential damage.
static int _estimated_trap_damage(trap_type trap)
{
    switch (trap)
    {
        case TRAP_BLADE:
           return (10 + random2(30));
        case TRAP_DART:
           return (random2(4));
        case TRAP_ARROW:
           return (random2(7));
        case TRAP_SPEAR:
           return (random2(10));
        case TRAP_BOLT:
           return (random2(13));
        case TRAP_AXE:
           return (random2(15));
        default:
           return (0);
    }
}

// Check whether a given trap (described by trap position) can be
// regarded as safe.  Takes into account monster intelligence and
// allegiance.  (just_check is used for intelligent monsters trying to
// avoid traps.)
static bool _is_trap_safe(const monsters *monster, const int trap_x,
                          const int trap_y, bool just_check)
{
    const int intel = mons_intel(monster->type);

    // Dumb monsters don't care at all.
    if (intel == I_PLANT)
        return (true);

    const trap_struct &trap = env.trap[trap_at_xy(trap_x,trap_y)];

    if (trap.type == TRAP_SHAFT && monster->will_trigger_shaft())
    {
        if ((mons_is_fleeing(monster) || mons_is_pacified(monster))
            && intel >= I_NORMAL)
        {
            return (true);
        }
        return (false);
    }

    // Monsters are not afraid of non-mechanical traps.  XXX: If we add
    // any non-mechanical traps that can damage monsters, we must add
    // checks for them here.
    const bool mechanical = trap_category(trap.type) == DNGN_TRAP_MECHANICAL;

    const bool player_knows_trap = (grd[trap_x][trap_y] != DNGN_UNDISCOVERED_TRAP);

    // Smarter trap handling for intelligent monsters
    // * monsters native to a branch can be assumed to know the trap
    //   locations and thus be able to avoid them
    // * friendlies and good neutrals can be assumed to have been warned
    //   by the player about all traps s/he knows about
    // * very intelligent monsters can be assumed to have a high T&D
    //   skill (or have memorised part of the dungeon layout ;) )
    if (intel >= I_NORMAL && mechanical
        && (mons_is_native_in_branch(monster)
            || mons_wont_attack(monster)
               && player_knows_trap
            || intel >= I_HIGH && one_chance_in(3)))
    {
        if (just_check)
            return (false); // Square is blocked.
        else
        {
            // Test for corridor-like environment.
            const int x = trap_x - monster->x;
            const int y = trap_y - monster->y;

            // The question is whether the monster (m) can easily reach its
            // presumable destination (x) without stepping on the trap. Traps
            // in corridors do not allow this. See e.g
            //  #x#        ##
            //  #^#   or  m^x
            //   m         ##
            //
            // The same problem occurs if paths are blocked by monsters,
            // hostile terrain or other traps rather than walls.
            // What we do is check whether the squares with the relative
            // positions (-1,0)/(+1,0) or (0,-1)/(0,+1) form a "corridor"
            // (relative to the _current_ monster position rather than the
            // trap one).
            // If they don't, the trap square is marked as "unsafe" (because
            // there's good alternative move for the monster to take),
            // otherwise the decision will be made according to later tests
            // (monster hp, trap type, ...)
            // If a monster still gets stuck in a corridor it will usually be
            // because it has less than half its maximum hp.

            if ((_mon_can_move_to_pos(monster, x-1, y, true)
                   || _mon_can_move_to_pos(monster, x+1,y, true))
                && (_mon_can_move_to_pos(monster, x,y-1, true)
                    || _mon_can_move_to_pos(monster, x,y+1, true)))
            {
                return (false);
            }
        }
    }

    // Friendlies will try not to be parted from you.
    if (intelligent_ally(monster) && trap.type == TRAP_TELEPORT
        && player_knows_trap && mons_near(monster))
    {
        return (false);
    }

    // Healthy monsters don't mind a little pain.
    if (mechanical && monster->hit_points >= monster->max_hit_points / 2
        && (intel == I_ANIMAL
            || monster->hit_points > _estimated_trap_damage(trap.type)))
    {
        return (true);
    }

    // Friendly and good neutral monsters don't enjoy Zot trap perks;
    // handle accordingly.
    if (mons_wont_attack(monster))
        return (mechanical ? mons_flies(monster) : trap.type != TRAP_ZOT);
    else
        return (!mechanical || mons_flies(monster));
}

static void _mons_open_door(monsters* monster, const coord_def &pos)
{
    dungeon_feature_type grid = grd(pos);
    const char *adj = "", *noun = "door";

    bool was_secret = false;
    bool was_seen   = false;

    std::set<coord_def> all_door;
    find_connected_range(pos, DNGN_CLOSED_DOOR, DNGN_SECRET_DOOR,
                         all_door);
    get_door_description(all_door.size(), &adj, &noun);

    for (std::set<coord_def>::iterator i = all_door.begin();
         i != all_door.end(); ++i)
    {
        const coord_def& dc = *i;
        if (grd(dc) == DNGN_SECRET_DOOR && see_grid(dc))
        {
            grid = grid_secret_door_appearance(dc.x, dc.y);
            was_secret = true;
        }

        if (see_grid(dc))
            was_seen = true;
        else
            set_terrain_changed(dc);

        grd[dc.x][dc.y] = DNGN_OPEN_DOOR;
    }

    if (was_seen)
    {
        viewwindow(true, false);

        if (was_secret)
        {
            mprf("%s was actually a secret door!",
                 feature_description(grid, NUM_TRAPS, false,
                                     DESC_CAP_THE, false).c_str());
            learned_something_new(TUT_SEEN_SECRET_DOOR, pos.x, pos.y);
        }

        std::string open_str = "opens the ";
        open_str += adj;
        open_str += noun;
        open_str += ".";

        monster->seen_context = open_str;

        if (!you.can_see(monster))
        {
            mprf("Something unseen %s", open_str.c_str());
            interrupt_activity(AI_FORCE_INTERRUPT);
        }
        else if (!you_are_delayed())
        {
            mprf("%s %s", monster->name(DESC_CAP_A).c_str(),
                 open_str.c_str());
        }
    }

    monster->lose_energy(EUT_MOVE);
}

// Check whether a monster can move to given square (described by its relative
// coordinates to the current monster position). just_check is true only for
// calls from is_trap_safe when checking the surrounding squares of a trap.
static bool _mon_can_move_to_pos(const monsters *monster, const int count_x,
                                 const int count_y, bool just_check)
{
    const int targ_x = monster->x + count_x;
    const int targ_y = monster->y + count_y;

    // Bounds check - don't consider moving out of grid!
    if (!inside_level_bounds(targ_x, targ_y))
        return (false);

    // Non-friendly and non-good neutral monsters won't enter sanctuaries.
    if (!mons_wont_attack(monster)
        && is_sanctuary(targ_x, targ_y)
        && !is_sanctuary(monster->x, monster->y))
    {
        return (false);
    }
    // Inside a sanctuary don't attack anything!
    if (is_sanctuary(monster->x, monster->y)
        && (targ_x == you.x_pos && targ_y == you.y_pos
            || mgrd[targ_x][targ_y] != NON_MONSTER))
    {
        return (false);
    }

    const dungeon_feature_type target_grid = grd[targ_x][targ_y];
    const habitat_type habitat = mons_habitat(monster);

    // Effectively slows down monster movement across water.
    // Fire elementals can't cross at all.
    bool no_water = false;
    if (monster->type == MONS_FIRE_ELEMENTAL || one_chance_in(5))
        no_water = true;

    const int targ_cloud_num  = env.cgrid[ targ_x ][ targ_y ];
    const int targ_cloud_type =
        targ_cloud_num == EMPTY_CLOUD ? CLOUD_NONE
                                      : env.cloud[targ_cloud_num].type;

    const int curr_cloud_num = env.cgrid[ monster->x ][ monster->y ];
    const int curr_cloud_type =
        curr_cloud_num == EMPTY_CLOUD ? CLOUD_NONE
                                     : env.cloud[curr_cloud_num].type;

    if (monster->type == MONS_BORING_BEETLE
        && (target_grid == DNGN_ROCK_WALL
            || target_grid == DNGN_CLEAR_ROCK_WALL))
    {
        // Don't burrow out of bounds.
        // XXX: Are these bounds still valid? (jpeg)
        //      We should probably use in_bounds() instead.
        if (targ_x <= 7 || targ_x >= (GXM - 8)
            || targ_y <= 7 || targ_y >= (GYM - 8))
        {
            return (false);
        }

        // Don't burrow at an angle (legacy behaviour).
        if (count_x != 0 && count_y != 0)
            return (false);
    }
    else if (!monster->can_pass_through_feat(target_grid)
             || (no_water && target_grid >= DNGN_DEEP_WATER
                 && target_grid <= DNGN_WATER_STUCK))
    {
        return (false);
    }
    else if (!_habitat_okay( monster, target_grid ))
    {
        return (false);
    }

    if (monster->type == MONS_WANDERING_MUSHROOM && see_grid(targ_x, targ_y))
        return (false);

    // Water elementals avoid fire and heat.
    if (monster->type == MONS_WATER_ELEMENTAL
        && (target_grid == DNGN_LAVA
            || targ_cloud_type == CLOUD_FIRE
            || targ_cloud_type == CLOUD_STEAM))
    {
        return (false);
    }

    // Fire elementals avoid water and cold
    if (monster->type == MONS_FIRE_ELEMENTAL
        && (grid_is_watery(target_grid)
            || targ_cloud_type == CLOUD_COLD))
    {
        return (false);
    }

    // Submerged water creatures avoid the shallows where
    // they would be forced to surface. -- bwr
    // [dshaligram] Monsters now prefer to head for deep water only if
    // they're low on hitpoints. No point in hiding if they want a
    // fight.
    if (habitat == HT_WATER
        && (targ_x != you.x_pos || targ_y != you.y_pos)
        && target_grid != DNGN_DEEP_WATER
        && grd[monster->x][monster->y] == DNGN_DEEP_WATER
        && monster->hit_points < (monster->max_hit_points * 3) / 4)
    {
        return (false);
    }

    // Smacking the player is always a good move if we're
    // hostile (even if we're heading somewhere else).
    // Also friendlies want to keep close to the player
    // so it's okay as well.

    // Smacking another monster is good, if the monsters
    // are aligned differently.
    if (mgrd[targ_x][targ_y] != NON_MONSTER)
    {
        if (just_check)
        {
            if (targ_x == monster->x && targ_y == monster->y)
                return (true);

            return (false); // blocks square
        }

        const int thismonster = monster_index(monster),
                  targmonster = mgrd[targ_x][targ_y];

        if (mons_aligned(thismonster, targmonster)
            && targmonster != MHITNOT
            && targmonster != MHITYOU
            && !_mons_can_displace(monster, &menv[targmonster]))
        {
            return (false);
        }
    }

    // Friendlies shouldn't try to move onto the player's
    // location, if they are aiming for some other target.
    if (mons_wont_attack(monster)
        && monster->foe != MHITYOU
        && (monster->foe != MHITNOT || monster->is_patrolling())
        && targ_x == you.x_pos
        && targ_y == you.y_pos)
    {
        return (false);
    }

    // Wandering through a trap is OK if we're pretty healthy,
    // really stupid, or immune to the trap.
    const int which_trap = trap_at_xy(targ_x,targ_y);
    if (which_trap >= 0 && !_is_trap_safe(monster, targ_x, targ_y, just_check))
        return (false);

    if (targ_cloud_num != EMPTY_CLOUD)
    {
        if (curr_cloud_num != EMPTY_CLOUD
            && targ_cloud_type == curr_cloud_type)
        {
            return (true);
        }

        switch (targ_cloud_type)
        {
        case CLOUD_MIASMA:
            // Even the dumbest monsters will avoid miasma if they can.
            return (mons_res_miasma(monster) > 0);

        case CLOUD_FIRE:
            if (mons_res_fire(monster) > 1)
                return (true);

            if (monster->hit_points >= 15 + random2avg(46, 5))
                return (true);
            break;

        case CLOUD_STINK:
            if (mons_res_poison(monster) > 0)
                return (true);
            if (x_chance_in_y(monster->hit_dice - 1, 5))
                return (true);
            if (monster->hit_points >= random2avg(19, 2))
                return (true);
            break;

        case CLOUD_COLD:
            if (mons_res_cold(monster) > 1)
                return (true);

            if (monster->hit_points >= 15 + random2avg(46, 5))
                return (true);
            break;

        case CLOUD_POISON:
            if (mons_res_poison(monster) > 0)
                return (true);

            if (monster->hit_points >= random2avg(37, 4))
                return (true);
            break;

        case CLOUD_GREY_SMOKE:
            // This isn't harmful, but dumb critters might think so.
            if (mons_intel(monster->type) > I_ANIMAL || coinflip())
                return (true);

            if (mons_res_fire(monster) > 0)
                return (true);

            if (monster->hit_points >= random2avg(19, 2))
                return (true);
            break;

        default:
            return (true);   // harmless clouds
        }

        // If we get here, the cloud is potentially harmful.
        // Exceedingly dumb creatures will still wander in.
        if (mons_intel(monster->type) != I_PLANT)
            return (false);
    }

    // If we end up here the monster can safely move.
    return (true);
}

static bool _monster_move(monsters *monster)
{
    FixedArray < bool, 3, 3 > good_move;
    int count_x, count_y, count;

    const habitat_type habitat = mons_habitat(monster);
    bool deep_water_available = false;

    if (monster->type == MONS_TRAPDOOR_SPIDER)
    {
        if(monster->has_ench(ENCH_SUBMERGED))
           return (false);

        // Trapdoor spiders hide if they can't see their target.
        bool can_see;

        if (monster->foe == MHITNOT)
            can_see = false;
        else if (monster->foe == MHITYOU)
            can_see = monster->can_see(&you);
        else
            can_see = monster->can_see(&menv[monster->foe]);

        if (monster_can_submerge(monster, grd[monster->x][monster->y])
            && !can_see && !mons_is_confused(monster)
            && !monster->has_ench(ENCH_BERSERK))
        {
            monster->add_ench(ENCH_SUBMERGED);
            monster->behaviour = BEH_LURK;
            return (false);
        }
    }

    // Berserking monsters make a lot of racket.
    if (monster->has_ench(ENCH_BERSERK))
    {
        int noise_level = get_shout_noise_level(mons_shouts(monster->type));
        if (noise_level > 0)
        {
            if (mons_near(monster) && player_monster_visible(monster))
            {
                if (one_chance_in(10))
                {
                    mprf(MSGCH_TALK_VISUAL, "%s rages.",
                         monster->name(DESC_CAP_THE).c_str());
                }
                noisy(noise_level, monster->x, monster->y);
            }
            else if (one_chance_in(5))
                handle_monster_shouts(monster, true);
            else
            {
                // Just be noisy without messaging the player.
                noisy(noise_level, monster->x, monster->y);
            }
        }
    }

    if (monster->confused())
    {
        if (mmov_x || mmov_y || one_chance_in(15))
        {
            coord_def newpos = monster->pos() + coord_def(mmov_x, mmov_y);
            if (in_bounds(newpos)
                && (habitat == HT_LAND
                    || monster_habitable_grid(monster, grd(newpos))))
            {
                return _do_move_monster(monster, mmov_x, mmov_y);
            }
        }
        return (false);
    }

    // Let's not even bother with this if mmov_x and mmov_y are zero.
    if (mmov_x == 0 && mmov_y == 0)
        return (false);

    for (count_x = 0; count_x < 3; count_x++)
        for (count_y = 0; count_y < 3; count_y++)
        {
             const int targ_x = monster->x + count_x - 1;
             const int targ_y = monster->y + count_y - 1;

             // Bounds check - don't consider moving out of grid!
             if (targ_x < 0 || targ_x >= GXM || targ_y < 0 || targ_y >= GYM)
             {
                 good_move[count_x][count_y] = false;
                 continue;
             }
             dungeon_feature_type target_grid = grd[targ_x][targ_y];

             if (target_grid == DNGN_DEEP_WATER)
                 deep_water_available = true;

             const monsters* mons = dynamic_cast<const monsters*>(monster);
             good_move[count_x][count_y] =
                 _mon_can_move_to_pos(mons, count_x-1, count_y-1);
        }

    // Now we know where we _can_ move.

    const coord_def newpos = monster->pos() + coord_def(mmov_x, mmov_y);
    // Normal/smart monsters know about secret doors
    // (they _live_ in the dungeon!)
    if (grd(newpos) == DNGN_CLOSED_DOOR
        || grd(newpos) == DNGN_SECRET_DOOR
           && mons_intel(monster_index(monster)) >= I_NORMAL)
    {
        if (mons_is_zombified(monster))
        {
            // For zombies, monster type is kept in mon->base_monster.
            if (mons_itemuse(monster->base_monster) >= MONUSE_OPEN_DOORS)
            {
                _mons_open_door(monster, newpos);
                return (true);
            }
        }
        else if (mons_itemuse(monster->type) >= MONUSE_OPEN_DOORS)
        {
            _mons_open_door(monster, newpos);
            return (true);
        }
    } // endif - secret/closed doors

    // Jellies eat doors.  Yum!
    if ((grd[monster->x + mmov_x][monster->y + mmov_y] == DNGN_CLOSED_DOOR
            || grd[monster->x + mmov_x][monster->y + mmov_y] == DNGN_OPEN_DOOR)
        && mons_itemuse(monster->type) == MONUSE_EATS_ITEMS)
    {
        grd[monster->x + mmov_x][monster->y + mmov_y] = DNGN_FLOOR;

        _jelly_grows(monster);

        if (see_grid(monster->x + mmov_x, monster->y + mmov_y))
        {
            viewwindow(true, false);

            if (!you.can_see(monster))
            {
                mpr("The door mysteriously vanishes.");
                interrupt_activity( AI_FORCE_INTERRUPT );
            }
        }
    } // done door-eating jellies


    // Water creatures have a preference for water they can hide in -- bwr
    // [ds] Weakened the powerful attraction to deep water if the monster
    // is in good health.
    if (habitat == HT_WATER
        && deep_water_available
        && grd[monster->x][monster->y] != DNGN_DEEP_WATER
        && grd[monster->x + mmov_x][monster->y + mmov_y] != DNGN_DEEP_WATER
        && (monster->x + mmov_x != you.x_pos
            || monster->y + mmov_y != you.y_pos)
        && (one_chance_in(3)
            || monster->hit_points <= (monster->max_hit_points * 3) / 4))
    {
        count = 0;

        for (count_x = 0; count_x < 3; count_x++)
            for (count_y = 0; count_y < 3; count_y++)
            {
                if (good_move[count_x][count_y]
                    && grd[monster->x + count_x - 1][monster->y + count_y - 1]
                            == DNGN_DEEP_WATER)
                {
                    count++;

                    if (one_chance_in( count ))
                    {
                        mmov_x = count_x - 1;
                        mmov_y = count_y - 1;
                    }
                }
            }
    }

    // Now, if a monster can't move in its intended direction, try
    // either side.  If they're both good, move in whichever dir
    // gets it closer (farther for fleeing monsters) to its target.
    // If neither does, do nothing.
    if (good_move[mmov_x + 1][mmov_y + 1] == false)
    {
        int current_distance = distance(monster->x, monster->y,
                                        monster->target_x, monster->target_y);

        int dir = -1;
        int i, mod, newdir;

        for (i = 0; i < 8; i++)
        {
            if (compass_x[i] == mmov_x && compass_y[i] == mmov_y)
            {
                dir = i;
                break;
            }
        }

        if (dir < 0)
            goto forget_it;

        int dist[2];

        // First 1 away, then 2 (3 is silly).
        for (int j = 1; j <= 2; j++)
        {
            int sdir, inc;

            if (coinflip())
            {
                sdir = -j;
                inc = 2*j;
            }
            else
            {
                sdir = j;
                inc = -2*j;
            }

            // Try both directions.
            for (mod = sdir, i = 0; i < 2; mod += inc, i++)
            {
                newdir = (dir + 8 + mod) % 8;
                if (good_move[compass_x[newdir] + 1][compass_y[newdir] + 1])
                {
                    dist[i] = distance(monster->x + compass_x[newdir],
                                       monster->y + compass_y[newdir],
                                       monster->target_x,
                                       monster->target_y);
                }
                else
                {
                    dist[i] = (mons_is_fleeing(monster)) ? (-FAR_AWAY)
                                                         : FAR_AWAY;
                }
            }

            // Now choose.
            if (dist[0] == dist[1] && abs(dist[0]) == FAR_AWAY)
                continue;

            // Which one was better? -- depends on FLEEING or not.
            if (mons_is_fleeing(monster))
            {
                if (dist[0] >= dist[1] && dist[0] >= current_distance)
                {
                    mmov_x = compass_x[((dir+8)+sdir)%8];
                    mmov_y = compass_y[((dir+8)+sdir)%8];
                    break;
                }
                if (dist[1] >= dist[0] && dist[1] >= current_distance)
                {
                    mmov_x = compass_x[((dir+8)-sdir)%8];
                    mmov_y = compass_y[((dir+8)-sdir)%8];
                    break;
                }
            }
            else
            {
                if (dist[0] <= dist[1] && dist[0] <= current_distance)
                {
                    mmov_x = compass_x[((dir+8)+sdir)%8];
                    mmov_y = compass_y[((dir+8)+sdir)%8];
                    break;
                }
                if (dist[1] <= dist[0] && dist[1] <= current_distance)
                {
                    mmov_x = compass_x[((dir+8)-sdir)%8];
                    mmov_y = compass_y[((dir+8)-sdir)%8];
                    break;
                }
            }
        }
    } // end - try to find good alternate move

forget_it:

    // ------------------------------------------------------------------
    // If we haven't found a good move by this point, we're not going to.
    // ------------------------------------------------------------------

    // Take care of beetle burrowing.
    if (monster->type == MONS_BORING_BEETLE)
    {
        dungeon_feature_type feat =
            grd[monster->x + mmov_x][monster->y + mmov_y];
        if ((feat == DNGN_ROCK_WALL || feat == DNGN_ROCK_WALL)
            && good_move[mmov_x + 1][mmov_y + 1] == true)
        {
            grd[monster->x + mmov_x][monster->y + mmov_y] = DNGN_FLOOR;
            set_terrain_changed(monster->pos() + coord_def(mmov_x, mmov_y));

            if (!silenced(you.x_pos, you.y_pos))
                mpr("You hear a grinding noise.", MSGCH_SOUND);
        }
    }

    bool ret = false;
    if (good_move[mmov_x + 1][mmov_y + 1] && !(mmov_x == 0 && mmov_y == 0))
    {
        // Check for attacking player.
        if (monster->x + mmov_x == you.x_pos
            && monster->y + mmov_y == you.y_pos)
        {
            ret    = monster_attack( monster_index(monster) );
            mmov_x = 0;
            mmov_y = 0;
        }

        // If we're following the player through stairs, the only valid
        // movement is towards the player. -- bwr
        if (testbits( monster->flags, MF_TAKING_STAIRS ))
        {
            const delay_type delay = current_delay_action();
            if (delay != DELAY_ASCENDING_STAIRS
                && delay != DELAY_DESCENDING_STAIRS)
            {
                monster->flags &= ~MF_TAKING_STAIRS;

#ifdef DEBUG_DIAGNOSTICS
                mprf(MSGCH_DIAGNOSTICS,
                     "BUG: %s was marked as follower when not following!",
                     monster->name(DESC_PLAIN).c_str(), true);
#endif
            }
            else
            {
                ret    = true;
                mmov_x = 0;
                mmov_y = 0;

#if DEBUG_DIAGNOSTICS
                mprf(MSGCH_DIAGNOSTICS,
                     "%s is skipping movement in order to follow.",
                     monster->name(DESC_CAP_THE).c_str(), true );
#endif
            }
        }

        // Check for attacking another monster.
        int targmon = mgrd[monster->x + mmov_x][monster->y + mmov_y];
        if (targmon != NON_MONSTER)
        {
            if (mons_aligned(monster_index(monster), targmon))
                ret = _monster_swaps_places(monster, mmov_x, mmov_y);
            else
            {
                monsters_fight(monster_index(monster), targmon);
                ret = true;
            }

            // If the monster swapped places, the work's already done.
            mmov_x = 0;
            mmov_y = 0;
        }

        if (monster->type == MONS_EFREET
            || monster->type == MONS_FIRE_ELEMENTAL)
        {
            place_cloud( CLOUD_FIRE, monster->x, monster->y,
                         2 + random2(4), monster->kill_alignment() );
        }

        if (monster->type == MONS_ROTTING_DEVIL
            || monster->type == MONS_CURSE_TOE)
        {
            place_cloud( CLOUD_MIASMA, monster->x, monster->y,
                         2 + random2(3), monster->kill_alignment() );
        }
    }
    else
    {
        mmov_x = mmov_y = 0;

        // Fleeing monsters that can't move will panic and possibly
        // turn to face their attacker.
        _make_mons_stop_fleeing(monster);
    }

    if (mmov_x || mmov_y || (monster->confused() && one_chance_in(6)))
        return _do_move_monster(monster, mmov_x, mmov_y);

    return ret;
}                               // end monster_move()

static void _setup_plant_spit(monsters *monster, bolt &pbolt)
{
    pbolt.name        = "acid";
    pbolt.type        = dchar_glyph(DCHAR_FIRED_ZAP);
    pbolt.range       = 9;
    pbolt.rangeMax    = 9;
    pbolt.colour      = YELLOW;
    pbolt.flavour     = BEAM_ACID;
    pbolt.beam_source = monster_index(monster);
    pbolt.damage      = dice_def( 3, 7 );
    pbolt.hit         = 20 + (3 * monster->hit_dice);
    pbolt.thrower     = KILL_MON_MISSILE;
    pbolt.aux_source.clear();
}

static bool _plant_spit(monsters *monster, bolt &pbolt)
{
    bool did_spit = false;

    char spit_string[INFO_SIZE];

    _setup_plant_spit(monster, pbolt);

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

    if (mons_should_fire(pbolt))
    {
        _make_mons_stop_fleeing(monster);
        strcpy( spit_string, " spits" );
        if (pbolt.target_x == you.x_pos && pbolt.target_y == you.y_pos)
            strcat( spit_string, " at you" );

        strcat( spit_string, "." );
        simple_monster_message( monster, spit_string );

        fire_beam( pbolt );
        did_spit = true;
    }

    return (did_spit);
}

static void _mons_in_cloud(monsters *monster)
{
    int wc = env.cgrid[monster->x][monster->y];
    int hurted = 0;
    bolt beam;

    const int speed = ((monster->speed > 0) ? monster->speed : 10);
    bool wake = false;

    if (mons_is_mimic( monster->type ))
    {
        mimic_alert(monster);
        return;
    }

    const cloud_struct &cloud(env.cloud[wc]);
    switch (cloud.type)
    {
    case CLOUD_DEBUGGING:
        end(1, false, "Fatal error: monster steps on nonexistent cloud!");
        return;

    case CLOUD_FIRE:
        if (monster->type == MONS_FIRE_VORTEX
            || monster->type == MONS_EFREET
            || monster->type == MONS_FIRE_ELEMENTAL)
        {
            return;
        }

        simple_monster_message(monster, " is engulfed in flame!");

        hurted +=
            resist_adjust_damage( monster,
                                  BEAM_FIRE,
                                  monster->res_fire(),
                                  ((random2avg(16, 3) + 6) * 10) / speed );

        hurted -= random2(1 + monster->ac);
        break;                  // to damage routine at end {dlb}

    case CLOUD_STINK:
        simple_monster_message(monster, " is engulfed in noxious gasses!");

        if (mons_res_poison(monster) > 0)
            return;

        beam.flavour = BEAM_CONFUSION;
        beam.thrower = cloud.beam_thrower();

        if (cloud.whose == KC_FRIENDLY)
            beam.beam_source = ANON_FRIENDLY_MONSTER;

        if (mons_class_is_confusable(monster->type)
            && 1 + random2(27) >= monster->hit_dice)
        {
            mons_ench_f2(monster, beam);
        }

        hurted += (random2(3) * 10) / speed;
        break;                  // to damage routine at end {dlb}

    case CLOUD_COLD:
        simple_monster_message(monster, " is engulfed in freezing vapours!");

        hurted +=
            resist_adjust_damage( monster,
                                  BEAM_COLD,
                                  monster->res_cold(),
                                  ((6 + random2avg(16, 3)) * 10) / speed );

        hurted -= random2(1 + monster->ac);
        break;                  // to damage routine at end {dlb}

    case CLOUD_POISON:
        simple_monster_message(monster, " is engulfed in a cloud of poison!");

        if (mons_res_poison(monster) > 0)
            return;

        poison_monster(monster, cloud.whose);
        // If the monster got poisoned, wake it up.
        wake = true;

        hurted += (random2(8) * 10) / speed;

        if (mons_res_poison(monster) < 0)
            hurted += (random2(4) * 10) / speed;
        break;                  // to damage routine at end {dlb}

    case CLOUD_STEAM:
    {
        // couldn't be bothered coding for armour of res fire

        simple_monster_message(monster, " is engulfed in steam!");

        const int steam_base_damage = steam_cloud_damage(cloud);
        hurted +=
            resist_adjust_damage(
                monster,
                BEAM_STEAM,
                monster->res_steam(),
                (random2avg(steam_base_damage, 2) * 10) / speed);

        hurted -= random2(1 + monster->ac);
        break;                  // to damage routine at end {dlb}
    }

    case CLOUD_MIASMA:
        simple_monster_message(monster, " is engulfed in a dark miasma!");

        if (mons_res_miasma(monster) > 0)
            return;

        poison_monster(monster, cloud.whose);

        if (monster->max_hit_points > 4 && coinflip())
            monster->max_hit_points--;

        beam.flavour = BEAM_SLOW;

        if (one_chance_in(3))
            mons_ench_f2(monster, beam);

        hurted += (10 * random2avg(12, 3)) / speed;    // 3
        break;              // to damage routine at end {dlb}

    default:                // 'harmless' clouds -- colored smoke, etc {dlb}.
        return;
    }

    // A sleeping monster that sustains damage will wake up.
    if ((wake || hurted > 0) && mons_is_sleeping(monster))
    {
        // We have no good coords to give the monster as the source of the
        // disturbance other than the cloud itself.
        behaviour_event(monster, ME_DISTURB, MHITNOT, monster->x, monster->y);
    }

    hurted = std::max(0, hurted);

    if (hurted > 0)
    {
#ifdef DEBUG_DIAGNOSTICS
        mprf(MSGCH_DIAGNOSTICS, "%s takes %d damage from cloud.",
             monster->name(DESC_CAP_THE).c_str(), hurted);
#endif
        hurt_monster(monster, hurted);

        if (monster->hit_points < 1)
        {
            mon_enchant death_ench(ENCH_NONE, 0, cloud.whose);
            monster_die(monster, death_ench.killer(), death_ench.kill_agent());
        }
    }
}

bool monster_descriptor(int which_class, unsigned char which_descriptor)
{
    if (which_descriptor == MDSC_LEAVES_HIDE)
    {
        switch (which_class)
        {
        case MONS_DRAGON:
        case MONS_TROLL:
        case MONS_ICE_DRAGON:
        case MONS_STEAM_DRAGON:
        case MONS_MOTTLED_DRAGON:
        case MONS_STORM_DRAGON:
        case MONS_GOLDEN_DRAGON:
        case MONS_SWAMP_DRAGON:
        case MONS_YAK:
        case MONS_SHEEP:
            return (true);
        default:
            return (false);
        }
    }

    if (which_descriptor == MDSC_REGENERATES)
    {
        switch (which_class)
        {
        case MONS_CACODEMON:
        case MONS_DEEP_TROLL:
        case MONS_HELLWING:
        case MONS_IMP:
        case MONS_IRON_TROLL:
        case MONS_LEMURE:
        case MONS_ROCK_TROLL:
        case MONS_SLIME_CREATURE:
        case MONS_SNORG:
        case MONS_TROLL:
        case MONS_HYDRA:
        case MONS_KILLER_KLOWN:
            return (true);
        default:
            return (false);
        }
    }

    if (which_descriptor == MDSC_NOMSG_WOUNDS)
    {
        if (mons_class_is_zombified(which_class)
            && which_class != MONS_SPECTRAL_THING)
        {
            return (true);
        }

        switch (which_class)
        {
        case MONS_RAKSHASA:
        case MONS_RAKSHASA_FAKE:
            return (true);
        default:
            return (false);
        }
    }
    return (false);
}                               // end monster_descriptor()

bool message_current_target()
{
    if (crawl_state.is_replaying_keys())
    {
        if (you.prev_targ == MHITNOT || you.prev_targ == MHITYOU)
            return (false);

        const monsters *montarget = &menv[you.prev_targ];
        return (you.prev_targ != MHITNOT && you.prev_targ != MHITYOU
                && mons_near(montarget) && player_monster_visible(montarget));
    }

    if (you.prev_targ != MHITNOT && you.prev_targ != MHITYOU)
    {
        const monsters *montarget = &menv[you.prev_targ];

        if (mons_near(montarget) && player_monster_visible(montarget))
        {
            mprf( MSGCH_PROMPT, "Current target: %s "
                  "(use p or f to fire at it again.)",
                  montarget->name(DESC_PLAIN).c_str() );
            return (true);
        }

        mpr("You have no current target.");
    }

    return (false);
}

// aaah, the simple joys of pointer arithmetic! {dlb}:
unsigned int monster_index(const monsters *monster)
{
    return (monster - menv.buffer());
}

int hurt_monster(monsters * victim, int damage_dealt)
{
    damage_dealt = std::max(std::min(damage_dealt, victim->hit_points), 0);

    return (victim->hurt(NULL, damage_dealt));
}

bool heal_monster(monsters * patient, int health_boost,
                  bool permit_growth)
{
    if (mons_is_statue(patient->type))
        return (false);

    if (health_boost < 1)
        return (false);
    else if (!permit_growth && patient->hit_points == patient->max_hit_points)
        return (false);

    patient->hit_points += health_boost;

    bool success = true;

    if (patient->hit_points > patient->max_hit_points)
    {
        if (permit_growth)
        {
            const monsterentry* m = get_monster_data(patient->type);
            const int maxhp =
                m->hpdice[0] * (m->hpdice[1] + m->hpdice[2]) + m->hpdice[3];

            // Limit HP growth.
            if (random2(3 * maxhp) > 2 * patient->max_hit_points)
                patient->max_hit_points++;
            else
                success = false;
        }

        patient->hit_points = patient->max_hit_points;
    }

    return (success);
}                               // end heal_monster()

static spell_type _map_wand_to_mspell(int wand_type)
{
    switch (wand_type)
    {
    case WAND_FLAME:
        return SPELL_THROW_FLAME;
    case WAND_FROST:
        return SPELL_THROW_FROST;
    case WAND_SLOWING:
        return SPELL_SLOW;
    case WAND_HASTING:
        return SPELL_HASTE;
    case WAND_MAGIC_DARTS:
        return SPELL_MAGIC_DART;
    case WAND_HEALING:
        return SPELL_LESSER_HEALING;
    case WAND_PARALYSIS:
        return SPELL_PARALYSE;
    case WAND_FIRE:
        return SPELL_BOLT_OF_FIRE;
    case WAND_COLD:
        return SPELL_BOLT_OF_COLD;
    case WAND_CONFUSION:
        return SPELL_CONFUSE;
    case WAND_INVISIBILITY:
        return SPELL_INVISIBILITY;
    case WAND_TELEPORTATION:
        return SPELL_TELEPORT_OTHER;
    case WAND_LIGHTNING:
        return SPELL_LIGHTNING_BOLT;
    case WAND_DRAINING:
        return SPELL_BOLT_OF_DRAINING;
    case WAND_DISINTEGRATION:
        return SPELL_DISINTEGRATE;
    case WAND_POLYMORPH_OTHER:
        return SPELL_POLYMORPH_OTHER;
    default:
        return SPELL_NO_SPELL;
    }
}

void seen_monster(monsters *monster)
{
    if (monster->flags & MF_SEEN)
        return;

    // First time we've seen this particular monster.
    monster->flags |= MF_SEEN;

    if (!mons_is_mimic(monster->type)
        && MONST_INTERESTING(monster)
        && monster->type != MONS_PANDEMONIUM_DEMON
        && monster->type != MONS_PLAYER_GHOST)
    {
        take_note(
            Note(NOTE_SEEN_MONSTER, monster->type, 0,
                 monster->name(DESC_NOCAP_A).c_str()));
    }
}

//---------------------------------------------------------------
//
// shift_monster
//
// Moves a monster to approximately (x, y) and returns true if
// the monster was moved.
//
//---------------------------------------------------------------
bool shift_monster( monsters *mon, int x, int y )
{
    bool found_move = false;

    int i, j;
    int tx, ty;
    int nx = 0, ny = 0;

    int count = 0;

    if (x == 0 && y == 0)
    {
        x = mon->x;
        y = mon->y;
    }

    for (i = -1; i <= 1; i++)
        for (j = -1; j <= 1; j++)
        {
            tx = x + i;
            ty = y + j;

            if (!inside_level_bounds(tx, ty))
                continue;

            // Don't drop on anything but vanilla floor right now.
            if (grd[tx][ty] != DNGN_FLOOR)
                continue;

            if (mgrd[tx][ty] != NON_MONSTER)
                continue;

            if (tx == you.x_pos && ty == you.y_pos)
                continue;

            if (one_chance_in(++count))
            {
                nx = tx;
                ny = ty;
                found_move = true;
            }
        }

    if (found_move)
    {
        const int mon_index = mgrd[mon->x][mon->y];
        mgrd[mon->x][mon->y] = NON_MONSTER;
        mgrd[nx][ny] = mon_index;
        mon->x = nx;
        mon->y = ny;
    }

    return (found_move);
}
