/**
 * @file
 * @brief Monster spell casting.
**/

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

#include <math.h>

#include "act-iter.h"
#include "areas.h"
#include "beam.h"
#include "branch.h"
#include "cloud.h"
#include "colour.h"
#include "coordit.h"
#include "delay.h"
#include "database.h"
#include "effects.h"
#include "env.h"
#include "evoke.h"
#include "fight.h"
#include "fprop.h"
#include "ghost.h"
#include "items.h"
#include "libutil.h"
#include "losglobal.h"
#include "mapmark.h"
#include "misc.h"
#include "message.h"
#include "mon-behv.h"
#include "mon-clone.h"
#include "mon-death.h"
#include "mon-pathfind.h"
#include "mon-pick.h"
#include "mon-place.h"
#include "mon-project.h"
#include "terrain.h"
#include "mgen_data.h"
#include "mon-act.h"
#include "mon-gear.h"
#include "mon-speak.h"
#include "ouch.h"
#include "random.h"
#include "religion.h"
#include "shout.h"
#include "spl-util.h"
#include "spl-cast.h"
#include "spl-clouds.h"
#include "spl-damage.h"
#include "spl-monench.h"
#include "spl-summoning.h"
#include "state.h"
#include "stepdown.h"
#include "stringutil.h"
#include "target.h"
#include "teleport.h"
#include "traps.h"
#include "view.h"
#include "viewchar.h"
#include "xom.h"

#include <algorithm>

// kraken stuff
const int MAX_ACTIVE_KRAKEN_TENTACLES = 4;
const int MAX_ACTIVE_STARSPAWN_TENTACLES = 2;

static bool _valid_mon_spells[NUM_SPELLS];

static int  _mons_mesmerise(monster* mons, bool actual = true);
static int  _mons_cause_fear(monster* mons, bool actual = true);
static int  _mons_mass_confuse(monster* mons, bool actual = true);
static int _mons_available_tentacles(monster* head);
static coord_def _mons_fragment_target(monster *mons);

void init_mons_spells()
{
    monster fake_mon;
    fake_mon.type       = MONS_BLACK_DRACONIAN;
    fake_mon.hit_points = 1;

    bolt pbolt;

    for (int i = 0; i < NUM_SPELLS; i++)
    {
        spell_type spell = (spell_type) i;

        _valid_mon_spells[i] = false;

        if (!is_valid_spell(spell))
            continue;

        if (spell == SPELL_MELEE
            || setup_mons_cast(&fake_mon, pbolt, spell, true))
        {
            _valid_mon_spells[i] = true;
        }
    }
}

bool is_valid_mon_spell(spell_type spell)
{
    if (spell < 0 || spell >= NUM_SPELLS)
        return false;

    return _valid_mon_spells[spell];
}

static void _scale_draconian_breath(bolt& beam, int drac_type)
{
    int scaling = 100;
    switch (drac_type)
    {
    case MONS_RED_DRACONIAN:
        beam.name       = "searing blast";
        beam.aux_source = "blast of searing breath";
        scaling         = 65;
        break;

    case MONS_WHITE_DRACONIAN:
        beam.name       = "chilling blast";
        beam.aux_source = "blast of chilling breath";
        beam.short_name = "frost";
        scaling         = 65;
        beam.ac_rule    = AC_NONE;
        break;

    case MONS_PLAYER_GHOST: // draconians only
        beam.name       = "blast of negative energy";
        beam.aux_source = "blast of draining breath";
        beam.flavour    = BEAM_NEG;
        beam.colour     = DARKGREY;
        scaling         = 65;
        break;
    }
    beam.damage.size = scaling * beam.damage.size / 100;
}

static spell_type _draco_type_to_breath(int drac_type)
{
    switch (drac_type)
    {
    case MONS_BLACK_DRACONIAN:   return SPELL_LIGHTNING_BOLT;
    case MONS_MOTTLED_DRACONIAN: return SPELL_STICKY_FLAME_SPLASH;
    case MONS_YELLOW_DRACONIAN:  return SPELL_SPIT_ACID;
    case MONS_GREEN_DRACONIAN:   return SPELL_POISONOUS_CLOUD;
    case MONS_PURPLE_DRACONIAN:  return SPELL_QUICKSILVER_BOLT;
    case MONS_RED_DRACONIAN:     return SPELL_FIRE_BREATH;
    case MONS_WHITE_DRACONIAN:   return SPELL_COLD_BREATH;
    case MONS_GREY_DRACONIAN:    return SPELL_NO_SPELL;
    case MONS_PALE_DRACONIAN:    return SPELL_STEAM_BALL;

    // Handled later.
    case MONS_PLAYER_GHOST:      return SPELL_DRACONIAN_BREATH;

    default:
        die("Invalid monster using draconian breath spell");
        break;
    }

    return SPELL_DRACONIAN_BREATH;
}

static bool _flavour_benefits_monster(beam_type flavour, monster& monster)
{
    switch (flavour)
    {
    case BEAM_HASTE:
        return !monster.has_ench(ENCH_HASTE);

    case BEAM_MIGHT:
        return !monster.has_ench(ENCH_MIGHT);

    case BEAM_INVISIBILITY:
        return !monster.has_ench(ENCH_INVIS);

    case BEAM_HEALING:
        return monster.hit_points != monster.max_hit_points;

    case BEAM_AGILITY:
        return !monster.has_ench(ENCH_AGILE);

    default:
        return false;
    }
}

// Find an allied monster to cast a beneficial beam spell at.
static bool _set_allied_target(monster* caster, bolt& pbolt, bool ignore_genus)
{
    monster* selected_target = NULL;
    int min_distance = INT_MAX;

    const monster_type caster_genus = mons_genus(caster->type);

    for (monster_near_iterator targ(caster, LOS_NO_TRANS); targ; ++targ)
    {
        if (*targ == caster)
            continue;

        const int targ_distance = grid_distance(targ->pos(), caster->pos());

        bool got_target = false;

        if ((mons_genus(targ->type) == caster_genus
                 || mons_genus(targ->base_monster) == caster_genus
                 || (caster_genus == MONS_SALAMANDER        // Let mystics haste
                     && mons_genus(targ->type) == MONS_NAGA)// their brethren
                 || targ->is_holy() && caster->is_holy()
                 || mons_enslaved_soul(caster)
                 || ignore_genus)
            && mons_aligned(*targ, caster)
            && !targ->has_ench(ENCH_CHARM)
            && !mons_is_firewood(*targ)
            && _flavour_benefits_monster(pbolt.flavour, **targ))
        {
            got_target = true;
        }

        if (got_target && targ_distance < min_distance
            && targ_distance < pbolt.range)
        {
            // Make sure we won't hit an invalid target with this aim.
            pbolt.target = targ->pos();
            fire_tracer(caster, pbolt);
            if (!mons_should_fire(pbolt)
                || pbolt.path_taken.back() != pbolt.target)
            {
                continue;
            }

            min_distance = targ_distance;
            selected_target = *targ;
        }
    }

    if (selected_target)
    {
        pbolt.target = selected_target->pos();
        return true;
    }

    // Didn't find a target.
    return false;
}

bolt mons_spell_beam(monster* mons, spell_type spell_cast, int power,
                     bool check_validity)
{
    ASSERT(power > 0);

    bolt beam;

    // Initialise to some bogus values so we can catch problems.
    beam.name         = "****";
    beam.colour       = 1000;
    beam.hit          = -1;
    beam.damage       = dice_def(1, 0);
    beam.ench_power   = -1;
    beam.glyph        = 0;
    beam.flavour      = BEAM_NONE;
    beam.thrower      = KILL_MISC;
    beam.is_beam      = false;
    beam.is_explosion = false;

    switch (spell_cast)
    { // add touch or range-setting spells here
        case SPELL_SANDBLAST:
            break;
        case SPELL_FLAME_TONGUE:
            // HD:1 monsters would get range 2, HD:2 -- 3, other 4, let's
            // use the mighty Throw Flame for big ranges.
            // Here, we have HD:1 -- 1, HD:2+ -- 2.
            beam.range = (power >= 20) ? 2 : 1;
            break;
        default:
        beam.range = spell_range(spell_cast, power, false);
    }

    const int drac_type = (mons_genus(mons->type) == MONS_DRACONIAN)
                            ? draco_or_demonspawn_subspecies(mons) : mons->type;

    spell_type real_spell = spell_cast;

    if (spell_cast == SPELL_DRACONIAN_BREATH)
        real_spell = _draco_type_to_breath(drac_type);

    if (spell_cast == SPELL_MAJOR_DESTRUCTION)
    {
        switch (random2(7))
        {
        case 0: real_spell = SPELL_BOLT_OF_FIRE;         break;
        case 1: real_spell = SPELL_FIREBALL;             break;
        case 2: real_spell = SPELL_LIGHTNING_BOLT;       break;
        case 3: real_spell = SPELL_STICKY_FLAME;         break;
        case 4: real_spell = SPELL_IRON_SHOT;            break;
        case 5: real_spell = SPELL_BOLT_OF_DRAINING;     break;
        case 6: real_spell = SPELL_ORB_OF_ELECTRICITY;   break;
        }
    }
    else if (spell_cast == SPELL_RANDOM_BOLT) // if casting directly
    {
        switch (random2(6))
        {
        case 0: real_spell = SPELL_VENOM_BOLT;           break;
        case 1: real_spell = SPELL_BOLT_OF_DRAINING;     break;
        case 2: real_spell = SPELL_BOLT_OF_FIRE;         break;
        case 3: real_spell = SPELL_LIGHTNING_BOLT;       break;
        case 4: real_spell = SPELL_QUICKSILVER_BOLT;     break;
        case 5: real_spell = SPELL_CRYSTAL_BOLT;         break;
        }
    }
    beam.glyph = dchar_glyph(DCHAR_FIRED_ZAP); // default
    beam.thrower = KILL_MON_MISSILE;
    beam.origin_spell = real_spell;
    beam.beam_source = mons->mindex();
    beam.source_name = mons->name(DESC_A, true);

    // FIXME: this should use the zap_data[] struct from beam.cc!
    switch (real_spell)
    {
    case SPELL_MAGIC_DART:
        beam.colour   = LIGHTMAGENTA;
        beam.name     = "magic dart";
        beam.damage   = dice_def(3, 4 + (power / 100));
        beam.hit      = AUTOMATIC_HIT;
        beam.flavour  = BEAM_MMISSILE;
        break;

    case SPELL_THROW_FLAME:
        beam.colour   = RED;
        beam.name     = "puff of flame";
        beam.damage   = dice_def(3, 5 + (power / 40));
        beam.hit      = 25 + power / 40;
        beam.flavour  = BEAM_FIRE;
        break;

    case SPELL_THROW_FROST:
        beam.colour   = WHITE;
        beam.name     = "puff of frost";
        beam.damage   = dice_def(3, 5 + (power / 40));
        beam.hit      = 25 + power / 40;
        beam.flavour  = BEAM_COLD;
        break;

    case SPELL_SANDBLAST:
        beam.colour   = BROWN;
        beam.name     = "rocky blast";
        beam.damage   = dice_def(3, 5 + (power / 40));
        beam.hit      = 20 + power / 40;
        beam.flavour  = BEAM_FRAG;
        beam.range    = 2;      // spell_range() is wrong here
        break;

    case SPELL_DISPEL_UNDEAD:
        beam.flavour  = BEAM_DISPEL_UNDEAD;
        beam.damage   = dice_def(3, min(6 + power / 10, 40));
        beam.is_beam  = true;
        break;

    case SPELL_PARALYSE:
        beam.flavour  = BEAM_PARALYSIS;
        beam.is_beam  = true;
        break;

    case SPELL_PETRIFY:
        beam.flavour  = BEAM_PETRIFY;
        beam.is_beam  = true;
        break;

    case SPELL_SLOW:
        beam.flavour  = BEAM_SLOW;
        beam.is_beam  = true;
        break;

    case SPELL_HASTE_OTHER:
    case SPELL_HASTE:
        beam.flavour  = BEAM_HASTE;
        break;

    case SPELL_MIGHT_OTHER:
    case SPELL_MIGHT:
        beam.flavour  = BEAM_MIGHT;
        break;

    case SPELL_CORONA:
        beam.flavour  = BEAM_CORONA;
        beam.is_beam  = true;
        break;

    case SPELL_CONFUSE:
        beam.flavour  = BEAM_CONFUSION;
        beam.is_beam  = true;
        break;

    case SPELL_HIBERNATION:
        beam.flavour  = BEAM_HIBERNATION;
        beam.is_beam  = true;
        break;

    case SPELL_SLEEP:
        beam.flavour    = BEAM_SLEEP;
        beam.is_beam    = true;
        beam.ench_power = 6 * mons->spell_hd(real_spell);
        break;

    case SPELL_POLYMORPH:
        beam.flavour  = BEAM_POLYMORPH;
        beam.is_beam  = true;
        break;

    case SPELL_MALMUTATE:
        beam.flavour  = BEAM_MALMUTATE;
        beam.is_beam  = true;
        /*
          // Be careful with this one.
          // Having allies mutate you is infuriating.
          beam.foe_ratio = 1000;
        What's the point of this?  Enchantments always hit...
        */
        break;

    case SPELL_FLAME_TONGUE:
        beam.name     = "flame tongue";
        beam.damage   = dice_def(3, 3 + power / 12);
        beam.colour   = RED;
        beam.flavour  = BEAM_FIRE;
        beam.hit      = 7 + power / 6;
        beam.is_beam  = true;
        break;

    case SPELL_VENOM_BOLT:
        beam.name     = "bolt of poison";
        beam.damage   = dice_def(3, 6 + power / 13);
        beam.colour   = LIGHTGREEN;
        beam.flavour  = BEAM_POISON;
        beam.hit      = 19 + power / 20;
        beam.is_beam  = true;
        break;

    case SPELL_POISON_ARROW:
        beam.name     = "poison arrow";
        beam.damage   = dice_def(3, 7 + power / 12);
        beam.colour   = LIGHTGREEN;
        beam.glyph    = dchar_glyph(DCHAR_FIRED_MISSILE);
        beam.flavour  = BEAM_POISON_ARROW;
        beam.hit      = 20 + power / 25;
        break;

    case SPELL_BOLT_OF_MAGMA:
        beam.name     = "bolt of magma";
        beam.damage   = dice_def(3, 8 + power / 11);
        beam.colour   = RED;
        beam.flavour  = BEAM_LAVA;
        beam.hit      = 17 + power / 25;
        beam.is_beam  = true;
        break;

    case SPELL_BOLT_OF_FIRE:
        beam.name     = "bolt of fire";
        beam.damage   = dice_def(3, 8 + power / 11);
        beam.colour   = RED;
        beam.flavour  = BEAM_FIRE;
        beam.hit      = 17 + power / 25;
        beam.is_beam  = true;
        break;

    case SPELL_THROW_ICICLE:
        beam.name     = "shard of ice";
        beam.damage   = dice_def(3, 8 + power / 11);
        beam.colour   = WHITE;
        beam.flavour  = BEAM_ICE;
        beam.hit      = 17 + power / 25;
        break;

    case SPELL_BOLT_OF_COLD:
        beam.name     = "bolt of cold";
        beam.damage   = dice_def(3, 8 + power / 11);
        beam.colour   = WHITE;
        beam.flavour  = BEAM_COLD;
        beam.hit      = 17 + power / 25;
        beam.is_beam  = true;
        break;

    case SPELL_BOLT_OF_INACCURACY:
        beam.name     = "narrow beam of energy";
        beam.damage   = calc_dice(12, 40 + 3 * power / 2);
        beam.colour   = YELLOW;
        beam.flavour  = BEAM_ENERGY;
        beam.hit      = 1;
        beam.is_beam  = true;
        break;

    case SPELL_PRIMAL_WAVE:
        beam.name     = "great wave of water";
        // Water attack is weaker than the pure elemental damage
        // attacks, but also less resistible.
        beam.damage   = dice_def(3, 6 + power / 12);
        beam.colour   = LIGHTBLUE;
        beam.flavour  = BEAM_WATER;
        // Huge wave of water is hard to dodge.
        beam.hit      = 20 + power / 20;
        beam.is_beam  = false;
        beam.glyph    = dchar_glyph(DCHAR_WAVY);
        break;

    case SPELL_FREEZING_CLOUD:
        beam.name     = "freezing blast";
        beam.damage   = dice_def(2, 9 + power / 11);
        beam.colour   = WHITE;
        beam.flavour  = BEAM_COLD;
        beam.hit      = 17 + power / 25;
        beam.is_beam  = true;
        beam.is_big_cloud = true;
        break;

    case SPELL_SHOCK:
        beam.name     = "zap";
        beam.damage   = dice_def(1, 8 + (power / 20));
        beam.colour   = LIGHTCYAN;
        beam.flavour  = BEAM_ELECTRICITY;
        beam.hit      = 17 + power / 20;
        beam.is_beam  = true;
        break;

    case SPELL_BLINKBOLT:
        beam.name     = "living lightning";
        beam.damage   = dice_def(2, 10 + power / 17);
        beam.colour   = LIGHTCYAN;
        beam.flavour  = BEAM_ELECTRICITY;
        beam.hit      = 16 + power / 40;
        beam.is_beam  = true;
        break;

    case SPELL_LIGHTNING_BOLT:
        beam.name     = "bolt of lightning";
        beam.damage   = dice_def(3, 10 + power / 17);
        beam.colour   = LIGHTCYAN;
        beam.flavour  = BEAM_ELECTRICITY;
        beam.hit      = 16 + power / 40;
        beam.is_beam  = true;
        break;

    case SPELL_INVISIBILITY:
    case SPELL_INVISIBILITY_OTHER:
        beam.flavour  = BEAM_INVISIBILITY;
        break;

    case SPELL_FIREBALL:
        beam.colour   = RED;
        beam.name     = "fireball";
        beam.damage   = dice_def(3, 7 + power / 10);
        beam.hit      = 40;
        beam.flavour  = BEAM_FIRE;
        beam.foe_ratio = 80;
        beam.is_explosion = true;
        break;

    case SPELL_FIRE_STORM:
        setup_fire_storm(mons, power / 2, beam);
        beam.foe_ratio = random_range(40, 55);
        break;

    case SPELL_HELLFIRE_BURST:
        beam.aux_source   = "burst of hellfire";
        beam.name         = "burst of hellfire";
        beam.ex_size      = 1;
        beam.flavour      = BEAM_HELLFIRE;
        beam.is_explosion = true;
        beam.colour       = LIGHTRED;
        beam.aux_source.clear();
        beam.is_tracer    = false;
        beam.hit          = 20;
        beam.damage       = mons_foe_is_mons(mons) ? dice_def(5, 7)
                                                   : dice_def(3, 15);
        break;

    case SPELL_HEAL_OTHER:
    case SPELL_MINOR_HEALING:
        beam.damage   = dice_def(2, mons->spell_hd(real_spell) / 2);
        beam.flavour  = BEAM_HEALING;
        beam.hit      = 25 + (power / 5);
        break;

    case SPELL_TELEPORT_SELF:
        beam.flavour    = BEAM_TELEPORT;
        beam.ench_power = 2000;
        break;

    case SPELL_TELEPORT_OTHER:
        beam.flavour  = BEAM_TELEPORT;
        beam.is_beam  = true;
        break;

    case SPELL_LEHUDIBS_CRYSTAL_SPEAR:      // was splinters
        beam.name     = "crystal spear";
        beam.damage   = dice_def(3, 16 + power / 10);
        beam.colour   = WHITE;
        beam.glyph    = dchar_glyph(DCHAR_FIRED_MISSILE);
        beam.flavour  = BEAM_MMISSILE;
        beam.hit      = 22 + power / 20;
        break;

    case SPELL_DIG:
        beam.flavour  = BEAM_DIGGING;
        beam.is_beam  = true;
        break;

    case SPELL_BOLT_OF_DRAINING:      // negative energy
        beam.name     = "bolt of negative energy";
        beam.damage   = dice_def(3, 9 + power / 13);
        beam.colour   = DARKGREY;
        beam.flavour  = BEAM_NEG;
        beam.hit      = 16 + power / 35;
        beam.is_beam  = true;
        break;

    case SPELL_ISKENDERUNS_MYSTIC_BLAST: // mystic blast
        beam.colour     = LIGHTMAGENTA;
        beam.name       = "orb of energy";
        beam.short_name = "energy";
        beam.damage     = dice_def(3, 7 + (power / 14));
        beam.hit        = 20 + (power / 20);
        beam.flavour    = BEAM_MMISSILE;
        break;

    case SPELL_DAZZLING_SPRAY:
        beam.colour     = LIGHTMAGENTA;
        beam.name       = "spray of energy";
        beam.short_name = "energy";
        beam.damage     = dice_def(3, 5 + (power / 17));
        beam.hit        = 16 + (power / 22);
        beam.flavour    = BEAM_MMISSILE;
        break;

    case SPELL_STEAM_BALL:
        beam.colour   = LIGHTGREY;
        beam.name     = "ball of steam";
        beam.damage   = dice_def(3, 7 + (power / 15));
        beam.hit      = 20 + power / 20;
        beam.flavour  = BEAM_STEAM;
        break;

    case SPELL_PAIN:
        beam.flavour    = BEAM_PAIN;
        beam.damage     = dice_def(1, 7 + (power / 20));
        beam.ench_power = max(50, 8 * mons->spell_hd(real_spell));
        beam.is_beam    = true;
        break;

    case SPELL_AGONY:
        beam.flavour    = BEAM_PAIN;
        beam.ench_power = mons->spell_hd(real_spell) * 6;
        beam.is_beam    = true;
        break;

    case SPELL_STICKY_FLAME:
        beam.hit = AUTOMATIC_HIT;
    case SPELL_STICKY_FLAME_SPLASH:
    case SPELL_STICKY_FLAME_RANGE:
        if (real_spell != SPELL_STICKY_FLAME)
            beam.hit      = 18 + power / 15;
        beam.colour   = RED;
        beam.name     = "sticky flame";
        // FIXME: splash needs to be "splash of liquid fire"
        beam.damage   = dice_def(3, 3 + power / 50);
        beam.flavour  = BEAM_FIRE;
        break;

    case SPELL_NOXIOUS_CLOUD:
        beam.name     = "noxious blast";
        beam.damage   = dice_def(1, 0);
        beam.colour   = GREEN;
        beam.flavour  = BEAM_MEPHITIC;
        beam.hit      = 18 + power / 25;
        beam.is_beam  = true;
        beam.is_big_cloud = true;
        break;

    case SPELL_POISONOUS_CLOUD:
        beam.name     = "blast of poison";
        beam.damage   = dice_def(3, 3 + power / 25);
        beam.colour   = LIGHTGREEN;
        beam.flavour  = BEAM_POISON;
        beam.hit      = 18 + power / 25;
        beam.is_beam  = true;
        beam.is_big_cloud = true;
        break;

    case SPELL_ENERGY_BOLT:        // eye of devastation
        beam.colour     = YELLOW;
        beam.name       = "bolt of energy";
        beam.short_name = "energy";
        beam.damage     = dice_def(3, 20);
        beam.hit        = 15 + power / 30;
        beam.flavour    = BEAM_DEVASTATION; // DEVASTATION is BEAM_MMISSILE
        beam.is_beam    = true;             // except it also destroys walls
        break;

    case SPELL_STING:              // sting
        beam.colour   = GREEN;
        beam.name     = "sting";
        beam.damage   = dice_def(1, 6 + power / 25);
        beam.hit      = 60;
        beam.flavour  = BEAM_POISON;
        break;

    case SPELL_IRON_SHOT:
        beam.colour   = LIGHTCYAN;
        beam.name     = "iron shot";
        beam.damage   = dice_def(3, 8 + (power / 9));
        beam.hit      = 20 + (power / 25);
        beam.glyph    = dchar_glyph(DCHAR_FIRED_MISSILE);
        beam.flavour  = BEAM_MMISSILE;   // similarly unresisted thing
        break;

    case SPELL_STONE_ARROW:
        beam.colour   = LIGHTGREY;
        beam.name     = "stone arrow";
        beam.damage   = dice_def(3, 5 + (power / 10));
        beam.hit      = 14 + power / 35;
        beam.glyph    = dchar_glyph(DCHAR_FIRED_MISSILE);
        beam.flavour  = BEAM_MMISSILE;   // similarly unresisted thing
        break;

    case SPELL_SPIT_POISON:
        beam.colour   = GREEN;
        beam.name     = "splash of poison";
        beam.damage   = dice_def(1, 4 + power / 10);
        beam.hit      = 16 + power / 20;
        beam.flavour  = BEAM_POISON;
        break;

    case SPELL_SPIT_ACID:
        beam.colour   = YELLOW;
        beam.name     = "splash of acid";
        beam.damage   = dice_def(3, 7);

        // Zotdef change: make acid splash dmg dependent on power
        // Oklob saplings pwr=48, oklobs pwr=120, acid blobs pwr=216
        //  =>             3d3        3d6            3d9
        if (crawl_state.game_is_zotdef())
            beam.damage   = dice_def(3, 2 + (power / 30));

        // Natural ability, so don't use spell_hd here
        beam.hit      = 20 + (3 * mons->get_hit_dice());
        beam.flavour  = BEAM_ACID;
        break;

    case SPELL_DISINTEGRATE:
        beam.flavour    = BEAM_DISINTEGRATION;
        beam.ench_power = 50;
        beam.damage     = dice_def(1, 30 + (power / 10));
        beam.is_beam    = true;
        break;

    case SPELL_MEPHITIC_CLOUD:
        beam.name     = "foul vapour";
        beam.damage   = dice_def(1, 0);
        beam.colour   = GREEN;
        beam.flavour  = BEAM_MEPHITIC;
        beam.hit      = 14 + power / 30;
        beam.ench_power = power; // probably meaningless
        beam.is_explosion = true;
        beam.is_big_cloud = true;
        break;

    case SPELL_MIASMA_BREATH:      // death drake
        beam.name     = "foul vapour";
        beam.damage   = dice_def(3, 5 + power / 24);
        beam.colour   = DARKGREY;
        beam.flavour  = BEAM_MIASMA;
        beam.hit      = 17 + power / 20;
        beam.is_beam  = true;
        beam.is_big_cloud = true;
        break;

    case SPELL_QUICKSILVER_BOLT:   // Quicksilver dragon and purple draconian
        beam.colour     = random_colour();
        beam.name       = "bolt of dispelling energy";
        beam.short_name = "energy";
        beam.damage     = dice_def(3, 20);
        beam.hit        = 16 + power / 25;
        beam.flavour    = BEAM_MMISSILE;
        break;

    case SPELL_HELLFIRE:           // fiend's hellfire
        beam.name         = "blast of hellfire";
        beam.aux_source   = "blast of hellfire";
        beam.colour       = LIGHTRED;
        beam.damage       = dice_def(3, 20);
        beam.hit          = 24;
        beam.flavour      = BEAM_HELLFIRE;
        beam.is_beam      = true;
        beam.is_explosion = true;
        break;

    case SPELL_METAL_SPLINTERS:
        beam.name       = "spray of metal splinters";
        beam.short_name = "metal splinters";
        beam.damage     = dice_def(3, 20 + power / 20);
        beam.colour     = CYAN;
        beam.flavour    = BEAM_FRAG;
        beam.hit        = 19 + power / 30;
        beam.is_beam    = true;
        break;

    case SPELL_BANISHMENT:
        beam.flavour  = BEAM_BANISH;
        beam.is_beam  = true;
        break;

    case SPELL_BLINK_OTHER:
        beam.flavour    = BEAM_BLINK;
        beam.is_beam    = true;
        break;

    case SPELL_BLINK_OTHER_CLOSE:
        beam.flavour    = BEAM_BLINK_CLOSE;
        beam.is_beam    = true;
        break;

    case SPELL_FIRE_BREATH:
        beam.name       = "blast of flame";
        beam.aux_source = "blast of fiery breath";
        beam.short_name = "flames";
        beam.damage     = dice_def(3, (mons->get_hit_dice() * 2));
        beam.colour     = RED;
        beam.hit        = 30;
        beam.flavour    = BEAM_FIRE;
        beam.is_beam    = true;
        break;

    case SPELL_CHAOS_BREATH:
        beam.name       = "blast of chaos";
        beam.aux_source = "blast of chaotic breath";
        beam.damage     = dice_def(3, (mons->get_hit_dice() * 2));
        beam.colour     = ETC_RANDOM;
        beam.hit        = 30;
        beam.flavour    = BEAM_CHAOS;
        beam.is_beam    = true;
        break;

    case SPELL_COLD_BREATH:
        beam.name       = "blast of cold";
        beam.aux_source = "blast of icy breath";
        beam.short_name = "frost";
        beam.damage     = dice_def(3, (mons->get_hit_dice() * 2));
        beam.colour     = WHITE;
        beam.hit        = 30;
        beam.flavour    = BEAM_COLD;
        beam.is_beam    = true;
        break;

    case SPELL_HOLY_BREATH:
        beam.name     = "blast of cleansing flame";
        beam.damage   = dice_def(3, (mons->get_hit_dice() * 2));
        beam.colour   = ETC_HOLY;
        beam.flavour  = BEAM_HOLY;
        beam.hit      = 18 + power / 25;
        beam.is_beam  = true;
        beam.is_big_cloud = true;
        break;

    case SPELL_DRACONIAN_BREATH:
        beam.damage      = dice_def(3, (mons->get_hit_dice() * 2));
        beam.hit         = 30;
        beam.is_beam     = true;
        break;

    case SPELL_PORKALATOR:
        beam.name     = "porkalator";
        beam.glyph    = 0;
        beam.flavour  = BEAM_PORKALATOR;
        beam.thrower  = KILL_MON_MISSILE;
        beam.is_beam  = true;
        break;

    case SPELL_IOOD:                  // tracer only
    case SPELL_LEGENDARY_DESTRUCTION: // ditto
    case SPELL_LRD:                   // for noise generation purposes
    case SPELL_PORTAL_PROJECTILE:     // ditto
    case SPELL_GLACIATE:              // ditto
    case SPELL_CLOUD_CONE:            // ditto
        beam.flavour  = BEAM_DEVASTATION;
        beam.is_beam  = true;
        // Doesn't take distance into account, but this is just a tracer so
        // we'll ignore that.  We need some damage on the tracer so the monster
        // doesn't think the spell is useless against other monsters.
        beam.damage   = dice_def(42, 1);
        beam.range    = LOS_RADIUS;
        break;

    case SPELL_PETRIFYING_CLOUD:
        beam.name     = "blast of calcifying dust";
        beam.colour   = WHITE;
        beam.damage   = dice_def(2, 6);
        beam.hit      = AUTOMATIC_HIT;
        beam.flavour  = BEAM_PETRIFYING_CLOUD;
        beam.foe_ratio = 30;
        beam.is_beam  = true;
        break;

#if TAG_MAJOR_VERSION == 34
    case SPELL_HOLY_LIGHT:
        beam.name     = "beam of golden light";
        beam.damage   = dice_def(3, 8 + power / 11);
        beam.colour   = ETC_HOLY;
        beam.flavour  = BEAM_HOLY;
        beam.hit      = 17 + power / 25;
        beam.is_beam  = true;
        break;

    case SPELL_SILVER_BLAST:
        beam.colour   = LIGHTGRAY;
        beam.name     = "silver bolt";
        beam.damage   = dice_def(3, 7 + power / 10);
        beam.hit      = 40;
        beam.flavour  = BEAM_BOLT_OF_ZIN;
        beam.foe_ratio = 80;
        beam.is_explosion = true;
        break;
#endif

    case SPELL_ENSNARE:
        beam.name     = "stream of webbing";
        beam.colour   = WHITE;
        beam.glyph    = dchar_glyph(DCHAR_FIRED_MISSILE);
        beam.flavour  = BEAM_ENSNARE;
        beam.hit      = 22 + power / 20;
        break;

    case SPELL_FORCE_LANCE:
        beam.colour   = LIGHTGREY;
        beam.name     = "lance of force";
        beam.damage   = dice_def(3, 6 + (power / 15));
        beam.hit      = 20 + power / 20;
        beam.flavour  = BEAM_MMISSILE;
        break;

    case SPELL_SENTINEL_MARK:
        beam.ench_power = 125; //Difficult to resist
        beam.flavour    = BEAM_SENTINEL_MARK;
        beam.is_beam    = true;
        break;

    case SPELL_GHOSTLY_FLAMES:
        beam.name     = "ghostly flame";
        beam.damage   = dice_def(0, 1);
        beam.colour   = CYAN;
        beam.flavour  = BEAM_GHOSTLY_FLAME;
        beam.hit      = AUTOMATIC_HIT;
        beam.is_beam  = true;
        beam.is_big_cloud = true;
        break;

    case SPELL_GHOSTLY_FIREBALL:
        beam.colour   = CYAN;
        beam.name     = "ghostly fireball";
        beam.damage   = dice_def(3, 7 + power / 13);
        beam.hit      = 40;
        beam.flavour  = BEAM_GHOSTLY_FLAME;
        beam.is_explosion = true;
        break;

    case SPELL_DIMENSION_ANCHOR:
        beam.ench_power = power / 2;
        beam.flavour    = BEAM_DIMENSION_ANCHOR;
        beam.is_beam    = true;
        break;

    case SPELL_THORN_VOLLEY:
        beam.colour   = BROWN;
        beam.name     = "volley of thorns";
        beam.damage   = dice_def(3, 5 + (power / 13));
        beam.hit      = 20 + power / 15;
        beam.flavour  = BEAM_MMISSILE;
        break;

    case SPELL_STRIP_RESISTANCE:
        beam.ench_power = mons->spell_hd(real_spell) * 6;
        beam.flavour    = BEAM_VULNERABILITY;
        beam.is_beam    = true;
        break;

    // XXX: This seems needed to give proper spellcasting messages, even though
    //      damage is done via another means
    case SPELL_FREEZE:
        beam.flavour    = BEAM_COLD;
        break;

    case SPELL_MALIGN_OFFERING:
        beam.flavour    = BEAM_MALIGN_OFFERING;
        beam.damage     = dice_def(2, 7 + (power / 13));
        beam.is_beam    = true;
        break;

    case SPELL_VIRULENCE:
        beam.flavour    = BEAM_VIRULENCE;
        beam.ench_power = mons->spell_hd(real_spell) * 6;
        beam.is_beam    = true;
        break;

    case SPELL_IGNITE_POISON_SINGLE:
        beam.flavour    = BEAM_IGNITE_POISON;
        beam.ench_power = mons->spell_hd(real_spell) * 12;
        beam.is_beam    = true;
        break;

    case SPELL_ORB_OF_ELECTRICITY:
        beam.name           = "orb of electricity";
        beam.colour         = LIGHTBLUE;
        beam.damage         = dice_def(3, 7 + power / 9);
        beam.hit            = 40;
        beam.flavour        = BEAM_ELECTRICITY;
        beam.is_explosion   = true;
        beam.foe_ratio      = random_range(40, 55);
        break;

    case SPELL_EXPLOSIVE_BOLT:
        beam.name       = "explosive bolt";
        beam.damage     = dice_def(1, 0); // deals damage through explosions
        beam.colour     = RED;
        beam.flavour    = BEAM_FIRE;
        beam.hit        = 17 + power / 25;
        beam.ench_power = power;
        beam.is_beam    = true;
        break;

    case SPELL_FLASH_FREEZE:
        beam.name     = "flash freeze";
        beam.damage   = dice_def(3, 9 + (power / 13));
        beam.colour   = WHITE;
        beam.flavour  = BEAM_ICE;
        beam.hit      = AUTOMATIC_HIT;
        break;

    case SPELL_SAP_MAGIC:
        beam.ench_power = mons->spell_hd(real_spell) * 10;
        beam.flavour    = BEAM_SAP_MAGIC;
        beam.is_beam    = true;
        break;

    case SPELL_CORRUPT_BODY:
        beam.flavour    = BEAM_CORRUPT_BODY;
        beam.is_beam    = true;
        break;

    case SPELL_SHADOW_BOLT:
        beam.name     = "shadow bolt";
        beam.is_beam  = true;
        // deliberate fall-through
    case SPELL_SHADOW_SHARD:
        if (real_spell == SPELL_SHADOW_SHARD)
            beam.name  = "shadow shard";
        beam.damage   = dice_def(3, 8 + power / 11);
        beam.colour   = MAGENTA;
        beam.flavour  = BEAM_MMISSILE;
        beam.hit      = 17 + power / 25;
        break;

    case SPELL_CRYSTAL_BOLT:
        beam.name     = "crystal bolt";
        beam.damage   = dice_def(3, 8 + power / 11);
        beam.colour   = GREEN;
        beam.flavour  = BEAM_CRYSTAL;
        beam.hit      = 17 + power / 25;
        beam.is_beam  = true;
        break;

    case SPELL_DRAIN_MAGIC:
        beam.ench_power = mons->spell_hd(real_spell) * 6;
        beam.flavour    = BEAM_DRAIN_MAGIC;
        break;

    case SPELL_CORROSIVE_BOLT:
        beam.colour   = YELLOW;
        beam.name     = "bolt of acid";
        beam.damage   = dice_def(3, 9 + power / 17);
        beam.flavour  = BEAM_ACID;
        beam.hit      = 17 + power / 25;
        beam.is_beam  = true;
        break;

    default:
        if (check_validity)
        {
            beam.flavour = NUM_BEAMS;
            return beam;
        }

        if (!is_valid_spell(real_spell))
        {
            die("Invalid spell #%d cast by %s", (int) real_spell,
                     mons->name(DESC_PLAIN, true).c_str());
        }

        die("Unknown monster spell '%s' cast by %s",
                 spell_title(real_spell),
                 mons->name(DESC_PLAIN, true).c_str());
    }

    if (beam.is_enchantment())
    {
        beam.glyph = 0;
        beam.name = "";
    }

    if (spell_cast == SPELL_AGONY)
        beam.name = "agony";

    if (spell_cast == SPELL_DRACONIAN_BREATH)
        _scale_draconian_breath(beam, drac_type);

    return beam;
}

