/*
 *  File:       beam.cc
 *  Summary:    Functions related to ranged attacks.
 *  Written by: Linley Henzell
 *
 *  Modified for Crawl Reference by $Author: zelgadis $ on $Date: 2008-12-15 22:58:13 -0500 (Mon, 15 Dec 2008) $
 *
 *  Change History (most recent first):
 *
 *   <7>    21mar2001    GDL    Replaced all FP arithmetic with integer*100 math
 *   <6>    07jan2001    GDL    complete rewrite.
 *   <5>    22July2000   GDL    allowed 'dummy' missiles from monsters
 *   <4>    11/14/99     cdl    evade beams with random40(ev) vice random2(ev)
 *                              all armour now protects against shrapnel
 *   <3>     6/ 2/99     DML    Added enums
 *   <2>     5/20/99     BWR    Added refreshs for curses
 *   <1>     -/--/--     LRH    Created
 */

#include "AppHdr.h"
#include "beam.h"

#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <cstdarg>
#include <iostream>
#include <set>
#include <algorithm>

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

#include "externs.h"

#include "cio.h"
#include "cloud.h"
#include "delay.h"
#include "effects.h"
#include "enum.h"
#include "fight.h"
#include "item_use.h"
#include "it_use2.h"
#include "items.h"
#include "itemname.h"
#include "itemprop.h"
#include "message.h"
#include "misc.h"
#include "monplace.h"
#include "monstuff.h"
#include "mon-util.h"
#include "mstuff2.h"
#include "mutation.h"
#include "ouch.h"
#include "player.h"
#include "religion.h"
#include "skills.h"
#include "spells1.h"
#include "spells3.h"
#include "spells4.h"
#include "state.h"
#include "stuff.h"
#include "terrain.h"
#include "traps.h"
#include "tutorial.h"
#include "view.h"
#include "xom.h"

#include "tiles.h"

#define BEAM_STOP       1000        // all beams stopped by subtracting this
                                    // from remaining range
#define MON_RESIST      0           // monster resisted
#define MON_UNAFFECTED  1           // monster unaffected
#define MON_AFFECTED    2           // monster was affected
#define MON_OTHER       3           // monster unaffected, but for other reasons

static int spreadx[] = { 0, 0, 1, -1 };
static int spready[] = { -1, 1, 0, 0 };
static int opdir[]   = { 2, 1, 4, 3 };
static FixedArray < bool, 19, 19 > explode_map;

// Helper functions (some of these should probably be public).
static void _sticky_flame_monster(int mn, kill_category who, int hurt_final);
static bool _affects_wall(const bolt &beam, int wall_feature);
static bool _isBouncy(bolt &beam, unsigned char gridtype);
static int _beam_source(const bolt &beam);
static std::string _beam_zapper(const bolt &beam);
static bool _beam_term_on_target(bolt &beam, int x, int y);
static void _beam_explodes(bolt &beam, int x, int y);
static int  _affect_wall(bolt &beam, int x, int y);
static int  _affect_place_clouds(bolt &beam, int x, int y);
static void _affect_place_explosion_clouds(bolt &beam, int x, int y);
static int  _affect_player(bolt &beam, item_def *item = NULL);
static int  _affect_monster(bolt &beam, monsters *mon, item_def *item = NULL);
static int  _affect_monster_enchantment(bolt &beam, monsters *mon);
static void _beam_paralyses_monster( bolt &pbolt, monsters *monster );
static void _beam_petrifies_monster( bolt &pbolt, monsters *monster );
static int  _range_used_on_hit(bolt &beam);
static void _explosion1(bolt &pbolt);
static void _explosion_map(bolt &beam, int x, int y,
                           int count, int dir, int r);
static void _explosion_cell(bolt &beam, int x, int y, bool drawOnly);

static void _ench_animation(int flavour, const monsters *mon = NULL,
                            bool force = false);
static void _zappy(zap_type z_type, int power, bolt &pbolt);
static void _monster_die(monsters *mons, const bolt &beam);
static bool _nasty_beam(monsters *mon, bolt &beam);
static bool _nice_beam(monsters *mon, bolt &beam);

static std::set<std::string> beam_message_cache;

static bool _beam_is_blockable(bolt &pbolt)
{
    // BEAM_ELECTRICITY is added here because chain lighting is not
    // a true beam (stops at the first target it gets to and redirects
    // from there)... but we don't want it shield blockable.
    return (!pbolt.is_beam && !pbolt.is_explosion
            && pbolt.flavour != BEAM_ELECTRICITY);
}

// Kludge to suppress multiple redundant messages for a single beam.
static void _beam_mpr(msg_channel_type channel, const char *s, ...)
{
    va_list args;
    va_start(args, s);

    char buf[500];
    vsnprintf(buf, sizeof buf, s, args);

    va_end(args);

    std::string message = buf;
    if (beam_message_cache.find(message) == beam_message_cache.end())
        mpr(message.c_str(), channel);

    beam_message_cache.insert(message);
}

static void _monster_die(monsters *mons, const bolt &beam)
{
    monster_die(mons, beam.killer(), beam.beam_source);
}

static kill_category _whose_kill(const bolt &beam)
{
    if (YOU_KILL(beam.thrower))
        return (KC_YOU);
    else if (MON_KILL(beam.thrower))
    {
        if (beam.beam_source == ANON_FRIENDLY_MONSTER)
            return (KC_FRIENDLY);
        if (!invalid_monster_index(beam.beam_source))
        {
            const monsters *mon = &menv[beam.beam_source];
            if (mons_friendly(mon))
                return (KC_FRIENDLY);
        }
    }
    return (KC_OTHER);
}

// A simple animated flash from Rupert Smith (expanded to be more
// generic).
void zap_animation(int colour, const monsters *mon, bool force)
{
    int x = you.x_pos, y = you.y_pos;

    // Default to whatever colour magic is today.
    if (colour == -1)
        colour = element_colour(EC_MAGIC);

    if (mon)
    {
        if (!force && !player_monster_visible( mon ))
            return;

        x = mon->x;
        y = mon->y;
    }

    if (!see_grid( x, y ))
        return;

    const int drawx = grid2viewX(x);
    const int drawy = grid2viewY(y);

    if (in_los_bounds(drawx, drawy))
    {
#ifdef USE_TILE
        TileDrawBolt(drawx-1, drawy-1, tileidx_zap(colour));
#else
        view_update();
        cgotoxy(drawx, drawy, GOTO_DNGN);
        put_colour_ch(colour, dchar_glyph(DCHAR_FIRED_ZAP));
#endif

        update_screen();
        delay(50);
    }
}

// Special front function for zap_animation to interpret enchantment flavours.
static void _ench_animation( int flavour, const monsters *mon, bool force )
{
    const int elem = (flavour == BEAM_HEALING)       ? EC_HEAL :
                     (flavour == BEAM_PAIN)          ? EC_UNHOLY :
                     (flavour == BEAM_DISPEL_UNDEAD) ? EC_HOLY :
                     (flavour == BEAM_POLYMORPH)     ? EC_MUTAGENIC :
                     (flavour == BEAM_TELEPORT
                        || flavour == BEAM_BANISH
                        || flavour == BEAM_BLINK)    ? EC_WARP
                                                     : EC_ENCHANT;

    zap_animation( element_colour( elem ), mon, force );
}

static void _beam_set_default_values(bolt &beam, int power)
{
    beam.range          = 8 + random2(5);    // default for "0" beams (I think)
    beam.rangeMax       = 0;
    beam.hit            = 0;                 // default for "0" beams (I think)
    beam.damage         = dice_def( 1, 0 );  // default for "0" beams (I think)
    beam.type           = 0;                 // default for "0" beams
    beam.flavour        = BEAM_MAGIC;        // default for "0" beams
    beam.ench_power     = power;
    beam.obvious_effect = false;
    beam.is_beam        = false;             // default for all beams.
    beam.is_tracer      = false;             // default for all player beams
    beam.thrower        = KILL_YOU_MISSILE;  // missile from player
    beam.aux_source.clear();                 // additional source info, unused
}

// If needs_tracer is true, we need to check the beam path for friendly
// monsters for *player beams* only! If allies are found, the player is
// prompted to stop or continue.
bool zapping(zap_type ztype, int power, bolt &pbolt, bool needs_tracer,
             std::string msg)
{

#if DEBUG_DIAGNOSTICS
    mprf(MSGCH_DIAGNOSTICS, "zapping: power=%d", power );
#endif

    // GDL: note that rangeMax is set to 0, which means that max range is
    // equal to range.  This is OK, since rangeMax really only matters for
    // stuff monsters throw/zap.

    // All of the following settings might be changed by _zappy().
    _beam_set_default_values(pbolt, power);

    // For player bolts, check whether tracer goes through friendlies.
    // NOTE: Whenever zapping() is called with a randomized value for power
    // (or effect), player_tracer should be called directly with the highest
    // power possible respecting current skill, experience level etc.

    if (needs_tracer && pbolt.thrower == KILL_YOU_MISSILE
        && !player_tracer(ztype, power, pbolt))
    {
        return (false);
    }

    // Fill in the bolt structure.
    _zappy( ztype, power, pbolt );

    if (!msg.empty())
        mpr(msg.c_str());

    if (ztype == ZAP_LIGHTNING)
    {
        // XXX: Needs to check silenced at other location, too. {dlb}
        noisy(25, you.x_pos, you.y_pos, "You hear a mighty clap of thunder!");
    }
    else if (ztype == ZAP_DIGGING)
        pbolt.aimed_at_spot = false;

    fire_beam(pbolt);

    return (true);
}

// pbolt needs to be initialized for tracing: with the the maximum range,
// and the flavour to allow for completely resistant monsters.
static void _get_max_range( zap_type z_type, int power, bolt &pbolt )
{
    // sorted by range
    switch (z_type)
    {
    case ZAP_SMALL_SANDBLAST:
        pbolt.rangeMax       = 2;
        pbolt.flavour        = BEAM_FRAG;                     // extra AC resist
        break;

    case ZAP_SANDBLAST:
        if (power > 50)
            power = 50;

//      pbolt.range          = 2 + random2(power) / 20;
        pbolt.rangeMax       = 2 + (power-1) / 20; // max 4
        pbolt.flavour        = BEAM_FRAG;                     // extra AC resist
        break;

    case ZAP_FLAME_TONGUE:
        if (power > 25)
            power = 25;

//      pbolt.range          = 1 + random2(2) + random2(power) / 10;
        pbolt.rangeMax       = 2 + (power-1) / 10; // max 4
        pbolt.flavour        = BEAM_FIRE;
        break;

    case ZAP_CLEANSING_FLAME:
        pbolt.name           = "golden flame";
        pbolt.rangeMax       = 7;
        pbolt.flavour        = BEAM_HOLY;
        pbolt.is_explosion   = true;
        break;

    case ZAP_MAGMA:
        pbolt.rangeMax       = 8;
        pbolt.flavour        = BEAM_LAVA;
        pbolt.is_beam        = true;
        break;

    case ZAP_IRON_BOLT:
        pbolt.rangeMax       = 9;
        pbolt.flavour        = BEAM_MMISSILE;               // unresistable
        break;

    case ZAP_CRYSTAL_SPEAR:
        pbolt.rangeMax       = 9;
        pbolt.flavour        = BEAM_MMISSILE;                // unresistable
        break;

    case ZAP_SPIT_POISON:
        if (power > 50)
            power = 50;

//      pbolt.range          = 3 + random2( 1 + power / 2 );
        pbolt.rangeMax       = 3 + power / 2;
        if (pbolt.rangeMax > 9)
            pbolt.rangeMax = 9;

        pbolt.flavour        = BEAM_POISON;
        break;

    case ZAP_BREATHE_FIRE:
        if (power > 50)
            power = 50;

//      pbolt.range          = 3 + random2( 1 + power / 2 );
        pbolt.rangeMax       = 3 + power / 2;
        if (pbolt.rangeMax > 9)
            pbolt.rangeMax = 9;

        pbolt.flavour        = BEAM_FIRE;
        pbolt.is_beam        = true;
        break;

    case ZAP_BREATHE_FROST:
        if (power > 50)
            power = 50;

//      pbolt.range          = 3 + random2( 1 + power / 2 );
        pbolt.rangeMax       = 3 + power / 2;
        if (pbolt.rangeMax > 9)
            pbolt.rangeMax = 9;

        pbolt.flavour        = BEAM_COLD;
        pbolt.is_beam        = true;
        break;

    case ZAP_BREATHE_ACID:
        if (power > 50)
            power = 50;

//      pbolt.range          = 3 + random2( 1 + power / 2 );
        pbolt.rangeMax       = 3 + power / 2;
        if (pbolt.rangeMax > 9)
            pbolt.rangeMax = 9;

        pbolt.flavour        = BEAM_ACID;
        pbolt.is_beam        = true;
        break;

    case ZAP_BREATHE_POISON:    // leaves clouds of gas
        if (power > 50)
            power = 50;

//      pbolt.range          = 3 + random2( 1 + power / 2 );
        pbolt.rangeMax       = 3 + power / 2;
        if (pbolt.rangeMax > 9)
            pbolt.rangeMax = 9;

        pbolt.flavour        = BEAM_POISON;
        pbolt.is_beam        = true;
        break;

    case ZAP_BREATHE_POWER:
        if (power > 50)
            power = 50;

//      pbolt.range          = 6 + random2( 1 + power / 2 );
        pbolt.rangeMax       = 6 + power / 2;
        if (pbolt.rangeMax > 9)
            pbolt.rangeMax = 9;

        pbolt.flavour        = BEAM_MMISSILE;                  // unresistable
        pbolt.is_beam        = true;
        break;

    case ZAP_BREATHE_STEAM:
        pbolt.rangeMax       = 9;
        pbolt.flavour        = BEAM_STEAM;
        pbolt.is_beam        = true;
        break;

    case ZAP_STRIKING:
    case ZAP_MAGIC_DARTS:
    case ZAP_STONE_ARROW:
    case ZAP_MYSTIC_BLAST:
        pbolt.rangeMax       = 12;
        pbolt.flavour        = BEAM_MMISSILE;               // unresistable
        break;

    case ZAP_STING:
    case ZAP_POISON_ARROW:
        pbolt.rangeMax       = 12;
        pbolt.flavour        = BEAM_POISON;
        break;

    case ZAP_FLAME:
    case ZAP_STICKY_FLAME:
        pbolt.rangeMax       = 12;
        pbolt.flavour        = BEAM_FIRE;
        break;

    case ZAP_FROST:
        pbolt.rangeMax       = 12;
        pbolt.flavour        = BEAM_COLD;
        break;

    case ZAP_ICE_BOLT:
        pbolt.rangeMax       = 12;
        pbolt.flavour        = BEAM_ICE;                     // half resistable
        break;

    case ZAP_ELECTRICITY:
        pbolt.rangeMax       = 13;
        pbolt.flavour        = BEAM_ELECTRICITY;           // beams & reflects
        pbolt.is_beam        = true;
        break;

    case ZAP_DISRUPTION:
    case ZAP_DISINTEGRATION:
        pbolt.name           = "0";
        pbolt.rangeMax       = 14;
        pbolt.flavour        = BEAM_DISINTEGRATION;
        break;

    case ZAP_PAIN:
        pbolt.name           = "0";
        pbolt.rangeMax       = 14;
        pbolt.flavour        = BEAM_PAIN;
        break;

    case ZAP_DISPEL_UNDEAD:
        pbolt.name           = "0";
        pbolt.rangeMax       = 14;
        pbolt.flavour        = BEAM_DISPEL_UNDEAD;
        break;

    case ZAP_FIRE:
        pbolt.rangeMax       = 16;
        pbolt.flavour        = BEAM_FIRE;
        pbolt.is_beam        = true;
        break;

    case ZAP_BONE_SHARDS:
        pbolt.rangeMax       = 16;
        pbolt.flavour        = BEAM_MAGIC;                      // unresisted
        pbolt.is_beam        = true;
        break;

    case ZAP_COLD:
        pbolt.rangeMax       = 16;
        pbolt.flavour        = BEAM_COLD;
        pbolt.is_beam        = true;
        break;

    case ZAP_NEGATIVE_ENERGY:
        pbolt.rangeMax       = 16;
        pbolt.flavour        = BEAM_NEG;                     // drains levels
        pbolt.is_beam        = true;
        break;

    case ZAP_BEAM_OF_ENERGY:    // bolt of innacuracy
        pbolt.range          = 16;
        pbolt.flavour        = BEAM_ENERGY;                    // unresisted
        pbolt.is_beam        = true;
        break;

    case ZAP_VENOM_BOLT:
        pbolt.rangeMax       = 17;
        pbolt.flavour        = BEAM_POISON;
        pbolt.is_beam        = true;
        break;

    case ZAP_LIGHTNING:
        pbolt.rangeMax       = 17;
        pbolt.flavour        = BEAM_ELECTRICITY;           // beams & reflects
        pbolt.is_beam        = true;
        break;

    // enchantments
    case ZAP_ENSLAVEMENT:
        pbolt.name           = "0";
        pbolt.rangeMax       = 11;
        pbolt.flavour        = BEAM_CHARM;
        break;

    case ZAP_BANISHMENT:
        pbolt.name           = "0";
        pbolt.rangeMax       = 11;
        pbolt.flavour        = BEAM_BANISH;
        break;

    case ZAP_DEGENERATION:
        pbolt.name           = "0";
        pbolt.rangeMax       = 11;
        pbolt.flavour        = BEAM_DEGENERATE;
        break;

    case ZAP_ENSLAVE_UNDEAD:
        pbolt.name           = "0";
        pbolt.rangeMax       = 11;
        pbolt.flavour        = BEAM_ENSLAVE_UNDEAD;
        break;

    case ZAP_CONTROL_DEMON:
        pbolt.name           = "0";
        pbolt.rangeMax       = 11;
        pbolt.flavour        = BEAM_ENSLAVE_DEMON;
        break;

    case ZAP_SLEEP:
        pbolt.name           = "0";
        pbolt.rangeMax       = 11;
        pbolt.flavour        = BEAM_SLEEP;
        break;

    case ZAP_BACKLIGHT:
        pbolt.name           = "0";
        pbolt.rangeMax       = 11;
        pbolt.flavour        = BEAM_BACKLIGHT;
        break;

    case ZAP_SLOWING:
        pbolt.name           = "0";
        pbolt.rangeMax       = 12;
        pbolt.flavour        = BEAM_SLOW;
        break;

    case ZAP_HASTING:
        pbolt.name           = "0";
        pbolt.rangeMax       = 12;
        pbolt.flavour        = BEAM_HASTE;
        break;

    case ZAP_PARALYSIS:
        pbolt.name           = "0";
        pbolt.rangeMax       = 12;
        pbolt.flavour        = BEAM_PARALYSIS;
        break;

    case ZAP_PETRIFY:
        pbolt.name           = "0";
        pbolt.rangeMax       = 12;
        pbolt.flavour        = BEAM_PETRIFY;
        break;

    case ZAP_CONFUSION:
        pbolt.name           = "0";
        pbolt.rangeMax       = 12;
        pbolt.flavour        = BEAM_CONFUSION;
        break;

    case ZAP_INVISIBILITY:
        pbolt.name           = "0";
        pbolt.rangeMax       = 12;
        pbolt.flavour        = BEAM_INVISIBILITY;
        break;

    case ZAP_HEALING:
        pbolt.name           = "0";
        pbolt.rangeMax       = 12;
        pbolt.flavour        = BEAM_HEALING;
        break;

    case ZAP_TELEPORTATION:
        pbolt.name           = "0";
        pbolt.rangeMax       = 13;
        pbolt.flavour        = BEAM_TELEPORT;
        break;

    case ZAP_POLYMORPH_OTHER:
        pbolt.name           = "0";
        pbolt.rangeMax       = 13;
        pbolt.flavour        = BEAM_POLYMORPH;
        break;

    case ZAP_AGONY:
        pbolt.name           = "0agony";
        pbolt.rangeMax       = 14;
        pbolt.flavour        = BEAM_PAIN;
        break;

    case ZAP_DIGGING:
        pbolt.name           = "0";
//        pbolt.range          = 3 + random2( power / 5 ) + random2(5);
        pbolt.rangeMax       = 6 + power / 5;
        pbolt.flavour        = BEAM_DIGGING;
        pbolt.is_beam        = true;
        break;

    // explosions
    case ZAP_FIREBALL:
        pbolt.name           = "fireball";
        pbolt.rangeMax       = 12;
        pbolt.flavour        = BEAM_FIRE;                      // fire
        pbolt.is_explosion   = true;
        break;

    case ZAP_ICE_STORM:
        pbolt.name           = "great blast of cold";
        pbolt.rangeMax       = 13;
        pbolt.ench_power     = power;              // used for radius
        pbolt.flavour        = BEAM_ICE;           // half resisted
        pbolt.is_explosion   = true;
        break;

    case ZAP_ORB_OF_FRAGMENTATION:                      // cap 150
        pbolt.name           = "metal orb";
        pbolt.rangeMax       = 16;
        pbolt.flavour        = BEAM_FRAG;                     // extra AC resist
        pbolt.is_explosion   = true;
        break;

    case ZAP_HELLFIRE:
        pbolt.rangeMax       = 16;
        pbolt.flavour        = BEAM_HELLFIRE;
        pbolt.is_explosion   = true;
        break;

    case ZAP_ORB_OF_ELECTRICITY:                        // cap 150
        pbolt.name           = "orb of electricity";
        pbolt.rangeMax       = 20;
        pbolt.flavour        = BEAM_ELECTRICITY;
        pbolt.is_explosion   = true;
        break;

    case ZAP_DEBUGGING_RAY:
    default: // buggy beam
        pbolt.rangeMax       = 16;
        pbolt.flavour        = BEAM_MMISSILE;                  // unresistable
        break;
    }
}

// Returns true if the path is considered "safe", and false if there are
// monsters in the way the player doesn't want to hit.
// NOTE: Doesn't check for the player being hit by a rebounding lightning bolt.
bool player_tracer( zap_type ztype, int power, bolt &pbolt, int range)
{
    // Non-controlleable during confusion.
    // (We'll shoot in a different direction anyway.)
    if (you.duration[DUR_CONF])
        return (true);

    _beam_set_default_values(pbolt, power);
    pbolt.name = "unimportant";
    _get_max_range(ztype, power, pbolt);

    // Override range if necessary.
    if (range > 0)
        pbolt.rangeMax = range;

    pbolt.is_tracer      = true;
    pbolt.source_x       = you.x_pos;
    pbolt.source_y       = you.y_pos;
    pbolt.can_see_invis  = player_see_invis();
    pbolt.smart_monster  = true;
    pbolt.attitude       = ATT_FRIENDLY;

    // Init tracer variables.
    pbolt.foe_count      = pbolt.fr_count = 0;
    pbolt.foe_power      = pbolt.fr_power = 0;
    pbolt.fr_helped      = pbolt.fr_hurt  = 0;
    pbolt.foe_helped     = pbolt.foe_hurt = 0;
    pbolt.foe_ratio      = 100;
    pbolt.beam_cancelled = false;
    pbolt.dont_stop_foe  = pbolt.dont_stop_fr = pbolt.dont_stop_player = false;

    fire_beam(pbolt);

    // Should only happen if the player answered 'n' to one of those
    // "Fire through friendly?" prompts.
    if (pbolt.beam_cancelled)
    {
#if DEBUG_DIAGNOSTICS
        mprf(MSGCH_DIAGNOSTICS, "%s", "Beam cancelled.");
#endif
        canned_msg(MSG_OK);
        you.turn_is_over = false;
        return (false);
    }

    // Set to non-tracing for actual firing.
    pbolt.is_tracer = false;
    return (true);
}

