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 
10 #include "spl-damage.h"
11 
12 #include <functional>
13 
14 #include "act-iter.h"
15 #include "areas.h"
16 #include "attack.h"
17 #include "beam.h"
18 #include "cloud.h"
19 #include "colour.h"
20 #include "coordit.h"
21 #include "directn.h"
22 #include "english.h"
23 #include "env.h"
24 #include "fight.h"
25 #include "fineff.h"
26 #include "fprop.h"
27 #include "god-conduct.h"
28 #include "god-passive.h"
29 #include "invent.h"
30 #include "items.h"
31 #include "level-state-type.h"
32 #include "los.h"
33 #include "losglobal.h"
34 #include "macro.h"
35 #include "mapmark.h"
36 #include "message.h"
37 #include "misc.h"
38 #include "mon-behv.h"
39 #include "mon-death.h"
40 #include "mon-tentacle.h"
41 #include "mutation.h"
42 #include "ouch.h"
43 #include "prompt.h"
44 #include "random.h"
45 #include "religion.h"
46 #include "shout.h"
47 #include "spl-goditem.h"
48 #include "spl-summoning.h"
49 #include "spl-util.h"
50 #include "spl-zap.h"
51 #include "stepdown.h"
52 #include "stringutil.h"
53 #include "target.h"
54 #include "terrain.h"
55 #include "transform.h"
56 #include "unicode.h"
57 #include "viewchar.h"
58 #include "view.h"
59 #include "xp-evoker-data.h" // for thunderbolt
60 
_act_worth_targeting(const actor & caster,const actor & a)61 static bool _act_worth_targeting(const actor &caster, const actor &a)
62 {
63     if (!caster.see_cell_no_trans(a.pos()))
64         return false;
65     if (a.is_player())
66         return true;
67     const monster &m = *a.as_monster();
68     return !mons_is_firewood(m)
69         && !mons_is_conjured(m.type)
70         && (!caster.is_player() || !god_protects(&you, &m, true));
71 }
72 
73 // returns the closest target to the caster, choosing randomly if there are more
74 // than one (see `fair` argument to distance_iterator).
_find_closest_target(const actor & caster,int radius,bool tracer)75 static actor* _find_closest_target(const actor &caster, int radius, bool tracer)
76 {
77     for (distance_iterator di(caster.pos(), !tracer, true, radius); di; ++di)
78     {
79         actor *act = actor_at(*di);
80         if (act && _act_worth_targeting(caster, *act)
81             && cell_see_cell(caster.pos(), *di, LOS_SOLID)
82             && (!tracer || caster.can_see(*act)))
83         {
84             return act;
85         }
86     }
87 
88     return nullptr;
89 }
90 
setup_fire_storm(const actor * source,int pow,bolt & beam)91 void setup_fire_storm(const actor *source, int pow, bolt &beam)
92 {
93     zappy(ZAP_FIRE_STORM, pow, source->is_monster(), beam);
94     beam.ex_size      = 2 + (random2(1000) < pow);
95     beam.source_id    = source->mid;
96     // XXX: Should this be KILL_MON_MISSILE?
97     beam.thrower      =
98         source->is_player() ? KILL_YOU_MISSILE : KILL_MON;
99     beam.aux_source.clear();
100     beam.is_tracer    = false;
101     beam.origin_spell = SPELL_FIRE_STORM;
102 }
103 
cast_fire_storm(int pow,bolt & beam,bool fail)104 spret cast_fire_storm(int pow, bolt &beam, bool fail)
105 {
106     if (grid_distance(beam.target, beam.source) > beam.range)
107     {
108         mpr("That is beyond the maximum range.");
109         return spret::abort;
110     }
111 
112     if (cell_is_solid(beam.target))
113     {
114         const char *feat = feat_type_name(env.grid(beam.target));
115         mprf("You can't place the storm on %s.", article_a(feat).c_str());
116         return spret::abort;
117     }
118 
119     setup_fire_storm(&you, pow, beam);
120 
121     bolt tempbeam = beam;
122     tempbeam.ex_size = (pow > 76) ? 3 : 2;
123     tempbeam.is_tracer = true;
124 
125     tempbeam.explode(false);
126     if (tempbeam.beam_cancelled)
127         return spret::abort;
128 
129     fail_check();
130 
131     beam.apply_beam_conducts();
132     beam.refine_for_explosion();
133     beam.explode(false);
134 
135     viewwindow();
136     update_screen();
137     return spret::success;
138 }
139 
140 // No setup/cast split here as monster damnation is completely different.
141 // XXX make this not true
cast_smitey_damnation(int pow,bolt & beam)142 bool cast_smitey_damnation(int pow, bolt &beam)
143 {
144     beam.name              = "damnation";
145     beam.aux_source        = "damnation";
146     beam.ex_size           = 1;
147     beam.flavour           = BEAM_DAMNATION;
148     beam.real_flavour      = beam.flavour;
149     beam.glyph             = dchar_glyph(DCHAR_FIRED_BURST);
150     beam.colour            = LIGHTRED;
151     beam.source_id         = MID_PLAYER;
152     beam.thrower           = KILL_YOU;
153     beam.obvious_effect    = false;
154     beam.pierce            = false;
155     beam.is_explosion      = true;
156     beam.ench_power        = pow;      // used for radius
157     beam.hit               = 20 + pow / 10;
158     beam.damage            = calc_dice(6, 30 + pow);
159     beam.attitude          = ATT_FRIENDLY;
160     beam.friend_info.count = 0;
161     beam.is_tracer         = true;
162 
163     beam.explode(false);
164 
165     if (beam.beam_cancelled)
166     {
167         canned_msg(MSG_OK);
168         return false;
169     }
170 
171     mpr("You call forth a pillar of damnation!");
172 
173     beam.is_tracer = false;
174     beam.in_explosion_phase = false;
175     beam.explode(true);
176 
177     return true;
178 }
179 
180 struct arc_victim
181 {
182     coord_def source;
183     actor *victim;
184 };
185 
186 static const int ARC_DIST = 3;
187 
_chain_lightning_to(const actor & caster,int pow,vector<arc_victim> & victims,set<actor * > & seen_set,int arcs)188 static void _chain_lightning_to(const actor &caster, int pow,
189                                 vector<arc_victim> &victims,
190                                 set<actor*> &seen_set, int arcs)
191 {
192     bolt beam;
193     zappy(ZAP_CHAIN_LIGHTNING, pow, caster.is_monster(), beam);
194     for (int i = 0; i < arcs; i++)
195         beam.damage.size = beam.damage.size * 2 / 3;
196     const int dam_size = beam.damage.size;
197 
198     vector<arc_victim> new_victims;
199     for (arc_victim &arc : victims)
200     {
201         beam.source = arc.source;
202         beam.target = arc.victim->pos();
203         beam.range = grid_distance(beam.source, beam.target);
204         beam.source_id      = caster.mid;
205         beam.thrower        = caster.is_player() ? KILL_YOU_MISSILE : KILL_MON_MISSILE;
206         beam.origin_spell   = SPELL_CHAIN_LIGHTNING;
207         beam.aux_source     = "arc of chain lightning";
208         // Reduce damage to the caster.
209         beam.damage.size = dam_size / (arc.victim == &caster ? 6 : 1);
210         beam.draw_delay = 0;
211         beam.redraw_per_cell = false;
212         beam.obvious_effect = true;
213         beam.fire();
214 
215         // arc!
216         for (distance_iterator di(beam.target, false, true, ARC_DIST); di; ++di)
217         {
218             actor *new_victim = actor_at(*di);
219             if (new_victim
220                 && seen_set.find(new_victim) == seen_set.end()
221                 && cell_see_cell(*di, beam.target, LOS_SOLID)
222                 && _act_worth_targeting(caster, *new_victim))
223             {
224                 new_victims.push_back(arc_victim{beam.target, new_victim});
225                 seen_set.insert(new_victim);
226             }
227         }
228     }
229 
230     if (beam.animate)
231     {
232         viewwindow(false);
233         update_screen();
234         scaled_delay(200);
235     }
236 
237     if (!new_victims.empty())
238         _chain_lightning_to(caster, pow, new_victims, seen_set, arcs + 1);
239 }
240 
241 // Assuming the player casts Chain Lightning, who *might* get hit?
242 // Doesn't include monsters the player can't see.
chain_lightning_targets()243 vector<coord_def> chain_lightning_targets()
244 {
245     vector<coord_def> targets;
246     actor *seed = _find_closest_target(you, LOS_RADIUS, true);
247     if (!seed)
248         return targets;
249 
250     set<actor*> seen;
251     vector<coord_def> to_check;
252     seen.insert(seed);
253     to_check.push_back(seed->pos());
254     targets.push_back(seed->pos());
255     while (!to_check.empty())
256     {
257         const coord_def arc_source = to_check.back();
258         to_check.pop_back();
259         for (distance_iterator di(arc_source, true, true, ARC_DIST); di; ++di)
260         {
261             actor *new_victim = actor_at(*di);
262             if (new_victim
263                 && you.can_see(*new_victim)
264                 && seen.find(new_victim) == seen.end()
265                 && cell_see_cell(*di, you.pos(), LOS_SOLID)
266                 && _act_worth_targeting(you, *new_victim))
267             {
268                 to_check.push_back(new_victim->pos());
269                 targets.push_back(new_victim->pos());
270                 seen.insert(new_victim);
271             }
272         }
273     }
274 
275     return targets;
276 }
277 
_warn_about_chain_lightning()278 static bool _warn_about_chain_lightning()
279 {
280     vector<const monster*> bad_targets;
281     for (coord_def p : chain_lightning_targets())
282     {
283         const monster* mon = monster_at(p);
284         string adj, suffix;
285         bool penance;
286         if (mon && bad_attack(mon, adj, suffix, penance, you.pos()))
287             bad_targets.push_back(mon);
288     }
289 
290     if (bad_targets.empty())
291         return false;
292 
293     const monster* ex_mon = bad_targets.back();
294     string adj, suffix;
295     bool penance;
296     bad_attack(ex_mon, adj, suffix, penance, you.pos());
297     const string and_more = bad_targets.size() > 1 ?
298             make_stringf(" (and %lu other bad targets)",
299                          bad_targets.size() - 1) : "";
300     const string prompt = make_stringf("Chain Lightning might hit %s%s. Cast it anyway?",
301                                        ex_mon->name(DESC_THE).c_str(),
302                                        and_more.c_str());
303     if (!yesno(prompt.c_str(), false, 'n'))
304     {
305         canned_msg(MSG_OK);
306         return true;
307     }
308     return false;
309 }
310 
cast_chain_lightning(int pow,const actor & caster,bool fail)311 spret cast_chain_lightning(int pow, const actor &caster, bool fail)
312 {
313     if (caster.is_player() && _warn_about_chain_lightning())
314         return spret::abort;
315     // NOTE: it's possible to hit something not in this list by arcing
316     // chain lightning through an invisible enemy through an ally. Oh well...
317 
318     fail_check();
319 
320     actor* act = _find_closest_target(caster, LOS_RADIUS, false);
321     if (!act)
322     {
323         if (caster.is_player())
324             canned_msg(MSG_NOTHING_HAPPENS);
325         return spret::success;
326     }
327 
328     if (you.can_see(caster))
329     {
330         mprf("Lightning arcs from %s %s!",
331              apostrophise(caster.name(DESC_PLAIN)).c_str(),
332              caster.hand_name(true).c_str());
333     }
334 
335     vector<arc_victim> victims;
336     victims.push_back(arc_victim{caster.pos(), act});
337     set<actor*> seen_set;
338     seen_set.insert(act);
339     const int initial_arc_dist = (grid_distance(caster.pos(), act->pos()) - 1) / ARC_DIST; // 1 arc at range 4, 2 at range 7
340     _chain_lightning_to(caster, pow, victims, seen_set, initial_arc_dist);
341 
342     return spret::success;
343 }
344 
345 // XXX no friendly check
cast_chain_spell(spell_type spell_cast,int pow,const actor * caster,bool fail)346 spret cast_chain_spell(spell_type spell_cast, int pow,
347                             const actor *caster, bool fail)
348 {
349     fail_check();
350     bolt beam;
351 
352     // initialise beam structure
353     beam.name           = "arc of chaos";
354     beam.aux_source     = "chain of chaos";
355     beam.glyph          = dchar_glyph(DCHAR_FIRED_ZAP);
356     beam.flavour        = BEAM_CHAOS;
357     beam.source_id      = caster->mid;
358     beam.thrower        = caster->is_player() ? KILL_YOU_MISSILE : KILL_MON_MISSILE;
359     beam.range          = 8;
360     beam.hit            = AUTOMATIC_HIT;
361     beam.obvious_effect = true;
362     beam.pierce         = false;       // since we want to stop at our target
363     beam.is_explosion   = false;
364     beam.is_tracer      = false;
365     beam.origin_spell   = spell_cast;
366 
367     if (const monster* mons = caster->as_monster())
368         beam.source_name = mons->name(DESC_PLAIN, true);
369 
370     bool first = true;
371     coord_def source, target;
372 
373     for (source = caster->pos(); pow > 0;
374          pow -= 8 + random2(13), source = target)
375     {
376         // infinity as far as this spell is concerned
377         // (Range - 1) is used because the distance is randomised and
378         // may be shifted by one.
379         int min_dist = LOS_DEFAULT_RANGE - 1;
380 
381         int dist;
382         int count = 0;
383 
384         target.x = -1;
385         target.y = -1;
386 
387         for (monster_iterator mi; mi; ++mi)
388         {
389             if (invalid_monster(*mi))
390                 continue;
391 
392             // Don't arc to things we cannot hit.
393             if (beam.ignores_monster(*mi))
394                 continue;
395 
396             dist = grid_distance(source, mi->pos());
397 
398             // check for the source of this arc
399             if (!dist)
400                 continue;
401 
402             // randomise distance (arcs don't care about a couple of feet)
403             dist += (random2(3) - 1);
404 
405             // always ignore targets further than current one
406             if (dist > min_dist)
407                 continue;
408 
409             if (!cell_see_cell(source, mi->pos(), LOS_SOLID)
410                 || !cell_see_cell(caster->pos(), mi->pos(), LOS_SOLID_SEE))
411             {
412                 continue;
413             }
414 
415             // check for actors along the arc path
416             ray_def ray;
417             if (!find_ray(source, mi->pos(), ray, opc_solid))
418                 continue;
419 
420             while (ray.advance())
421                 if (actor_at(ray.pos()))
422                     break;
423 
424             if (ray.pos() != mi->pos())
425                 continue;
426 
427             count++;
428 
429             if (dist < min_dist)
430             {
431                 // switch to looking for closer targets (but not always)
432                 if (!one_chance_in(10))
433                 {
434                     min_dist = dist;
435                     target = mi->pos();
436                     count = 0;
437                 }
438             }
439             else if (target.x == -1 || one_chance_in(count))
440             {
441                 // either first target, or new selected target at
442                 // min_dist == dist.
443                 target = mi->pos();
444             }
445         }
446 
447         // now check if the player is a target
448         dist = grid_distance(source, you.pos());
449 
450         if (dist)       // i.e., player was not the source
451         {
452             // distance randomised (as above)
453             dist += (random2(3) - 1);
454 
455             // select player if only, closest, or randomly selected
456             if ((target.x == -1
457                     || dist < min_dist
458                     || (dist == min_dist && one_chance_in(count + 1)))
459                 && cell_see_cell(source, you.pos(), LOS_SOLID))
460             {
461                 target = you.pos();
462             }
463         }
464 
465         const bool see_source = you.see_cell(source);
466         const bool see_targ   = you.see_cell(target);
467 
468         if (target.x == -1)
469         {
470             if (see_source)
471                 mprf("The %s grounds out.", beam.name.c_str());
472 
473             break;
474         }
475 
476         // Trying to limit message spamming here so we'll only mention
477         // the chaos at the start or when it's out of LoS.
478         if (first && see_source)
479             mpr("A swirling arc of seething chaos appears!");
480         first = false;
481 
482         if (see_source && !see_targ)
483             mprf("The %s arcs out of your line of sight!", beam.name.c_str());
484         else if (!see_source && see_targ)
485             mprf("The %s suddenly appears!", beam.name.c_str());
486 
487         beam.source = source;
488         beam.target = target;
489         beam.colour       = ETC_RANDOM;
490         beam.ench_power   = pow;
491         beam.damage       = calc_dice(3, 5 + pow / 6);
492         beam.real_flavour = BEAM_CHAOS;
493         beam.flavour      = BEAM_CHAOS;
494 
495         // Be kinder to the caster.
496         if (target == caster->pos())
497         {
498             // This should not hit the caster, too scary as a player effect and
499             // too kind to the player as a monster effect.
500             if (spell_cast == SPELL_CHAIN_OF_CHAOS)
501             {
502                 beam.real_flavour = BEAM_VISUAL;
503                 beam.flavour      = BEAM_VISUAL;
504             }
505 
506             // Reduce damage when the spell arcs to the caster.
507             beam.damage.num = max(1, beam.damage.num / 2);
508             beam.damage.size = max(3, beam.damage.size / 2);
509         }
510         beam.fire();
511     }
512 
513     return spret::success;
514 }
515 
516 /*
517  * Handle the application of damage from a player spell that doesn't apply these
518  * through struct bolt. This can apply god conducts and handles any necessary
519  * death cleanup.
520  * @param mon          The monster.
521  * @param damage       The damage to apply, if any. Regardless of damage done,
522  *                     the monster will have death cleanup applied via
523  *                     monster_die() if it's now dead.
524  * @param flavour      The beam flavour of damage.
525  * @param god_conducts If true, apply any god conducts, in which case the
526  *                     monster must be alive. Some callers need to apply
527  *                     effects prior to damage that might kill the monster,
528  *                     hence handle conducts on their own.
529 */
_player_hurt_monster(monster & mon,int damage,beam_type flavour,bool god_conducts=true)530 static void _player_hurt_monster(monster &mon, int damage, beam_type flavour,
531                                  bool god_conducts = true)
532 {
533     ASSERT(mon.alive() || !god_conducts);
534 
535     if (god_conducts && god_protects(&mon, false))
536         return;
537 
538     god_conduct_trigger conducts[3];
539     if (god_conducts)
540         set_attack_conducts(conducts, mon, you.can_see(mon));
541 
542     if (damage)
543     {
544         majin_bo_vampirism(mon, min(damage, mon.stat_hp()));
545         mon.hurt(&you, damage, flavour, KILLED_BY_BEAM);
546     }
547 
548     if (mon.alive())
549     {
550         behaviour_event(&mon, ME_WHACK, &you);
551 
552         if (damage && you.can_see(mon))
553             print_wounds(mon);
554     }
555     // monster::hurt() wasn't called, so we do death cleanup.
556     else if (!damage)
557         monster_die(mon, KILL_YOU, NON_MONSTER);
558 }
559 
_drain_lifeable(const actor * agent,const actor * act)560 static bool _drain_lifeable(const actor* agent, const actor* act)
561 {
562     if (act->res_negative_energy() >= 3)
563         return false;
564 
565     if (!agent)
566         return true;
567 
568     const monster* mons = agent->as_monster();
569     const monster* m = act->as_monster();
570 
571     return !(agent->is_player() && act->wont_attack()
572              || mons && act->is_player() && mons->wont_attack()
573              || mons && m && mons_atts_aligned(mons->attitude, m->attitude));
574 }
575 
_los_spell_pre_damage_monsters(const actor * agent,vector<monster * > affected_monsters,const char * verb)576 static void _los_spell_pre_damage_monsters(const actor* agent,
577                                            vector<monster *> affected_monsters,
578                                            const char *verb)
579 {
580     // Filter out affected monsters that we don't know for sure are there
581     vector<monster*> seen_monsters;
582     for (monster *mon : affected_monsters)
583         if (you.can_see(*mon))
584             seen_monsters.push_back(mon);
585 
586     if (!seen_monsters.empty())
587     {
588         counted_monster_list mons_list =
589             counted_monster_list(seen_monsters);
590         const string message = make_stringf("%s %s %s.",
591                 mons_list.describe(DESC_THE).c_str(),
592                 conjugate_verb("be", mons_list.count() > 1).c_str(), verb);
593         if (strwidth(message) < get_number_of_cols() - 2)
594             mpr(message);
595         else
596         {
597             // Exclamation mark to suggest that a lot of creatures were
598             // affected.
599             mprf("The monsters around %s are %s!",
600                 agent && agent->is_monster() && you.can_see(*agent)
601                 ? agent->as_monster()->name(DESC_THE).c_str()
602                 : "you", verb);
603         }
604     }
605 }
606 
_los_spell_damage_player(const actor * agent,bolt & beam,bool actual)607 static int _los_spell_damage_player(const actor* agent, bolt &beam,
608                                     bool actual)
609 {
610     int hurted = actual ? beam.damage.roll()
611                         // Monsters use the average for foe calculations.
612                         : (1 + beam.damage.num * beam.damage.size) / 2;
613     hurted = check_your_resists(hurted, beam.flavour, beam.name, 0,
614             // Drain life doesn't apply drain effects.
615             actual && beam.origin_spell != SPELL_DRAIN_LIFE);
616     if (actual && hurted > 0)
617     {
618 
619         if (agent && !agent->is_player())
620         {
621             ouch(hurted, KILLED_BY_BEAM, agent->mid,
622                  make_stringf("by %s", beam.name.c_str()).c_str(), true,
623                  agent->as_monster()->name(DESC_A).c_str());
624             you.expose_to_element(beam.flavour, 5);
625         }
626     }
627 
628     return hurted;
629 }
630 
_los_spell_damage_monster(const actor * agent,monster & target,bolt & beam,bool actual)631 static int _los_spell_damage_monster(const actor* agent, monster &target,
632                                      bolt &beam, bool actual)
633 {
634 
635     beam.thrower = (agent && agent->is_player()) ? KILL_YOU :
636                     agent                        ? KILL_MON
637                                                  : KILL_MISC;
638 
639     // Set conducts here. The monster needs to be alive when this is done, and
640     // mons_adjust_flavoured() could kill it.
641     god_conduct_trigger conducts[3];
642     if (actual && YOU_KILL(beam.thrower))
643         set_attack_conducts(conducts, target, you.can_see(target));
644 
645     int hurted = actual ? beam.damage.roll()
646                         // Tracers use the average for damage calculations.
647                         : (1 + beam.damage.num * beam.damage.size) / 2;
648     hurted = mons_adjust_flavoured(&target, beam, hurted,
649                  // Drain life doesn't apply drain effects.
650                  actual && beam.origin_spell != SPELL_DRAIN_LIFE);
651     dprf("damage done: %d", hurted);
652 
653     if (actual)
654     {
655         if (YOU_KILL(beam.thrower))
656             _player_hurt_monster(target, hurted, beam.flavour, false);
657         else if (hurted)
658             target.hurt(agent, hurted, beam.flavour);
659 
660         // Cold-blooded creatures can be slowed.
661         if (beam.origin_spell == SPELL_OZOCUBUS_REFRIGERATION
662             && target.alive())
663         {
664             target.expose_to_element(beam.flavour, 5);
665         }
666     }
667 
668     // So that summons don't restore HP.
669     if (beam.origin_spell == SPELL_DRAIN_LIFE && target.is_summoned())
670         return 0;
671 
672     return hurted;
673 }
674 
675 
_cast_los_attack_spell(spell_type spell,int pow,const actor * agent,bool actual,bool fail,int * damage_done)676 static spret _cast_los_attack_spell(spell_type spell, int pow,
677                                          const actor* agent, bool actual,
678                                          bool fail, int* damage_done)
679 {
680     const bool player_caster = agent && agent->is_player();
681     const monster* mons = agent ? agent->as_monster() : nullptr;
682 
683     const zap_type zap = spell_to_zap(spell);
684     if (zap == NUM_ZAPS)
685         return spret::abort;
686 
687     bolt beam;
688     zappy(zap, pow, mons, beam);
689     beam.source_id = agent ? agent->mid : MID_NOBODY;
690     beam.foe_ratio = 80;
691 
692     const char *player_msg = nullptr, *global_msg = nullptr,
693                *mons_vis_msg = nullptr, *mons_invis_msg = nullptr,
694                *verb = nullptr, *prompt_verb = nullptr;
695     bool (*vulnerable)(const actor *, const actor *) = nullptr;
696 
697     switch (spell)
698     {
699         case SPELL_OZOCUBUS_REFRIGERATION:
700             player_msg = "The heat is drained from your surroundings.";
701             global_msg = "Something drains the heat from around you.";
702             mons_vis_msg = " drains the heat from the surrounding"
703                            " environment!";
704             mons_invis_msg = "The ambient heat is drained!";
705             verb = "frozen";
706             prompt_verb = "refrigerate";
707             vulnerable = [](const actor *caster, const actor *act) {
708                 return act != caster
709                        // Players don't get immunity with rC+++.
710                        && (act->is_player() || act->res_cold() < 3)
711                        && !god_protects(caster, act->as_monster());
712             };
713             break;
714 
715         case SPELL_DRAIN_LIFE:
716             player_msg = "You draw life from your surroundings.";
717             global_msg = "Something draws the life force from your"
718                          " surroundings.";
719             mons_vis_msg = " draws from the surrounding life force!";
720             mons_invis_msg = "The surrounding life force dissipates!";
721             verb = "drained of life";
722             prompt_verb = "drain life";
723             vulnerable = &_drain_lifeable;
724             break;
725 
726         case SPELL_SONIC_WAVE:
727             player_msg = "You send a blast of sound all around you.";
728             global_msg = "Something sends a blast of sound all around you.";
729             mons_vis_msg = " sends a blast of sound all around you!";
730             mons_invis_msg = "Sound blasts the surrounding area!";
731             verb = "blasted";
732             // prompt_verb = "sing" The singing sword prompts in melee-attack
733             vulnerable = [](const actor *caster, const actor *act) {
734                 return act != caster
735                        && !god_protects(caster, act->as_monster());
736             };
737             break;
738 
739         default:
740             return spret::abort;
741     }
742 
743     auto vul_hitfunc = [vulnerable](const actor *act) -> bool
744     {
745         return (*vulnerable)(&you, act);
746     };
747 
748     if (actual)
749     {
750         if (player_caster)
751         {
752             targeter_radius hitfunc(&you, LOS_NO_TRANS);
753             // Singing Sword's spell shouldn't give a prompt at this time.
754             if (spell != SPELL_SONIC_WAVE)
755             {
756                 if (stop_attack_prompt(hitfunc, prompt_verb, vul_hitfunc))
757                     return spret::abort;
758 
759                 fail_check();
760             }
761 
762             mpr(player_msg);
763             flash_view_delay(UA_PLAYER, beam.colour, 300, &hitfunc);
764 
765             if (spell == SPELL_OZOCUBUS_REFRIGERATION)
766             {
767                 mpr("You feel very cold.");
768                 you.increase_duration(DUR_NO_POTIONS, 7 + random2(9), 15);
769             }
770         }
771         else
772         {
773             if (!agent)
774                 mpr(global_msg);
775             else if (you.can_see(*agent))
776                 simple_monster_message(*mons, mons_vis_msg);
777             else if (you.see_cell(agent->pos()))
778                 mpr(mons_invis_msg);
779 
780             if (!agent || you.see_cell(agent->pos()))
781                 flash_view_delay(UA_MONSTER, beam.colour, 300);
782         }
783     }
784 
785     bool affects_you = false;
786     vector<monster *> affected_monsters;
787 
788     for (actor_near_iterator ai((agent ? agent : &you)->pos(), LOS_NO_TRANS);
789          ai; ++ai)
790     {
791         if (!actual && !agent->can_see(**ai))
792             continue;
793 
794         if ((*vulnerable)(agent, *ai))
795         {
796             if (ai->is_player())
797                 affects_you = true;
798             else if (*ai != agent)
799                 affected_monsters.push_back(ai->as_monster());
800         }
801     }
802 
803     const int avg_damage = (1 + beam.damage.num * beam.damage.size) / 2;
804     int total_damage = 0;
805     // XXX: This ordering is kind of broken; it's to preserve the message
806     // order from the original behaviour in the case of refrigerate.
807     if (affects_you)
808     {
809         total_damage = _los_spell_damage_player(agent, beam, actual);
810         if (!actual && mons)
811         {
812             if (mons->wont_attack())
813             {
814                 beam.friend_info.count++;
815                 beam.friend_info.power +=
816                     (you.get_experience_level() * total_damage / avg_damage);
817             }
818             else
819             {
820                 beam.foe_info.count++;
821                 beam.foe_info.power +=
822                     (you.get_experience_level() * total_damage / avg_damage);
823             }
824         }
825     }
826 
827     if (actual && !affected_monsters.empty())
828         _los_spell_pre_damage_monsters(agent, affected_monsters, verb);
829 
830     for (auto m : affected_monsters)
831     {
832         // Watch out for invalidation. Example: Ozocubu's refrigeration on
833         // a bunch of ballistomycete spores that blow each other up.
834         if (!m->alive())
835             continue;
836 
837         int this_damage = _los_spell_damage_monster(agent, *m, beam, actual);
838         total_damage += this_damage;
839 
840         if (!actual)
841         {
842             if (mons)
843             {
844                 if (mons_atts_aligned(m->attitude, mons->attitude))
845                 {
846                     beam.friend_info.count++;
847                     beam.friend_info.power +=
848                         (m->get_hit_dice() * this_damage / avg_damage);
849                 }
850                 else
851                 {
852                     beam.foe_info.count++;
853                     beam.foe_info.power +=
854                         (m->get_hit_dice() * this_damage / avg_damage);
855                 }
856             }
857             else if (player_caster
858                      && this_damage
859                      && !m->wont_attack()
860                      && mons_is_threatening(*m))
861             {
862                 beam.foe_info.count++;
863                 beam.foe_info.power++;
864             }
865         }
866     }
867 
868     if (damage_done)
869         *damage_done = total_damage;
870 
871     if (actual)
872         return spret::success;
873 
874     return mons_should_fire(beam) ? spret::success : spret::abort;
875 }
876 
trace_los_attack_spell(spell_type spell,int pow,const actor * agent)877 spret trace_los_attack_spell(spell_type spell, int pow, const actor* agent)
878 {
879     return _cast_los_attack_spell(spell, pow, agent, false, false, nullptr);
880 }
881 
fire_los_attack_spell(spell_type spell,int pow,const actor * agent,bool fail,int * damage_done)882 spret fire_los_attack_spell(spell_type spell, int pow, const actor* agent,
883                             bool fail, int* damage_done)
884 {
885     return _cast_los_attack_spell(spell, pow, agent, true, fail, damage_done);
886 }
887 
freeze_damage(int pow)888 dice_def freeze_damage(int pow)
889 {
890     return dice_def(1, 3 + pow / 3);
891 }
892 
cast_freeze(int pow,monster * mons,bool fail)893 spret cast_freeze(int pow, monster* mons, bool fail)
894 {
895     pow = min(25, pow);
896 
897     if (!mons || mons->submerged())
898     {
899         fail_check();
900         canned_msg(MSG_NOTHING_CLOSE_ENOUGH);
901         // If there's no monster there, you still pay the costs in
902         // order to prevent locating invisible/submerged monsters.
903         return spret::success;
904     }
905 
906     if (stop_attack_prompt(mons, false, you.pos()))
907     {
908         canned_msg(MSG_OK);
909         return spret::abort;
910     }
911 
912     fail_check();
913 
914     // Set conducts here. The monster needs to be alive when this is done, and
915     // mons_adjust_flavoured() could kill it.
916     god_conduct_trigger conducts[3];
917     set_attack_conducts(conducts, *mons);
918 
919     bolt beam;
920     beam.flavour = BEAM_COLD;
921     beam.thrower = KILL_YOU;
922 
923     const int orig_hurted = freeze_damage(pow).roll();
924     // calculate the resist adjustment to punctuate
925     int hurted = mons_adjust_flavoured(mons, beam, orig_hurted, false);
926     mprf("You freeze %s%s%s",
927          mons->name(DESC_THE).c_str(),
928          hurted ? "" : " but do no damage",
929          attack_strength_punctuation(hurted).c_str());
930 
931     // print the resist message and expose to the cold
932     mons_adjust_flavoured(mons, beam, orig_hurted);
933 
934     _player_hurt_monster(*mons, hurted, beam.flavour, false);
935 
936     if (mons->alive())
937     {
938         mons->expose_to_element(BEAM_COLD, orig_hurted);
939         you.pet_target = mons->mindex();
940     }
941 
942     return spret::success;
943 }
944 
cast_airstrike(int pow,const dist & beam,bool fail)945 spret cast_airstrike(int pow, const dist &beam, bool fail)
946 {
947     if (cell_is_solid(beam.target))
948     {
949         canned_msg(MSG_UNTHINKING_ACT);
950         return spret::abort;
951     }
952 
953     monster* mons = monster_at(beam.target);
954     if (!mons || mons->submerged())
955     {
956         fail_check();
957         canned_msg(MSG_SPELL_FIZZLES);
958         return spret::success; // still losing a turn
959     }
960 
961     if (!god_protects(mons)
962         && stop_attack_prompt(mons, false, you.pos()))
963     {
964         return spret::abort;
965     }
966 
967     fail_check();
968 
969     noisy(spell_effect_noise(SPELL_AIRSTRIKE), beam.target);
970 
971     bolt pbeam;
972     pbeam.flavour = BEAM_AIR;
973 
974     int empty_space = 0;
975     for (adjacent_iterator ai(beam.target); ai; ++ai)
976         if (!monster_at(*ai) && !cell_is_solid(*ai))
977             empty_space++;
978 
979     empty_space = max(3, empty_space);
980 
981     int hurted = 5 + empty_space + random2avg(2 + div_rand_round(pow, 7), 2);
982 #ifdef DEBUG_DIAGNOSTICS
983     const int preac = hurted;
984 #endif
985     hurted = mons->apply_ac(mons->beam_resists(pbeam, hurted, false));
986     dprf("preac: %d, postac: %d", preac, hurted);
987 
988     mprf("The air twists around and strikes %s%s%s",
989          mons->name(DESC_THE).c_str(),
990          hurted ? "" : " but does no damage",
991          attack_strength_punctuation(hurted).c_str());
992     _player_hurt_monster(*mons, hurted, pbeam.flavour);
993 
994     if (mons->alive())
995         you.pet_target = mons->mindex();
996 
997     return spret::success;
998 }
999 
base_fragmentation_damage(int pow)1000 dice_def base_fragmentation_damage(int pow)
1001 {
1002     return dice_def(3, 5 + pow / 5);
1003 }
1004 
1005 enum class frag_damage_type
1006 {
1007     rock, // default
1008     metal, // extra damage
1009     crystal, // extra damage & radius
1010     ice, // BEAM_ICE, not BEAM_FRAG
1011     player_gargoyle, // weaker, because (?)
1012 };
1013 
1014 struct frag_effect
1015 {
1016     frag_damage_type damage;
1017     colour_t colour;
1018     string name;
1019     const char* terrain_name;
1020     bool direct;
1021     bool hit_centre;
1022 };
1023 
1024 // Initializes the provided frag_effect with the appropriate Lee's Rapid
1025 // Deconstruction explosion for blowing up the player. Returns true iff the
1026 // player can be deconstructed.
_init_frag_player(frag_effect & effect)1027 static bool _init_frag_player(frag_effect &effect)
1028 {
1029     if (you.form == transformation::statue || you.species == SP_GARGOYLE)
1030     {
1031         effect.name       = "blast of rock fragments";
1032         effect.colour     = BROWN;
1033         if (you.form != transformation::statue)
1034             effect.damage = frag_damage_type::player_gargoyle;
1035         return true;
1036     }
1037     if (you.petrified() || you.petrifying())
1038     {
1039         effect.name       = "blast of petrified fragments";
1040         effect.colour     = mons_class_colour(player_mons(true));
1041         return true;
1042     }
1043     if (you.form == transformation::ice_beast)
1044     {
1045         effect.name       = "icy blast";
1046         effect.colour     = WHITE;
1047         effect.damage     = frag_damage_type::ice;
1048         return true;
1049     }
1050     return false;
1051 }
1052 
1053 struct monster_frag
1054 {
1055     const char* type;
1056     colour_t colour;
1057     frag_damage_type damage;
1058 };
1059 
1060 static const map<monster_type, monster_frag> fraggable_monsters = {
1061     { MONS_TOENAIL_GOLEM,     { "toenail", RED } },
1062     // I made saltlings not have a big crystal explosion for balance reasons -
1063     // there are so many of them, it seems wrong to have them be so harmful to
1064     // their own allies. This could be wrong!
1065     { MONS_SALTLING,          { "salt crystal", WHITE } },
1066     { MONS_EARTH_ELEMENTAL,   { "rock", BROWN } },
1067     { MONS_ROCKSLIME,         { "rock", BROWN } },
1068     { MONS_USHABTI,           { "rock", BROWN } },
1069     { MONS_STATUE,            { "rock", BROWN } },
1070     { MONS_GARGOYLE,          { "rock", BROWN } },
1071     { MONS_IRON_ELEMENTAL,    { "metal", CYAN, frag_damage_type::metal } },
1072     { MONS_IRON_GOLEM,        { "metal", CYAN, frag_damage_type::metal } },
1073     { MONS_PEACEKEEPER,       { "metal", CYAN, frag_damage_type::metal } },
1074     { MONS_WAR_GARGOYLE,      { "metal", CYAN, frag_damage_type::metal } },
1075     { MONS_CRYSTAL_GUARDIAN,  { "crystal", DARKGREY,
1076                                 frag_damage_type::crystal } },
1077     { MONS_ORANGE_STATUE,     { "orange crystal", LIGHTRED,
1078                                 frag_damage_type::crystal } },
1079     { MONS_OBSIDIAN_STATUE,   { "obsidian", GREEN,
1080                                 frag_damage_type::crystal } },
1081     { MONS_ROXANNE,           { "sapphire", BLUE, frag_damage_type::crystal } },
1082 };
1083 
1084 // Initializes the provided frag_effect with the appropriate Lee's Rapid
1085 // Deconstruction explosion for blowing up the given monster. Return true iff
1086 // that monster can be deconstructed.
_init_frag_monster(frag_effect & effect,const monster & mon)1087 static bool _init_frag_monster(frag_effect &effect, const monster &mon)
1088 {
1089     auto frag_f = fraggable_monsters.find(mon.type);
1090     if (frag_f != fraggable_monsters.end())
1091     {
1092         const monster_frag &frag = frag_f->second;
1093         effect.damage = frag.damage;
1094         const bool crystal = frag.damage == frag_damage_type::crystal;
1095         effect.name = make_stringf("blast of %s %s", frag.type,
1096                                    crystal ? "shards" : "fragments");
1097         effect.colour = frag.colour;
1098         return true;
1099     }
1100 
1101     // Petrifying or petrified monsters can be exploded.
1102     if (mon.petrified() || mon.petrifying())
1103     {
1104         monster_info minfo(&mon);
1105         effect.name       = "blast of petrified fragments";
1106         effect.colour     = minfo.colour();
1107         return true;
1108     }
1109     if (mon.is_icy()) // blast of ice
1110     {
1111         effect.name       = "icy blast";
1112         effect.colour     = WHITE;
1113         effect.damage     = frag_damage_type::ice;
1114         return true;
1115     }
1116     if (mon.is_skeletal()) // blast of bone
1117     {
1118         effect.name   = "blast of bone shards";
1119         effect.colour = LIGHTGREY;
1120         return true;
1121     }
1122     // Targeted monster not shatterable.
1123     return false;
1124 }
1125 
1126 struct feature_frag
1127 {
1128     const char* type;
1129     const char* what;
1130     frag_damage_type damage;
1131 };
1132 
1133 static const map<dungeon_feature_type, feature_frag> fraggable_terrain = {
1134     // Stone and rock terrain
1135     { DNGN_ROCK_WALL, { "rock", "wall" } },
1136     { DNGN_SLIMY_WALL, { "rock", "wall" } },
1137     { DNGN_STONE_WALL, { "rock", "wall" } },
1138     { DNGN_CLEAR_ROCK_WALL, { "rock", "wall" } },
1139     { DNGN_CLEAR_STONE_WALL, { "rock", "wall" } },
1140     { DNGN_ORCISH_IDOL, { "rock", "stone idol" } },
1141     { DNGN_GRANITE_STATUE, { "rock", "statue" } },
1142     { DNGN_PETRIFIED_TREE, { "rock", "petrified wood" } },
1143     // Stone arches and doors
1144     { DNGN_OPEN_DOOR, { "rock", "stone door frame" } },
1145     { DNGN_OPEN_CLEAR_DOOR, { "rock", "stone door frame" } },
1146     { DNGN_CLOSED_DOOR, { "rock", "stone door frame" } },
1147     { DNGN_CLOSED_CLEAR_DOOR, { "rock", "stone door frame" } },
1148     { DNGN_RUNED_DOOR, { "rock", "stone door frame" } },
1149     { DNGN_RUNED_CLEAR_DOOR, { "rock", "stone door frame" } },
1150     { DNGN_SEALED_DOOR, { "rock", "stone door frame" } },
1151     { DNGN_SEALED_CLEAR_DOOR, { "rock", "stone door frame" } },
1152     { DNGN_STONE_ARCH, { "rock", "stone arch" } },
1153     // Metal -- small but nasty explosion
1154     { DNGN_METAL_WALL, { "metal", "metal wall", frag_damage_type::metal } },
1155     { DNGN_GRATE, { "metal", "iron grate", frag_damage_type::metal } },
1156     // Crystal -- large & nasty explosion
1157     { DNGN_CRYSTAL_WALL, { "crystal", "crystal wall",
1158                            frag_damage_type::crystal } },
1159 };
1160 
1161 // Initializes the provided frag_effect with the appropriate Lee's Rapid
1162 // Deconstruction explosion for the given target. Return true iff the target
1163 // can be deconstructed.
_init_frag_grid(frag_effect & effect,coord_def target,const char ** what)1164 static bool _init_frag_grid(frag_effect &effect,
1165                             coord_def target, const char **what)
1166 {
1167     const dungeon_feature_type grid = env.grid(target);
1168 
1169     auto frag_f = fraggable_terrain.find(grid);
1170     if (frag_f == fraggable_terrain.end())
1171         return false;
1172     const feature_frag &frag = frag_f->second;
1173 
1174     effect.damage = frag.damage;
1175     const bool crystal = frag.damage == frag_damage_type::crystal;
1176     effect.name = make_stringf("blast of %s %s", frag.type,
1177                                crystal ? "shards" : "fragments");
1178     if (what)
1179         *what = frag.what;
1180 
1181     if (!feat_is_solid(grid))
1182         effect.hit_centre = true; // to hit monsters standing on doors
1183 
1184    // If it was recoloured, use that colour instead.
1185    if (env.grid_colours(target))
1186        effect.colour = env.grid_colours(target);
1187    else
1188    {
1189        effect.colour = element_colour(get_feature_def(grid).colour(),
1190                                      false, target);
1191    }
1192     return true;
1193 }
1194 
1195 // Initializes the provided frag_effect with the appropriate Lee's Rapid
1196 // Deconstruction explosion for the given target.
_init_frag_effect(frag_effect & effect,const actor & caster,coord_def target,const char ** what)1197 static bool _init_frag_effect(frag_effect &effect, const actor &caster,
1198                               coord_def target, const char **what)
1199 {
1200     if (target == you.pos() && _init_frag_player(effect))
1201     {
1202         effect.direct = true;
1203         return true;
1204     }
1205 
1206     const actor* victim = actor_at(target);
1207     if (victim && victim->alive() && victim->is_monster()
1208         && caster.can_see(*victim)
1209         && _init_frag_monster(effect, *victim->as_monster()))
1210     {
1211         return true;
1212     }
1213 
1214     return _init_frag_grid(effect, target, what);
1215 }
1216 
setup_fragmentation_beam(bolt & beam,int pow,const actor * caster,const coord_def target,bool quiet,const char ** what,bool & hole)1217 bool setup_fragmentation_beam(bolt &beam, int pow, const actor *caster,
1218                               const coord_def target, bool quiet,
1219                               const char **what, bool &hole)
1220 {
1221     beam.glyph       = dchar_glyph(DCHAR_FIRED_BURST);
1222     beam.source_id   = caster->mid;
1223     beam.thrower     = caster->is_player() ? KILL_YOU : KILL_MON;
1224     beam.source      = you.pos();
1225     beam.hit         = AUTOMATIC_HIT;
1226 
1227     frag_effect effect = {};
1228     if (!_init_frag_effect(effect, *caster, target, what))
1229     {
1230         // Couldn't find a monster or wall to shatter - abort casting!
1231         if (caster->is_player() && !quiet)
1232             mpr("You can't deconstruct that!");
1233         return false;
1234     }
1235 
1236     beam.colour = effect.colour;
1237     beam.name = effect.name;
1238     if (effect.direct) // cast on the player, not next to them
1239         beam.aux_source = "by Lee's Rapid Deconstruction";
1240     else
1241         beam.aux_source = effect.name;
1242 
1243     beam.damage  = base_fragmentation_damage(pow);
1244     beam.flavour = BEAM_FRAG;
1245     beam.ex_size = 1;
1246     switch (effect.damage)
1247     {
1248         case frag_damage_type::rock:
1249         default:
1250             break;
1251         case frag_damage_type::metal:
1252             beam.damage.num++;
1253             break;
1254         case frag_damage_type::crystal:
1255             beam.damage.num++;
1256             beam.ex_size++;
1257             break;
1258         case frag_damage_type::ice:
1259             beam.flavour = BEAM_ICE;
1260             break;
1261         case frag_damage_type::player_gargoyle:
1262             beam.damage.num--;
1263             break;
1264     }
1265 
1266     if (effect.hit_centre)
1267         hole = false;
1268 
1269     beam.source_name = caster->name(DESC_PLAIN, true);
1270     beam.target = target;
1271 
1272     return true;
1273 }
1274 
cast_fragmentation(int pow,const actor * caster,const coord_def target,bool fail)1275 spret cast_fragmentation(int pow, const actor *caster,
1276                               const coord_def target, bool fail)
1277 {
1278     bool hole                = true;
1279     const char *what         = nullptr;
1280 
1281     bolt beam;
1282 
1283     if (!setup_fragmentation_beam(beam, pow, caster, target, false, &what,
1284                                   hole))
1285     {
1286         return spret::abort;
1287     }
1288 
1289     if (caster->is_player())
1290     {
1291         bolt tempbeam;
1292         bool temp;
1293         setup_fragmentation_beam(tempbeam, pow, caster, target, true, nullptr,
1294                                  temp);
1295         tempbeam.is_tracer = true;
1296         tempbeam.explode(false);
1297         if (tempbeam.beam_cancelled)
1298         {
1299             canned_msg(MSG_OK);
1300             return spret::abort;
1301         }
1302     }
1303 
1304     fail_check();
1305 
1306     if (what != nullptr) // Terrain explodes.
1307     {
1308         if (you.see_cell(target))
1309             mprf("The %s shatters!", what);
1310     }
1311     else if (target == you.pos()) // You explode.
1312     {
1313         const int dam = beam.damage.roll();
1314         mprf("You shatter%s", attack_strength_punctuation(dam).c_str());
1315 
1316         ouch(dam, KILLED_BY_BEAM, caster->mid,
1317              "by Lee's Rapid Deconstruction", true,
1318              caster->is_player() ? "you"
1319                                  : caster->name(DESC_A).c_str());
1320     }
1321     else // Monster explodes.
1322     {
1323         // Checks by setup_fragmentation_beam() must guarantee that we have an
1324         // alive monster.
1325         monster* mon = monster_at(target);
1326         ASSERT(mon);
1327 
1328         const int dam = beam.damage.roll();
1329         if (you.see_cell(target))
1330         {
1331             mprf("%s shatters%s", mon->name(DESC_THE).c_str(),
1332                  attack_strength_punctuation(dam).c_str());
1333         }
1334 
1335         if (caster->is_player())
1336         {
1337             _player_hurt_monster(*mon, dam, BEAM_MINDBURST);
1338             if (mon->alive())
1339                 you.pet_target = mon->mindex();
1340         }
1341         else if (dam)
1342             mon->hurt(caster, dam, BEAM_MINDBURST);
1343     }
1344 
1345     beam.explode(true, hole);
1346 
1347     return spret::success;
1348 }
1349 
_shatter_mon_dice(const monster * mon)1350 static int _shatter_mon_dice(const monster *mon)
1351 {
1352     if (!mon)
1353         return DEFAULT_SHATTER_DICE;
1354 
1355     // Double damage to stone, metal and crystal - the same as the list of
1356     // monsters affected by LRD.
1357     if (map_find(fraggable_monsters, mon->type))
1358         return DEFAULT_SHATTER_DICE * 2;
1359     if (mon->is_insubstantial())
1360         return 1;
1361     if (mon->petrifying() || mon->petrified()
1362         || mon->is_skeletal() || mon->is_icy())
1363     {
1364         return DEFAULT_SHATTER_DICE * 2;
1365     }
1366     else if (mon->airborne() || mons_is_slime(*mon))
1367         return 1;
1368     // Normal damage to everything else.
1369     else
1370         return DEFAULT_SHATTER_DICE;
1371 }
1372 
shatter_damage(int pow,monster * mon)1373 dice_def shatter_damage(int pow, monster *mon)
1374 {
1375     return dice_def(_shatter_mon_dice(mon), 5 + pow / 3);
1376 }
1377 
_shatter_monsters(coord_def where,int pow,actor * agent)1378 static int _shatter_monsters(coord_def where, int pow, actor *agent)
1379 {
1380     monster* mon = monster_at(where);
1381 
1382     if (!mon || !mon->alive() || mon == agent || mons_is_conjured(mon->type))
1383         return 0;
1384 
1385     const dice_def dam_dice = shatter_damage(pow, mon);
1386     int damage = max(0, dam_dice.roll() - random2(mon->armour_class()));
1387 
1388     if (agent->is_player())
1389         _player_hurt_monster(*mon, damage, BEAM_MMISSILE);
1390     else if (damage)
1391         mon->hurt(agent, damage);
1392 
1393     return damage;
1394 }
1395 
1396 static const map<dungeon_feature_type, int> terrain_shatter_chances = {
1397     { DNGN_CLOSED_DOOR,     100 }, // also applies to all other door types
1398     { DNGN_GRATE,           100 },
1399     { DNGN_ORCISH_IDOL,     100 },
1400     { DNGN_GRANITE_STATUE,  100 },
1401     { DNGN_CLEAR_ROCK_WALL,  33 },
1402     { DNGN_ROCK_WALL,        33 },
1403     { DNGN_SLIMY_WALL,       33 },
1404     { DNGN_CRYSTAL_WALL,     33 },
1405     { DNGN_TREE,             33 }, // also applies to all other types of tree
1406     { DNGN_CLEAR_STONE_WALL, 25 },
1407     { DNGN_STONE_WALL,       25 },
1408     { DNGN_METAL_WALL,       15 },
1409 };
1410 
1411 // Returns a percentage chance of the given wall being shattered by the given
1412 // agent casting Shatter, where 100 is guaranteed.
terrain_shatter_chance(coord_def where,const actor & agent)1413 int terrain_shatter_chance(coord_def where, const actor &agent)
1414 {
1415     // if not in-bounds then we can't really shatter it -- bwr
1416     if (!in_bounds(where))
1417         return 0;
1418 
1419     if (env.markers.property_at(where, MAT_ANY, "veto_destroy") == "veto")
1420         return 0;
1421 
1422     dungeon_feature_type feat = env.grid(where);
1423     if (feat_is_tree(feat))
1424     {
1425         if (agent.deity() == GOD_FEDHAS)
1426             return 0;
1427         feat = DNGN_TREE;
1428     }
1429     else if (feat_is_door(feat))
1430         feat = DNGN_CLOSED_DOOR;
1431 
1432     auto feat_chance = terrain_shatter_chances.find(feat);
1433     if (feat_chance == terrain_shatter_chances.end())
1434         return 0;
1435     return feat_chance->second;
1436 }
1437 
_shatter_walls(coord_def where,actor * agent)1438 static int _shatter_walls(coord_def where, actor *agent)
1439 {
1440     const int chance = terrain_shatter_chance(where, *agent);
1441 
1442     if (!x_chance_in_y(chance, 100))
1443         return 0;
1444 
1445     if (you.see_cell(where))
1446     {
1447         const dungeon_feature_type feat = env.grid(where);
1448         if (feat_is_door(feat))
1449             mpr("A door shatters!");
1450         else if (feat == DNGN_GRATE)
1451             mpr("An iron grate is ripped into pieces!");
1452     }
1453 
1454     noisy(spell_effect_noise(SPELL_SHATTER), where);
1455     destroy_wall(where);
1456     return 1;
1457 }
1458 
_shatter_player_dice()1459 static int _shatter_player_dice()
1460 {
1461     if (you.is_insubstantial())
1462         return 1;
1463     if (you.petrified() || you.petrifying())
1464         return 6; // reduced later by petrification's damage reduction
1465     else if (you.form == transformation::statue
1466              || you.form == transformation::ice_beast
1467              || you.species == SP_GARGOYLE)
1468         return 6;
1469     else if (you.airborne())
1470         return 1;
1471     else
1472         return 3;
1473 }
1474 
1475 /**
1476  * Is this a valid target for shatter?
1477  *
1478  * @param act     The actor being considered
1479  * @return        Whether the actor will take damage from shatter.
1480  */
_shatterable(const actor * act)1481 static bool _shatterable(const actor *act)
1482 {
1483     if (act->is_player())
1484         return _shatter_player_dice();
1485     return !mons_is_conjured(act->as_monster()->type)
1486            && _shatter_mon_dice(act->as_monster());
1487 }
1488 
cast_shatter(int pow,bool fail)1489 spret cast_shatter(int pow, bool fail)
1490 {
1491     targeter_radius hitfunc(&you, LOS_ARENA);
1492     auto vulnerable = [](const actor *act) -> bool
1493     {
1494         return !act->is_player()
1495                && !god_protects(act->as_monster())
1496                && _shatterable(act);
1497     };
1498     if (stop_attack_prompt(hitfunc, "attack", vulnerable))
1499         return spret::abort;
1500 
1501     fail_check();
1502     const bool silence = silenced(you.pos());
1503 
1504     if (silence)
1505         mpr("The dungeon shakes!");
1506     else
1507     {
1508         noisy(spell_effect_noise(SPELL_SHATTER), you.pos());
1509         mprf(MSGCH_SOUND, "The dungeon rumbles!");
1510     }
1511 
1512     run_animation(ANIMATION_SHAKE_VIEWPORT, UA_PLAYER);
1513 
1514     int dest = 0;
1515     for (distance_iterator di(you.pos(), true, true, LOS_RADIUS); di; ++di)
1516     {
1517         // goes from the center out, so newly dug walls recurse
1518         if (!cell_see_cell(you.pos(), *di, LOS_SOLID))
1519             continue;
1520 
1521         _shatter_monsters(*di, pow, &you);
1522         dest += _shatter_walls(*di, &you);
1523     }
1524 
1525     if (dest && !silence)
1526         mprf(MSGCH_SOUND, "Ka-crash!");
1527 
1528     return spret::success;
1529 }
1530 
_shatter_player(int pow,actor * wielder,bool devastator=false)1531 static int _shatter_player(int pow, actor *wielder, bool devastator = false)
1532 {
1533     if (wielder->is_player())
1534         return 0;
1535 
1536     dice_def dam_dice(_shatter_player_dice(), 5 + pow / 3);
1537 
1538     int damage = max(0, dam_dice.roll() - random2(you.armour_class()));
1539 
1540     if (damage > 0)
1541     {
1542         mprf(damage > 15 ? "You shudder from the earth-shattering force%s"
1543                         : "You shudder%s",
1544              attack_strength_punctuation(damage).c_str());
1545         if (devastator)
1546             ouch(damage, KILLED_BY_MONSTER, wielder->mid);
1547         else
1548             ouch(damage, KILLED_BY_BEAM, wielder->mid, "by Shatter");
1549     }
1550 
1551     return damage;
1552 }
1553 
mons_shatter(monster * caster,bool actual)1554 bool mons_shatter(monster* caster, bool actual)
1555 {
1556     const bool silence = silenced(caster->pos());
1557     int foes = 0;
1558 
1559     if (actual)
1560     {
1561         if (silence)
1562         {
1563             mprf("The dungeon shakes around %s!",
1564                  caster->name(DESC_THE).c_str());
1565         }
1566         else
1567         {
1568             noisy(spell_effect_noise(SPELL_SHATTER), caster->pos(), caster->mid);
1569             mprf(MSGCH_SOUND, "The dungeon rumbles around %s!",
1570                  caster->name(DESC_THE).c_str());
1571         }
1572     }
1573 
1574     int pow = 5 + div_rand_round(caster->get_hit_dice() * 9, 2);
1575 
1576     int dest = 0;
1577     for (distance_iterator di(caster->pos(), true, true, LOS_RADIUS); di; ++di)
1578     {
1579         // goes from the center out, so newly dug walls recurse
1580         if (!cell_see_cell(caster->pos(), *di, LOS_SOLID))
1581             continue;
1582 
1583         if (actual)
1584         {
1585             _shatter_monsters(*di, pow, caster);
1586             if (*di == you.pos())
1587                 _shatter_player(pow, caster);
1588             dest += _shatter_walls(*di, caster);
1589         }
1590         else
1591         {
1592             if (you.pos() == *di)
1593                 foes -= _shatter_player_dice();
1594             if (const monster *victim = monster_at(*di))
1595             {
1596                 dprf("[%s]", victim->name(DESC_PLAIN, true).c_str());
1597                 foes += _shatter_mon_dice(victim)
1598                      * (victim->wont_attack() ? -1 : 1);
1599             }
1600         }
1601     }
1602 
1603     if (dest && !silence)
1604         mprf(MSGCH_SOUND, "Ka-crash!");
1605 
1606     if (actual)
1607         run_animation(ANIMATION_SHAKE_VIEWPORT, UA_MONSTER);
1608 
1609     if (!caster->wont_attack())
1610         foes *= -1;
1611 
1612     if (!actual)
1613         dprf("Shatter foe HD: %d", foes);
1614 
1615     return foes > 0; // doesn't matter if actual
1616 }
1617 
shillelagh(actor * wielder,coord_def where,int pow)1618 void shillelagh(actor *wielder, coord_def where, int pow)
1619 {
1620     bolt beam;
1621     beam.name = "shillelagh";
1622     beam.flavour = BEAM_VISUAL;
1623     beam.set_agent(wielder);
1624     beam.colour = BROWN;
1625     beam.glyph = dchar_glyph(DCHAR_EXPLOSION);
1626     beam.range = 1;
1627     beam.ex_size = 1;
1628     beam.is_explosion = true;
1629     beam.source = wielder->pos();
1630     beam.target = where;
1631     beam.hit = AUTOMATIC_HIT;
1632     beam.loudness = 7;
1633     beam.explode();
1634 
1635     counted_monster_list affected_monsters;
1636     for (adjacent_iterator ai(where, false); ai; ++ai)
1637     {
1638         monster *mon = monster_at(*ai);
1639         if (!mon || !mon->alive() || mon->submerged()
1640             || mon->is_insubstantial() || !you.can_see(*mon)
1641             || mon == wielder)
1642         {
1643             continue;
1644         }
1645         affected_monsters.add(mon);
1646     }
1647     if (!affected_monsters.empty())
1648     {
1649         const string message =
1650             make_stringf("%s shudder%s.",
1651                          affected_monsters.describe().c_str(),
1652                          affected_monsters.count() == 1? "s" : "");
1653         if (strwidth(message) < get_number_of_cols() - 2)
1654             mpr(message);
1655         else
1656             mpr("There is a shattering impact!");
1657     }
1658 
1659     // need to do this again to do the actual damage
1660     for (adjacent_iterator ai(where, false); ai; ++ai)
1661         _shatter_monsters(*ai, pow * 3 / 2, wielder);
1662 
1663     if ((you.pos() - wielder->pos()).rdist() <= 1 && in_bounds(you.pos()))
1664         _shatter_player(pow, wielder, true);
1665 }
1666 
irradiate_damage(int pow,bool random)1667 dice_def irradiate_damage(int pow, bool random)
1668 {
1669     const int dice = 3;
1670     const int max_dam = 40 + (random ? div_rand_round(pow, 2) : pow / 2);
1671     return calc_dice(dice, max_dam);
1672 }
1673 
1674 /**
1675  * Irradiate the given cell. (Per the spell.)
1676  *
1677  * @param where     The cell in question.
1678  * @param pow       The power with which the spell is being cast.
1679  * @param agent     The agent (player or monster) doing the irradiating.
1680  */
_irradiate_cell(coord_def where,int pow,actor * agent)1681 static int _irradiate_cell(coord_def where, int pow, actor *agent)
1682 {
1683     monster *mons = monster_at(where);
1684     if (!mons || !mons->alive())
1685         return 0; // XXX: handle damaging the player for mons casts...?
1686 
1687     const dice_def dam_dice = irradiate_damage(pow);
1688     const int base_dam = dam_dice.roll();
1689     const int dam = mons->apply_ac(base_dam);
1690 
1691     if (god_protects(mons, false))
1692         return 0;
1693 
1694     mprf("%s is blasted with magical radiation%s",
1695          mons->name(DESC_THE).c_str(),
1696          attack_strength_punctuation(dam).c_str());
1697 
1698     if (agent->is_player())
1699         _player_hurt_monster(*mons, dam, BEAM_MMISSILE);
1700     else if (dam)
1701         mons->hurt(agent, dam, BEAM_MMISSILE);
1702 
1703     if (mons->alive())
1704         mons->malmutate("");
1705 
1706     return dam;
1707 }
1708 
1709 /**
1710  * Attempt to cast the spell "Irradiate", damaging & deforming enemies around
1711  * the player.
1712  *
1713  * @param pow   The power at which the spell is being cast.
1714  * @param who   The actor doing the irradiating.
1715  * @param fail  Whether the player has failed to cast the spell.
1716  * @return      spret::abort if the player changed their mind about casting after
1717  *              realizing they would hit an ally; spret::fail if they failed the
1718  *              cast chance; spret::success otherwise.
1719  */
cast_irradiate(int powc,actor * who,bool fail)1720 spret cast_irradiate(int powc, actor* who, bool fail)
1721 {
1722     targeter_radius hitfunc(who, LOS_NO_TRANS, 1, 0, 1);
1723     auto vulnerable = [who](const actor *act) -> bool
1724     {
1725         return !act->is_player()
1726                && !god_protects(who, act->as_monster());
1727     };
1728 
1729     if (stop_attack_prompt(hitfunc, "irradiate", vulnerable))
1730         return spret::abort;
1731 
1732     fail_check();
1733 
1734     ASSERT(who);
1735     if (who->is_player())
1736         mpr("You erupt in a fountain of uncontrolled magic!");
1737     else
1738     {
1739         simple_monster_message(*who->as_monster(),
1740                                " erupts in a fountain of uncontrolled magic!");
1741     }
1742 
1743     bolt beam;
1744     beam.name = "irradiate";
1745     beam.flavour = BEAM_VISUAL;
1746     beam.set_agent(&you);
1747     beam.colour = ETC_MUTAGENIC;
1748     beam.glyph = dchar_glyph(DCHAR_EXPLOSION);
1749     beam.range = 1;
1750     beam.ex_size = 1;
1751     beam.is_explosion = true;
1752     beam.explode_delay = beam.explode_delay * 3 / 2;
1753     beam.source = you.pos();
1754     beam.target = you.pos();
1755     beam.hit = AUTOMATIC_HIT;
1756     beam.explode(true, true);
1757 
1758     apply_random_around_square([powc, who] (coord_def where) {
1759         return _irradiate_cell(where, powc, who);
1760     }, who->pos(), true, 8);
1761 
1762     if (who->is_player())
1763         contaminate_player(1000 + random2(500));
1764     return spret::success;
1765 }
1766 
1767 // How much work can we consider we'll have done by igniting a cloud here?
1768 // Considers a cloud under a susceptible ally bad, a cloud under a a susceptible
1769 // enemy good, and other clouds relatively unimportant.
_ignite_tracer_cloud_value(coord_def where,actor * agent)1770 static int _ignite_tracer_cloud_value(coord_def where, actor *agent)
1771 {
1772     actor* act = actor_at(where);
1773     if (act)
1774     {
1775         const int dam = actor_cloud_immune(*act, CLOUD_FIRE)
1776                         ? 0
1777                         : resist_adjust_damage(act, BEAM_FIRE, 40);
1778 
1779         if (god_protects(agent, act->as_monster()))
1780             return 0;
1781 
1782         return mons_aligned(act, agent) ? -dam : dam;
1783     }
1784     // We've done something, but its value is indeterminate
1785     else
1786         return 1;
1787 }
1788 
1789 /**
1790  * Place flame clouds over toxic bogs, by the power of Ignite Poison.
1791  *
1792  * @param where     The tile in question.
1793  * @param pow       The power with which Ignite Poison is being cast.
1794  *                  If -1, this indicates the spell is a test-run 'tracer'.
1795  * @param agent     The caster of Ignite Poison.
1796  * @return          If we're just running a tracer, return the expected 'value'
1797  *                  of creating fire clouds in the given location (could be
1798  *                  negative if there are allies there).
1799  *                  If it's not a tracer, return 1 if a flame cloud is created
1800  *                  and 0 otherwise.
1801  */
_ignite_poison_bog(coord_def where,int pow,actor * agent)1802 static int _ignite_poison_bog(coord_def where, int pow, actor *agent)
1803 {
1804     const bool tracer = (pow == -1);  // Only testing damage, not dealing it
1805 
1806     if (env.grid(where) != DNGN_TOXIC_BOG)
1807         return false;
1808 
1809     if (tracer)
1810     {
1811         const int value = _ignite_tracer_cloud_value(where, agent);
1812         // Player doesn't care about magnitude.
1813         return agent && agent->is_player() ? sgn(value) : value;
1814     }
1815 
1816     // Tone down bog clouds
1817     if (!one_chance_in(4))
1818         return false;
1819 
1820     place_cloud(CLOUD_FIRE, where, 2 + random2(pow / 30), agent);
1821 
1822     return true;
1823 }
1824 
1825 /**
1826  * Turn poisonous clouds in the given tile into flame clouds, by the power of
1827  * Ignite Poison.
1828  *
1829  * @param where     The tile in question.
1830  * @param pow       The power with which Ignite Poison is being cast.
1831  *                  If -1, this indicates the spell is a test-run 'tracer'.
1832  * @param agent     The caster of Ignite Poison.
1833  * @return          If we're just running a tracer, return the expected 'value'
1834  *                  of creating fire clouds in the given location (could be
1835  *                  negative if there are allies there).
1836  *                  If it's not a tracer, return 1 if a flame cloud is created
1837  *                  and 0 otherwise.
1838  */
_ignite_poison_clouds(coord_def where,int pow,actor * agent)1839 static int _ignite_poison_clouds(coord_def where, int pow, actor *agent)
1840 {
1841     const bool tracer = (pow == -1);  // Only testing damage, not dealing it
1842 
1843     cloud_struct* cloud = cloud_at(where);
1844     if (!cloud)
1845         return false;
1846 
1847     if (cloud->type != CLOUD_MEPHITIC && cloud->type != CLOUD_POISON)
1848         return false;
1849 
1850     if (tracer)
1851     {
1852         const int value = _ignite_tracer_cloud_value(where, agent);
1853         // Player doesn't care about magnitude.
1854         return agent && agent->is_player() ? sgn(value) : value;
1855     }
1856 
1857     cloud->type = CLOUD_FIRE;
1858     cloud->decay = 30 + random2(20 + pow); // from 3-5 turns to 3-15 turns
1859     cloud->whose = agent->kill_alignment();
1860     cloud->killer = agent->is_player() ? KILL_YOU_MISSILE : KILL_MON_MISSILE;
1861     cloud->source = agent->mid;
1862     return true;
1863 }
1864 
1865 /**
1866  * Burn poisoned monsters in the given tile, removing their poison state &
1867  * damaging them.
1868  *
1869  * @param where     The tile in question.
1870  * @param pow       The power with which Ignite Poison is being cast.
1871  *                  If -1, this indicates the spell is a test-run 'tracer'.
1872  * @param agent     The caster of Ignite Poison.
1873  * @return          If we're just running a tracer, return the expected damage
1874  *                  of burning the monster in the given location (could be
1875  *                  negative if there are allies there).
1876  *                  If it's not a tracer, return 1 if damage is caused & 0
1877  *                  otherwise.
1878  */
_ignite_poison_monsters(coord_def where,int pow,actor * agent)1879 static int _ignite_poison_monsters(coord_def where, int pow, actor *agent)
1880 {
1881     bolt beam;
1882     beam.flavour = BEAM_FIRE;   // This is dumb, only used for adjust!
1883 
1884     const bool tracer = (pow == -1);  // Only testing damage, not dealing it
1885     if (tracer)                       // Give some fake damage to test resists
1886         pow = 100;
1887 
1888     // If a monster casts Ignite Poison, it can't hit itself.
1889     // This doesn't apply to the other functions: it can ignite
1890     // clouds where it's standing!
1891 
1892     monster* mon = monster_at(where);
1893     if (invalid_monster(mon) || mon == agent)
1894         return 0;
1895 
1896     // how poisoned is the victim?
1897     const mon_enchant ench = mon->get_ench(ENCH_POISON);
1898     const int pois_str = ench.ench == ENCH_NONE ? 0 : ench.degree;
1899 
1900     // poison currently does roughly 6 damage per degree (over its duration)
1901     // do roughly 2x to 3x that much, scaling with spellpower
1902     const dice_def dam_dice(pois_str * 2, 12 + div_rand_round(pow * 6, 100));
1903 
1904     const int base_dam = dam_dice.roll();
1905     const int damage = mons_adjust_flavoured(mon, beam, base_dam, false);
1906     if (damage <= 0)
1907         return 0;
1908 
1909     if (god_protects(agent, mon, tracer))
1910         return 0;
1911 
1912     mon->expose_to_element(BEAM_FIRE, damage);
1913 
1914     if (tracer)
1915     {
1916         // players don't care about magnitude, just care if enemies exist
1917         if (agent && agent->is_player())
1918             return mons_aligned(mon, agent) ? -1 : 1;
1919         return mons_aligned(mon, agent) ? -1 * damage : damage;
1920     }
1921 
1922     if (you.see_cell(mon->pos()))
1923     {
1924         mprf("%s seems to burn from within%s",
1925              mon->name(DESC_THE).c_str(),
1926              attack_strength_punctuation(damage).c_str());
1927     }
1928 
1929     dprf("Dice: %dd%d; Damage: %d", dam_dice.num, dam_dice.size, damage);
1930 
1931     mon->hurt(agent, damage);
1932 
1933     if (mon->alive())
1934     {
1935         behaviour_event(mon, ME_WHACK, agent);
1936 
1937         // Monster survived, remove any poison.
1938         mon->del_ench(ENCH_POISON, true); // suppress spam
1939         print_wounds(*mon);
1940     }
1941 
1942     return 1;
1943 }
1944 
1945 /**
1946  * Burn poisoned players in the given tile, removing their poison state &
1947  * damaging them.
1948  *
1949  * @param where     The tile in question.
1950  * @param pow       The power with which Ignite Poison is being cast.
1951  *                  If -1, this indicates the spell is a test-run 'tracer'.
1952  * @param agent     The caster of Ignite Poison.
1953  * @return          If we're just running a tracer, return the expected damage
1954  *                  of burning the player in the given location (could be
1955  *                  negative if the player is an ally).
1956  *                  If it's not a tracer, return 1 if damage is caused & 0
1957  *                  otherwise.
1958  */
1959 
_ignite_poison_player(coord_def where,int pow,actor * agent)1960 static int _ignite_poison_player(coord_def where, int pow, actor *agent)
1961 {
1962     if (agent->is_player() || where != you.pos())
1963         return 0;
1964 
1965     const bool tracer = (pow == -1);  // Only testing damage, not dealing it
1966     if (tracer)                       // Give some fake damage to test resists
1967         pow = 100;
1968 
1969     // Step down heavily beyond light poisoning (or we could easily one-shot a heavily poisoned character)
1970     const int pois_str = stepdown((double)you.duration[DUR_POISONING] / 5000,
1971                                   2.25);
1972     if (!pois_str)
1973         return 0;
1974 
1975     const int base_dam = roll_dice(pois_str, 5 + pow/7);
1976     const int damage = resist_adjust_damage(&you, BEAM_FIRE, base_dam);
1977 
1978     if (tracer)
1979         return mons_aligned(&you, agent) ? -1 * damage : damage;
1980 
1981     const int resist = player_res_fire();
1982     if (resist > 0)
1983         mpr("You feel like your blood is boiling!");
1984     else if (resist < 0)
1985         mpr("The poison in your system burns terribly!");
1986     else
1987         mpr("The poison in your system burns!");
1988 
1989     ouch(damage, KILLED_BY_BEAM, agent->mid,
1990          "by burning poison", you.can_see(*agent),
1991          agent->as_monster()->name(DESC_A, true).c_str());
1992     if (damage > 0)
1993         you.expose_to_element(BEAM_FIRE, 2);
1994 
1995     mprf(MSGCH_RECOVERY, "You are no longer poisoned.");
1996     you.duration[DUR_POISONING] = 0;
1997 
1998     return damage ? 1 : 0;
1999 }
2000 
2001 /**
2002  * Would casting Ignite Poison possibly harm one of the player's allies in the
2003  * given cell?
2004  *
2005  * @param  where    The cell in question.
2006  * @return          1 if there's potential harm, 0 otherwise.
2007  */
_ignite_ally_harm(const coord_def & where)2008 static int _ignite_ally_harm(const coord_def &where)
2009 {
2010     if (where == you.pos())
2011         return 0; // you're not your own ally!
2012     // (prevents issues with duplicate prompts when standing in an igniteable
2013     // cloud)
2014 
2015     return (_ignite_poison_clouds(where, -1, &you) < 0)   ? 1 :
2016            (_ignite_poison_monsters(where, -1, &you) < 0) ? 1 :
2017            (_ignite_poison_bog(where, -1, &you) < 0)      ? 1 :
2018             0;
2019 }
2020 
2021 /**
2022  * Let the player choose to abort a casting of ignite poison, if it seems
2023  * like a bad idea. (If they'd ignite themself.)
2024  *
2025  * @return      Whether the player chose to abort the casting.
2026  */
maybe_abort_ignite()2027 static bool maybe_abort_ignite()
2028 {
2029     string prompt = "You are standing ";
2030 
2031     // XXX XXX XXX major code duplication (ChrisOelmueller)
2032     if (const cloud_struct* cloud = cloud_at(you.pos()))
2033     {
2034         if ((cloud->type == CLOUD_MEPHITIC || cloud->type == CLOUD_POISON)
2035             && !actor_cloud_immune(you, CLOUD_FIRE))
2036         {
2037             prompt += "in a cloud of ";
2038             prompt += cloud->cloud_name(true);
2039             prompt += "! Ignite poison anyway?";
2040             if (!yesno(prompt.c_str(), false, 'n'))
2041                 return true;
2042         }
2043     }
2044 
2045     if (apply_area_visible(_ignite_ally_harm, you.pos()) > 0)
2046     {
2047         return !yesno("You might harm nearby allies! Ignite poison anyway?",
2048                       false, 'n');
2049     }
2050 
2051     return false;
2052 }
2053 
2054 /**
2055  * Does Ignite Poison affect the given creature?
2056  *
2057  * @param act       The creature in question.
2058  * @return          Whether Ignite Poison can directly damage the given
2059  *                  creature (not counting clouds).
2060  */
ignite_poison_affects(const actor * act)2061 bool ignite_poison_affects(const actor* act)
2062 {
2063     if (act->is_player())
2064         return you.duration[DUR_POISONING];
2065     return act->as_monster()->has_ench(ENCH_POISON);
2066 }
2067 
2068 /**
2069  * Does Ignite Poison do something to this cell?
2070  *
2071  * @param where       Where to look
2072  * @param agent     Who's casting
2073  * @return          If this cell will be affected
2074  */
ignite_poison_affects_cell(const coord_def where,actor * agent)2075 bool ignite_poison_affects_cell(const coord_def where, actor* agent)
2076 {
2077     return _ignite_poison_clouds(where, -1, agent)
2078          + _ignite_poison_monsters(where, -1, agent)
2079          + _ignite_poison_player(where, -1, agent)
2080          + _ignite_poison_bog(where, -1, agent) != 0;
2081 }
2082 
2083 /**
2084  * Cast the spell Ignite Poison, burning poisoned creatures and poisonous
2085  * clouds in LOS.
2086  *
2087  * @param agent         The spell's caster.
2088  * @param pow           The power with which the spell is being cast.
2089  * @param fail          If it's a player spell, whether the spell fail chance
2090  *                      was hit (whether the spell will fail as soon as the
2091  *                      player chooses not to abort the casting)
2092  * @param mon_tracer    Whether the 'casting' is just a tracer (a check to see
2093  *                      if it's worth actually casting)
2094  * @return              If it's a tracer, spret::success if the spell should
2095  *                      be cast & spret::abort otherwise.
2096  *                      If it's a real spell, spret::abort if the player chose
2097  *                      to abort the spell, spret::fail if they failed the cast
2098  *                      chance, and spret::success otherwise.
2099  */
cast_ignite_poison(actor * agent,int pow,bool fail,bool tracer)2100 spret cast_ignite_poison(actor* agent, int pow, bool fail, bool tracer)
2101 {
2102     if (tracer)
2103     {
2104         // Estimate how much useful effect we'd get if we cast the spell now
2105         const int work = apply_area_visible([agent] (coord_def where) {
2106             return _ignite_poison_clouds(where, -1, agent)
2107                  + _ignite_poison_monsters(where, -1, agent)
2108                  + _ignite_poison_player(where, -1, agent)
2109                  + _ignite_poison_bog(where, -1, agent);
2110         }, agent->pos());
2111 
2112         return work > 0 ? spret::success : spret::abort;
2113     }
2114 
2115     if (agent->is_player())
2116     {
2117         if (maybe_abort_ignite())
2118         {
2119             canned_msg(MSG_OK);
2120             return spret::abort;
2121         }
2122         fail_check();
2123     }
2124 
2125     targeter_radius hitfunc(agent, LOS_NO_TRANS);
2126     flash_view_delay(
2127         agent->is_player()
2128             ? UA_PLAYER
2129             : UA_MONSTER,
2130         RED, 100, &hitfunc);
2131 
2132     mprf("%s %s the poison in %s surroundings!", agent->name(DESC_THE).c_str(),
2133          agent->conj_verb("ignite").c_str(),
2134          agent->pronoun(PRONOUN_POSSESSIVE).c_str());
2135 
2136     // this could conceivably cause crashes if the player dies midway through
2137     // maybe split it up...?
2138     apply_area_visible([pow, agent] (coord_def where) {
2139         _ignite_poison_clouds(where, pow, agent);
2140         _ignite_poison_monsters(where, pow, agent);
2141         _ignite_poison_bog(where, pow, agent);
2142         // Only relevant if a monster is casting this spell
2143         // (never hurts the caster)
2144         _ignite_poison_player(where, pow, agent);
2145         return 0; // ignored
2146     }, agent->pos());
2147 
2148     return spret::success;
2149 }
2150 
_ignition_square(const actor *,bolt beam,coord_def square,bool center)2151 static void _ignition_square(const actor */*agent*/, bolt beam, coord_def square, bool center)
2152 {
2153     // HACK: bypass visual effect
2154     beam.target = square;
2155     beam.in_explosion_phase = true;
2156     beam.explosion_affect_cell(square);
2157     if (center)
2158         noisy(spell_effect_noise(SPELL_IGNITION),square);
2159 }
2160 
get_ignition_blast_sources(const actor * agent,bool tracer)2161 vector<coord_def> get_ignition_blast_sources(const actor *agent, bool tracer)
2162 {
2163     // Ignition affects squares that had hostile monsters on them at the time
2164     // of casting. This way nothing bad happens when monsters die halfway
2165     // through the spell.
2166     vector<coord_def> blast_sources;
2167     if (!agent)
2168         return blast_sources;
2169 
2170     for (actor_near_iterator ai(agent->pos(), LOS_NO_TRANS);
2171          ai; ++ai)
2172     {
2173         if (ai->is_monster()
2174             && !ai->as_monster()->wont_attack()
2175             && !mons_is_firewood(*ai->as_monster())
2176             && !mons_is_tentacle_segment(ai->as_monster()->type)
2177             && (!tracer || agent->can_see(**ai)))
2178         {
2179             blast_sources.push_back(ai->position);
2180         }
2181     }
2182     return blast_sources;
2183 }
2184 
cast_ignition(const actor * agent,int pow,bool fail)2185 spret cast_ignition(const actor *agent, int pow, bool fail)
2186 {
2187     ASSERT(agent->is_player());
2188 
2189     fail_check();
2190 
2191     //targeter_radius hitfunc(agent, LOS_NO_TRANS);
2192 
2193     // Ignition affects squares that had hostile monsters on them at the time
2194     // of casting. This way nothing bad happens when monsters die halfway
2195     // through the spell.
2196     vector<coord_def> blast_sources = get_ignition_blast_sources(agent);
2197 
2198     if (blast_sources.empty())
2199         canned_msg(MSG_NOTHING_HAPPENS);
2200     else
2201     {
2202         mpr("The air bursts into flame!");
2203 
2204         vector<coord_def> blast_adjacents;
2205 
2206         // Used to draw explosion cells
2207         bolt beam_visual;
2208         beam_visual.set_agent(agent);
2209         beam_visual.flavour       = BEAM_VISUAL;
2210         // XXX: why is this different from fireball?
2211         beam_visual.glyph         = dchar_glyph(DCHAR_FIRED_BURST);
2212         beam_visual.colour        = RED;
2213         beam_visual.ex_size       = 1;
2214         beam_visual.is_explosion  = true;
2215 
2216         // Used to deal damage; invisible
2217         bolt beam_actual;
2218         zappy(ZAP_IGNITION, pow, false, beam_actual);
2219         beam_actual.set_agent(agent);
2220         beam_actual.ex_size       = 0;
2221         beam_actual.apply_beam_conducts();
2222 
2223 #ifdef DEBUG_DIAGNOSTICS
2224         dprf(DIAG_BEAM, "ignition dam=%dd%d",
2225              beam_actual.damage.num, beam_actual.damage.size);
2226 #endif
2227 
2228         // Fake "shaped" radius 1 explosions (skipping squares with friends).
2229         for (coord_def pos : blast_sources)
2230         {
2231             for (adjacent_iterator ai(pos); ai; ++ai)
2232             {
2233                 if (cell_is_solid(*ai)
2234                     && (!beam_actual.can_affect_wall(*ai)
2235                         || you_worship(GOD_FEDHAS)))
2236                 {
2237                     continue;
2238                 }
2239 
2240                 actor *act = actor_at(*ai);
2241 
2242                 // Friendly creature, don't blast this square.
2243                 if (act && (act == agent
2244                             || (act->is_monster()
2245                                 && act->as_monster()->wont_attack())))
2246                 {
2247                     continue;
2248                 }
2249 
2250                 blast_adjacents.push_back(*ai);
2251                 if (Options.use_animations & UA_BEAM)
2252                     beam_visual.explosion_draw_cell(*ai);
2253             }
2254             if (Options.use_animations & UA_BEAM)
2255                 beam_visual.explosion_draw_cell(pos);
2256         }
2257         if (Options.use_animations & UA_BEAM)
2258         {
2259             viewwindow(false);
2260             update_screen();
2261             scaled_delay(50);
2262         }
2263 
2264         // Real explosions on each individual square.
2265         for (coord_def pos : blast_sources)
2266             _ignition_square(agent, beam_actual, pos, true);
2267         for (coord_def pos : blast_adjacents)
2268             _ignition_square(agent, beam_actual, pos, false);
2269     }
2270 
2271     return spret::success;
2272 }
2273 
_discharge_monsters(const coord_def & where,int pow,const actor & agent)2274 static int _discharge_monsters(const coord_def &where, int pow,
2275                                const actor &agent)
2276 {
2277     actor* victim = actor_at(where);
2278 
2279     if (!victim || !victim->alive())
2280         return 0;
2281 
2282     int damage = (&agent == victim) ? 1 + random2(3 + pow / 15)
2283                                     : 3 + random2(5 + pow / 10
2284                                                   + (random2(pow) / 10));
2285 
2286     bolt beam;
2287     beam.flavour    = BEAM_ELECTRICITY; // used for mons_adjust_flavoured
2288     beam.glyph      = dchar_glyph(DCHAR_FIRED_ZAP);
2289     beam.colour     = LIGHTBLUE;
2290 #ifdef USE_TILE
2291     beam.tile_beam  = -1;
2292 #endif
2293     beam.draw_delay = 0;
2294 
2295     dprf("Static discharge on (%d,%d) pow: %d", where.x, where.y, pow);
2296     if ((Options.use_animations & UA_BEAM)
2297         && (victim->is_player() || victim->res_elec() <= 0))
2298     {
2299         beam.draw(where);
2300     }
2301 
2302     if (victim->is_player())
2303     {
2304         damage = 1 + random2(3 + pow / 15);
2305         dprf("You: static discharge damage: %d", damage);
2306         damage = check_your_resists(damage, BEAM_ELECTRICITY,
2307                                     "static discharge");
2308         mprf("You are struck by an arc of lightning%s",
2309              attack_strength_punctuation(damage).c_str());
2310         ouch(damage, KILLED_BY_BEAM, agent.mid, "by static electricity", true,
2311              agent.is_player() ? "you" : agent.name(DESC_A).c_str());
2312         if (damage > 0)
2313             victim->expose_to_element(BEAM_ELECTRICITY, 2);
2314     }
2315     // rEelec monsters don't allow arcs to continue.
2316     else if (victim->res_elec() > 0)
2317         return 0;
2318     else if (god_protects(&agent, victim->as_monster(), false))
2319         return 0;
2320     else
2321     {
2322         monster* mons = victim->as_monster();
2323 
2324         // We need to initialize these before the monster has died.
2325         god_conduct_trigger conducts[3];
2326         if (agent.is_player())
2327             set_attack_conducts(conducts, *mons, you.can_see(*mons));
2328 
2329         dprf("%s: static discharge damage: %d",
2330              mons->name(DESC_PLAIN, true).c_str(), damage);
2331         damage = mons_adjust_flavoured(mons, beam, damage, false);
2332         mprf("%s is struck by an arc of lightning%s",
2333                 mons->name(DESC_THE).c_str(),
2334                 attack_strength_punctuation(damage).c_str());
2335         damage = mons_adjust_flavoured(mons, beam, damage);
2336 
2337         if (agent.is_player())
2338             _player_hurt_monster(*mons, damage, beam.flavour, false);
2339         else if (damage)
2340             mons->hurt(agent.as_monster(), damage);
2341     }
2342 
2343     // Recursion to give us chain-lightning -- bwr
2344     // Low power slight chance added for low power characters -- bwr
2345     if ((pow >= 10 && !one_chance_in(4)) || (pow >= 3 && one_chance_in(10)))
2346     {
2347         pow /= random_range(2, 3);
2348         damage += apply_random_around_square([pow, &agent] (coord_def where2) {
2349             return _discharge_monsters(where2, pow, agent);
2350         }, where, true, 1);
2351     }
2352     else if (damage > 0)
2353     {
2354         // Only printed if we did damage, so that the messages in
2355         // cast_discharge() are clean. -- bwr
2356         mpr("The lightning grounds out.");
2357     }
2358 
2359     return damage;
2360 }
2361 
safe_discharge(coord_def where,vector<const actor * > & exclude)2362 bool safe_discharge(coord_def where, vector<const actor *> &exclude)
2363 {
2364     for (adjacent_iterator ai(where); ai; ++ai)
2365     {
2366         const actor *act = actor_at(*ai);
2367         if (!act)
2368             continue;
2369 
2370         if (find(exclude.begin(), exclude.end(), act) == exclude.end())
2371         {
2372             if (act->is_monster())
2373             {
2374                 // Harmless to these monsters, so don't prompt about them.
2375                 if (act->res_elec() > 0
2376                     || god_protects(act->as_monster()))
2377                 {
2378                     continue;
2379                 }
2380 
2381                 if (stop_attack_prompt(act->as_monster(), false, where))
2382                     return false;
2383             }
2384             // Don't prompt for the player, but always continue arcing.
2385 
2386             exclude.push_back(act);
2387             if (!safe_discharge(act->pos(), exclude))
2388                 return false;
2389         }
2390     }
2391 
2392     return true;
2393 }
2394 
cast_discharge(int pow,const actor & agent,bool fail,bool prompt)2395 spret cast_discharge(int pow, const actor &agent, bool fail, bool prompt)
2396 {
2397     vector<const actor *> exclude;
2398     if (agent.is_player() && prompt && !safe_discharge(you.pos(), exclude))
2399         return spret::abort;
2400 
2401     fail_check();
2402 
2403     const int num_targs = 1 + random2(random_range(1, 3) + pow / 20);
2404     const int dam =
2405         apply_random_around_square([pow, &agent] (coord_def target) {
2406             return _discharge_monsters(target, pow, agent);
2407         }, agent.pos(), true, num_targs);
2408 
2409     dprf("Arcs: %d Damage: %d", num_targs, dam);
2410 
2411     if (dam > 0)
2412     {
2413         if (Options.use_animations & UA_BEAM)
2414             scaled_delay(100);
2415     }
2416     else
2417     {
2418         if (coinflip())
2419             mpr("The air crackles with electrical energy.");
2420         else
2421         {
2422             const bool plural = coinflip();
2423             mprf("%s blue arc%s ground%s harmlessly.",
2424                  plural ? "Some" : "A",
2425                  plural ? "s" : "",
2426                  plural ? " themselves" : "s itself");
2427         }
2428     }
2429     return spret::success;
2430 }
2431 
sandblast_find_ammo()2432 pair<int, item_def *> sandblast_find_ammo()
2433 {
2434     item_def *stone = nullptr;
2435     int num_stones = 0;
2436     for (item_def& i : you.inv)
2437     {
2438         if (i.is_type(OBJ_MISSILES, MI_STONE)
2439             && check_warning_inscriptions(i, OPER_DESTROY))
2440         {
2441             num_stones += i.quantity;
2442             stone = &i;
2443         }
2444     }
2445     return make_pair(num_stones, stone);
2446 }
2447 
cast_sandblast(int pow,bolt & beam,bool fail)2448 spret cast_sandblast(int pow, bolt &beam, bool fail)
2449 {
2450     auto ammo = sandblast_find_ammo();
2451 
2452     if (ammo.first == 0 || !ammo.second)
2453     {
2454         mpr("You don't have any stones to cast with.");
2455         return spret::abort;
2456     }
2457 
2458     zap_type zap = ZAP_SANDBLAST;
2459     const spret ret = zapping(zap, pow, beam, true, nullptr, fail);
2460 
2461     if (ret == spret::success)
2462     {
2463         if (dec_inv_item_quantity(letter_to_index(ammo.second->slot), 1))
2464             mpr("You now have no stones remaining.");
2465         else if (!you.quiver_action.spell_is_quivered(SPELL_SANDBLAST))
2466             mprf_nocap("%s", ammo.second->name(DESC_INVENTORY).c_str());
2467     }
2468 
2469     return ret;
2470 }
2471 
_elec_not_immune(const actor * act)2472 static bool _elec_not_immune(const actor *act)
2473 {
2474     return act->res_elec() < 3 && !god_protects(act->as_monster());
2475 }
2476 
get_thunderbolt_last_aim(actor * caster)2477 coord_def get_thunderbolt_last_aim(actor *caster)
2478 {
2479     const int &last_turn = caster->props[THUNDERBOLT_LAST_KEY].get_int();
2480     const coord_def &last_aim = caster->props[THUNDERBOLT_AIM_KEY].get_coord();
2481 
2482     // check against you.pos() in case the player has moved instantaneously,
2483     // via mesmerise, wjc, etc. In principle, this should probably also
2484     // record and check the player's location on their last cast.
2485     if (last_turn && last_turn + 1 == you.num_turns && last_aim != you.pos())
2486         return last_aim;
2487     else
2488         return coord_def();
2489 }
2490 
_set_thundervolt_last_aim(actor * caster,coord_def aim)2491 static void _set_thundervolt_last_aim(actor *caster, coord_def aim)
2492 {
2493     int &last_turn = caster->props[THUNDERBOLT_LAST_KEY].get_int();
2494     coord_def &last_aim = caster->props[THUNDERBOLT_AIM_KEY].get_coord();
2495 
2496     last_turn = you.num_turns;
2497     last_aim = aim;
2498 }
2499 
cast_thunderbolt(actor * caster,int pow,coord_def aim,bool fail)2500 spret cast_thunderbolt(actor *caster, int pow, coord_def aim, bool fail)
2501 {
2502     int &charges = caster->props[THUNDERBOLT_CHARGES_KEY].get_int();
2503     ASSERT(charges <= LIGHTNING_MAX_CHARGE);
2504 
2505     coord_def prev = get_thunderbolt_last_aim(caster);
2506     if (!in_bounds(prev))
2507         charges = 0;
2508 
2509     targeter_thunderbolt hitfunc(caster, spell_range(SPELL_THUNDERBOLT, pow),
2510                                  prev);
2511     hitfunc.set_aim(aim);
2512 
2513     if (caster->is_player()
2514         && stop_attack_prompt(hitfunc, "zap", _elec_not_immune))
2515     {
2516         return spret::abort;
2517     }
2518 
2519     fail_check();
2520 
2521     const int juice
2522         = (spell_mana(SPELL_THUNDERBOLT, false) + charges)
2523           * LIGHTNING_CHARGE_MULT;
2524 
2525     dprf("juice: %d", juice);
2526 
2527     bolt beam;
2528     beam.name              = "thunderbolt";
2529     beam.aux_source        = "lightning rod";
2530     beam.origin_spell      = SPELL_THUNDERBOLT;
2531     beam.flavour           = BEAM_ELECTRICITY;
2532     beam.glyph             = dchar_glyph(DCHAR_FIRED_BURST);
2533     beam.colour            = LIGHTCYAN;
2534     beam.range             = 1;
2535     beam.hit               = AUTOMATIC_HIT;
2536     beam.ac_rule           = ac_type::proportional;
2537     beam.set_agent(caster);
2538 #ifdef USE_TILE
2539     beam.tile_beam = -1;
2540 #endif
2541     beam.draw_delay = 0;
2542 
2543     if (Options.use_animations & UA_BEAM)
2544     {
2545         for (const auto &entry : hitfunc.zapped)
2546         {
2547             if (entry.second <= 0)
2548                 continue;
2549 
2550             beam.draw(entry.first);
2551         }
2552 
2553         scaled_delay(200);
2554     }
2555 
2556     beam.glyph = 0; // FIXME: a hack to avoid "appears out of thin air"
2557 
2558     for (const auto &entry : hitfunc.zapped)
2559     {
2560         if (entry.second <= 0)
2561             continue;
2562 
2563         // beams are incredibly spammy in debug mode
2564         if (!actor_at(entry.first))
2565             continue;
2566 
2567         int arc = hitfunc.arc_length[entry.first.distance_from(hitfunc.origin)];
2568         ASSERT(arc > 0);
2569         dprf("at distance %d, arc length is %d",
2570              entry.first.distance_from(hitfunc.origin), arc);
2571         beam.source = beam.target = entry.first;
2572         beam.source.x -= sgn(beam.source.x - hitfunc.origin.x);
2573         beam.source.y -= sgn(beam.source.y - hitfunc.origin.y);
2574         beam.damage = dice_def(div_rand_round(juice, LIGHTNING_CHARGE_MULT),
2575                                div_rand_round(30 + pow / 6, arc + 2));
2576         beam.fire();
2577     }
2578 
2579     _set_thundervolt_last_aim(caster, aim);
2580 
2581     if (charges < LIGHTNING_MAX_CHARGE)
2582         charges++;
2583 
2584     return spret::success;
2585 }
2586 
2587 // Find an enemy who would suffer from Awaken Forest.
forest_near_enemy(const actor * mon)2588 actor* forest_near_enemy(const actor *mon)
2589 {
2590     const coord_def pos = mon->pos();
2591 
2592     for (radius_iterator ri(pos, LOS_NO_TRANS); ri; ++ri)
2593     {
2594         actor* foe = actor_at(*ri);
2595         if (!foe || mons_aligned(foe, mon))
2596             continue;
2597 
2598         for (adjacent_iterator ai(*ri); ai; ++ai)
2599             if (feat_is_tree(env.grid(*ai)) && cell_see_cell(pos, *ai, LOS_DEFAULT))
2600                 return foe;
2601     }
2602 
2603     return nullptr;
2604 }
2605 
2606 // Print a message only if you can see any affected trees.
forest_message(const coord_def pos,const string & msg,msg_channel_type ch)2607 void forest_message(const coord_def pos, const string &msg, msg_channel_type ch)
2608 {
2609     for (radius_iterator ri(pos, LOS_DEFAULT); ri; ++ri)
2610         if (feat_is_tree(env.grid(*ri))
2611             && cell_see_cell(you.pos(), *ri, LOS_DEFAULT))
2612         {
2613             mprf(ch, "%s", msg.c_str());
2614             return;
2615         }
2616 }
2617 
forest_damage(const actor * mon)2618 void forest_damage(const actor *mon)
2619 {
2620     const coord_def pos = mon->pos();
2621     const int hd = mon->get_hit_dice();
2622 
2623     if (one_chance_in(4))
2624     {
2625         forest_message(pos, random_choose(
2626             "The trees move their gnarly branches around.",
2627             "You feel roots moving beneath the ground.",
2628             "Branches wave dangerously above you.",
2629             "Trunks creak and shift.",
2630             "Tree limbs sway around you."), MSGCH_TALK_VISUAL);
2631     }
2632 
2633     for (radius_iterator ri(pos, LOS_NO_TRANS); ri; ++ri)
2634     {
2635         actor* foe = actor_at(*ri);
2636         if (!foe || mons_aligned(foe, mon))
2637             continue;
2638 
2639         if (is_sanctuary(foe->pos()))
2640             continue;
2641 
2642         for (adjacent_iterator ai(*ri); ai; ++ai)
2643             if (feat_is_tree(env.grid(*ai)) && cell_see_cell(pos, *ai, LOS_NO_TRANS))
2644             {
2645                 int dmg = 0;
2646                 string msg;
2647 
2648                 if (!apply_chunked_AC(1, foe->evasion(ev_ignore::none, mon)))
2649                 {
2650                     msg = random_choose(
2651                             "@foe@ @is@ waved at by a branch",
2652                             "A tree reaches out but misses @foe@",
2653                             "A root lunges up near @foe@");
2654                 }
2655                 else if (!(dmg = foe->apply_ac(hd + random2(hd), hd * 2 - 1,
2656                                                ac_type::proportional)))
2657                 {
2658                     msg = random_choose(
2659                             "@foe@ @is@ scraped by a branch",
2660                             "A tree reaches out and scrapes @foe@",
2661                             "A root barely touches @foe@ from below");
2662                     if (foe->is_monster())
2663                         behaviour_event(foe->as_monster(), ME_WHACK);
2664                 }
2665                 else
2666                 {
2667                     msg = random_choose(
2668                         "@foe@ @is@ hit by a branch",
2669                         "A tree reaches out and hits @foe@",
2670                         "A root smacks @foe@ from below");
2671                     if (foe->is_monster())
2672                         behaviour_event(foe->as_monster(), ME_WHACK);
2673                 }
2674 
2675                 msg = replace_all(replace_all(msg,
2676                     "@foe@", foe->name(DESC_THE)),
2677                     "@is@", foe->conj_verb("be"))
2678                     + attack_strength_punctuation(dmg);
2679                 if (you.see_cell(foe->pos()))
2680                     mpr(msg);
2681 
2682                 if (dmg <= 0)
2683                     break;
2684 
2685                 foe->hurt(mon, dmg, BEAM_MISSILE, KILLED_BY_BEAM, "",
2686                           "by angry trees");
2687 
2688                 break;
2689             }
2690     }
2691 }
2692 
dazzle_chance_numerator(int hd)2693 int dazzle_chance_numerator(int hd)
2694 {
2695     return 95 - hd * 4;
2696 }
2697 
dazzle_chance_denom(int pow)2698 int dazzle_chance_denom(int pow)
2699 {
2700     return 150 - pow;
2701 }
2702 
dazzle_monster(monster * mons,int pow)2703 bool dazzle_monster(monster * mons, int pow)
2704 {
2705     if (!mons || !mons_can_be_dazzled(mons->type))
2706         return false;
2707 
2708     const int numerator = dazzle_chance_numerator(mons->get_hit_dice());
2709     if (x_chance_in_y(numerator, dazzle_chance_denom(pow)))
2710     {
2711         mons->add_ench(mon_enchant(ENCH_BLIND, 1, &you,
2712                        random_range(4, 8) * BASELINE_DELAY));
2713         return true;
2714     }
2715 
2716     return false;
2717 }
2718 
cast_dazzling_flash(int pow,bool fail,bool tracer)2719 spret cast_dazzling_flash(int pow, bool fail, bool tracer)
2720 {
2721     int range = spell_range(SPELL_DAZZLING_FLASH, pow);
2722     auto hitfunc = find_spell_targeter(SPELL_DAZZLING_FLASH, pow, range);
2723     bool (*vulnerable) (const actor *) = [](const actor * act) -> bool
2724     {
2725         // No fedhas checks needed, plants can't be dazzled
2726         return act->is_monster()
2727                && mons_can_be_dazzled(act->as_monster()->type);
2728     };
2729 
2730     if (tracer)
2731     {
2732         for (radius_iterator ri(you.pos(), range, C_SQUARE, LOS_SOLID_SEE, true); ri; ++ri)
2733         {
2734             if (!in_bounds(*ri))
2735                 continue;
2736 
2737             const monster* mon = monster_at(*ri);
2738 
2739             if (!mon || !you.can_see(*mon))
2740                 continue;
2741 
2742             if (!mon->friendly() && (*vulnerable)(mon))
2743                 return spret::success;
2744         }
2745 
2746         return spret::abort;
2747     }
2748 
2749 
2750     // [eb] the simulationist in me wants to use LOS_DEFAULT
2751     // and let this blind through glass
2752     if (stop_attack_prompt(*hitfunc, "dazzle", vulnerable))
2753         return spret::abort;
2754 
2755     fail_check();
2756 
2757     bolt beam;
2758     beam.name = "energy";
2759     beam.flavour = BEAM_VISUAL;
2760     beam.origin_spell = SPELL_DAZZLING_FLASH;
2761     beam.set_agent(&you);
2762     beam.colour = WHITE;
2763     beam.glyph = dchar_glyph(DCHAR_EXPLOSION);
2764     beam.range = range;
2765     beam.ex_size = range;
2766     beam.is_explosion = true;
2767     beam.source = you.pos();
2768     beam.target = you.pos();
2769     beam.hit = AUTOMATIC_HIT;
2770     beam.loudness = 0;
2771     beam.explode(true, true);
2772 
2773     for (radius_iterator ri(you.pos(), range, C_SQUARE, LOS_SOLID_SEE, true);
2774          ri; ++ri)
2775     {
2776         monster* mons = monster_at(*ri);
2777         if (mons && dazzle_monster(mons, pow))
2778             simple_monster_message(*mons, " is dazzled.");
2779     }
2780 
2781     return spret::success;
2782 }
2783 
_toxic_can_affect(const actor * act)2784 static bool _toxic_can_affect(const actor *act)
2785 {
2786     if (act->is_monster() && act->as_monster()->submerged())
2787         return false;
2788 
2789     // currently monsters are still immune at rPois 1
2790     return act->res_poison() < (act->is_player() ? 3 : 1);
2791 }
2792 
cast_toxic_radiance(actor * agent,int pow,bool fail,bool mon_tracer)2793 spret cast_toxic_radiance(actor *agent, int pow, bool fail, bool mon_tracer)
2794 {
2795     if (agent->is_player())
2796     {
2797         targeter_radius hitfunc(&you, LOS_NO_TRANS);
2798         if (stop_attack_prompt(hitfunc, "poison", _toxic_can_affect))
2799             return spret::abort;
2800 
2801         fail_check();
2802 
2803         if (!you.duration[DUR_TOXIC_RADIANCE])
2804             mpr("You begin to radiate toxic energy.");
2805         else
2806             mpr("Your toxic radiance grows in intensity.");
2807 
2808         you.increase_duration(DUR_TOXIC_RADIANCE, 2 + random2(pow/20), 15);
2809         toxic_radiance_effect(&you, 10, true);
2810 
2811         flash_view_delay(UA_PLAYER, GREEN, 300, &hitfunc);
2812 
2813         return spret::success;
2814     }
2815     else if (mon_tracer)
2816     {
2817         for (actor_near_iterator ai(agent->pos(), LOS_NO_TRANS); ai; ++ai)
2818         {
2819             if (!_toxic_can_affect(*ai) || mons_aligned(agent, *ai))
2820                 continue;
2821             else
2822                 return spret::success;
2823         }
2824 
2825         // Didn't find any susceptible targets
2826         return spret::abort;
2827     }
2828     else
2829     {
2830         monster* mon_agent = agent->as_monster();
2831         simple_monster_message(*mon_agent,
2832                                " begins to radiate toxic energy.");
2833 
2834         mon_agent->add_ench(mon_enchant(ENCH_TOXIC_RADIANCE, 1, mon_agent,
2835                                         (4 + random2avg(pow/15, 2)) * BASELINE_DELAY));
2836         toxic_radiance_effect(agent, 10);
2837 
2838         targeter_radius hitfunc(mon_agent, LOS_NO_TRANS);
2839         flash_view_delay(UA_MONSTER, GREEN, 300, &hitfunc);
2840 
2841         return spret::success;
2842     }
2843 }
2844 
2845 /*
2846  * Attempt to poison all monsters in line of sight of the caster.
2847  *
2848  * @param agent   The caster.
2849  * @param mult    A number to multiply the damage by.
2850  *                This is the time taken for the player's action in auts,
2851  *                or 10 if the spell was cast this turn.
2852  * @param on_cast Whether the spell was cast this turn. This only matters
2853  *                if the player cast the spell. If true, we trigger conducts
2854  *                if the player hurts allies; if false, we don't, to avoid
2855  *                the player being accidentally put under penance.
2856  *                Defaults to false.
2857  */
toxic_radiance_effect(actor * agent,int mult,bool on_cast)2858 void toxic_radiance_effect(actor* agent, int mult, bool on_cast)
2859 {
2860     int pow;
2861     if (agent->is_player())
2862         pow = calc_spell_power(SPELL_OLGREBS_TOXIC_RADIANCE, true);
2863     else
2864         pow = agent->as_monster()->get_hit_dice() * 8;
2865 
2866     for (actor_near_iterator ai(agent->pos(), LOS_NO_TRANS); ai; ++ai)
2867     {
2868         if (!_toxic_can_affect(*ai))
2869             continue;
2870 
2871         // Monsters can skip hurting friendlies
2872         if (agent->is_monster() && mons_aligned(agent, *ai))
2873             continue;
2874 
2875         int dam = roll_dice(1, 1 + pow / 20) * div_rand_round(mult, BASELINE_DELAY);
2876         dam = resist_adjust_damage(*ai, BEAM_POISON, dam);
2877 
2878         if (ai->is_player())
2879         {
2880             // We're affected only if we're not the agent.
2881             if (!agent->is_player())
2882             {
2883                 ouch(dam, KILLED_BY_BEAM, agent->mid,
2884                     "by Olgreb's Toxic Radiance", true,
2885                     agent->as_monster()->name(DESC_A).c_str());
2886 
2887                 poison_player(roll_dice(2, 3), agent->name(DESC_A),
2888                               "toxic radiance", false);
2889             }
2890         }
2891         else
2892         {
2893             god_conduct_trigger conducts[3];
2894 
2895             // Only trigger conducts on the turn the player casts the spell
2896             // (see PR #999).
2897             if (on_cast && agent->is_player())
2898                 set_attack_conducts(conducts, *ai->as_monster());
2899 
2900             ai->hurt(agent, dam, BEAM_POISON);
2901 
2902             if (ai->alive())
2903             {
2904                 behaviour_event(ai->as_monster(), ME_ANNOY, agent,
2905                                 agent->pos());
2906                 int q = mult / BASELINE_DELAY;
2907                 int levels = roll_dice(q, 2) - q + (roll_dice(1, 20) <= (mult % BASELINE_DELAY));
2908                 if (!ai->as_monster()->has_ench(ENCH_POISON)) // Always apply poison to an unpoisoned enemy
2909                     levels = max(levels, 1);
2910                 poison_monster(ai->as_monster(), agent, levels);
2911             }
2912         }
2913     }
2914 }
2915 
cast_poisonous_vapours(int pow,const dist & beam,bool fail,bool test)2916 spret cast_poisonous_vapours(int pow, const dist &beam, bool fail, bool test)
2917 {
2918     if (cell_is_solid(beam.target))
2919     {
2920         if (!test)
2921             canned_msg(MSG_UNTHINKING_ACT);
2922         return spret::abort;
2923     }
2924 
2925     monster* mons = monster_at(beam.target);
2926     if (!mons || !you.can_see(*mons))
2927     {
2928         if (test)
2929             return spret::abort;
2930     }
2931     else if (mons->res_poison() > 0 && mons->observable())
2932     {
2933         if (!test)
2934         {
2935             mprf("%s cannot be affected by poisonous vapours!",
2936                 mons->name(DESC_THE).c_str());
2937         }
2938         return spret::abort;
2939     }
2940 
2941     if (test)
2942         return spret::success;
2943 
2944     if (mons && you.can_see(*mons) && stop_attack_prompt(mons, false, you.pos()))
2945         return spret::abort;
2946 
2947     fail_check();
2948 
2949     if (!mons || mons->res_poison() > 0)
2950     {
2951         canned_msg(MSG_SPELL_FIZZLES);
2952         return spret::success; // still losing a turn
2953     }
2954 
2955     const int amount = max(1, div_rand_round(pow, 15));
2956     mprf("Poisonous vapours surround %s!", mons->name(DESC_THE).c_str());
2957     poison_monster(mons, &you, amount);
2958 
2959     behaviour_event(mons, ME_WHACK, &you);
2960     if (mons->alive())
2961         you.pet_target = mons->mindex();
2962 
2963     return spret::success;
2964 }
2965 
cast_searing_ray(int pow,bolt & beam,bool fail)2966 spret cast_searing_ray(int pow, bolt &beam, bool fail)
2967 {
2968     const spret ret = zapping(ZAP_SEARING_RAY, pow, beam, true, nullptr,
2969                                    fail);
2970 
2971     if (ret == spret::success)
2972     {
2973         monster * mons = monster_at(beam.target);
2974         // Special value, used to avoid terminating ray immediately, since we
2975         // took a non-wait action on this turn (ie: casting it)
2976         you.attribute[ATTR_SEARING_RAY] = -1;
2977         you.props["searing_ray_aimed_at_spot"].get_bool() = beam.aimed_at_spot
2978                                                             || !mons;
2979         you.props["searing_ray_target"].get_coord() = beam.target;
2980 
2981         if (mons)
2982             you.props["searing_ray_mid"].get_int() = mons->mid;
2983 
2984         string msg = "(Press <w>%</w> to maintain the ray.)";
2985         insert_commands(msg, { CMD_WAIT });
2986         mpr(msg);
2987     }
2988 
2989     return ret;
2990 }
2991 
handle_searing_ray()2992 void handle_searing_ray()
2993 {
2994     if (you.attribute[ATTR_SEARING_RAY] == 0)
2995         return;
2996 
2997     // Convert prepping value into stage one value (so it can fire next turn)
2998     if (you.attribute[ATTR_SEARING_RAY] == -1)
2999     {
3000         you.attribute[ATTR_SEARING_RAY] = 1;
3001         return;
3002     }
3003 
3004     if (crawl_state.prev_cmd != CMD_WAIT)
3005     {
3006         end_searing_ray();
3007         return;
3008     }
3009 
3010     ASSERT_RANGE(you.attribute[ATTR_SEARING_RAY], 1, 4);
3011 
3012     // All of these effects interrupt a channeled ray
3013     if (you.confused() || you.berserk())
3014     {
3015         end_searing_ray();
3016         return;
3017     }
3018 
3019     if (!enough_mp(1, true))
3020     {
3021         mpr("Without enough magic to sustain it, your searing ray dissipates.");
3022         end_searing_ray();
3023         return;
3024     }
3025 
3026     const zap_type zap = zap_type(ZAP_SEARING_RAY);
3027     const int pow = calc_spell_power(SPELL_SEARING_RAY, true);
3028 
3029     if (!you.props["searing_ray_aimed_at_spot"].get_bool())
3030     {
3031         monster* mons = nullptr;
3032         mons = monster_by_mid(you.props["searing_ray_mid"].get_int());
3033         // homing targeting, save the target location in case it dies or
3034         // disappears
3035         if (mons && mons->alive() && you.can_see(*mons))
3036             you.props["searing_ray_target"].get_coord() = mons->pos();
3037         else
3038             you.props["searing_ray_aimed_at_spot"] = true;
3039     }
3040 
3041     bolt beam;
3042     beam.thrower = KILL_YOU_MISSILE;
3043     beam.range   = calc_spell_range(SPELL_SEARING_RAY, pow);
3044     beam.source  = you.pos();
3045     beam.target  = you.props["searing_ray_target"].get_coord();
3046 
3047     // If friendlies have moved into the beam path, give a chance to abort
3048     if (!player_tracer(zap, pow, beam))
3049     {
3050         mpr("You stop channeling your searing ray.");
3051         end_searing_ray();
3052         return;
3053     }
3054 
3055     zappy(zap, pow, false, beam);
3056 
3057     aim_battlesphere(&you, SPELL_SEARING_RAY);
3058     beam.fire();
3059     trigger_battlesphere(&you);
3060 
3061     pay_mp(1);
3062     finalize_mp_cost();
3063 
3064     if (++you.attribute[ATTR_SEARING_RAY] > 3)
3065     {
3066         mpr("You finish channeling your searing ray.");
3067         end_searing_ray();
3068     }
3069 }
3070 
end_searing_ray()3071 void end_searing_ray()
3072 {
3073     you.attribute[ATTR_SEARING_RAY] = 0;
3074     you.props.erase("searing_ray_target");
3075     you.props.erase("searing_ray_aimed_at_spot");
3076 }
3077 
3078 /**
3079  * Can a casting of Glaciate by the player injure the given creature?
3080  *
3081  * @param victim        The potential victim.
3082  * @return              Whether Glaciate can harm that victim.
3083  *                      (False for IOODs or friendly battlespheres.)
3084  */
_player_glaciate_affects(const actor * victim)3085 static bool _player_glaciate_affects(const actor *victim)
3086 {
3087     // TODO: deduplicate this with beam::ignores
3088     if (!victim)
3089         return false;
3090 
3091     const monster* mon = victim->as_monster();
3092     if (!mon) // player
3093         return true;
3094 
3095     return !mons_is_projectile(*mon)
3096             && (!mons_is_avatar(mon->type) || !mons_aligned(&you, mon));
3097 }
3098 
glaciate_damage(int pow,int eff_range)3099 dice_def glaciate_damage(int pow, int eff_range)
3100 {
3101     // At or within range 3, this is equivalent to the old Ice Storm damage.
3102     return calc_dice(10, (54 + 3 * pow / 2) / eff_range);
3103 }
3104 
cast_glaciate(actor * caster,int pow,coord_def aim,bool fail)3105 spret cast_glaciate(actor *caster, int pow, coord_def aim, bool fail)
3106 {
3107     const int range = spell_range(SPELL_GLACIATE, pow);
3108     targeter_cone hitfunc(caster, range);
3109     hitfunc.set_aim(aim);
3110 
3111     if (caster->is_player()
3112         && stop_attack_prompt(hitfunc, "glaciate", _player_glaciate_affects))
3113     {
3114         return spret::abort;
3115     }
3116 
3117     fail_check();
3118 
3119     bolt beam;
3120     beam.name              = "great icy blast";
3121     beam.aux_source        = "great icy blast";
3122     beam.flavour           = BEAM_ICE;
3123     beam.glyph             = dchar_glyph(DCHAR_EXPLOSION);
3124     beam.colour            = WHITE;
3125     beam.range             = 1;
3126     beam.hit               = AUTOMATIC_HIT;
3127     beam.source_id         = caster->mid;
3128     beam.hit_verb          = "engulfs";
3129     beam.origin_spell      = SPELL_GLACIATE;
3130     beam.set_agent(caster);
3131 #ifdef USE_TILE
3132     beam.tile_beam = -1;
3133 #endif
3134     beam.draw_delay = 0;
3135 
3136     if (Options.use_animations & UA_BEAM)
3137     {
3138         for (int i = 1; i <= range; i++)
3139         {
3140             for (const auto &entry : hitfunc.sweep[i])
3141             {
3142                 if (entry.second <= 0)
3143                     continue;
3144 
3145                 beam.draw(entry.first);
3146             }
3147             scaled_delay(25);
3148         }
3149 
3150         scaled_delay(100);
3151     }
3152 
3153     if (you.can_see(*caster) || caster->is_player())
3154     {
3155         mprf("%s %s a mighty blast of ice!",
3156              caster->name(DESC_THE).c_str(),
3157              caster->conj_verb("conjure").c_str());
3158     }
3159 
3160     beam.glyph = 0;
3161 
3162     for (int i = 1; i <= range; i++)
3163     {
3164         for (const auto &entry : hitfunc.sweep[i])
3165         {
3166             if (entry.second <= 0)
3167                 continue;
3168 
3169             const int eff_range = max(3, (6 * i / LOS_DEFAULT_RANGE));
3170 
3171             beam.damage = glaciate_damage(pow, eff_range);
3172 
3173             if (actor_at(entry.first))
3174             {
3175                 beam.source = beam.target = entry.first;
3176                 beam.source.x -= sgn(beam.source.x - hitfunc.origin.x);
3177                 beam.source.y -= sgn(beam.source.y - hitfunc.origin.y);
3178                 beam.fire();
3179             }
3180             place_cloud(CLOUD_COLD, entry.first,
3181                         (18 + random2avg(45,2)) / eff_range, caster);
3182         }
3183     }
3184 
3185     noisy(spell_effect_noise(SPELL_GLACIATE), hitfunc.origin);
3186 
3187     return spret::success;
3188 }
3189 
cast_starburst(int pow,bool fail,bool tracer)3190 spret cast_starburst(int pow, bool fail, bool tracer)
3191 {
3192     int range = spell_range(SPELL_STARBURST, pow);
3193 
3194     vector<coord_def> offsets = { coord_def(range, 0),
3195                                 coord_def(range, range),
3196                                 coord_def(0, range),
3197                                 coord_def(-range, range),
3198                                 coord_def(-range, 0),
3199                                 coord_def(-range, -range),
3200                                 coord_def(0, -range),
3201                                 coord_def(range, -range) };
3202 
3203     bolt beam;
3204     beam.range        = range;
3205     beam.source       = you.pos();
3206     beam.source_id    = MID_PLAYER;
3207     beam.is_tracer    = tracer;
3208     beam.is_targeting = tracer;
3209     beam.dont_stop_player = true;
3210     beam.friend_info.dont_stop = true;
3211     beam.foe_info.dont_stop = true;
3212     beam.attitude = ATT_FRIENDLY;
3213     beam.thrower      = KILL_YOU;
3214     beam.origin_spell = SPELL_STARBURST;
3215     beam.draw_delay   = 5;
3216     zappy(ZAP_BOLT_OF_FIRE, pow, false, beam);
3217 
3218     for (const coord_def & offset : offsets)
3219     {
3220         beam.target = you.pos() + offset;
3221         if (!tracer && !player_tracer(ZAP_BOLT_OF_FIRE, pow, beam))
3222             return spret::abort;
3223 
3224         if (tracer)
3225         {
3226             beam.fire();
3227             // something to hit
3228             if (beam.foe_info.count > 0)
3229                 return spret::success;
3230         }
3231     }
3232 
3233     if (tracer)
3234         return spret::abort;
3235 
3236     fail_check();
3237 
3238     // Randomize for nice animations
3239     shuffle_array(offsets);
3240     for (auto & offset : offsets)
3241     {
3242         beam.target = you.pos() + offset;
3243         beam.fire();
3244     }
3245 
3246     return spret::success;
3247 }
3248 
foxfire_attack(const monster * foxfire,const actor * target)3249 void foxfire_attack(const monster *foxfire, const actor *target)
3250 {
3251     actor * summoner = actor_by_mid(foxfire->summoner);
3252     if (!summoner || !summoner->alive())
3253     {
3254         // perish as your master once did
3255         return;
3256     }
3257 
3258     // Don't allow foxfires that have wandered off to attack before dissapating
3259     if (summoner && !(summoner->can_see(*foxfire)
3260                       && summoner->see_cell(target->pos())))
3261     {
3262         return;
3263     }
3264 
3265     bolt beam;
3266     beam.thrower = (foxfire && foxfire->friendly()) ? KILL_YOU :
3267                    (foxfire)                       ? KILL_MON
3268                                                   : KILL_MISC;
3269     beam.range       = 1;
3270     beam.source      = foxfire->pos();
3271     beam.source_id   = foxfire->summoner;
3272     beam.source_name = summoner->name(DESC_PLAIN, true);
3273     zappy(ZAP_FOXFIRE, foxfire->get_hit_dice(), !foxfire->friendly(), beam);
3274     beam.aux_source  = beam.name;
3275     beam.target      = target->pos();
3276     beam.fire();
3277 }
3278 
3279 /**
3280  * Hailstorm the given cell. (Per the spell.)
3281  *
3282  * @param where     The cell in question.
3283  * @param pow       The power with which the spell is being cast.
3284  * @param agent     The agent (player or monster) doing the hailstorming.
3285  */
_hailstorm_cell(coord_def where,int pow,actor * agent)3286 static void _hailstorm_cell(coord_def where, int pow, actor *agent)
3287 {
3288     bolt beam;
3289     zappy(ZAP_HAILSTORM, pow, agent->is_monster(), beam);
3290     beam.thrower    = agent->is_player() ? KILL_YOU : KILL_MON;
3291     beam.source_id  = agent->mid;
3292     beam.attitude   = agent->temp_attitude();
3293 #ifdef USE_TILE
3294     beam.tile_beam  = -1;
3295 #endif
3296     beam.draw_delay = 10;
3297     beam.source     = where;
3298     beam.target     = where;
3299     beam.hit_verb   = "pelts";
3300 
3301     beam.fire();
3302 }
3303 
cast_hailstorm(int pow,bool fail,bool tracer)3304 spret cast_hailstorm(int pow, bool fail, bool tracer)
3305 {
3306     const int range = calc_spell_range(SPELL_HAILSTORM, pow);
3307     // used only for vulnerability check, not for the actual targeting
3308     auto hitfunc = find_spell_targeter(SPELL_HAILSTORM, pow, range);
3309     bool (*vulnerable) (const actor *) = [](const actor * act) -> bool
3310     {
3311       // actor guaranteed to be monster from usage,
3312       // but we'll verify it as a matter of good hygiene.
3313         const monster* mon = act->as_monster();
3314         return mon && !mons_is_firewood(*mon)
3315             && !god_protects(mon)
3316             && !mons_is_projectile(*mon)
3317             && !(mons_is_avatar(mon->type) && mons_aligned(&you, mon))
3318             && !testbits(mon->flags, MF_DEMONIC_GUARDIAN);
3319     };
3320 
3321     if (tracer)
3322     {
3323         for (radius_iterator ri(you.pos(), range, C_SQUARE, LOS_NO_TRANS, true);
3324              ri; ++ri)
3325         {
3326             if (grid_distance(you.pos(), *ri) == 1 || !in_bounds(*ri))
3327                 continue;
3328 
3329             const monster* mon = monster_at(*ri);
3330 
3331             if (!mon || !you.can_see(*mon))
3332                 continue;
3333 
3334             if (!mon->friendly() && (*vulnerable)(mon))
3335                 return spret::success;
3336         }
3337 
3338         return spret::abort;
3339     }
3340 
3341     if (stop_attack_prompt(*hitfunc, "hailstorm", vulnerable))
3342         return spret::abort;
3343 
3344     fail_check();
3345 
3346     mpr("A cannonade of hail descends around you!");
3347 
3348     for (radius_iterator ri(you.pos(), range, C_SQUARE, LOS_NO_TRANS, true);
3349          ri; ++ri)
3350     {
3351         if (grid_distance(you.pos(), *ri) == 1 || !in_bounds(*ri))
3352             continue;
3353 
3354         _hailstorm_cell(*ri, pow, &you);
3355     }
3356 
3357     return spret::success;
3358 }
3359 
_imb_actor(actor * act,int pow,coord_def source)3360 static void _imb_actor(actor * act, int pow, coord_def source)
3361 {
3362     bolt beam;
3363     zappy(ZAP_MYSTIC_BLAST, pow, false, beam);
3364     beam.source          = source;
3365     beam.thrower         = KILL_YOU;
3366     beam.source_id       = MID_PLAYER;
3367     beam.range           = LOS_RADIUS;
3368     beam.ench_power      = pow;
3369     beam.aimed_at_spot   = true;
3370 
3371     beam.target          = act->pos();
3372 
3373     beam.flavour          = BEAM_VISUAL;
3374     beam.affects_nothing = true;
3375     beam.fire();
3376 
3377     zappy(ZAP_MYSTIC_BLAST, pow, false, beam);
3378     beam.affects_nothing = false;
3379 
3380     beam.affect_actor(act);
3381 }
3382 
cast_imb(int pow,bool fail)3383 spret cast_imb(int pow, bool fail)
3384 {
3385     int range = spell_range(SPELL_ISKENDERUNS_MYSTIC_BLAST, pow);
3386     auto hitfunc = find_spell_targeter(SPELL_ISKENDERUNS_MYSTIC_BLAST, pow, range);
3387 
3388     bool (*vulnerable) (const actor *) = [](const actor * act) -> bool
3389     {
3390         return !(act->is_monster()
3391                  && (mons_is_conjured(act->as_monster()->type)
3392                      || god_protects(act->as_monster())));
3393     };
3394 
3395     if (stop_attack_prompt(*hitfunc, "blast", vulnerable))
3396         return spret::abort;
3397 
3398     fail_check();
3399 
3400     mpr("You erupt in a blast of force!");
3401 
3402     vector<actor *> act_list;
3403     // knock back into dispersal could move the player, so save the current pos
3404     coord_def source = you.pos();
3405 
3406     for (actor_near_iterator ai(source, LOS_SOLID_SEE); ai; ++ai)
3407     {
3408         if (ai->pos().distance_from(you.pos()) > range
3409             || ai->pos() == you.pos() // so it's never aimed_at_feet
3410             || mons_is_conjured(ai->as_monster()->type)) // skip prisms &c.
3411         {
3412             continue;
3413         }
3414 
3415         act_list.push_back(*ai);
3416     }
3417 
3418     far_to_near_sorter sorter = { source };
3419     sort(act_list.begin(), act_list.end(), sorter);
3420 
3421     for (actor *act : act_list)
3422         if (cell_see_cell(source, act->pos(), LOS_SOLID_SEE)) // sanity check vs dispersal
3423             _imb_actor(act, pow, source);
3424 
3425     return spret::success;
3426 }
3427 
actor_apply_toxic_bog(actor * act)3428 void actor_apply_toxic_bog(actor * act)
3429 {
3430     if (env.grid(act->pos()) != DNGN_TOXIC_BOG)
3431         return;
3432 
3433     if (!act->ground_level())
3434         return;
3435 
3436     const bool player = act->is_player();
3437     monster *mons = !player ? act->as_monster() : nullptr;
3438 
3439     if (mons && mons->type == MONS_FENSTRIDER_WITCH)
3440         return; // stilting above the muck!
3441 
3442     actor *oppressor = nullptr;
3443 
3444     for (map_marker *marker : env.markers.get_markers_at(act->pos()))
3445     {
3446         if (marker->get_type() == MAT_TERRAIN_CHANGE)
3447         {
3448             map_terrain_change_marker* tmarker =
3449                     dynamic_cast<map_terrain_change_marker*>(marker);
3450             if (tmarker->change_type == TERRAIN_CHANGE_BOG)
3451                 oppressor = actor_by_mid(tmarker->mon_num);
3452         }
3453     }
3454 
3455     const int base_damage = dice_def(4, 6).roll();
3456     const int damage = resist_adjust_damage(act, BEAM_POISON_ARROW, base_damage);
3457     const int resist = base_damage - damage;
3458 
3459     const int final_damage = timescale_damage(act, damage);
3460 
3461     if (player && final_damage > 0)
3462     {
3463         mprf("You fester in the toxic bog%s",
3464                 attack_strength_punctuation(final_damage).c_str());
3465     }
3466     else if (final_damage > 0)
3467     {
3468         behaviour_event(mons, ME_DISTURB, 0, act->pos());
3469         mprf("%s festers in the toxic bog%s",
3470                 mons->name(DESC_THE).c_str(),
3471                 attack_strength_punctuation(final_damage).c_str());
3472     }
3473 
3474     if (final_damage > 0 && resist > 0)
3475     {
3476         if (player)
3477             canned_msg(MSG_YOU_PARTIALLY_RESIST);
3478 
3479         act->poison(oppressor, 7, true);
3480     }
3481     else if (final_damage > 0)
3482         act->poison(oppressor, 21, true);
3483 
3484     if (final_damage)
3485     {
3486 
3487         const string oppr_name =
3488             oppressor ? " "+apostrophise(oppressor->name(DESC_THE))
3489                       : "";
3490         dprf("%s %s %d damage from%s toxic bog.",
3491              act->name(DESC_THE).c_str(),
3492              act->conj_verb("take").c_str(),
3493              final_damage,
3494              oppr_name.c_str());
3495 
3496         act->hurt(oppressor, final_damage, BEAM_MISSILE,
3497                   KILLED_BY_POISON, "", "toxic bog");
3498     }
3499 }
3500 
find_ramparts_walls(const coord_def & center)3501 vector<coord_def> find_ramparts_walls(const coord_def &center)
3502 {
3503     vector<coord_def> wall_locs;
3504     for (radius_iterator ri(center,
3505             spell_range(SPELL_FROZEN_RAMPARTS, -1, false), C_SQUARE,
3506                                                         LOS_NO_TRANS, true);
3507         ri; ++ri)
3508     {
3509         const auto feat = env.grid(*ri);
3510         if (feat_is_wall(feat))
3511             wall_locs.push_back(*ri);
3512     }
3513     return wall_locs;
3514 }
3515 
3516 /**
3517  * Cast Frozen Ramparts
3518  *
3519  * @param caster The caster.
3520  * @param pow    The spell power.
3521  * @param fail   Did this spell miscast? If true, abort the cast.
3522  * @return       spret::fail if one could be found but we miscast, and
3523  *               spret::success if the spell was successfully cast.
3524 */
cast_frozen_ramparts(int pow,bool fail)3525 spret cast_frozen_ramparts(int pow, bool fail)
3526 {
3527     vector<coord_def> wall_locs = find_ramparts_walls(you.pos());
3528 
3529     if (wall_locs.empty())
3530     {
3531         mpr("There are no walls around you to affect.");
3532         return spret::abort;
3533     }
3534 
3535     fail_check();
3536 
3537     for (auto pos: wall_locs)
3538     {
3539         if (in_bounds(pos))
3540             noisy(spell_effect_noise(SPELL_FROZEN_RAMPARTS), pos);
3541         env.pgrid(pos) |= FPROP_ICY;
3542     }
3543 
3544     env.level_state |= LSTATE_ICY_WALL;
3545     you.props[FROZEN_RAMPARTS_KEY] = you.pos();
3546 
3547     mpr("The walls around you are covered in ice.");
3548     you.duration[DUR_FROZEN_RAMPARTS] = random_range(40 + pow,
3549                                                      80 + pow * 3 / 2);
3550     return spret::success;
3551 }
3552 
end_frozen_ramparts()3553 void end_frozen_ramparts()
3554 {
3555     if (!you.props.exists(FROZEN_RAMPARTS_KEY))
3556         return;
3557 
3558     const auto &pos = you.props[FROZEN_RAMPARTS_KEY].get_coord();
3559     ASSERT(in_bounds(pos));
3560 
3561     for (distance_iterator di(pos, false, false,
3562                 spell_range(SPELL_FROZEN_RAMPARTS, -1, false)); di; di++)
3563     {
3564         env.pgrid(*di) &= ~FPROP_ICY;
3565         env.map_knowledge(*di).flags &= ~MAP_ICY;
3566     }
3567 
3568     you.props.erase(FROZEN_RAMPARTS_KEY);
3569 
3570     env.level_state &= ~LSTATE_ICY_WALL;
3571 }
3572 
ramparts_damage(int pow,bool random)3573 dice_def ramparts_damage(int pow, bool random)
3574 {
3575     int size = 2 + pow / 5;
3576     if (random)
3577         size = 2 + div_rand_round(pow, 5);
3578     return dice_def(1, size);
3579 }
3580 
_maxwells_target_check(monster & m)3581 static bool _maxwells_target_check(monster &m)
3582 {
3583     return _act_worth_targeting(you, m)
3584             && !m.wont_attack();
3585 }
3586 
wait_spell_active(spell_type spell)3587 bool wait_spell_active(spell_type spell)
3588 {
3589     // XX deduplicate code somehow
3590     return spell == SPELL_SEARING_RAY
3591                 && you.attribute[ATTR_SEARING_RAY] != 0
3592             || spell == SPELL_MAXWELLS_COUPLING
3593                 && you.props.exists("maxwells_charge_time");
3594 }
3595 
3596 // returns the closest target to the player, choosing randomly if there are more
3597 // than one (see `fair` argument to distance_iterator).
_find_maxwells_target(bool tracer)3598 static monster* _find_maxwells_target(bool tracer)
3599 {
3600     for (distance_iterator di(you.pos(), !tracer, true, LOS_RADIUS); di; ++di)
3601     {
3602         monster *mon = monster_at(*di);
3603         if (mon && _maxwells_target_check(*mon)
3604             && (!tracer || you.can_see(*mon)))
3605         {
3606             return mon;
3607         }
3608     }
3609 
3610     return nullptr;
3611 }
3612 
3613 // find all possible targets at the closest distance; used for targeting
find_maxwells_possibles()3614 vector<monster *> find_maxwells_possibles()
3615 {
3616     vector<monster *> result;
3617     monster *seed = _find_maxwells_target(true);
3618     if (seed)
3619     {
3620         const int distance = max(abs(you.pos().x - seed->pos().x),
3621                                  abs(you.pos().y - seed->pos().y));
3622         for (distance_iterator di(you.pos(), true, true, distance); di; ++di)
3623         {
3624             monster *mon = monster_at(*di);
3625             if (mon && _maxwells_target_check(*mon) && you.can_see(*mon))
3626                 result.push_back(mon);
3627         }
3628     }
3629     return result;
3630 }
3631 
cast_maxwells_coupling(int pow,bool fail,bool tracer)3632 spret cast_maxwells_coupling(int pow, bool fail, bool tracer)
3633 {
3634     monster* const mon = _find_maxwells_target(tracer);
3635 
3636     if (tracer)
3637     {
3638         if (!mon || !you.can_see(*mon))
3639             return spret::abort;
3640         else
3641             return spret::success;
3642     }
3643 
3644     fail_check();
3645 
3646     mpr("You begin accumulating electric charge.");
3647     string msg = "(Press <w>%</w> to continue charging.)";
3648     insert_commands(msg, { CMD_WAIT });
3649     mpr(msg);
3650 
3651     you.props["maxwells_charge_time"] =
3652         - (30 + div_rand_round(random2((200 - pow) * 40), 200));
3653     return spret::success;
3654 }
3655 
_discharge_maxwells_coupling()3656 static void _discharge_maxwells_coupling()
3657 {
3658     monster* const mon = _find_maxwells_target(false);
3659 
3660     if (!mon)
3661     {
3662         mpr("Your charge dissipates without a target.");
3663         return;
3664     }
3665 
3666     targeter_radius hitfunc(&you, LOS_NO_TRANS);
3667     flash_view_delay(UA_PLAYER, LIGHTCYAN, 100, &hitfunc);
3668 
3669     god_conduct_trigger conducts[3];
3670     set_attack_conducts(conducts, *mon, you.can_see(*mon));
3671 
3672     if (mon->type == MONS_ROYAL_JELLY && !mon->is_summoned())
3673     {
3674         // need to do this here, because react_to_damage is never called
3675         mprf("A cloud of jellies burst out of %s as the current"
3676              " ripples through it!", mon->name(DESC_THE).c_str());
3677         trj_spawn_fineff::schedule(&you, mon, mon->pos(), mon->hit_points);
3678     }
3679     else
3680         mprf("The electricity discharges through %s!", mon->name(DESC_THE).c_str());
3681 
3682     const bool goldify = have_passive(passive_t::goldify_corpses);
3683     if (goldify)
3684         simple_monster_message(*mon, " vaporises and condenses as gold!");
3685     else
3686         simple_monster_message(*mon, " vaporises in an electric haze!");
3687 
3688     const coord_def pos = mon->pos();
3689     item_def* corpse = monster_die(*mon, KILL_YOU,
3690                                     actor_to_death_source(&you));
3691     if (corpse && !goldify)
3692         destroy_item(corpse->index());
3693 
3694     noisy(spell_effect_noise(SPELL_MAXWELLS_COUPLING), pos, you.mid);
3695 }
3696 
handle_maxwells_coupling()3697 void handle_maxwells_coupling()
3698 {
3699     if (!you.props.exists("maxwells_charge_time"))
3700         return;
3701 
3702     // All of these effects interrupt charging
3703     if (you.confused() || you.berserk())
3704     {
3705         end_maxwells_coupling();
3706         return;
3707     }
3708 
3709     int charging_auts_remaining = you.props["maxwells_charge_time"].get_int();
3710 
3711     if (charging_auts_remaining < 0)
3712     {
3713         mpr("You feel charge building up...");
3714         you.props["maxwells_charge_time"] = - (charging_auts_remaining
3715                                             + you.time_taken);
3716         return;
3717     }
3718 
3719     if (crawl_state.prev_cmd != CMD_WAIT)
3720     {
3721         end_maxwells_coupling();
3722         return;
3723     }
3724 
3725     if (charging_auts_remaining <= you.time_taken)
3726     {
3727         you.time_taken = charging_auts_remaining;
3728         you.props.erase("maxwells_charge_time");
3729         _discharge_maxwells_coupling();
3730         return;
3731     }
3732 
3733     you.props["maxwells_charge_time"] = charging_auts_remaining
3734                                       - you.time_taken;
3735     mpr("You feel charge building up...");
3736 }
3737 
end_maxwells_coupling(bool quiet)3738 void end_maxwells_coupling(bool quiet)
3739 {
3740     if (!you.props.exists("maxwells_charge_time"))
3741         return;
3742     if (!quiet)
3743         mpr("The insufficient charge dissipates harmlessly.");
3744     you.props.erase("maxwells_charge_time");
3745 }
3746 
find_bog_locations(const coord_def & center,int pow)3747 vector<coord_def> find_bog_locations(const coord_def &center, int pow)
3748 {
3749     vector<coord_def> bog_locs;
3750     const int radius = spell_range(SPELL_NOXIOUS_BOG, pow, false);
3751 
3752     for (radius_iterator ri(center, radius, C_SQUARE, LOS_NO_TRANS, true); ri;
3753             ri++)
3754     {
3755         if (!feat_has_solid_floor(env.grid(*ri)))
3756             continue;
3757 
3758         // If a candidate cell is next to a solid feature, we can't bog it.
3759         // Additionally, if it's next to a cell we can't currently see, we
3760         // can't bog it, regardless of what the cell contains. Don't want to
3761         // leak information about out-of-los cells.
3762         bool valid = true;
3763         for (adjacent_iterator ai(*ri); ai; ai++)
3764         {
3765             if (!you.see_cell(*ai) || feat_is_solid(env.grid(*ai)))
3766             {
3767                 valid = false;
3768                 break;
3769             }
3770         }
3771         if (valid)
3772             bog_locs.push_back(*ri);
3773     }
3774 
3775     return bog_locs;
3776 }
cast_noxious_bog(int pow,bool fail)3777 spret cast_noxious_bog(int pow, bool fail)
3778 {
3779     vector <coord_def> bog_locs = find_bog_locations(you.pos(), pow);
3780     if (bog_locs.empty())
3781     {
3782         mpr("There are no places for you to create a bog.");
3783         return spret::abort;
3784     }
3785 
3786     fail_check();
3787 
3788     const int turns = 5 + random2(pow / 10);
3789     you.increase_duration(DUR_NOXIOUS_BOG, turns);
3790 
3791     for (auto pos : bog_locs)
3792     {
3793         temp_change_terrain(pos, DNGN_TOXIC_BOG, turns * BASELINE_DELAY,
3794                 TERRAIN_CHANGE_BOG, you.as_monster());
3795     }
3796 
3797     flash_view_delay(UA_PLAYER, LIGHTGREEN, 100);
3798     mpr("You spew toxic sludge!");
3799 
3800     return spret::success;
3801 }
3802