static bool _los_free_spell(spell_type spell_cast)
{
    return spell_cast == SPELL_HELLFIRE_BURST
       || spell_cast == SPELL_BRAIN_FEED
       || spell_cast == SPELL_SMITING
       || spell_cast == SPELL_HAUNT
       || spell_cast == SPELL_FIRE_STORM
       || spell_cast == SPELL_AIRSTRIKE
       || spell_cast == SPELL_WATERSTRIKE
       || spell_cast == SPELL_HOLY_FLAMES
       || spell_cast == SPELL_SUMMON_SPECTRAL_ORCS
       || spell_cast == SPELL_CHAOTIC_MIRROR;
}

// Set up bolt structure for monster spell casting.
bool setup_mons_cast(monster* mons, bolt &pbolt, spell_type spell_cast,
                     bool check_validity)
{
    // always set these -- used by things other than fire_beam()

    // [ds] Used to be 12 * MHD and later buggily forced to -1 downstairs.
    // Setting this to a more realistic number now that that bug is
    // squashed.
    pbolt.ench_power = 4 * mons->spell_hd(spell_cast);
    pbolt.beam_source = mons->mindex();

    // Convenience for the hapless innocent who assumes that this
    // damn function does all possible setup. [ds]
    if (pbolt.target.origin())
        pbolt.target = mons->target;

    // Set bolt type and range.
    if (_los_free_spell(spell_cast))
    {
        pbolt.range = 0;
        pbolt.glyph = 0;
        switch (spell_cast)
        {
        case SPELL_BRAIN_FEED:
        case SPELL_SMITING:
        case SPELL_AIRSTRIKE:
        case SPELL_WATERSTRIKE:
        case SPELL_HOLY_FLAMES:
        case SPELL_CHAOTIC_MIRROR:
            return true;
        default:
            // Other spells get normal setup:
            break;
        }
    }

    // The below are no-ops since they don't involve direct_effect,
    // fire_tracer, or beam.
    switch (spell_cast)
    {
    case SPELL_STICKS_TO_SNAKES:
    case SPELL_SUMMON_SMALL_MAMMAL:
    case SPELL_VAMPIRIC_DRAINING:
    case SPELL_INJURY_MIRROR:
    case SPELL_MAJOR_HEALING:
#if TAG_MAJOR_VERSION == 34
    case SPELL_VAMPIRE_SUMMON:
#endif
    case SPELL_SHADOW_CREATURES:       // summon anything appropriate for level
    case SPELL_WEAVE_SHADOWS:
#if TAG_MAJOR_VERSION == 34
    case SPELL_FAKE_RAKSHASA_SUMMON:
#endif
    case SPELL_FAKE_MARA_SUMMON:
    case SPELL_SUMMON_ILLUSION:
    case SPELL_SUMMON_DEMON:
    case SPELL_MONSTROUS_MENAGERIE:
    case SPELL_ANIMATE_DEAD:
    case SPELL_TWISTED_RESURRECTION:
    case SPELL_SIMULACRUM:
    case SPELL_CALL_IMP:
    case SPELL_SUMMON_MINOR_DEMON:
#if TAG_MAJOR_VERSION == 34
    case SPELL_SUMMON_SCORPIONS:
#endif
    case SPELL_SUMMON_SWARM:
    case SPELL_SUMMON_UFETUBUS:
    case SPELL_SUMMON_HELL_BEAST:  // Geryon
    case SPELL_SUMMON_UNDEAD:
    case SPELL_SUMMON_ICE_BEAST:
    case SPELL_SUMMON_MUSHROOMS:
    case SPELL_CONJURE_BALL_LIGHTNING:
    case SPELL_SUMMON_DRAKES:
    case SPELL_SUMMON_HORRIBLE_THINGS:
    case SPELL_MALIGN_GATEWAY:
    case SPELL_HAUNT:
    case SPELL_SYMBOL_OF_TORMENT:
    case SPELL_CAUSE_FEAR:
    case SPELL_MESMERISE:
    case SPELL_DRAIN_LIFE:
    case SPELL_SUMMON_GREATER_DEMON:
    case SPELL_CANTRIP:
    case SPELL_BROTHERS_IN_ARMS:
    case SPELL_BERSERKER_RAGE:
    case SPELL_TROGS_HAND:
    case SPELL_SWIFTNESS:
    case SPELL_STONESKIN:
    case SPELL_WATER_ELEMENTALS:
    case SPELL_FIRE_ELEMENTALS:
    case SPELL_AIR_ELEMENTALS:
    case SPELL_EARTH_ELEMENTALS:
    case SPELL_IRON_ELEMENTALS:
#if TAG_MAJOR_VERSION == 34
    case SPELL_SUMMON_ELEMENTAL:
#endif
    case SPELL_CREATE_TENTACLES:
    case SPELL_BLINK:
    case SPELL_CONTROLLED_BLINK:
    case SPELL_BLINK_RANGE:
    case SPELL_BLINK_AWAY:
    case SPELL_BLINK_CLOSE:
    case SPELL_TOMB_OF_DOROKLOHE:
    case SPELL_CHAIN_LIGHTNING:    // the only user is reckless
    case SPELL_SUMMON_EYEBALLS:
    case SPELL_SUMMON_BUTTERFLIES:
#if TAG_MAJOR_VERSION == 34
    case SPELL_MISLEAD:
#endif
    case SPELL_CALL_TIDE:
    case SPELL_INK_CLOUD:
    case SPELL_SILENCE:
    case SPELL_AWAKEN_FOREST:
    case SPELL_DRUIDS_CALL:
    case SPELL_SUMMON_SPECTRAL_ORCS:
    case SPELL_REGENERATION:
    case SPELL_CORPSE_ROT:
    case SPELL_LEDAS_LIQUEFACTION:
    case SPELL_SUMMON_DRAGON:
    case SPELL_SUMMON_HYDRA:
    case SPELL_FIRE_SUMMON:
    case SPELL_DEATHS_DOOR:
    case SPELL_OZOCUBUS_ARMOUR:
    case SPELL_OZOCUBUS_REFRIGERATION:
    case SPELL_OLGREBS_TOXIC_RADIANCE:
    case SPELL_SHATTER:
    case SPELL_FRENZY:
#if TAG_MAJOR_VERSION == 34
    case SPELL_SUMMON_TWISTER:
#endif
    case SPELL_BATTLESPHERE:
    case SPELL_SPECTRAL_WEAPON:
    case SPELL_WORD_OF_RECALL:
    case SPELL_INJURY_BOND:
    case SPELL_CALL_LOST_SOUL:
    case SPELL_BLINK_ALLIES_ENCIRCLE:
    case SPELL_MASS_CONFUSION:
    case SPELL_ENGLACIATION:
#if TAG_MAJOR_VERSION == 34
    case SPELL_SHAFT_SELF:
#endif
    case SPELL_AWAKEN_VINES:
    case SPELL_CONTROL_WINDS:
    case SPELL_WALL_OF_BRAMBLES:
    case SPELL_HASTE_PLANTS:
    case SPELL_WIND_BLAST:
    case SPELL_SUMMON_VERMIN:
    case SPELL_TORNADO:
    case SPELL_DISCHARGE:
    case SPELL_IGNITE_POISON:
    case SPELL_EPHEMERAL_INFUSION:
    case SPELL_FORCEFUL_INVITATION:
    case SPELL_PLANEREND:
    case SPELL_CHAIN_OF_CHAOS:
    case SPELL_BLACK_MARK:
    case SPELL_GRAND_AVATAR:
#if TAG_MAJOR_VERSION == 34
    case SPELL_REARRANGE_PIECES:
#endif
    case SPELL_BLINK_ALLIES_AWAY:
    case SPELL_SHROUD_OF_GOLUBRIA:
    case SPELL_PHANTOM_MIRROR:
        return true;
    default:
        if (check_validity)
        {
            bolt beam = mons_spell_beam(mons, spell_cast, 1, true);
            return beam.flavour != NUM_BEAMS;
        }
        break;
    }

    int power = 12 * mons->spell_hd(spell_cast);

    bolt theBeam = mons_spell_beam(mons, spell_cast, power);

    bolt_parent_init(&theBeam, &pbolt);
    pbolt.source = mons->pos();
    pbolt.is_tracer = false;
    if (!pbolt.is_enchantment())
        pbolt.aux_source = pbolt.name;
    else
        pbolt.aux_source.clear();

    // Your shadow can target these spells at other monsters;
    // other monsters can't.
    if (mons->mid != MID_PLAYER)
    {
        if (spell_cast == SPELL_HASTE
            || spell_cast == SPELL_MIGHT
            || spell_cast == SPELL_INVISIBILITY
            || spell_cast == SPELL_MINOR_HEALING
            || spell_cast == SPELL_TELEPORT_SELF
            || spell_cast == SPELL_SILENCE
            || spell_cast == SPELL_FRENZY)
        {
            pbolt.target = mons->pos();
        }
        else if (spell_cast == SPELL_LRD)
        {
            pbolt.target = _mons_fragment_target(mons);
            pbolt.aimed_at_spot = true; // to get noise to work properly
        }
     }

    return true;
}

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

    if (mons_genus(mons->type) == MONS_DRACONIAN)
    {
        switch (draco_or_demonspawn_subspecies(mons))
        {
        case MONS_DRACONIAN:
        case MONS_YELLOW_DRACONIAN:     // already handled as ability
            break;
        case MONS_GREY_DRACONIAN:       // no breath
            break;
        default:
            draco_breath = SPELL_DRACONIAN_BREATH;
            break;
        }
    }

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

        fire_tracer(mons, 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;
}

// Function should return false if friendlies shouldn't animate any dead.
// Currently, this only happens if the player is in the middle of butchering
// a corpse (infuriating), or if they are less than satiated.  Only applies
// to friendly corpse animators. {due}
static bool _animate_dead_okay(spell_type spell)
{
    // It's always okay in the arena.
    if (crawl_state.game_is_arena())
        return true;

    if (is_butchering() || is_vampire_feeding())
        return false;

    if (you.hunger_state < HS_SATIATED && you.mutation[MUT_HERBIVOROUS] < 3)
        return false;

    if (god_hates_spell(spell, you.religion))
        return false;

    return true;
}

// Spells that work even if magic is off.  Be careful to not add ones which
// appear both ways (SPELL_LIGHTNING_BOLT is also storm dragon breath, etc).
static bool _is_physiological_spell(spell_type spell)
{
    return spell == SPELL_QUICKSILVER_BOLT
        || spell == SPELL_METAL_SPLINTERS
        || spell == SPELL_STICKY_FLAME_SPLASH
        || spell == SPELL_SPIT_POISON
        || spell == SPELL_HOLY_BREATH
        || spell == SPELL_FIRE_BREATH;
}

// Returns true if the spell is something you wouldn't want done if
// you had a friendly target...  only returns a meaningful value for
// non-beam spells.
static bool _ms_direct_nasty(spell_type monspell)
{
    return spell_needs_foe(monspell)
           && !spell_typematch(monspell, SPTYP_SUMMONING);
}

// Can be affected by the 'Haste Plants' spell
static bool _is_hastable_plant(const monster* mons)
{
    return mons->holiness() == MH_PLANT
           && !mons_is_firewood(mons)
           && mons->type != MONS_SNAPLASHER_VINE
           && mons->type != MONS_SNAPLASHER_VINE_SEGMENT;
}

// Checks if the foe *appears* to be immune to negative energy.  We
// can't just use foe->res_negative_energy(), because that'll mean
// monsters will just "know" whether a player is fully life-protected.
static bool _foe_should_res_negative_energy(const actor* foe)
{
    if (foe->is_player())
    {
        switch (you.is_undead)
        {
        case US_ALIVE:
            // Demonspawn are not demons, and statue form grants only
            // partial resistance.
            return false;
        case US_SEMI_UNDEAD:
            // Non-bloodless vampires do not appear immune.
            return you.hunger_state == HS_STARVING;
        default:
            return true;
        }
    }

    return foe->holiness() != MH_NATURAL;
}

static bool _valid_blink_ally(const monster* caster, const monster* target)
{
    return mons_aligned(caster, target) && caster != target
           && !target->no_tele(true, false, true);
}

static bool _valid_encircle_ally(const monster* caster, const monster* target,
                                 const coord_def foepos)
{
    return _valid_blink_ally(caster, target)
           && target->pos().distance_from(foepos) > 1;
}

static bool _valid_blink_away_ally(const monster* caster, const monster* target,
                                   const coord_def foepos)
{
    return _valid_blink_ally(caster, target)
           && target->pos().distance_from(foepos) < 3;
}

static bool _valid_druids_call_target(const monster* caller, const monster* callee)
{
    return mons_aligned(caller, callee) && mons_is_beast(callee->type)
           && callee->get_experience_level() <= 20
           && !callee->is_shapeshifter()
           && !caller->see_cell(callee->pos())
           && mons_habitat(callee) != HT_WATER
           && mons_habitat(callee) != HT_LAVA
           && !callee->is_travelling();
}

// If true, this monster's spellcasting should respect the breath timer.
static bool _is_breath_caster(monster_type mtyp)
{
    // Wind drakes breathe as a special ability, not a spell; but they do
    // "cast" Airstrike by flapping their wings, and that shouldn't respect
    // the breath timer. All other drakes and dragons have only breath
    // spells (if you count the refrigeration roar of SoH[Coc]).
    return mons_genus(mtyp) == MONS_DRAGON
           || mons_genus(mtyp) == MONS_DRAKE && mtyp != MONS_WIND_DRAKE;
}

static bool _mirrorable(const monster* agent, const monster* mon)
{
    return mon != agent
           && mons_aligned(mon, agent)
           && !mon->is_stationary()
           && !mon->is_summoned()
           && !mons_is_conjured(mon->type)
           && !mons_is_unique(mon->type);
}

