From 8157424c6cf53a9f18e00a3dc0bd4feab48926ec Mon Sep 17 00:00:00 2001
From: Steve Melenchuk <smelenchuk@gmail.com>
Date: Wed, 5 Feb 2014 19:38:31 -0700
Subject: [PATCH] Glaciate (damage-falloff-with-distance version).

Level 9 Conjuration/Ice; generates a cone-shaped great blast of ice
around a specified target. The minimum range of the cone is 3, and the
maximum is LOS at maximum spellpower; damage within a minimum-range cone
is equivalent to Ice Storm, and falls off with the square of the distance
for larger cones.

Leaves freezing clouds over the affected area, like Ice Storm; they
dissipate really quickly over large areas, though.

Targets hit with Glaciate are flash-frozen; they are subject to slow
movement for three turns.

Targets killed with Glaciate have a 3/5 chance of becoming a block of
ice (similar to a pillar of salt).

Also contains a monster-castable version of the spell.

Possibly to be a considered a replacement for Ice Storm.

Large chunks of this either originate from a patch from Keanan Smith
(Siegurt), seen at https://crawl.develz.org/mantis/view.php?id=7760, or
from the following discussion on Tavern:
https://crawl.develz.org/tavern/viewtopic.php?f=8&t=9854
---
 crawl-ref/source/beam.cc       |  61 +++++++++++++++++++++--
 crawl-ref/source/enum.h        |   6 +++
 crawl-ref/source/mon-cast.cc   |  50 ++++++++++++++++++-
 crawl-ref/source/mon-data.h    |  12 +++++
 crawl-ref/source/mon-ench.cc   |   2 +
 crawl-ref/source/mon-info.cc   |   5 +-
 crawl-ref/source/mon-place.cc  |   4 +-
 crawl-ref/source/spl-cast.cc   |   5 ++
 crawl-ref/source/spl-damage.cc |  75 ++++++++++++++++++++++++++++
 crawl-ref/source/spl-damage.h  |   3 ++
 crawl-ref/source/spl-data.h    |  13 +++++
 crawl-ref/source/target.cc     | 108 +++++++++++++++++++++++++++++++++++++++++
 crawl-ref/source/target.h      |  13 +++++
 13 files changed, 349 insertions(+), 8 deletions(-)

diff --git a/crawl-ref/source/beam.cc b/crawl-ref/source/beam.cc
index 7e9767d..097776c 100644
--- a/crawl-ref/source/beam.cc
+++ b/crawl-ref/source/beam.cc
@@ -4094,10 +4094,13 @@ void bolt::affect_player()
     }
     else if (name == "explosive bolt")
         _explosive_bolt_explode(this, you.pos());