dice_def calc_dice( int num_dice, int max_damage )
{
    dice_def    ret( num_dice, 0 );

    if (num_dice <= 1)
    {
        ret.num  = 1;
        ret.size = max_damage;
    }
    else if (max_damage <= num_dice)
    {
        ret.num  = max_damage;
        ret.size = 1;
    }
    else
    {
        // Divide the damage among the dice, and add one
        // occasionally to make up for the fractions. -- bwr
        ret.size  = max_damage / num_dice;
        ret.size += x_chance_in_y(max_damage % num_dice, num_dice);
    }

    return (ret);
}

// Need to see zapping() for default values not set within this function {dlb}
static void _zappy( zap_type z_type, int power, bolt &pbolt )
{
    int temp_rand = 0;          // probability determination {dlb}

    // Note: The incoming power is not linear in the case of spellcasting.
    // The power curve currently allows for the character to reasonably
    // get up to a power level of about a 100, but more than that will
    // be very hard (and the maximum is 200).  The low level power caps
    // provide the useful feature in that they allow for low level spells
    // to have quick advancement, but don't cause them to obsolete the
    // higher level spells. -- bwr
    //
    // I've added some example characters below to show how little
    // people should be concerned about the power caps.
    //
    // The example characters are simplified to three stats:
    //
    // - Intelligence: This magnifies power, it's very useful.
    //
    // - Skills: This represents the character having Spellcasting
    //   and the average of the component skills at this level.
    //   Although, Spellcasting probably isn't quite as high as
    //   other spell skills for a lot of characters, note that it
    //   contributes much less to the total power (about 20%).
    //
    // - Enhancers:  These are equipment that the player can use to
    //   apply additional magnifiers (x1.5) to power.  There are
    //   also inhibitors that reduce power (/2.0), but we're not
    //   concerned about those here.  Anyways, the character can
    //   currently have up to 3 levels (for x1.5, x2.25, x3.375).
    //   The lists below should help to point out the difficulty
    //   and cost of getting more than one level of enhancement.
    //
    //   Here's a list of current magnifiers:
    //
    //   - rings of fire/cold
    //   - staff of fire/cold/air/earth/poison/death/conjure/enchant/summon
    //   - staff of Olgreb (poison)
    //   - robe of the Archmagi (necro, conjure, enchant, summon)
    //   - Mummy intrinsic (+1 necromancy at level 13, +2 at level 26)
    //   - Necromutation (+1 to necromancy -- note: undead can't use this)
    //   - Ring of Fire (+1 to fire)
    //
    //   The maximum enhancement, by school (but capped at 3):
    //
    //   - Necromancy:  4 (Mummies), 3 (others)
    //   - Fire:        4
    //   - Cold:        3
    //   - Conjuration: 2
    //   - Enchantment: 2
    //   - Summoning:   2
    //   - Air:         1
    //   - Earth:       1
    //   - Poison:      1
    //   - Translocations, Transmigrations, Divinations intentionally 0

    switch (z_type)
    {
    // level 1
    //
    // This cap is to keep these easy and very cheap spells from
    // becoming too powerful.
    //
    // Example characters with about 25 power:
    //
    // - int  5, skills 20, 0 enhancers
    // - int  5, skills 14, 1 enhancer
    // - int 10, skills 10, 0 enhancers
    // - int 10, skills  7, 1 enhancers
    // - int 15, skills  7, 0 enhancers
    // - int 20, skills  6, 0 enhancers
    case ZAP_STRIKING:
    case ZAP_MAGIC_DARTS:
    case ZAP_STING:
    case ZAP_ELECTRICITY:
    case ZAP_FLAME_TONGUE:
    case ZAP_SMALL_SANDBLAST:
    case ZAP_DISRUPTION:                // ench_power boosted below
    case ZAP_PAIN:                      // ench_power boosted below
        if (power > 25)
            power = 25;
        break;

    // level 2/3
    //
    // The following examples should make it clear that in the
    // early game this cap is only limiting to serious spellcasters
    // (they could easily reach the 20-10-0 example).
    //
    // Example characters with about 50 power:
    //
    // - int 10, skills 20, 0 enhancers
    // - int 10, skills 14, 1 enhancer
    // - int 15, skills 14, 0 enhancers
    // - int 15, skills 10, 1 enhancer
    // - int 20, skills 10, 0 enhancers
    // - int 20, skills  7, 1 enhancer
    // - int 25, skills  8, 0 enhancers
    case ZAP_SANDBLAST:
    case ZAP_FLAME:             // also ability (pow = lev * 2)
    case ZAP_FROST:             // also ability (pow = lev * 2)
    case ZAP_STONE_ARROW:
        if (power > 50)
            power = 50;
        break;

    // Here are some examples that show that its fairly safe to assume
    // that a high level character can easily have 75 power.
    //
    // Example characters with about 75 power:
    //
    // - int 10, skills 27, 1 enhancer
    // - int 15, skills 27, 0 enhancers
    // - int 15, skills 16, 1 enhancer
    // - int 20, skills 20, 0 enhancers
    // - int 20, skills 14, 1 enhancer
    // - int 25, skills 16, 0 enhancers

    // level 4
    //
    // The following examples should make it clear that this is the
    // effective maximum power.  Its not easy to get to 100 power,
    // but 20-20-1 or 25-16-1 is certainly attainable by a high level
    // spellcaster.  As you can see from the examples at 150 and 200,
    // getting much power beyond this is very difficult.
    //
    // Level 3 and 4 spells cannot be overpowered.
    //
    // Example characters with about 100 power:
    //
    // - int 10, skills 27, 2 enhancers
    // - int 15, skills 27, 1 enhancer
    // - int 20, skills 20, 1 enhancer
    // - int 25, skills 24, 0 enhancers
    // - int 25, skills 16, 1 enhancer
    case ZAP_MYSTIC_BLAST:
    case ZAP_STICKY_FLAME:
    case ZAP_ICE_BOLT:
    case ZAP_DISPEL_UNDEAD:     // ench_power raised below
        if (power > 100)
            power = 100;
        break;

    // levels 5-7
    //
    // These spells used to be capped, but its very hard to raise
    // power over 100, and these examples should show that.
    // Only the twinkiest of characters are expected to get to 150.
    //
    // Example characters with about 150 power:
    //
    // - int 15, skills 27, 3 enhancers (actually, only 146)
    // - int 20, skills 27, 2 enhancers (actually, only 137)
    // - int 20, skills 21, 3 enhancers
    // - int 25, skills 26, 2 enhancers
    // - int 30, skills 21, 2 enhancers
    // - int 40, skills 24, 1 enhancer
    // - int 70, skills 20, 0 enhancers
    case ZAP_FIRE:
    case ZAP_COLD:
    case ZAP_VENOM_BOLT:
    case ZAP_MAGMA:
    case ZAP_AGONY:
    case ZAP_LIGHTNING:                 // also invoc * 6 or lev * 2 (abils)
    case ZAP_NEGATIVE_ENERGY:           // also ability (pow = lev * 6)
    case ZAP_IRON_BOLT:
    case ZAP_DISINTEGRATION:
    case ZAP_FIREBALL:
    case ZAP_ORB_OF_ELECTRICITY:
    case ZAP_ORB_OF_FRAGMENTATION:
    case ZAP_POISON_ARROW:
        // if (power > 150)
        //     power = 150;
        break;

    // levels 8-9
    //
    // These spells are capped at 200 (which is the cap in calc_spell_power).
    // As an example of how little of a cap that is, consider the fact
    // that a 70-27-3 character has an uncapped power of 251.  Characters
    // are never expected to get to this cap.
    //
    // Example characters with about 200 power:
    //
    // - int 30, skills 27, 3 enhancers (actually, only 190)
    // - int 40, skills 27, 2 enhancers (actually, only 181)
    // - int 40, skills 23, 3 enhancers
    // - int 70, skills 27, 0 enhancers (actually, only 164)
    // - int 70, skills 27, 1 enhancers (actually, only 194)
    // - int 70, skills 20, 2 enhancers
    // - int 70, skills 13, 3 enhancers
    case ZAP_CRYSTAL_SPEAR:
    case ZAP_HELLFIRE:
    case ZAP_ICE_STORM:
    case ZAP_CLEANSING_FLAME:
        // if (power > 200)
        //     power = 200;
        break;

    // unlimited power (needs a good reason)
    case ZAP_BONE_SHARDS:    // incoming power is modified for mass
    case ZAP_BEAM_OF_ENERGY: // inaccuracy (only on staff, hardly hits)
        break;

    // natural/mutant breath/spit powers (power ~= characer level)
    case ZAP_SPIT_POISON:               // lev + mut * 5
    case ZAP_BREATHE_FIRE:              // lev + mut * 4 + 12 (if dragonform)
    case ZAP_BREATHE_FROST:             // lev
    case ZAP_BREATHE_ACID:              // lev (or invoc * 3 from minor destr)
    case ZAP_BREATHE_POISON:            // lev
    case ZAP_BREATHE_POWER:             // lev
    case ZAP_BREATHE_STEAM:             // lev
        if (power > 50)
            power = 50;
        break;

    // enchantments and other resistable effects
    case ZAP_SLOWING:
    case ZAP_HASTING:
    case ZAP_PARALYSIS:
    case ZAP_PETRIFY:
    case ZAP_BACKLIGHT:
    case ZAP_SLEEP:
    case ZAP_CONFUSION:
    case ZAP_INVISIBILITY:
    case ZAP_ENSLAVEMENT:
    case ZAP_TELEPORTATION:
    case ZAP_DIGGING:
    case ZAP_POLYMORPH_OTHER:
    case ZAP_DEGENERATION:
    case ZAP_BANISHMENT:
        // This is the only power that matters.  We magnify it apparently
        // to get values that work better with magic resistance checks...
        // those checks will scale down this value and max it out at 120.
        pbolt.ench_power *= 3;
        pbolt.ench_power /= 2;
        break;

    // anything else we cap to 100
    default:
        if (power > 100)
            power = 100;
        break;
    }

    // Note:  I'm only displaying the top damage and such here, that's
    // because it's really not been known before (since the above caps
    // didn't exist), so they were all pretty much unlimited before.
    // Also note, that the high end damage occurs at the cap, only
    // players that are that powerful can get that damage... and
    // although these numbers might seem small, you should remember
    // that Dragons in this game are 60-90 hp monsters, and very
    // few monsters have more than 100 hp (and that 1d5 damage is
    // still capable of taking a good sized chunk (and possibly killing)
    // any monster you're likely to meet in the first three levels). -- bwr

    // Note: damage > 100 signals that "random2(damage - 100)" will be
    // applied three times, which not only ups the damage but gives
    // a more normal distribution.
    switch (z_type)
    {
    case ZAP_STRIKING:                                  // cap 25
        pbolt.name           = "force bolt";
        pbolt.colour         = BLACK;
        pbolt.range          = 8 + random2(5);
        pbolt.damage         = dice_def( 1, 5 );            // dam: 5
        pbolt.hit            = 8 + power / 10;              // 25: 10
        pbolt.type           = dchar_glyph(DCHAR_SPACE);
        pbolt.flavour        = BEAM_MMISSILE;               // unresistable
        pbolt.obvious_effect = true;
        break;

    case ZAP_MAGIC_DARTS:                               // cap 25
        pbolt.name           = "magic dart";
        pbolt.colour         = LIGHTMAGENTA;
        pbolt.range          = random2(5) + 8;
        pbolt.damage         = dice_def( 1, 3 + power / 5 ); // 25: 1d8
        pbolt.hit            = AUTOMATIC_HIT;                // hits always
        pbolt.type           = dchar_glyph(DCHAR_FIRED_ZAP);
        pbolt.flavour        = BEAM_MMISSILE;                // unresistable
        pbolt.obvious_effect = true;
        break;

    case ZAP_STING:                                     // cap 25
        pbolt.name           = "sting";
        pbolt.colour         = GREEN;
        pbolt.range          = 8 + random2(5);
        pbolt.damage         = dice_def( 1, 3 + power / 5 ); // 25: 1d8
        pbolt.hit            = 8 + power / 5;                // 25: 13
        pbolt.type           = dchar_glyph(DCHAR_FIRED_ZAP);
        pbolt.flavour        = BEAM_POISON;                  // extra damage
        pbolt.obvious_effect = true;
        break;

    case ZAP_ELECTRICITY:                               // cap 20
        pbolt.name           = "zap";
        pbolt.colour         = LIGHTCYAN;
        pbolt.range          = 6 + random2(8);               // extended in beam
        pbolt.damage         = dice_def( 1, 3 + random2(power) / 2 );// 25: 1d11
        pbolt.hit            = 8 + power / 7;                        // 25: 11
        pbolt.type           = dchar_glyph(DCHAR_FIRED_ZAP);
        pbolt.flavour        = BEAM_ELECTRICITY;             // beams & reflects
        pbolt.obvious_effect = true;
        pbolt.is_beam        = true;
        break;

    case ZAP_DISRUPTION:                                // cap 25
        pbolt.name           = "0";
        pbolt.flavour        = BEAM_DISINTEGRATION;
        pbolt.range          = 7 + random2(8);
        pbolt.damage         = dice_def( 1, 4 + power / 5 );    // 25: 1d9
        pbolt.ench_power    *= 3;
        break;

    case ZAP_PAIN:                                      // cap 25
        pbolt.name           = "0";
        pbolt.flavour        = BEAM_PAIN;
        pbolt.range          = 7 + random2(8);
        pbolt.damage         = dice_def( 1, 4 + power / 5 );    // 25: 1d9
        pbolt.ench_power    *= 7;
        pbolt.ench_power    /= 2;
        break;

    case ZAP_FLAME_TONGUE:                              // cap 25
        pbolt.name           = "flame";
        pbolt.colour         = RED;

        pbolt.range          = 1 + random2(2) + random2(power) / 10;
        if (pbolt.range > 4)
            pbolt.range = 4;

        pbolt.damage         = dice_def( 1, 8 + power / 4 );    // 25: 1d14
        pbolt.hit            = 7 + power / 6;                   // 25: 11
        pbolt.type           = dchar_glyph(DCHAR_FIRED_BOLT);
        pbolt.flavour        = BEAM_FIRE;
        pbolt.obvious_effect = true;
        break;

    case ZAP_SMALL_SANDBLAST:                           // cap 25
        pbolt.name           = "blast of ";

        temp_rand            = random2(4);
        pbolt.name          += (temp_rand == 0) ? "dust" :
                               (temp_rand == 1) ? "dirt" :
                               (temp_rand == 2) ? "grit" : "sand";

        pbolt.colour         = BROWN;
        pbolt.range          = (random2(power) > random2(30)) ? 2 : 1;
        pbolt.damage         = dice_def( 1, 8 + power / 4 );    // 25: 1d14
        pbolt.hit            = 8 + power / 5;                   // 25: 13
        pbolt.type           = dchar_glyph(DCHAR_FIRED_BOLT);
        pbolt.flavour        = BEAM_FRAG;                     // extra AC resist
        pbolt.obvious_effect = true;
        break;

    case ZAP_SANDBLAST:                                 // cap 50
        pbolt.name           = coinflip() ? "blast of rock" : "rocky blast";

        pbolt.colour         = BROWN;

        pbolt.range          = 2 + random2(power) / 20;
        if (pbolt.range > 4)
            pbolt.range = 4;

        pbolt.damage         = dice_def( 2, 4 + power / 3 );    // 25: 2d12
        pbolt.hit            = 13 + power / 10;                 // 25: 15
        pbolt.type           = dchar_glyph(DCHAR_FIRED_BOLT);
        pbolt.flavour        = BEAM_FRAG;                     // extra AC resist
        pbolt.obvious_effect = true;
        break;

    case ZAP_BONE_SHARDS:
        pbolt.name           = "spray of bone shards";
        pbolt.colour         = LIGHTGREY;
        pbolt.range          = 7 + random2(10);

        // Incoming power is highly dependant on mass (see spells3.cc).
        // Basic function is power * 15 + mass...  with the largest
        // available mass (3000) we get a power of 4500 at a power
        // level of 100 (for 3d20).
        pbolt.damage         = dice_def( 3, 2 + (power / 250) );
        pbolt.hit            = 8 + (power / 100);               // max hit: 53
        pbolt.type           = dchar_glyph(DCHAR_FIRED_ZAP);
        pbolt.flavour        = BEAM_MAGIC;                      // unresisted
        pbolt.obvious_effect = true;
        pbolt.is_beam        = true;
        break;

    case ZAP_FLAME:                                     // cap 50
        pbolt.name           = "puff of flame";
        pbolt.colour         = RED;
        pbolt.range          = 8 + random2(5);
        pbolt.damage         = dice_def( 2, 4 + power / 10 );// 25: 2d6  50: 2d9
        pbolt.hit            = 8 + power / 10;               // 25: 10   50: 13
        pbolt.type           = dchar_glyph(DCHAR_FIRED_ZAP);
        pbolt.flavour        = BEAM_FIRE;
        pbolt.obvious_effect = true;
        break;

    case ZAP_FROST:                                     // cap 50
        pbolt.name           = "puff of frost";
        pbolt.colour         = WHITE;
        pbolt.range          = 8 + random2(5);
        pbolt.damage         = dice_def( 2, 4 + power / 10 );// 25: 2d6  50: 2d9
        pbolt.hit            = 8 + power / 10;               // 25: 10   50: 13
        pbolt.type           = dchar_glyph(DCHAR_FIRED_ZAP);
        pbolt.flavour        = BEAM_COLD;
        pbolt.obvious_effect = true;
        break;

    case ZAP_STONE_ARROW:                               // cap 100
        pbolt.name           = "stone arrow";
        pbolt.colour         = LIGHTGREY;
        pbolt.range          = 8 + random2(5);
        pbolt.damage         = dice_def( 2, 5 + power / 7 );// 25: 2d8  50: 2d12
        pbolt.hit            = 8 + power / 10;              // 25: 10    50: 13
        pbolt.type           = dchar_glyph(DCHAR_FIRED_MISSILE);
        pbolt.flavour        = BEAM_MMISSILE;               // irresistible
        pbolt.obvious_effect = true;
        break;

    case ZAP_STICKY_FLAME:                              // cap 100
        pbolt.name           = "sticky flame";          // extra damage
        pbolt.colour         = RED;
        pbolt.range          = 8 + random2(5);
                               // 50: 2d7  100: 2d11
        pbolt.damage         = dice_def( 2, 3 + power / 12 );
                               // 50: 16   100: 21
        pbolt.hit            = 11 + power / 10;
        pbolt.type           = dchar_glyph(DCHAR_FIRED_ZAP);
        pbolt.flavour        = BEAM_FIRE;
        pbolt.obvious_effect = true;
        break;

    case ZAP_MYSTIC_BLAST:                              // cap 100
        pbolt.name           = "orb of energy";
        pbolt.colour         = LIGHTMAGENTA;
        pbolt.range          = 8 + random2(5);
        pbolt.damage         = calc_dice( 2, 15 + (power * 2) / 5 );
        pbolt.hit            = 10 + power / 7;               // 50: 17   100: 24
        pbolt.type           = dchar_glyph(DCHAR_FIRED_ZAP);
        pbolt.flavour        = BEAM_MMISSILE;                // unresistable
        pbolt.obvious_effect = true;
        break;

    case ZAP_ICE_BOLT:                                  // cap 100
        pbolt.name           = "bolt of ice";
        pbolt.colour         = WHITE;
        pbolt.range          = 8 + random2(5);
        pbolt.damage         = calc_dice( 3, 10 + power / 2 );
        pbolt.hit            = 9 + power / 12;               // 50: 13   100: 17
        pbolt.type           = dchar_glyph(DCHAR_FIRED_ZAP);
        pbolt.flavour        = BEAM_ICE;                     // half resistable
        break;

    case ZAP_DISPEL_UNDEAD:                             // cap 100
        pbolt.name           = "0";
        pbolt.flavour        = BEAM_DISPEL_UNDEAD;
        pbolt.range          = 7 + random2(8);
        pbolt.damage         = calc_dice( 3, 20 + (power * 3) / 4 );
        pbolt.ench_power    *= 3;
        pbolt.ench_power    /= 2;
        break;

    case ZAP_MAGMA:                                     // cap 150
        pbolt.name           = "bolt of magma";
        pbolt.colour         = RED;
        pbolt.range          = 5 + random2(4);
        pbolt.damage         = calc_dice( 4, 10 + (power * 3) / 5 );
        pbolt.hit            = 8 + power / 25;               // 50: 10   100: 14
        pbolt.type           = dchar_glyph(DCHAR_FIRED_ZAP);
        pbolt.flavour        = BEAM_LAVA;
        pbolt.obvious_effect = true;
        pbolt.is_beam        = true;
        break;

    case ZAP_FIRE:                                      // cap 150
        pbolt.name           = "bolt of fire";
        pbolt.colour         = RED;
        pbolt.range          = 7 + random2(10);
        pbolt.damage         = calc_dice( 6, 18 + power * 2 / 3 );
        pbolt.hit            = 10 + power / 25;              // 50: 12   100: 14
        pbolt.type           = dchar_glyph(DCHAR_FIRED_ZAP);
        pbolt.flavour        = BEAM_FIRE;
        pbolt.obvious_effect = true;
        pbolt.is_beam        = true;
        break;

    case ZAP_COLD:                                      // cap 150
        pbolt.name           = "bolt of cold";
        pbolt.colour         = WHITE;
        pbolt.range          = 7 + random2(10);
        pbolt.damage         = calc_dice( 6, 18 + power * 2 / 3 );
        pbolt.hit            = 10 + power / 25;              // 50: 12   100: 14
        pbolt.type           = dchar_glyph(DCHAR_FIRED_ZAP);
        pbolt.flavour        = BEAM_COLD;
        pbolt.obvious_effect = true;
        pbolt.is_beam        = true;
        break;

    case ZAP_VENOM_BOLT:                                // cap 150
        pbolt.name           = "bolt of poison";
        pbolt.colour         = LIGHTGREEN;
        pbolt.range          = 8 + random2(10);
        pbolt.damage         = calc_dice( 4, 15 + power / 2 );
        pbolt.hit            = 8 + power / 20;               // 50: 10   100: 13
        pbolt.type           = dchar_glyph(DCHAR_FIRED_ZAP);
        pbolt.flavour        = BEAM_POISON;                  // extra damage
        pbolt.obvious_effect = true;
        pbolt.is_beam        = true;
        break;

    case ZAP_NEGATIVE_ENERGY:                           // cap 150
        // These always auto-identify, so no generic name.
        pbolt.name           = "bolt of negative energy";
        pbolt.colour         = DARKGREY;
        pbolt.range          = 7 + random2(10);
        pbolt.damage         = calc_dice( 4, 15 + (power * 3) / 5 );
        pbolt.hit            = 8 + power / 20;               // 50: 10   100: 13
        pbolt.type           = dchar_glyph(DCHAR_FIRED_ZAP);
        pbolt.flavour        = BEAM_NEG;                     // drains levels
        pbolt.obvious_effect = true;
        pbolt.is_beam        = true;
        break;

    case ZAP_IRON_BOLT:                                 // cap 150
        pbolt.name           = "iron bolt";
        pbolt.colour         = LIGHTCYAN;
        pbolt.range          = 5 + random2(5);
        pbolt.damage         = calc_dice( 9, 15 + (power * 3) / 4 );
        pbolt.hit            = 7 + power / 15;               // 50: 10   100: 13
        pbolt.type           = dchar_glyph(DCHAR_FIRED_MISSILE);
        pbolt.flavour        = BEAM_MMISSILE;                // unresistable
        pbolt.obvious_effect = true;
        break;

    case ZAP_POISON_ARROW:                              // cap 150
        pbolt.name           = "poison arrow";
        pbolt.colour         = LIGHTGREEN;
        pbolt.range          = 8 + random2(5);
        pbolt.damage         = calc_dice( 4, 15 + power );
        pbolt.hit            = 5 + power / 10;                // 50: 10  100: 15
        pbolt.type           = dchar_glyph(DCHAR_FIRED_MISSILE);
        pbolt.flavour        = BEAM_POISON_ARROW;             // extra damage
        pbolt.obvious_effect = true;
        break;


    case ZAP_DISINTEGRATION:                            // cap 150
        pbolt.name           = "0";
        pbolt.flavour        = BEAM_DISINTEGRATION;
        pbolt.range          = 7 + random2(8);
        pbolt.damage         = calc_dice( 3, 15 + (power * 3) / 4 );
        pbolt.ench_power    *= 5;
        pbolt.ench_power    /= 2;
        pbolt.is_beam        = true;
        break;

    case ZAP_LIGHTNING:                                 // cap 150
        // also for breath (at pow = lev * 2; max dam: 33)
        pbolt.name           = "bolt of lightning";
        pbolt.colour         = LIGHTCYAN;
        pbolt.range          = 8 + random2(10);            // extended in beam
        pbolt.damage         = calc_dice( 1, 10 + (power * 3) / 5 );
        pbolt.hit            = 7 + random2(power) / 20;    // 50: 7-9  100: 7-12
        pbolt.type           = dchar_glyph(DCHAR_FIRED_ZAP);
        pbolt.flavour        = BEAM_ELECTRICITY;           // beams & reflects
        pbolt.obvious_effect = true;
        pbolt.is_beam        = true;
        break;

    case ZAP_FIREBALL:                                  // cap 150
        pbolt.name           = "fireball";
        pbolt.colour         = RED;
        pbolt.range          = 8 + random2(5);
        pbolt.damage         = calc_dice( 3, 10 + power / 2 );
        pbolt.hit            = 40;                             // hit: 40
        pbolt.type           = dchar_glyph(DCHAR_FIRED_ZAP);
        pbolt.flavour        = BEAM_FIRE;                      // fire
        pbolt.is_explosion   = true;
        break;

    case ZAP_ORB_OF_ELECTRICITY:                        // cap 150
        pbolt.name           = "orb of electricity";
        pbolt.colour         = LIGHTBLUE;
        pbolt.range          = 9 + random2(12);
        pbolt.damage         = calc_dice( 1, 15 + (power * 4) / 5 );
        pbolt.damage.num     = 0;                  // only does explosion damage
        pbolt.hit            = 40;                 // hit: 40
        pbolt.type           = dchar_glyph(DCHAR_FIRED_ZAP);
        pbolt.flavour        = BEAM_ELECTRICITY;
        pbolt.is_explosion   = true;
        break;

    case ZAP_ORB_OF_FRAGMENTATION:                      // cap 150
        pbolt.name           = "metal orb";
        pbolt.colour         = CYAN;
        pbolt.range          = 9 + random2(7);
        pbolt.damage         = calc_dice( 3, 30 + (power * 3) / 4 );
        pbolt.hit            = 20;                            // hit: 20
        pbolt.type           = dchar_glyph(DCHAR_FIRED_ZAP);
        pbolt.flavour        = BEAM_FRAG;                     // extra AC resist
        pbolt.is_explosion   = true;
        break;

    case ZAP_CLEANSING_FLAME:
        pbolt.name           = "golden flame";
        pbolt.colour         = YELLOW;
        pbolt.range          = 7;
        pbolt.damage         = calc_dice( 3, 20 + (power * 2) / 3 );
        pbolt.hit            = 150;
        pbolt.type           = dchar_glyph(DCHAR_FIRED_ZAP);
        pbolt.flavour        = BEAM_HOLY;
        pbolt.obvious_effect = true;
        pbolt.is_explosion   = true;
        break;

    case ZAP_CRYSTAL_SPEAR:                             // cap 200
        pbolt.name           = "crystal spear";
        pbolt.colour         = WHITE;
        pbolt.range          = 6 + random2(4);
        pbolt.damage         = calc_dice( 10, 23 + power );
        pbolt.hit            = 10 + power / 15;              // 50: 13   100: 16
        pbolt.type           = dchar_glyph(DCHAR_FIRED_MISSILE);
        pbolt.flavour        = BEAM_MMISSILE;                // unresistable
        pbolt.obvious_effect = true;
        break;

    case ZAP_HELLFIRE:                                  // cap 200
        pbolt.name           = "hellfire";
        pbolt.colour         = RED;
        pbolt.range          = 7 + random2(10);
        pbolt.damage         = calc_dice( 3, 10 + (power * 3) / 4 );
        pbolt.hit            = 20 + power / 10;              // 50: 25   100: 30
        pbolt.type           = dchar_glyph(DCHAR_FIRED_ZAP);
        pbolt.flavour        = BEAM_HELLFIRE;
        pbolt.obvious_effect = true;
        pbolt.is_explosion   = true;
        break;

    case ZAP_ICE_STORM:                                 // cap 200
        pbolt.name           = "great blast of cold";
        pbolt.colour         = BLUE;
        pbolt.range          = 9 + random2(5);
        pbolt.damage         = calc_dice( 10, 18 + power );
        pbolt.damage.num     = 0;                  // only does explosion damage
        pbolt.hit            = 20 + power / 10;    // 50: 25   100: 30
        pbolt.ench_power     = power;              // used for radius
        pbolt.type           = dchar_glyph(DCHAR_FIRED_ZAP);
        pbolt.flavour        = BEAM_ICE;           // half resisted
        pbolt.is_explosion   = true;
        break;

    case ZAP_BEAM_OF_ENERGY:    // bolt of innacuracy
        pbolt.name           = "narrow beam of energy";
        pbolt.colour         = YELLOW;
        pbolt.range          = 7 + random2(10);
        pbolt.damage         = calc_dice( 12, 40 + (power * 3) / 2 );
        pbolt.hit            = 1;
        pbolt.type           = dchar_glyph(DCHAR_FIRED_ZAP);
        pbolt.flavour        = BEAM_ENERGY;                    // unresisted
        pbolt.obvious_effect = true;
        pbolt.is_beam        = true;
        break;

    case ZAP_SPIT_POISON:       // cap 50
        // max pow = lev + mut * 5 = 42
        pbolt.name           = "splash of poison";
        pbolt.colour         = GREEN;

        pbolt.range          = 3 + random2( 1 + power / 2 );
        if (pbolt.range > 9)
            pbolt.range = 9;

        pbolt.damage         = dice_def( 1, 4 + power / 2 );    // max dam: 25
        pbolt.hit            = 5 + random2( 1 + power / 3 );    // max hit: 19
        pbolt.type           = dchar_glyph(DCHAR_FIRED_ZAP);
        pbolt.flavour        = BEAM_POISON;
        pbolt.obvious_effect = true;
        break;

    case ZAP_BREATHE_FIRE:      // cap 50
        // max pow = lev + mut * 4 + 12 = 51 (capped to 50)
        pbolt.name           = "fiery breath";
        pbolt.colour         = RED;

        pbolt.range          = 3 + random2( 1 + power / 2 );
        if (pbolt.range > 9)
            pbolt.range = 9;

        pbolt.damage         = dice_def( 3, 4 + power / 3 );    // max dam: 60
        pbolt.hit            = 8 + random2( 1 + power / 3 );    // max hit: 25
        pbolt.type           = dchar_glyph(DCHAR_FIRED_ZAP);
        pbolt.flavour        = BEAM_FIRE;
        pbolt.obvious_effect = true;
        pbolt.is_beam        = true;
        break;

    case ZAP_BREATHE_FROST:     // cap 50
        // max power = lev = 27
        pbolt.name           = "freezing breath";
        pbolt.colour         = WHITE;

        pbolt.range          = 3 + random2( 1 + power / 2 );
        if (pbolt.range > 9)
            pbolt.range = 9;

        pbolt.damage         = dice_def( 3, 4 + power / 3 );    // max dam: 39
        pbolt.hit            = 8 + random2( 1 + power / 3 );
        pbolt.type           = dchar_glyph(DCHAR_FIRED_ZAP);
        pbolt.flavour        = BEAM_COLD;
        pbolt.obvious_effect = true;
        pbolt.is_beam        = true;
        break;

    case ZAP_BREATHE_ACID:      // cap 50
        // max power = lev for ability, 50 for minor destruction (max dam: 57)
        pbolt.name           = "acid";
        pbolt.colour         = YELLOW;

        pbolt.range          = 3 + random2( 1 + power / 2 );
        if (pbolt.range > 9)
            pbolt.range = 9;

        pbolt.damage         = dice_def( 3, 3 + power / 3 );    // max dam: 36
        pbolt.hit            = 5 + random2( 1 + power / 3 );
        pbolt.type           = dchar_glyph(DCHAR_FIRED_ZAP);
        pbolt.flavour        = BEAM_ACID;
        pbolt.obvious_effect = true;
        pbolt.is_beam        = true;
        break;

    case ZAP_BREATHE_POISON:    // leaves clouds of gas // cap 50
        // max power = lev = 27
        pbolt.name           = "poison gas";
        pbolt.colour         = GREEN;

        pbolt.range          = 3 + random2( 1 + power / 2 );
        if (pbolt.range > 9)
            pbolt.range = 9;

        pbolt.damage         = dice_def( 3, 2 + power / 6 );    // max dam: 18
        pbolt.hit            = 6 + random2( 1 + power / 3 );
        pbolt.type           = dchar_glyph(DCHAR_FIRED_ZAP);
        pbolt.flavour        = BEAM_POISON;
        pbolt.obvious_effect = true;
        pbolt.is_beam        = true;
        break;

    case ZAP_BREATHE_POWER:     // cap 50
        pbolt.name           = "bolt of energy";
        // max power = lev = 27

        pbolt.colour         = BLUE;
        if (random2(power) >= 8)
            pbolt.colour = LIGHTBLUE;
        if (random2(power) >= 12)
            pbolt.colour = MAGENTA;
        if (random2(power) >= 17)
            pbolt.colour = LIGHTMAGENTA;

        pbolt.range          = 6 + random2( 1 + power / 2 );
        if (pbolt.range > 9)
            pbolt.range = 9;

        pbolt.damage         = dice_def( 3, 3 + power / 3 );   // max dam: 36
        pbolt.hit            = 5 + random2( 1 + power / 3 );
        pbolt.type           = dchar_glyph(DCHAR_FIRED_ZAP);
        pbolt.flavour        = BEAM_MMISSILE;                  // unresistable
        pbolt.obvious_effect = true;
        pbolt.is_beam        = true;
        break;

    case ZAP_BREATHE_STEAM:     // cap 50
        // max power = lev = 27
        pbolt.name           = "ball of steam";
        pbolt.colour         = LIGHTGREY;

        pbolt.range          = 6 + random2(5);
        if (pbolt.range > 9)
            pbolt.range = 9;

        pbolt.damage         = dice_def( 3, 4 + power / 5 );    // max dam: 27
        pbolt.hit            = 10 + random2( 1 + power / 5 );
        pbolt.type           = dchar_glyph(DCHAR_FIRED_ZAP);
        pbolt.flavour        = BEAM_STEAM;
        pbolt.obvious_effect = true;
        pbolt.is_beam        = true;
        break;

    case ZAP_SLOWING:
        pbolt.name           = "0";
        pbolt.flavour        = BEAM_SLOW;
        // pbolt.is_beam = true;
        break;

    case ZAP_HASTING:
        pbolt.name           = "0";
        pbolt.flavour        = BEAM_HASTE;
        // pbolt.is_beam = true;
        break;

    case ZAP_PARALYSIS:
        pbolt.name           = "0";
        pbolt.flavour        = BEAM_PARALYSIS;
        // pbolt.is_beam = true;
        break;

    case ZAP_PETRIFY:
        pbolt.name           = "0";
        pbolt.flavour        = BEAM_PETRIFY;
        // pbolt.is_beam = true;
        break;

    case ZAP_CONFUSION:
        pbolt.name           = "0";
        pbolt.flavour        = BEAM_CONFUSION;
        // pbolt.is_beam = true;
        break;

    case ZAP_INVISIBILITY:
        pbolt.name           = "0";
        pbolt.flavour        = BEAM_INVISIBILITY;
        // pbolt.is_beam = true;
        break;

    case ZAP_HEALING:
        pbolt.name           = "0";
        pbolt.flavour        = BEAM_HEALING;
        pbolt.damage         = dice_def( 1, 7 + power / 3 );
        // pbolt.is_beam = true;
        break;

    case ZAP_DIGGING:
        pbolt.name           = "0";
        pbolt.flavour        = BEAM_DIGGING;
        // not ordinary "0" beam range {dlb}
        pbolt.range          = 3 + random2( power / 5 ) + random2(5);
        pbolt.is_beam        = true;
        break;

    case ZAP_TELEPORTATION:
        pbolt.name           = "0";
        pbolt.flavour        = BEAM_TELEPORT;
        pbolt.range          = 9 + random2(5);
        // pbolt.is_beam = true;
        break;

    case ZAP_POLYMORPH_OTHER:
        pbolt.name           = "0";
        pbolt.flavour        = BEAM_POLYMORPH;
        pbolt.range          = 9 + random2(5);
        // pbolt.is_beam = true;
        break;

    case ZAP_ENSLAVEMENT:
        pbolt.name           = "0";
        pbolt.flavour        = BEAM_CHARM;
        pbolt.range          = 7 + random2(5);
        // pbolt.is_beam = true;
        break;

    case ZAP_BANISHMENT:
        pbolt.name           = "0";
        pbolt.flavour        = BEAM_BANISH;
        pbolt.range          = 7 + random2(5);
        // pbolt.is_beam = true;
        break;

    case ZAP_DEGENERATION:
        pbolt.name           = "0";
        pbolt.flavour        = BEAM_DEGENERATE;
        pbolt.range          = 7 + random2(5);
        // pbolt.is_beam = true;
        break;

    case ZAP_ENSLAVE_UNDEAD:
        pbolt.name           = "0";
        pbolt.flavour        = BEAM_ENSLAVE_UNDEAD;
        pbolt.range          = 7 + random2(5);
        // pbolt.is_beam = true;
        break;

    case ZAP_AGONY:
        pbolt.name           = "0agony";
        pbolt.flavour        = BEAM_PAIN;
        pbolt.range          = 7 + random2(8);
        pbolt.ench_power    *= 5;
        // pbolt.is_beam = true;
        break;

    case ZAP_CONTROL_DEMON:
        pbolt.name           = "0";
        pbolt.flavour        = BEAM_ENSLAVE_DEMON;
        pbolt.range          = 7 + random2(5);
        pbolt.ench_power    *= 3;
        pbolt.ench_power    /= 2;
        // pbolt.is_beam = true;
        break;

    case ZAP_SLEEP:             //jmf: added
        pbolt.name           = "0";
        pbolt.flavour        = BEAM_SLEEP;
        pbolt.range          = 7 + random2(5);
        // pbolt.is_beam = true;
        break;

    case ZAP_BACKLIGHT: //jmf: added
        pbolt.name           = "0";
        pbolt.flavour        = BEAM_BACKLIGHT;
        pbolt.colour         = BLUE;
        pbolt.range          = 7 + random2(5);
        // pbolt.is_beam = true;
        break;

    case ZAP_DEBUGGING_RAY:
        pbolt.name           = "debugging ray";
        pbolt.colour         = random_colour();
        pbolt.range          = 7 + random2(10);
        pbolt.damage         = dice_def( 1500, 1 );            // dam: 1500
        pbolt.hit            = 1500;                           // hit: 1500
        pbolt.type           = dchar_glyph(DCHAR_FIRED_DEBUG);
        pbolt.flavour        = BEAM_MMISSILE;                  // unresistable
        pbolt.obvious_effect = true;
        break;

    default:
        pbolt.name           = "buggy beam";
        pbolt.colour         = random_colour();
        pbolt.range          = 7 + random2(10);
        pbolt.damage         = dice_def( 1, 0 );
        pbolt.hit            = 60;
        pbolt.type           = dchar_glyph(DCHAR_FIRED_DEBUG);
        pbolt.flavour        = BEAM_MMISSILE;                  // unresistable
        pbolt.obvious_effect = true;
        break;
    }

    if (wearing_amulet(AMU_INACCURACY))
    {
        pbolt.hit -= 5;
        pbolt.hit = std::max(0, pbolt.hit);
    }
}