// Checks to see if a particular spell is worth casting in the first place.
static bool _ms_waste_of_time(const monster* mon, spell_type monspell)
{
    bool ret = false;
    actor *foe = mon->get_foe();

    // Keep friendly summoners from spamming summons constantly.
    if (mon->friendly()
        && (!foe || foe->is_player())
        && spell_typematch(monspell, SPTYP_SUMMONING))
    {
        return true;
    }

    if (!mon->wont_attack())
    {
        if (spell_harms_area(monspell) && env.sanctuary_time > 0)
            return true;

        if (spell_harms_target(monspell) && is_sanctuary(mon->target))
            return true;
    }

    if (_is_breath_caster(mon->type) && mon->has_ench(ENCH_BREATH_WEAPON))
        return true;

    // Don't bother casting a summon spell if we're already at its cap
    if (summons_are_capped(monspell)
        && count_summons(mon, monspell) >= summons_limit(monspell))
    {
        return true;
    }

    // Eventually, we'll probably want to be able to have monsters
    // learn which of their elemental bolts were resisted and have those
    // handled here as well. - bwr
    switch (monspell)
    {
    case SPELL_CALL_TIDE:
        return !player_in_branch(BRANCH_SHOALS)
               || mon->has_ench(ENCH_TIDE)
               || !foe
               || (grd(mon->pos()) == DNGN_DEEP_WATER
                   && grd(foe->pos()) == DNGN_DEEP_WATER);

    case SPELL_BRAIN_FEED:
        ret = (!foe || !foe->is_player());
        break;

    case SPELL_VAMPIRIC_DRAINING:
        if (!foe
            || mon->hit_points + 1 >= mon->max_hit_points
            || !adjacent(mon->pos(), foe->pos()))
        {
            ret = true;
        }
    // fall through
    case SPELL_BOLT_OF_DRAINING:
    case SPELL_AGONY:
    case SPELL_MALIGN_OFFERING:
        if (!foe || _foe_should_res_negative_energy(foe))
            ret = true;
        break;
    case SPELL_MIASMA_BREATH:
        ret = (!foe || foe->res_rotting());
        break;

    case SPELL_DISPEL_UNDEAD:
        // [ds] How is dispel undead intended to interact with vampires?
        ret = (!foe || foe->holiness() != MH_UNDEAD);
        break;

    case SPELL_CORONA:
        ret = (!foe || foe->backlit() || foe->glows_naturally()
               || mons_class_flag(foe->type, M_SHADOW));
        break;

    case SPELL_BERSERKER_RAGE:
        if (!mon->needs_berserk(false))
            ret = true;
        break;

    case SPELL_FRENZY:
        if (!mon->can_go_frenzy())
            ret = true;
        break;

    case SPELL_HASTE:
        if (mon->has_ench(ENCH_HASTE))
            ret = true;
        break;

    case SPELL_MIGHT:
        if (mon->has_ench(ENCH_MIGHT))
            ret = true;
        break;

    case SPELL_SWIFTNESS:
        if (mon->has_ench(ENCH_SWIFT))
            ret = true;
        break;

    case SPELL_REGENERATION:
        if (mon->has_ench(ENCH_REGENERATION) || mon->has_ench(ENCH_DEATHS_DOOR)
            || mon->holiness() == MH_UNDEAD)
        {
            ret = true;
        }
        break;

    case SPELL_INJURY_MIRROR:
        if (mon->has_ench(ENCH_MIRROR_DAMAGE)
            || (mon->props.exists("mirror_recast_time")
                && you.elapsed_time < mon->props["mirror_recast_time"].get_int()))
        {
            ret = true;
        }
        break;

    case SPELL_TROGS_HAND:
        if (mon->has_ench(ENCH_RAISED_MR) || mon->has_ench(ENCH_REGENERATION))
            ret = true;
        break;

    case SPELL_STONESKIN:
        if (mon->has_ench(ENCH_STONESKIN))
            ret = true;
        break;

    case SPELL_INVISIBILITY:
        if (mon->has_ench(ENCH_INVIS)
            || mon->glows_naturally()
            || mon->friendly() && !you.can_see_invisible(false))
        {
            ret = true;
        }
        break;

    case SPELL_MINOR_HEALING:
    case SPELL_MAJOR_HEALING:
        if (mon->hit_points > mon->max_hit_points / 2)
            ret = true;
        break;

    case SPELL_TELEPORT_SELF:
        // Monsters aren't smart enough to know when to cancel teleport.
        if (mon->has_ench(ENCH_TP) || mon->no_tele(true, false))
            ret = true;
        break;

    case SPELL_BLINK_CLOSE:
        if (!foe || adjacent(mon->pos(), foe->pos()))
            ret = true;
    case SPELL_BLINK:
    case SPELL_CONTROLLED_BLINK:
    case SPELL_BLINK_RANGE:
    case SPELL_BLINK_AWAY:
        // Prefer to keep a tornado going rather than blink.
        if (mon->no_tele(true, false) || mon->has_ench(ENCH_TORNADO))
            ret = true;
        break;

    case SPELL_BLINK_OTHER:
    case SPELL_BLINK_OTHER_CLOSE:
        if (!foe)
            ret = true;
        else if (foe->is_monster()
            && foe->as_monster()->has_ench(ENCH_DIMENSION_ANCHOR))
            ret = true;
        else if (foe->is_player() && you.duration[DUR_DIMENSION_ANCHOR])
            ret = true;
        break;

    case SPELL_TELEPORT_OTHER:
        // Monsters aren't smart enough to know when to cancel teleport.
        if (mon->foe == MHITYOU)
        {
            ret = you.duration[DUR_TELEPORT];
            break;
        }
        else if (mon->foe != MHITNOT)
        {
            ret = (menv[mon->foe].has_ench(ENCH_TP));
            break;
        }
        // intentional fall-through

    case SPELL_SLOW:
    case SPELL_CONFUSE:
    case SPELL_PAIN:
    case SPELL_BANISHMENT:
    case SPELL_DISINTEGRATE:
    case SPELL_PARALYSE:
    case SPELL_SLEEP:
    case SPELL_HIBERNATION:
    case SPELL_DIMENSION_ANCHOR:
    {
        if ((monspell == SPELL_HIBERNATION || monspell == SPELL_SLEEP)
            && (!foe || foe->asleep() || !foe->can_sleep()))
        {
            ret = true;
            break;
        }

        // Occasionally we don't estimate... just fire and see.
        if (one_chance_in(5))
        {
            ret = false;
            break;
        }

        // Only intelligent monsters estimate.
        int intel = mons_intel(mon);
        if (intel < I_NORMAL)
        {
            ret = false;
            break;
        }

        // We'll estimate the target's resistance to magic, by first getting
        // the actual value and then randomising it.
        int est_magic_resist = (mon->foe == MHITNOT) ? 10000 : 0;

        if (mon->foe != MHITNOT)
        {
            if (mon->foe == MHITYOU)
                est_magic_resist = you.res_magic();
            else
                est_magic_resist = menv[mon->foe].res_magic();

            // now randomise (normal intels less accurate than high):
            if (intel == I_NORMAL)
                est_magic_resist += random2(80) - 40;
            else
                est_magic_resist += random2(30) - 15;
        }

        int power = 12 * mon->spell_hd(monspell)
                       * (monspell == SPELL_PAIN ? 2 : 1);
        power = stepdown_value(power, 30, 40, 100, 120);

        // Determine the amount of chance allowed by the benefit from
        // the spell.  The estimated difficulty is the probability
        // of rolling over 100 + diff on 2d100. -- bwr
        int diff = (monspell == SPELL_PAIN
                    || monspell == SPELL_SLOW
                    || monspell == SPELL_CONFUSE) ? 0 : 50;

        if (est_magic_resist - power > diff)
            ret = true;
        break;
    }

    case SPELL_SUMMON_ILLUSION:
        if (!foe || !actor_is_illusion_cloneable(foe))
            ret = true;
        break;

    case SPELL_AWAKEN_FOREST:
        if (mon->has_ench(ENCH_AWAKEN_FOREST)
            || env.forest_awoken_until > you.elapsed_time
            || !forest_near_enemy(mon)
            || you_worship(GOD_FEDHAS))
        {
            ret = true;
        }
        break;

    case SPELL_DEATHS_DOOR:
        // The caster may be an (undead) enslaved soul.
        if (mon->holiness() == MH_UNDEAD
            || mon->has_ench(ENCH_DEATHS_DOOR)
            || mon->has_ench(ENCH_FATIGUE))
        {
            ret = true;
        }
        break;

    case SPELL_OZOCUBUS_ARMOUR:
        if (mon->has_ench(ENCH_OZOCUBUS_ARMOUR))
            ret = true;
        break;

    case SPELL_BATTLESPHERE:
        if (find_battlesphere(mon))
            ret = true;
        break;

    case SPELL_SPECTRAL_WEAPON:
        if (find_spectral_weapon(mon))
            ret = true;
        break;

    case SPELL_INJURY_BOND:
        for (monster_iterator mi; mi; ++mi)
        {
            if (mons_aligned(mon, *mi) && !mi->has_ench(ENCH_CHARM)
                && *mi != mon && mon->see_cell_no_trans(mi->pos())
                && !mi->has_ench(ENCH_INJURY_BOND))
            {
                    return false; // We found at least one target; that's enough.
            }
        }
        ret = true;
        break;

    case SPELL_GHOSTLY_FIREBALL:
        ret = (!foe || foe->holiness() == MH_UNDEAD);
        break;

    case SPELL_BLINK_ALLIES_ENCIRCLE:
    {
        if (!mon->get_foe() || mon->get_foe() && !mon->can_see(mon->get_foe()))
            return true;

        const coord_def foepos = mon->get_foe()->pos();

        for (monster_near_iterator mi(mon, LOS_NO_TRANS); mi; ++mi)
        {
            if (_valid_encircle_ally(mon, *mi, foepos))
                return false; // We found at least one valid ally; that's enough.
        }
        return true;
    }

    case SPELL_AWAKEN_VINES:
    if (!mon->get_foe() || (mon->has_ench(ENCH_AWAKEN_VINES)
                            && mon->props["vines_awakened"].get_int() >= 3))
    {
        return true;
    }
    else // To account for multiple dryads in range of each other
    {
        int count = 0;
        for (monster_near_iterator mi(mon, LOS_NO_TRANS); mi; ++mi)
        {
            if (mi->type == MONS_SNAPLASHER_VINE)
                ++count;
        }
        if (count > 2)
            return true;
    }
    break;

    case SPELL_CONTROL_WINDS:
        return mon->has_ench(ENCH_CONTROL_WINDS);

    case SPELL_WATERSTRIKE:
        return !foe || !feat_is_water(grd(foe->pos()));

    case SPELL_HASTE_PLANTS:
        for (monster_near_iterator mi(mon, LOS_NO_TRANS); mi; ++mi)
        {
            // Isn't useless if there's a single viable target for it
            if (mons_aligned(*mi, mon)
                && _is_hastable_plant(*mi)
                && !mi->has_ench(ENCH_HASTE))
            {
                return false;
            }
        }
        return true;

    // Don't use unless our foe is close to us and there are no allies already
    // between the two of us
    case SPELL_WIND_BLAST:
        if (foe && foe->pos().distance_from(mon->pos()) < 4)
        {
            bolt tracer;
            tracer.target = foe->pos();
            tracer.range  = LOS_RADIUS;
            tracer.hit    = AUTOMATIC_HIT;
            fire_tracer(mon, tracer);

            actor* act = actor_at(tracer.path_taken.back());
            if (act && mons_aligned(mon, act))
                return true;
            else
                return false;
        }
        else
            return true;

    case SPELL_STRIP_RESISTANCE:
        if (foe)
        {
            if (foe->is_monster() && foe->as_monster()->has_ench(ENCH_LOWERED_MR))
                return true;
            else if (foe->is_player() && you.duration[DUR_LOWERED_MR])
                return true;
            else
                return false;
        }
        return true;

    case SPELL_BROTHERS_IN_ARMS:
        return mon->props.exists("brothers_count")
               && mon->props["brothers_count"].get_int() >= 2;

    case SPELL_SUMMON_MUSHROOMS:
        return mon->get_foe() == NULL;

    case SPELL_FREEZE:
        return !foe || !adjacent(mon->pos(), foe->pos());

    case SPELL_DRUIDS_CALL:
        // Don't cast unless there's at least one valid target
        for (monster_iterator mi; mi; ++mi)
        {
            if (_valid_druids_call_target(mon, *mi))
                return false;
        }
        return true;

    // Don't spam mesmerisation if you're already mesmerised
    case SPELL_MESMERISE:
        return you.beheld_by(mon) && coinflip();

    case SPELL_DISCHARGE:
        // TODO: possibly check for friendlies nearby?
        // Perhaps it will be used recklessly like chain lightning...
        if ((mon->foe == MHITYOU || mon->foe == MHITNOT)
            && !adjacent(you.pos(), mon->pos())
            || (mon->foe != MHITYOU && mon->foe != MHITNOT
                && !adjacent(menv[mon->foe].pos(), mon->pos())))
        {
            ret = true;
        }
        break;

    case SPELL_PORTAL_PROJECTILE:
    {
        bolt beam;
        beam.source      = mon->pos();
        beam.target      = mon->target;
        beam.beam_source = mon->mindex();
        ret = !handle_throw((monster *)mon, beam, true, true);
        break;
    }

    case SPELL_VIRULENCE:
        return foe && foe->res_poison(false) >= 3;

    case SPELL_IGNITE_POISON_SINGLE:
        return !foe || !ignite_poison_affects(foe);

    case SPELL_FLASH_FREEZE:
        if (!foe
            || (foe->is_player() && you.duration[DUR_FROZEN])
            || (foe->is_monster() && foe->as_monster()->has_ench(ENCH_FROZEN)))
        {
            ret = true;
        }
        break;

    case SPELL_LEGENDARY_DESTRUCTION:
        if (!foe || mon->hit_points <= 10)
            ret = true;
        break;

    case SPELL_BLACK_MARK:
        if (mon->has_ench(ENCH_BLACK_MARK))
            ret = true;
        break;

    case SPELL_GRAND_AVATAR:
        if (mon->has_ench(ENCH_GRAND_AVATAR))
            ret = true;
        break;

     // No need to spam cantrips if we're just travelling around
    case SPELL_CANTRIP:
        if (mon->friendly() && mon->foe == MHITYOU)
            ret = true;
        break;

    case SPELL_BLINK_ALLIES_AWAY:
    {
        if (!mon->get_foe() || mon->get_foe() && !mon->can_see(mon->get_foe()))
            return true;

        const coord_def foepos = mon->get_foe()->pos();

        for (monster_near_iterator mi(mon, LOS_NO_TRANS); mi; ++mi)
        {
            if (_valid_blink_away_ally(mon, *mi, foepos))
                return false;
        }
        return true;
    }

    case SPELL_SHROUD_OF_GOLUBRIA:
        if (mon->has_ench(ENCH_SHROUD))
            ret = true;
        break;

    // Don't let clones duplicate anything, to prevent exponential explosion
    case SPELL_FAKE_MARA_SUMMON:
        return mon->has_ench(ENCH_PHANTOM_MIRROR);

    case SPELL_PHANTOM_MIRROR:
        if (!mon->has_ench(ENCH_PHANTOM_MIRROR))
        {
            for (monster_near_iterator mi(mon); mi; ++mi)
            {
                // A single valid target is enough.
                if (_mirrorable(mon, *mi))
                    return false;
            }
        }
        return true;

    case SPELL_OLGREBS_TOXIC_RADIANCE:
        return mon->has_ench(ENCH_TOXIC_RADIANCE);

#if TAG_MAJOR_VERSION == 34
    case SPELL_SUMMON_TWISTER:
    case SPELL_SHAFT_SELF:
    case SPELL_MISLEAD:
    case SPELL_SUMMON_SCORPIONS:
    case SPELL_SUMMON_ELEMENTAL:
#endif
    case SPELL_NO_SPELL:
        ret = true;
        break;

    default:
        break;
    }

    return ret;
}

// Spells a monster may want to cast if fleeing from the player, and
// the player is not in sight.
static bool _ms_useful_fleeing_out_of_sight(const monster* mon,
                                            spell_type monspell)
{
    if (monspell == SPELL_NO_SPELL || _ms_waste_of_time(mon, monspell))
        return false;

    switch (monspell)
    {
    case SPELL_HASTE:
    case SPELL_SWIFTNESS:
    case SPELL_INVISIBILITY:
    case SPELL_MINOR_HEALING:
    case SPELL_MAJOR_HEALING:
    case SPELL_ANIMATE_DEAD:
    case SPELL_TWISTED_RESURRECTION:
        return true;

    default:
        if (spell_typematch(monspell, SPTYP_SUMMONING) && one_chance_in(4))
            return true;
        break;
    }

    return false;
}

static bool _ms_low_hitpoint_cast(const monster* mon, spell_type monspell)
{
    if (_ms_waste_of_time(mon, monspell))
        return false;

    bool targ_adj      = false;
    bool targ_sanct    = false;
    bool targ_friendly = false;
    bool targ_undead   = false;

    if (mon->foe == MHITYOU || mon->foe == MHITNOT)
    {
        if (adjacent(you.pos(), mon->pos()))
            targ_adj = true;
        if (is_sanctuary(you.pos()))
            targ_sanct = true;
        if (you.undead_or_demonic())
            targ_undead = true;
    }
    else
    {
        if (adjacent(menv[mon->foe].pos(), mon->pos()))
            targ_adj = true;
        if (is_sanctuary(menv[mon->foe].pos()))
            targ_sanct = true;
        if (menv[mon->foe].undead_or_demonic())
            targ_undead = true;
    }

    targ_friendly = (mon->foe == MHITYOU
                     ? mon->wont_attack()
                     : mons_aligned(mon, mon->get_foe()));

    switch (monspell)
    {
    case SPELL_HEAL_OTHER:
        return targ_friendly && mon->foe != MHITYOU;
    case SPELL_TELEPORT_OTHER:
        return !targ_sanct && !targ_friendly;
    case SPELL_MINOR_HEALING:
    case SPELL_MAJOR_HEALING:
    case SPELL_INVISIBILITY:
    case SPELL_TELEPORT_SELF:
    case SPELL_HASTE:
    case SPELL_DEATHS_DOOR:
    case SPELL_BERSERKER_RAGE:
    case SPELL_FRENZY:
    case SPELL_MIGHT:
    case SPELL_WIND_BLAST:
    case SPELL_EPHEMERAL_INFUSION:
        return true;
    case SPELL_VAMPIRIC_DRAINING:
        return !targ_sanct && targ_adj && !targ_friendly && !targ_undead;
    case SPELL_BLINK_AWAY:
    case SPELL_BLINK_RANGE:
    case SPELL_INJURY_MIRROR:
        return !targ_friendly;
    case SPELL_BLINK_OTHER:
        return !targ_sanct && targ_adj && !targ_friendly;
    case SPELL_CONFUSE:
    case SPELL_DRAIN_LIFE:
    case SPELL_BANISHMENT:
    case SPELL_HELLFIRE_BURST:
    case SPELL_FIREBALL:
    case SPELL_AIRSTRIKE:
    case SPELL_IOOD:
    case SPELL_ENSNARE:
    case SPELL_THROW_FLAME:
    case SPELL_MEPHITIC_CLOUD:
        return !targ_friendly && !targ_sanct;
    case SPELL_BLINK:
    case SPELL_CONTROLLED_BLINK:
        return targ_adj;
    case SPELL_TOMB_OF_DOROKLOHE:
        return true;
    case SPELL_NO_SPELL:
        return false;
    case SPELL_INK_CLOUD:
        if (mon->type == MONS_KRAKEN)
            return true;
    default:
        return !targ_adj && spell_typematch(monspell, SPTYP_SUMMONING);
    }
}

// Spells for a quick get-away.
// Currently only used to get out of a net.
static bool _ms_quick_get_away(const monster* mon, spell_type monspell)
{
    switch (monspell)
    {
    case SPELL_TELEPORT_SELF:
        // Don't cast again if already about to teleport.
        if (mon->has_ench(ENCH_TP))
            return false;
        // intentional fall-through
    case SPELL_BLINK:
        return true;
    default:
        return false;
    }
}

static void _mons_set_priest_wizard_god(monster* mons, bool& priest,
                                        bool& wizard, god_type& god)
{
    priest = mons->is_priest();
    wizard = mons->is_actual_spellcaster();

    // If the monster's a priest, assume summons come from priestly
    // abilities, in which case they'll have the same god. If the
    // monster is neither a priest nor a wizard, assume summons come
    // from intrinsic abilities, in which case they'll also have the
    // same god.
    god = (priest || !(priest || wizard)) ? mons->god : GOD_NO_GOD;

    // Permanent wizard summons of Yred should have the same god even
    // though they aren't priests. This is so that e.g. the zombies of
    // Yred's enslaved souls will properly turn on you if you abandon
    // Yred.
    if (mons->god == GOD_YREDELEMNUL)
        god = mons->god;
}

// Is it worth bothering to invoke recall? (Currently defined by there being at
// least 3 things we could actually recall, and then with a probability inversely
// proportional to how many HD of allies are current nearby)
static bool _should_recall(monster* caller)
{
    // It's a long recitation - if we're winded, we can't use it.
    if (caller->has_ench(ENCH_BREATH_WEAPON))
        return false;

    int num = 0;
    for (monster_iterator mi; mi; ++mi)
    {
        if (mons_is_recallable(caller, *mi) && !caller->can_see(*mi))
            ++num;
    }

    // Since there are reinforcements we could recall, do we think we need them?
    if (num > 2)
    {
        int ally_hd = 0;
        for (monster_iterator mi; mi; ++mi)
        {
            if (*mi != caller && caller->can_see(*mi) && mons_aligned(caller, *mi)
                && !mons_is_firewood(*mi))
            {
                ally_hd += mi->get_experience_level();
            }
        }
        return 25 + roll_dice(2, 22) > ally_hd;
    }
    else
        return false;
}

unsigned short mons_word_of_recall(monster* mons, unsigned short recall_target)
{
    unsigned short num_recalled = 0;
    vector<monster* > mon_list;

    // Build the list of recallable monsters and randomize
    for (monster_iterator mi; mi; ++mi)
    {
        // Don't recall ourselves
        if (*mi == mons)
            continue;

        if (!mons_is_recallable(mons, *mi))
            continue;

        // Don't recall things that are already close to us
        if ((mons && mons->can_see(*mi))
            || (!mons && you.can_see(*mi)))
        {
            continue;
        }

        mon_list.push_back(*mi);
    }
    shuffle_array(mon_list);

    coord_def target   = (mons) ? mons->pos() : you.pos();
    unsigned short foe = (mons) ? mons->foe   : MHITYOU;

    // Now actually recall things
    for (unsigned int i = 0; i < mon_list.size(); ++i)
    {
        coord_def empty;
        if (find_habitable_spot_near(target, mons_base_type(mon_list[i]),
                                     3, false, empty)
            && mon_list[i]->move_to_pos(empty))
        {
            mon_list[i]->behaviour = BEH_SEEK;
            mon_list[i]->foe = foe;
            ++num_recalled;
            simple_monster_message(mon_list[i], " is recalled.");
        }
        // Can only recall a couple things at once
        if (num_recalled == recall_target)
            break;
    }
    return num_recalled;
}

static bool _valid_vine_spot(coord_def p)
{
    if (actor_at(p) || !monster_habitable_grid(MONS_PLANT, grd(p)))
        return false;

    int num_trees = 0;
    bool valid_trees = false;
    for (adjacent_iterator ai(p); ai; ++ai)
    {
        if (feat_is_tree(grd(*ai)))
        {
            // Make sure this spot is not on a diagonal to its only adjacent
            // tree (so that the vines can pull back against the tree properly)
            if (num_trees || !((*ai-p).sgn().x != 0 && (*ai-p).sgn().y != 0))
            {
                valid_trees = true;
                break;
            }
            else
                ++num_trees;
        }
    }

    if (!valid_trees)
        return false;

    // Now the connectivity check
    return !plant_forbidden_at(p, true);
}

static bool _awaken_vines(monster* mon, bool test_only = false)
{
    vector<coord_def> spots;
    for (radius_iterator ri(mon->pos(), LOS_NO_TRANS); ri; ++ri)
    {
        if (_valid_vine_spot(*ri))
            spots.push_back(*ri);
    }

    shuffle_array(spots);

    actor* foe = mon->get_foe();

    int num_vines = 1 + random2(3);
    if (mon->props.exists("vines_awakened"))
        num_vines = min(num_vines, 3 - mon->props["vines_awakened"].get_int());
    bool seen = false;

    for (unsigned int i = 0; i < spots.size(); ++i)
    {
        // Don't place vines where they can't see our target
        if (!cell_see_cell(spots[i], foe->pos(), LOS_NO_TRANS))
            continue;

        // Don't place a vine too near to another existing one
        bool too_close = false;
        for (distance_iterator di(spots[i], false, true, 3); di; ++di)
        {
            monster* m = monster_at(*di);
            if (m && m->type == MONS_SNAPLASHER_VINE)
            {
                too_close = true;
                break;
            }
        }
        if (too_close)
            continue;

        // We've found at least one valid spot, so the spell should be castable
        if (test_only)
            return true;

        // Actually place the vine and update properties
        if (monster* vine = create_monster(
            mgen_data(MONS_SNAPLASHER_VINE, SAME_ATTITUDE(mon), mon,
                        0, SPELL_AWAKEN_VINES, spots[i], mon->foe,
                        MG_FORCE_PLACE, mon->god, MONS_NO_MONSTER)))
        {
            vine->props["vine_awakener"].get_int() = mon->mid;
            mon->props["vines_awakened"].get_int()++;
            mon->add_ench(mon_enchant(ENCH_AWAKEN_VINES, 1, NULL, 200));
            --num_vines;
            if (you.can_see(vine))
                seen = true;
        }

        // We've finished placing all our vines
        if (num_vines == 0)
            break;
    }

    if (test_only)
        return false;
    else
    {
        if (seen)
            mpr("Vines fly forth from the trees!");
        return true;
    }
}

