Revert "Track who destroys an item; incur Nemelex penance for deck destruction."
[crawl.git] / crawl-ref / source / spl-damage.cc
1 /**
2  * @file
3  * @brief Damage-dealing spells not already handled elsewhere.
4  *           Other targeted spells are covered in spl-zap.cc.
5 **/
6
7 #include "AppHdr.h"
8
9 #include "spl-damage.h"
10 #include "externs.h"
11
12 #include "act-iter.h"
13 #include "areas.h"
14 #include "beam.h"
15 #include "cloud.h"
16 #include "colour.h"
17 #include "coord.h"
18 #include "coordit.h"
19 #include "directn.h"
20 #include "env.h"
21 #include "feature.h"
22 #include "food.h"
23 #include "fprop.h"
24 #include "godabil.h"
25 #include "godconduct.h"
26 #include "itemname.h"
27 #include "itemprop.h"
28 #include "items.h"
29 #include "libutil.h"
30 #include "losglobal.h"
31 #include "message.h"
32 #include "misc.h"
33 #include "mon-behv.h"
34 #include "mon-iter.h"
35 #include "mon-stuff.h"
36 #include "ouch.h"
37 #include "player-equip.h"
38 #include "shout.h"
39 #include "spl-util.h"
40 #include "stuff.h"
41 #include "target.h"
42 #include "terrain.h"
43 #include "transform.h"
44 #include "traps.h"
45 #include "view.h"
46 #include "viewchar.h"
47
48 // This spell has two main advantages over Fireball:
49 //
50 // (1) The release is instantaneous, so monsters will not
51 //     get an action before the player... this allows the
52 //     player to hit monsters with a double fireball (this
53 //     is why we only allow one delayed fireball at a time,
54 //     if you want to allow for more, then the release should
55 //     take at least some amount of time).
56 //
57 //     The casting of this spell still costs a turn.  So
58 //     casting Delayed Fireball and immediately releasing
59 //     the fireball is only slightly different from casting
60 //     a regular Fireball (monsters act in the middle instead
61 //     of at the end).  This is why we allow for the spell
62 //     level discount so that Fireball is free with this spell
63 //     (so that it only costs 7 levels instead of 13 to have
64 //     both).
65 //
66 // (2) When the fireball is released, it is guaranteed to
67 //     go off... the spell only fails at this point.  This can
68 //     be a large advantage for characters who have difficulty
69 //     casting Fireball in their standard equipment.  However,
70 //     the power level for the actual fireball is determined at
71 //     release, so if you do swap out your enhancers you'll
72 //     get a less powerful ball when it's released. - bwr
73 //
74 spret_type cast_delayed_fireball(bool fail)
75 {
76     if (you.attribute[ATTR_DELAYED_FIREBALL])
77     {
78         mpr("You are already charged.");
79         return SPRET_ABORT;
80     }
81
82     fail_check();
83     // Okay, this message is weak but functional. - bwr
84     mpr("You feel magically charged.");
85     you.attribute[ATTR_DELAYED_FIREBALL] = 1;
86     return SPRET_SUCCESS;
87 }
88
89 void setup_fire_storm(const actor *source, int pow, bolt &beam)
90 {
91     beam.name         = "great blast of fire";
92     beam.ex_size      = 2 + (random2(pow) > 75);
93     beam.flavour      = BEAM_LAVA;
94     beam.real_flavour = beam.flavour;
95     beam.glyph        = dchar_glyph(DCHAR_FIRED_ZAP);
96     beam.colour       = RED;
97     beam.beam_source  = source->mindex();
98     // XXX: Should this be KILL_MON_MISSILE?
99     beam.thrower      =
100         source->is_player() ? KILL_YOU_MISSILE : KILL_MON;
101     beam.aux_source.clear();
102     beam.obvious_effect = false;
103     beam.is_beam      = false;
104     beam.is_tracer    = false;
105     beam.is_explosion = true;
106     beam.ench_power   = pow;      // used for radius
107     beam.hit          = 20 + pow / 10;
108     beam.damage       = calc_dice(8, 5 + pow);
109 }
110
111 spret_type cast_fire_storm(int pow, bolt &beam, bool fail)
112 {
113     if (distance2(beam.target, beam.source) > dist_range(beam.range))
114     {
115         mpr("That is beyond the maximum range.");
116         return SPRET_ABORT;
117     }
118
119     if (cell_is_solid(beam.target))
120     {
121         mpr("You can't place the storm on a wall.");
122         return SPRET_ABORT;
123     }
124
125     fail_check();
126     setup_fire_storm(&you, pow, beam);
127
128     bolt tempbeam = beam;
129     tempbeam.ex_size = (pow > 76) ? 3 : 2;
130     tempbeam.is_tracer = true;
131
132     tempbeam.explode(false);
133     if (tempbeam.beam_cancelled)
134     {
135         canned_msg(MSG_OK);
136         return SPRET_ABORT;
137     }
138
139     beam.refine_for_explosion();
140     beam.explode(false);
141
142     viewwindow();
143     return SPRET_SUCCESS;
144 }
145
146 // No setup/cast split here as monster hellfire is completely different.
147 // Sad, but needed to maintain balance - monster hellfirers get asymmetric
148 // torment too.
149 bool cast_hellfire_burst(int pow, bolt &beam)
150 {
151     beam.name              = "burst of hellfire";
152     beam.aux_source        = "burst of hellfire";
153     beam.ex_size           = 1;
154     beam.flavour           = BEAM_HELLFIRE;
155     beam.real_flavour      = beam.flavour;
156     beam.glyph             = dchar_glyph(DCHAR_FIRED_BURST);
157     beam.colour            = RED;
158     beam.beam_source       = MHITYOU;
159     beam.thrower           = KILL_YOU;
160     beam.obvious_effect    = false;
161     beam.is_beam           = false;
162     beam.is_explosion      = true;
163     beam.ench_power        = pow;      // used for radius
164     beam.hit               = 20 + pow / 10;
165     beam.damage            = calc_dice(6, 30 + pow);
166     beam.can_see_invis     = you.can_see_invisible();
167     beam.smart_monster     = true;
168     beam.attitude          = ATT_FRIENDLY;
169     beam.friend_info.count = 0;
170     beam.is_tracer         = true;
171
172     beam.explode(false);
173
174     if (beam.beam_cancelled)
175     {
176         canned_msg(MSG_OK);
177         return false;
178     }
179
180     mpr("You call forth a pillar of hellfire!");
181
182     beam.is_tracer = false;
183     beam.in_explosion_phase = false;
184     beam.explode(true);
185
186     return true;
187 }
188
189 // XXX no friendly check
190 spret_type cast_chain_lightning(int pow, const actor *caster, bool fail)
191 {
192     fail_check();
193     bolt beam;
194
195     // initialise beam structure
196     beam.name           = "lightning arc";
197     beam.aux_source     = "chain lightning";
198     beam.beam_source    = caster->mindex();
199     beam.thrower        = caster->is_player() ? KILL_YOU_MISSILE : KILL_MON_MISSILE;
200     beam.range          = 8;
201     beam.hit            = AUTOMATIC_HIT;
202     beam.glyph          = dchar_glyph(DCHAR_FIRED_ZAP);
203     beam.flavour        = BEAM_ELECTRICITY;
204     beam.obvious_effect = true;
205     beam.is_beam        = false;       // since we want to stop at our target
206     beam.is_explosion   = false;
207     beam.is_tracer      = false;
208
209     if (const monster* mons = caster->as_monster())
210         beam.source_name = mons->name(DESC_PLAIN, true);
211
212     bool first = true;
213     coord_def source, target;
214
215     for (source = caster->pos(); pow > 0;
216          pow -= 8 + random2(13), source = target)
217     {
218         // infinity as far as this spell is concerned
219         // (Range - 1) is used because the distance is randomised and
220         // may be shifted by one.
221         int min_dist = MONSTER_LOS_RANGE - 1;
222
223         int dist;
224         int count = 0;
225
226         target.x = -1;
227         target.y = -1;
228
229         for (monster_iterator mi; mi; ++mi)
230         {
231             if (invalid_monster(*mi))
232                 continue;
233
234             // Don't arc to things we cannot hit.
235             if (beam.ignores_monster(*mi))
236                 continue;
237
238             dist = grid_distance(source, mi->pos());
239
240             // check for the source of this arc
241             if (!dist)
242                 continue;
243
244             // randomise distance (arcs don't care about a couple of feet)
245             dist += (random2(3) - 1);
246
247             // always ignore targets further than current one
248             if (dist > min_dist)
249                 continue;
250
251             if (!cell_see_cell(source, mi->pos(), LOS_SOLID))
252                 continue;
253
254             count++;
255
256             if (dist < min_dist)
257             {
258                 // switch to looking for closer targets (but not always)
259                 if (!one_chance_in(10))
260                 {
261                     min_dist = dist;
262                     target = mi->pos();
263                     count = 0;
264                 }
265             }
266             else if (target.x == -1 || one_chance_in(count))
267             {
268                 // either first target, or new selected target at
269                 // min_dist == dist.
270                 target = mi->pos();
271             }
272         }
273
274         // now check if the player is a target
275         dist = grid_distance(source, you.pos());
276
277         if (dist)       // i.e., player was not the source
278         {
279             // distance randomised (as above)
280             dist += (random2(3) - 1);
281
282             // select player if only, closest, or randomly selected
283             if ((target.x == -1
284                     || dist < min_dist
285                     || (dist == min_dist && one_chance_in(count + 1)))
286                 && cell_see_cell(source, you.pos(), LOS_SOLID))
287             {
288                 target = you.pos();
289             }
290         }
291
292         const bool see_source = you.see_cell(source);
293         const bool see_targ   = you.see_cell(target);
294
295         if (target.x == -1)
296         {
297             if (see_source)
298                 mpr("The lightning grounds out.");
299
300             break;
301         }
302
303         // Trying to limit message spamming here so we'll only mention
304         // the thunder at the start or when it's out of LoS.
305         const char* msg = "You hear a mighty clap of thunder!";
306         noisy(25, source, (first || !see_source) ? msg : NULL);
307         first = false;
308
309         if (see_source && !see_targ)
310             mpr("The lightning arcs out of your line of sight!");
311         else if (!see_source && see_targ)
312             mpr("The lightning arc suddenly appears!");
313
314         if (!you.see_cell_no_trans(target))
315         {
316             // It's no longer in the caster's LOS and influence.
317             pow = pow / 2 + 1;
318         }
319
320         beam.source = source;
321         beam.target = target;
322         beam.colour = LIGHTBLUE;
323         beam.damage = calc_dice(5, 10 + pow * 2 / 3);
324
325         // Be kinder to the caster.
326         if (target == caster->pos())
327         {
328             if (!(beam.damage.num /= 2))
329                 beam.damage.num = 1;
330             if ((beam.damage.size /= 2) < 3)
331                 beam.damage.size = 3;
332         }
333         beam.fire();
334     }
335
336     more();
337     return SPRET_SUCCESS;
338 }
339
340 // Poisonous light passes right through invisible players
341 // and monsters, and so, they are unaffected by this spell --
342 // assumes only you can cast this spell (or would want to).
343 static bool _toxic_radianceable(const actor *agent, const actor *act)
344 {
345     if (act->is_monster() && act->as_monster()->submerged())
346         return false;
347
348     // currently monsters are still immune at rPois 1
349     return (act->res_poison() < (act->is_player() ? 3 : 1));
350 }
351
352 static bool _toxic_radianceable_hitfunc(const actor *act)
353 {
354     if (act->invisible())
355         return false;
356
357     return _toxic_radianceable(&you, act);
358 }
359
360 static int _toxic_levels(int pow)
361 {
362     return 1 + random2(pow / 20);
363 }
364
365 static int _toxic_irradiate_player(actor* agent, int pow, int avg,
366                                    bool actual, bool added_effects)
367 {
368     int levels = (actual) ? _toxic_levels(pow) : avg;
369
370     if (!agent || agent->is_player())
371         levels = 2; // XXX: preserve original behaviour
372     // Determine whether the player is hit by the radiance. {dlb}
373     if (you.duration[DUR_INVIS])
374     {
375         if (actual)
376             mpr("The light passes straight through your body.");
377         levels = 0;
378     }
379     else if (actual)
380     {
381         levels = poison_player(levels,
382                                agent ? agent->name(DESC_A) : "",
383                                "toxic radiance");
384         if (levels)
385             mpr("You feel rather sick.");
386     }
387
388     return levels;
389 }
390
391 static int _toxic_irradiate_monster(actor* agent, monster* target, int pow,
392                                     int avg, bool actual, bool added_effects)
393 {
394     int levels = (actual) ? _toxic_levels(pow) : avg;
395
396     // Monsters affected by corona are still invisible in that
397     // radiation passes through them without affecting them. Therefore,
398     // this check should not be !mons->invisible().
399     if (!target->has_ench(ENCH_INVIS))
400     {
401         if (actual && !poison_monster(target, agent, levels, false, false))
402             levels = 0;
403     }
404     else if (you.can_see_invisible())
405     {
406         if (actual)
407         {
408             // message player re:"miss" where appropriate {dlb}
409             mprf("The light passes through %s.",
410                  target->name(DESC_THE).c_str());
411         }
412         levels = 0;
413     }
414
415     return levels;
416 }
417
418 static counted_monster_list _counted_monster_list_from_vector(
419     vector<monster *> affected_monsters)
420 {
421     counted_monster_list mons;
422     for (vector<monster *>::iterator it = affected_monsters.begin();
423          it != affected_monsters.end(); it++)
424     {
425         mons.add(*it);
426     }
427
428     return mons;
429 }
430
431 static void _post_toxic_radiate(actor* agent, bool player,
432                                 vector<monster *> affected_monsters,
433                                 int pow, int total_damage)
434 {
435     if (!affected_monsters.empty())
436     {
437         counted_monster_list mons_list =
438             _counted_monster_list_from_vector(affected_monsters);
439         const string message =
440             make_stringf("%s %s poisoned.",
441                          mons_list.describe().c_str(),
442                          mons_list.count() == 1? "is" : "are");
443         if (strwidth(message) < get_number_of_cols() - 2)
444             mpr(message.c_str());
445         else
446         {
447             // Exclamation mark to suggest that a lot of creatures were
448             // affected.
449             if (!agent)
450                 mpr("Nearby monsters are poisoned!");
451             else
452                 mprf("The monsters around %s are poisoned!",
453                      agent->is_monster() && you.can_see(agent)
454                      ? agent->as_monster()->name(DESC_THE).c_str()
455                      : "you");
456         }
457
458         if (agent && agent->is_player())
459         {
460             // Sanctuary is violated if either you or any victims are in it.
461             bool sanct = is_sanctuary(you.pos());
462             for (vector<monster *>::iterator it = affected_monsters.begin();
463                  it != affected_monsters.end() && !sanct; it++)
464             {
465                 if (is_sanctuary((*it)->pos()))
466                     sanct = true;
467             }
468             if (sanct)
469                 remove_sanctuary(true);
470         }
471     }
472 }
473
474 static bool _refrigerateable(const actor *agent, const actor *act)
475 {
476     // Inconsistency: monsters suffer no damage at rC+++, players suffer
477     // considerable damage.
478     return (act->is_player() || act->res_cold() < 3);
479 }
480
481 static bool _refrigerateable_hitfunc(const actor *act)
482 {
483     return _refrigerateable(&you, act);
484 }
485
486 static void _pre_refrigerate(actor* agent, bool player,
487                              vector<monster *> affected_monsters)
488 {
489     if (!affected_monsters.empty())
490     {
491         counted_monster_list mons_list =
492             _counted_monster_list_from_vector(affected_monsters);
493         const string message =
494             make_stringf("%s %s frozen.",
495                          mons_list.describe().c_str(),
496                          mons_list.count() == 1? "is" : "are");
497         if (strwidth(message) < get_number_of_cols() - 2)
498             mpr(message.c_str());
499         else
500         {
501             // Exclamation mark to suggest that a lot of creatures were
502             // affected.
503             mprf("The monsters around %s are frozen!",
504                  agent && agent->is_monster() && you.can_see(agent)
505                  ? agent->as_monster()->name(DESC_THE).c_str()
506                  : "you");
507         }
508     }
509 }
510
511 static const dice_def _refrigerate_damage(int pow)
512 {
513     return dice_def(3, 5 + pow / 10);
514 }
515
516 static int _refrigerate_player(actor* agent, int pow, int avg,
517                                bool actual, bool added_effects)
518 {
519     const dice_def dam_dice = _refrigerate_damage(pow);
520
521     int hurted = check_your_resists((actual) ? dam_dice.roll() : avg,
522                                     BEAM_COLD, "refrigeration", 0, actual);
523     if (actual && hurted > 0)
524     {
525         mpr("You feel very cold.");
526         if (agent && !agent->is_player())
527             ouch(hurted, agent->as_monster()->mindex(), KILLED_BY_BEAM,
528                  "by Ozocubu's Refrigeration", true,
529                  agent->as_monster()->name(DESC_A).c_str());
530         else
531             ouch(hurted, NON_MONSTER, KILLED_BY_FREEZING);
532
533         // Note: this used to be 12!... and it was also applied even if
534         // the player didn't take damage from the cold, so we're being
535         // a lot nicer now.  -- bwr
536         expose_player_to_element(BEAM_COLD, 5, added_effects);
537     }
538
539     return hurted;
540 }
541
542 static int _refrigerate_monster(actor* agent, monster* target, int pow, int avg,
543                                 bool actual, bool added_effects)
544 {
545     const dice_def dam_dice = _refrigerate_damage(pow);
546
547     bolt beam;
548     beam.flavour = BEAM_COLD;
549     beam.thrower = (agent && agent->is_player()) ? KILL_YOU :
550                    (agent)                       ? KILL_MON
551                                                  : KILL_MISC;
552
553     int hurted = mons_adjust_flavoured(target, beam,
554                                        (actual) ? dam_dice.roll() : avg,
555                                        actual);
556     dprf("damage done: %d", hurted);
557
558     if (actual)
559     {
560         target->hurt(agent, hurted, BEAM_COLD);
561
562         if (agent && agent->is_player()
563             && (is_sanctuary(you.pos()) || is_sanctuary(target->pos())))
564             remove_sanctuary(true);
565
566         // Cold-blooded creatures can be slowed.
567         if (target->alive())
568             target->expose_to_element(BEAM_COLD, 5);
569     }
570
571     return hurted;
572 }
573
574 static bool _drain_lifeable(const actor* agent, const actor* act)
575 {
576     if (act->res_negative_energy())
577         return false;
578
579     if (!agent)
580         return true;
581
582     const monster* mons = agent->as_monster();
583     const monster* m = act->as_monster();
584
585     return !(agent->is_player() && act->wont_attack()
586              || mons && act->is_player() && mons->wont_attack()
587              || mons && m && mons_atts_aligned(mons->attitude, m->attitude));
588 }
589
590 static bool _drain_lifeable_hitfunc(const actor* act)
591 {
592     return _drain_lifeable(&you, act);
593 }
594
595 static int _drain_player(actor* agent, int pow, int avg,
596                          bool actual, bool added_effects)
597 {
598     if (actual)
599     {
600         monster* mons = agent->as_monster();
601         ouch(avg, mons ? mons->mindex() : NON_MONSTER,
602              KILLED_BY_BEAM, "by drain life");
603     }
604
605     return avg;
606 }
607
608 static int _drain_monster(actor* agent, monster* target, int pow, int avg,
609                           bool actual, bool added_effects)
610 {
611     if (actual)
612     {
613         if (agent->is_player())
614         {
615             mprf("You draw life from %s.",
616                  target->name(DESC_THE).c_str());
617         }
618
619         behaviour_event(target, ME_WHACK, agent, agent->pos());
620
621         target->hurt(agent, avg);
622
623         if (target->alive() && you.can_see(target))
624             print_wounds(target);
625     }
626
627     if (!target->is_summoned())
628         return avg;
629
630     return 0;
631 }
632
633 static void _post_drain_life(actor* agent, bool player,
634                              vector<monster *> affected_monsters,
635                              int pow, int total_damage)
636 {
637     total_damage /= 2;
638
639     total_damage = min(pow * 2, total_damage);
640
641     if (total_damage)
642     {
643         if (agent->is_player())
644         {
645             mpr("You feel life flooding into your body.");
646             inc_hp(total_damage);
647         }
648         else if (agent)
649         {
650             monster* mons = agent->as_monster();
651             ASSERT(mons);
652             if (mons->heal(total_damage))
653                 simple_monster_message(mons, " is healed.");
654         }
655     }
656 }
657
658 spret_type cast_los_attack_spell(spell_type spell, int pow, actor* agent,
659                                  bool actual, bool added_effects, bool fail)
660 {
661     monster* mons = agent ? agent->as_monster() : NULL;
662     coord_def start_pos = agent ? agent->pos() : you.pos();
663
664     colour_t flash_colour = BLACK;
665     string player_msg, global_msg, mons_vis_msg, mons_invis_msg,
666            harm_msg = "harm";
667     bool (*vulnerable)(const actor *, const actor *) = NULL;
668     bool (*vul_hitfunc)(const actor *) = NULL;
669     int (*damage_player)(actor *, int, int, bool, bool) = NULL;
670     int (*damage_monster)(actor *, monster *, int, int, bool, bool) = NULL;
671     void (*pre_hook)(actor*, bool, vector<monster *>) = NULL;
672     void (*post_hook)(actor*, bool, vector<monster *>, int, int) = NULL;
673
674     int hurted = 0;
675     int this_damage = 0;
676     int total_damage = 0;
677
678     bolt beam;
679     beam.beam_source = (mons) ? mons->mindex() : MHITNOT;
680     beam.foe_ratio = 80;
681
682     switch(spell)
683     {
684         case SPELL_OZOCUBUS_REFRIGERATION:
685             player_msg = "The heat is drained from your surroundings.";
686             global_msg = "Something drains the heat from around you.";
687             mons_vis_msg = " drains the heat from the surrounding"
688                            " environment!";
689             mons_invis_msg = "The ambient heat is drained!";
690             flash_colour = LIGHTCYAN;
691             vulnerable = &_refrigerateable;
692             vul_hitfunc = &_refrigerateable_hitfunc;
693             damage_player = &_refrigerate_player;
694             damage_monster = &_refrigerate_monster;
695             pre_hook = &_pre_refrigerate;
696             hurted = (3 + (5 + pow / 10)) / 2; // average
697             break;
698
699         case SPELL_DRAIN_LIFE:
700             player_msg = "You draw life from your surroundings.";
701             global_msg = "Something draws the life force from your"
702                          " surroundings.";
703             mons_vis_msg = " draws from the surrounding life force!";
704             mons_invis_msg = "The surrounding life force dissipates!";
705             flash_colour = DARKGREY;
706             vulnerable = &_drain_lifeable;
707             vul_hitfunc = &_drain_lifeable_hitfunc;
708             damage_player = &_drain_player;
709             damage_monster = &_drain_monster;
710             post_hook = &_post_drain_life;
711             hurted = 3 + random2(7) + random2(pow);
712             break;
713
714         case SPELL_OLGREBS_TOXIC_RADIANCE:
715             player_msg = "You radiate a sickly green light!";
716             global_msg = mons_invis_msg =
717                 "The air is filled with a sickly green light!";
718             mons_vis_msg = " radiates a sickly green light!";
719             flash_colour = GREEN;
720             vulnerable = &_toxic_radianceable;
721             vul_hitfunc = &_toxic_radianceable_hitfunc;
722             damage_player = &_toxic_irradiate_player;
723             damage_monster = &_toxic_irradiate_monster;
724             harm_msg = "poison";
725             post_hook = &_post_toxic_radiate;
726             hurted = 1 + (pow / 10); // average
727             break;
728
729         default: return SPRET_ABORT;
730     }
731
732     if (agent && agent->is_player())
733     {
734         ASSERT(actual);
735         targetter_los hitfunc(&you, LOS_SOLID);
736         {
737             if (stop_attack_prompt(hitfunc, harm_msg, vul_hitfunc))
738                 return SPRET_ABORT;
739         }
740         fail_check();
741
742         mpr(player_msg);
743         flash_view(flash_colour, &hitfunc);
744         more();
745         mesclr();
746         flash_view(0);
747     }
748     else if (actual)
749     {
750         if (!agent)
751             mpr(global_msg);
752         else if (you.can_see(agent))
753             simple_monster_message(mons, mons_vis_msg.c_str());
754         else if (you.see_cell(agent->pos()))
755             mpr(mons_invis_msg);
756
757         flash_view_delay(flash_colour, 300);
758     }
759
760     bool affects_you = false;
761     vector<monster *> affected_monsters;
762
763     for (actor_iterator ai(agent ? agent : &you); ai; ++ai)
764     {
765         if (cell_see_cell(start_pos, ai->pos(), LOS_SOLID)
766             && (*vulnerable)(agent, *ai))
767         {
768             if (ai->is_player())
769                 affects_you = true;
770             else
771                 affected_monsters.push_back(ai->as_monster());
772         }
773     }
774
775     // XXX: This ordering is kind of broken; it's to preserve the message
776     // order from the original behaviour in the case of refrigerate.
777     if (affects_you)
778     {
779         total_damage = (*damage_player)(agent, pow, hurted, actual,
780                                         added_effects);
781         if (!actual && mons)
782         {
783             if (mons->wont_attack())
784             {
785                 beam.friend_info.count++;
786                 beam.friend_info.power +=
787                     (you.get_experience_level() * this_damage / hurted);
788             }
789             else
790             {
791                 beam.foe_info.count++;
792                 beam.foe_info.power +=
793                     (you.get_experience_level() * this_damage / hurted);
794             }
795         }
796     }
797
798     if (actual && pre_hook)
799         (*pre_hook)(agent, affects_you, affected_monsters);
800
801     for (vector<monster *>::iterator it = affected_monsters.begin();
802          it != affected_monsters.end(); it++)
803     {
804         monster* m = (monster *)(*it);
805
806         // Watch out for invalidation. Example: Ozocubu's refrigeration on
807         // a bunch of giant spores that blow each other up.
808         if (!m->alive())
809             continue;
810
811         this_damage = (*damage_monster)(agent, m, pow, hurted, actual,
812                                         added_effects);
813         total_damage += this_damage;
814
815         if (!actual && mons)
816         {
817             if (mons_atts_aligned(m->attitude, mons->attitude))
818             {
819                 beam.friend_info.count++;
820                 beam.friend_info.power += (m->hit_dice * this_damage / hurted);
821             }
822             else
823             {
824                 beam.foe_info.count++;
825                 beam.foe_info.power += (m->hit_dice * this_damage / hurted);
826             }
827         }
828     }
829
830     if (actual)
831     {
832         if (post_hook)
833             (*post_hook)(agent, affects_you, affected_monsters, pow,
834                          total_damage);
835
836         return SPRET_SUCCESS;
837     }
838
839     return mons_should_fire(beam) ? SPRET_SUCCESS : SPRET_ABORT;
840 }
841
842 // Screaming Sword
843 void sonic_damage(bool scream)
844 {
845     // First build the message.
846     counted_monster_list affected_monsters;
847
848     for (monster_iterator mi(&you); mi; ++mi)
849         if (cell_see_cell(you.pos(), mi->pos(), LOS_SOLID)
850             && !silenced(mi->pos()))
851         {
852             affected_monsters.add(*mi);
853         }
854
855     /* dpeg sez:
856        * damage applied to everyone but the wielder (reasoning: the sword
857          does not like competitors, so no allies)
858        -- so sorry, Beoghites, Jiyvaites and the rest.
859     */
860
861     if (!affected_monsters.empty())
862     {
863         const string message =
864             make_stringf("%s %s hurt by the noise.",
865                          affected_monsters.describe().c_str(),
866                          affected_monsters.count() == 1? "is" : "are");
867         if (strwidth(message) < get_number_of_cols() - 2)
868             mpr(message.c_str());
869         else
870         {
871             // Exclamation mark to suggest that a lot of creatures were
872             // affected.
873             mpr("The monsters around you reel from the noise!");
874         }
875     }
876
877     // Now damage the creatures.
878     for (monster_iterator mi(you.get_los()); mi; ++mi)
879     {
880         if (!cell_see_cell(you.pos(), mi->pos(), LOS_SOLID)
881             || silenced(mi->pos()))
882         {
883             continue;
884         }
885         int hurt = (random2(2) + 1) * (random2(2) + 1) * (random2(3) + 1)
886                  + (random2(3) + 1) + 1;
887         if (scream)
888             hurt = max(hurt * 2, 16);
889         int cap = scream ? mi->max_hit_points / 2 : mi->max_hit_points * 3 / 10;
890         hurt = min(hurt, max(cap, 1));
891         // not so much damage if you're a n00b
892         hurt = div_rand_round(hurt * you.experience_level, 27);
893         /* per dpeg:
894          * damage is universal (well, only to those who can hear, but not sure
895            we can determine that in-game), i.e. smiting, no resists
896         */
897         dprf("damage done: %d", hurt);
898         mi->hurt(&you, hurt);
899
900         if (is_sanctuary(you.pos()) || is_sanctuary(mi->pos()))
901             remove_sanctuary(true);
902     }
903 }
904
905 spret_type vampiric_drain(int pow, monster* mons, bool fail)
906 {
907     if (mons == NULL || mons->submerged())
908     {
909         fail_check();
910         canned_msg(MSG_NOTHING_CLOSE_ENOUGH);
911         // Cost to disallow freely locating invisible/submerged
912         // monsters.
913         return SPRET_SUCCESS;
914     }
915
916     if (mons->observable() && mons->holiness() != MH_NATURAL)
917     {
918         mpr("Draining that being is not a good idea.");
919         return SPRET_ABORT;
920     }
921
922     god_conduct_trigger conducts[3];
923     disable_attack_conducts(conducts);
924
925     const bool abort = stop_attack_prompt(mons, false, you.pos());
926
927     if (!abort && !fail)
928     {
929         set_attack_conducts(conducts, mons);
930
931         behaviour_event(mons, ME_WHACK, &you, you.pos());
932     }
933
934     enable_attack_conducts(conducts);
935
936     if (abort)
937     {
938         canned_msg(MSG_OK);
939         return SPRET_ABORT;
940     }
941
942     fail_check();
943
944     if (!mons->alive())
945     {
946         canned_msg(MSG_NOTHING_HAPPENS);
947         return SPRET_SUCCESS;
948     }
949
950     // Monster might be invisible or player misled.
951     if (mons->holiness() == MH_UNDEAD || mons->holiness() == MH_DEMONIC)
952     {
953         mpr("Aaaarggghhhhh!");
954         dec_hp(random2avg(39, 2) + 10, false, "vampiric drain backlash");
955         return SPRET_SUCCESS;
956     }
957
958     if (mons->holiness() != MH_NATURAL || mons->res_negative_energy())
959     {
960         canned_msg(MSG_NOTHING_HAPPENS);
961         return SPRET_SUCCESS;
962     }
963
964     // The practical maximum of this is about 25 (pow @ 100). - bwr
965     int hp_gain = 3 + random2avg(9, 2) + random2(pow) / 7;
966
967     hp_gain = min(mons->hit_points, hp_gain);
968     hp_gain = min(you.hp_max - you.hp, hp_gain);
969
970     if (!hp_gain)
971     {
972         canned_msg(MSG_NOTHING_HAPPENS);
973         return SPRET_SUCCESS;
974     }
975
976     const bool mons_was_summoned = mons->is_summoned();
977
978     mons->hurt(&you, hp_gain);
979
980     if (mons->alive())
981         print_wounds(mons);
982
983     hp_gain = div_rand_round(hp_gain, 2);
984
985     if (hp_gain && !mons_was_summoned && !you.duration[DUR_DEATHS_DOOR])
986     {
987         mpr("You feel life coursing into your body.");
988         inc_hp(hp_gain);
989     }
990
991     return SPRET_SUCCESS;
992 }
993
994 spret_type cast_freeze(int pow, monster* mons, bool fail)
995 {
996     pow = min(25, pow);
997
998     if (mons == NULL || mons->submerged())
999     {
1000         fail_check();
1001         canned_msg(MSG_NOTHING_CLOSE_ENOUGH);
1002         // If there's no monster there, you still pay the costs in
1003         // order to prevent locating invisible/submerged monsters.
1004         return SPRET_SUCCESS;
1005     }
1006
1007     god_conduct_trigger conducts[3];
1008     disable_attack_conducts(conducts);
1009
1010     const bool abort = stop_attack_prompt(mons, false, you.pos());
1011
1012     if (!abort && !fail)
1013     {
1014         set_attack_conducts(conducts, mons);
1015
1016         mprf("You freeze %s.", mons->name(DESC_THE).c_str());
1017
1018         behaviour_event(mons, ME_ANNOY, &you);
1019     }
1020
1021     enable_attack_conducts(conducts);
1022
1023     if (abort)
1024     {
1025         canned_msg(MSG_OK);
1026         return SPRET_ABORT;
1027     }
1028
1029     fail_check();
1030
1031     bolt beam;
1032     beam.flavour = BEAM_COLD;
1033     beam.thrower = KILL_YOU;
1034
1035     const int orig_hurted = roll_dice(1, 3 + pow / 3);
1036     int hurted = mons_adjust_flavoured(mons, beam, orig_hurted);
1037     mons->hurt(&you, hurted);
1038
1039     if (mons->alive())
1040     {
1041         mons->expose_to_element(BEAM_COLD, orig_hurted);
1042         print_wounds(mons);
1043
1044         const int cold_res = mons->res_cold();
1045         if (cold_res <= 0)
1046         {
1047             const int stun = (1 - cold_res) * random2(2 + pow/5);
1048             mons->speed_increment -= stun;
1049         }
1050     }
1051
1052     return SPRET_SUCCESS;
1053 }
1054
1055 spret_type cast_airstrike(int pow, const dist &beam, bool fail)
1056 {
1057     monster* mons = monster_at(beam.target);
1058     if (!mons
1059         || mons->submerged()
1060         || (cell_is_solid(beam.target) && mons_wall_shielded(mons)))
1061     {
1062         fail_check();
1063         canned_msg(MSG_SPELL_FIZZLES);
1064         return SPRET_SUCCESS; // still losing a turn
1065     }
1066
1067     if (mons->res_wind() > 0)
1068     {
1069         if (mons->observable())
1070         {
1071             mprf("But air would do no harm to %s!",
1072                  mons->name(DESC_THE).c_str());
1073             return SPRET_ABORT;
1074         }
1075
1076         fail_check();
1077         mprf("The air twists arounds and harmlessly tosses %s around.",
1078              mons->name(DESC_THE).c_str());
1079         // Bailing out early, no need to upset the gods or the target.
1080         return SPRET_SUCCESS; // you still did discover the invisible monster
1081     }
1082
1083     god_conduct_trigger conducts[3];
1084     disable_attack_conducts(conducts);
1085
1086     if (stop_attack_prompt(mons, false, you.pos()))
1087         return SPRET_ABORT;
1088
1089     fail_check();
1090     set_attack_conducts(conducts, mons);
1091
1092     mprf("The air twists around and strikes %s!",
1093          mons->name(DESC_THE).c_str());
1094     noisy(4, beam.target);
1095
1096     behaviour_event(mons, ME_ANNOY, &you);
1097
1098     enable_attack_conducts(conducts);
1099
1100     int hurted = 8 + random2(random2(4) + (random2(pow) / 6)
1101                    + (random2(pow) / 7));
1102
1103     bolt pbeam;
1104     pbeam.flavour = BEAM_AIR;
1105     hurted = mons->apply_ac(mons->beam_resists(pbeam, hurted, false));
1106
1107     mons->hurt(&you, hurted);
1108     if (mons->alive())
1109         print_wounds(mons);
1110
1111     return SPRET_SUCCESS;
1112 }
1113
1114 // Just to avoid typing this over and over.
1115 // Returns true if monster died. -- bwr
1116 static bool _player_hurt_monster(monster& m, int damage,
1117                                  beam_type flavour = BEAM_MISSILE)
1118 {
1119     if (damage > 0)
1120     {
1121         m.hurt(&you, damage, flavour, false);
1122
1123         if (m.alive())
1124         {
1125             print_wounds(&m);
1126             behaviour_event(&m, ME_WHACK, &you);
1127         }
1128         else
1129         {
1130             monster_die(&m, KILL_YOU, NON_MONSTER);
1131             return true;
1132         }
1133     }
1134
1135     return false;
1136 }
1137
1138 // Here begin the actual spells:
1139 static int _shatter_mon_dice(const monster *mon)
1140 {
1141     if (!mon)
1142         return 0;
1143
1144     // Removed a lot of silly monsters down here... people, just because
1145     // it says ice, rock, or iron in the name doesn't mean it's actually
1146     // made out of the substance. - bwr
1147     switch (mon->type)
1148     {
1149     // Double damage to stone, metal and crystal.
1150     case MONS_EARTH_ELEMENTAL:
1151     case MONS_CLAY_GOLEM:
1152     case MONS_STONE_GOLEM:
1153     case MONS_STATUE:
1154     case MONS_GARGOYLE:
1155     case MONS_IRON_ELEMENTAL:
1156     case MONS_IRON_GOLEM:
1157     case MONS_METAL_GARGOYLE:
1158     case MONS_CRYSTAL_GOLEM:
1159     case MONS_SILVER_STATUE:
1160     case MONS_ORANGE_STATUE:
1161     case MONS_ROXANNE:
1162         return 6;
1163
1164     // 1/3 damage to liquids.
1165     case MONS_JELLYFISH:
1166     case MONS_WATER_ELEMENTAL:
1167         return 1;
1168
1169     default:
1170         const bool petrifying = mon->petrifying();
1171         const bool petrified = mon->petrified();
1172
1173         // Extra damage to petrifying/petrified things.
1174         // Undo the damage reduction as well; base damage is 4 : 6.
1175         if (petrifying || petrified)
1176             return petrifying ? 6 : 12;
1177         // No damage to insubstantials.
1178         else if (mon->is_insubstantial())
1179             return 0;
1180         // 1/3 damage to fliers and slimes.
1181         else if (mons_flies(mon) || mons_is_slime(mon))
1182             return 1;
1183         // 3/2 damage to ice.
1184         else if (mon->is_icy())
1185             return 4;
1186         // Double damage to bone.
1187         else if (mon->is_skeletal())
1188             return 6;
1189         // Normal damage to everything else.
1190         else
1191             return 3;
1192     }
1193 }
1194
1195 static int _shatter_monsters(coord_def where, int pow, actor *agent)
1196 {
1197     dice_def dam_dice(0, 5 + pow / 3); // Number of dice set below.
1198     monster* mon = monster_at(where);
1199
1200     if (mon == NULL || mon == agent)
1201         return 0;
1202
1203     dam_dice.num = _shatter_mon_dice(mon);
1204     int damage = max(0, dam_dice.roll() - random2(mon->armour_class()));
1205
1206     if (damage <= 0)
1207         return 0;
1208
1209     if (agent->is_player())
1210     {
1211         _player_hurt_monster(*mon, damage);
1212
1213         if (is_sanctuary(you.pos()) || is_sanctuary(mon->pos()))
1214             remove_sanctuary(true);
1215     }
1216     else
1217         mon->hurt(agent, damage);
1218
1219     return damage;
1220 }
1221
1222 static int _shatter_items(coord_def where, int pow, actor *)
1223 {
1224     UNUSED(pow);
1225
1226     int broke_stuff = 0;
1227
1228     for (stack_iterator si(where); si; ++si)
1229     {
1230         if (si->base_type == OBJ_POTIONS)
1231         {
1232             for (int j = 0; j < si->quantity; ++j)
1233             {
1234                 if (one_chance_in(10))
1235                 {
1236                     broke_stuff++;
1237                     if (!dec_mitm_item_quantity(si->index(), 1)
1238                         && is_blood_potion(*si))
1239                     {
1240                        remove_oldest_blood_potion(*si);
1241                     }
1242                 }
1243             }
1244         }
1245     }
1246
1247     if (broke_stuff)
1248     {
1249         if (player_can_hear(where))
1250             mpr("You hear glass break.", MSGCH_SOUND);
1251
1252         return 1;
1253     }
1254
1255     return 0;
1256 }
1257
1258 static int _shatter_walls(coord_def where, int pow, actor *agent)
1259 {
1260     int chance = 0;
1261
1262     // if not in-bounds then we can't really shatter it -- bwr
1263     if (!in_bounds(where))
1264         return 0;
1265
1266     if (env.markers.property_at(where, MAT_ANY, "veto_shatter") == "veto")
1267         return 0;
1268
1269     const dungeon_feature_type grid = grd(where);
1270
1271     switch (grid)
1272     {
1273     case DNGN_CLOSED_DOOR:
1274     case DNGN_RUNED_DOOR:
1275     case DNGN_OPEN_DOOR:
1276     case DNGN_SEALED_DOOR:
1277         if (you.see_cell(where))
1278             mpr("A door shatters!");
1279         chance = 100;
1280         break;
1281
1282     case DNGN_GRATE:
1283         if (you.see_cell(where))
1284             mpr("An iron grate is ripped into pieces!");
1285         chance = 100;
1286         break;
1287
1288     case DNGN_METAL_WALL:
1289         chance = pow / 10;
1290         break;
1291
1292     case DNGN_ORCISH_IDOL:
1293     case DNGN_GRANITE_STATUE:
1294         chance = 50;
1295         break;
1296
1297     case DNGN_CLEAR_STONE_WALL:
1298     case DNGN_STONE_WALL:
1299         chance = pow / 6;
1300         break;
1301
1302     case DNGN_CLEAR_ROCK_WALL:
1303     case DNGN_ROCK_WALL:
1304     case DNGN_SLIMY_WALL:
1305         chance = pow / 4;
1306         break;
1307
1308     case DNGN_GREEN_CRYSTAL_WALL:
1309         chance = 50;
1310         break;
1311
1312     case DNGN_TREE:
1313     case DNGN_MANGROVE:
1314         chance = 33;
1315         break;
1316
1317     default:
1318         break;
1319     }
1320
1321     if (x_chance_in_y(chance, 100))
1322     {
1323         noisy(30, where);
1324
1325         nuke_wall(where);
1326
1327         if (agent->is_player() && grid == DNGN_ORCISH_IDOL)
1328             did_god_conduct(DID_DESTROY_ORCISH_IDOL, 8);
1329         if (agent->is_player() && feat_is_tree(grid))
1330             did_god_conduct(DID_KILL_PLANT, 1);
1331
1332         return 1;
1333     }
1334
1335     return 0;
1336 }
1337
1338 static bool _shatterable(const actor *act)
1339 {
1340     if (act->is_player())
1341         return true; // no player ghostlies... at least user-controllable ones
1342     return _shatter_mon_dice(act->as_monster());
1343 }
1344
1345 spret_type cast_shatter(int pow, bool fail)
1346 {
1347     {
1348         int r_min = 3 + you.skill(SK_EARTH_MAGIC) / 5;
1349         targetter_los hitfunc(&you, LOS_ARENA, r_min, min(r_min + 1, 8));
1350         if (stop_attack_prompt(hitfunc, "harm", _shatterable))
1351             return SPRET_ABORT;
1352     }
1353
1354     fail_check();
1355     const bool silence = silenced(you.pos());
1356
1357     if (silence)
1358         mpr("The dungeon shakes!");
1359     else
1360     {
1361         noisy(30, you.pos());
1362         mpr("The dungeon rumbles!", MSGCH_SOUND);
1363     }
1364
1365     int rad = 3 + you.skill_rdiv(SK_EARTH_MAGIC, 1, 5);
1366
1367     int dest = 0;
1368     for (distance_iterator di(you.pos(), true, true, rad); di; ++di)
1369     {
1370         // goes from the center out, so newly dug walls recurse
1371         if (!cell_see_cell(you.pos(), *di, LOS_SOLID))
1372             continue;
1373
1374         _shatter_items(*di, pow, &you);
1375         _shatter_monsters(*di, pow, &you);
1376         dest += _shatter_walls(*di, pow, &you);
1377     }
1378
1379     if (dest && !silence)
1380         mpr("Ka-crash!", MSGCH_SOUND);
1381
1382     return SPRET_SUCCESS;
1383 }
1384
1385 static int _shatter_player_dice()
1386 {
1387     if (you.petrified())
1388         return 12; // reduced later
1389     else if (you.petrifying())
1390         return 6;  // reduced later
1391     // Same order as for monsters -- petrified flyers get hit hard, skeletal
1392     // flyers get no extra damage.
1393     else if (you.airborne())
1394         return 1;
1395     else if (you.form == TRAN_STATUE)
1396         return 6;
1397     else if (you.form == TRAN_ICE_BEAST)
1398         return 4;
1399     else
1400         return 3;
1401 }
1402
1403 static int _shatter_player(int pow, actor *wielder, bool devastator = false)
1404 {
1405     if (wielder->is_player())
1406         return 0;
1407
1408     dice_def dam_dice(_shatter_player_dice(), 5 + pow / 3);
1409
1410     int damage = max(0, dam_dice.roll() - random2(you.armour_class()));
1411
1412     if (damage > 0)
1413     {
1414         mpr(damage > 15 ? "You shudder from the earth-shattering force."
1415                         : "You shudder.");
1416         if (devastator)
1417             ouch(damage, wielder->mindex(), KILLED_BY_MONSTER);
1418         else
1419             ouch(damage, wielder->mindex(), KILLED_BY_BEAM, "by Shatter");
1420     }
1421
1422     return damage;
1423 }
1424
1425 bool mons_shatter(monster* caster, bool actual)
1426 {
1427     const bool silence = silenced(caster->pos());
1428     int foes = 0;
1429
1430     if (actual)
1431     {
1432         if (silence)
1433             mprf("The dungeon shakes around %s!",
1434                  caster->name(DESC_THE).c_str());
1435         else
1436         {
1437             noisy(30, caster->pos(), caster->mindex());
1438             mprf(MSGCH_SOUND, "The dungeon rumbles around %s!",
1439                  caster->name(DESC_THE).c_str());
1440         }
1441     }
1442
1443     int pow = 5 + div_rand_round(caster->hit_dice * 9, 2);
1444     int rad = 3 + div_rand_round(caster->hit_dice, 5);
1445
1446     int dest = 0;
1447     for (distance_iterator di(caster->pos(), true, true, rad); di; ++di)
1448     {
1449         // goes from the center out, so newly dug walls recurse
1450         if (!cell_see_cell(caster->pos(), *di, LOS_SOLID))
1451             continue;
1452
1453         if (actual)
1454         {
1455             _shatter_items(*di, pow, caster);
1456             _shatter_monsters(*di, pow, caster);
1457             if (*di == you.pos())
1458                 _shatter_player(pow, caster);
1459             dest += _shatter_walls(*di, pow, caster);
1460         }
1461         else
1462         {
1463             if (you.pos() == *di)
1464                 foes -= _shatter_player_dice();
1465             if (const monster *victim = monster_at(*di))
1466             {
1467                 dprf("[%s]", victim->name(DESC_PLAIN, true).c_str());
1468                 foes += _shatter_mon_dice(victim)
1469                      * (victim->wont_attack() ? -1 : 1);
1470             }
1471         }
1472     }
1473
1474     if (dest && !silence)
1475         mpr("Ka-crash!", MSGCH_SOUND);
1476
1477     if (!caster->wont_attack())
1478         foes *= -1;
1479
1480     if (!actual)
1481         dprf("Shatter foe HD: %d", foes);
1482
1483     return (foes > 0); // doesn't matter if actual
1484 }
1485
1486 void shillelagh(actor *wielder, coord_def where, int pow)
1487 {
1488     bolt beam;
1489     beam.name = "shillelagh";
1490     beam.flavour = BEAM_VISUAL;
1491     beam.set_agent(wielder);
1492     beam.colour = BROWN;
1493     beam.glyph = dchar_glyph(DCHAR_EXPLOSION);
1494     beam.range = 1;
1495     beam.ex_size = 1;
1496     beam.is_explosion = true;
1497     beam.source = wielder->pos();
1498     beam.target = where;
1499     beam.hit = AUTOMATIC_HIT;
1500     beam.loudness = 7;
1501     beam.explode();
1502
1503     counted_monster_list affected_monsters;
1504     for (adjacent_iterator ai(where, false); ai; ++ai)
1505     {
1506         monster *mon = monster_at(*ai);
1507         if (!mon || !mon->alive() || mon->submerged()
1508             || mon->is_insubstantial() || !you.can_see(mon)
1509             || mon == wielder)
1510         {
1511             continue;
1512         }
1513         affected_monsters.add(mon);
1514     }
1515     if (!affected_monsters.empty())
1516     {
1517         const string message =
1518             make_stringf("%s shudder%s.",
1519                          affected_monsters.describe().c_str(),
1520                          affected_monsters.count() == 1? "s" : "");
1521         if (strwidth(message) < get_number_of_cols() - 2)
1522             mpr(message.c_str());
1523         else
1524             mpr("There is a shattering impact!");
1525     }
1526
1527     // need to do this again to do the actual damage
1528     for (adjacent_iterator ai(where, false); ai; ++ai)
1529         _shatter_monsters(*ai, pow * 3 / 2, wielder);
1530
1531     if ((you.pos() - wielder->pos()).abs() <= 2 && in_bounds(you.pos()))
1532         _shatter_player(pow, wielder, true);
1533 }
1534
1535 static int _ignite_poison_affect_item(item_def& item, bool in_inv)
1536 {
1537     int strength = 0;
1538
1539     // Poison branding becomes fire branding.
1540     // don't affect non-wielded weapons, they don't start dripping poison until
1541     // you wield them. -doy
1542     if (&item == you.weapon()
1543         && you.duration[DUR_WEAPON_BRAND]
1544         && get_weapon_brand(item) == SPWPN_VENOM)
1545     {
1546         if (set_item_ego_type(item, OBJ_WEAPONS, SPWPN_FLAMING))
1547         {
1548             mprf("%s bursts into flame!",
1549                  item.name(DESC_YOUR).c_str());
1550
1551             you.wield_change = true;
1552
1553             int increase = 1 + you.duration[DUR_WEAPON_BRAND]
1554                                /(2 * BASELINE_DELAY);
1555
1556             you.increase_duration(DUR_WEAPON_BRAND, increase, 80);
1557         }
1558
1559         // and don't destroy it
1560         return 0;
1561     }
1562     else if (item.base_type == OBJ_MISSILES && item.special == SPMSL_POISONED)
1563     {
1564         // Burn poison ammo.
1565         strength = item.quantity;
1566     }
1567     else if (item.base_type == OBJ_POTIONS)
1568     {
1569         // Burn poisonous potions.
1570         switch (item.sub_type)
1571         {
1572         case POT_STRONG_POISON:
1573             strength = 20 * item.quantity;
1574             break;
1575         case POT_DEGENERATION:
1576         case POT_POISON:
1577             strength = 10 * item.quantity;
1578             break;
1579         default:
1580             break;
1581         }
1582     }
1583     else if (item.base_type == OBJ_CORPSES &&
1584              item.sub_type == CORPSE_BODY &&
1585              chunk_is_poisonous(mons_corpse_effect(item.mon_type)))
1586     {
1587         strength = mons_weight(item.mon_type) / 25;
1588     }
1589     else if (item.base_type == OBJ_FOOD &&
1590              item.sub_type == FOOD_CHUNK &&
1591              chunk_is_poisonous(mons_corpse_effect(item.mon_type)))
1592     {
1593         strength += 30 * item.quantity;
1594     }
1595
1596     if (strength)
1597     {
1598         if (in_inv)
1599         {
1600             if (item.base_type == OBJ_POTIONS)
1601             {
1602                 mprf("%s explode%s!",
1603                      item.name(DESC_PLAIN).c_str(),
1604                      item.quantity == 1 ? "s" : "");
1605             }
1606             else
1607             {
1608                 mprf("Your %s burn%s!",
1609                      item.name(DESC_PLAIN).c_str(),
1610                      item.quantity == 1 ? "s" : "");
1611             }
1612         }
1613
1614         if (item.base_type == OBJ_CORPSES
1615             && item.sub_type == CORPSE_BODY
1616             && mons_skeleton(item.mon_type))
1617         {
1618             turn_corpse_into_skeleton(item);
1619         }
1620         else
1621         {
1622             if (in_inv && &item == you.weapon())
1623             {
1624                 unwield_item();
1625                 canned_msg(MSG_EMPTY_HANDED_NOW);
1626             }
1627             item_was_destroyed(item);
1628             if (in_inv)
1629                 destroy_item(item);
1630             else
1631                 destroy_item(item.index());
1632         }
1633     }
1634
1635     return strength;
1636 }
1637
1638 static int _ignite_poison_objects(coord_def where, int pow, int, actor *actor)
1639 {
1640     UNUSED(pow);
1641
1642     int strength = 0;
1643
1644     for (stack_iterator si(where); si; ++si)
1645         strength += _ignite_poison_affect_item(*si, false);
1646
1647     if (strength > 0)
1648     {
1649         place_cloud(CLOUD_FIRE, where,
1650                     strength + roll_dice(3, strength / 4), actor);
1651     }
1652
1653     return strength;
1654 }
1655
1656 static int _ignite_poison_clouds(coord_def where, int pow, int, actor *actor)
1657 {
1658     UNUSED(pow);
1659
1660     const int i = env.cgrid(where);
1661     if (i != EMPTY_CLOUD)
1662     {
1663         cloud_struct& cloud = env.cloud[i];
1664
1665         if (cloud.type == CLOUD_MEPHITIC)
1666         {
1667             cloud.decay /= 2;
1668
1669             if (cloud.decay < 1)
1670                 cloud.decay = 1;
1671         }
1672         else if (cloud.type != CLOUD_POISON)
1673             return false;
1674
1675         cloud.type = CLOUD_FIRE;
1676         cloud.whose = actor->kill_alignment();
1677         cloud.killer = actor->is_player() ? KILL_YOU_MISSILE : KILL_MON_MISSILE;
1678         cloud.source = actor->mid;
1679         return true;
1680     }
1681
1682     return false;
1683 }
1684
1685 static int _ignite_poison_monsters(coord_def where, int pow, int, actor *actor)
1686 {
1687     bolt beam;
1688     beam.flavour = BEAM_FIRE;   // This is dumb, only used for adjust!
1689
1690     dice_def dam_dice(0, 5 + pow/7);  // Dice added below if applicable.
1691
1692     // If a monster casts Ignite Poison, it can't hit itself.
1693     // This doesn't apply to the other functions: it can ignite
1694     // clouds or items where it's standing!
1695
1696     monster* mon = monster_at(where);
1697     if (mon == NULL || mon == actor)
1698         return 0;
1699
1700     // Monsters which have poison corpses or poisonous attacks.
1701     if (mons_is_poisoner(mon))
1702         dam_dice.num = 3;
1703
1704     // Monsters which are poisoned:
1705     int strength = 0;
1706
1707     // First check for player poison.
1708     mon_enchant ench = mon->get_ench(ENCH_POISON);
1709     if (ench.ench != ENCH_NONE)
1710         strength += ench.degree;
1711
1712     // Strength is now the sum of both poison types
1713     // (although only one should actually be present at a given time).
1714     dam_dice.num += strength;
1715
1716     int damage = dam_dice.roll();
1717     if (damage > 0)
1718     {
1719         damage = mons_adjust_flavoured(mon, beam, damage);
1720         simple_monster_message(mon, " seems to burn from within!");
1721
1722         dprf("Dice: %dd%d; Damage: %d", dam_dice.num, dam_dice.size, damage);
1723
1724         mon->hurt(actor, damage);
1725
1726         if (mon->alive())
1727         {
1728             behaviour_event(mon, ME_WHACK, actor);
1729
1730             // Monster survived, remove any poison.
1731             mon->del_ench(ENCH_POISON);
1732             print_wounds(mon);
1733         }
1734         else
1735         {
1736             monster_die(mon,
1737                         actor->is_player() ? KILL_YOU : KILL_MON,
1738                         actor->mindex());
1739         }
1740
1741         return 1;
1742     }
1743
1744     return 0;
1745 }
1746
1747 // The self effects of Ignite Poison are beautiful and
1748 // shouldn't be thrown out. Let's save them for a monster
1749 // version of the spell!
1750
1751 static int _ignite_poison_player(coord_def where, int pow, int, actor *actor)
1752 {
1753     if (actor->is_player() || where != you.pos())
1754         return 0;
1755
1756     int totalstrength = 0;
1757
1758     for (int i = 0; i < ENDOFPACK; ++i)
1759     {
1760         item_def& item = you.inv[i];
1761         if (!item.defined())
1762             continue;
1763
1764         totalstrength += _ignite_poison_affect_item(item, true);
1765     }
1766
1767     if (totalstrength)
1768     {
1769         place_cloud(
1770             CLOUD_FIRE, you.pos(),
1771             random2(totalstrength / 4 + 1) + random2(totalstrength / 4 + 1) +
1772             random2(totalstrength / 4 + 1) + random2(totalstrength / 4 + 1) + 1,
1773             actor);
1774     }
1775
1776     int damage = 0;
1777     // Player is poisonous.
1778     if (player_mutation_level(MUT_SPIT_POISON)
1779         || player_mutation_level(MUT_STINGER)
1780         || you.form == TRAN_SPIDER // poison attack
1781         || (!form_changed_physiology()
1782             && (you.species == SP_GREEN_DRACONIAN       // poison breath
1783                 || you.species == SP_KOBOLD             // poisonous corpse
1784                 || you.species == SP_NAGA)))            // spit poison
1785     {
1786         damage = roll_dice(3, 5 + pow / 7);
1787     }
1788
1789     // Player is poisoned.
1790     damage += roll_dice(you.duration[DUR_POISONING], 6);
1791
1792     if (damage)
1793     {
1794         const int resist = player_res_fire();
1795         if (resist > 0)
1796         {
1797             mpr("You feel like your blood is boiling!");
1798             damage /= 3;
1799         }
1800         else if (resist < 0)
1801         {
1802             mpr("The poison in your system burns terribly!");
1803             damage *= 3;
1804         }
1805         else
1806             mpr("The poison in your system burns!");
1807
1808         ouch(damage, actor->as_monster()->mindex(), KILLED_BY_MONSTER,
1809              actor->as_monster()->name(DESC_A).c_str());
1810
1811         if (you.duration[DUR_POISONING] > 0)
1812         {
1813             mpr("You feel that the poison has left your system.");
1814             you.duration[DUR_POISONING] = 0;
1815         }
1816     }
1817
1818     if (damage || totalstrength)
1819         return 1;
1820     else
1821         return 0;
1822 }
1823
1824 static bool maybe_abort_ignite()
1825 {
1826     // Fire cloud immunity.
1827     if (you.duration[DUR_FIRE_SHIELD] || you.mutation[MUT_IGNITE_BLOOD])
1828         return false;
1829
1830     string prompt = "You are standing ";
1831
1832     const int i = env.cgrid(you.pos());
1833     if (i != EMPTY_CLOUD)
1834     {
1835         cloud_struct& cloud = env.cloud[i];
1836
1837         if (cloud.type == CLOUD_MEPHITIC || cloud.type == CLOUD_POISON)
1838         {
1839             prompt += "in a cloud of ";
1840             prompt += cloud_type_name(cloud.type, true);
1841             prompt += "! Ignite poison anyway?";
1842             return (!yesno(prompt.c_str(), false, 'n'));
1843         }
1844     }
1845
1846     // Now check for items at player position.
1847     item_def item;
1848     for (stack_iterator si(you.pos()); si; ++si)
1849     {
1850         item = *si;
1851
1852         if (item.base_type == OBJ_MISSILES && item.special == SPMSL_POISONED)
1853         {
1854             prompt += "over ";
1855             prompt += (item.quantity == 1 ? "a " : "") + (item.name(DESC_PLAIN));
1856             prompt += "! Ignite poison anyway?";
1857             return (!yesno(prompt.c_str(), false, 'n'));
1858         }
1859         else if (item.base_type == OBJ_POTIONS && item_type_known(item))
1860         {
1861             switch (item.sub_type)
1862             {
1863             case POT_STRONG_POISON:
1864             case POT_DEGENERATION:
1865             case POT_POISON:
1866                 prompt += "over ";
1867                 prompt += (item.quantity == 1 ? "a " : "") + (item.name(DESC_PLAIN));
1868                 prompt += "! Ignite poison anyway?";
1869                 return (!yesno(prompt.c_str(), false, 'n'));
1870             default:
1871                 break;
1872             }
1873         }
1874         else if (item.base_type == OBJ_CORPSES
1875                  && item.sub_type == CORPSE_BODY
1876                  && chunk_is_poisonous(mons_corpse_effect(item.mon_type)))
1877         {
1878             prompt += "over ";
1879             prompt += (item.quantity == 1 ? "a " : "") + (item.name(DESC_PLAIN));
1880             prompt += "! Ignite poison anyway?";
1881             return (!yesno(prompt.c_str(), false, 'n'));
1882         }
1883         else if (item.base_type == OBJ_FOOD &&
1884                  item.sub_type == FOOD_CHUNK &&
1885                  chunk_is_poisonous(mons_corpse_effect(item.mon_type)))
1886         {
1887             prompt += "over ";
1888             prompt += (item.quantity == 1 ? "a " : "") + (item.name(DESC_PLAIN));
1889             prompt += "! Ignite poison anyway?";
1890             return (!yesno(prompt.c_str(), false, 'n'));
1891         }
1892     }
1893
1894     return false;
1895 }
1896
1897 spret_type cast_ignite_poison(int pow, bool fail)
1898 {
1899     if (maybe_abort_ignite())
1900     {
1901         canned_msg(MSG_OK);
1902         return SPRET_ABORT;
1903     }
1904
1905     fail_check();
1906     targetter_los hitfunc(&you, LOS_NO_TRANS);
1907     flash_view(RED, &hitfunc);
1908
1909     mpr("You ignite the poison in your surroundings!");
1910
1911     apply_area_visible(_ignite_poison_clouds, pow, &you);
1912     apply_area_visible(_ignite_poison_objects, pow, &you);
1913     apply_area_visible(_ignite_poison_monsters, pow, &you);
1914 // Not currently relevant - nothing will ever happen as long as
1915 // the actor is &you.
1916     apply_area_visible(_ignite_poison_player, pow, &you);
1917
1918 #ifndef USE_TILE_LOCAL
1919     delay(100); // show a brief flash
1920 #endif
1921     flash_view(0);
1922     return SPRET_SUCCESS;
1923 }
1924
1925 static int _discharge_monsters(coord_def where, int pow, int, actor *)
1926 {
1927     monster* mons = monster_at(where);
1928     int damage = 0;
1929
1930     bolt beam;
1931     beam.flavour = BEAM_ELECTRICITY; // used for mons_adjust_flavoured
1932
1933     dprf("Static discharge on (%d,%d) pow: %d", where.x, where.y, pow);
1934     if (where == you.pos())
1935     {
1936         mpr("You are struck by lightning.");
1937         damage = 1 + random2(3 + pow / 15);
1938         dprf("You: static discharge damage: %d", damage);
1939         damage = check_your_resists(damage, BEAM_ELECTRICITY,
1940                                     "static discharge");
1941         ouch(damage, NON_MONSTER, KILLED_BY_WILD_MAGIC, "static electricity");
1942     }
1943     else if (mons == NULL)
1944         return 0;
1945     else if (mons->res_elec() > 0)
1946         return 0;
1947     else
1948     {
1949         damage = 3 + random2(5 + pow / 10 + (random2(pow) / 10));
1950         dprf("%s: static discharge damage: %d",
1951              mons->name(DESC_PLAIN, true).c_str(), damage);
1952         damage = mons_adjust_flavoured(mons, beam, damage);
1953
1954         if (damage)
1955         {
1956             mprf("%s is struck by lightning.",
1957                  mons->name(DESC_THE).c_str());
1958             _player_hurt_monster(*mons, damage);
1959         }
1960     }
1961
1962     // Recursion to give us chain-lightning -- bwr
1963     // Low power slight chance added for low power characters -- bwr
1964     if ((pow >= 10 && !one_chance_in(4)) || (pow >= 3 && one_chance_in(10)))
1965     {
1966         mpr("The lightning arcs!");
1967         pow /= (coinflip() ? 2 : 3);
1968         damage += apply_random_around_square(_discharge_monsters, where,
1969                                              true, pow, 1);
1970     }
1971     else if (damage > 0)
1972     {
1973         // Only printed if we did damage, so that the messages in
1974         // cast_discharge() are clean. -- bwr
1975         mpr("The lightning grounds out.");
1976     }
1977
1978     return damage;
1979 }
1980
1981 static bool _safe_discharge(coord_def where, vector<const monster *> &exclude)
1982 {
1983     for (adjacent_iterator ai(where); ai; ++ai)
1984     {
1985         const monster *mon = monster_at(*ai);
1986         if (!mon)
1987             continue;
1988
1989         if (find(exclude.begin(), exclude.end(), mon) == exclude.end())
1990         {
1991             // Harmless to these monsters, so don't prompt about hitting them
1992             if (mon->res_elec() > 0)
1993                 continue;
1994
1995             if (stop_attack_prompt(mon, false, where))
1996                 return false;
1997
1998             exclude.push_back(mon);
1999             if (!_safe_discharge(mon->pos(), exclude))
2000                 return false;
2001         }
2002     }
2003
2004     return true;
2005 }
2006
2007 spret_type cast_discharge(int pow, bool fail)
2008 {
2009     fail_check();
2010
2011     vector<const monster *> exclude;
2012     if (!_safe_discharge(you.pos(), exclude))
2013         return SPRET_ABORT;
2014
2015     const int num_targs = 1 + random2(random_range(1, 3) + pow / 20);
2016     const int dam = apply_random_around_square(_discharge_monsters, you.pos(),
2017                                                true, pow, num_targs);
2018
2019     dprf("Arcs: %d Damage: %d", num_targs, dam);
2020
2021     if (dam == 0)
2022     {
2023         if (coinflip())
2024             mpr("The air around you crackles with electrical energy.");
2025         else
2026         {
2027             const bool plural = coinflip();
2028             mprf("%s blue arc%s ground%s harmlessly %s you.",
2029                  plural ? "Some" : "A",
2030                  plural ? "s" : "",
2031                  plural ? " themselves" : "s itself",
2032                  plural ? "around" : (coinflip() ? "beside" :
2033                                       coinflip() ? "behind" : "before"));
2034         }
2035     }
2036     return SPRET_SUCCESS;
2037 }
2038
2039 static int _disperse_monster(monster* mon, int pow)
2040 {
2041     if (!mon)
2042         return 0;
2043
2044     if (mon->no_tele())
2045         return 0;
2046
2047     if (mon->check_res_magic(pow) > 0)
2048     {
2049         monster_blink(mon);
2050         return 1;
2051     }
2052     else
2053     {
2054         monster_teleport(mon, true);
2055         return 1;
2056     }
2057
2058     return 0;
2059 }
2060
2061 spret_type cast_dispersal(int pow, bool fail)
2062 {
2063     fail_check();
2064     if (!apply_monsters_around_square(_disperse_monster, you.pos(), pow))
2065         mpr("The air shimmers briefly around you.");
2066
2067     return SPRET_SUCCESS;
2068 }
2069
2070 bool setup_fragmentation_beam(bolt &beam, int pow, const actor *caster,
2071                               const coord_def target, bool allow_random,
2072                               bool get_max_distance, bool quiet,
2073                               const char **what, bool &destroy_wall, bool &hole)
2074 {
2075     beam.flavour     = BEAM_FRAG;
2076     beam.glyph       = dchar_glyph(DCHAR_FIRED_BURST);
2077     beam.beam_source = caster->mindex();
2078     beam.thrower     = caster->is_player() ? KILL_YOU : KILL_MON;
2079     beam.ex_size     = 1;
2080     beam.source      = you.pos();
2081     beam.hit         = AUTOMATIC_HIT;
2082
2083     beam.source_name = caster->name(DESC_PLAIN, true);
2084     beam.aux_source = "by Lee's Rapid Deconstruction"; // for direct attack
2085
2086     beam.target = target;
2087
2088     // Number of dice vary... 3 is easy/common, but it can get as high as 6.
2089     beam.damage = dice_def(0, 5 + pow / 5);
2090
2091     monster* mon = monster_at(target);
2092     const dungeon_feature_type grid = grd(target);
2093
2094     if (target == you.pos())
2095     {
2096         const bool petrifying = you.petrifying();
2097         const bool petrified = you.petrified();
2098
2099         if (you.form == TRAN_STATUE)
2100         {
2101             beam.ex_size    = 2;
2102             beam.name       = "blast of rock fragments";
2103             beam.colour     = BROWN;
2104             beam.damage.num = 3;
2105             return true;
2106         }
2107         else if (petrifying || petrified)
2108         {
2109             beam.ex_size    = petrifying ? 1 : 2;
2110             beam.name       = "blast of petrified fragments";
2111             beam.colour     = mons_class_colour(player_mons(true));
2112             beam.damage.num = petrifying ? 2 : 3;
2113             return true;
2114         }
2115         else if (you.form == TRAN_ICE_BEAST) // blast of ice
2116         {
2117             beam.name       = "icy blast";
2118             beam.colour     = WHITE;
2119             beam.damage.num = 2;
2120             beam.flavour    = BEAM_ICE;
2121             return true;
2122         }
2123
2124         goto do_terrain;
2125     }
2126
2127     // Set up the explosion if there's a visible monster.
2128     if (mon && (caster->is_monster() || (you.can_see(mon))))
2129     {
2130         switch (mon->type)
2131         {
2132         case MONS_TOENAIL_GOLEM:
2133             beam.damage.num = 2;
2134             beam.name       = "blast of toenail fragments";
2135             beam.colour     = RED;
2136             break;
2137
2138         case MONS_IRON_ELEMENTAL:
2139         case MONS_IRON_GOLEM:
2140         case MONS_METAL_GARGOYLE:
2141             beam.name       = "blast of metal fragments";
2142             beam.colour     = CYAN;
2143             beam.damage.num = 4;
2144             break;
2145
2146         case MONS_EARTH_ELEMENTAL:
2147         case MONS_CLAY_GOLEM:
2148         case MONS_STONE_GOLEM:
2149         case MONS_STATUE:
2150         case MONS_GARGOYLE:
2151             beam.ex_size    = 2;
2152             beam.name       = "blast of rock fragments";
2153             beam.colour     = BROWN;
2154             beam.damage.num = 3;
2155             break;
2156
2157         case MONS_SILVER_STATUE:
2158         case MONS_ORANGE_STATUE:
2159         case MONS_CRYSTAL_GOLEM:
2160         case MONS_ROXANNE:
2161             beam.ex_size    = 2;
2162             beam.damage.num = 4;
2163             if (mon->type == MONS_SILVER_STATUE)
2164             {
2165                 beam.name       = "blast of silver fragments";
2166                 beam.colour     = WHITE;
2167             }
2168             else if (mon->type == MONS_ORANGE_STATUE)
2169             {
2170                 beam.name       = "blast of orange crystal shards";
2171                 beam.colour     = LIGHTRED;
2172             }
2173             else if (mon->type == MONS_CRYSTAL_GOLEM)
2174             {
2175                 beam.name       = "blast of crystal shards";
2176                 beam.colour     = GREEN;
2177             }
2178             else
2179             {
2180                 beam.name       = "blast of sapphire shards";
2181                 beam.colour     = BLUE;
2182             }
2183             break;
2184
2185         default:
2186             const bool petrifying = mon->petrifying();
2187             const bool petrified = mon->petrified();
2188
2189             // Petrifying or petrified monsters can be exploded.
2190             if (petrifying || petrified)
2191             {
2192                 beam.ex_size    = petrifying ? 1 : 2;
2193                 beam.name       = "blast of petrified fragments";
2194                 beam.colour     = mons_class_colour(mon->type);
2195                 beam.damage.num = petrifying ? 2 : 3;
2196                 break;
2197             }
2198             else if (mon->is_icy()) // blast of ice
2199             {
2200                 beam.name       = "icy blast";
2201                 beam.colour     = WHITE;
2202                 beam.damage.num = 2;
2203                 beam.flavour    = BEAM_ICE;
2204                 break;
2205             }
2206             else if (mon->is_skeletal()) // blast of bone
2207             {
2208                 beam.name   = "blast of bone shards";
2209                 beam.colour = LIGHTGREY;
2210                 beam.damage.num = 2;
2211                 break;
2212             }
2213             // Targeted monster not shatterable, try the terrain instead.
2214             goto do_terrain;
2215         }
2216
2217         beam.aux_source = beam.name;
2218
2219         // Got a target, let's blow it up.
2220         return true;
2221     }
2222
2223   do_terrain:
2224
2225     if (env.markers.property_at(target, MAT_ANY,
2226                                 "veto_fragmentation") == "veto")
2227     {
2228         if (caster->is_player() && !quiet)
2229         {
2230             mprf("%s seems to be unnaturally hard.",
2231                  feature_description_at(target, false, DESC_THE, false).c_str());
2232         }
2233         return false;
2234     }
2235
2236     switch (grid)
2237     {
2238     // Stone and rock terrain
2239     case DNGN_ORCISH_IDOL:
2240         if (!caster->is_player())
2241             return false; // don't let monsters blow up orcish idols
2242
2243         if (what && (*what == NULL))
2244             *what = "stone idol";
2245         // fall-through
2246     case DNGN_ROCK_WALL:
2247     case DNGN_SLIMY_WALL:
2248     case DNGN_STONE_WALL:
2249     case DNGN_CLEAR_ROCK_WALL:
2250     case DNGN_CLEAR_STONE_WALL:
2251         if (what && (*what == NULL))
2252             *what = "wall";
2253         // fall-through
2254     case DNGN_GRANITE_STATUE:   // normal rock -- big explosion
2255         if (what && (*what == NULL))
2256             *what = "statue";
2257
2258         beam.name       = "blast of rock fragments";
2259         beam.damage.num = 3;
2260
2261         if ((grid == DNGN_ORCISH_IDOL
2262              || grid == DNGN_GRANITE_STATUE
2263              || grid == DNGN_GRATE
2264              || pow >= 40 && grid == DNGN_ROCK_WALL
2265                  && (allow_random && one_chance_in(3)
2266                      || !allow_random && get_max_distance)
2267              || pow >= 40 && grid == DNGN_CLEAR_ROCK_WALL
2268                  && (allow_random && one_chance_in(3)
2269                      || !allow_random && get_max_distance)
2270              || pow >= 60 && grid == DNGN_STONE_WALL
2271                  && (allow_random && one_chance_in(10)
2272                      || !allow_random && get_max_distance)
2273              || pow >= 60 && grid == DNGN_CLEAR_STONE_WALL
2274                  && (allow_random && one_chance_in(10)
2275                      || !allow_random && get_max_distance)))
2276         {
2277             beam.ex_size = 2;
2278             destroy_wall = true;
2279         }
2280         break;
2281
2282     // Metal -- small but nasty explosion
2283     case DNGN_METAL_WALL:
2284         if (what)
2285             *what = "metal wall";
2286         // fall through
2287     case DNGN_GRATE:
2288         if (what && (*what == NULL))
2289             *what = "iron grate";
2290         beam.name       = "blast of metal fragments";
2291         beam.damage.num = 4;
2292
2293         if (pow >= 80 && (allow_random && x_chance_in_y(pow / 5, 500)
2294                           || !allow_random && get_max_distance)
2295             || grid == DNGN_GRATE)
2296         {
2297             beam.damage.num += 2;
2298             destroy_wall     = true;
2299         }
2300         break;
2301
2302     // Crystal
2303     case DNGN_GREEN_CRYSTAL_WALL:       // crystal -- large & nasty explosion
2304         if (what)
2305             *what = "crystal wall";
2306         beam.ex_size    = 2;
2307         beam.name       = "blast of crystal shards";
2308         beam.damage.num = 4;
2309
2310         if (allow_random && coinflip()
2311             || !allow_random && get_max_distance)
2312         {
2313             beam.ex_size = 3;
2314             destroy_wall = true;
2315         }
2316         break;
2317
2318     // Stone doors and arches
2319
2320     case DNGN_OPEN_DOOR:
2321     case DNGN_CLOSED_DOOR:
2322     case DNGN_RUNED_DOOR:
2323     case DNGN_SEALED_DOOR:
2324         // Doors always blow up, stone arches never do (would cause problems).
2325         if (what)
2326             *what = "door";
2327         destroy_wall = true;
2328
2329         // fall-through
2330     case DNGN_STONE_ARCH:          // Floor -- small explosion.
2331         if (what && (*what == NULL))
2332             *what = "stone arch";
2333         hole            = false;  // to hit monsters standing on doors
2334         beam.name       = "blast of rock fragments";
2335         beam.damage.num = 2;
2336         break;
2337
2338     default:
2339         // Couldn't find a monster or wall to shatter - abort casting!
2340         if (caster->is_player() && !quiet)
2341             mpr("You can't deconstruct that!");
2342         return false;
2343     }
2344
2345     // If it was recoloured, use that colour instead.
2346     if (env.grid_colours(target))
2347         beam.colour = env.grid_colours(target);
2348     else
2349     {
2350         beam.colour = element_colour(get_feature_def(grid).colour,
2351                                      false, target);
2352     }
2353
2354     beam.aux_source = beam.name;
2355
2356     return true;
2357 }
2358
2359 spret_type cast_fragmentation(int pow, const actor *caster,
2360                               const coord_def target, bool fail)
2361 {
2362     bool destroy_wall = false;
2363     bool hole         = true;
2364     const char *what  = NULL;
2365     const dungeon_feature_type grid = grd(target);
2366
2367     bolt beam;
2368
2369     if (!setup_fragmentation_beam(beam, pow, caster, target, true, false,
2370                                   false, &what, destroy_wall, hole))
2371     {
2372         return SPRET_ABORT;
2373     }
2374
2375     if (caster->is_player())
2376     {
2377         if (grid == DNGN_ORCISH_IDOL)
2378         {
2379             if (!yesno("Really insult Beogh by defacing this idol?",
2380                        false, 'n'))
2381             {
2382                 canned_msg(MSG_OK);
2383                 return SPRET_ABORT;
2384             }
2385         }
2386
2387         bolt tempbeam;
2388         bool temp;
2389         setup_fragmentation_beam(tempbeam, pow, caster, target, false, true,
2390                                  true, NULL, temp, temp);
2391         tempbeam.is_tracer = true;
2392         tempbeam.explode(false);
2393         if (tempbeam.beam_cancelled)
2394         {
2395             canned_msg(MSG_OK);
2396             return SPRET_ABORT;
2397         }
2398     }
2399
2400     monster* mon = monster_at(target);
2401
2402     fail_check();
2403
2404     if (what != NULL) // Terrain explodes.
2405     {
2406         if (you.see_cell(target))
2407             mprf("The %s shatters!", what);
2408         if (destroy_wall)
2409             nuke_wall(target);
2410     }
2411     else if (target == you.pos()) // You explode.
2412     {
2413         mpr("You shatter!");
2414
2415         ouch(beam.damage.roll(), caster->mindex(), KILLED_BY_BEAM,
2416              "by Lee's Rapid Deconstruction", true,
2417              caster->is_player() ? "themself"
2418                                  : caster->name(DESC_A).c_str());
2419     }
2420     else // Monster explodes.
2421     {
2422         if (you.see_cell(target))
2423             mprf("%s shatters!", mon->name(DESC_THE).c_str());
2424
2425         if ((mons_is_statue(mon->type) || mon->is_skeletal())
2426              && x_chance_in_y(pow / 5, 50)) // potential insta-kill
2427         {
2428             monster_die(mon,
2429                         caster->is_player() ? KILL_YOU
2430                                             : KILL_MON,
2431                         NON_MONSTER);
2432             beam.damage.num += 2;
2433         }
2434         else if (caster->is_player())
2435         {
2436             if (_player_hurt_monster(*mon, beam.damage.roll(),
2437                                      BEAM_DISINTEGRATION))
2438             {
2439                 beam.damage.num += 2;
2440             }
2441         }
2442         else
2443         {
2444             mon->hurt(caster, beam.damage.roll(), BEAM_DISINTEGRATION);
2445             if (!mon->alive())
2446                 beam.damage.num += 2;
2447         }
2448     }
2449
2450     beam.explode(true, hole);
2451
2452     // Monsters shouldn't be able to blow up idols,
2453     // but this check is here just in case...
2454     if (caster->is_player() && grid == DNGN_ORCISH_IDOL)
2455         did_god_conduct(DID_DESTROY_ORCISH_IDOL, 8);
2456
2457     return SPRET_SUCCESS;
2458 }
2459
2460 int wielding_rocks()
2461 {
2462     const item_def* wpn = you.weapon();
2463     if (!wpn || wpn->base_type != OBJ_MISSILES)
2464         return 0;
2465     else if (wpn->sub_type == MI_STONE)
2466         return 1;
2467     else if (wpn->sub_type == MI_LARGE_ROCK)
2468         return 2;
2469     else
2470         return 0;
2471 }
2472
2473 spret_type cast_sandblast(int pow, bolt &beam, bool fail)
2474 {
2475     zap_type zap = ZAP_SMALL_SANDBLAST;
2476     switch (wielding_rocks())
2477     {
2478     case 1:
2479         zap = ZAP_SANDBLAST;
2480         break;
2481     case 2:
2482         zap = ZAP_LARGE_SANDBLAST;
2483         break;
2484     default:
2485         break;
2486     }
2487
2488     const spret_type ret = zapping(zap, pow, beam, true, NULL, fail);
2489
2490     if (ret == SPRET_SUCCESS && zap != ZAP_SMALL_SANDBLAST)
2491         dec_inv_item_quantity(you.equip[EQ_WEAPON], 1);
2492
2493     return ret;
2494 }
2495
2496 static bool _elec_not_immune(const actor *act)
2497 {
2498     return act->res_elec() < 3;
2499 }
2500
2501 spret_type cast_thunderbolt(actor *caster, int pow, coord_def aim, bool fail)
2502 {
2503     coord_def prev;
2504     if (caster->props.exists("thunderbolt_last")
2505         && caster->props["thunderbolt_last"].get_int() + 1 == you.num_turns)
2506     {
2507         prev = caster->props["thunderbolt_aim"].get_coord();
2508     }
2509
2510     targetter_thunderbolt hitfunc(caster, spell_range(SPELL_THUNDERBOLT, pow),
2511                                   prev);
2512     hitfunc.set_aim(aim);
2513
2514     if (caster->is_player())
2515     {
2516         if (stop_attack_prompt(hitfunc, "zap", _elec_not_immune))
2517             return SPRET_ABORT;
2518     }
2519
2520     fail_check();
2521
2522     int juice = prev.origin() ? 2 * ROD_CHARGE_MULT
2523                               : caster->props["thunderbolt_mana"].get_int();
2524     bolt beam;
2525     beam.name              = "lightning";
2526     beam.aux_source        = "rod of lightning";
2527     beam.flavour           = BEAM_ELECTRICITY;
2528     beam.glyph             = dchar_glyph(DCHAR_FIRED_BURST);
2529     beam.colour            = LIGHTCYAN;
2530     beam.range             = 1;
2531     // Dodging a horizontal arc is nearly impossible: you'd have to fall prone
2532     // or jump high.
2533     beam.hit               = prev.origin() ? 10 + pow / 20 : 1000;
2534     beam.ac_rule           = AC_PROPORTIONAL;
2535     beam.set_agent(caster);
2536 #ifdef USE_TILE
2537     beam.tile_beam = -1;
2538 #endif
2539     beam.draw_delay = 0;
2540
2541     for (map<coord_def, aff_type>::const_iterator p = hitfunc.zapped.begin();
2542          p != hitfunc.zapped.end(); ++p)
2543     {
2544         if (p->second <= 0)
2545             continue;
2546
2547         beam.draw(p->first);
2548     }
2549
2550     delay(200);
2551
2552     beam.glyph = 0; // FIXME: a hack to avoid "appears out of thin air"
2553
2554     for (map<coord_def, aff_type>::const_iterator p = hitfunc.zapped.begin();
2555          p != hitfunc.zapped.end(); ++p)
2556     {
2557         if (p->second <= 0)
2558             continue;
2559
2560         // beams are incredibly spammy in debug mode
2561         if (!actor_at(p->first))
2562             continue;
2563
2564         int arc = hitfunc.arc_length[p->first.range(hitfunc.origin)];
2565         ASSERT(arc > 0);
2566         dprf("at distance %d, arc length is %d", p->first.range(hitfunc.origin),
2567                                                  arc);
2568         beam.source = beam.target = p->first;
2569         beam.source.x -= sgn(beam.source.x - hitfunc.origin.x);
2570         beam.source.y -= sgn(beam.source.y - hitfunc.origin.y);
2571         beam.damage = dice_def(div_rand_round(juice, ROD_CHARGE_MULT),
2572                                div_rand_round(30 + pow / 6, arc + 2));
2573         beam.fire();
2574     }
2575
2576     caster->props["thunderbolt_last"].get_int() = you.num_turns;
2577     caster->props["thunderbolt_aim"].get_coord() = aim;
2578
2579     noisy(15 + div_rand_round(juice, ROD_CHARGE_MULT), hitfunc.origin);
2580
2581     return SPRET_SUCCESS;
2582 }
2583
2584 // Find an enemy who would suffer from Awaken Forest.
2585 actor* forest_near_enemy(const actor *mon)
2586 {
2587     const coord_def pos = mon->pos();
2588
2589     for (radius_iterator ri(pos, LOS_RADIUS); ri; ++ri)
2590     {
2591         actor* foe = actor_at(*ri);
2592         if (!foe || mons_aligned(foe, mon))
2593             continue;
2594
2595         for (adjacent_iterator ai(*ri); ai; ++ai)
2596             if (feat_is_tree(grd(*ai)) && cell_see_cell(pos, *ai, LOS_DEFAULT))
2597                 return foe;
2598     }
2599
2600     return NULL;
2601 }
2602
2603 // Print a message only if you can see any affected trees.
2604 void forest_message(const coord_def pos, const string &msg, msg_channel_type ch)
2605 {
2606     for (radius_iterator ri(pos, LOS_RADIUS); ri; ++ri)
2607         if (feat_is_tree(grd(*ri))
2608             && cell_see_cell(you.pos(), *ri, LOS_DEFAULT)
2609             && cell_see_cell(pos, *ri, LOS_DEFAULT))
2610         {
2611             mpr(msg, ch);
2612             return;
2613         }
2614 }
2615
2616 void forest_damage(const actor *mon)
2617 {
2618     const coord_def pos = mon->pos();
2619     const int hd = mon->get_experience_level();
2620
2621     if (one_chance_in(4))
2622         forest_message(pos, random_choose(
2623             "The trees move their gnarly branches around.",
2624             "You feel roots moving beneath the ground.",
2625             "Branches wave dangerously above you.",
2626             "Trunks creak and shift.",
2627             "Tree limbs sway around you.",
2628             0), MSGCH_TALK_VISUAL);
2629
2630     for (radius_iterator ri(pos, LOS_RADIUS); ri; ++ri)
2631     {
2632         actor* foe = actor_at(*ri);
2633         if (!foe || mons_aligned(foe, mon))
2634             continue;
2635
2636         for (adjacent_iterator ai(*ri); ai; ++ai)
2637             if (feat_is_tree(grd(*ai)) && cell_see_cell(pos, *ai, LOS_DEFAULT))
2638             {
2639                 int evnp = foe->melee_evasion(mon, EV_IGNORE_PHASESHIFT);
2640                 int dmg = 0;
2641                 string msg;
2642
2643                 if (!apply_chunked_AC(1, evnp))
2644                 {
2645                     msg = random_choose(
2646                             "@foe@ @is@ waved at by a branch.",
2647                             "A tree reaches out but misses @foe@.",
2648                             "A root lunges up near @foe@.",
2649                             0);
2650                 }
2651                 else if (!apply_chunked_AC(1, foe->melee_evasion(mon) - evnp))
2652                 {
2653                     msg = random_choose(
2654                             "A branch passes through @foe@!",
2655                             "A tree reaches out and and passes through @foe@!",
2656                             "A root lunges and passes through @foe@ from below.",
2657                             0);
2658                 }
2659                 else if (!(dmg = foe->apply_ac(div_rand_round(hd, 2) + random2(hd),
2660                                                (hd * 3 - 1) / 2, AC_PROPORTIONAL)))
2661                 {
2662                     msg = random_choose(
2663                             "@foe@ @is@ scraped by a branch!",
2664                             "A tree reaches out and scrapes @foe@!",
2665                             "A root barely touches @foe@ from below.",
2666                             0);
2667                 }
2668                 else
2669                 {
2670                     msg = random_choose(
2671                         "@foe@ @is@ hit by a branch!",
2672                         "A tree reaches out and hits @foe@!",
2673                         "A root smacks @foe@ from below.",
2674                         0);
2675                 }
2676
2677                 msg = replace_all(replace_all(msg,
2678                     // "it" looks butt-ugly here...
2679                     "@foe@", foe->visible_to(&you) ? foe->name(DESC_THE)
2680                                                    : "something"),
2681                     "@is@", foe->is_player() ? "are" : "is");
2682                 if (you.see_cell(foe->pos()))
2683                     mpr(msg.c_str());
2684
2685                 if (dmg <= 0)
2686                     break;
2687
2688                 if (foe->is_player())
2689                 {
2690                     ouch(dmg, mon->mindex(), KILLED_BY_BEAM,
2691                          "angry trees", true);
2692                 }
2693                 else
2694                     foe->hurt(mon, dmg);
2695
2696                 break;
2697             }
2698     }
2699 }
2700
2701 vector<bolt> get_spray_rays(const actor *caster, coord_def aim, int range, int max_rays)
2702 {
2703     coord_def aim_dir = (caster->pos() - aim).sgn();
2704
2705     int num_targets = 0;
2706     vector<bolt> beams;
2707     int range2 = dist_range(6);
2708
2709     bolt base_beam;
2710
2711     base_beam.set_agent(const_cast<actor *>(caster));
2712     base_beam.attitude = ATT_FRIENDLY;
2713     base_beam.is_tracer = true;
2714     base_beam.is_targetting = true;
2715     base_beam.range = range;
2716     base_beam.source = caster->pos();
2717     base_beam.target = aim;
2718
2719     bolt center_beam = base_beam;
2720     center_beam.hit = AUTOMATIC_HIT;
2721     center_beam.fire();
2722     center_beam.target = center_beam.path_taken.back();
2723     center_beam.hit = 1;
2724     center_beam.fire();
2725     center_beam.is_tracer = false;
2726     beams.push_back(center_beam);
2727
2728     for (distance_iterator di(aim, false, false, 3); di; ++di)
2729     {
2730         if (monster_at(*di))
2731         {
2732             coord_def delta = caster->pos() - *di;
2733
2734             //Don't aim secondary rays at friendlies
2735             if ((caster->is_player() ? monster_at(*di)->attitude != ATT_HOSTILE
2736                     : monster_at(*di)->attitude != ATT_FRIENDLY))
2737                 continue;
2738
2739             if (!caster->can_see(monster_at(*di)))
2740                 continue;
2741
2742             //Don't try to aim at a target if it's out of range
2743             if (delta.abs() > range2)
2744                 continue;
2745
2746             //Don't try to aim at targets in the opposite direction of main aim
2747             if (abs(aim_dir.x - delta.sgn().x) + abs(aim_dir.y - delta.sgn().y) >= 2)
2748                 continue;
2749
2750             //Test if this beam stops at a location used by any prior beam
2751             bolt testbeam = base_beam;
2752             testbeam.target = *di;
2753             testbeam.hit = AUTOMATIC_HIT;
2754             testbeam.fire();
2755             bool duplicate = false;
2756
2757             for (unsigned int i = 0; i < beams.size(); ++i)
2758             {
2759                 if (testbeam.path_taken.back() == beams[i].target)
2760                 {
2761                     duplicate = true;
2762                     continue;
2763                 }
2764             }
2765             if (!duplicate)
2766             {
2767                 bolt tempbeam = base_beam;
2768                 tempbeam.target = testbeam.path_taken.back();
2769                 tempbeam.fire();
2770                 tempbeam.is_tracer = false;
2771                 base_beam.is_targetting = false;
2772                 beams.push_back(tempbeam);
2773                 num_targets++;
2774             }
2775
2776             if (num_targets == max_rays - 1)
2777               break;
2778         }
2779     }
2780
2781     return beams;
2782 }
2783
2784 static bool _dazzle_can_hit(const actor *act)
2785 {
2786     if (act->is_monster())
2787     {
2788         const monster* mons = act->as_monster();
2789         bolt testbeam;
2790         testbeam.thrower = KILL_YOU;
2791         zappy(ZAP_DAZZLING_SPRAY, 100, testbeam);
2792
2793         return (mons->type != MONS_BATTLESPHERE
2794                 && mons->type != MONS_ORB_OF_DESTRUCTION
2795                 && mons_species(mons->type) != MONS_BUSH
2796                 && !fedhas_shoot_through(testbeam, mons));
2797     }
2798     else
2799         return false;
2800 }
2801
2802 spret_type cast_dazzling_spray(actor *caster, int pow, coord_def aim, bool fail)
2803 {
2804     int range = spell_range(SPELL_DAZZLING_SPRAY, pow);
2805
2806     targetter_spray hitfunc(&you, range, ZAP_DAZZLING_SPRAY);
2807
2808     hitfunc.set_aim(aim);
2809
2810     if (caster->is_player())
2811     {
2812         if (stop_attack_prompt(hitfunc, "fire towards", _dazzle_can_hit))
2813             return SPRET_ABORT;
2814     }
2815
2816     fail_check();
2817
2818     vector<bolt> beams = get_spray_rays(caster, aim, range, 3);
2819
2820     if (beams.size() == 0)
2821     {
2822         mpr("You can't see any targets in that direction!");
2823         return SPRET_ABORT;
2824     }
2825
2826     for (unsigned int i = 0; i < beams.size(); ++i)
2827     {
2828         zappy(ZAP_DAZZLING_SPRAY, pow, beams[i]);
2829         beams[i].fire();
2830     }
2831
2832     return SPRET_SUCCESS;
2833 }