/*  NEW (GDL):
 *  Now handles all beamed/thrown items and spells, tracers, and their effects.
 *  item is used for items actually thrown/launched
 *
 *  If item is NULL, there is no physical object being thrown that could
 *  land on the ground.
 */


// Affect monster in wall unless it can shield itself using the wall.
// The wall will always shield the monster if the beam bounces off the
// wall, and a monster can't use a metal wall to shield itself from
// electricity.
static bool _affect_mon_in_wall(bolt &pbolt, item_def *item, int tx, int ty)
{
    UNUSED(item);

    int mid = mgrd[tx][ty];

    if (mid == NON_MONSTER)
        return (false);

    if (pbolt.is_enchant
        || (!pbolt.is_explosion && !pbolt.is_big_cloud
            && (grd[tx][ty] == DNGN_METAL_WALL
                || pbolt.flavour != BEAM_ELECTRICITY)))
    {
        monsters *mon = &menv[mid];
        if (!mons_is_wall_shielded(mon->type))
            return (true);
    }

    return (false);
}

/*
 * Beam pseudo code:
 *
 * 1. Calculate stepx and stepy - algorithms depend on finding a step axis
 *    which results in a line of rise 1 or less (ie 45 degrees or less)
 * 2. Calculate range.  Tracers always have max range, otherwise the beam
 *    will have somewhere between range and rangeMax
 * 3. Loop tracing out the line:
 *      3a. Check for walls and wall affecting beams
 *      3b. If no valid move is found, try a fuzzy move
 *      3c. If no valid move is yet found, try bouncing
 *      3d. If no valid move or bounce is found, break
 *      4. Check for beam termination on target
 *      5. Affect the cell which the beam just moved into -> affect()
 *      6. Decrease remaining range appropriately
 *      7. Check for early out due to aimed_at_feet
 *      8. Draw the beam
 * 9. Drop an object where the beam 'landed'
 *10. Beams explode where the beam 'landed'
 *11. If no message generated yet, send "nothing happens" (enchantments only)
 *
 */