static bool _place_druids_call_beast(const monster* druid, monster* beast,
                                     const actor* target)
{
    for (int t = 0; t < 20; ++t)
    {
        // Attempt to find some random spot out of the target's los to place
        // the beast (but not too far away).
        coord_def area = clamp_in_bounds(target->pos() + coord_def(random_range(-11, 11),
                                                                   random_range(-11, 11)));
        if (cell_see_cell(target->pos(), area, LOS_DEFAULT))
            continue;

        coord_def base_spot;
        int tries = 0;
        while (tries < 10 && base_spot.origin())
        {
            find_habitable_spot_near(area, mons_base_type(beast), 3, false, base_spot);
            if (cell_see_cell(target->pos(), base_spot, LOS_DEFAULT))
                base_spot.reset();
            ++tries;
        }

        if (base_spot.origin())
            continue;

        beast->move_to_pos(base_spot);

        // Wake the beast up and calculate a path to the druid's target.
        // (Note that both BEH_WANDER and MTRAV_PATROL are necessary for it
        // to follow the given path and also not randomly wander off instead)
        beast->behaviour = BEH_WANDER;
        beast->foe = druid->foe;

        monster_pathfind mp;
        if (mp.init_pathfind(beast, target->pos()))
        {
            beast->travel_path = mp.calc_waypoints();
            if (!beast->travel_path.empty())
            {
                beast->target = beast->travel_path[0];
                beast->travel_target = MTRAV_PATROL;
            }
        }

        // Assign blame (for statistical purposes, mostly)
        mons_add_blame(beast, "called by " + druid->name(DESC_A));

        return true;
    }

    return false;
}

static void _cast_druids_call(const monster* mon)
{
    vector<monster*> mon_list;
    for (monster_iterator mi; mi; ++mi)
    {
        if (_valid_druids_call_target(mon, *mi))
            mon_list.push_back(*mi);
    }

    shuffle_array(mon_list);

    const actor* target = mon->get_foe();
    const int num = min((int)mon_list.size(),
                        mon->get_experience_level() > 10 ? random_range(2, 3)
                                                      : random_range(1, 2));

    for (int i = 0; i < num; ++i)
        _place_druids_call_beast(mon, mon_list[i], target);
}

static double _angle_between(coord_def origin, coord_def p1, coord_def p2)
{
    double ang0 = atan2(p1.x - origin.x, p1.y - origin.y);
    double ang  = atan2(p2.x - origin.x, p2.y - origin.y);
    return min(fabs(ang - ang0), fabs(ang - ang0 + 2 * PI));
}

// Does there already appear to be a bramble wall in this direction?
// We approximate this by seeing if there are at least two briar patches in
// a ray between us and our target, which turns out to be a pretty decent
// metric in practice.
static bool _already_bramble_wall(const monster* mons, coord_def targ)
{
    bolt tracer;
    tracer.source = mons->pos();
    tracer.target = targ;
    tracer.range = 12;
    tracer.is_tracer = true;
    tracer.is_beam = true;
    tracer.fire();

    int briar_count = 0;
    bool targ_reached = false;
    for (unsigned int i = 0; i < tracer.path_taken.size(); ++i)
    {
        coord_def p = tracer.path_taken[i];

        if (!targ_reached && p == targ)
            targ_reached = true;
        else if (!targ_reached)
            continue;

        if (monster_at(p) && monster_at(p)->type == MONS_BRIAR_PATCH)
            ++briar_count;
    }

    return briar_count > 1;
}

static bool _wall_of_brambles(monster* mons)
{
    mgen_data briar_mg = mgen_data(MONS_BRIAR_PATCH, SAME_ATTITUDE(mons),
                                   mons, 0, 0, coord_def(-1, -1), MHITNOT,
                                   MG_FORCE_PLACE);

    // We want to raise a defensive wall if we think our foe is moving to attack
    // us, and otherwise raise a wall further away to block off their escape.
    // (Each wall type uses different parameters)
    bool defensive = mons->props["foe_approaching"].get_bool();

    coord_def aim_pos = you.pos();
    coord_def targ_pos = mons->pos();

    // A defensive wall cannot provide any cover if our target is already
    // adjacent, so don't bother creating one.
    if (defensive && mons->pos().distance_from(aim_pos) == 1)
        return false;

    // Don't raise a non-defensive wall if it looks like there's an existing one
    // in the same direction already (this looks rather silly to see walls
    // springing up in the distance behind already-closed paths, and probably
    // is more likely to aid the player than the monster)
    if (!defensive)
    {
        if (_already_bramble_wall(mons, aim_pos))
            return false;
    }

    // Select a random radius for the circle used draw an arc from (affects
    // both shape and distance of the resulting wall)
    int rad = (defensive ? random_range(3, 5)
                         : min(11, mons->pos().distance_from(you.pos()) + 6));

    // Adjust the center of the circle used to draw the arc of the wall if
    // we're raising one defensively, based on both its radius and foe distance.
    // (The idea is the ensure that our foe will end up on the other side of it
    // without always raising the wall in exactly the same shape and position)
    if (defensive)
    {
        coord_def adjust = (targ_pos - aim_pos).sgn();

        targ_pos += adjust;
        if (rad == 5)
            targ_pos += adjust;
        if (mons->pos().distance_from(aim_pos) == 2)
            targ_pos += adjust;
    }

    // XXX: There is almost certainly a better way to calculate the points
    //      along the desired arcs, though this code produces the proper look.
    vector<coord_def> points;
    for (distance_iterator di(targ_pos, false, false, rad); di; ++di)
    {
        if (di.radius() == rad || di.radius() == rad - 1)
        {
            if (!actor_at(*di) && !cell_is_solid(*di))
            {
                if (defensive && _angle_between(targ_pos, aim_pos, *di) <= PI/4.0
                    || (!defensive
                        && _angle_between(targ_pos, aim_pos, *di) <= PI/(4.2 + rad/6.0)))
                {
                    points.push_back(*di);
                }
            }
        }
    }

    bool seen = false;
    for (unsigned int i = 0; i < points.size(); ++i)
    {
        briar_mg.pos = points[i];
        monster* briar = create_monster(briar_mg, false);
        if (briar)
        {
            briar->add_ench(mon_enchant(ENCH_SHORT_LIVED, 1, NULL, 80 + random2(100)));
            if (you.can_see(briar))
                seen = true;
        }
    }

    if (seen)
        mpr("Thorny briars emerge from the ground!");

    return true;
}

// Returns the clone just created (null otherwise)
monster* cast_phantom_mirror(monster* mons, monster* targ, int hp_perc, int summ_type)
{
    // Create clone.
    monster *mirror = clone_mons(targ, true);

    // Abort if we failed to place the monster for some reason.
    if (!mirror)
        return NULL;

    // Don't leak the real one with the targeting interface.
    if (you.prev_targ == mons->mindex())
    {
        you.prev_targ = MHITNOT;
        crawl_state.cancel_cmd_repeat();
    }
    mons->reset_client_id();

    mirror->mark_summoned(5, true, summ_type);
    mirror->add_ench(ENCH_PHANTOM_MIRROR);
    mirror->summoner = mons->mid;
    mirror->hit_points = mirror->hit_points * 100 / hp_perc;
    mirror->max_hit_points = mirror->max_hit_points * 100 / hp_perc;

    // Sometimes swap the two monsters, so as to disguise the original and the copy.
    if (coinflip())
    {
        // We can skip some habitability checks here, since the monsters are
        // known to be identical.
        coord_def p1 = targ->pos();
        coord_def p2 = mirror->pos();

        targ->set_position(p2);
        mirror->set_position(p1);

        mgrd(p1) = mirror->mindex();
        mgrd(p2) = targ->mindex();

        // Possibly constriction should be preserved so as to not sometimes leak
        // info on whether the clone or the original is adjacent to you, but
        // there's a fair amount of complication in this for very narrow benefit.
        targ->clear_far_constrictions();
        mirror->clear_far_constrictions();
    }

    return mirror;
}

static bool _trace_los(monster* agent, bool (*vulnerable)(actor*))
{
    bolt tracer;
    tracer.foe_ratio = 80;
    for (actor_near_iterator ai(agent, LOS_NO_TRANS); ai; ++ai)
    {
        if (agent == *ai || !agent->can_see(*ai) || !vulnerable(*ai))
            continue;

        if (mons_aligned(agent, *ai))
        {
            tracer.friend_info.count++;
            tracer.friend_info.power +=
                    ai->is_player() ? you.experience_level
                                    : ai->as_monster()->get_experience_level();
        }
        else
        {
            tracer.foe_info.count++;
            tracer.foe_info.power +=
                    ai->is_player() ? you.experience_level
                                    : ai->as_monster()->get_experience_level();
        }
    }
    return mons_should_fire(tracer);
}

static bool _tornado_vulnerable(actor* victim)
{
    return !victim->res_wind();
}

static bool _should_tornado(monster* agent)
{
    if (agent->has_ench(ENCH_TORNADO) || agent->has_ench(ENCH_TORNADO_COOLDOWN))
        return false;

    return _trace_los(agent, _tornado_vulnerable);
}

static bool _torment_vulnerable(actor* victim)
{
    if (victim->is_player())
        return !player_res_torment(true, false);

    return !victim->res_torment();
}

static bool _elec_vulnerable(actor* victim)
{
    return victim->res_elec() < 3;
}

static bool _dummy_vulnerable(actor* victim)
{
    return true;
}

static bool _should_ephemeral_infusion(monster* agent)
{
    if (agent->has_ench(ENCH_EPHEMERAL_INFUSION)
        || agent->hit_points == agent->max_hit_points)
    {
        return false;
    }

    for (actor_near_iterator ai(agent, LOS_NO_TRANS); ai; ++ai)
    {
        if (agent == *ai || !ai->visible_to(agent)
            || ai->is_player() || !mons_aligned(*ai, agent))
        {
            continue;
        }
        monster* mon = ai->as_monster();
        if (!mon->has_ench(ENCH_EPHEMERAL_INFUSION)
            && mon->hit_points * 3 <= mon->max_hit_points * 2)
        {
            return true;
        }
    }
    return agent->hit_points * 3 <= agent->max_hit_points;
}

static void _cast_ephemeral_infusion(monster* agent)
{
    for (actor_near_iterator ai(agent, LOS_NO_TRANS); ai; ++ai)
    {
        if (!ai->visible_to(agent)
            || ai->is_player()
            || !mons_aligned(*ai, agent))
        {
            continue;
        }
        monster* mon = ai->as_monster();
        if (!mon->has_ench(ENCH_EPHEMERAL_INFUSION)
            && mon->hit_points < mon->max_hit_points)
        {
            const int dur =
                random2avg(agent->spell_hd(SPELL_EPHEMERAL_INFUSION), 2)
                * BASELINE_DELAY;
            mon->add_ench(
                mon_enchant(ENCH_EPHEMERAL_INFUSION,
                            2 * agent->spell_hd(SPELL_EPHEMERAL_INFUSION),
                            agent, dur));
        }
    }
}

static void _cast_black_mark(monster* agent)
{
    for (actor_near_iterator ai(agent, LOS_NO_TRANS); ai; ++ai)
    {
        if (!ai->visible_to(agent)
            || ai->is_player()
            || !mons_aligned(*ai, agent))
        {
            continue;
        }
        monster* mon = ai->as_monster();
        if (!mon->has_ench(ENCH_BLACK_MARK))
        {
            mon->add_ench(ENCH_BLACK_MARK);
            simple_monster_message(mon, " begins absorbing vital energies!");
        }
    }
}

static bool _glaciate_tracer(monster *caster, int pow, coord_def aim)
{
    targetter_cone hitfunc(caster, spell_range(SPELL_GLACIATE, pow));
    hitfunc.set_aim(aim);

    mon_attitude_type castatt = caster->temp_attitude();
    int friendly = 0, enemy = 0;

    for (map<coord_def, aff_type>::const_iterator p = hitfunc.zapped.begin();
         p != hitfunc.zapped.end(); ++p)
    {
        if (p->second <= 0)
            continue;

        const actor *victim = actor_at(p->first);
        if (!victim)
            continue;

        if (mons_atts_aligned(castatt, victim->temp_attitude()))
            friendly += victim->get_experience_level();
        else
            enemy += victim->get_experience_level();
    }

    return enemy > friendly;
}

bool mons_should_cloud_cone(monster* agent, int power, const coord_def pos)
{
    targetter_shotgun hitfunc(agent,
                              spell_range(SPELL_CLOUD_CONE, power));

    hitfunc.set_aim(pos);

    bolt tracer;
    tracer.foe_ratio = 80;
    tracer.beam_source = agent->mindex();
    tracer.target = pos;
    for (actor_near_iterator ai(agent, LOS_NO_TRANS); ai; ++ai)
    {
        if (hitfunc.is_affected(ai->pos()) == AFF_NO || !agent->can_see(*ai))
            continue;

        if (mons_aligned(agent, *ai))
        {
            tracer.friend_info.count++;
            tracer.friend_info.power += ai->get_experience_level();
        }
        else
        {
            tracer.foe_info.count++;
            tracer.foe_info.power += ai->get_experience_level();
        }
    }

    return mons_should_fire(tracer);
}

static bool _spray_tracer(monster *caster, int pow, bolt parent_beam, spell_type spell)
{
    vector<bolt> beams = get_spray_rays(caster, parent_beam.target,
                                        spell_range(spell, pow), 3);
    if (beams.size() == 0)
        return false;

    bolt beam;

    for (unsigned int i = 0; i < beams.size(); ++i)
    {
        bolt_parent_init(&parent_beam, &(beams[i]));
        fire_tracer(caster, beams[i]);
        beam.friend_info += beams[i].friend_info;
        beam.foe_info    += beams[i].foe_info;
    }

    return mons_should_fire(beam);
}

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

    // A polymorphed unique will retain his or her spells even in another
    // form. If the new form has the SPELLCASTER flag, casting happens as
    // normally, otherwise we need to enforce it, but it only happens with
    // a 50% chance.
    const bool spellcasting_poly(
        !mons->can_use_spells()
        && mons_class_flag(mons->type, M_SPEAKS)
        && mons->has_spells());

    if (is_sanctuary(mons->pos()) && !mons->wont_attack())
        return false;

    // Yes, there is a logic to this ordering {dlb}:
    // .. berserk check is necessary for out-of-sequence actions like emergency
    // slot spells {blue}
    if (mons->asleep()
        || mons->submerged()
        || mons->berserk_or_insane()
        || (!mons->can_use_spells()
            && !spellcasting_poly
            && draco_breath == SPELL_NO_SPELL))
    {
        return false;
    }

    bool priest;
    bool wizard;
    god_type god;

    _mons_set_priest_wizard_god(mons, priest, wizard, god);

    if ((silenced(mons->pos()) || mons->has_ench(ENCH_MUTE)
         || (mons->has_ench(ENCH_WATER_HOLD) && !mons->res_water_drowning()))
        && (priest || wizard || spellcasting_poly
            || mons_class_flag(mons->type, M_SPELL_NO_SILENT)))
    {
        return false;
    }

    // Shapeshifters don't get spells.
    if (mons->is_shapeshifter() && (priest || wizard))
        return false;
    else if (mons_is_confused(mons, false))
        return false;
    else if (mons_is_ghost_demon(mons->type)
             && !mons->ghost->spellcaster)
    {
        return false;
    }
    // Servitors never fail to (try) to cast a spell
    else if (mons->type != MONS_SPELLFORGED_SERVITOR
             && random2(200) > mons->get_hit_dice() + 50)
    {
        return false;
    }
    else if (spellcasting_poly && coinflip()) // 50% chance of not casting
        return false;
    else
    {
        spell_type spell_cast = SPELL_NO_SPELL;
        monster_spells hspell_pass(mons->spells);

        if (!mon_enemies_around(mons))
        {
            // Force the casting of dig when the player is not visible -
            // this is EVIL!
            // only do this for monsters that are actually seeking out a
            // hostile target -doy
            if (mons->has_spell(SPELL_DIG)
                && mons_is_seeking(mons)
                && !(mons->wont_attack() && mons->foe == MHITYOU))
            {
                spell_cast = SPELL_DIG;
                finalAnswer = true;
            }
            else if ((mons->has_spell(SPELL_MINOR_HEALING)
                         || mons->has_spell(SPELL_MAJOR_HEALING))
                     && mons->hit_points < mons->max_hit_points)
            {
                // The player's out of sight!
                // Quick, let's take a turn to heal ourselves. -- bwr
                spell_cast = mons->has_spell(SPELL_MAJOR_HEALING) ?
                                 SPELL_MAJOR_HEALING : SPELL_MINOR_HEALING;
                finalAnswer = true;
            }
            else if (mons_is_fleeing(mons) || mons->pacified())
            {
                // 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(mons, hspell_pass[i])
                        && one_chance_in(++foundcount))
                    {
                        spell_cast = hspell_pass[i];
                        finalAnswer = true;
                    }
                }
            }
            else if (mons->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(mons)
            && mons->caught() && one_chance_in(4))
        {
            for (int i = 0; i < NUM_MONSTER_SPELL_SLOTS; ++i)
            {
                if (_ms_quick_get_away(mons, hspell_pass[i]))
                {
                    spell_cast = hspell_pass[i];
                    finalAnswer = true;
                    break;
                }
            }
        }

        // Promote the casting of useful spells for low-HP monsters.
        // (kraken should always cast their escape spell of inky).
        if (!finalAnswer
            && mons->hit_points < mons->max_hit_points / 3
            && (coinflip() || mons->type == MONS_KRAKEN))
        {
            // 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(mons) || mons->pacified())
                && _ms_low_hitpoint_cast(mons, 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(mons, 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() && !mon_enemies_around(mons)
                && !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(mons, hspell_pass[i])
                         || hspell_pass[i] == SPELL_DIG)
                {
                    // Instead of making a new one,
                    // make the weapon attack
                    if (hspell_pass[i] == SPELL_SPECTRAL_WEAPON)
                        hspell_pass[i] = SPELL_MELEE;

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

            const bolt orig_beem = beem;
            // Up to five tries to pick a spell,
            // with the last try being a self-enchantment.
            for (int loopy = 0; loopy < 5; ++loopy)
            {
                beem = orig_beem;

                bool spellOK = false;

                // Setup spell.
                // If we're in the last attempt, try the self-enchantment.
                if (loopy == 4 && coinflip())
                    spell_cast = hspell_pass[2];
                // Monsters that are fleeing or pacified and leaving the
                // level will always try to choose their emergency spell.
                else if (mons_is_fleeing(mons) || mons->pacified())
                {
                    spell_cast = (one_chance_in(5) ? SPELL_NO_SPELL
                                                   : hspell_pass[5]);

                    if (crawl_state.game_is_zotdef()
                        && mons->type == MONS_ICE_STATUE)
                    {
                        // Don't spam ice beasts when wounded.
                        spell_cast = SPELL_NO_SPELL;
                    }

                    // Pacified monsters leaving the level will only
                    // try and cast escape spells.
                    if (spell_cast != SPELL_NO_SPELL
                        && mons->pacified()
                        && !testbits(get_spell_flags(spell_cast), SPFLAG_ESCAPE))
                    {
                        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.
                if (spell_cast != SPELL_MELEE)
                    setup_mons_cast(mons, beem, spell_cast);

                // Try to find a nearby ally to haste, heal, might,
                // or make invisible.
                if ((spell_cast == SPELL_HASTE_OTHER
                     || spell_cast == SPELL_HEAL_OTHER
                     || spell_cast == SPELL_MIGHT_OTHER
                     || spell_cast == SPELL_INVISIBILITY_OTHER)
                        && !_set_allied_target(mons, beem,
                               mons->type == MONS_IRONBRAND_CONVOKER))
                {
                    spell_cast = SPELL_NO_SPELL;
                    continue;
                }

                // Alligators shouldn't spam swiftness.
                if (spell_cast == SPELL_SWIFTNESS
                    && mons->type == MONS_ALLIGATOR
                    && ((int) mons->number + random2avg(170, 5) >=
                        you.num_turns))
                {
                    spell_cast = SPELL_NO_SPELL;
                    continue;
                }

                // And Mara shouldn't cast player ghost if he can't
                // see the player
                if (spell_cast == SPELL_SUMMON_ILLUSION
                    && mons->type == MONS_MARA
                    && (!foe
                        || !mons->can_see(foe)
                        || !actor_is_illusion_cloneable(foe)))
                {
                    spell_cast = SPELL_NO_SPELL;
                    continue;
                }

                // Monsters are limited casting it, too.
                if (spell_cast == SPELL_MALIGN_GATEWAY
                    && !can_cast_malign_gateway())
                {
                    spell_cast = SPELL_NO_SPELL;
                    continue;
                }

                // Same limitations as player.
                if (spell_cast == SPELL_LEDAS_LIQUEFACTION
                    && (!mons->stand_on_solid_ground()
                        || liquefied(mons->pos())))
                {
                    spell_cast = SPELL_NO_SPELL;
                    continue;
                }

                // Don't torment your allies. Maybe we want to add other
                // spells here? Mass confusion, tornado, etc.?
                if (you.visible_to(mons) && mons_aligned(mons, &you))
                {
                    if ((spell_cast == SPELL_SYMBOL_OF_TORMENT
                             && !player_res_torment(false)
                             && !player_kiku_res_torment())
                         || (spell_cast == SPELL_OZOCUBUS_REFRIGERATION
                             && !player_res_cold(false)))
                    {
                        spell_cast = SPELL_NO_SPELL;
                        continue;
                    }
                }

                // Don't knockback something we're trying to constrict.
                const actor *victim = actor_at(beem.target);
                if (victim &&
                    beem.can_knockback(victim)
                    && mons->is_constricting()
                    && mons->constricting->count(victim->mid))
                {
                        spell_cast = SPELL_NO_SPELL;
                        continue;
                }

                // beam-type spells requiring tracers
                if (spell_needs_tracer(spell_cast))
                {
                    const bool explode =
                        spell_is_direct_explosion(spell_cast);
                    fire_tracer(mons, beem, explode);
                    // 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(mons, (mons->foe == MHITYOU) ?
                           &you : foe)) // foe=get_foe() is NULL for friendlies
                    {                   // targeting you, which is bad here.
                        spellOK = false;
                    }
                    else if (mons->foe == MHITYOU || mons->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 (!you.visible_to(mons)
                            && (mons->target != you.pos() || coinflip()))
                        {
                            spellOK = false;
                        }
                    }
                    else if (!mons->can_see(&menv[mons->foe]))
                        spellOK = false;
                    else if (mons->type == MONS_DAEVA
                             && mons->god == GOD_SHINING_ONE)
                    {
                        const monster* mon = &menv[mons->foe];

                        // Don't allow TSO-worshipping daevas to make
                        // unchivalric magic attacks, except against
                        // appropriate monsters.
                        if (find_stab_type(mons, mon) != STAB_NO_STAB
                            && !tso_unchivalric_attack_safe_monster(mon))
                        {
                            spellOK = false;
                        }
                    }
                }

                if (!spellOK)
                    spell_cast = SPELL_NO_SPELL;

                if (spell_cast != SPELL_NO_SPELL)
                    break;
            }
        }

        bool was_drac_breath = false;

        // 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))
            && !player_or_mon_in_sanct(mons)
            && !mons->has_ench(ENCH_BREATH_WEAPON))
        {
            spell_cast = draco_breath;
            setup_mons_cast(mons, beem, spell_cast);
            finalAnswer = true;
            was_drac_breath = true;
        }

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

        // Friendly monsters don't use polymorph, as it's likely to cause
        // runaway growth of an enemy.
        if (spell_cast == SPELL_POLYMORPH && mons->friendly())
            return false;

        // Past this point, we're actually casting, instead of just pondering.

        // Check for antimagic.
        if (mons->has_ench(ENCH_ANTIMAGIC)
            && !x_chance_in_y(4 * BASELINE_DELAY,
                              4 * BASELINE_DELAY
                              + mons->get_ench(ENCH_ANTIMAGIC).duration)
            && !_is_physiological_spell(spell_cast)
            && spell_cast != draco_breath)
        {
            // This may be a bad idea -- if we decide monsters shouldn't
            // lose a turn like players do not, please make this just return.
            simple_monster_message(mons, " falters for a moment.");
            mons->lose_energy(EUT_SPELL);
            return true;
        }
        // Try to animate dead: if nothing rises, pretend we didn't cast it.
        else if (spell_cast == SPELL_ANIMATE_DEAD)
        {
            if (mons->friendly() && !_animate_dead_okay(spell_cast))
                return false;

            if (mons->is_summoned())
                return false;

            if (!animate_dead(mons, 100, SAME_ATTITUDE(mons),
                              mons->foe, mons, "", god, false))
            {
                return false;
            }
        }
        // Ditto for crawling corpses.
        else if (spell_cast == SPELL_TWISTED_RESURRECTION)
        {
            if (mons->friendly() && !_animate_dead_okay(spell_cast))
                return false;

            if (mons->is_summoned())
                return false;

            if (!twisted_resurrection(mons, 500, SAME_ATTITUDE(mons),
                                      mons->foe, god, false))
            {
                return false;
            }
        }
        // Ditto for simulacra.
        else if (spell_cast == SPELL_SIMULACRUM)
        {
            if (mons->friendly() && !_animate_dead_okay(spell_cast))
                return false;

            if (!monster_simulacrum(mons, false))
                return false;
        }
        // Ditto for vines
        else if (spell_cast == SPELL_AWAKEN_VINES)
        {
            if (!_awaken_vines(mons, true))
                return false;
        }
        // Try to cause fear: if nothing is scared, pretend we didn't cast it.
        else if (spell_cast == SPELL_CAUSE_FEAR)
        {
            if (_mons_cause_fear(mons, false) < 0)
                return false;
        }
        // Check use of LOS attack spells.
        else if (spell_cast == SPELL_DRAIN_LIFE
                 || spell_cast == SPELL_OZOCUBUS_REFRIGERATION)
        {
            if (cast_los_attack_spell(spell_cast, mons->spell_hd(spell_cast),
                                      mons, false) != SPRET_SUCCESS)
            {
                return false;
            }
        }
        else if (spell_cast == SPELL_OLGREBS_TOXIC_RADIANCE)
        {
            if (cast_toxic_radiance(mons, 100, false, true) != SPRET_SUCCESS)
                return false;
        }
        else if (spell_cast == SPELL_IGNITE_POISON)
        {
            if (cast_ignite_poison(mons, 0, false, true) != SPRET_SUCCESS)
                return false;
        }
        // See if we have a good spot to cast LRD at.
        else if (spell_cast == SPELL_LRD)
        {
            if (!in_bounds(beem.target))
                return false;
        }
        // Try to cast Shatter; if nothing happened, pretend we didn't cast it.
        else if (spell_cast == SPELL_SHATTER)
        {
            if (!mons_shatter(mons, false))
                return false;
        }
        // Check if it's possible to spawn more tentacles. If not, don't bother trying.
        else if (spell_cast == SPELL_CREATE_TENTACLES)
        {
            if (!_mons_available_tentacles(mons))
                return false;
        }
        // Only cast word of recall if there's enough things to bother recalling
        else if (spell_cast == SPELL_WORD_OF_RECALL)
        {
            if (!_should_recall(mons))
                return false;
        }
        // Try to mass confuse; if we can't, pretend nothing happened.
        else if (spell_cast == SPELL_MASS_CONFUSION)
        {
            if (_mons_mass_confuse(mons, false) < 0)
                return false;
        }
        // Check if our enemy can be slowed for Metabolic Englaciation.
        else if (spell_cast == SPELL_ENGLACIATION)
        {
            if (!foe
                || !mons->see_cell_no_trans(mons->target)
                || foe->res_cold() > 0
                || (foe->is_monster()
                    && (mons_is_firewood(foe->as_monster())
                        || foe->as_monster()->has_ench(ENCH_SLOW)))
                || (!foe->is_monster()
                    && you.duration[DUR_SLOW] > 0))
            {
                return false;
            }
        }
        else if (spell_cast == SPELL_TORNADO)
        {
            if (!_should_tornado(mons))
                return false;
        }
        else if (spell_cast == SPELL_EPHEMERAL_INFUSION)
        {
            if (!_should_ephemeral_infusion(mons))
                return false;
        }
        else if (spell_cast == SPELL_GLACIATE)
        {
            if (!foe
                || !_glaciate_tracer(mons,
                                     12 * mons->spell_hd(spell_cast),
                                     foe->pos()))
            {
                return false;
            }
        }
        else if (spell_cast == SPELL_CLOUD_CONE)
        {
            if (!foe
                || !mons_should_cloud_cone(mons,
                                           12 * mons->spell_hd(spell_cast),
                                           foe->pos()))
            {
                return false;
            }
        }
        else if (spell_cast == SPELL_DAZZLING_SPRAY)
        {
            if (!foe
                || !_spray_tracer(mons, 12 * mons->spell_hd(spell_cast),
                                  beem, spell_cast))
            {
                return false;
            }
        }
        else if (spell_cast == SPELL_SYMBOL_OF_TORMENT)
        {
            if (!_trace_los(mons, _torment_vulnerable))
                return false;
        }
        else if (spell_cast == SPELL_CHAIN_LIGHTNING)
        {
            if (!_trace_los(mons, _elec_vulnerable))
                return false;
        }
        else if (spell_cast == SPELL_CHAIN_OF_CHAOS)
        {
            if (!_trace_los(mons, _dummy_vulnerable))
                return false;
        }

        if (mons->type == MONS_BALL_LIGHTNING)
            mons->suicide();

        // Dragons now have a time-out on their breath weapons, draconians too!
        if (_is_breath_caster(mons->type)
            || mons_genus(mons->type) == MONS_DRACONIAN && was_drac_breath)
        {
            setup_breath_timeout(mons);
        }

        // FINALLY! determine primary spell effects {dlb}:
        if (spell_cast == SPELL_BLINK || spell_cast == SPELL_CONTROLLED_BLINK)
        {
            // Why only cast blink if nearby? {dlb}
            if (monsterNearby)
            {
                mons_cast_noise(mons, beem, spell_cast);
                monster_blink(mons);

                mons->lose_energy(EUT_SPELL);
            }
            else
                return false;
        }
        else if (spell_cast == SPELL_BLINK_RANGE)
        {
            blink_range(mons);
            mons->lose_energy(EUT_SPELL);
        }
        else if (spell_cast == SPELL_BLINK_AWAY)
        {
            blink_away(mons, true);
            mons->lose_energy(EUT_SPELL);
        }
        else if (spell_cast == SPELL_BLINK_CLOSE)
        {
            blink_close(mons);
            mons->lose_energy(EUT_SPELL);
        }
        else
        {
            const int orig_hp = (foe) ? foe->stat_hp() : 0;
            const bool battlesphere = mons->props.exists("battlesphere");
            if (spell_needs_foe(spell_cast))
                make_mons_stop_fleeing(mons);

            if (battlesphere)
                aim_battlesphere(mons, spell_cast, beem.ench_power, beem);
            mons_cast(mons, beem, spell_cast);
            if (battlesphere)
                trigger_battlesphere(mons, beem);
            if (mons->has_ench(ENCH_GRAND_AVATAR))
                trigger_grand_avatar(mons, foe, spell_cast, orig_hp);
            if (wizard && mons->has_ench(ENCH_SAP_MAGIC))
            {
                mons->add_ench(mon_enchant(ENCH_ANTIMAGIC, 0,
                                           mons->get_ench(ENCH_SAP_MAGIC)
                                                 .agent(),
                                           BASELINE_DELAY
                                           * spell_difficulty(spell_cast)));
            }
            mons->lose_energy(EUT_SPELL);
        }
    } // end "if (mons->can_use_spells())"

    return true;
}

