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 ¢er)
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 ¢er, 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