void fire_beam(bolt &pbolt, item_def *item, bool drop_item)
{
    bool beamTerminate;     // Has beam been 'stopped' by something?
    int &tx(pbolt.pos.x), &ty(pbolt.pos.y);     // test(new) x,y - integer
    int rangeRemaining;
    bool did_bounce = false;
    cursor_control coff(false);

    // [ds] Forcing the beam out of explosion phase here - currently
    // no caller relies on the beam already being in_explosion_phase.
    // This fixes beams being in explosion after use as a tracer.
    pbolt.in_explosion_phase = false;

    beam_message_cache.clear();

#ifdef USE_TILE
    int tile_beam = -1;

    if (item && !pbolt.is_tracer && pbolt.flavour == BEAM_MISSILE)
    {
        tile_beam = tileidx_item_throw(*item,
                                       pbolt.target_x - pbolt.source_x,
                                       pbolt.target_y - pbolt.source_y);
    }
#endif

#if DEBUG_DIAGNOSTICS
    if (pbolt.flavour != BEAM_LINE_OF_SIGHT)
    {
        mprf( MSGCH_DIAGNOSTICS, "%s%s%s (%d,%d) to (%d,%d): "
              "ty=%d col=%d flav=%d hit=%d dam=%dd%d range=%d",
                 (pbolt.is_beam) ? "beam" : "missile",
                 (pbolt.is_explosion) ? "*" :
                 (pbolt.is_big_cloud) ? "+" : "",
                 (pbolt.is_tracer) ? " tracer" : "",
                 pbolt.source_x, pbolt.source_y,
                 pbolt.target_x, pbolt.target_y,
                 pbolt.type, pbolt.colour, pbolt.flavour,
                 pbolt.hit, pbolt.damage.num, pbolt.damage.size,
                 pbolt.range);
    }
#endif

    // init
    pbolt.aimed_at_feet = (pbolt.target_x == pbolt.source_x
                           && pbolt.target_y == pbolt.source_y);
    pbolt.msg_generated = false;

    ray_def ray;

    if (pbolt.chose_ray)
        ray = pbolt.ray;
    else
    {
        ray.fullray_idx = -1;   // to quiet valgrind
        find_ray( pbolt.source_x, pbolt.source_y,
                  pbolt.target_x, pbolt.target_y, true, ray,
                  0, true );
    }

    if (!pbolt.aimed_at_feet)
        ray.advance_through(pbolt.target());

    // Give chance for beam to affect one cell even if aimed_at_feet.
    beamTerminate = false;

    // Setup range.
    rangeRemaining = pbolt.range;
    if (pbolt.rangeMax > pbolt.range)
    {
        if (pbolt.is_tracer)
            rangeRemaining = pbolt.rangeMax;
        else
            rangeRemaining += random2((pbolt.rangeMax - pbolt.range) + 1);
    }

    // Before we start drawing the beam, turn buffering off.
#ifdef WIN32CONSOLE
    bool oldValue = true;
    if (!pbolt.is_tracer)
        oldValue = set_buffering(false);
#endif
    while (!beamTerminate)
    {
        tx = ray.x();
        ty = ray.y();

        // Shooting through clouds affects accuracy.
        if (env.cgrid[tx][ty] != EMPTY_CLOUD)
            pbolt.hit = std::max(pbolt.hit - 2, 0);

        // See if tx, ty is blocked by something.
        if (grid_is_solid(grd[tx][ty]))
        {
            // First, check to see if this beam affects walls.
            if (_affects_wall(pbolt, grd[tx][ty]))
            {
                // Should we ever get a tracer with a wall-affecting
                // beam (possible I suppose), we'll quit tracing now.
                if (!pbolt.is_tracer)
                    rangeRemaining -= affect(pbolt, tx, ty, item);

                // If it's still a wall, quit.
                if (grid_is_solid(grd[tx][ty]))
                    break;      // breaks from line tracing
            }
            else
            {
                // BEGIN bounce case.  Bouncing protects any monster
                // in the wall.
                if (!_isBouncy(pbolt, grd[tx][ty]))
                {
                    // Affect any monster that might be in the wall.
                    rangeRemaining -= affect(pbolt, tx, ty, item);

                    do
                    {
                        ray.regress();
                    }
                    while (grid_is_solid(grd(ray.pos())));

                    tx = ray.x();
                    ty = ray.y();
                    break;          // breaks from line tracing
                }

                did_bounce = true;

                // bounce
                do
                {
                    do
                        ray.regress();
                    while (grid_is_solid(grd(ray.pos())));

                    ray.advance_and_bounce();
                    rangeRemaining -= 2;
                }
                while (rangeRemaining > 0
                        && grid_is_solid(grd[ray.x()][ray.y()]));

                if (rangeRemaining < 1)
                    break;

                tx = ray.x();
                ty = ray.y();
            } // end else - beam doesn't affect walls
        } // endif - is tx, ty wall?

        // At this point, if grd[tx][ty] is still a wall, we
        // couldn't find any path: bouncy, fuzzy, or not - so break.
        if (grid_is_solid(grd[tx][ty]))
            break;

        // Check for "target termination"
        // occurs when beam can be targetted at empty
        // cell (e.g. a mage wants an explosion to happen
        // between two monsters).

        // In this case, don't affect the cell - players and
        // monsters have no chance to dodge or block such
        // a beam, and we want to avoid silly messages.
        if (tx == pbolt.target_x && ty == pbolt.target_y)
            beamTerminate = _beam_term_on_target(pbolt, tx, ty);

        // Affect the cell, except in the special case noted
        // above -- affect() will early out if something gets
        // hit and the beam is type 'term on target'.
        if (!beamTerminate || !pbolt.is_explosion)
        {
            // Random beams: randomize before affect().
            bool random_beam = false;
            if (pbolt.flavour == BEAM_RANDOM)
            {
                random_beam = true;
                pbolt.flavour = static_cast<beam_type>(
                                    random_range(BEAM_FIRE, BEAM_ACID));
            }

            if (!pbolt.affects_nothing)
                rangeRemaining -= affect(pbolt, tx, ty, item);

            if (random_beam)
            {
                pbolt.flavour = BEAM_RANDOM;
                pbolt.effect_known = false;
            }
        }

        if (pbolt.beam_cancelled)
            return;

        // Always decrease range by 1.
        rangeRemaining--;

        // Check for range termination.
        if (rangeRemaining <= 0)
            beamTerminate = true;

        // Special case - beam was aimed at feet.
        if (pbolt.aimed_at_feet)
            beamTerminate = true;

        // Actually draw the beam/missile/whatever,
        // if the player can see the cell.
        if (!pbolt.is_tracer && pbolt.name[0] != '0' && see_grid(tx,ty))
        {
            // We don't clean up the old position.
            // First, most people like to see the full path,
            // and second, it is hard to do it right with
            // respect to killed monsters, cloud trails, etc.

            // Draw new position.
            int drawx = grid2viewX(tx);
            int drawy = grid2viewY(ty);

#ifdef USE_TILE
            if (tile_beam == -1)
                tile_beam = tileidx_bolt(pbolt);

            if (tile_beam != -1 && in_los_bounds(drawx, drawy)
                && (tx != you.x_pos || ty != you.y_pos))
            {
                TileDrawBolt(drawx-1, drawy-1, tile_beam);
                delay(15);
            }
            else
#endif
            // bounds check
            if (in_los_bounds(drawx, drawy))
            {
#ifndef USE_TILE
                cgotoxy(drawx, drawy);
                put_colour_ch(
                    pbolt.colour == BLACK ? random_colour() : pbolt.colour,
                    pbolt.type );
#endif
                // Get curses to update the screen so we can see the beam.
                update_screen();

                delay(15);

#ifdef MISSILE_TRAILS_OFF
                // mv: It's not optimal but is usually enough.
                if (!pbolt.is_beam || pbolt.name[0] == '0')
                    viewwindow(true, false);
#endif
            }

        }

        if (!did_bounce)
            ray.advance_through(pbolt.target());
        else
            ray.advance(true);
    } // end- while !beamTerminate

    // The beam has finished, and terminated at tx, ty.

    // Leave an object, if applicable.
    if (drop_item && item)
        beam_drop_object(pbolt, item, tx, ty);

    ASSERT(!drop_item || item);

    // Check for explosion.  NOTE that for tracers, we have to make a copy
    // of target co-ords and then reset after calling this -- tracers should
    // never change any non-tracers fields in the beam structure. -- GDL
    int ox = pbolt.target_x;
    int oy = pbolt.target_y;

    _beam_explodes(pbolt, tx, ty);

    if (pbolt.is_tracer)
    {
        pbolt.target_x = ox;
        pbolt.target_y = oy;
    }

    // Canned msg for enchantments that affected no-one, but only if the
    // enchantment is yours.
    if (pbolt.name[0] == '0')
    {
        if (!pbolt.is_tracer && !pbolt.msg_generated && !pbolt.obvious_effect
            && YOU_KILL(pbolt.thrower))
        {
            canned_msg(MSG_NOTHING_HAPPENS);
        }
    }

    if (!pbolt.is_tracer && !invalid_monster_index(pbolt.beam_source))
    {
        if (pbolt.foe_hurt == 0 && pbolt.fr_hurt > 0)
            xom_is_stimulated(128);
        else if (pbolt.foe_helped > 0 && pbolt.fr_helped == 0)
            xom_is_stimulated(128);

        // Allow friendlies to react to projectiles, except when in
        // sanctuary when pet_target can only be explicitly changed by
        // the player.
        const monsters *mon = &menv[pbolt.beam_source];
        if (pbolt.foe_hurt > 0 && !mons_wont_attack(mon)
            && you.pet_target == MHITNOT && env.sanctuary_time <= 0)
        {
            you.pet_target = pbolt.beam_source;
        }
    }

    // That's it!
#ifdef WIN32CONSOLE
    if (!pbolt.is_tracer)
        set_buffering(oldValue);
#endif
}


// Returns damage taken by a monster from a "flavoured" (fire, ice, etc.)
// attack -- damage from clouds and branded weapons handled elsewhere.
int mons_adjust_flavoured(monsters *monster, bolt &pbolt, int hurted,
                          bool doFlavouredEffects)
{
    // If we're not doing flavoured effects, must be preliminary
    // damage check only.
    // Do not print messages or apply any side effects!
    int resist = 0;
    int original = hurted;

    switch (pbolt.flavour)
    {
    case BEAM_FIRE:
    case BEAM_STEAM:
        hurted = resist_adjust_damage(
                    monster,
                    pbolt.flavour,
                    (pbolt.flavour == BEAM_FIRE) ? monster->res_fire()
                                                 : monster->res_steam(),
                    hurted, true);

        if (!hurted)
        {
            if (doFlavouredEffects)
                simple_monster_message(monster, " appears unharmed.");
        }
        else if (original > hurted)
        {
            if (doFlavouredEffects)
                simple_monster_message(monster, " resists.");
        }
        else if (original < hurted)
        {
            if (mons_is_icy(monster))
            {
                if (doFlavouredEffects)
                    simple_monster_message(monster, " melts!");
            }
            else
            {
                if (doFlavouredEffects)
                {
                    if (pbolt.flavour == BEAM_FIRE)
                        simple_monster_message(monster,
                                               " is burned terribly!");
                    else
                        simple_monster_message(monster,
                                               " is scalded terribly!");
                }
            }
        }
        break;

    case BEAM_COLD:
        hurted = resist_adjust_damage(monster, pbolt.flavour,
                                      monster->res_cold(),
                                      hurted, true);
        if (!hurted)
        {
            if (doFlavouredEffects)
                simple_monster_message(monster, " appears unharmed.");
        }
        else if (original > hurted)
        {
            if (doFlavouredEffects)
                simple_monster_message(monster, " resists.");
        }
        else if (original < hurted)
        {
            if (doFlavouredEffects)
                simple_monster_message(monster, " is frozen!");
        }
        break;

    case BEAM_ELECTRICITY:
        hurted = resist_adjust_damage(monster, pbolt.flavour,
                                      monster->res_elec(),
                                      hurted, true);
        if (!hurted)
        {
            if (doFlavouredEffects)
                simple_monster_message(monster, " appears unharmed.");
        }
        break;

    case BEAM_ACID:
        hurted = resist_adjust_damage(monster, pbolt.flavour,
                                      mons_res_acid(monster),
                                      hurted, true);
        if (!hurted)
        {
            if (doFlavouredEffects)
                simple_monster_message(monster, " appears unharmed.");
        }
        break;

    case BEAM_POISON:
    {
        int res = mons_res_poison(monster);
        hurted  = resist_adjust_damage(monster, pbolt.flavour, res,
                                       hurted, true);
        if (!hurted && res > 0)
        {
            if (doFlavouredEffects)
                simple_monster_message( monster, " appears unharmed." );
        }
        else if (res <= 0 && doFlavouredEffects && !one_chance_in(3))
            poison_monster( monster, _whose_kill(pbolt) );

        break;
    }

    case BEAM_POISON_ARROW:
        hurted = resist_adjust_damage(monster, pbolt.flavour,
                                      mons_res_poison(monster),
                                      hurted);
        if (hurted < original)
        {
            if (doFlavouredEffects)
            {
                simple_monster_message( monster, " partially resists." );

                // Poison arrow can poison any living thing regardless of
                // poison resistance. -- bwr
                if (mons_has_lifeforce(monster))
                    poison_monster( monster, _whose_kill(pbolt), 2, true );
            }
        }
        else if (doFlavouredEffects)
            poison_monster( monster, _whose_kill(pbolt), 4 );

        break;

    case BEAM_NEG:
        if (mons_res_negative_energy(monster))
        {
            if (doFlavouredEffects)
                simple_monster_message(monster, " appears unharmed.");

            hurted = 0;
        }
        else
        {
            // Early out for tracer/no side effects.
            if (!doFlavouredEffects)
                return (hurted);

            simple_monster_message(monster, " is drained.");
            pbolt.obvious_effect = true;

            if (YOU_KILL(pbolt.thrower))
            {
                did_god_conduct(DID_NECROMANCY, 2 + random2(3),
                                pbolt.effect_known);
            }

            if (one_chance_in(5))
            {
                monster->hit_dice--;
                monster->experience = 0;
            }

            monster->max_hit_points -= 2 + random2(3);
            monster->hit_points     -= 2 + random2(3);

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

            if (monster->hit_dice < 1)
                monster->hit_points = 0;
        }                       // end else
        break;

    case BEAM_MIASMA:
        if (mons_res_negative_energy( monster ) == 3)
        {
            if (doFlavouredEffects)
                simple_monster_message(monster, " appears unharmed.");

            hurted = 0;
        }
        else
        {
            // Early out for tracer/no side effects.
            if (!doFlavouredEffects)
                return (hurted);

            if (mons_res_poison( monster ) <= 0)
                poison_monster( monster, _whose_kill(pbolt) );

            if (one_chance_in( 3 + 2 * mons_res_negative_energy(monster) ))
            {
                bolt beam;
                beam.flavour = BEAM_SLOW;
                mons_ench_f2( monster, beam );
            }
        }
        break;

    case BEAM_HOLY:             // flame of cleansing
        // Cleansing flame doesn't hurt holy monsters or monsters your
        // god wouldn't like to be hurt.
        if (mons_is_holy(monster)
            || you.religion == GOD_SHINING_ONE
               && is_unchivalric_attack(&you, monster, monster)
            || is_good_god(you.religion)
               && (is_follower(monster) || mons_neutral(monster)))
        {
            hurted = 0;
        }
        else if (mons_is_unholy(monster))
            hurted = (hurted * 3) / 2;
        else if (!mons_is_evil(monster))
            hurted /= 2;

        if (doFlavouredEffects)
        {
            simple_monster_message(monster, (hurted == 0) ?
                " appears unharmed." : " writhes in agony!");
        }

        break;

    case BEAM_ICE:
        // ice - about 50% of damage is cold, other 50% is impact and
        // can't be resisted (except by AC, of course)
        hurted = resist_adjust_damage(monster, pbolt.flavour,
                                      monster->res_cold(), hurted,
                                      true);
        if (hurted < original)
        {
            if (doFlavouredEffects)
                simple_monster_message(monster, " partially resists.");
        }
        else if (hurted > original)
        {
            if (doFlavouredEffects)
                simple_monster_message(monster, " is frozen!");
        }
        break;

    case BEAM_LAVA:
        hurted = resist_adjust_damage(monster, pbolt.flavour,
                                      monster->res_fire(), hurted, true);

        if (hurted < original)
        {
            if (doFlavouredEffects)
                simple_monster_message(monster, " partially resists.");
        }
        else if (hurted > original)
        {
            if (mons_is_icy(monster))
            {
                if (doFlavouredEffects)
                    simple_monster_message(monster, " melts!");
            }
            else
            {
                if (doFlavouredEffects)
                    simple_monster_message(monster, " is burned terribly!");
            }
        }
        break;
    default:
        break;
    }                           // end of switch

    if (pbolt.name == "hellfire")
    {
        resist = mons_res_fire(monster);
        if (resist > 2)
        {
            if (doFlavouredEffects)
                simple_monster_message(monster, " appears unharmed.");

            hurted = 0;
        }
        else if (resist > 0)
        {
            if (doFlavouredEffects)
                simple_monster_message(monster, " partially resists.");

            hurted /= 2;
        }
        else if (resist < 0)
        {
            if (mons_is_icy(monster))
            {
                if (doFlavouredEffects)
                    simple_monster_message(monster, " melts!");
            }
            else
            {
                if (doFlavouredEffects)
                    simple_monster_message(monster, " is burned terribly!");
            }

            hurted *= 12;       // hellfire
            hurted /= 10;
        }
    }

    return (hurted);
}                               // end mons_adjust_flavoured()

static bool _monster_resists_mass_enchantment(monsters *monster,
                                              enchant_type wh_enchant,
                                              int pow)
{
    // Assuming that the only mass charm is control undead.
    if (wh_enchant == ENCH_CHARM)
    {
        if (mons_friendly(monster))
            return (true);

        if (mons_class_holiness(monster->type) != MH_UNDEAD)
            return (true);

        if (check_mons_resist_magic( monster, pow ))
        {
            simple_monster_message(monster,
                                   mons_immune_magic(monster)? " is unaffected."
                                                             : " resists.");
            return (true);
        }
    }
    else if (wh_enchant == ENCH_CONFUSION
             || mons_holiness(monster) == MH_NATURAL)
    {
        if (wh_enchant == ENCH_CONFUSION
            && !mons_class_is_confusable(monster->type))
        {
            return (true);
        }

        if (check_mons_resist_magic( monster, pow ))
        {
            simple_monster_message(monster,
                                   mons_immune_magic(monster)? " is unaffected."
                                                             : " resists.");
            return (true);
        }
    }
    else  // trying to enchant an unnatural creature doesn't work
    {
        simple_monster_message(monster, " is unaffected.");
        return (true);
    }

    return (false);
}

// Enchants all monsters in player's sight.
// If m_succumbed is non-NULL, will be set to the number of monsters that
// were enchanted. If m_attempted is non-NULL, will be set to the number of
// monsters that we tried to enchant.
bool mass_enchantment( enchant_type wh_enchant, int pow, int origin,
                       int *m_succumbed, int *m_attempted )
{
    int i;                      // loop variable {dlb}
    bool msg_generated = false;
    monsters *monster;

    if (m_succumbed)
        *m_succumbed = 0;
    if (m_attempted)
        *m_attempted = 0;

    viewwindow(false, false);

    if (pow > 200)
        pow = 200;

    const kill_category kc = (origin == MHITYOU ? KC_YOU : KC_OTHER);

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

        if (monster->type == -1 || !mons_near(monster))
            continue;

        if (monster->has_ench(wh_enchant))
            continue;

        if (m_attempted)
            ++*m_attempted;

        if (_monster_resists_mass_enchantment(monster, wh_enchant, pow))
            continue;

        if (monster->add_ench(mon_enchant(wh_enchant, 0, kc)))
        {
            if (m_succumbed)
                ++*m_succumbed;

            if (player_monster_visible( monster ))
            {
                // turn message on
                msg_generated = true;
                switch (wh_enchant)
                {
                case ENCH_FEAR:
                    simple_monster_message(monster,
                                           " looks frightened!");
                    break;
                case ENCH_CONFUSION:
                    simple_monster_message(monster,
                                           " looks rather confused.");
                    break;
                case ENCH_CHARM:
                    simple_monster_message(monster,
                                           " submits to your will.");
                    break;
                default:
                    // oops, I guess not!
                    msg_generated = false;
                }
            }

            // Extra check for fear (monster needs to reevaluate behaviour).
            if (wh_enchant == ENCH_FEAR)
                behaviour_event( monster, ME_SCARE, origin );
        }
    }                           // end "for i"

    if (!msg_generated)
        canned_msg(MSG_NOTHING_HAPPENS);

    return (msg_generated);
}                               // end mass_enchantment()

// Monster has probably failed save, now it gets enchanted somehow.
// * Returns MON_RESIST if monster is unaffected due to magic resist.
// * Returns MON_UNAFFECTED if monster is immune to enchantment.
// * Returns MON_AFFECTED in all other cases (already enchanted, etc).
int mons_ench_f2(monsters *monster, bolt &pbolt)
{
    switch (pbolt.flavour)      // put in magic resistance
    {
    case BEAM_SLOW:
        // try to remove haste, if monster is hasted
        if (monster->del_ench(ENCH_HASTE))
        {
            if (simple_monster_message(monster,
                                       " is no longer moving quickly."))
            {
                pbolt.obvious_effect = true;
            }

            return (MON_AFFECTED);
        }

        // not hasted, slow it
        if (!monster->has_ench(ENCH_SLOW)
            && !mons_is_stationary(monster)
            && monster->add_ench(mon_enchant(ENCH_SLOW, 0, _whose_kill(pbolt))))
        {
            if (!mons_is_paralysed(monster) && !mons_is_petrified(monster)
                && simple_monster_message(monster, " seems to slow down."))
            {
                pbolt.obvious_effect = true;
            }
        }
        return (MON_AFFECTED);

    case BEAM_HASTE:
        if (monster->del_ench(ENCH_SLOW))
        {
            if (simple_monster_message(monster, " is no longer moving slowly."))
                pbolt.obvious_effect = true;

            return (MON_AFFECTED);
        }

        // Not slowed, haste it.
        if (!monster->has_ench(ENCH_HASTE)
            && !mons_is_stationary(monster)
            && monster->add_ench(ENCH_HASTE))
        {
            if (!mons_is_paralysed(monster) && !mons_is_petrified(monster)
                && simple_monster_message(monster, " seems to speed up."))
            {
                pbolt.obvious_effect = true;
            }
        }
        return (MON_AFFECTED);

    case BEAM_HEALING:
        if (YOU_KILL(pbolt.thrower))
        {
            if (cast_healing(5 + roll_dice( pbolt.damage ),
                             monster->x, monster->y) > 0)
            {
                pbolt.obvious_effect = true;
            }
            pbolt.msg_generated = true; // to avoid duplicate "nothing happens"
        }
        else if (heal_monster( monster, 5 + roll_dice( pbolt.damage ), false ))
        {
            if (monster->hit_points == monster->max_hit_points)
            {
                if (simple_monster_message(monster,
                                           "'s wounds heal themselves!"))
                {
                    pbolt.obvious_effect = true;
                }
            }
            else if (simple_monster_message(monster, " is healed somewhat."))
                pbolt.obvious_effect = true;
        }
        return (MON_AFFECTED);

    case BEAM_PARALYSIS:
        _beam_paralyses_monster(pbolt, monster);
        return (MON_AFFECTED);

    case BEAM_PETRIFY:
        _beam_petrifies_monster(pbolt, monster);
        return (MON_AFFECTED);

    case BEAM_CONFUSION:
        if (!mons_class_is_confusable(monster->type))
            return (MON_UNAFFECTED);

        if (monster->add_ench( mon_enchant(ENCH_CONFUSION, 0,
                               _whose_kill(pbolt)) ))
        {
            // Put in an exception for things you won't notice becoming
            // confused.
            if (simple_monster_message(monster, " appears confused."))
                pbolt.obvious_effect = true;
        }
        return (MON_AFFECTED);

    case BEAM_INVISIBILITY:
    {
        // Store the monster name before it becomes an "it" -- bwr
        const std::string monster_name = monster->name(DESC_CAP_THE);

        if (!monster->has_ench(ENCH_INVIS)
            && monster->add_ench(ENCH_INVIS))
        {
            // A casting of invisibility erases backlight.
            monster->del_ench(ENCH_BACKLIGHT);

            // Can't use simple_monster_message() here, since it checks
            // for visibility of the monster (and it's now invisible).
            // -- bwr
            if (mons_near( monster ))
            {
                mprf("%s flickers %s",
                     monster_name.c_str(),
                     player_monster_visible(monster) ? "for a moment."
                                                     : "and vanishes!" );

                if (Options.tutorial_left)
                {
                    learned_something_new(TUT_INVISIBLE_DANGER);
                    if (!player_monster_visible(monster))
                        Options.tut_seen_invisible = you.num_turns;
                }
            }

            pbolt.obvious_effect = true;
        }
        return (MON_AFFECTED);
    }
    case BEAM_CHARM:
        if (player_will_anger_monster(monster))
        {
            simple_monster_message(monster, " is repulsed!");
            return (MON_OTHER);
        }

        if (monster->add_ench(ENCH_CHARM))
        {
            // Put in an exception for fungi, plants and other things
            // you won't notice becoming charmed.
            if (simple_monster_message(monster, " is charmed."))
                pbolt.obvious_effect = true;
        }
        return (MON_AFFECTED);

    default:
        break;
    }

    return (MON_AFFECTED);
}                               // end mons_ench_f2()

// degree is ignored.
static void _slow_monster(monsters *mon, int /* degree */)
{
    bolt beam;
    beam.flavour = BEAM_SLOW;
    mons_ench_f2(mon, beam);
}

static void _beam_paralyses_monster(bolt &pbolt, monsters *monster)
{
    if (!monster->has_ench(ENCH_PARALYSIS)
        && monster->add_ench(ENCH_PARALYSIS)
        && (!monster->has_ench(ENCH_PETRIFIED)
            || monster->has_ench(ENCH_PETRIFYING)))
    {
        if (simple_monster_message(monster, " suddenly stops moving!"))
            pbolt.obvious_effect = true;

        mons_check_pool(monster, pbolt.killer(), pbolt.beam_source);
    }
}