static int _monster_abjure_target(monster* target, int pow, bool actual)
{
    int duration;

    if (!target->is_summoned(&duration))
        return 0;

    pow = max(20, fuzz_value(pow, 40, 25));

    if (!actual)
        return pow > 40 || pow >= duration;

    // TSO and Trog's abjuration protection.
    bool shielded = false;
    if (you_worship(GOD_SHINING_ONE))
    {
        pow = pow * (30 - target->get_hit_dice()) / 30;
        if (pow < duration)
        {
            simple_god_message(" protects your fellow warrior from evil "
                               "magic!");
            shielded = true;
        }
    }
    else if (you_worship(GOD_TROG))
    {
        pow = pow / 2;
        if (pow < duration)
        {
            simple_god_message(" shields your ally from puny magic!");
            shielded = true;
        }
    }
    else if (is_sanctuary(target->pos()))
    {
        pow = 0;
        mprf(MSGCH_GOD, "Zin's power protects your fellow warrior from evil magic!");
        shielded = true;
    }

    dprf("Abj: dur: %d, pow: %d, ndur: %d", duration, pow, duration - pow);

    mon_enchant abj = target->get_ench(ENCH_ABJ);
    if (!target->lose_ench_duration(abj, pow))
    {
        if (!shielded)
            simple_monster_message(target, " shudders.");
        return 1;
    }

    return 0;
}

static int _monster_abjuration(const monster* caster, bool actual)
{
    int maffected = 0;

    if (actual)
        mpr("Send 'em back where they came from!");

    int pow = caster->spell_hd(SPELL_ABJURATION) * 20;

    for (monster_near_iterator mi(caster->pos(), LOS_NO_TRANS); mi; ++mi)
    {
        if (!mons_aligned(caster, *mi))
            maffected += _monster_abjure_target(*mi, pow, actual);
    }

    return maffected;
}

static bool _mons_abjured(monster* mons, bool nearby)
{
    if (nearby && _monster_abjuration(mons, false) > 0
        && one_chance_in(3))
    {
        _monster_abjuration(mons, true);
        return true;
    }

    return false;
}

static monster_type _pick_swarmer()
{
    static monster_type swarmers[] =
    {
        MONS_KILLER_BEE,     MONS_KILLER_BEE,    MONS_KILLER_BEE,
        MONS_SCORPION,       MONS_WORM,          MONS_VAMPIRE_MOSQUITO,
        MONS_GOLIATH_BEETLE, MONS_SPIDER,        MONS_BUTTERFLY,
        MONS_YELLOW_WASP,    MONS_WORKER_ANT,    MONS_WORKER_ANT,
        MONS_WORKER_ANT
    };

    return RANDOM_ELEMENT(swarmers);
}

static monster_type _pick_random_wraith()
{
    static monster_type wraiths[] =
    {
        MONS_WRAITH, MONS_SHADOW_WRAITH, MONS_FREEZING_WRAITH,
        MONS_PHANTASMAL_WARRIOR, MONS_PHANTOM, MONS_HUNGRY_GHOST
    };

    return RANDOM_ELEMENT(wraiths);
}

static void _haunt_fixup(monster* summon, coord_def pos)
{
    actor* victim = actor_at(pos);
    if (victim && victim != summon)
    {
        summon->add_ench(mon_enchant(ENCH_HAUNTING, 1, victim,
                                     INFINITE_DURATION));
        summon->foe = victim->mindex();
    }
}

static monster_type _pick_horrible_thing()
{
    return one_chance_in(4) ? MONS_TENTACLED_MONSTROSITY
                            : MONS_ABOMINATION_LARGE;
}

static monster_type _pick_undead_summon()
{
    static monster_type undead[] =
    {
        MONS_NECROPHAGE, MONS_JIANGSHI, MONS_HUNGRY_GHOST, MONS_FLAYED_GHOST,
        MONS_ZOMBIE, MONS_SKELETON, MONS_SIMULACRUM, MONS_SPECTRAL_THING,
        MONS_FLYING_SKULL, MONS_MUMMY, MONS_VAMPIRE, MONS_WIGHT, MONS_WRAITH,
        MONS_SHADOW_WRAITH, MONS_FREEZING_WRAITH, MONS_PHANTASMAL_WARRIOR, MONS_SHADOW
    };

    return RANDOM_ELEMENT(undead);
}

static monster_type _pick_vermin()
{
    return random_choose_weighted(8, MONS_ORANGE_RAT,
                                  5, MONS_REDBACK,
                                  2, MONS_TARANTELLA,
                                  2, MONS_JUMPING_SPIDER,
                                  3, MONS_DEMONIC_CRAWLER,
                                  0);
}

static void _do_high_level_summon(monster* mons, bool monsterNearby,
                                  spell_type spell_cast,
                                  monster_type (*mpicker)(), int nsummons,
                                  god_type god, coord_def *target = NULL,
                                  void (*post_hook)(monster*, coord_def) = NULL,
                                  bool allow_abjure = true)
{
    if (allow_abjure && _mons_abjured(mons, monsterNearby))
        return;

    const int duration = min(2 + mons->spell_hd(spell_cast) / 5, 6);

    for (int i = 0; i < nsummons; ++i)
    {
        monster_type which_mons = mpicker();

        if (which_mons == MONS_NO_MONSTER)
            continue;

        monster* summon = create_monster(
            mgen_data(which_mons, SAME_ATTITUDE(mons), mons,
                      duration, spell_cast, target ? *target : mons->pos(),
                      mons->foe, 0, god));
        if (summon && post_hook)
            post_hook(summon, target ? *target : mons->pos());
    }
}

static void _mons_summon_elemental(monster* mons,
                                   bool monsterNearby,
                                   spell_type spell_cast,
                                   monster_type elemental,
                                   god_type god)
{
    if (_mons_abjured(mons, monsterNearby))
        return;

    const int count = 1 + (mons->spell_hd(spell_cast) > 15)
                      + random2(mons->spell_hd(spell_cast) / 7 + 1);

    for (int i = 0; i < count; i++)
    {
        create_monster(
            mgen_data(elemental, SAME_ATTITUDE(mons), mons,
                      3, spell_cast, mons->pos(), mons->foe, 0, god));
    }
    return;
}

// Returns true if a message referring to the player's legs makes sense.
static bool _legs_msg_applicable()
{
    return you.species != SP_NAGA && !you.fishtail;
}

void mons_cast_haunt(monster* mons)
{
    coord_def fpos;

    switch (mons->foe)
    {
    case MHITNOT:
        return;

    case MHITYOU:
        fpos = you.pos();
        break;

    default:
        fpos = menv[mons->foe].pos();
    }

    _do_high_level_summon(mons, mons_near(mons), SPELL_HAUNT,
                          _pick_random_wraith, random_range(2, 4),
                          GOD_NO_GOD, &fpos, _haunt_fixup);
}

static void _mons_cast_summon_illusion(monster* mons, spell_type spell)
{
    actor *foe = mons->get_foe();
    if (!foe || !actor_is_illusion_cloneable(foe))
        return;

    mons_summon_illusion_from(mons, foe, spell);
}

void mons_cast_spectral_orcs(monster* mons)
{
    coord_def fpos;

    switch (mons->foe)
    {
    case MHITNOT:
        return;

    case MHITYOU:
        fpos = you.pos();
        break;

    default:
        fpos = menv[mons->foe].pos();
    }

    const int abj = 3;

    for (int i = random2(3) + 1; i > 0; --i)
    {
        monster_type mon = MONS_ORC;
        if (coinflip())
            mon = MONS_ORC_WARRIOR;
        else if (one_chance_in(3))
            mon = MONS_ORC_KNIGHT;
        else if (one_chance_in(10))
            mon = MONS_ORC_WARLORD;

        // Use the original monster type as the zombified type here, to
        // get the proper stats from it.
        if (monster *orc = create_monster(
                  mgen_data(MONS_SPECTRAL_THING, SAME_ATTITUDE(mons), mons,
                          abj, SPELL_SUMMON_SPECTRAL_ORCS, fpos, mons->foe,
                          0, mons->god, mon)))
        {
            // set which base type this orc is pretending to be for gear
            // purposes
            if (mon != MONS_ORC)
            {
                orc->mname = mons_type_name(mon, DESC_PLAIN);
                orc->flags |= MF_NAME_REPLACE | MF_NAME_DESCRIPTOR;
            }
            orc->number = (int) mon;

            // give gear using the base type
            give_item(orc, env.absdepth0, true, true);

            // set gear as summoned
            orc->mark_summoned(abj, true, SPELL_SUMMON_SPECTRAL_ORCS);
        }
    }
}

static bool _mons_vampiric_drain(monster *mons)
{
    actor *target = mons->get_foe();
    if (!target)
        return false;
    if (grid_distance(mons->pos(), target->pos()) > 1)
        return false;

    int pow = mons->spell_hd(SPELL_VAMPIRIC_DRAINING) * 12;
    int hp_cost = 3 + random2avg(9, 2) + 1 + random2(pow) / 7;

    hp_cost = min(hp_cost, target->stat_hp());
    hp_cost = min(hp_cost, mons->max_hit_points - mons->hit_points);
    if (target->res_negative_energy() > 0)
        hp_cost -= hp_cost * target->res_negative_energy() / 3;

    if (!hp_cost)
    {
        simple_monster_message(mons,
                               " is infused with unholy energy, but nothing happens.",
                               MSGCH_MONSTER_SPELL);
        return false;
    }

    dprf("vamp draining: %d damage, %d healing", hp_cost, hp_cost/2);

    if (you.can_see(mons))
    {
        simple_monster_message(mons,
                               " is infused with unholy energy.",
                               MSGCH_MONSTER_SPELL);
    }
    else
        mpr("Unholy energy fills the air.");

    if (target->is_player())
    {
        ouch(hp_cost, mons->mindex(), KILLED_BY_BEAM, "by vampiric draining");
        if (mons->heal(hp_cost * 2 / 3))
        {
            simple_monster_message(mons,
                " draws life force from you and is healed!");
        }
    }
    else
    {
        monster* mtarget = target->as_monster();
        const string targname = mtarget->name(DESC_THE);
        mtarget->hurt(mons, hp_cost);
        if (mons->heal(hp_cost * 2 / 3))
        {
            simple_monster_message(mons,
                make_stringf(" draws life force from %s and is healed!",
                targname.c_str()).c_str());
        }
        if (mtarget->alive())
            print_wounds(mtarget);
    }

    return true;
}

static bool _mons_cast_freeze(monster* mons)
{
    actor *target = mons->get_foe();
    if (!target)
        return false;
    if (grid_distance(mons->pos(), target->pos()) > 1)
        return false;

    const int pow = mons->spell_hd(SPELL_FREEZE) * 6;

    const int base_damage = roll_dice(1, 3 + pow / 3);
    int damage = 0;

    if (target->is_player())
    {
        damage = resist_adjust_damage(&you, BEAM_COLD, player_res_cold(),
                                      base_damage, true);
    }
    else
    {
        bolt beam;
        beam.flavour = BEAM_COLD;
        damage = mons_adjust_flavoured(mons, beam, base_damage);
    }

    if (you.can_see(target))
    {
        mprf("%s %s frozen.", target->name(DESC_THE).c_str(),
                              target->conj_verb("are").c_str());
    }

    target->hurt(mons, damage);

    if (target->alive())
    {
        target->expose_to_element(BEAM_COLD, damage);

        if (target->is_monster() && target->res_cold() <= 0)
        {
            const int stun = (1 - target->res_cold()) * random2(min(7, 2 + pow/12));
            target->as_monster()->speed_increment -= stun;
        }
    }

    return true;
}

void setup_breath_timeout(monster* mons)
{
    ASSERT(mons_genus(mons->type) == MONS_DRAGON
           || mons_genus(mons->type) == MONS_DRAKE
           || mons_genus(mons->type) == MONS_DRACONIAN);

    if (mons->has_ench(ENCH_BREATH_WEAPON))
        return;

    int timeout = roll_dice(1, 5);

    dprf("dragon/draconian breath timeout %d", timeout);

    mon_enchant breath_timeout = mon_enchant(ENCH_BREATH_WEAPON, 1, mons, timeout*10);
    mons->add_ench(breath_timeout);
}

/**
 * Maybe mesmerise the player.
 *
 * This function decides whether or not it is possible for the player to become
 * mesmerised by mons. It will return a variety of values depending on whether
 * or not this can succeed or has succeeded; finally, it will add mons to the
 * player's list of beholders.
 *
 * @param mons      The monster doing the mesmerisation.
 * @param actual    Whether or not we are actually casting the spell. If false,
 *                  no messages are emitted.
 * @return          0 if the player could be mesmerised but wasn't, 1 if the
 *                  player was mesmerised, -1 if the player couldn't be
 *                  mesmerised.
**/
static int _mons_mesmerise(monster* mons, bool actual)
{
    bool already_mesmerised = you.beheld_by(mons);

    if (!you.visible_to(mons)             // Don't mesmerise while invisible.
        || (!you.can_see(mons)            // Or if we are, and you're aren't
            && !already_mesmerised)       // already mesmerised by us.
        || !player_can_hear(mons->pos())  // Or if you're silenced, or we are.
        || you.berserk()                  // Or if you're berserk.
        || mons->has_ench(ENCH_CONFUSION) // Or we're confused,
        || mons_is_fleeing(mons)          // fleeing,
        || mons->pacified()               // pacified,
        || mons->friendly())              // or friendly!
    {
        return -1;
    }

    if (actual)
    {
        if (!already_mesmerised)
        {
            simple_monster_message(mons, " attempts to bespell you!");
            flash_view(LIGHTMAGENTA);
        }
        else
        {
            mprf("%s draws you further into %s thrall.",
                    mons->name(DESC_THE).c_str(),
                    mons->pronoun(PRONOUN_POSSESSIVE).c_str());
        }
    }

    const int pow = min(mons->spell_hd(SPELL_MESMERISE) * 10, 200);

    // Don't mesmerise if you pass an MR check or have clarity.
    // If you're already mesmerised, you cannot resist further.
    if ((you.check_res_magic(pow) > 0 || you.clarity()
         || you.duration[DUR_MESMERISE_IMMUNE]) && !already_mesmerised)
    {
        if (actual)
        {
            if (you.clarity())
                canned_msg(MSG_YOU_UNAFFECTED);
            else
                canned_msg(MSG_YOU_RESIST);
        }

        return 0;
    }

    you.add_beholder(mons);

    return 1;
}

// Check whether targets might be scared.
// Returns 0, if targets can be scared but the attempt failed or wasn't made.
// Returns 1, if targets are scared.
// Returns -1, if targets can never be scared.
static int _mons_cause_fear(monster* mons, bool actual)
{
    if (actual)
    {
        if (you.can_see(mons))
            simple_monster_message(mons, " radiates an aura of fear!");
        else if (you.see_cell(mons->pos()))
            mpr("An aura of fear fills the air!");
    }

    int retval = -1;

    const int pow = min(mons->spell_hd(SPELL_CAUSE_FEAR) * 18, 200);

    if (mons->can_see(&you) && !mons->wont_attack() && !you.afraid_of(mons))
    {
        if (you.holiness() != MH_NATURAL)
        {
            if (actual)
                canned_msg(MSG_YOU_UNAFFECTED);
        }
        else if (you.check_res_magic(pow) > 0)
        {
            if (actual)
                canned_msg(MSG_YOU_RESIST);
        }
        else if (actual && you.add_fearmonger(mons))
        {
            retval = 1;

            you.increase_duration(DUR_AFRAID, 10 + random2avg(pow / 10, 4));

            if (!mons->has_ench(ENCH_FEAR_INSPIRING))
                mons->add_ench(ENCH_FEAR_INSPIRING);
        }
        else
          retval = 0;
    }

    for (monster_near_iterator mi(mons->pos()); mi; ++mi)
    {
        if (*mi == mons)
            continue;

        // Magic-immune, unnatural and "firewood" monsters are
        // immune to being scared. Same-aligned monsters are
        // never affected, even though they aren't immune.
        // Will not further scare a monster that is already afraid.
        if (mons_immune_magic(*mi)
            || mi->holiness() != MH_NATURAL
            || mons_is_firewood(*mi)
            || mons_atts_aligned(mi->attitude, mons->attitude)
            || mi->has_ench(ENCH_FEAR))
        {
            continue;
        }

        retval = 0;

        // It's possible to scare this monster. If its magic
        // resistance fails, do so.
        int res_margin = mi->check_res_magic(pow);
        if (res_margin > 0)
        {
            if (actual)
                simple_monster_message(*mi, mons_resist_string(*mi, res_margin));
            continue;
        }

        if (actual
            && mi->add_ench(mon_enchant(ENCH_FEAR, 0, mons)))
        {
            retval = 1;

            if (you.can_see(*mi))
                simple_monster_message(*mi, " looks frightened!");

            behaviour_event(*mi, ME_SCARE, mons);

            if (!mons->has_ench(ENCH_FEAR_INSPIRING))
                mons->add_ench(ENCH_FEAR_INSPIRING);
        }
    }

    if (actual && retval == 1 && you.see_cell(mons->pos()))
        flash_view_delay(DARKGREY, 300);

    return retval;
}

static int _mons_mass_confuse(monster* mons, bool actual)
{
    int retval = -1;

    const int pow = min(mons->spell_hd(SPELL_MASS_CONFUSION) * 8, 200);

    if (mons->can_see(&you) && !mons->wont_attack())
    {
        retval = 0;

        if (actual)
            if (you.check_res_magic(pow) > 0)
                canned_msg(MSG_YOU_RESIST);
            else
            {
                you.confuse(mons, 2 + random2(5));
                retval = 1;
            }
    }

    for (monster_near_iterator mi(mons->pos()); mi; ++mi)
    {
        if (*mi == mons)
            continue;

        if (mons_immune_magic(*mi)
            || mons_is_firewood(*mi)
            || mons_atts_aligned(mi->attitude, mons->attitude))
        {
            continue;
        }

        retval = 0;

        int res_margin = mi->check_res_magic(pow);
        if (res_margin > 0)
        {
            if (actual)
                simple_monster_message(*mi, mons_resist_string(*mi, res_margin));
            continue;
        }
        if (actual)
        {
            retval = 1;
            mi->confuse(mons, 2 + random2(5));
        }
    }

    return retval;
}

static coord_def _mons_fragment_target(monster *mons)
{
    coord_def target(GXM+1, GYM+1);
    int pow = 6 * mons->spell_hd(SPELL_LRD);

    // Shadow casting should try to affect the same tile as the player.
    if (mons_is_player_shadow(mons))
    {
        bool temp;
        bolt beam;
        if (!setup_fragmentation_beam(beam, pow, mons, mons->target, false,
                                      true, true, NULL, temp, temp))
        {
            return target;
        }
        return mons->target;
    }

    int range = spell_range(SPELL_LRD, pow, false);
    int maxpower = 0;
    for (distance_iterator di(mons->pos(), true, true, range); di; ++di)
    {
        bool temp;

        if (!cell_see_cell(mons->pos(), *di, LOS_SOLID))
            continue;

        bolt beam;
        if (!setup_fragmentation_beam(beam, pow, mons, *di, false, true, true,
                                      NULL, temp, temp))
        {
            continue;
        }

        beam.range = range;
        fire_tracer(mons, beam, true);
        if (!mons_should_fire(beam))
            continue;

        bolt beam2;
        if (!setup_fragmentation_beam(beam2, pow, mons, *di, false, false, true,
                                      NULL, temp, temp))
        {
            continue;
        }

        beam2.range = range;
        fire_tracer(mons, beam2, true);

        if (beam2.foe_info.count > 0
            && beam2.foe_info.power > maxpower)
        {
            maxpower = beam2.foe_info.power;
            target = *di;
        }
    }

    return target;
}

static void _blink_allies_encircle(const monster* mon)
{
    vector<monster*> allies;
    const coord_def foepos = mon->get_foe()->pos();

    for (monster_near_iterator mi(mon, LOS_NO_TRANS); mi; ++mi)
    {
        if (_valid_encircle_ally(mon, *mi, foepos))
            allies.push_back(*mi);
    }
    shuffle_array(allies);

    int count = max(1, mon->spell_hd(SPELL_BLINK_ALLIES_ENCIRCLE) / 8
                       + random2(mon->spell_hd(SPELL_BLINK_ALLIES_ENCIRCLE) / 4));

    for (unsigned int i = 0; i < allies.size() && count; ++i)
    {
        coord_def empty;
        if (find_habitable_spot_near(foepos, mons_base_type(allies[i]), 1, false, empty))
        {
            if (allies[i]->blink_to(empty))
            {
                // XXX: This seems an awkward way to give a message for something
                // blinking from out of sight into sight. Probably could use a
                // more general solution.
                if (!(allies[i]->flags & MF_WAS_IN_VIEW)
                    && allies[i]->flags & MF_SEEN)
                {
                    simple_monster_message(allies[i], " blinks into view!");
                }
                allies[i]->behaviour = BEH_SEEK;
                allies[i]->foe = mon->foe;
                count--;
            }
        }
    }
}