-    else if (name == "flash freeze")
+    else if (name == "flash freeze" || name == "great icy blast")
     {
         if (you.duration[DUR_FROZEN])
-            canned_msg(MSG_YOU_UNAFFECTED);
+        {
+            if (name == "flash freeze")
+                canned_msg(MSG_YOU_UNAFFECTED);
+        }
         else
         {
             mprf(MSGCH_WARN, "You are encased in ice.");
@@ -4484,6 +4487,42 @@ static bool _dazzle_monster(monster* mons, actor* act)
     return false;
 }
 
+static void _glaciate_freeze(monster* mon, killer_type englaciator,
+                             int beam_source)
+{
+    const coord_def where = mon->pos();
+    const monster_type pillar_type =
+        mons_is_zombified(mon) ? mons_zombie_base(mon)
+                               : mons_species(mon->type);
+    const int hd = mon->get_experience_level();
+
+    simple_monster_message(mon, " is frozen into a solid block of ice!");
+
+    // If the monster leaves a corpse when it dies, destroy the corpse.
+    int corpse = monster_die(mon, englaciator, beam_source);
+    if (corpse != -1)
+        destroy_item(corpse);
+
+    if (monster *pillar = create_monster(
+                        mgen_data(MONS_BLOCK_OF_ICE,
+                                  BEH_HOSTILE,
+                                  0,
+                                  0,
+                                  0,
+                                  where,
+                                  MHITNOT,
+                                  MG_FORCE_PLACE,
+                                  GOD_NO_GOD,
+                                  pillar_type),
+                                  false))
+    {
+        // Enemies with more HD leave longer-lasting blocks of ice.
+        int time_left = (random2(8) + hd) * BASELINE_DELAY;
+        mon_enchant temp_en(ENCH_SLOWLY_DYING, 1, 0, time_left);
+        pillar->update_ench(temp_en);
+    }
+}
+
 void bolt::monster_post_hit(monster* mon, int dmg)
 {
     if (YOU_KILL(thrower) && mons_near(mon))
@@ -4562,10 +4601,13 @@ void bolt::monster_post_hit(monster* mon, int dmg)
 
     if (name == "spray of energy")
         _dazzle_monster(mon, agent());
-    else if (name == "flash freeze")
+    else if (name == "flash freeze" || name == "great icy blast")
     {
         if (mon->has_ench(ENCH_FROZEN))
-            simple_monster_message(mon, " is unaffected.");
+        {
+            if (name == "flash freeze")
+                simple_monster_message(mon, " is unaffected.");
+        }
         else
         {
             simple_monster_message(mon, " is flash-frozen.");
@@ -5001,6 +5043,11 @@ void bolt::affect_monster(monster* mon)
         if (mon->mindex() == beam_source && source_name.empty())
             source_name = mon->name(DESC_A, true);
 
+        if (name == "great icy blast" && x_chance_in_y(3, 5))
+        {
+            // Includes monster_die as part of converting to block of ice.
+            _glaciate_freeze(mon, thrower, beam_source);
+        }
         // Prevent spore explosions killing plants from being registered
         // as a Fedhas misconduct.  Deaths can trigger the ally dying or
         // plant dying conducts, but spore explosions shouldn't count
@@ -5008,7 +5055,7 @@ void bolt::affect_monster(monster* mon)
         //
         // FIXME: Should be a better way of doing this.  For now, we are
         // just falsifying the death report... -cao
-        if (you_worship(GOD_FEDHAS) && flavour == BEAM_SPORE
+        else if (you_worship(GOD_FEDHAS) && flavour == BEAM_SPORE
             && fedhas_protects(mon))
         {
             if (mon->attitude == ATT_FRIENDLY)
@@ -5069,6 +5116,10 @@ bool bolt::ignores_monster(const monster* mon) const
     if (name == "great blast of fire" && mon->type == MONS_FIRE_VORTEX)
         return true;
 
+    // Don't blow up blocks of ice with the spell that creates them.
+    if (name == "great icy blast" && mon->type == MONS_BLOCK_OF_ICE)
+        return true;
+
     if (flavour == BEAM_WATER && mon->type == MONS_WATER_ELEMENTAL)
         return true;
 
diff --git a/crawl-ref/source/enum.h b/crawl-ref/source/enum.h
index 50554f7..bfc53b8 100644
--- a/crawl-ref/source/enum.h
+++ b/crawl-ref/source/enum.h
@@ -2776,6 +2776,9 @@ enum monster_type                      // menv[].type
     MONS_BALL_LIGHTNING,
     MONS_ORB_OF_DESTRUCTION,    // a projectile, not a real mon
     MONS_PILLAR_OF_SALT,
+#if TAG_MAJOR_VERSION > 34
+    MONS_BLOCK_OF_ICE,
+#endif
     MONS_HELL_LORD,             // genus
     MONS_MERGED_SLIME_CREATURE, // used only for recolouring
     MONS_SENSED,                // dummy monster for unspecified sensed mons
@@ -2931,6 +2934,8 @@ enum monster_type                      // menv[].type
     MONS_ASTERION,
     MONS_NATASHA,
     MONS_VASHNIA,
+
+    MONS_BLOCK_OF_ICE,
 #endif
 
     NUM_MONSTERS,               // used for polymorph
@@ -3849,6 +3854,7 @@ enum spell_type
     SPELL_REARRANGE_PIECES,
     SPELL_MAJOR_DESTRUCTION,
     SPELL_BLINK_ALLIES_AWAY,
+    SPELL_GLACIATE,
     NUM_SPELLS
 };
 
diff --git a/crawl-ref/source/mon-cast.cc b/crawl-ref/source/mon-cast.cc
index 69e2355..4966413 100644
--- a/crawl-ref/source/mon-cast.cc
+++ b/crawl-ref/source/mon-cast.cc
@@ -53,6 +53,7 @@
 #include "state.h"
 #include "stuff.h"
 #include "areas.h"
+#include "target.h"
 #include "teleport.h"
 #include "traps.h"
 #include "view.h"
@@ -1249,6 +1250,7 @@ bool setup_mons_cast(monster* mons, bolt &pbolt, spell_type spell_cast,
     case SPELL_REARRANGE_PIECES:
     case SPELL_BLINK_ALLIES_AWAY:
     case SPELL_SHROUD_OF_GOLUBRIA:
+    case SPELL_GLACIATE:
         return true;
     default:
         if (check_validity)
@@ -2586,6 +2588,33 @@ static void _cast_black_mark(monster* agent)
     }
 }
 
+static bool _glaciate_tracer(monster *caster, int pow, coord_def aim)
+{
+    targetter_cone hitfunc(caster, spell_range(SPELL_GLACIATE, pow));
+    hitfunc.set_aim(aim);
+
+    mon_attitude_type castatt = caster->temp_attitude();
+    int friendly = 0, enemy = 0;
+
+    for (map<coord_def, aff_type>::const_iterator p = hitfunc.zapped.begin();
+         p != hitfunc.zapped.end(); ++p)
+    {
+        if (p->second <= 0)
+            continue;
+
+        const actor *victim = actor_at(p->first);
+        if (!victim)
+            continue;
+
+        if (mons_atts_aligned(castatt, victim->temp_attitude()))
+            friendly += victim->get_experience_level();
+        else
+            enemy += victim->get_experience_level();
+    }
+
+    return enemy > friendly;
+}
+
 //---------------------------------------------------------------
 //
 // handle_mon_spell
@@ -3133,6 +3162,16 @@ bool handle_mon_spell(monster* mons, bolt &beem)
             if (!_should_ephemeral_infusion(mons))
                 return false;
         }
+        else if (spell_cast == SPELL_GLACIATE)
+        {
+            if (!foe
+                || !_glaciate_tracer(mons,
+                                     12 * mons->spell_hd(SPELL_GLACIATE),
+                                     foe->pos()))
+            {
+                return false;
+            }
+        }
 
         if (mons->type == MONS_BALL_LIGHTNING)
             mons->suicide();
@@ -5776,6 +5815,14 @@ void mons_cast(monster* mons, bolt &pbolt, spell_type spell_cast,
         mons->add_ench(mon_enchant(ENCH_SHROUD));
 
         return;
+
+    case SPELL_GLACIATE:
+    {
+        actor *foe = mons->get_foe();
+        ASSERT(foe);
+        cast_glaciate(mons, 12 * mons->spell_hd(SPELL_GLACIATE), foe->pos());
+        return;
+    }
     }
 
 
@@ -6226,7 +6273,8 @@ void mons_cast_noise(monster* mons, const bolt &pbolt,
                                || pbolt.visible())
                            // ugh. --Grunt
                            && (actual_spell != SPELL_LRD)
-                           && (actual_spell != SPELL_PORTAL_PROJECTILE);
+                           && (actual_spell != SPELL_PORTAL_PROJECTILE)
+                           && (actual_spell != SPELL_GLACIATE);
 
     vector<string> key_list;
     unsigned int num_spell_keys =
diff --git a/crawl-ref/source/mon-data.h b/crawl-ref/source/mon-data.h
index ff55cfb..0752052 100644
--- a/crawl-ref/source/mon-data.h
+++ b/crawl-ref/source/mon-data.h
@@ -5976,6 +5976,18 @@ static monsterentry mondata[] =
 },
 
 {
+    MONS_BLOCK_OF_ICE, '8', ETC_ICE, "block of ice",
+    M_NO_EXP_GAIN | M_STATIONARY,
+    mrd(MR_RES_COLD, 3) | MR_VUL_FIRE | MR_RES_POISON,
+    0, 10, MONS_BLOCK_OF_ICE, MONS_BLOCK_OF_ICE, MH_NONLIVING, MAG_IMMUNE,
+    { AT_NO_ATK, AT_NO_ATK, AT_NO_ATK, AT_NO_ATK },
+    { 3, 0, 5, 20 },
+    15, 0, MST_NO_SPELLS, CE_NOCORPSE, Z_NOZOMBIE, S_SILENT,
+    I_PLANT, HT_LAND, FL_NONE, 10, DEFAULT_ENERGY,
+    MONUSE_STARTING_EQUIPMENT, MONEAT_NOTHING, SIZE_MEDIUM
+},
+
+{
     MONS_DIAMOND_OBELISK, '8', WHITE, "diamond obelisk",
     M_ARTIFICIAL | M_STATIONARY | M_NO_EXP_GAIN | M_NO_POLY_TO,
     MR_NO_FLAGS,
diff --git a/crawl-ref/source/mon-ench.cc b/crawl-ref/source/mon-ench.cc
index d20b1a1..d01b602 100644
--- a/crawl-ref/source/mon-ench.cc
+++ b/crawl-ref/source/mon-ench.cc
@@ -1597,6 +1597,8 @@ void monster::apply_enchantment(const mon_enchant &me)
             {
                 if (type == MONS_PILLAR_OF_SALT)
                     mprf("%s crumbles away.", name(DESC_THE, false).c_str());
+                else if (type == MONS_BLOCK_OF_ICE)
+                    mprf("%s melts away.", name(DESC_THE, false).c_str());
                 else
                 {
                     mprf("A nearby %s withers and dies.",
diff --git a/crawl-ref/source/mon-info.cc b/crawl-ref/source/mon-info.cc
index 977b603..9d58d29 100644
--- a/crawl-ref/source/mon-info.cc
+++ b/crawl-ref/source/mon-info.cc
@@ -830,7 +830,7 @@ string monster_info::_core_name() const
     case MONS_SIMULACRUM_SMALL: case MONS_SIMULACRUM_LARGE:
 #endif
     case MONS_SPECTRAL_THING:   case MONS_PILLAR_OF_SALT:
-    case MONS_CHIMERA:
+    case MONS_BLOCK_OF_ICE:     case MONS_CHIMERA:
     case MONS_SENSED:
         nametype = base_type;
         break;
@@ -1033,6 +1033,9 @@ string monster_info::common_name(description_level_type desc) const
     case MONS_PILLAR_OF_SALT:
         ss << (nocore ? "" : " ") << "shaped pillar of salt";
         break;
+    case MONS_BLOCK_OF_ICE:
+        ss << (nocore ? "" : " ") << "shaped block of ice";
+        break;
     default:
         break;
     }
diff --git a/crawl-ref/source/mon-place.cc b/crawl-ref/source/mon-place.cc
index 9947ba6..d8897ce 100644
--- a/crawl-ref/source/mon-place.cc
+++ b/crawl-ref/source/mon-place.cc
@@ -1497,7 +1497,9 @@ static monster* _place_monster_aux(const mgen_data &mg, const monster *leader,
     if (montype == MONS_GLOWING_SHAPESHIFTER)
         mon->add_ench(ENCH_GLOWING_SHAPESHIFTER);
 
-    if (mg.cls == MONS_TOADSTOOL || mg.cls == MONS_PILLAR_OF_SALT)
+    if (mg.cls == MONS_TOADSTOOL
+        || mg.cls == MONS_PILLAR_OF_SALT
+        || mg.cls == MONS_BLOCK_OF_ICE)
     {
         // This enchantment is a timer that counts down until death.
         // It should last longer than the lifespan of a corpse, to avoid
diff --git a/crawl-ref/source/spl-cast.cc b/crawl-ref/source/spl-cast.cc
index 63743b3..26e5652 100644
--- a/crawl-ref/source/spl-cast.cc
+++ b/crawl-ref/source/spl-cast.cc
@@ -1126,6 +1126,8 @@ static targetter* _spell_targetter(spell_type spell, int pow, int range)
         return new targetter_spray(&you, range, ZAP_DAZZLING_SPRAY);
     case SPELL_EXPLOSIVE_BOLT:
         return new targetter_explosive_bolt(&you, pow, range);
+    case SPELL_GLACIATE:
+        return new targetter_cone(&you, range);
     case SPELL_MAGIC_DART:
     case SPELL_FORCE_LANCE:
     case SPELL_SHOCK:
@@ -1849,6 +1851,9 @@ static spret_type _do_cast(spell_type spell, int powc,
         mpr("This rod is automatically evoked when it strikes in combat.");
         return SPRET_ABORT;
 
+    case SPELL_GLACIATE:
+        return cast_glaciate(&you, powc, target, fail);
+
     default:
         return SPRET_NONE;
     }
diff --git a/crawl-ref/source/spl-damage.cc b/crawl-ref/source/spl-damage.cc
index b71d3ea..fb43ccc 100644
--- a/crawl-ref/source/spl-damage.cc
+++ b/crawl-ref/source/spl-damage.cc
@@ -2946,3 +2946,78 @@ void end_searing_ray()
     you.props.erase("searing_ray_target");
     you.props.erase("searing_ray_aimed_at_spot");
 }
+
+spret_type cast_glaciate(actor *caster, int pow, coord_def aim, bool fail)
+{
+    targetter_cone hitfunc(caster, spell_range(SPELL_GLACIATE, pow));
+    hitfunc.set_aim(aim);
+
+    if (caster->is_player())
+    {
+        if (stop_attack_prompt(hitfunc, "glaciate"))
+            return SPRET_ABORT;
+    }
+
+    fail_check();
+
+    if (you.can_see(caster) || caster->is_player())
+    {
+        mprf("%s %s a mighty blast of ice!",
+             caster->name(DESC_THE).c_str(),
+             caster->conj_verb("conjure").c_str());
+    }
+
+    bolt beam;
+    beam.name              = "great icy blast";
+    beam.aux_source        = "great icy blast";
+    beam.flavour           = BEAM_ICE;
+    beam.glyph             = dchar_glyph(DCHAR_EXPLOSION);
+    beam.colour            = WHITE;
+    beam.range             = 1;
+    beam.hit               = AUTOMATIC_HIT;
+    beam.beam_source       = caster->mindex();
+    beam.set_agent(caster);
+#ifdef USE_TILE
+    beam.tile_beam = -1;
+#endif
+    beam.draw_delay = 0;
+
+    for (map<coord_def, aff_type>::const_iterator p = hitfunc.zapped.begin();
+         p != hitfunc.zapped.end(); ++p)
+    {
+        if (p->second <= 0)
+            continue;
+
+        beam.draw(p->first);
+    }
+
+    delay(200);
+
+    beam.glyph = 0;
+
+    for (map<coord_def, aff_type>::const_iterator p = hitfunc.zapped.begin();
+         p != hitfunc.zapped.end(); ++p)
+    {
+        if (p->second <= 0)
+            continue;
+
+        const int range2 = max(9, (p->first - caster->pos()).abs());
+
+        if (actor_at(p->first))
+        {
+            beam.source = beam.target = p->first;
+            beam.source.x -= sgn(beam.source.x - hitfunc.origin.x);
+            beam.source.y -= sgn(beam.source.y - hitfunc.origin.y);
+            // At range 3 (max ice storm size), this is equivalent to ice
+            // storm damage.
+            beam.damage            = calc_dice(7, (198 + 9 * pow) / range2);
+            beam.fire();
+        }
+        place_cloud(CLOUD_COLD, p->first,
+                    (18 + random2avg(45,2)) / range2, caster);
+    }
+
+    noisy(9, hitfunc.origin);
+
+    return SPRET_SUCCESS;
+}
diff --git a/crawl-ref/source/spl-damage.h b/crawl-ref/source/spl-damage.h
index f19db4d..4816958 100644
--- a/crawl-ref/source/spl-damage.h
+++ b/crawl-ref/source/spl-damage.h
@@ -64,4 +64,7 @@ void toxic_radiance_effect(actor* agent, int mult);
 spret_type cast_searing_ray(int pow, bolt &beam, bool fail);
 void handle_searing_ray();
 void end_searing_ray();
+
+spret_type cast_glaciate(actor *caster, int pow, coord_def aim,
+                         bool fail = false);
 #endif
diff --git a/crawl-ref/source/spl-data.h b/crawl-ref/source/spl-data.h
index bd200b0..9d92b4b 100644
--- a/crawl-ref/source/spl-data.h
+++ b/crawl-ref/source/spl-data.h
@@ -3561,6 +3561,19 @@ struct spell_desc
 },
 
 {
+    SPELL_GLACIATE, "Glaciate",
+     SPTYP_CONJURATION | SPTYP_ICE,
+     SPFLAG_DIR_OR_TARGET | SPFLAG_AREA,
+     9,
+     200,
+     3, LOS_RADIUS,
+     0,
+     NULL,
+     false,
+     false
+},
+
+{
     SPELL_NO_SPELL, "nonexistent spell",
      0,
      SPFLAG_TESTING,
diff --git a/crawl-ref/source/target.cc b/crawl-ref/source/target.cc
index 043dbc1..0f33dad 100644
--- a/crawl-ref/source/target.cc
+++ b/crawl-ref/source/target.cc
@@ -14,6 +14,8 @@
 #include "spl-damage.h"
 #include "terrain.h"
 
+#include <math.h>
+
 #define notify_fail(x) (why_not = (x), false)
 
 static string _wallmsg(coord_def c)
@@ -1171,3 +1173,109 @@ aff_type targetter_explosive_bolt::is_affected(coord_def loc)
 
     return on_path ? AFF_TRACER : AFF_NO;
 }
+
+targetter_cone::targetter_cone(const actor *act, int r)
+{
+    ASSERT(act);
+    agent = act;
+    origin = act->pos();
+    aim = origin;
+    ASSERT_RANGE(r, 1 + 1, you.current_vision + 1);
+    range2 = sqr(r) + 1;
+}
+
+bool targetter_cone::valid_aim(coord_def a)
+{
+    if (a != origin && !cell_see_cell(origin, a, LOS_NO_TRANS))
+    {
+        // Scrying/glass/tree/grate.
+        if (agent->see_cell(a))
+            return notify_fail("There's something in the way.");
+        return notify_fail("You cannot see that place.");
+    }
+    if ((origin - a).abs() > range2)
+        return notify_fail("Out of range.");
+    return true;
+}
+
+// Ripped off from targetter_thunderbolt::set_aim.
+bool targetter_cone::set_aim(coord_def a)
+{
+    aim = a;
+    zapped.clear();
+
+    if (a == origin)
+        return false;
+
+    const coord_def delta = a - origin;
+    const double arc = PI/4;
+    coord_def l, r;
+    l.x = origin.x + (cos(-arc) * delta.x - sin(-arc) * delta.y + 0.5);
+    l.y = origin.y + (sin(-arc) * delta.x + cos(-arc) * delta.y + 0.5);
+    r.x = origin.x + (cos( arc) * delta.x - sin( arc) * delta.y + 0.5);
+    r.y = origin.y + (sin( arc) * delta.x + cos( arc) * delta.y + 0.5);
+
+    ray_def ray;
+    coord_def p; // ray.pos() does lots of processing, cache it
+
+    _make_ray(ray, origin, l);
+    bool hit = true;
+    while ((origin - (p = ray.pos())).abs() <= range2)
+    {
+        if (!map_bounds(p) || opc_solid_see(p) >= OPC_OPAQUE)
+            hit = false;
+        if (hit && p != origin && zapped[p] <= 0)
+            zapped[p] = AFF_YES;
+        ray.advance();
+    }
+
+    _make_ray(ray, origin, r);
+    hit = true;
+    while ((origin - (p = ray.pos())).abs() <= range2)
+    {
+        if (!map_bounds(p) || opc_solid_see(p) >= OPC_OPAQUE)
+            hit = false;
+        if (hit && p != origin && zapped[p] <= 0)
+            zapped[p] = AFF_YES;
+        ray.advance();
+    }
+
+    coord_def a1 = l - origin;
+    coord_def a2 = r - origin;
+    if (left_of(a2, a1))
+        swapv(a1, a2);
+
+    for (int x = -LOS_RADIUS; x <= LOS_RADIUS; ++x)
+        for (int y = -LOS_RADIUS; y <= LOS_RADIUS; ++y)
+        {
+            if (sqr(x) + sqr(y) > range2)
+                continue;
+            coord_def q(x, y);
+            if (left_of(a1, q) && left_of(q, a2))
+            {
+                (p = q) += origin;
+                if (zapped[p] <= 0
+                    && map_bounds(p)
+                    && opc_solid_see(p) < OPC_OPAQUE
+                    && cell_see_cell(origin, p, LOS_NO_TRANS))
+                {
+                    zapped[p] = AFF_YES;
+                }
+            }
+        }
+
+    zapped[origin] = AFF_NO;
+
+    return true;
+}
+
+aff_type targetter_cone::is_affected(coord_def loc)
+{
+    if (loc == aim)
+        return zapped[loc] ? AFF_YES : AFF_TRACER;
+
+    if ((loc - origin).abs() > range2)
+        return AFF_NO;
+
+    return zapped[loc];
+}
diff --git a/crawl-ref/source/target.h b/crawl-ref/source/target.h
index 6fbcce5..8127eb9 100644
--- a/crawl-ref/source/target.h
+++ b/crawl-ref/source/target.h
@@ -236,4 +236,17 @@ private:
     explosion_map exp_map;
 };
 
+class targetter_cone : public targetter
+{
+public:
+    targetter_cone(const actor *act, int r);
+
+    bool valid_aim(coord_def a);
+    bool set_aim(coord_def a);
+    aff_type is_affected(coord_def loc);
+    map<coord_def, aff_type> zapped;
+private:
+    int range2;
+};
+
 #endif
-- 
1.8.5.3