// Petrification works in two stages. First the monster is slowed down in
// all of its actions and cannot move away (petrifying), and when that times
// out it remains properly petrified (no movement or actions). The second
// part is similar to paralysis, except that insubstantial monsters can't be
// affected and that stabbing damage is drastically reduced.
static void _beam_petrifies_monster(bolt &pbolt, monsters *monster)
{
    int petrifying = monster->has_ench(ENCH_PETRIFYING);
    if (monster->has_ench(ENCH_PETRIFIED))
    {
        // If the petrifying is not yet finished, we can force it to happen
        // right away by casting again. Otherwise, the spell has no further
        // effect.
        if (petrifying > 0)
        {
            monster->del_ench(ENCH_PETRIFYING, true);
            if (!monster->has_ench(ENCH_PARALYSIS)
                && simple_monster_message(monster, " stops moving altogether!"))
            {
                pbolt.obvious_effect = true;
            }
        }
    }
    else if (monster->add_ench(ENCH_PETRIFIED)
             && !monster->has_ench(ENCH_PARALYSIS))
    {
        // Add both the petrifying and the petrified enchantment. The former
        // will run out sooner and result in plain petrification behaviour.
        monster->add_ench(ENCH_PETRIFYING);
        if (simple_monster_message(monster, " is moving more slowly."))
            pbolt.obvious_effect = true;

        mons_check_pool(monster, pbolt.killer(), pbolt.beam_source);
    }
}

// Returns true if the curare killed the monster.
bool curare_hits_monster( const bolt &beam,  monsters *monster,
                          kill_category who, int levels )
{
    const bool res_poison = mons_res_poison(monster) > 0;
    bool mondied = false;

    poison_monster(monster, who, levels, false);

    if (!mons_res_asphyx(monster))
    {
        int hurted = roll_dice(2, 6);

        // Note that the hurtage is halved by poison resistance.
        if (res_poison)
            hurted /= 2;

        if (hurted)
        {
            simple_monster_message(monster, " convulses.");
            if ((monster->hit_points -= hurted) < 1)
            {
                _monster_die(monster, beam);
                mondied = true;
            }
        }

        if (!mondied)
            _slow_monster(monster, levels);
    }

    // Deities take notice.
    if (who == KC_YOU)
        did_god_conduct( DID_POISON, 5 + random2(3) );

    return (mondied);
}

// Actually poisons a monster (w/ message).
bool poison_monster( monsters *monster,
                     kill_category from_whom,
                     int levels,
                     bool force,
                     bool verbose)
{
    if (!monster->alive())
        return (false);

    if (!levels || (!force && mons_res_poison(monster) > 0))
        return (false);

    const mon_enchant old_pois = monster->get_ench(ENCH_POISON);
    monster->add_ench( mon_enchant(ENCH_POISON, levels, from_whom) );
    const mon_enchant new_pois = monster->get_ench(ENCH_POISON);

    // Actually do the poisoning.
    // Note: order important here.
    if (verbose && new_pois.degree > old_pois.degree)
    {
        simple_monster_message( monster,
                                !old_pois.degree? " is poisoned."
                                                : " looks even sicker." );
    }

    // Finally, take care of deity preferences.
    if (from_whom == KC_YOU)
        did_god_conduct( DID_POISON, 5 + random2(3) );

    return (new_pois.degree > old_pois.degree);
}

// Actually napalms a monster (w/ message).
void _sticky_flame_monster( int mn, kill_category who, int levels )
{
    monsters *monster = &menv[mn];

    if (!monster->alive())
        return;

    if (mons_res_sticky_flame(monster))
        return;

    if (monster->add_ench(mon_enchant(ENCH_STICKY_FLAME, levels, who)))
    {
        simple_monster_message(monster, " is covered in liquid flames!");
        behaviour_event(monster, ME_WHACK, who == KC_YOU ? MHITYOU : MHITNOT);
    }
}

//
//  Used by monsters in "planning" which spell to cast. Fires off a "tracer"
//  which tells the monster what it'll hit if it breathes/casts etc.
//
//  The output from this tracer function is four variables in the beam struct:
//  fr_count, foe_count: a count of how many friends and foes will (probably)
//  be hit by this beam
//  fr_power, foe_power: a measure of how many 'friendly' hit dice it will
//  affect, and how many 'unfriendly' hit dice.
//
//  Note that beam properties must be set, as the tracer will take them
//  into account, as well as the monster's intelligence.
//
void fire_tracer(const monsters *monster, bolt &pbolt)
{
    // Don't fiddle with any input parameters other than tracer stuff!
    pbolt.is_tracer     = true;
    pbolt.source_x      = monster->x;    // always safe to do.
    pbolt.source_y      = monster->y;
    pbolt.beam_source   = monster_index(monster);
    pbolt.can_see_invis = mons_see_invis(monster);
    pbolt.smart_monster = (mons_intel(monster->type) >= I_NORMAL);
    pbolt.attitude      = mons_attitude(monster);

    // Init tracer variables.
    pbolt.foe_count     = pbolt.fr_count = 0;
    pbolt.foe_power     = pbolt.fr_power = 0;
    pbolt.fr_helped     = pbolt.fr_hurt  = 0;
    pbolt.foe_helped    = pbolt.foe_hurt = 0;
    pbolt.foe_ratio     = 80;        // default - see mons_should_fire()

    // Foe ratio for summoning greater demons & undead -- they may be
    // summoned, but they're hostile and would love nothing better
    // than to nuke the player and his minions.
    if (mons_att_wont_attack(pbolt.attitude)
        && !mons_att_wont_attack(monster->attitude))
    {
        pbolt.foe_ratio = 25;
    }

    // Fire!
    fire_beam(pbolt);

    // Unset tracer flag (convenience).
    pbolt.is_tracer     = false;
}

bool check_line_of_sight( int sx, int sy, int tx, int ty )
{
    const int dist = grid_distance( sx, sy, tx, ty );

    // Can always see one square away.
    if (dist <= 1)
        return (true);

    // Currently we limit the range to 8.
    if (dist > MONSTER_LOS_RANGE)
        return (false);

    // Note that we are guaranteed to be within the player LOS range,
    // so fallback is unnecessary.
    ray_def ray;
    return find_ray( sx, sy, tx, ty, false, ray );
}

// When a mimic is hit by a ranged attack, it teleports away (the slow
// way) and changes its appearance - the appearance change is in
// monster_teleport() in mstuff2.cc.
void mimic_alert(monsters *mimic)
{
    if (!mimic->alive())
        return;

    bool should_id = !testbits(mimic->flags, MF_KNOWN_MIMIC)
                     && player_monster_visible(mimic) && mons_near(mimic);

    // If we got here, we at least got a resists message, if not
    // a full wounds printing. Thus, might as well id the mimic.
    if (mimic->has_ench(ENCH_TP))
    {
        if (should_id)
            mimic->flags |= MF_KNOWN_MIMIC;

        return;
    }

    const bool instant_tele = !one_chance_in(3);
    monster_teleport( mimic, instant_tele );

    // At least for this short while, we know it's a mimic.
    if (!instant_tele && should_id)
        mimic->flags |= MF_KNOWN_MIMIC;
}

static bool _isBouncy(bolt &beam, unsigned char gridtype)
{
    if (beam.name[0] == '0')
        return (false);

    if (beam.flavour == BEAM_ELECTRICITY && gridtype != DNGN_METAL_WALL)
        return (true);

    if ((beam.flavour == BEAM_FIRE || beam.flavour == BEAM_COLD)
         && gridtype == DNGN_GREEN_CRYSTAL_WALL )
    {
        return (true);
    }
    return (false);
}

static void _beam_explodes(bolt &beam, int x, int y)
{
    cloud_type cl_type;

    // This will be the last thing this beam does.  Set target_x
    // and target_y to hold explosion co'ords.

    beam.target_x = x;
    beam.target_y = y;

    // Generic explosion.
    if (beam.is_explosion) // beam.flavour == BEAM_EXPLOSION || beam.flavour == BEAM_HOLY)
    {
        _explosion1(beam);
        return;
    }

    if (beam.flavour >= BEAM_POTION_STINKING_CLOUD
        && beam.flavour <= BEAM_POTION_RANDOM)
    {
        switch (beam.flavour)
        {
        case BEAM_POTION_STINKING_CLOUD:
            beam.colour = GREEN;
            break;

        case BEAM_POTION_POISON:
            beam.colour = (coinflip() ? GREEN : LIGHTGREEN);
            break;

        case BEAM_POTION_MIASMA:
        case BEAM_POTION_BLACK_SMOKE:
            beam.colour = DARKGREY;
            break;

        case BEAM_POTION_STEAM:
        case BEAM_POTION_GREY_SMOKE:
            beam.colour = LIGHTGREY;
            break;

        case BEAM_POTION_FIRE:
            beam.colour = (coinflip() ? RED : LIGHTRED);
            break;

        case BEAM_POTION_COLD:
            beam.colour = (coinflip() ? BLUE : LIGHTBLUE);
            break;

        case BEAM_POTION_BLUE_SMOKE:
            beam.colour = LIGHTBLUE;
            break;

        case BEAM_POTION_PURP_SMOKE:
            beam.colour = MAGENTA;
            break;

        case BEAM_POTION_RANDOM:
        default:
            // Leave it the colour of the potion, the clouds will colour
            // themselves on the next refresh. -- bwr
            break;
        }

        _explosion1(beam);
        return;
    }

    if (beam.is_tracer)
        return;

    // cloud producer -- POISON BLAST
    if (beam.name == "blast of poison")
    {
        big_cloud( CLOUD_POISON, _whose_kill(beam), x, y, 0, 7 + random2(5) );
        return;
    }

    // cloud producer -- FOUL VAPOR (SWAMP DRAKE?)
    if (beam.name == "foul vapour")
    {
        cl_type = (beam.flavour == BEAM_MIASMA) ? CLOUD_MIASMA : CLOUD_STINK;
        big_cloud( cl_type, _whose_kill(beam), x, y, 0, 9 );
        return;
    }

    if (beam.name == "freezing blast")
    {
        big_cloud( CLOUD_COLD, _whose_kill(beam), x, y,
                   random_range(10, 15), 9 );
        return;
    }

    // special cases - orbs & blasts of cold
    if (beam.name == "orb of electricity"
        || beam.name == "metal orb"
        || beam.name == "great blast of cold")
    {
        _explosion1( beam );
        return;
    }

    // cloud producer only -- stinking cloud
    if (beam.name == "ball of vapour")
    {
        _explosion1( beam );
        return;
    }
}

static bool _beam_term_on_target(bolt &beam, int x, int y)
{
    if (beam.flavour == BEAM_LINE_OF_SIGHT)
    {
        if (beam.thrower != KILL_YOU_MISSILE)
            beam.foe_count++;
        return (true);
    }

    // Generic - all explosion-type beams can be targeted at empty space,
    // and will explode there.  This semantic also means that a creature
    // in the target cell will have no chance to dodge or block, so we
    // DON'T affect() the cell if this function returns true!

    if (beam.is_explosion || beam.is_big_cloud)
        return (true);

    // POISON BLAST
    if (beam.name == "blast of poison")
        return (true);

    // FOUL VAPOR (SWAMP DRAKE)
    if (beam.name == "foul vapour")
        return (true);

    // STINKING CLOUD
    if (beam.name == "ball of vapour")
        return (true);

    if (beam.aimed_at_spot && x == beam.target_x && y == beam.target_y)
        return (true);

    return (false);
}

void beam_drop_object( bolt &beam, item_def *item, int x, int y )
{
    ASSERT( item != NULL );

    // Conditions: beam is missile and not tracer.
    if (beam.is_tracer || beam.flavour != BEAM_MISSILE)
        return;

    if (item->flags & ISFLAG_SUMMONED)
    {
        if (see_grid(x, y))
        {
            mprf("%s disappears in a puff of smoke!",
                 item->name(DESC_CAP_THE).c_str());
        }
        item_was_destroyed(*item, beam.beam_source);

        return;
    }

    if (YOU_KILL(beam.thrower)
            && !thrown_object_destroyed(item, x, y, false)
        || MON_KILL(beam.thrower)
            && !mons_thrown_object_destroyed(item, x, y, false,
                                             beam.beam_source))
    {
        if (item->sub_type == MI_THROWING_NET)
        {
            // Player or monster on position is caught in net.
            if (you.x_pos == x && you.y_pos == y && you.attribute[ATTR_HELD]
                || mgrd[x][y] != NON_MONSTER &&
                    mons_is_caught(&menv[mgrd[x][y]]))
            {
                // If no trapping net found mark this one.
                if (get_trapping_net(x,y, true) == NON_ITEM)
                    set_item_stationary(*item);
            }
        }

        copy_item_to_grid( *item, x, y, 1 );
    }
}

// Returns true if the beam hits the player, fuzzing the beam if necessary
// for monsters without see invis firing tracers at the player.
static bool _found_player(const bolt &beam, int x, int y)
{
    const bool needs_fuzz = (beam.is_tracer && !beam.can_see_invis
                             && you.invisible() && !YOU_KILL(beam.thrower));
    const int dist = needs_fuzz? 2 : 0;

    return (grid_distance(x, y, you.x_pos, you.y_pos) <= dist);
}

int affect(bolt &beam, int x, int y, item_def *item)
{
    // Extra range used by hitting something.
    int rangeUsed = 0;

    // Line of sight never affects anything.
    if (beam.flavour == BEAM_LINE_OF_SIGHT)
        return (0);

    if (grid_is_solid(grd[x][y]))
    {
        if (beam.is_tracer)          // Tracers always stop on walls.
            return (BEAM_STOP);

        if (_affects_wall(beam, grd[x][y]))
            rangeUsed += _affect_wall(beam, x, y);

        // If it's still a wall, quit - we can't do anything else to a
        // wall (but we still might be able to do something to any
        // monster inside the wall).  Otherwise effects (like clouds,
        // etc.) are still possible.
        if (grid_is_solid(grd[x][y]))
        {
            int mid = mgrd[x][y];
            if (mid != NON_MONSTER)
            {
                monsters *mon = &menv[mid];
                if (_affect_mon_in_wall(beam, NULL, x, y))
                    rangeUsed += _affect_monster( beam, mon, item );
                else if (you.can_see(mon))
                {
                    mprf("The %s protects %s from harm.",
                         raw_feature_description(grd(mon->pos())).c_str(),
                         mon->name(DESC_NOCAP_THE).c_str());
                }
            }

            return (rangeUsed);
        }
    }

    // grd[x][y] will NOT be a wall for the remainder of this function.

    // If not a tracer, affect items and place clouds.
    if (!beam.is_tracer)
    {
        const int burn_power = (beam.is_explosion) ? 5 :
                                    (beam.is_beam) ? 3 : 2;

        expose_items_to_element(beam.flavour, x, y, burn_power);
        rangeUsed += _affect_place_clouds(beam, x, y);
    }

    // If player is at this location, try to affect unless term_on_target.
    if (_found_player(beam, x, y))
    {
        // Done this way so that poison blasts affect the target once (via
        // place_cloud) and explosion spells only affect the target once
        // (during the explosion phase, not an initial hit during the
        // beam phase).
        if (!beam.is_big_cloud
            && (!beam.is_explosion || beam.in_explosion_phase))
        {
            rangeUsed += _affect_player( beam, item );
        }

        if (_beam_term_on_target(beam, x, y))
            return (BEAM_STOP);
    }

    // If there is a monster at this location, affect it.
    // Submerged monsters aren't really there. -- bwr
    int mid = mgrd[x][y];
    if (mid != NON_MONSTER)
    {
        monsters *mon = &menv[mid];
        const bool invisible = YOU_KILL(beam.thrower) && !you.can_see(mon);

        // Monsters submerged in shallow water can be targeted by beams
        // aimed at that spot.
        if (mon->alive()
            // Don't stop tracers on invisible monsters.
            && (!invisible || !beam.is_tracer)
            && (!mon->submerged()
                || beam.aimed_at_spot && beam.target() == mon->pos()
                   && grd(mon->pos()) == DNGN_SHALLOW_WATER))
        {
            if (!beam.is_big_cloud
                && (!beam.is_explosion || beam.in_explosion_phase))
            {
                rangeUsed += _affect_monster( beam, &menv[mid], item );
            }

            if (_beam_term_on_target(beam, x, y))
                return (BEAM_STOP);
        }
    }

    return (rangeUsed);
}

static bool _is_fiery(const bolt &beam)
{
    return (beam.flavour == BEAM_FIRE || beam.flavour == BEAM_HELLFIRE
            || beam.flavour == BEAM_LAVA);
}

static bool _is_superhot(const bolt &beam)
{
    if (!_is_fiery(beam))
        return (false);

    return (beam.name == "bolt of fire"
            || beam.name == "bolt of magma"
            || beam.name.find("hellfire") != std::string::npos
               && beam.in_explosion_phase);
}

static bool _affects_wall(const bolt &beam, int wall)
{
    // digging
    if (beam.flavour == BEAM_DIGGING)
        return (true);

    // Isn't this much nicer than the hack to remove ice bolts, disrupt,
    // and needles (just because they were also coloured "white") -- bwr
    if (beam.flavour == BEAM_DISINTEGRATION && beam.damage.num >= 3)
        return (true);

    if (_is_fiery(beam) && wall == DNGN_WAX_WALL)
        return (true);

    // eye of devastation?
    if (beam.flavour == BEAM_NUKE)
        return (true);

    return (false);
}

// Returns amount of extra range used up by affectation of this wall.
static int _affect_wall(bolt &beam, int x, int y)
{
    int rangeUsed = 0;

    // DIGGING
    if (beam.flavour == BEAM_DIGGING)
    {
        if (grd[x][y] == DNGN_STONE_WALL
            || grd[x][y] == DNGN_METAL_WALL
            || grd[x][y] == DNGN_PERMAROCK_WALL
            || grd[x][y] == DNGN_CLEAR_STONE_WALL
            || grd[x][y] == DNGN_CLEAR_PERMAROCK_WALL
            || !in_bounds(x, y))
        {
            return (0);
        }

        if (grd[x][y] == DNGN_ROCK_WALL || grd[x][y] == DNGN_CLEAR_ROCK_WALL)
        {
            grd[x][y] = DNGN_FLOOR;

            // Blood does not transfer onto floor.
            if (is_bloodcovered(x,y))
                env.map[x][y].property = FPROP_NONE;

            if (!beam.msg_generated)
            {
                if (!silenced(you.x_pos, you.y_pos))
                {
                    mpr("You hear a grinding noise.", MSGCH_SOUND);
                    beam.obvious_effect = true;
                }

                beam.msg_generated = true;
            }
        }

        return (rangeUsed);
    }
    // END DIGGING EFFECT

    // FIRE effect
    if (_is_fiery(beam))
    {
        const int wgrd = grd[x][y];
        if (wgrd != DNGN_WAX_WALL)
            return (0);

        if (!_is_superhot(beam))
        {
            if (beam.flavour != BEAM_HELLFIRE)
            {
                if (see_grid(x, y))
                    _beam_mpr(MSGCH_PLAIN,
                             "The wax appears to soften slightly.");
                else if (player_can_smell())
                    _beam_mpr(MSGCH_PLAIN, "You smell warm wax.");
            }

            return (BEAM_STOP);
        }

        grd[x][y] = DNGN_FLOOR;
        if (see_grid(x, y))
            _beam_mpr(MSGCH_PLAIN, "The wax bubbles and burns!");
        else if (player_can_smell())
            _beam_mpr(MSGCH_PLAIN, "You smell burning wax.");

        place_cloud(CLOUD_FIRE, x, y, random2(10) + 15, _whose_kill(beam));

        beam.obvious_effect = true;

        return (BEAM_STOP);
    }

    // NUKE / DISRUPT
    if (beam.flavour == BEAM_DISINTEGRATION || beam.flavour == BEAM_NUKE)
    {
        int targ_grid = grd[x][y];

        if ((targ_grid == DNGN_ROCK_WALL || targ_grid == DNGN_WAX_WALL
                 || targ_grid == DNGN_CLEAR_ROCK_WALL)
            && in_bounds(x, y))
        {
            grd[ x ][ y ] = DNGN_FLOOR;
            if (!silenced(you.x_pos, you.y_pos))
            {
                mpr("You hear a grinding noise.", MSGCH_SOUND);
                beam.obvious_effect = true;
            }
        }

        if (targ_grid == DNGN_ORCISH_IDOL
            || targ_grid == DNGN_GRANITE_STATUE)
        {
            grd[x][y] = DNGN_FLOOR;

            // Blood does not transfer onto floor.
            if (is_bloodcovered(x,y))
                env.map[x][y].property = FPROP_NONE;

            if (!silenced(you.x_pos, you.y_pos))
            {
                if (!see_grid( x, y ))
                    mpr("You hear a hideous screaming!", MSGCH_SOUND);
                else
                {
                    mpr("The statue screams as its substance crumbles away!",
                            MSGCH_SOUND);
                }
            }
            else if (see_grid(x,y))
                mpr("The statue twists and shakes as its substance crumbles away!");

            if (targ_grid == DNGN_ORCISH_IDOL
                && beam.beam_source == NON_MONSTER)
            {
                beogh_idol_revenge();
            }
            beam.obvious_effect = true;
        }

        return (BEAM_STOP);
    }

    return (rangeUsed);
}

static int _affect_place_clouds(bolt &beam, int x, int y)
{
    if (beam.in_explosion_phase)
    {
        _affect_place_explosion_clouds( beam, x, y );
        return (0);       // return value irrelevant for explosions
    }

    // check for CLOUD HITS
    if (env.cgrid[x][y] != EMPTY_CLOUD)     // hit a cloud
    {
        // polymorph randomly changes clouds in its path
        if (beam.flavour == BEAM_POLYMORPH)
        {
            env.cloud[ env.cgrid[x][y] ].type =
                static_cast<cloud_type>(1 + random2(8));
        }

        // now exit (all enchantments)
        if (beam.name[0] == '0')
            return (0);

        int clouty = env.cgrid[x][y];

        // fire cancelling cold & vice versa
        if ((env.cloud[clouty].type == CLOUD_COLD
             && (beam.flavour == BEAM_FIRE
                 || beam.flavour == BEAM_LAVA))
            || (env.cloud[clouty].type == CLOUD_FIRE
                && beam.flavour == BEAM_COLD))
        {
            if (!silenced(x, y)
                && !silenced(you.x_pos, you.y_pos))
            {
                mpr("You hear a sizzling sound!", MSGCH_SOUND);
            }

            delete_cloud( clouty );
            return (5);
        }
    }

    // POISON BLAST
    if (beam.name == "blast of poison")
        place_cloud( CLOUD_POISON, x, y, random2(4) + 2, _whose_kill(beam) );

    // FIRE/COLD over water/lava
    if (grd[x][y] == DNGN_LAVA && beam.flavour == BEAM_COLD
        || grid_is_watery(grd[x][y]) && _is_fiery(beam))
    {
        place_cloud( CLOUD_STEAM, x, y, 2 + random2(5), _whose_kill(beam) );
    }

    if (grid_is_watery(grd[x][y]) && beam.flavour == BEAM_COLD
        && beam.damage.num * beam.damage.size > 35)
    {
        place_cloud( CLOUD_COLD, x, y,
                     beam.damage.num * beam.damage.size / 30 + 1,
                     _whose_kill(beam) );
    }

    // GREAT BLAST OF COLD
    if (beam.name == "great blast of cold")
        place_cloud( CLOUD_COLD, x, y, random2(5) + 3, _whose_kill(beam) );


    // BALL OF STEAM
    if (beam.name == "ball of steam")
        place_cloud( CLOUD_STEAM, x, y, random2(5) + 2, _whose_kill(beam) );

    if (beam.flavour == BEAM_MIASMA)
        place_cloud( CLOUD_MIASMA, x, y, random2(5) + 2, _whose_kill(beam) );

    // POISON GAS
    if (beam.name == "poison gas")
        place_cloud( CLOUD_POISON, x, y, random2(4) + 3, _whose_kill(beam) );

    return (0);
}