static void _blink_allies_away(const monster* mon)
{
    vector<monster*> allies;
    const coord_def foepos = mon->get_foe()->pos();

    for (monster_near_iterator mi(mon, LOS_NO_TRANS); mi; ++mi)
    {
        if (_valid_blink_away_ally(mon, *mi, foepos))
            allies.push_back(*mi);
    }
    shuffle_array(allies);

    int count = max(1, mon->spell_hd(SPELL_BLINK_ALLIES_AWAY) / 8
                       + random2(mon->spell_hd(SPELL_BLINK_ALLIES_AWAY) / 4));

    for (unsigned int i = 0; i < allies.size() && count; ++i)
    {
        if (blink_away(allies[i], &you, false))
            count--;
    }
}

static int _max_tentacles(const monster* mon)
{
    if (mons_base_type(mon) == MONS_KRAKEN)
        return MAX_ACTIVE_KRAKEN_TENTACLES;
    else if (mon->type == MONS_TENTACLED_STARSPAWN)
        return MAX_ACTIVE_STARSPAWN_TENTACLES;
    else
        return 0;
}

static int _mons_available_tentacles(monster* head)
{
    int tentacle_count = 0;

    for (monster_iterator mi; mi; ++mi)
    {
        if (mi->is_child_tentacle_of(head))
            tentacle_count++;
    }

    return _max_tentacles(head) - tentacle_count;
}

static void _mons_create_tentacles(monster* head)
{
    int head_index = head->mindex();
    if (invalid_monster_index(head_index))
    {
        mprf(MSGCH_ERROR, "Error! Tentacle head is not a part of the current environment!");
        return;
    }

    int possible_count = _mons_available_tentacles(head);

    if (possible_count <= 0)
        return;

    monster_type tent_type = mons_tentacle_child_type(head);

    vector<coord_def> adj_squares;

    // Collect open adjacent squares. Candidate squares must be
    // unoccupied.
    for (adjacent_iterator adj_it(head->pos()); adj_it; ++adj_it)
    {
        if (in_bounds(*adj_it) && !monster_at(*adj_it))
            adj_squares.push_back(*adj_it);
    }

    if (unsigned(possible_count) > adj_squares.size())
        possible_count = adj_squares.size();
    else if (adj_squares.size() > unsigned(possible_count))
        shuffle_array(adj_squares);

    int visible_count = 0;

    for (int i = 0 ; i < possible_count; ++i)
    {
        if (monster *tentacle = create_monster(
            mgen_data(tent_type, SAME_ATTITUDE(head), head,
                        0, 0, adj_squares[i], head->foe,
                        MG_FORCE_PLACE, head->god, MONS_NO_MONSTER, head_index,
                        head->colour, PROX_CLOSE_TO_PLAYER)))
        {
            if (you.can_see(tentacle))
                visible_count++;

            tentacle->props["inwards"].get_int() = head_index;

            if (head->holiness() == MH_UNDEAD)
                tentacle->flags |= MF_FAKE_UNDEAD;
        }
    }

    if (mons_base_type(head) == MONS_KRAKEN)
    {
        if (visible_count == 1)
            mpr("A tentacle rises from the water!");
        else if (visible_count > 1)
            mpr("Tentacles burst out of the water!");
    }
    else if (head->type == MONS_TENTACLED_STARSPAWN)
    {
        if (visible_count == 1)
            mpr("A tentacle flies out from the starspawn's body!");
        else if (visible_count > 1)
            mpr("Tentacles burst from the starspawn's body!");
    }

    return;
}

static bool _mon_spell_bail_out_early(monster* mons, spell_type spell_cast)
{
    switch (spell_cast)
    {
    case SPELL_ANIMATE_DEAD:
    case SPELL_TWISTED_RESURRECTION:
    case SPELL_SIMULACRUM:
        if (mons->friendly() && !_animate_dead_okay(spell_cast))
            return true;
        break;

    default:
        break;
    }

    return false;
}

struct branch_summon_pair
{
    branch_type     origin;
    const pop_entry *pop;
};

static const pop_entry _invitation_lair[] =
{ // Lair enemies
  {  1,   1,   80, FLAT, MONS_YAK },
  {  1,   1,   60, FLAT, MONS_BLINK_FROG },
  {  1,   1,   40, FLAT, MONS_ELEPHANT },
  {  1,   1,   20, FLAT, MONS_SPINY_FROG },
  { 0,0,0,FLAT,MONS_0 }
};

static const pop_entry _invitation_snake[] =
{ // Snake enemies
  {  1,   1,   90, FLAT, MONS_NAGA },
  {  1,   1,   70, FLAT, MONS_WATER_MOCCASIN },
  {  1,   1,   30, FLAT, MONS_BLACK_MAMBA },
  {  1,   1,   10, FLAT, MONS_GUARDIAN_SERPENT },
  { 0,0,0,FLAT,MONS_0 }
};

static const pop_entry _invitation_spider[] =
{ // Spider enemies
  {  1,   1,   80, FLAT, MONS_SPIDER },
  {  1,   1,   60, FLAT, MONS_JUMPING_SPIDER },
  {  1,   1,   40, FLAT, MONS_BORING_BEETLE },
  {  1,   1,   20, FLAT, MONS_ORB_SPIDER },
  { 0,0,0,FLAT,MONS_0 }
};

static const pop_entry _invitation_swamp[] =
{ // Swamp enemies
  {  1,   1,   60, FLAT, MONS_RAVEN },
  {  1,   1,   60, FLAT, MONS_SWAMP_DRAKE },
  {  1,   1,   40, FLAT, MONS_VAMPIRE_MOSQUITO },
  {  1,   1,   40, FLAT, MONS_INSUBSTANTIAL_WISP },
  {  1,   1,   20, FLAT, MONS_BABY_ALLIGATOR },
  { 0,0,0,FLAT,MONS_0 }
};

static const pop_entry _invitation_shoals[] =
{ // Swamp enemies
  {  1,   1,   80, FLAT, MONS_MERFOLK },
  {  1,   1,   60, FLAT, MONS_MERMAID },
  {  1,   1,   40, FLAT, MONS_MANTICORE },
  {  1,   1,   20, FLAT, MONS_SNAPPING_TURTLE },
  { 0,0,0,FLAT,MONS_0 }
};

static const pop_entry _invitation_orc[] =
{ // Orc enemies
  {  1,   1,   90, FLAT, MONS_ORC_PRIEST },
  {  1,   1,   70, FLAT, MONS_ORC_WARRIOR },
  {  1,   1,   30, FLAT, MONS_WARG },
  {  1,   1,   10, FLAT, MONS_TROLL },
  { 0,0,0,FLAT,MONS_0 }
};

static const pop_entry _invitation_elf[] =
{ // Elf enemies
  {  1,   1,   90, FLAT, MONS_DEEP_ELF_FIGHTER },
  {  1,   1,   70, FLAT, MONS_DEEP_ELF_PRIEST },
  {  1,   1,   40, FLAT, MONS_DEEP_ELF_MAGE },
  { 0,0,0,FLAT,MONS_0 }
};

static const pop_entry _invitation_vaults[] =
{ // Vaults enemies
  {  1,   1,   80, FLAT, MONS_HUMAN },
  {  1,   1,   60, FLAT, MONS_YAKTAUR },
  {  1,   1,   40, FLAT, MONS_IRONHEART_PRESERVER },
  {  1,   1,   20, FLAT, MONS_VAULT_SENTINEL },
  { 0,0,0,FLAT,MONS_0 }
};

static const pop_entry _invitation_crypt[] =
{ // Crypt enemies
  {  1,   1,   80, FLAT, MONS_WRAITH },
  {  1,   1,   60, FLAT, MONS_SHADOW },
  {  1,   1,   40, FLAT, MONS_VAMPIRE },
  {  1,   1,   20, FLAT, MONS_SHADOW_WRAITH },
  { 0,0,0,FLAT,MONS_0 }
};

static branch_summon_pair _invitation_summons[] =
{
  { BRANCH_LAIR,   _invitation_lair },
  { BRANCH_SNAKE,  _invitation_snake },
  { BRANCH_SPIDER, _invitation_spider },
  { BRANCH_SWAMP,  _invitation_swamp },
  { BRANCH_SHOALS, _invitation_shoals },
  { BRANCH_ORC,    _invitation_orc },
  { BRANCH_ELF,    _invitation_elf },
  { BRANCH_VAULTS, _invitation_vaults },
  { BRANCH_CRYPT,  _invitation_crypt }
};

static const pop_entry _planerend_lair[] =
{ // Lair enemies
  {  1,   1,  100, FLAT, MONS_CATOBLEPAS },
  {  1,   1,  100, FLAT, MONS_DIRE_ELEPHANT },
  {  1,   1,   60, FLAT, MONS_TORPOR_SNAIL },
  { 0,0,0,FLAT,MONS_0 }
};

static const pop_entry _planerend_snake[] =
{ // Snake enemies
  {  1,   1,   40, FLAT, MONS_ANACONDA },
  {  1,   1,   60, FLAT, MONS_SALAMANDER_FIREBRAND },
  {  1,   1,  100, FLAT, MONS_GUARDIAN_SERPENT },
  {  1,   1,  100, FLAT, MONS_GREATER_NAGA },
  { 0,0,0,FLAT,MONS_0 }
};

static const pop_entry _planerend_spider[] =
{ // Spider enemies
  {  1,   1,  100, FLAT, MONS_GHOST_MOTH },
  {  1,   1,  100, FLAT, MONS_EMPEROR_SCORPION },
  {  1,   1,   20, FLAT, MONS_RED_WASP },
  {  1,   1,   60, FLAT, MONS_ORB_SPIDER },
  {  1,   1,   20, FLAT, MONS_TARANTELLA },
  { 0,0,0,FLAT,MONS_0 }
};

static const pop_entry _planerend_swamp[] =
{ // Swamp enemies
  {  1,   1,   80, FLAT, MONS_SWAMP_DRAGON },
  {  1,   1,   80, FLAT, MONS_HYDRA },
  {  1,   1,  100, FLAT, MONS_SHAMBLING_MANGROVE },
  {  1,   1,   40, FLAT, MONS_THORN_HUNTER },
  { 0,0,0,FLAT,MONS_0 }
};

static const pop_entry _planerend_shoals[] =
{ // Swamp enemies
  {  1,   1,   50, FLAT, MONS_SIREN },
  {  1,   1,  100, FLAT, MONS_MERFOLK_JAVELINEER },
  {  1,   1,  100, FLAT, MONS_MERFOLK_AQUAMANCER },
  {  1,   1,   80, FLAT, MONS_ALLIGATOR_SNAPPING_TURTLE },
  { 0,0,0,FLAT,MONS_0 }
};

static const pop_entry _planerend_slime[] =
{ // Slime enemies
  {  1,   1,  100, FLAT, MONS_ACID_BLOB },
  {  1,   1,  100, FLAT, MONS_AZURE_JELLY },
  {  1,   1,  100, FLAT, MONS_SLIME_CREATURE }, // changed to titanic below
  {  1,   1,   80, FLAT, MONS_GIANT_ORANGE_BRAIN },
  { 0,0,0,FLAT,MONS_0 }
};

static const pop_entry _planerend_orc[] =
{ // Orc enemies
  {  1,   1,  100, FLAT, MONS_ORC_WARLORD },
  {  1,   1,   80, FLAT, MONS_ORC_SORCERER },
  {  1,   1,   80, FLAT, MONS_ORC_HIGH_PRIEST },
  {  1,   1,   40, FLAT, MONS_STONE_GIANT },
  {  1,   1,   60, FLAT, MONS_OGRE_MAGE },
  { 0,0,0,FLAT,MONS_0 }
};

static const pop_entry _planerend_elf[] =
{ // Elf enemies
  {  1,   1,  100, FLAT, MONS_DEEP_ELF_SORCERER },
  {  1,   1,  100, FLAT, MONS_DEEP_ELF_HIGH_PRIEST },
  {  1,   1,   60, FLAT, MONS_DEEP_ELF_MASTER_ARCHER },
  {  1,   1,   60, FLAT, MONS_DEEP_ELF_BLADEMASTER },
  { 0,0,0,FLAT,MONS_0 }
};

static const pop_entry _planerend_vaults[] =
{ // Vaults enemies
  {  1,   1,   80, FLAT, MONS_VAULT_SENTINEL },
  {  1,   1,   80, FLAT, MONS_IRONHEART_PRESERVER },
  {  1,   1,   60, FLAT, MONS_IRONBRAND_CONVOKER },
  {  1,   1,  100, FLAT, MONS_WAR_GARGOYLE },
  { 0,0,0,FLAT,MONS_0 }
};

static const pop_entry _planerend_crypt[] =
{ // Crypt enemies
  {  1,   1,  100, FLAT, MONS_VAMPIRE_KNIGHT },
  {  1,   1,  100, FLAT, MONS_FLAYED_GHOST },
  {  1,   1,  100, FLAT, MONS_ANCIENT_CHAMPION },
  {  1,   1,   80, FLAT, MONS_DEEP_ELF_DEATH_MAGE },
  { 0,0,0,FLAT,MONS_0 }
};

static const pop_entry _planerend_tomb[] =
{ // Tomb enemies
  {  1,   1,   80, FLAT, MONS_GUARDIAN_MUMMY },
  {  1,   1,  100, FLAT, MONS_SPHINX },
  {  1,   1,  100, FLAT, MONS_MUMMY_PRIEST },
  { 0,0,0,FLAT,MONS_0 }
};

static const pop_entry _planerend_abyss[] =
{ // Abyss enemies
  {  1,   1,  100, FLAT, MONS_STARCURSED_MASS },
  {  1,   1,   80, FLAT, MONS_APOCALYPSE_CRAB },
  {  1,   1,   50, FLAT, MONS_THRASHING_HORROR },
  {  1,   1,   50, FLAT, MONS_WORLDBINDER },
  { 0,0,0,FLAT,MONS_0 }
};

static const pop_entry _planerend_zot[] =
{ // Zot enemies
  {  1,   1,   80, FLAT, MONS_DRACONIAN_SHIFTER },
  {  1,   1,   80, FLAT, MONS_DRACONIAN_SCORCHER },
  {  1,   1,   80, FLAT, MONS_DRACONIAN_ZEALOT },
  {  1,   1,  100, FLAT, MONS_GOLDEN_DRAGON },
  {  1,   1,  100, FLAT, MONS_MOTH_OF_WRATH },
  { 0,0,0,FLAT,MONS_0 }
};

static branch_summon_pair _planerend_summons[] =
{
  { BRANCH_LAIR,   _planerend_lair },
  { BRANCH_SNAKE,  _planerend_snake },
  { BRANCH_SPIDER, _planerend_spider },
  { BRANCH_SWAMP,  _planerend_swamp },
  { BRANCH_SHOALS, _planerend_shoals },
  { BRANCH_SLIME,  _planerend_slime },
  { BRANCH_ORC,    _planerend_orc },
  { BRANCH_ELF,    _planerend_elf },
  { BRANCH_VAULTS, _planerend_vaults },
  { BRANCH_CRYPT,  _planerend_crypt },
  { BRANCH_TOMB,   _planerend_tomb },
  { BRANCH_ABYSS,  _planerend_abyss },
  { BRANCH_ZOT,    _planerend_zot }
};

static void _branch_summon_helper(monster* mons, spell_type spell_cast,
                                  branch_summon_pair *summon_list,
                                  const size_t list_size, int count)
{
    int which = 0;
    // XXX: should this not depend on the specific spell?
    if (spell_cast == SPELL_FORCEFUL_INVITATION
        && mons->props.exists("invitation_branch"))
    {
        which = mons->props["invitation_branch"].get_byte();
    }
    else
    {
        which = random2(list_size);
        if (spell_cast == SPELL_FORCEFUL_INVITATION)
            mons->props["invitation_branch"].get_byte() = which;
    }

    if (you.see_cell(mons->pos()))
    {
        string msg = getSpeakString("branch summon cast prefix");
        if (!msg.empty())
        {
            msg  = replace_all(msg, "@The_monster@", mons->name(DESC_THE));
            msg += " ";
            msg += branches[summon_list[which].origin].longname;
            msg += "!";
            mprf(mons->wont_attack() ? MSGCH_FRIEND_ENCHANT
                 : MSGCH_MONSTER_ENCHANT, "%s", msg.c_str());
        }
    }

    for (int i = 0; i < count; i++)
    {
        monster_type type = pick_monster_from(summon_list[which].pop, 1);
        if (type == MONS_NO_MONSTER)
            continue;

        create_monster(
            mgen_data(type, SAME_ATTITUDE(mons), mons,
                      1, spell_cast, mons->pos(), mons->foe, 0, GOD_NO_GOD,
                      MONS_NO_MONSTER, type == MONS_SLIME_CREATURE ? 5 : 0));
    }
}