static void _affect_place_explosion_clouds(bolt &beam, int x, int y)
{
    cloud_type cl_type;
    int duration;

    // First check: FIRE/COLD over water/lava.
    if (grd[x][y] == DNGN_LAVA && beam.flavour == BEAM_COLD
         || grid_is_watery(grd[x][y]) && _is_fiery(beam))
    {
        place_cloud( CLOUD_STEAM, x, y, 2 + random2(5), _whose_kill(beam) );
        return;
    }

    if (beam.flavour >= BEAM_POTION_STINKING_CLOUD
        && beam.flavour <= BEAM_POTION_RANDOM)
    {
        duration = roll_dice( 2, 3 + beam.ench_power / 20 );

        switch (beam.flavour)
        {
        case BEAM_POTION_STINKING_CLOUD:
        case BEAM_POTION_POISON:
        case BEAM_POTION_MIASMA:
        case BEAM_POTION_STEAM:
        case BEAM_POTION_FIRE:
        case BEAM_POTION_COLD:
        case BEAM_POTION_BLACK_SMOKE:
        case BEAM_POTION_GREY_SMOKE:
        case BEAM_POTION_BLUE_SMOKE:
        case BEAM_POTION_PURP_SMOKE:
            cl_type = beam2cloud(beam.flavour);
            break;

        case BEAM_POTION_RANDOM:
            switch (random2(11))
            {
            case 0:  cl_type = CLOUD_FIRE;           break;
            case 1:  cl_type = CLOUD_STINK;          break;
            case 2:  cl_type = CLOUD_COLD;           break;
            case 3:  cl_type = CLOUD_POISON;         break;
            case 4:  cl_type = CLOUD_BLACK_SMOKE;    break;
            case 5:  cl_type = CLOUD_GREY_SMOKE;     break;
            case 6:  cl_type = CLOUD_BLUE_SMOKE;     break;
            case 7:  cl_type = CLOUD_PURP_SMOKE;     break;
            default: cl_type = CLOUD_STEAM;          break;
            }
            break;

        default:
            cl_type = CLOUD_STEAM;
            break;
        }

        place_cloud( cl_type, x, y, duration, _whose_kill(beam) );
    }

    // then check for more specific explosion cloud types.
    if (beam.name == "ice storm")
        place_cloud( CLOUD_COLD, x, y, 2 + random2avg(5, 2), _whose_kill(beam) );

    if (beam.name == "stinking cloud")
    {
        duration =  1 + random2(4) + random2( (beam.ench_power / 50) + 1 );
        place_cloud( CLOUD_STINK, x, y, duration, _whose_kill(beam) );
    }

    if (beam.name == "great blast of fire")
    {
        duration = 1 + random2(5) + roll_dice( 2, beam.ench_power / 5 );

        if (duration > 20)
            duration = 20 + random2(4);

        place_cloud( CLOUD_FIRE, x, y, duration, _whose_kill(beam) );

        if (grd[x][y] == DNGN_FLOOR && mgrd[x][y] == NON_MONSTER
            && one_chance_in(4))
        {
            const god_type god =
                (crawl_state.is_god_acting()) ? crawl_state.which_god_acting()
                                              : GOD_NO_GOD;

            mons_place(
                mgen_data::hostile_at(
                    MONS_FIRE_VORTEX, coord_def(x, y), 0, 0, false, god));
        }
    }
}

static int _beam_ouch_agent(const bolt &beam)
{
    return YOU_KILL(beam.thrower) ? 0 : beam.beam_source;
}

// A little helper function to handle the calling of ouch()...
static void _beam_ouch(int dam, bolt &beam)
{
    // The order of this is important.
    if (YOU_KILL(beam.thrower) && beam.aux_source.empty())
    {
        ouch(dam, 0, KILLED_BY_TARGETTING);
    }
    else if (MON_KILL(beam.thrower))
    {
        if (beam.flavour == BEAM_SPORE)
            ouch(dam, beam.beam_source, KILLED_BY_SPORE);
        else
        {
            ouch(dam, beam.beam_source, KILLED_BY_BEAM,
                 beam.aux_source.c_str());
        }
    }
    else // KILL_MISC || (YOU_KILL && aux_source)
    {
        ouch( dam, beam.beam_source, KILLED_BY_WILD_MAGIC,
              beam.aux_source.c_str() );
    }
}

// [ds] Apply a fuzz if the monster lacks see invisible and is trying to target
// an invisible player. This makes invisibility slightly more powerful.
static bool _fuzz_invis_tracer(bolt &beem)
{
    // Did the monster have a rough idea of where you are?
    int dist = grid_distance(beem.target_x, beem.target_y,
                             you.x_pos, you.y_pos);

    // No, ditch this.
    if (dist > 2)
        return (false);

    const int beam_src = _beam_source(beem);
    if (beam_src != MHITNOT && beam_src != MHITYOU)
    {
        // Monsters that can sense invisible
        const monsters *mon = &menv[beam_src];
        if (mons_sense_invis(mon))
            return (!dist);
    }

    // Apply fuzz now.
    int xfuzz = random_range(-2, 2),
        yfuzz = random_range(-2, 2);

    const int newx = beem.target_x + xfuzz,
              newy = beem.target_y + yfuzz;

    if (in_bounds(newx, newy)
        && (newx != beem.source_x
            || newy != beem.source_y))
    {
        beem.target_x = newx;
        beem.target_y = newy;
    }

    // Fire away!
    return (true);
}

// A first step towards to-hit sanity for beams. We're still being
// very kind to the player, but it should be fairer to monsters than
// 4.0.
bool test_beam_hit(int attack, int defence)
{
#ifdef DEBUG_DIAGNOSTICS
    mprf(MSGCH_DIAGNOSTICS, "Beam attack: %d, defence: %d", attack, defence);
#endif
    return (attack == AUTOMATIC_HIT
            || random2(attack) >= random2avg(defence, 2));
}

static std::string _beam_zapper(const bolt &beam)
{
    const int beam_src = _beam_source(beam);
    if (beam_src == MHITYOU)
        return ("self");
    else if (beam_src == MHITNOT)
        return ("");
    else
        return menv[beam_src].name(DESC_PLAIN);
}

static bool _beam_is_harmless(bolt &beam, monsters *mon)
{
    // For enchantments, this is already handled in _nasty_beam().
    if (beam.name[0] == '0')
        return (!_nasty_beam(mon, beam));

    // The others are handled here.
    switch (beam.flavour)
    {
    case BEAM_DIGGING:
        return (true);

    // Cleansing flame doesn't affect player's followers.
    case BEAM_HOLY:
        return (mons_is_holy(mon)
                || is_good_god(you.religion)
                   && ( is_follower(mon) || mons_neutral(mon) ));

    case BEAM_STEAM:
        return (mons_res_steam(mon) >= 3);

    case BEAM_FIRE:
        return (mons_res_fire(mon) >= 3);

    case BEAM_COLD:
        return (mons_res_cold(mon) >= 3);

    case BEAM_MIASMA:
    case BEAM_NEG:
        return (mons_res_negative_energy(mon) == 3);

    case BEAM_ELECTRICITY:
        return (mons_res_elec(mon) >= 3);

    case BEAM_POISON:
        return (mons_res_poison(mon) >= 3);

    case BEAM_ACID:
        return (mons_res_acid(mon) >= 3);

    default:
        return (false);
    }
}

static bool _beam_is_harmless_player(bolt &beam)
{
#ifdef DEBUG_DIAGNOSTICS
    mprf(MSGCH_DIAGNOSTICS, "beam flavour: %d", beam.flavour);
#endif

    // Shouldn't happen anyway since enchantments are either aimed at self
    // (not prompted) or cast at monsters and don't explode or bounce.
    if (beam.name[0] == '0')
        return (false);

    // The others are handled here.
    switch (beam.flavour)
    {
    case BEAM_DIGGING:
        return (true);

    // Cleansing flame doesn't affect player's followers.
    case BEAM_HOLY:
        return (is_good_god(you.religion));

    case BEAM_STEAM:
        return (player_res_steam(false) >= 3);

    case BEAM_MIASMA:
    case BEAM_NEG:
        return (player_prot_life(false) >= 3);

    case BEAM_POISON:
        return (player_res_poison(false));

    case BEAM_POTION_STINKING_CLOUD:
        return (player_res_poison(false) || player_mental_clarity(false));

    case BEAM_ELECTRICITY:
        return (player_res_electricity(false));

    case BEAM_FIRE:
    case BEAM_COLD:
    case BEAM_ACID:
        // Fire and ice can destroy inventory items, acid damage equipment.
        return (false);

    default:
        return (false);
    }
}

// Returns amount of extra range used up by affectation of the player.
static int _affect_player( bolt &beam, item_def *item )
{
    // Digging -- don't care.
    if (beam.flavour == BEAM_DIGGING)
        return (0);

    // Check for tracer.
    if (beam.is_tracer)
    {
        // Check whether thrower can see player, unless thrower == player.
        if (YOU_KILL(beam.thrower))
        {
            // Don't ask if we're aiming at ourselves.
            if (!beam.aimed_at_feet && !beam.dont_stop_player
                && !_beam_is_harmless_player(beam))
            {
                if (yesno("That beam is likely to hit you. Continue anyway?",
                    false, 'n'))
                {
                    beam.fr_count += 1;
                    beam.fr_power += you.experience_level;
                    beam.dont_stop_player = true;
                }
                else
                {
                    beam.beam_cancelled = true;
                    return (BEAM_STOP);
                }
            }
        }
        else if (beam.can_see_invis || !you.invisible()
                 || _fuzz_invis_tracer(beam))
        {
            if (mons_att_wont_attack(beam.attitude))
            {
                beam.fr_count += 1;
                beam.fr_power += you.experience_level;
            }
            else
            {
                beam.foe_count++;
                beam.foe_power += you.experience_level;
            }
        }
        return (_range_used_on_hit(beam));
    }

    // Trigger an interrupt, so travel will stop on misses
    // which generate smoke.
    if (!YOU_KILL(beam.thrower))
        interrupt_activity(AI_MONSTER_ATTACKS);

    // BEGIN real beam code
    beam.msg_generated = true;

    // Use beamHit, NOT beam.hit, for modification of tohit.. geez!
    int beamHit = beam.hit;

    // Monsters shooting at an invisible player are very inaccurate.
    if (you.invisible() && !beam.can_see_invis)
        beamHit /= 2;

    if (beam.name[0] != '0')
    {
        if (!beam.is_explosion && !beam.aimed_at_feet)
        {
            // BEGIN BEAM/MISSILE
            int dodge = player_evasion();

            if (beam.is_beam)
            {
                // Beams can be dodged.
                if (player_light_armour(true)
                    && !beam.aimed_at_feet && coinflip())
                {
                    exercise(SK_DODGING, 1);
                }

                if (you.duration[DUR_DEFLECT_MISSILES])
                    beamHit = random2(beamHit * 2) / 3;
                else if (you.duration[DUR_REPEL_MISSILES]
                         || player_mutation_level(MUT_REPULSION_FIELD) == 3)
                {
                    beamHit -= random2(beamHit / 2);
                }

                if (!test_beam_hit(beamHit, dodge))
                {
                    mprf("The %s misses you.", beam.name.c_str());
                    return (0);           // no extra used by miss!
                }
            }
            else if (_beam_is_blockable(beam))
            {
                // Non-beams can be blocked or dodged.
                if (you.shield()
                    && !beam.aimed_at_feet
                    && player_shield_class() > 0)
                {
                    int exer = one_chance_in(3) ? 1 : 0;
                    const int hit = random2( beam.hit * 130 / 100
                                             + you.shield_block_penalty() );

                    const int block = you.shield_bonus();

#ifdef DEBUG_DIAGNOSTICS
                    mprf(MSGCH_DIAGNOSTICS, "Beamshield: hit: %d, block %d",
                         hit, block);
#endif
                    if (hit < block)
                    {
                        mprf( "You block the %s.", beam.name.c_str() );
                        you.shield_block_succeeded();
                        return (BEAM_STOP);
                    }

                    // Some training just for the "attempt".
                    if (coinflip())
                        exercise( SK_SHIELDS, exer );
                }

                if (player_light_armour(true) && !beam.aimed_at_feet
                    && coinflip())
                {
                    exercise(SK_DODGING, 1);
                }

                if (you.duration[DUR_DEFLECT_MISSILES])
                    beamHit = random2(beamHit / 2);
                else if (you.duration[DUR_REPEL_MISSILES]
                         || player_mutation_level(MUT_REPULSION_FIELD) == 3)
                {
                    beamHit = random2(beamHit);
                }

                // miss message
                if (!test_beam_hit(beamHit, dodge))
                {
                    mprf("The %s misses you.", beam.name.c_str());
                    return (0);
                }
            }
        }
    }
    else
    {
        bool nasty = true, nice = false;

        // BEGIN enchantment beam
        if (beam.flavour != BEAM_HASTE
            && beam.flavour != BEAM_INVISIBILITY
            && beam.flavour != BEAM_HEALING
            && beam.flavour != BEAM_POLYMORPH
            && beam.flavour != BEAM_DISPEL_UNDEAD
            && (beam.flavour != BEAM_TELEPORT && beam.flavour != BEAM_BANISH
                || !beam.aimed_at_feet)
            && you_resist_magic( beam.ench_power ))
        {
            bool need_msg = true;
            if (beam.thrower != KILL_YOU_MISSILE
                && !invalid_monster_index(beam.beam_source))
            {
                monsters *mon = &menv[beam.beam_source];
                if (!player_monster_visible(mon))
                {
                    mpr("Something tries to affect you, but you resist.");
                    need_msg = false;
                }
            }
            if (need_msg)
                canned_msg(MSG_YOU_RESIST);

            // You *could* have gotten a free teleportation in the Abyss,
            // but no, you resisted.
            if (beam.flavour != BEAM_TELEPORT && you.level_type == LEVEL_ABYSS)
                xom_is_stimulated(255);

            return (_range_used_on_hit(beam));
        }

        _ench_animation( beam.flavour );

        // these colors are misapplied - see mons_ench_f2() {dlb}
        switch (beam.flavour)
        {
        case BEAM_SLEEP:
            you.put_to_sleep(beam.ench_power);
            break;

        case BEAM_BACKLIGHT:
            if (!you.duration[DUR_INVIS])
            {
                if (you.duration[DUR_BACKLIGHT])
                    mpr("You glow brighter.");
                else
                    mpr("You are outlined in light.");

                you.duration[DUR_BACKLIGHT] += random_range(15, 35);
                if (you.duration[DUR_BACKLIGHT] > 250)
                    you.duration[DUR_BACKLIGHT] = 250;

                beam.obvious_effect = true;
            }
            else
            {
                mpr("You feel strangely conspicuous.");
                if ((you.duration[DUR_BACKLIGHT] += random_range(3, 5)) > 250)
                    you.duration[DUR_BACKLIGHT] = 250;
                beam.obvious_effect = true;
            }
            break;

        case BEAM_POLYMORPH:
            if (MON_KILL(beam.thrower))
            {
                mpr("Strange energies course through your body.");
                you.mutate();
                beam.obvious_effect = true;
            }
            else if (get_ident_type(OBJ_WANDS, WAND_POLYMORPH_OTHER)
                     == ID_KNOWN_TYPE)
            {
                mpr("This is polymorph other only!");
            }
            else
                canned_msg( MSG_NOTHING_HAPPENS );

            break;

        case BEAM_SLOW:
            potion_effect( POT_SLOWING, beam.ench_power );
            beam.obvious_effect = true;
            break;

        case BEAM_HASTE:
            potion_effect( POT_SPEED, beam.ench_power );
            contaminate_player( 1, beam.effect_known );
            beam.obvious_effect = true;
            nasty = false;
            nice  = true;
            break;

        case BEAM_HEALING:
            potion_effect( POT_HEAL_WOUNDS, beam.ench_power );
            beam.obvious_effect = true;
            nasty = false;
            nice  = true;
            break;

        case BEAM_PARALYSIS:
            potion_effect( POT_PARALYSIS, beam.ench_power );
            beam.obvious_effect = true;
            break;

        case BEAM_PETRIFY:
            you.petrify( beam.ench_power );
            beam.obvious_effect = true;
            break;

        case BEAM_CONFUSION:
            potion_effect( POT_CONFUSION, beam.ench_power );
            beam.obvious_effect = true;
            break;

        case BEAM_INVISIBILITY:
            potion_effect( POT_INVISIBILITY, beam.ench_power );
            contaminate_player( 1 + random2(2), beam.effect_known );
            beam.obvious_effect = true;
            nasty = false;
            nice  = true;
            break;

        case BEAM_TELEPORT:
            you_teleport();

            // An enemy helping you escape while in the Abyss, or an
            // enemy stabilizing a teleport that was about to happen.
            if (!mons_att_wont_attack(beam.attitude)
                && you.level_type == LEVEL_ABYSS)
            {
                xom_is_stimulated(255);
            }

            beam.obvious_effect = true;
            break;

        case BEAM_BLINK:
            random_blink(false);
            beam.obvious_effect = true;
            break;

        case BEAM_CHARM:
            potion_effect( POT_CONFUSION, beam.ench_power );
            beam.obvious_effect = true;
            break;     // enslavement - confusion?

        case BEAM_BANISH:
            if (YOU_KILL(beam.thrower))
            {
                mpr("This spell isn't strong enough to banish yourself.");
                break;
            }
            if (you.level_type == LEVEL_ABYSS)
            {
                mpr("You feel trapped.");
                break;
            }
            you.banished        = true;
            you.banished_by     = _beam_zapper(beam);
            beam.obvious_effect = true;
            break;

        case BEAM_PAIN:
            if (player_res_torment())
            {
                mpr("You are unaffected.");
                break;
            }

            mpr("Pain shoots through your body!");

            if (beam.aux_source.empty())
                beam.aux_source = "by nerve-wracking pain";

            _beam_ouch( roll_dice( beam.damage ), beam );
            beam.obvious_effect = true;
            break;

        case BEAM_DISPEL_UNDEAD:
            if (!you.is_undead)
            {
                mpr("You are unaffected.");
                break;
            }

            mpr( "You convulse!" );

            if (beam.aux_source.empty())
                beam.aux_source = "by dispel undead";

            if (you.is_undead == US_SEMI_UNDEAD)
            {
                if (you.hunger_state == HS_ENGORGED)
                    beam.damage.size /= 2;
                else if (you.hunger_state > HS_SATIATED)
                {
                    beam.damage.size *= 2;
                    beam.damage.size /= 3;
                }
            }
            _beam_ouch( roll_dice( beam.damage ), beam );
            beam.obvious_effect = true;
            break;

        case BEAM_DISINTEGRATION:
            mpr("You are blasted!");

            if (beam.aux_source.empty())
                beam.aux_source = "a disintegration bolt";

            _beam_ouch( roll_dice( beam.damage ), beam );
            beam.obvious_effect = true;
            break;

        default:
            // _All_ enchantments should be enumerated here!
            mpr("Software bugs nibble your toes!");
            break;
        }

        if (nasty)
        {
            if (mons_att_wont_attack(beam.attitude))
            {
                beam.fr_hurt++;
                if (beam.beam_source == NON_MONSTER)
                {
                    // Beam from player rebounded and hit player.
                    if (!beam.aimed_at_feet)
                        xom_is_stimulated(255);
                }
                else
                {
                    // Beam from an ally or neutral.
                    xom_is_stimulated(128);
                }
            }
            else
                beam.foe_hurt++;
        }

        if (nice)
        {
            if (mons_att_wont_attack(beam.attitude))
                beam.fr_helped++;
            else
            {
                beam.foe_helped++;
                xom_is_stimulated(128);
            }
        }

        // Regardless of affect, we need to know if this is a stopper
        // or not - it seems all of the above are.
        return (_range_used_on_hit(beam));

        // END enchantment beam
    }

    // THE BEAM IS NOW GUARANTEED TO BE A NON-ENCHANTMENT WHICH HIT

    const bool engulfs = (beam.is_explosion || beam.is_big_cloud);
    mprf( "The %s %s you!",
          beam.name.c_str(), (engulfs) ? "engulfs" : "hits" );

    int hurted = 0;
    int burn_power = (beam.is_explosion) ? 5 :
                          (beam.is_beam) ? 3 : 2;

    // Roll the damage.
    hurted += roll_dice( beam.damage );

#if DEBUG_DIAGNOSTICS
    int roll = hurted;
#endif

    int armour_damage_reduction = random2( 1 + player_AC() );
    if (beam.flavour == BEAM_ELECTRICITY)
        armour_damage_reduction /= 2;
    hurted -= armour_damage_reduction;

    // shrapnel
    if (beam.flavour == BEAM_FRAG && !player_light_armour())
    {
        hurted -= random2( 1 + player_AC() );
        hurted -= random2( 1 + player_AC() );
    }

#if DEBUG_DIAGNOSTICS
    mprf(MSGCH_DIAGNOSTICS,
         "Player damage: rolled=%d; after AC=%d", roll, hurted );
#endif

    if (you.equip[EQ_BODY_ARMOUR] != -1)
    {
        if (!player_light_armour(false) && one_chance_in(4)
            && x_chance_in_y(item_mass(you.inv[you.equip[EQ_BODY_ARMOUR]]) + 1,
                             1000))
        {
            exercise( SK_ARMOUR, 1 );
        }
    }

    bool was_affected = false;
    int  old_hp       = you.hp;

    if (hurted < 0)
        hurted = 0;

    // If the beam is an actual missile or of the MMISSILE type (Earth magic)
    // we might bleed on the floor.
    if (!engulfs
        && (beam.flavour == BEAM_MISSILE || beam.flavour == BEAM_MMISSILE))
    {
        int blood = hurted/2; // assumes DVORP_PIERCING, factor: 0.5
        if (blood > you.hp)
            blood = you.hp;

        bleed_onto_floor(you.x_pos, you.y_pos, -1, blood, true);
    }

    hurted = check_your_resists( hurted, beam.flavour );

    if (beam.flavour == BEAM_MIASMA && hurted > 0)
    {
        if (player_res_poison() <= 0)
        {
            poison_player(1);
            was_affected = true;
        }

        if (one_chance_in( 3 + 2 * player_prot_life() ))
        {
            potion_effect( POT_SLOWING, 5 );
            was_affected = true;
        }
    }

    // handling of missiles
    if (item && item->base_type == OBJ_MISSILES)
    {
        if (item->sub_type == MI_THROWING_NET)
        {
            player_caught_in_net();
            was_affected = true;
        }
        else if (item->special == SPMSL_POISONED)
        {
            if (!player_res_poison()
                && (hurted || beam.ench_power == AUTOMATIC_HIT
                              && x_chance_in_y(90 - 3 * player_AC(), 100)))
            {
                poison_player( 1 + random2(3) );
                was_affected = true;
            }
        }
        else if (item->special == SPMSL_CURARE)
        {
            if (x_chance_in_y(90 - 3 * player_AC(), 100))
            {
                curare_hits_player( _beam_ouch_agent(beam), 1 + random2(3) );
                was_affected = true;
            }
        }
    }

    // Sticky flame.
    if (beam.name == "sticky flame"
        && (you.species != SP_MOTTLED_DRACONIAN
            || you.experience_level < 6))
    {
        if (!player_equip( EQ_BODY_ARMOUR, ARM_MOTTLED_DRAGON_ARMOUR ))
        {
            you.duration[DUR_LIQUID_FLAMES] += random2avg(7, 3) + 1;
            was_affected = true;
        }
    }

    // Simple cases for scroll burns.
    if (beam.flavour == BEAM_LAVA || beam.name == "hellfire")
        expose_player_to_element(BEAM_LAVA, burn_power);

    // More complex (geez..)
    if (beam.flavour == BEAM_FIRE && beam.name != "ball of steam")
        expose_player_to_element(BEAM_FIRE, burn_power);

    // Potions exploding.
    if (beam.flavour == BEAM_COLD)
        expose_player_to_element(BEAM_COLD, burn_power);

    if (beam.flavour == BEAM_ACID)
        splash_with_acid(5);

    // Spore pops.
    if (beam.in_explosion_phase && beam.flavour == BEAM_SPORE)
        expose_player_to_element(BEAM_SPORE, burn_power);

#if DEBUG_DIAGNOSTICS
    mprf(MSGCH_DIAGNOSTICS, "Damage: %d", hurted );
#endif

    if (hurted > 0 || old_hp < you.hp || was_affected)
    {
        if (mons_att_wont_attack(beam.attitude))
        {
            beam.fr_hurt++;

            // Beam from player rebounded and hit player.
            // Xom's amusement at the player's being damaged is handled
            // elsewhere.
            if (beam.beam_source == NON_MONSTER)
            {
                if (!beam.aimed_at_feet)
                    xom_is_stimulated(255);
            }
            else if (was_affected)
                xom_is_stimulated(128);
        }
        else
            beam.foe_hurt++;
    }

    _beam_ouch( hurted, beam );

    return (_range_used_on_hit( beam ));
}

static int _beam_source(const bolt &beam)
{
    return (MON_KILL(beam.thrower)     ? beam.beam_source :
            beam.thrower == KILL_MISC  ? MHITNOT
                                       : MHITYOU);
}

static int _name_to_skill_level(const std::string& name)
{
    skill_type type = SK_THROWING;

    if (name.find("dart") != std::string::npos)
        type = SK_DARTS;
    else if (name.find("needle") != std::string::npos)
        type = SK_DARTS;
    else if (name.find("bolt") != std::string::npos)
        type = SK_CROSSBOWS;
    else if (name.find("arrow") != std::string::npos)
        type = SK_BOWS;
    else if (name.find("stone") != std::string::npos)
        type = SK_SLINGS;

    if (type == SK_DARTS || type == SK_SLINGS)
        return (you.skills[type] + you.skills[SK_THROWING]);

    return (2 * you.skills[type]);
}

static void _update_hurt_or_helped(bolt &beam, monsters *mon)
{
    if (!mons_atts_aligned(beam.attitude, mons_attitude(mon)))
    {
        if (_nasty_beam(mon, beam))
            beam.foe_hurt++;
        else if (_nice_beam(mon, beam))
            beam.foe_helped++;
    }
    else
    {
        if (_nasty_beam(mon, beam))
        {
            beam.fr_hurt++;

            // Harmful beam from this monster rebounded and hit the monster.
            int midx = monster_index(mon);
            if (midx == beam.beam_source)
                xom_is_stimulated(128);
        }
        else if (_nice_beam(mon, beam))
            beam.fr_helped++;
    }
}

// Returns amount of range used up by affectation of this monster.
static int _affect_monster(bolt &beam, monsters *mon, item_def *item)
{
    const int tid = mgrd[mon->x][mon->y];
    const int mons_type  = menv[tid].type;
    const int thrower    = YOU_KILL(beam.thrower) ? KILL_YOU_MISSILE
                                                  : KILL_MON_MISSILE;
    const bool submerged = mon->submerged();

    int hurt;
    int hurt_final;

    // digging -- don't care.
    if (beam.flavour == BEAM_DIGGING)
        return (0);

    // fire storm creates these, so we'll avoid affecting them
    if (beam.name == "great blast of fire"
        && mon->type == MONS_FIRE_VORTEX)
    {
        return (0);
    }

    // check for tracer
    if (beam.is_tracer)
    {
        // Can we see this monster?
        if (!beam.can_see_invis && menv[tid].invisible()
            || thrower == KILL_YOU_MISSILE && !see_grid(mon->x, mon->y))
        {
            // Can't see this monster, ignore it.
            return 0;
        }

        if (!mons_atts_aligned(beam.attitude, mons_attitude(mon)))
        {
            beam.foe_count += 1;
            beam.foe_power += mons_power(mons_type);
        }
        else
        {
            beam.fr_count += 1;
            beam.fr_power += mons_power(mons_type);
        }
    }
    else if ((beam.flavour == BEAM_DISINTEGRATION || beam.flavour == BEAM_NUKE)
             && mons_is_statue(mons_type) && !mons_is_icy(mons_type))
    {
        if (!silenced(you.x_pos, you.y_pos))
        {
            if (!see_grid( mon->x, mon->y ))
                mpr("You hear a hideous screaming!", MSGCH_SOUND);
            else
            {
                mpr("The statue screams as its substance crumbles away!",
                    MSGCH_SOUND);
            }
        }
        else if (see_grid( mon->x, mon->y ))
        {
                mpr("The statue twists and shakes as its substance "
                    "crumbles away!");
        }
        beam.obvious_effect = true;
        _update_hurt_or_helped(beam, mon);
        mon->hit_points = 0;
        _monster_die(mon, beam);
        return (BEAM_STOP);
    }

    bool hit_woke_orc = false;
    if (beam.name[0] == '0') // enchantments
    {
        if (beam.is_tracer)
        {
            if (beam.thrower == KILL_YOU_MISSILE || beam.thrower == KILL_YOU)
            {
                if (!_beam_is_harmless(beam, mon)
                    && (beam.fr_count == 1 && !beam.dont_stop_fr
                        || beam.foe_count == 1 && !beam.dont_stop_foe))
                {
                    const bool target = (beam.target_x == mon->x
                                         && beam.target_y == mon->y);

                    if (stop_attack_prompt(mon, true, target))
                    {
                        beam.beam_cancelled = true;
                        return (BEAM_STOP);
                    }
                    if (beam.fr_count == 1 && !beam.dont_stop_fr)
                        beam.dont_stop_fr = true;
                    else
                        beam.dont_stop_foe = true;
                }
            }

            return (_range_used_on_hit(beam));
        }

        // BEGIN non-tracer enchantment

        // Submerged monsters are unaffected by enchantments.
        if (submerged)
            return (0);

        god_conduct_trigger conducts[3];
        disable_attack_conducts(conducts);

        // Nasty enchantments will annoy the monster, and are considered
        // naughty (even if a monster might resist).
        if (_nasty_beam(mon, beam))
        {
            if (YOU_KILL(beam.thrower))
            {
                if (is_sanctuary(mon->x, mon->y)
                    || is_sanctuary(you.x_pos, you.y_pos))
                {
                    remove_sanctuary(true);
                }

                set_attack_conducts(conducts, mon, player_monster_visible(mon));

                if (you.religion == GOD_BEOGH
                    && mons_species(mon->type) == MONS_ORC
                    && mons_is_sleeping(mon) && !player_under_penance()
                    && you.piety >= piety_breakpoint(2) && mons_near(mon))
                {
                    hit_woke_orc = true;
                }
            }

            behaviour_event(mon, ME_ANNOY, _beam_source(beam));
        }
        else
            behaviour_event(mon, ME_ALERT, _beam_source(beam));

        enable_attack_conducts(conducts);

        // !@#*( affect_monster_enchantment() has side-effects on
        // the beam structure which screw up range_used_on_hit(),
        // so call it now and store.
        int rangeUsed = _range_used_on_hit(beam);

        // Doing this here so that the player gets to see monsters
        // "flicker and vanish" when turning invisible....
        _ench_animation( beam.flavour, mon );

        // now do enchantment affect
        int ench_result = _affect_monster_enchantment(beam, mon);
        if (mon->alive())
        {
            switch (ench_result)
            {
            case MON_RESIST:
                if (simple_monster_message(mon, " resists."))
                    beam.msg_generated = true;
                break;
            case MON_UNAFFECTED:
                if (simple_monster_message(mon, " is unaffected."))
                    beam.msg_generated = true;
                break;
            default:
                _update_hurt_or_helped(beam, mon);
                break;
            }
        }
        if (hit_woke_orc)
            beogh_follower_convert(mon, true);

        return (rangeUsed);

        // END non-tracer enchantment
    }


    // BEGIN non-enchantment (could still be tracer)
    if (submerged && !beam.aimed_at_spot)
        return (0);                   // missed me!

    // We need to know how much the monster _would_ be hurt by this, before
    // we decide if it actually hits.

    // Roll the damage:
    hurt = roll_dice( beam.damage );

    // Water absorbs some of the damage for submerged monsters.
    if (submerged)
    {
        // Can't hurt submerged water creatures with electricity.
        if (beam.flavour == BEAM_ELECTRICITY)
        {
            if (see_grid(mon->x, mon->y) && !beam.is_tracer)
            {
                mprf("The %s arcs harmlessly into the water.",
                     beam.name.c_str());
            }
            return (BEAM_STOP);
        }

        hurt = hurt * 2 / 3;
    }

    hurt_final = hurt;

    if (beam.is_tracer)
        hurt_final -= std::max(mon->ac / 2, 0);
    else
        hurt_final -= random2(1 + mon->ac);

    if (beam.flavour == BEAM_FRAG)
    {
        hurt_final -= random2(1 + mon->ac);
        hurt_final -= random2(1 + mon->ac);
    }

    if (hurt_final < 0)
        hurt_final = 0;

    const int raw_damage = hurt_final;

    // Check monster resists, _without_ side effects (since the
    // beam/missile might yet miss!)
    hurt_final = mons_adjust_flavoured( mon, beam, raw_damage, false );

#if DEBUG_DIAGNOSTICS
    if (!beam.is_tracer)
    {
        mprf(MSGCH_DIAGNOSTICS,
             "Monster: %s; Damage: pre-AC: %d; post-AC: %d; post-resist: %d",
             mon->name(DESC_PLAIN).c_str(), hurt, raw_damage, hurt_final);
    }
#endif

    // Now, we know how much this monster would (probably) be
    // hurt by this beam.
    if (beam.is_tracer)
    {
        if (beam.thrower == KILL_YOU_MISSILE || beam.thrower == KILL_YOU)
        {
            if (!_beam_is_harmless(beam, mon)
                && (beam.fr_count == 1 && !beam.dont_stop_fr
                    || beam.foe_count == 1 && !beam.dont_stop_foe))
            {
                const bool target = (beam.target_x == mon->x
                                     && beam.target_y == mon->y);

                if (stop_attack_prompt(mon, true, target))
                {
                    beam.beam_cancelled = true;
                    return (BEAM_STOP);
                }
                if (beam.fr_count == 1 && !beam.dont_stop_fr)
                    beam.dont_stop_fr = true;
                else
                    beam.dont_stop_foe = true;
            }
        }

        // Check only if actual damage.
        if (hurt_final > 0 && hurt > 0)
        {
            // Monster could be hurt somewhat, but only apply the
            // monster's power based on how badly it is affected.
            // For example, if a fire giant (power 16) threw a
            // fireball at another fire giant, and it only took
            // 1/3 damage, then power of 5 would be applied to
            // foe_power or fr_power.
            if (!mons_atts_aligned(beam.attitude, mons_attitude(mon)))
            {
                // Counting foes is only important for monster tracers.
                beam.foe_power += 2 * hurt_final * mons_power(mons_type)
                                                    / hurt;
            }
            else
            {
                beam.fr_power += 2 * hurt_final * mons_power(mons_type)
                                                   / hurt;
            }
        }

        // Either way, we could hit this monster, so return range used.
        return (_range_used_on_hit(beam));
    }
    // END non-enchantment (could still be tracer)

    // BEGIN real non-enchantment beam

    // Player beams which hit friendlies or good neutrals will annoy
    // them and be considered naughty if they do damage (this is so as
    // not to penalize players that fling fireballs into a melee with
    // fire elementals on their side - the elementals won't give a sh*t,
    // after all).

    god_conduct_trigger conducts[3];
    disable_attack_conducts(conducts);

    if (_nasty_beam(mon, beam))
    {
        if (YOU_KILL(beam.thrower) && hurt_final > 0)
        {
            // It's not the player's fault if he didn't see the monster or
            // the monster was caught in an unexpected blast of ?immolation.
            const bool okay =
                (!player_monster_visible(mon)
                 || beam.aux_source == "reading a scroll of immolation"
                    && !beam.effect_known);

            if (is_sanctuary(mon->x, mon->y)
                || is_sanctuary(you.x_pos, you.y_pos))
            {
                remove_sanctuary(true);
            }

            set_attack_conducts(conducts, mon, !okay);
        }

        if (you.religion == GOD_BEOGH && mons_species(mon->type) == MONS_ORC
            && mons_is_sleeping(mon) && YOU_KILL(beam.thrower)
            && !player_under_penance() && you.piety >= piety_breakpoint(2)
            && mons_near(mon))
        {
            hit_woke_orc = true;
        }
    }

    // Explosions always 'hit'.
    const bool engulfs = (beam.is_explosion || beam.is_big_cloud);

    int beam_hit = beam.hit;
    if (menv[tid].invisible() && !beam.can_see_invis)
        beam_hit /= 2;

    // FIXME We're randomising mon->evasion, which is further
    // randomised inside test_beam_hit. This is so we stay close to the 4.0
    // to-hit system (which had very little love for monsters).
    if (!engulfs && !test_beam_hit(beam_hit, random2(mon->ev)))
    {
        // If the PLAYER cannot see the monster, don't tell them anything!
        if (player_monster_visible( &menv[tid] ) && mons_near(mon))
        {
            msg::stream << "The " << beam.name << " misses "
                        << mon->name(DESC_NOCAP_THE) << '.' << std::endl;
        }
        return (0);
    }

    // The monster may block the beam.
    if (!engulfs && _beam_is_blockable(beam))
    {
        const int shield_block = mon->shield_bonus();
        if (shield_block > 0)
        {
            const int hit = random2( beam.hit * 130 / 100
                                     + mon->shield_block_penalty() );
            if (hit < shield_block && mons_near(mon)
                && player_monster_visible(mon))
            {
                mprf("%s blocks the %s.",
                     mon->name(DESC_CAP_THE).c_str(),
                     beam.name.c_str());

                mon->shield_block_succeeded();
                return (BEAM_STOP);
            }
        }
    }

    _update_hurt_or_helped(beam, mon);

    enable_attack_conducts(conducts);

    // The beam hit.
    if (mons_near(mon))
    {
        mprf("The %s %s %s.",
             beam.name.c_str(),
             engulfs? "engulfs" : "hits",
             player_monster_visible(&menv[tid]) ?
                 mon->name(DESC_NOCAP_THE).c_str() : "something");

    }
    else
    {
        // The player might hear something, if _they_ fired a missile
        // (not beam).
        if (!silenced(you.x_pos, you.y_pos) && beam.flavour == BEAM_MISSILE
            && YOU_KILL(beam.thrower))
        {
            mprf(MSGCH_SOUND, "The %s hits something.", beam.name.c_str());
        }
    }

    // handling of missiles
    if (item && item->base_type == OBJ_MISSILES
        && item->sub_type == MI_THROWING_NET)
    {
        monster_caught_in_net(mon, beam);
    }

    // Note that hurt_final was calculated above, so we don't need it again.
    // just need to apply flavoured specials (since we called with
    // doFlavouredEffects = false above).
    hurt_final = mons_adjust_flavoured(mon, beam, raw_damage);

    // If the beam is an actual missile or of the MMISSILE type (Earth magic)
    // we might bleed on the floor.
    if (!engulfs
        && (beam.flavour == BEAM_MISSILE || beam.flavour == BEAM_MMISSILE)
        && !mons_is_summoned(mon) && !mons_is_submerged(mon))
    {
        // Using raw_damage instead of the flavoured one!
        int blood = raw_damage/2; // assumes DVORP_PIERCING, factor: 0.5
        if (blood > mon->hit_points)
            blood = mon->hit_points;

        bleed_onto_floor(mon->x, mon->y, mon->type, blood, true);
    }

    // Now hurt monster.
    hurt_monster( mon, hurt_final );

    if (mon->hit_points < 1)
    {
        _monster_die(mon, beam);
    }
    else
    {
        if (thrower == KILL_YOU_MISSILE && mons_near(mon))
        {
            const monsters *mons = static_cast<const monsters*>(mon);
            print_wounds(mons);
        }

        // Don't annoy friendlies or good neutrals if the player's beam
        // did no damage.  Hostiles will still take umbrage.
        if (hurt_final > 0 || !mons_wont_attack(mon) || !YOU_KILL(beam.thrower))
            behaviour_event(mon, ME_ANNOY, _beam_source(beam));

        // sticky flame
        if (beam.name == "sticky flame")
        {
            int levels = 1 + random2( hurt_final ) / 2;
            if (levels > 4)
                levels = 4;

            _sticky_flame_monster( tid, _whose_kill(beam), levels );
        }

        // Handle missile effects.
        if (item)
        {
            if (item->base_type == OBJ_MISSILES
                && item->special == SPMSL_POISONED)
            {
                int num_levels = 0;
                // ench_power == AUTOMATIC_HIT if this is a poisoned needle.
                if (beam.ench_power == AUTOMATIC_HIT
                    && x_chance_in_y(90 - 3 * mon->ac, 100))
                {
                    num_levels = 2;
                }
                else if (random2(hurt_final) - random2(mon->ac) > 0)
                    num_levels = 1;

                int num_success = 0;
                if (YOU_KILL(beam.thrower))
                {
                    const int skill_level = _name_to_skill_level(beam.name);
                    if (x_chance_in_y(skill_level + 25, 50))
                        num_success++;
                    if (x_chance_in_y(skill_level, 50))
                        num_success++;
                }
                else
                    num_success = 1;

                if (num_success)
                {
                    if (num_success == 2)
                        num_levels++;
                    poison_monster( mon, _whose_kill(beam), num_levels );
                }
            }
        }

        bool wake_mimic = true;
        if (item && item->base_type == OBJ_MISSILES
            && item->special == SPMSL_CURARE)
        {
            if (beam.ench_power == AUTOMATIC_HIT
                && curare_hits_monster( beam, mon, _whose_kill(beam), 2 ))
            {
                wake_mimic = false;
            }
        }

        if (wake_mimic && mons_is_mimic( mon->type ))
            mimic_alert(mon);
        else if (hit_woke_orc)
            beogh_follower_convert(mon, true);
    }

    return (_range_used_on_hit(beam));
}

static int _affect_monster_enchantment(bolt &beam, monsters *mon)
{
    bool death_check = false;

    if (beam.flavour == BEAM_TELEPORT) // teleportation
    {
        if (check_mons_resist_magic( mon, beam.ench_power )
            && !beam.aimed_at_feet)
        {
            return (mons_immune_magic(mon) ? MON_UNAFFECTED : MON_RESIST);
        }

        if (mons_near(mon) && player_monster_visible(mon))
            beam.obvious_effect = true;

        monster_teleport(mon, false);

        return (MON_AFFECTED);
    }

    if (beam.flavour == BEAM_BLINK)
    {
        if (!beam.aimed_at_feet
            && check_mons_resist_magic( mon, beam.ench_power ))
        {
            return mons_immune_magic(mon) ? MON_UNAFFECTED : MON_RESIST;
        }

        if (mons_near( mon ) && player_monster_visible( mon ))
            beam.obvious_effect = true;

        monster_blink( mon );
        return (MON_AFFECTED);
    }

    if (beam.flavour == BEAM_POLYMORPH)
    {
        if (!mon->can_mutate())
            return (MON_UNAFFECTED);

        if (check_mons_resist_magic( mon, beam.ench_power ))
            return (mons_immune_magic(mon) ? MON_UNAFFECTED : MON_RESIST);

        if (mon->mutate())
            beam.obvious_effect = true;

        if (YOU_KILL(beam.thrower))
        {
            did_god_conduct(DID_DELIBERATE_MUTATING, 2 + random2(3),
                            beam.effect_known);
        }

        return (MON_AFFECTED);
    }

    if (beam.flavour == BEAM_BANISH)
    {
        if (check_mons_resist_magic( mon, beam.ench_power ))
            return mons_immune_magic(mon) ? MON_UNAFFECTED : MON_RESIST;

        if (you.level_type == LEVEL_ABYSS)
            simple_monster_message(mon, " wobbles for a moment.");
        else
            _monster_die(mon, beam);

        beam.obvious_effect = true;
        return (MON_AFFECTED);
    }

    if (beam.flavour == BEAM_DEGENERATE)
    {
        if (mons_holiness(mon) != MH_NATURAL
            || mon->type == MONS_PULSATING_LUMP)
        {
            return (MON_UNAFFECTED);
        }

        if (check_mons_resist_magic( mon, beam.ench_power ))
            return mons_immune_magic(mon) ? MON_UNAFFECTED : MON_RESIST;

        if (monster_polymorph(mon, MONS_PULSATING_LUMP))
            beam.obvious_effect = true;

        return (MON_AFFECTED);
    }

    if (beam.flavour == BEAM_DISPEL_UNDEAD)
    {
        if (mons_holiness(mon) != MH_UNDEAD)
            return (MON_UNAFFECTED);

        if (simple_monster_message(mon, " convulses!"))
            beam.obvious_effect = true;

        hurt_monster( mon, roll_dice( beam.damage ) );

        death_check = true;
    }

    if (beam.flavour == BEAM_ENSLAVE_UNDEAD
        && mons_holiness(mon) == MH_UNDEAD)
    {
#if DEBUG_DIAGNOSTICS
        mprf(MSGCH_DIAGNOSTICS,
             "HD: %d; pow: %d", mon->hit_dice, beam.ench_power );
#endif

        if (mon->attitude == ATT_FRIENDLY)
            return (MON_UNAFFECTED);

        if (check_mons_resist_magic( mon, beam.ench_power ))
            return mons_immune_magic(mon) ? MON_UNAFFECTED : MON_RESIST;

        beam.obvious_effect = true;
        if (player_will_anger_monster(mon))
        {
            simple_monster_message(mon, " is repulsed!");
            return (MON_OTHER);
        }

        simple_monster_message(mon, " is enslaved.");

        // Wow, permanent enslaving!
        mon->attitude = ATT_FRIENDLY;
        return (MON_AFFECTED);
    }

    if (beam.flavour == BEAM_ENSLAVE_DEMON
        && mons_holiness(mon) == MH_DEMONIC)
    {
#if DEBUG_DIAGNOSTICS
        mprf(MSGCH_DIAGNOSTICS,
             "HD: %d; pow: %d", mon->hit_dice, beam.ench_power );
#endif

        if (mon->hit_dice * 11 / 2 >= random2(beam.ench_power)
            || mons_is_unique(mon->type))
        {
            return (MON_RESIST);
        }

        // Already friendly.
        if (mons_friendly(mon))
            return (MON_UNAFFECTED);

        beam.obvious_effect = true;
        if (player_will_anger_monster(mon))
        {
            simple_monster_message(mon, " is repulsed!");
            return (MON_OTHER);
        }

        simple_monster_message(mon, " is enslaved.");

        // Wow, permanent enslaving!
        if (one_chance_in(2 + mon->hit_dice / 4))
            mon->attitude = ATT_FRIENDLY;
        else
            mon->add_ench(ENCH_CHARM);

        // Break fleeing and suchlike.
        mon->behaviour = BEH_SEEK;
        return (MON_AFFECTED);
    }

    //
    // Everything past this point must pass this magic resistance test.
    //
    // Using check_mons_resist_magic here since things like disintegrate
    // are beyond this point. -- bwr

    // death_check should be set true already if one of the beams above
    // did its thing but wants to see if the monster died.
    //
    if (!death_check
        && check_mons_resist_magic( mon, beam.ench_power )
        && beam.flavour != BEAM_HASTE
        && beam.flavour != BEAM_HEALING
        && beam.flavour != BEAM_INVISIBILITY)
    {
        return mons_immune_magic(mon) ? MON_UNAFFECTED : MON_RESIST;
    }

    if (beam.flavour == BEAM_PAIN)      /* pain/agony */
    {
        if (mons_res_negative_energy( mon ))
            return (MON_UNAFFECTED);

        if (simple_monster_message(mon, " convulses in agony!"))
            beam.obvious_effect = true;

        if (beam.name.find("agony") != std::string::npos)
        {
            // AGONY
            mon->hit_points = mon->hit_points / 2;

            if (mon->hit_points < 1)
                mon->hit_points = 1;
        }
        else
        {
            // PAIN
            hurt_monster( mon, roll_dice( beam.damage ) );
        }

        death_check = true;
    }

    if (beam.flavour == BEAM_DISINTEGRATION)     /* disrupt/disintegrate */
    {
        if (simple_monster_message(mon, " is blasted."))
            beam.obvious_effect = true;

        hurt_monster( mon, roll_dice( beam.damage ) );

        death_check = true;
    }


    if (beam.flavour == BEAM_SLEEP)
    {
        if (mon->has_ench(ENCH_SLEEP_WARY))  // slept recently
            return (MON_RESIST);

        if (mons_holiness(mon) != MH_NATURAL) // no unnatural
            return (MON_UNAFFECTED);

        // Cold res monsters resist hibernation (for consistency
        // with mass sleep).
        if (mons_res_cold(mon) > 0)
            return (MON_UNAFFECTED);

        if (simple_monster_message(mon, " looks drowsy..."))
            beam.obvious_effect = true;

        mon->put_to_sleep();

        return (MON_AFFECTED);
    }

    if (beam.flavour == BEAM_BACKLIGHT)
    {
        if (backlight_monsters(mon->x, mon->y, beam.hit, 0))
        {
            beam.obvious_effect = true;
            return (MON_AFFECTED);
        }
        return (MON_UNAFFECTED);
    }

    // Everything else?
    if (!death_check)
        return (mons_ench_f2(mon, beam));

    if (mon->hit_points < 1)
        _monster_die(mon, beam);
    else
    {
        print_wounds(mon);

        if (mons_is_mimic( mon->type ))
            mimic_alert(mon);
    }

    return (MON_AFFECTED);
}