void mons_cast(monster* mons, bolt &pbolt, spell_type spell_cast,
               bool do_noise, bool special_ability)
{
    // Always do setup.  It might be done already, but it doesn't hurt
    // to do it again (cheap).
    setup_mons_cast(mons, pbolt, spell_cast);

    // single calculation permissible {dlb}
    bool monsterNearby = mons_near(mons);

    int sumcount = 0;
    int sumcount2;
    int duration = 0;

#ifdef DEBUG_DIAGNOSTICS
    mprf(MSGCH_DIAGNOSTICS, "Mon #%d casts %s (#%d)",
         mons->mindex(), spell_title(spell_cast), spell_cast);
#endif

    // for cancelling spells before messages are printed
    // this is a hack, the monster should really have never chosen to cast
    // the spell in the first place, we should never have gotten here -doy
    if (_mon_spell_bail_out_early(mons, spell_cast))
        return;

    bool orig_noise = do_noise;

    if (spell_cast == SPELL_CANTRIP
        || spell_cast == SPELL_VAMPIRIC_DRAINING
        || spell_cast == SPELL_IOOD
        || spell_cast == SPELL_INJURY_MIRROR
        || spell_cast == SPELL_DRAIN_LIFE
        || spell_cast == SPELL_TROGS_HAND
        || spell_cast == SPELL_LEDAS_LIQUEFACTION
        || spell_cast == SPELL_PORTAL_PROJECTILE
        || spell_cast == SPELL_FORCEFUL_INVITATION
        || spell_cast == SPELL_PLANEREND)
    {
        do_noise = false;       // Spell itself does the messaging.
    }

    if (_los_free_spell(spell_cast) && !spell_is_direct_explosion(spell_cast))
    {
        if (mons->foe == MHITYOU || mons->foe == MHITNOT)
        {
            if (monsterNearby)
            {
                if (do_noise)
                    mons_cast_noise(mons, pbolt, spell_cast, special_ability);
                direct_effect(mons, spell_cast, pbolt, &you);
            }
            return;
        }

        if (do_noise)
            mons_cast_noise(mons, pbolt, spell_cast, special_ability);
        direct_effect(mons, spell_cast, pbolt, mons->get_foe());
        return;
    }

#ifdef ASSERTS
    const unsigned int flags = get_spell_flags(spell_cast);

    ASSERT(!(flags & SPFLAG_TESTING));

    // Targeted spells need a valid target.
    // Wizard-mode cast monster spells may target the boundary (shift-dir).
    ASSERT(!(flags & SPFLAG_TARGETING_MASK) || map_bounds(pbolt.target));
#endif

    if (do_noise)
        mons_cast_noise(mons, pbolt, spell_cast, special_ability);

    bool priest;
    bool wizard;
    god_type god;

    _mons_set_priest_wizard_god(mons, priest, wizard, god);

    switch (spell_cast)
    {
    default:
        break;

    case SPELL_MAJOR_HEALING:
        if (mons->heal(50 + random2avg(mons->spell_hd(spell_cast) * 10, 2)))
            simple_monster_message(mons, " is healed.");
        return;

    case SPELL_INJURY_MIRROR:
        simple_monster_message(mons,
                               make_stringf(" offers %s to %s, and fills with unholy energy.",
                                   mons->pronoun(PRONOUN_REFLEXIVE).c_str(),
                                   god_name(mons->god).c_str()).c_str(),
                               MSGCH_MONSTER_SPELL);
        mons->add_ench(mon_enchant(ENCH_MIRROR_DAMAGE, 0, mons, random_range(7, 9) * BASELINE_DELAY));
        mons->props["mirror_recast_time"].get_int() = you.elapsed_time + 150 + random2(60);
        return;

    case SPELL_VAMPIRIC_DRAINING:
        _mons_vampiric_drain(mons);
        return;

    case SPELL_BERSERKER_RAGE:
        mons->props.erase("brothers_count");
        mons->go_berserk(true);
        return;

    case SPELL_FRENZY:
        mons->go_frenzy(mons);
        return;

    case SPELL_TROGS_HAND:
    {
        simple_monster_message(mons,
                               make_stringf(" invokes %s protection!",
                                   apostrophise(god_name(mons->god)).c_str()).c_str(),
                               MSGCH_MONSTER_SPELL);
        // Not spell_hd(spell_cast); this is an invocation
        const int dur = BASELINE_DELAY
            * min(5 + roll_dice(2, (mons->get_hit_dice() * 10) / 3 + 1), 100);
        mons->add_ench(mon_enchant(ENCH_RAISED_MR, 0, mons, dur));
        mons->add_ench(mon_enchant(ENCH_REGENERATION, 0, mons, dur));
        dprf("Trog's Hand cast (dur: %d aut)", dur);
        return;
    }

    case SPELL_SWIFTNESS:
        mons->add_ench(ENCH_SWIFT);
        if (mons->type == MONS_ALLIGATOR)
        {
            mons->number = you.num_turns;
            simple_monster_message(mons, " puts on a burst of speed!");
        }
        else
            simple_monster_message(mons, " seems to move somewhat quicker.");
        return;

    case SPELL_STONESKIN:
    {
        if (you.can_see(mons))
        {
            mprf("%s skin hardens.",
                 apostrophise(mons->name(DESC_THE)).c_str());
        }
        const int power = (mons->spell_hd(spell_cast) * 15) / 10;
        mons->add_ench(mon_enchant(ENCH_STONESKIN, 0, mons,
                       BASELINE_DELAY * (10 + (2 * random2(power)))));
        return;
    }

    case SPELL_SILENCE:
        mons->add_ench(ENCH_SILENCE);
        invalidate_agrid(true);
        simple_monster_message(mons, "'s surroundings become eerily quiet.");
        return;

    case SPELL_CALL_TIDE:
        if (player_in_branch(BRANCH_SHOALS))
        {
            const int tide_duration = BASELINE_DELAY
                * random_range(80, 200, 2);
            mons->add_ench(mon_enchant(ENCH_TIDE, 0, mons,
                                       tide_duration));
            mons->props[TIDE_CALL_TURN].get_int() = you.num_turns;
            if (simple_monster_message(
                    mons,
                    " sings a water chant to call the tide!"))
            {
                flash_view_delay(ETC_WATER, 300);
            }
        }
        return;

    case SPELL_INK_CLOUD:
        if (!feat_is_watery(grd(mons->pos())))
            return;

        big_cloud(CLOUD_INK, mons, mons->pos(), 30, 30);

        simple_monster_message(
            mons,
            " squirts a massive cloud of ink into the water!");
        return;

#if TAG_MAJOR_VERSION == 34
    case SPELL_VAMPIRE_SUMMON:
#endif
    case SPELL_SUMMON_SMALL_MAMMAL:
        sumcount2 = 1 + random2(3);

        for (sumcount = 0; sumcount < sumcount2; ++sumcount)
        {
            monster_type rats[] = { MONS_QUOKKA, MONS_GREEN_RAT, MONS_RAT };

            const monster_type mon = (one_chance_in(3) ? MONS_BAT
                                                       : RANDOM_ELEMENT(rats));
            create_monster(
                mgen_data(mon, SAME_ATTITUDE(mons), mons,
                          5, spell_cast, mons->pos(), mons->foe, 0, god));
        }
        return;

    case SPELL_STICKS_TO_SNAKES:
    {
        const int pow = (mons->spell_hd(spell_cast) * 15) / 10;
        int cnt = 1 + random2(1 + pow / 4);
        monster_type sum;
        for (int i = 0; i < cnt; i++)
        {
            if (random2(mons->spell_hd(spell_cast)) > 27
                || one_chance_in(5 - min(4, div_rand_round(pow * 2, 25))))
            {
                sum = x_chance_in_y(pow / 3, 100) ? MONS_WATER_MOCCASIN
                                                  : MONS_ADDER;
            }
            else
                sum = MONS_BALL_PYTHON;

            if (create_monster(
                    mgen_data(sum, SAME_ATTITUDE(mons), mons,
                              5, spell_cast, mons->pos(), mons->foe,
                              0, god)))
            {
                i++;
            }
        }
        return;
    }

    case SPELL_SHADOW_CREATURES:       // summon anything appropriate for level
    case SPELL_WEAVE_SHADOWS:
    {
        if (spell_cast == SPELL_SHADOW_CREATURES
            && _mons_abjured(mons, monsterNearby))
        {
            return;
        }

        level_id place = (spell_cast == SPELL_SHADOW_CREATURES)
                         ? level_id::current()
                         : level_id(BRANCH_DUNGEON,
                                    min(27, max(1, mons->spell_hd(spell_cast))));

        sumcount2 = 1 + random2(mons->spell_hd(spell_cast) / 5 + 1);

        for (sumcount = 0; sumcount < sumcount2; ++sumcount)
        {
            create_monster(
                mgen_data(RANDOM_MOBILE_MONSTER, SAME_ATTITUDE(mons), mons,
                          5, spell_cast, mons->pos(), mons->foe, 0, god,
                          MONS_NO_MONSTER, 0, BLACK, PROX_ANYWHERE, place));
        }
        return;
    }

    case SPELL_WATER_ELEMENTALS:
        _mons_summon_elemental(mons, monsterNearby, spell_cast,
                               MONS_WATER_ELEMENTAL, god);
        return;

    case SPELL_EARTH_ELEMENTALS:
        _mons_summon_elemental(mons, monsterNearby, spell_cast,
                               MONS_EARTH_ELEMENTAL, god);
        return;

    case SPELL_IRON_ELEMENTALS:
        _mons_summon_elemental(mons, monsterNearby, spell_cast,
                               MONS_IRON_ELEMENTAL, god);
        return;

    case SPELL_AIR_ELEMENTALS:
        _mons_summon_elemental(mons, monsterNearby, spell_cast,
                               MONS_AIR_ELEMENTAL, god);
        return;

    case SPELL_FIRE_ELEMENTALS:
        _mons_summon_elemental(mons, monsterNearby, spell_cast,
                               MONS_FIRE_ELEMENTAL, god);
        return;

    case SPELL_SUMMON_ILLUSION:
        _mons_cast_summon_illusion(mons, spell_cast);
        return;

    case SPELL_CREATE_TENTACLES:
        _mons_create_tentacles(mons);
        return;

    case SPELL_FAKE_MARA_SUMMON:
        // We only want there to be two fakes, which, plus Mara, means
        // a total of three Maras; if we already have two, give up, otherwise
        // we want to summon either one more or two more.
        sumcount2 = 2 - count_summons(mons, SPELL_FAKE_MARA_SUMMON);
        if (sumcount2 <= 0)
            return;

        for (sumcount = 0; sumcount < sumcount2; sumcount++)
            cast_phantom_mirror(mons, mons, 50, SPELL_FAKE_MARA_SUMMON);

        if (you.can_see(mons))
        {
            mprf("%s shimmers and seems to become %s!", mons->name(DESC_THE).c_str(),
                                                        sumcount2 == 1 ? "two"
                                                                       : "three");
        }

        return;

    case SPELL_SUMMON_DEMON: // class 2-4 demons
        if (_mons_abjured(mons, monsterNearby))
            return;

        sumcount2 = 1 + random2(mons->spell_hd(spell_cast) / 10 + 1);

        duration  = min(2 + mons->spell_hd(spell_cast) / 10, 6);
        for (sumcount = 0; sumcount < sumcount2; sumcount++)
        {
            create_monster(
                mgen_data(summon_any_demon(RANDOM_DEMON_COMMON),
                          SAME_ATTITUDE(mons), mons, duration, spell_cast,
                          mons->pos(), mons->foe, 0, god));
        }
        return;

    case SPELL_MONSTROUS_MENAGERIE:
        if (_mons_abjured(mons, monsterNearby))
            return;

        cast_monstrous_menagerie(mons, mons->spell_hd(spell_cast) * 6, mons->god);
        return;

    case SPELL_ANIMATE_DEAD:
        animate_dead(mons, 5 + random2(5), SAME_ATTITUDE(mons),
                     mons->foe, mons, "", god);
        return;

    case SPELL_TWISTED_RESURRECTION:
        // Double efficiency compared to maxed out player spell: one
        // elf corpse gives 4.5 HD.
        twisted_resurrection(mons, 500, SAME_ATTITUDE(mons),
                             mons->foe, god);
        return;

    case SPELL_SIMULACRUM:
        monster_simulacrum(mons, true);
        return;

    case SPELL_CALL_IMP:
        duration  = min(2 + mons->spell_hd(spell_cast) / 5, 6);
            create_monster(
                mgen_data(random_choose_weighted(
                            1, MONS_IRON_IMP,
                            2, MONS_SHADOW_IMP,
                            2, MONS_WHITE_IMP,
                            4, MONS_CRIMSON_IMP,
                            0),
                          SAME_ATTITUDE(mons), mons,
                          duration, spell_cast, mons->pos(), mons->foe, 0,
                          god));
        return;

    case SPELL_SUMMON_MINOR_DEMON: // class 5 demons
        sumcount2 = 1 + random2(3);

        duration  = min(2 + mons->spell_hd(spell_cast) / 5, 6);
        for (sumcount = 0; sumcount < sumcount2; ++sumcount)
        {
            create_monster(
                mgen_data(summon_any_demon(RANDOM_DEMON_LESSER),
                          SAME_ATTITUDE(mons), mons,
                          duration, spell_cast, mons->pos(), mons->foe, 0,
                          god));
        }
        return;

    case SPELL_SUMMON_SWARM:
        _do_high_level_summon(mons, monsterNearby, spell_cast,
                              _pick_swarmer, random_range(3, 6), god,
                              NULL, NULL, false);
        return;

    case SPELL_SUMMON_UFETUBUS:
        sumcount2 = 2 + random2(2);

        duration  = min(2 + mons->spell_hd(spell_cast) / 5, 6);

        for (sumcount = 0; sumcount < sumcount2; ++sumcount)
        {
            create_monster(
                mgen_data(MONS_UFETUBUS, SAME_ATTITUDE(mons), mons,
                          duration, spell_cast, mons->pos(), mons->foe, 0,
                          god));
        }
        return;

    case SPELL_SUMMON_HELL_BEAST:  // Geryon
        create_monster(
            mgen_data(MONS_HELL_BEAST, SAME_ATTITUDE(mons), mons,
                      4, spell_cast, mons->pos(), mons->foe, 0, god));
        return;

    case SPELL_SUMMON_ICE_BEAST:
        // Zotdef: reduce ice beast frequency, and reduce duration to 3
        if (!crawl_state.game_is_zotdef() || !one_chance_in(3))
        {
            int dur = crawl_state.game_is_zotdef() ? 3 : 5;
            create_monster(
                mgen_data(MONS_ICE_BEAST, SAME_ATTITUDE(mons), mons,
                          dur, spell_cast, mons->pos(), mons->foe, 0, god));
        }
        return;

    case SPELL_SUMMON_MUSHROOMS:   // Summon a ring of icky crawling fungi.
        if (_mons_abjured(mons, monsterNearby))
            return;

        sumcount2 = 2 + random2(mons->spell_hd(spell_cast) / 4 + 1);
        duration  = min(2 + mons->spell_hd(spell_cast) / 5, 6);
        for (int i = 0; i < sumcount2; ++i)
        {
            // Attempt to place adjacent to target first, and only at a wider
            // radius if no adjacent spots can be found
            coord_def empty;
            find_habitable_spot_near(mons->get_foe()->pos(),
                                     MONS_WANDERING_MUSHROOM, 1, false, empty);
            if (empty.origin())
            {
                find_habitable_spot_near(mons->get_foe()->pos(),
                                         MONS_WANDERING_MUSHROOM, 2, false, empty);
            }

            // Can't find any room, so stop trying
            if (empty.origin())
                return;

            create_monster(
                mgen_data(one_chance_in(3) ? MONS_DEATHCAP
                                           : MONS_WANDERING_MUSHROOM,
                          SAME_ATTITUDE(mons), mons, duration, spell_cast,
                          empty, mons->foe, MG_FORCE_PLACE, god));
        }
        return;

    case SPELL_SUMMON_HORRIBLE_THINGS:
        _do_high_level_summon(mons, monsterNearby, spell_cast,
                              _pick_horrible_thing, random_range(3, 5), god);
        return;

    case SPELL_MALIGN_GATEWAY:
        if (!can_cast_malign_gateway())
        {
            dprf("ERROR: %s can't cast malign gateway, but is casting anyway! "
                 "Counted %d gateways.", mons->name(DESC_THE).c_str(),
                 count_malign_gateways());
        }
        cast_malign_gateway(mons, 200);
        return;

    case SPELL_CONJURE_BALL_LIGHTNING:
    {
        const int n = min(8, 2 + random2avg(mons->spell_hd(spell_cast) / 4, 2));
        for (int i = 0; i < n; ++i)
        {
            if (monster *ball = create_monster(
                    mgen_data(MONS_BALL_LIGHTNING, SAME_ATTITUDE(mons),
                              mons, 0, spell_cast, mons->pos(), mons->foe,
                              0, god)))
            {
                ball->add_ench(ENCH_SHORT_LIVED);
            }
        }
        return;
    }

    case SPELL_SUMMON_UNDEAD:
        _do_high_level_summon(mons, monsterNearby, spell_cast,
                              _pick_undead_summon,
                              2 + random2(mons->spell_hd(spell_cast) / 5 + 1), god);
        return;

    case SPELL_BROTHERS_IN_ARMS:
    {
        // Invocation; don't use spell_hd
        const int power = (mons->get_hit_dice() * 20)
                          + random2(mons->get_hit_dice() * 5)
                          - random2(mons->get_hit_dice() * 5);
        monster_type to_summon;

        if (mons->type == MONS_SPRIGGAN_BERSERKER)
        {
            monster_type berserkers[] = { MONS_POLAR_BEAR, MONS_ELEPHANT,
                                          MONS_DEATH_YAK };
            to_summon = RANDOM_ELEMENT(berserkers);
        }
        else
        {
            monster_type berserkers[] = { MONS_BLACK_BEAR, MONS_OGRE, MONS_TROLL,
                                           MONS_HILL_GIANT, MONS_DEEP_TROLL,
                                           MONS_TWO_HEADED_OGRE};
            to_summon = RANDOM_ELEMENT(berserkers);
        }

        summon_berserker(power, mons, to_summon);
        mons->props["brothers_count"].get_int()++;
        return;
    }

    case SPELL_SYMBOL_OF_TORMENT:
        torment(mons, mons->mindex(), mons->pos());
        return;

    case SPELL_MESMERISE:
        _mons_mesmerise(mons);
        return;

    case SPELL_CAUSE_FEAR:
        _mons_cause_fear(mons);
        return;

    case SPELL_DRAIN_LIFE:
        cast_los_attack_spell(spell_cast, mons->spell_hd(spell_cast), mons, true);
        return;

    case SPELL_OZOCUBUS_REFRIGERATION:
        cast_los_attack_spell(spell_cast, mons->spell_hd(spell_cast) * 5,
                              mons, true);
        return;

    case SPELL_OLGREBS_TOXIC_RADIANCE:
        cast_toxic_radiance(mons, mons->spell_hd(spell_cast) * 8);
        return;

    case SPELL_LRD:
    {
        if (in_bounds(pbolt.target))
        {
           cast_fragmentation(6 * mons->spell_hd(spell_cast), mons,
                              pbolt.target, false);
        }
        else if (you.can_see(mons))
            canned_msg(MSG_NOTHING_HAPPENS);

        return;
    }

    case SPELL_SHATTER:
        mons_shatter(mons);
        return;

    case SPELL_LEDAS_LIQUEFACTION:
        if (!mons->has_ench(ENCH_LIQUEFYING) && you.can_see(mons))
        {
            mprf("%s liquefies the ground around %s!", mons->name(DESC_THE).c_str(),
                 mons->pronoun(PRONOUN_REFLEXIVE).c_str());
            flash_view_delay(BROWN, 80);
        }

        mons->add_ench(ENCH_LIQUEFYING);
        invalidate_agrid(true);
        return;

    case SPELL_CORPSE_ROT:
        corpse_rot(mons);
        return;

    case SPELL_SUMMON_GREATER_DEMON:
        if (_mons_abjured(mons, monsterNearby))
            return;

        duration  = min(2 + mons->spell_hd(spell_cast) / 10, 6);

        create_monster(
            mgen_data(summon_any_demon(RANDOM_DEMON_GREATER),
                      SAME_ATTITUDE(mons), mons,
                      duration, spell_cast,
                      mons->pos(), mons->foe, 0, god));
        return;

    // Journey -- Added in Summon Lizards and Draconians
    case SPELL_SUMMON_DRAKES:
        if (_mons_abjured(mons, monsterNearby))
            return;

        sumcount2 = 1 + random2(mons->spell_hd(spell_cast) / 5 + 1);

        duration  = min(2 + mons->spell_hd(spell_cast) / 10, 6);

        {
            vector<monster_type> monsters;

            for (sumcount = 0; sumcount < sumcount2; ++sumcount)
            {
                bool drag = false;
                monster_type mon = summon_any_dragon(DRAGON_LIZARD);

                if (mon == MONS_DRAGON)
                {
                    drag = true;
                    mon = summon_any_dragon(DRAGON_DRAGON);
                }

                monsters.push_back(mon);

                if (drag)
                    break;
            }

            for (int i = 0, size = monsters.size(); i < size; ++i)
            {
                create_monster(
                    mgen_data(monsters[i], SAME_ATTITUDE(mons), mons,
                              duration, spell_cast,
                              mons->pos(), mons->foe, 0, god));
            }
        }
        return;

    case SPELL_DRUIDS_CALL:
        _cast_druids_call(mons);
        return;

    case SPELL_BATTLESPHERE:
        cast_battlesphere(mons, min(6 * mons->spell_hd(spell_cast), 200), mons->god, false);
        return;

    case SPELL_SPECTRAL_WEAPON:
        cast_spectral_weapon(mons, min(6 * mons->spell_hd(spell_cast), 200), mons->god, false);
        return;

    case SPELL_TORNADO:
    {
        int dur = 60;
        if (you.can_see(mons))
        {
            bool flying = mons->flight_mode();
            mprf("A great vortex of raging winds appears %s%s%s!",
                 flying ? "around " : "and lifts ",
                 mons->name(DESC_THE).c_str(),
                 flying ? "" : " up!");
        }
        else if (you.see_cell(mons->pos()))
            mpr("A great vortex of raging winds appears out of thin air!");
        mons->props["tornado_since"].get_int() = you.elapsed_time;
        mon_enchant me(ENCH_TORNADO, 0, mons, dur);
        mons->add_ench(me);
        if (mons->has_ench(ENCH_FLIGHT))
        {
            mon_enchant me2 = mons->get_ench(ENCH_FLIGHT);
            me2.duration = me.duration;
            mons->update_ench(me2);
        }
        else
            mons->add_ench(mon_enchant(ENCH_FLIGHT, 0, mons, dur));
        return;
    }

    case SPELL_EPHEMERAL_INFUSION:
        _cast_ephemeral_infusion(mons);
        return;

    // TODO: Outsource the cantrip messages and allow specification of
    //       special cantrip spells per monster, like for speech, both as
    //       "self buffs" and "player enchantments".
    case SPELL_CANTRIP:
    {
        // Monster spell of uselessness, just prints a message.
        // This spell exists so that some monsters with really strong
        // spells (ie orc priest) can be toned down a bit. -- bwr
        //
        // XXX: Needs expansion, and perhaps different priest/mage flavours.

        // Don't give any message if the monster isn't nearby.
        // (Otherwise you could get them from halfway across the level.)
        if (!mons_near(mons))
            return;

        const bool friendly  = mons->friendly();
        const bool buff_only = !friendly && is_sanctuary(you.pos());
        const msg_channel_type channel = (friendly) ? MSGCH_FRIEND_ENCHANT
                                                    : MSGCH_MONSTER_ENCHANT;

        if (mons->type == MONS_GASTRONOK)
        {
            bool has_mon_foe = !invalid_monster_index(mons->foe);
            string slugform = "";
            if (buff_only || crawl_state.game_is_arena() && !has_mon_foe
                || friendly && !has_mon_foe || coinflip())
            {
                slugform = getSpeakString("gastronok_self_buff");
                if (!slugform.empty())
                {
                    slugform = replace_all(slugform, "@The_monster@",
                                           mons->name(DESC_THE));
                    mprf(channel, "%s", slugform.c_str());
                }
            }
            else if (!friendly && !has_mon_foe)
            {
                mons_cast_noise(mons, pbolt, spell_cast);

                // "Enchant" the player.
                slugform = getSpeakString("gastronok_debuff");
                if (!slugform.empty()
                    && (slugform.find("legs") == string::npos
                        || _legs_msg_applicable()))
                {
                    mpr(slugform.c_str());
                }
            }
            else
            {
                // "Enchant" another monster.
                const monster* foe      = mons->get_foe()->as_monster();
                slugform = getSpeakString("gastronok_other_buff");
                if (!slugform.empty())
                {
                    slugform = replace_all(slugform, "@The_monster@",
                                           foe->name(DESC_THE));
                    mprf(MSGCH_MONSTER_ENCHANT, "%s", slugform.c_str());
                }
            }
        }
        else
        {
            // Messages about the monster influencing itself.
            const char* buff_msgs[] =
            {
                " glows brightly for a moment.",
                " looks braver.",
                " becomes somewhat translucent.",
                "'s eyes start to glow.",
            };

            // Messages about the monster influencing you.
            const char* other_msgs[] =
            {
                "You feel troubled.",
                "You feel a wave of unholy energy pass over you.",
            };

            if (buff_only || crawl_state.game_is_arena() || x_chance_in_y(2,3))
            {
                simple_monster_message(mons, RANDOM_ELEMENT(buff_msgs),
                                       channel);
            }
            else if (friendly)
            {
                simple_monster_message(mons, " shimmers for a moment.",
                                       channel);
            }
            else // "Enchant" the player.
            {
                mons_cast_noise(mons, pbolt, spell_cast);
                mpr(RANDOM_ELEMENT(other_msgs));
            }
        }
        return;
    }
    case SPELL_BLINK_OTHER:
    {
        // Allow the caster to comment on moving the foe.
        string msg = getSpeakString(mons->name(DESC_PLAIN) + " blink_other");
        if (!msg.empty() && msg != "__NONE")
        {
            mons_speaks_msg(mons, msg, MSGCH_TALK,
                            silenced(you.pos()) || silenced(mons->pos()));
        }
        break;
    }
    case SPELL_BLINK_OTHER_CLOSE:
    {
        // Allow the caster to comment on moving the foe.
        string msg = getSpeakString(mons->name(DESC_PLAIN)
                                    + " blink_other_close");
        if (!msg.empty() && msg != "__NONE")
        {
            mons_speaks_msg(mons, msg, MSGCH_TALK,
                            silenced(you.pos()) || silenced(mons->pos()));
        }
        break;
    }
    case SPELL_TOMB_OF_DOROKLOHE:
    {
        sumcount = 0;

        const int hp_lost = mons->max_hit_points - mons->hit_points;

        if (!hp_lost)
            sumcount++;

        const dungeon_feature_type safe_tiles[] =
        {
            DNGN_SHALLOW_WATER, DNGN_FLOOR, DNGN_OPEN_DOOR,
        };

        bool proceed;

        for (adjacent_iterator ai(mons->pos()); ai; ++ai)
        {
            const actor* act = actor_at(*ai);

            // We can blink away the crowd, but only our allies.
            if (act
                && (act->is_player()
                    || (act->is_monster()
                        && act->as_monster()->attitude != mons->attitude)))
            {
                sumcount++;
            }

            // Make sure we have a legitimate tile.
            proceed = false;
            for (unsigned int i = 0; i < ARRAYSZ(safe_tiles) && !proceed; ++i)
                if (grd(*ai) == safe_tiles[i] || feat_is_trap(grd(*ai)))
                    proceed = true;

            if (!proceed && feat_is_reachable_past(grd(*ai)))
                sumcount++;
        }

        if (sumcount)
        {
            mons->blink();
            return;
        }

        sumcount = 0;
        for (adjacent_iterator ai(mons->pos()); ai; ++ai)
        {
            if (monster_at(*ai))
            {
                monster_at(*ai)->blink();
                if (monster_at(*ai))
                {
                    monster_at(*ai)->teleport(true);
                    if (monster_at(*ai))
                        continue;
                }
            }

            // Make sure we have a legitimate tile.
            proceed = false;
            for (unsigned int i = 0; i < ARRAYSZ(safe_tiles) && !proceed; ++i)
                if (grd(*ai) == safe_tiles[i] || feat_is_trap(grd(*ai)))
                    proceed = true;

            if (proceed)
            {
                // All items are moved inside.
                if (igrd(*ai) != NON_ITEM)
                    move_items(*ai, mons->pos());

                // All clouds are destroyed.
                if (env.cgrid(*ai) != EMPTY_CLOUD)
                    delete_cloud(env.cgrid(*ai));

                // All traps are destroyed.
                if (trap_def *ptrap = find_trap(*ai))
                    ptrap->destroy();

                // Actually place the wall.
                temp_change_terrain(*ai, DNGN_ROCK_WALL, INFINITE_DURATION,
                                    TERRAIN_CHANGE_TOMB, mons);
                sumcount++;
            }
        }

        if (sumcount)
        {
            mpr("Walls emerge from the floor!");

            // XXX: Assume that the entombed monster can regenerate.
            // Also, base the regeneration rate on HD to avoid
            // randomness.
            const int tomb_duration = BASELINE_DELAY
                * hp_lost * max(1, mons->spell_hd(spell_cast) / 3);
            int mon_index = mons->mindex();
            env.markers.add(new map_tomb_marker(mons->pos(),
                                                tomb_duration,
                                                mon_index,
                                                mon_index));
            env.markers.clear_need_activate(); // doesn't need activation
        }
        return;
    }
    case SPELL_CHAIN_LIGHTNING:
    case SPELL_CHAIN_OF_CHAOS:
        cast_chain_spell(spell_cast, 4 * mons->spell_hd(spell_cast), mons);
        return;
    case SPELL_SUMMON_EYEBALLS:
        if (mons->type != MONS_DISSOLUTION
            && _mons_abjured(mons, monsterNearby))
        {
            return;
        }

        sumcount2 = 1 + random2(mons->spell_hd(spell_cast) / 7 + 1);

        duration = min(2 + mons->spell_hd(spell_cast) / 10, 6);

        for (sumcount = 0; sumcount < sumcount2; sumcount++)
        {
            const monster_type mon = random_choose_weighted(
                                       100, MONS_GIANT_EYEBALL,
                                        80, MONS_EYE_OF_DRAINING,
                                        60, MONS_GOLDEN_EYE,
                                        40, MONS_SHINING_EYE,
                                        20, MONS_GREAT_ORB_OF_EYES,
                                        10, MONS_EYE_OF_DEVASTATION,
                                         0);

            create_monster(
                mgen_data(mon, SAME_ATTITUDE(mons), mons, duration,
                          spell_cast, mons->pos(), mons->foe, 0, god));
        }
        return;
    case SPELL_SUMMON_BUTTERFLIES:
        duration = min(2 + mons->spell_hd(spell_cast) / 5, 6);
        for (int i = 0; i < 10; ++i)
        {
            create_monster(
                mgen_data(MONS_BUTTERFLY, SAME_ATTITUDE(mons),
                          mons, duration, spell_cast, mons->pos(),
                          mons->foe, 0, god));
        }
        return;
    case SPELL_IOOD:
        if (mons->type == MONS_ORB_SPIDER && !mons->has_ench(ENCH_IOOD_CHARGED))
        {
            mons->add_ench(ENCH_IOOD_CHARGED);

            if (!monsterNearby)
                return;
            string msg = getSpeakString("orb spider charge");
            if (!msg.empty())
            {
                msg = replace_all(msg, "@The_monster@", mons->name(DESC_THE));
                mprf(mons->wont_attack() ? MSGCH_FRIEND_ENCHANT
                     : MSGCH_MONSTER_ENCHANT, "%s", msg.c_str());
            }
            return;
        }
        mons->del_ench(ENCH_IOOD_CHARGED);
        if (orig_noise)
            mons_cast_noise(mons, pbolt, spell_cast, special_ability);
        cast_iood(mons, 6 * mons->spell_hd(spell_cast), &pbolt);
        return;
    case SPELL_AWAKEN_FOREST:
        duration = 50 + random2(mons->spell_hd(spell_cast) * 20);

        mons->add_ench(mon_enchant(ENCH_AWAKEN_FOREST, 0, mons, duration));
        // Actually, it's a boolean marker... save for a sanity check.
        env.forest_awoken_until = you.elapsed_time + duration;

        // You may be unable to see the monster, but notice an affected tree.
        forest_message(mons->pos(), "The forest starts to sway and rumble!");
        return;

    case SPELL_SUMMON_DRAGON:
        if (_mons_abjured(mons, monsterNearby))
            return;

        cast_summon_dragon(mons, mons->spell_hd(spell_cast) * 5, god);
        return;
    case SPELL_SUMMON_HYDRA:
        if (_mons_abjured(mons, monsterNearby))
            return;

        cast_summon_hydra(mons, mons->spell_hd(spell_cast) * 5, god);
        return;
    case SPELL_FIRE_SUMMON:
        if (_mons_abjured(mons, monsterNearby))
            return;

        sumcount2 = 1 + random2(mons->spell_hd(spell_cast) / 5 + 1);

        duration = min(2 + mons->spell_hd(spell_cast) / 10, 6);

        for (sumcount = 0; sumcount < sumcount2; sumcount++)
        {
            const monster_type mon = random_choose_weighted(
                                       3, MONS_EFREET,
                                       3, MONS_SUN_DEMON,
                                       3, MONS_BALRUG,
                                       2, MONS_HELLION,
                                       1, MONS_BRIMSTONE_FIEND,
                                       0);

            create_monster(
                mgen_data(mon, SAME_ATTITUDE(mons), mons, duration,
                          spell_cast, mons->pos(), mons->foe, 0, god));
        }
        return;

    case SPELL_DEATHS_DOOR:
        if (!mons->has_ench(ENCH_DEATHS_DOOR))
        {
            const int dur = BASELINE_DELAY * 2 * mons->skill(SK_NECROMANCY);
            simple_monster_message(mons,
                                   " stands defiantly in death's doorway!");
            mons->hit_points = max(min(mons->hit_points,
                                       mons->skill(SK_NECROMANCY)), 1);
            mons->add_ench(mon_enchant(ENCH_DEATHS_DOOR, 0, mons, dur));
        }
        return;

    case SPELL_REGENERATION:
    {
        simple_monster_message(mons,
                               "'s wounds begin to heal before your eyes!");
        const int dur = BASELINE_DELAY
            * min(5 + roll_dice(2, (mons->spell_hd(spell_cast) * 10) / 3 + 1), 100);
        mons->add_ench(mon_enchant(ENCH_REGENERATION, 0, mons, dur));
        return;
    }

    case SPELL_OZOCUBUS_ARMOUR:
    {
        if (you.can_see(mons))
        {
            mprf("A film of ice covers %s body!",
                 apostrophise(mons->name(DESC_THE)).c_str());
        }
        const int power = (mons->spell_hd(spell_cast) * 15) / 10;
        mons->add_ench(mon_enchant(ENCH_OZOCUBUS_ARMOUR,
                                   20 + random2(power) + random2(power),
                                   mons));

        return;
    }

    case SPELL_WORD_OF_RECALL:
    {
        mon_enchant recall_timer =
            mon_enchant(ENCH_WORD_OF_RECALL, 1, mons, 30);
        mons->add_ench(recall_timer);
        mons->speed_increment -= 30;

        return;
    }

    case SPELL_INJURY_BOND:
    {
        simple_monster_message(mons,
            make_stringf(" begins to accept %s allies' injuries.",
                         mons->pronoun(PRONOUN_POSSESSIVE).c_str()).c_str());
        // FIXME: allies preservers vs the player
        for (monster_near_iterator mi(mons, LOS_NO_TRANS); mi; ++mi)
        {
            if (mons_aligned(mons, *mi) && !mi->has_ench(ENCH_CHARM)
                && *mi != mons)
            {
                mon_enchant bond = mon_enchant(ENCH_INJURY_BOND, 1, mons,
                                               40 + random2(80));
                mi->add_ench(bond);
            }
        }

        return;
    }

    case SPELL_CALL_LOST_SOUL:
        create_monster(mgen_data(MONS_LOST_SOUL, SAME_ATTITUDE(mons),
                                 mons, 2, spell_cast, mons->pos(),
                                 mons->foe, 0, god));
        return;

    case SPELL_BLINK_ALLIES_ENCIRCLE:
        _blink_allies_encircle(mons);
        return;

    case SPELL_MASS_CONFUSION:
        _mons_mass_confuse(mons);
        return;

    case SPELL_ENGLACIATION:
        if (you.can_see(mons))
            simple_monster_message(mons, " radiates an aura of cold.");
        else if (mons->see_cell_no_trans(you.pos()))
            mpr("A wave of cold passes over you.");
        apply_area_visible(englaciate, min(12 * mons->spell_hd(spell_cast), 200), mons);
        return;

    case SPELL_AWAKEN_VINES:
        _awaken_vines(mons);
        return;

    case SPELL_CONTROL_WINDS:
        if (you.can_see(mons))
            mprf("The winds start to flow at %s will.", mons->name(DESC_ITS).c_str());
        mons->add_ench(mon_enchant(ENCH_CONTROL_WINDS, 1, mons, 200 + random2(150)));
        return;

    case SPELL_WALL_OF_BRAMBLES:
        // If we can't cast this for some reason (can be expensive to determine
        // at every call to _ms_waste_of_time), refund the energy for it so that
        // the caster can do something else
        if (!_wall_of_brambles(mons))
        {
            mons->speed_increment +=
                get_monster_data(mons->type)->energy_usage.spell;
        }
        return;

    case SPELL_HASTE_PLANTS:
    {
        int num = 2 + random2(3);
        for (monster_near_iterator mi(mons, LOS_NO_TRANS); mi && num > 0; ++mi)
        {
            if (mons_aligned(*mi, mons)
                && _is_hastable_plant(*mi)
                && !mi->has_ench(ENCH_HASTE))
            {
                mi->add_ench(ENCH_HASTE);
                simple_monster_message(*mi, " seems to speed up.");
                --num;
            }
        }
        return;
    }

    case SPELL_WIND_BLAST:
    {
        actor *foe = mons->get_foe();
        if (foe && mons->can_see(foe))
        {
            simple_monster_message(mons, " summons a great blast of wind!");
            wind_blast(mons, 12 * mons->spell_hd(spell_cast), foe->pos());
        }
        return;
    }

    case SPELL_FREEZE:
        _mons_cast_freeze(mons);
        return;

    case SPELL_SUMMON_VERMIN:
        _do_high_level_summon(mons, monsterNearby, spell_cast,
                              _pick_vermin, one_chance_in(4) ? 3 : 2 , god);
        return;

    case SPELL_DISCHARGE:
    {
        const int power = min(200, mons->spell_hd(spell_cast) * 12);
        const int num_targs = 1 + random2(random_range(1, 3) + power / 20);
        const int dam = apply_random_around_square(discharge_monsters,
                                                   mons->pos(), true, power,
                                                   num_targs, mons);
        if (dam == 0)
        {
            if (!you.can_see(mons))
                mpr("You hear crackling.");
            else if (coinflip())
            {
                mprf("The air around %s crackles with electrical energy.",
                     mons->name(DESC_THE).c_str());
            }
            else
            {
                const bool plural = coinflip();
                mprf("%s blue arc%s ground%s harmlessly %s %s.",
                     plural ? "Some" : "A",
                     plural ? "s" : "",
                     plural ? " themselves" : "s itself",
                     plural ? "around" : (coinflip() ? "beside" :
                                          coinflip() ? "behind" : "before"),
                     mons->name(DESC_THE).c_str());
            }
        }
        return;
    }
    case SPELL_PORTAL_PROJECTILE:
    {
        // Swap weapons if necessary so that that happens before the spell
        // casting message.
        item_def *launcher = NULL;
        mons_usable_missile(mons, &launcher);
        const item_def *weapon = mons->mslot_item(MSLOT_WEAPON);
        if (launcher && launcher != weapon)
            mons->swap_weapons();
        mons_cast_noise(mons, pbolt, spell_cast);
        handle_throw(mons, pbolt, true, false);
        return;
    }

    case SPELL_IGNITE_POISON:
        cast_ignite_poison(mons, mons->spell_hd(spell_cast) * 6, false);
        return;

    case SPELL_LEGENDARY_DESTRUCTION:
    {
        mons->hurt(mons, 10);
        spell_type real_spell;
        int range = 0;
        do
        {
            real_spell = random_choose_weighted(
                             10, SPELL_ORB_OF_ELECTRICITY,
                             10, SPELL_LEHUDIBS_CRYSTAL_SPEAR,
                              2, SPELL_IOOD,
                              5, SPELL_GHOSTLY_FIREBALL,
                             10, SPELL_FIREBALL,
                             10, SPELL_FLASH_FREEZE,
                              0);
            range = spell_range(real_spell,
                                12 * mons->spell_hd(real_spell),
                                false);
        }
        while (distance2(mons->pos(), pbolt.target) > dist_range(range));
        mons_cast(mons, pbolt, real_spell, false, special_ability);
        return;
    }

    case SPELL_FORCEFUL_INVITATION:
        _branch_summon_helper(mons, spell_cast, _invitation_summons,
                              ARRAYSZ(_invitation_summons), 1 + random2(3));
        return;

    case SPELL_PLANEREND:
        _branch_summon_helper(mons, spell_cast, _planerend_summons,
                              ARRAYSZ(_planerend_summons), 1 + random2(3));
        return;

    case SPELL_BLACK_MARK:
        _cast_black_mark(mons);
        return;

    case SPELL_GRAND_AVATAR:
    {
        duration = min(2 + mons->spell_hd(spell_cast) / 10, 6);

        monster* avatar =
            create_monster(
                mgen_data(MONS_GRAND_AVATAR, SAME_ATTITUDE(mons), mons,
                          duration, spell_cast, mons->pos(), mons->foe, 0,
                          god, MONS_NO_MONSTER, 0, BLACK, PROX_ANYWHERE,
                          level_id::current(), mons->spell_hd(spell_cast)));
        if (avatar)
        {
            simple_monster_message(mons, " calls forth a grand avatar!");
            mons->add_ench(mon_enchant(ENCH_GRAND_AVATAR, 1, avatar));
            for (monster_near_iterator mi(mons, LOS_NO_TRANS); mi; ++mi)
            {
                if (*mi != mons && mons_aligned(mons, *mi)
                    && !mi->has_ench(ENCH_CHARM)
                    && !mons_is_avatar((*mi)->type))
                {
                    mi->add_ench(mon_enchant(ENCH_GRAND_AVATAR, 1, avatar));
                }
            }
        }
        return;
    }
#if TAG_MAJOR_VERSION == 34
    case SPELL_REARRANGE_PIECES:
    {
        bool did_message = false;
        vector<actor* > victims;
        for (actor_near_iterator ai(mons, LOS_NO_TRANS); ai; ++ai)
            victims.push_back(*ai);
        shuffle_array(victims);
        for (vector<actor* >::iterator it = victims.begin();
             it != victims.end(); it++)
        {
            actor* victim1 = *it;
            it++;
            if (it == victims.end())
                break;
            actor* victim2 = *it;
            if (victim1->is_player())
                swap_with_monster(victim2->as_monster());
            else if (victim2->is_player())
                swap_with_monster(victim1->as_monster());
            else
            {
                if (!did_message
                    && (you.can_see(victim1)
                        || you.can_see(victim2)))
                {
                    mpr("Some monsters swap places.");
                    did_message = true;
                }

                swap_monsters(victim1->as_monster(), victim2->as_monster());
            }
        }
        return;
    }
#endif

    case SPELL_BLINK_ALLIES_AWAY:
        _blink_allies_away(mons);
        return;

    case SPELL_SHROUD_OF_GOLUBRIA:
        if (you.can_see(mons))
        {
            mprf("Space distorts along a thin shroud covering %s body.",
                 apostrophise(mons->name(DESC_THE)).c_str());
        }
        mons->add_ench(mon_enchant(ENCH_SHROUD));
        return;

    case SPELL_DAZZLING_SPRAY:
    {
        vector<bolt> beams = get_spray_rays(mons, pbolt.target, pbolt.range, 3,
                                            ZAP_DAZZLING_SPRAY);
        for (unsigned int i = 0; i < beams.size(); ++i)
        {
            bolt_parent_init(&pbolt, &(beams[i]));
            beams[i].fire();
        }
        return;
    }

    case SPELL_GLACIATE:
    {
        actor *foe = mons->get_foe();
        ASSERT(foe);
        cast_glaciate(mons, 12 * mons->spell_hd(spell_cast), foe->pos());
        return;
    }

    case SPELL_CLOUD_CONE:
    {
        ASSERT(mons->get_foe());
        cast_cloud_cone(mons, 12 * mons->spell_hd(spell_cast),
                        mons->get_foe()->pos());
        return;
    }

    case SPELL_PHANTOM_MIRROR:
    {
        // Find appropriate ally to clone.
        vector<monster*> targets;
        for (monster_near_iterator mi(mons); mi; ++mi)
        {
            if (_mirrorable(mons, *mi))
                targets.push_back(*mi);
        }

        // If we've found something, mirror it.
        if (targets.size())
        {
            monster* targ = targets[random2(targets.size())];
            if (cast_phantom_mirror(mons, targ))
                simple_monster_message(targ, " shimmers and seems to become two!");
        }
        return;
    }

    }

    // If a monster just came into view and immediately cast a spell,
    // we need to refresh the screen before drawing the beam.
    viewwindow();
    if (spell_is_direct_explosion(spell_cast))
    {
        const actor *foe = mons->get_foe();
        const bool need_more = foe && (foe->is_player() || you.see_cell(foe->pos()));
        pbolt.in_explosion_phase = false;
        pbolt.refine_for_explosion();
        pbolt.explode(need_more);
    }
    else
        pbolt.fire();
}