// Extra range used on hit.
static int _range_used_on_hit(bolt &beam)
{
    // Non-beams can only affect one thing (player/monster).
    if (!beam.is_beam)
        return (BEAM_STOP);

    // CHECK ENCHANTMENTS
    if (beam.name[0] == '0')
    {
        switch(beam.flavour)
        {
        case BEAM_SLOW:
        case BEAM_HASTE:
        case BEAM_HEALING:
        case BEAM_PARALYSIS:
        case BEAM_PETRIFY:
        case BEAM_CONFUSION:
        case BEAM_INVISIBILITY:
        case BEAM_TELEPORT:
        case BEAM_POLYMORPH:
        case BEAM_CHARM:
        case BEAM_BANISH:
        case BEAM_PAIN:
        case BEAM_DISINTEGRATION:
        case BEAM_DEGENERATE:
        case BEAM_DISPEL_UNDEAD:
        case BEAM_ENSLAVE_UNDEAD:
        case BEAM_ENSLAVE_DEMON:
        case BEAM_SLEEP:
        case BEAM_BACKLIGHT:
            return (BEAM_STOP);
        default:
            break;
        }

        return (0);
    }

    // Hellfire stops for nobody!
    if (beam.name == "hellfire")
        return (0);

    // Generic explosion.
    if (beam.is_explosion || beam.is_big_cloud)
        return (BEAM_STOP);

    // Plant spit.
    if (beam.flavour == BEAM_ACID)
        return (BEAM_STOP);

    // Lava doesn't go far, but it goes through most stuff.
    if (beam.flavour == BEAM_LAVA)
        return (1);

    // If it isn't lightning, reduce range by a lot.
    if (beam.flavour != BEAM_ELECTRICITY)
        return (random2(4) + 2);

    return (0);
}

// Takes a bolt and refines it for use in the explosion function. Called
// from missile() and beam() in beam.cc. Explosions which do not follow from
// beams (eg scrolls of immolation) bypass this function.
static void _explosion1(bolt &pbolt)
{
    int ex_size = 1;
    // convenience
    int x = pbolt.target_x;
    int y = pbolt.target_y;
    const char *seeMsg  = NULL;
    const char *hearMsg = NULL;

    // Assume that the player can see/hear the explosion, or
    // gets burned by it anyway.  :)
    pbolt.msg_generated = true;

    if (pbolt.name == "hellfire")
    {
        seeMsg  = "The hellfire explodes!";
        hearMsg = "You hear a strangely unpleasant explosion.";

        pbolt.type    = dchar_glyph(DCHAR_FIRED_BURST);
        pbolt.flavour = BEAM_HELLFIRE;
    }

    if (pbolt.name == "golden flame")
    {
        seeMsg  = "The flame explodes!";
        hearMsg = "You feel a deep, resonant explosion.";

        pbolt.type    = dchar_glyph(DCHAR_FIRED_BURST);
        pbolt.flavour = BEAM_HOLY;
        ex_size = 2;
    }

    if (pbolt.name == "fireball")
    {
        seeMsg  = "The fireball explodes!";
        hearMsg = "You hear an explosion.";

        pbolt.type    = dchar_glyph(DCHAR_FIRED_BURST);
        pbolt.flavour = BEAM_FIRE;
        ex_size = 1;
    }

    if (pbolt.name == "orb of electricity")
    {
        seeMsg  = "The orb of electricity explodes!";
        hearMsg = "You hear a clap of thunder!";

        pbolt.type       = dchar_glyph(DCHAR_FIRED_BURST);
        pbolt.flavour    = BEAM_ELECTRICITY;
        pbolt.colour     = LIGHTCYAN;
        pbolt.damage.num = 1;
        ex_size          = 2;
    }

    if (pbolt.name == "orb of energy")
    {
        seeMsg  = "The orb of energy explodes.";
        hearMsg = "You hear an explosion.";
    }

    if (pbolt.name == "metal orb")
    {
        seeMsg  = "The orb explodes into a blast of deadly shrapnel!";
        hearMsg = "You hear an explosion!";

        pbolt.name    = "blast of shrapnel";
        pbolt.type    = dchar_glyph(DCHAR_FIRED_ZAP);
        pbolt.flavour = BEAM_FRAG;     // Sets it from pure damage to shrapnel
                                       // (which is absorbed extra by armour).
    }

    if (pbolt.name == "great blast of cold")
    {
        seeMsg  = "The blast explodes into a great storm of ice!";
        hearMsg = "You hear a raging storm!";

        pbolt.name       = "ice storm";
        pbolt.damage.num = 6;
        pbolt.type       = dchar_glyph(DCHAR_FIRED_ZAP);
        pbolt.colour     = WHITE;
        ex_size          = 2 + (random2( pbolt.ench_power ) > 75);
    }

    if (pbolt.name == "ball of vapour")
    {
        seeMsg     = "The ball expands into a vile cloud!";
        hearMsg    = "You hear a gentle \'poof\'.";
        if (!pbolt.is_tracer)
            pbolt.name = "stinking cloud";
    }

    if (pbolt.name == "potion")
    {
        seeMsg     = "The potion explodes!";
        hearMsg    = "You hear an explosion!";
        if (!pbolt.is_tracer)
            pbolt.name = "cloud";
    }

    if (seeMsg == NULL)
    {
        seeMsg  = "The beam explodes into a cloud of software bugs!";
        hearMsg = "You hear the sound of one hand clapping!";
    }


    if (!pbolt.is_tracer && *seeMsg && *hearMsg)
    {
        // Check for see/hear/no msg.
        if (see_grid(x,y) || x == you.x_pos && y == you.y_pos)
            mpr(seeMsg);
        else
        {
            if (silenced(x,y) || silenced(you.x_pos, you.y_pos))
                pbolt.msg_generated = false;
            else
                mpr(hearMsg, MSGCH_SOUND);
        }
    }

    pbolt.ex_size = ex_size;
    explosion( pbolt );
}                               // end explosion1()


#define MAX_EXPLOSION_RADIUS 9

// explosion() is considered to emanate from beam->target_x, target_y
// and has a radius equal to ex_size.  The explosion will respect
// boundaries like walls, but go through/around statues/idols/etc.
//
// For each cell affected by the explosion, affect() is called.
int explosion( bolt &beam, bool hole_in_the_middle,
               bool explode_in_wall, bool stop_at_statues,
               bool stop_at_walls, bool show_more)
{
    if (in_bounds(beam.source_x, beam.source_y)
        && !(beam.source_x == beam.target_x
             && beam.source_y == beam.target_y)
        && (!explode_in_wall || stop_at_statues || stop_at_walls))
    {
        ray_def ray;
        int     max_dist = grid_distance(beam.source_x, beam.source_y,
                                         beam.target_x, beam.target_y);

        ray.fullray_idx = -1; // to quiet valgrind
        find_ray( beam.source_x, beam.source_y, beam.target_x, beam.target_y,
                  true, ray, 0, true );

        // Can cast explosions out from statues or walls.
        if (ray.x() == beam.source_x && ray.y() == beam.source_y)
        {
            max_dist--;
            ray.advance(true);
        }

        int dist = 0;
        while (dist++ <= max_dist && !(ray.x()    == beam.target_x
                                       && ray.y() == beam.target_y))
        {
            if (grid_is_solid(ray.x(), ray.y()))
            {
                bool is_wall = grid_is_wall(grd[ray.x()][ray.y()]);
                if (!stop_at_statues && !is_wall)
                {
#if DEBUG_DIAGNOSTICS
                    mpr("Explosion beam passing over a statue or other "
                        "non-wall solid feature.", MSGCH_DIAGNOSTICS);
#endif
                    continue;
                }
                else if (!stop_at_walls && is_wall)
                {
#if DEBUG_DIAGNOSTICS
                    mpr("Explosion beam passing through a wall.",
                        MSGCH_DIAGNOSTICS);
#endif
                    continue;
                }

#if DEBUG_DIAGNOSTICS
                if (!is_wall && stop_at_statues)
                {
                    mpr("Explosion beam stopped by a statue or other "
                        "non-wall solid feature.", MSGCH_DIAGNOSTICS);
                }
                else if (is_wall && stop_at_walls)
                {
                    mpr("Explosion beam stopped by a by wall.",
                        MSGCH_DIAGNOSTICS);
                }
                else
                {
                    mpr("Explosion beam stopped by someting buggy.",
                        MSGCH_DIAGNOSTICS);
                }
#endif

                break;
            }
            ray.advance(true);
        } // while (dist++ <= max_dist)

        // Backup so we don't explode inside the wall.
        if (!explode_in_wall && grid_is_wall(grd[ray.x()][ray.y()]))
        {
#if DEBUG_DIAGNOSTICS
            int old_x = ray.x();
            int old_y = ray.y();
#endif
            ray.regress();
#if DEBUG_DIAGNOSTICS
            mprf(MSGCH_DIAGNOSTICS,
                 "Can't explode in a solid wall, backing up a step "
                 "along the beam path from (%d, %d) to (%d, %d)",
                 old_x, old_y, ray.x(), ray.y());
#endif
        }
        beam.target_x = ray.x();
        beam.target_y = ray.y();
    } // if (!explode_in_wall)

    int r = beam.ex_size;

    // Beam is now an explosion.
    beam.in_explosion_phase = true;

    if (is_sanctuary(beam.target_x, beam.target_y))
    {
        if (!beam.is_tracer && see_grid(beam.target()) && !beam.name.empty())
        {
            mprf(MSGCH_GOD, "By Zin's power, the %s is contained.",
                 beam.name.c_str());
        }
        return (-1);
    }

#if DEBUG_DIAGNOSTICS
    mprf(MSGCH_DIAGNOSTICS,
         "explosion at (%d, %d) : t=%d c=%d f=%d hit=%d dam=%dd%d",
         beam.target_x, beam.target_y,
         beam.type, beam.colour, beam.flavour,
         beam.hit, beam.damage.num, beam.damage.size );
#endif

    // For now, we don't support explosions greater than 9 radius.
    if (r > MAX_EXPLOSION_RADIUS)
        r = MAX_EXPLOSION_RADIUS;

    // make a noise
    noisy(10 + 5 * r, beam.target_x, beam.target_y);

    // set map to false
    explode_map.init(false);

    // Discover affected cells - recursion is your friend!
    // this is done to model an explosion's behaviour around
    // corners where a simple 'line of sight' isn't quite
    // enough.  This might be slow for really big explosions,
    // as the recursion runs approximately as R^2.
    _explosion_map(beam, 0, 0, 0, 0, r);

    // Go through affected cells, drawing effect and
    // calling affect() for each.  Now, we get a bit
    // fancy, drawing all radius 0 effects, then radius 1,
    // radius 2, etc.  It looks a bit better that way.

    // turn buffering off
#ifdef WIN32CONSOLE
    bool oldValue = true;
    if (!beam.is_tracer)
        oldValue = set_buffering(false);
#endif

    // --------------------- begin boom ---------------

    bool drawing = true;
    for (int i = 0; i < 2; i++)
    {
        // do center -- but only if its affected
        if (!hole_in_the_middle)
            _explosion_cell(beam, 0, 0, drawing);

        // do the rest of it
        for (int rad = 1; rad <= r; rad ++)
        {
            // do sides
            for (int ay = 1 - rad; ay <= rad - 1; ay += 1)
            {
                if (explode_map[-rad+9][ay+9])
                    _explosion_cell(beam, -rad, ay, drawing);

                if (explode_map[rad+9][ay+9])
                    _explosion_cell(beam, rad, ay, drawing);
            }

            // do top & bottom
            for (int ax = -rad; ax <= rad; ax += 1)
            {
                if (explode_map[ax+9][-rad+9])
                    _explosion_cell(beam, ax, -rad, drawing);

                if (explode_map[ax+9][rad+9])
                    _explosion_cell(beam, ax, rad, drawing);
            }

            // new-- delay after every 'ring' {gdl}
            // If we don't refresh curses we won't
            // guarantee that the explosion is visible.
            if (drawing)
                update_screen();
            // Only delay on real explosion.
            if (!beam.is_tracer && drawing)
                delay(50);
        }

        drawing = false;
    }

    int cells_seen = 0;
    for ( int i = -9; i <= 9; ++i )
        for ( int j = -9; j <= 9; ++j )
            if ( explode_map[i+9][j+9]
                 && see_grid(beam.target_x + i, beam.target_y + j) )
            {
                cells_seen++;
            }

    // ---------------- end boom --------------------------

#ifdef WIN32CONSOLE
    if (!beam.is_tracer)
        set_buffering(oldValue);
#endif

    // Duplicate old behaviour - pause after entire explosion
    // has been drawn.
    if (!beam.is_tracer && cells_seen > 0 && show_more)
        more();

    return (cells_seen);
}

static void _explosion_cell(bolt &beam, int x, int y, bool drawOnly)
{
    bool random_beam = false;
    int realx = beam.target_x + x;
    int realy = beam.target_y + y;

    if (!drawOnly)
    {
        // Random beams: randomize before affect().
        if (beam.flavour == BEAM_RANDOM)
        {
            random_beam  = true;
            beam.flavour = static_cast<beam_type>(
                               random_range(BEAM_FIRE, BEAM_ACID) );
        }

        affect(beam, realx, realy);

        if (random_beam)
            beam.flavour = BEAM_RANDOM;
    }

    // Early out for tracer.
    if (beam.is_tracer)
        return;

    if (drawOnly)
    {
        int drawx = grid2viewX(realx);
        int drawy = grid2viewY(realy);

        if (see_grid(realx, realy) || realx == you.x_pos && realy == you.y_pos)
        {
#ifdef USE_TILE
            if (in_los_bounds(drawx, drawy))
                TileDrawBolt(drawx-1, drawy-1, tileidx_bolt(beam));
#else
            // bounds check
            if (in_los_bounds(drawx, drawy))
            {
                cgotoxy(drawx, drawy, GOTO_DNGN);
                put_colour_ch(
                    beam.colour == BLACK ? random_colour() : beam.colour,
                    dchar_glyph( DCHAR_EXPLOSION ) );
            }
#endif
        }
    }
}

static void _explosion_map( bolt &beam, int x, int y,
                           int count, int dir, int r )
{
    // Check to see out of range.
    if (x*x + y*y > r*r + r)
        return;

    // Check count.
    if (count > 10*r)
        return;

    const coord_def loc(beam.target_x + x, beam.target_y + y);

    // Make sure we haven't run off the map.
    if (!map_bounds(loc))
        return;

    // Check sanctuary.
    if (is_sanctuary(loc.x, loc.y))
        return;

    // Check to see if we're blocked by something specifically, we're blocked
    // by WALLS.  Not statues, idols, etc.
    const int dngn_feat = grd(loc);

    // Special case: Explosion originates from rock/statue
    // (e.g. Lee's rapid deconstruction) - in this case, ignore
    // solid cells at the center of the explosion.
    if (dngn_feat <= DNGN_MAXWALL
        && (x != 0 || y != 0) && !_affects_wall(beam, dngn_feat))
    {
        return;
    }

    // Hmm, I think we're ok.
    explode_map[x+9][y+9] = true;

    // Now recurse in every direction except the one we came from.
    for (int i = 0; i < 4; i++)
    {
        if (i+1 != dir)
        {
            int cadd = 5;
            if (x * spreadx[i] < 0 || y * spready[i] < 0)
                cadd = 17;

            _explosion_map( beam, x + spreadx[i], y + spready[i],
                            count + cadd, opdir[i], r );
        }
    }
}

// Returns true if the beam is harmful (ignoring monster
// resists) -- mon is given for 'special' cases where,
// for example, "Heal" might actually hurt undead, or
// "Holy Word" being ignored by holy monsters, etc.
//
// Only enchantments should need the actual monster type
// to determine this; non-enchantments are pretty
// straightforward.
static bool _nasty_beam(monsters *mon, bolt &beam)
{
    // Take care of non-enchantments.
    if (beam.name[0] != '0')
        return (true);

    // Now for some non-hurtful enchantments.

    // No charming holy beings!
    if (beam.flavour == BEAM_CHARM)
        return (mons_is_holy(mon));

    // degeneration / sleep
    if (beam.flavour == BEAM_DEGENERATE || beam.flavour == BEAM_SLEEP)
        return (mons_holiness(mon) == MH_NATURAL);

    // dispel undead / control undead
    if (beam.flavour == BEAM_DISPEL_UNDEAD
        || beam.flavour == BEAM_ENSLAVE_UNDEAD)
    {
        return (mons_holiness(mon) == MH_UNDEAD);
    }

    // pain/agony
    if (beam.flavour == BEAM_PAIN)
        return (!mons_res_negative_energy(mon));

    // control demon
    if (beam.flavour == BEAM_ENSLAVE_DEMON)
        return (mons_holiness(mon) == MH_DEMONIC);

    // haste/healing/invisibility
    if (beam.flavour == BEAM_HASTE || beam.flavour == BEAM_HEALING
        || beam.flavour == BEAM_INVISIBILITY)
    {
        return (false);
     }

    // everything else is considered nasty by everyone
    return (true);
}

static bool _nice_beam(monsters *mon, bolt &beam)
{
    // haste/healing/invisibility
    if (beam.flavour == BEAM_HASTE || beam.flavour == BEAM_HEALING
        || beam.flavour == BEAM_INVISIBILITY)
    {
        return (true);
    }

    return (false);
}

////////////////////////////////////////////////////////////////////////////
// bolt

// A constructor for bolt to help guarantee that we start clean (this has
// caused way too many bugs).  Putting it here since there's no good place to
// put it, and it doesn't do anything other than initialize it's members.
//
// TODO: Eventually it'd be nice to have a proper factory for these things
// (extended from setup_mons_cast() and zapping() which act as limited ones).
bolt::bolt() : range(0), rangeMax(0), type('*'),
               colour(BLACK),
               flavour(BEAM_MAGIC), source_x(0), source_y(0), damage(0,0),
               ench_power(0), hit(0), target_x(0), target_y(0), pos(),
               thrower(KILL_MISC), ex_size(0), beam_source(MHITNOT), name(),
               is_beam(false), is_explosion(false), is_big_cloud(false),
               is_enchant(false), is_energy(false), is_launched(false),
               is_thrown(false), target_first(false), aimed_at_spot(false),
               aux_source(), affects_nothing(false), obvious_effect(false),
               effect_known(true), fr_count(0), foe_count(0), fr_power(0),
               foe_power(0), fr_hurt(0), foe_hurt(0), fr_helped(0),
               foe_helped(0), is_tracer(false), aimed_at_feet(false),
               msg_generated(false), in_explosion_phase(false),
               smart_monster(false), can_see_invis(false),
               attitude(ATT_HOSTILE), foe_ratio(0), chose_ray(false),
               beam_cancelled(false), dont_stop_foe(false),
               dont_stop_fr(false), dont_stop_player(false)
{
}

killer_type bolt::killer() const
{
    if (flavour == BEAM_BANISH)
        return (KILL_RESET);

    switch (thrower)
    {
    case KILL_YOU:
    case KILL_YOU_MISSILE:
        return (flavour == BEAM_PARALYSIS
                || flavour == BEAM_PETRIFY) ? KILL_YOU : KILL_YOU_MISSILE;

    case KILL_MON:
    case KILL_MON_MISSILE:
        return (KILL_MON_MISSILE);

    default:
        return (KILL_MON_MISSILE);
    }
}

void bolt::set_target(const dist &d)
{
    if (!d.isValid)
        return;

    target_x = d.tx;
    target_y = d.ty;

    chose_ray = d.choseRay;
    if (d.choseRay)
        ray = d.ray;

    if (d.isEndpoint)
        aimed_at_spot = true;
}

void bolt::setup_retrace()
{
    if (pos.x && pos.y)
    {
        target_x = pos.x;
        target_y = pos.y;
    }
    std::swap(source_x, target_x);
    std::swap(source_y, target_y);
    affects_nothing = true;
    aimed_at_spot   = true;
}