static int _noise_level(const monster* mons, spell_type spell,
                                  bool silent, bool innate)
{
    const unsigned int flags = get_spell_flags(spell);

    int noise;

    if (silent
        || (innate
            && !mons_class_flag(mons->type, M_NOISY_SPELLS)
            && !(flags & SPFLAG_NOISY)
            && mons_genus(mons->type) != MONS_DRAGON))
    {
        noise = 0;
    }
    else
    {
        if (mons_genus(mons->type) == MONS_DRAGON)
            noise = get_shout_noise_level(S_ROAR);
        else
            noise = spell_noise(spell);
    }
    return noise;
}

static unsigned int _noise_keys(vector<string>& key_list,
                                const monster* mons, const bolt& pbolt,
                                spell_type spell, bool priest, bool wizard,
                                bool innate, bool targeted)
{
    const string cast_str = " cast";

    const mon_body_shape shape = get_mon_shape(mons);
    const string    spell_name = spell_title(spell);
    const bool      real_spell = !innate && (priest || wizard);

    // Before just using generic per-spell and per-monster casts, try
    // per-monster, per-spell, with the monster type name, then the
    // species name, then the genus name, then wizard/priest/demon.
    // We don't include "real" or "gestures" here since that can be
    // be determined from the monster type; or "targeted" since that
    // can be determined from the spell.
    key_list.push_back(spell_name + " "
                       + mons_type_name(mons->type, DESC_PLAIN) + cast_str);
    key_list.push_back(spell_name + " "
                       + mons_type_name(mons_species(mons->type), DESC_PLAIN)
                       + cast_str);
    key_list.push_back(spell_name + " "
                       + mons_type_name(mons_genus(mons->type), DESC_PLAIN)
                       + cast_str);
    if (wizard)
    {
        key_list.push_back(make_stringf("%s %swizard%s",
                               spell_name.c_str(),
                               shape <= MON_SHAPE_NAGA ? "" : "non-humanoid ",
                               cast_str.c_str()));
    }
    else if (priest)
        key_list.push_back(spell_name + " priest" + cast_str);
    else if (mons_is_demon(mons->type))
        key_list.push_back(spell_name + " demon" + cast_str);

    // Now try just the spell's name.
    if (shape <= MON_SHAPE_NAGA)
    {
        if (real_spell)
            key_list.push_back(spell_name + cast_str + " real");
        if (mons_intel(mons) >= I_NORMAL)
            key_list.push_back(spell_name + cast_str + " gestures");
    }
    else if (real_spell)
    {
        // A real spell being cast by something with no hands?  Maybe
        // it's a polymorphed spellcaster which kept its original spells.
        // If so, the cast message for its new type/species/genus probably
        // won't look right.
        if (!mons_class_flag(mons->type, M_ACTUAL_SPELLS | M_PRIEST))
        {
            // XXX: We should probably include the monster's shape,
            // to get a variety of messages.
            if (wizard)
            {
                string key = "polymorphed wizard" + cast_str;
                if (targeted)
                    key_list.push_back(key + " targeted");
                key_list.push_back(key);
            }
            else if (priest)
            {
                string key = "polymorphed priest" + cast_str;
                if (targeted)
                    key_list.push_back(key + " targeted");
                key_list.push_back(key);
            }
        }
    }

    key_list.push_back(spell_name + cast_str);

    const unsigned int num_spell_keys = key_list.size();

    // Next the monster type name, then species name, then genus name.
    key_list.push_back(mons_type_name(mons->type, DESC_PLAIN) + cast_str);
    key_list.push_back(mons_type_name(mons_species(mons->type), DESC_PLAIN)
                       + cast_str);
    key_list.push_back(mons_type_name(mons_genus(mons->type), DESC_PLAIN)
                       + cast_str);

    // Last, generic wizard, priest or demon.
    if (wizard)
    {
        key_list.push_back(make_stringf("%swizard%s",
                               shape <= MON_SHAPE_NAGA ? "" : "non-humanoid ",
                               cast_str.c_str()));
    }
    else if (priest)
        key_list.push_back("priest" + cast_str);
    else if (mons_is_demon(mons->type))
        key_list.push_back("demon" + cast_str);

    if (targeted)
    {
        // For targeted spells, try with the targeted suffix first.
        for (unsigned int i = key_list.size() - 1; i >= num_spell_keys; i--)
        {
            string str = key_list[i] + " targeted";
            key_list.insert(key_list.begin() + i, str);
        }

        // Generic beam messages.
        if (pbolt.visible())
        {
            key_list.push_back(pbolt.get_short_name() + " beam " + cast_str);
            key_list.push_back("beam catchall cast");
        }
    }

    return num_spell_keys;
}

static string _noise_message(const vector<string>& key_list,
                             unsigned int num_spell_keys,
                             bool silent, bool unseen)
{
    string prefix;
    if (silent)
        prefix = "silent ";
    else if (unseen)
        prefix = "unseen ";

    string msg;
    for (unsigned int i = 0; i < key_list.size(); i++)
    {
        const string key = key_list[i];

#ifdef DEBUG_MONSPEAK
        dprf(DIAG_SPEECH, "monster casting lookup: %s%s",
             prefix.c_str(), key.c_str());
#endif

        msg = getSpeakString(prefix + key);
        if (msg == "__NONE")
        {
            msg = "";
            break;
        }
        else if (msg == "__NEXT")
        {
            msg = "";
            if (i < num_spell_keys)
                i = num_spell_keys - 1;
            else if (ends_with(key, " targeted"))
                i++;
            continue;
        }
        else if (!msg.empty())
            break;

        // If we got no message and we're using the silent prefix, then
        // try again without the prefix.
        if (prefix != "silent ")
            continue;

        msg = getSpeakString(key);
        if (msg == "__NONE")
        {
            msg = "";
            break;
        }
        else if (msg == "__NEXT")
        {
            msg = "";
            if (i < num_spell_keys)
                i = num_spell_keys - 1;
            else if (ends_with(key, " targeted"))
                i++;
            continue;
        }
        else if (!msg.empty())
            break;
    }

    return msg;
}

static void _noise_fill_target(string& targ_prep, string& target,
                               const monster* mons, const bolt& pbolt,
                               bool gestured)
{
    targ_prep = "at";
    target    = "nothing";

    bolt tracer = pbolt;
    // For a targeted but rangeless spell make the range positive so that
    // fire_tracer() will fill out path_taken.
    if (pbolt.range == 0 && pbolt.target != mons->pos())
        tracer.range = ENV_SHOW_DIAMETER;
    fire_tracer(mons, tracer);

    if (pbolt.target == you.pos())
        target = "you";
    else if (pbolt.target == mons->pos())
        target = mons->pronoun(PRONOUN_REFLEXIVE);
    // Monsters should only use targeted spells while foe == MHITNOT
    // if they're targeting themselves.
    else if (mons->foe == MHITNOT && !mons_is_confused(mons, true))
        target = "NONEXISTENT FOE";
    else if (!invalid_monster_index(mons->foe)
             && menv[mons->foe].type == MONS_NO_MONSTER)
    {
        target = "DEAD FOE";
    }
    else if (in_bounds(pbolt.target) && you.see_cell(pbolt.target))
    {
        if (const monster* mtarg = monster_at(pbolt.target))
        {
            if (you.can_see(mtarg))
                target = mtarg->name(DESC_THE);
        }
    }

    const bool visible_path      = pbolt.visible() || gestured;

    // Monster might be aiming past the real target, or maybe some fuzz has
    // been applied because the target is invisible.
    if (target == "nothing")
    {
        if (pbolt.aimed_at_spot || pbolt.origin_spell == SPELL_DIG)
        {
            int count = 0;
            for (adjacent_iterator ai(pbolt.target); ai; ++ai)
            {
                const actor* act = actor_at(*ai);
                if (act && act != mons && you.can_see(act))
                {
                    targ_prep = "next to";

                    if (act->is_player() || one_chance_in(++count))
                        target = act->name(DESC_THE);

                    if (act->is_player())
                        break;
                }
            }

            if (targ_prep == "at")
            {
                if (grd(pbolt.target) != DNGN_FLOOR)
                {
                    target = feature_description(grd(pbolt.target),
                                                 NUM_TRAPS, "", DESC_THE,
                                                 false);
                }
                else
                    target = "thin air";
            }

            return;
        }

        bool mons_targ_aligned = false;

        const vector<coord_def> &path = tracer.path_taken;
        for (unsigned int i = 0; i < path.size(); i++)
        {
            const coord_def pos = path[i];

            if (pos == mons->pos())
                continue;

            const monster* m = monster_at(pos);
            if (pos == you.pos())
            {
                // Be egotistical and assume that the monster is aiming at
                // the player, rather than the player being in the path of
                // a beam aimed at an ally.
                if (!mons->wont_attack())
                {
                    targ_prep = "at";
                    target    = "you";
                    break;
                }
                // If the ally is confused or aiming at an invisible enemy,
                // with the player in the path, act like it's targeted at
                // the player if there isn't any visible target earlier
                // in the path.
                else if (target == "nothing")
                {
                    targ_prep         = "at";
                    target            = "you";
                    mons_targ_aligned = true;
                }
            }
            else if (visible_path && m && you.can_see(m))
            {
                bool is_aligned  = mons_aligned(m, mons);
                string name = m->name(DESC_THE);

                if (target == "nothing")
                {
                    mons_targ_aligned = is_aligned;
                    target            = name;
                }
                // If the first target was aligned with the beam source then
                // the first subsequent non-aligned monster in the path will
                // take it's place.
                else if (mons_targ_aligned && !is_aligned)
                {
                    mons_targ_aligned = false;
                    target            = name;
                }
                targ_prep = "at";
            }
            else if (visible_path && target == "nothing")
            {
                int count = 0;
                for (adjacent_iterator ai(pbolt.target); ai; ++ai)
                {
                    const actor* act = monster_at(*ai);
                    if (act && act != mons && you.can_see(act))
                    {
                        targ_prep = "past";
                        if (act->is_player()
                            || one_chance_in(++count))
                        {
                            target = act->name(DESC_THE);
                        }

                        if (act->is_player())
                            break;
                    }
                }
            }
        } // for (unsigned int i = 0; i < path.size(); i++)
    } // if (target == "nothing" && targeted)

    const actor* foe = mons->get_foe();

    // If we still can't find what appears to be the target, and the
    // monster isn't just throwing the spell in a random direction,
    // we should be able to tell what the monster was aiming for if
    // we can see the monster's foe and the beam (or the beam path
    // implied by gesturing).  But only if the beam didn't actually hit
    // anything (but if it did hit something, why didn't that monster
    // show up in the beam's path?)
    if (target == "nothing"
        && (tracer.foe_info.count + tracer.friend_info.count) == 0
        && foe != NULL
        && you.can_see(foe)
        && !mons->confused()
        && visible_path)
    {
        target = foe->name(DESC_THE);
        targ_prep = (pbolt.aimed_at_spot ? "next to" : "past");
    }

    // If the monster gestures to create an invisible beam then
    // assume that anything close to the beam is the intended target.
    // Also, if the monster gestures to create a visible beam but it
    // misses still say that the monster gestured "at" the target,
    // rather than "past".
    if (gestured || target == "nothing")
        targ_prep = "at";

    // "throws whatever at something" is better than "at nothing"
    if (target == "nothing")
        target = "something";
}

void mons_cast_noise(monster* mons, const bolt &pbolt,
                     spell_type spell_cast, bool special_ability)
{
    bool force_silent = false;

    spell_type actual_spell = spell_cast;

    if (spell_cast == SPELL_DRACONIAN_BREATH)
    {
        monster_type type = mons->type;
        if (mons_genus(type) == MONS_DRACONIAN)
            type = draco_or_demonspawn_subspecies(mons);

        switch (type)
        {
        case MONS_MOTTLED_DRACONIAN:
            actual_spell = SPELL_STICKY_FLAME_SPLASH;
            break;

        case MONS_YELLOW_DRACONIAN:
            actual_spell = SPELL_SPIT_ACID;
            break;

        case MONS_PLAYER_GHOST:
            // Draining breath is silent.
            force_silent = true;
            break;

        default:
            break;
        }
    }
    else if (mons->type == MONS_SHADOW_DRAGON)
        // Draining breath is silent.
        force_silent = true;

    const bool unseen    = !you.can_see(mons);
    const bool silent    = silenced(mons->pos()) || force_silent;
    const bool no_silent = mons_class_flag(mons->type, M_SPELL_NO_SILENT);

    if (unseen && silent)
        return;

    const unsigned int flags = get_spell_flags(actual_spell);

    const bool priest = mons->is_priest();
    const bool wizard = mons->is_actual_spellcaster();
    const bool innate = !(priest || wizard || no_silent)
                        || (flags & SPFLAG_INNATE) || special_ability;

    int noise = _noise_level(mons, actual_spell, silent, innate);

    const bool targeted = (flags & SPFLAG_TARGETING_MASK)
                           && (pbolt.target != mons->pos()
                               || pbolt.visible());

    vector<string> key_list;
    unsigned int num_spell_keys =
        _noise_keys(key_list, mons, pbolt, actual_spell,
                    priest, wizard, innate, targeted);

    string msg = _noise_message(key_list, num_spell_keys, silent, unseen);

    if (msg.empty())
    {
        if (silent)
            return;

        noisy(noise, mons->pos(), mons->mindex());
        return;
    }

    // FIXME: we should not need to look at the message text.
    const bool gestured = msg.find("Gesture") != string::npos
                          || msg.find(" gesture") != string::npos
                          || msg.find("Point") != string::npos
                          || msg.find(" point") != string::npos;

    string targ_prep = "at";
    string target    = "NO_TARGET";

    if (targeted)
        _noise_fill_target(targ_prep, target, mons, pbolt, gestured);

    msg = replace_all(msg, "@at@",     targ_prep);
    msg = replace_all(msg, "@target@", target);

    string beam_name;
    if (!targeted)
        beam_name = "NON TARGETED BEAM";
    else if (pbolt.name.empty())
        beam_name = "INVALID BEAM";
    else
        beam_name = pbolt.get_short_name();

    msg = replace_all(msg, "@beam@", beam_name);

    const msg_channel_type chan =
        (unseen              ? MSGCH_SOUND :
         mons->friendly()    ? MSGCH_FRIEND_SPELL
                             : MSGCH_MONSTER_SPELL);

    if (silent)
        mons_speaks_msg(mons, msg, chan, true);
    else if (noisy(noise, mons->pos(), mons->mindex()) || !unseen)
    {
        // noisy() returns true if the player heard the noise.
        mons_speaks_msg(mons, msg, chan);
    }
}
