1 /**
2  * @file
3  * @brief Functions related to ranged attacks.
4 **/
5 
6 #include "AppHdr.h"
7 
8 #include "beam.h"
9 
10 #include <algorithm>
11 #include <cmath>
12 #include <cstdarg>
13 #include <cstdio>
14 #include <cstdlib>
15 #include <cstring>
16 #include <iostream>
17 #include <set>
18 
19 #include "act-iter.h"
20 #include "areas.h"
21 #include "attack.h"
22 #include "attitude-change.h"
23 #include "bloodspatter.h"
24 #include "chardump.h"
25 #include "cloud.h"
26 #include "colour.h"
27 #include "coordit.h"
28 #include "delay.h"
29 #include "directn.h"
30 #include "dungeon.h"
31 #include "english.h"
32 #include "exercise.h"
33 #include "fight.h"
34 #include "fineff.h"
35 #include "god-conduct.h"
36 #include "god-item.h"
37 #include "god-passive.h" // passive_t::convert_orcs
38 #include "item-use.h"
39 #include "item-prop.h"
40 #include "items.h"
41 #include "killer-type.h"
42 #include "libutil.h"
43 #include "losglobal.h"
44 #include "los.h"
45 #include "message.h"
46 #include "mon-behv.h"
47 #include "mon-death.h"
48 #include "mon-explode.h"
49 #include "mon-place.h"
50 #include "mon-poly.h"
51 #include "mon-util.h"
52 #include "mutation.h"
53 #include "nearby-danger.h"
54 #include "options.h"
55 #include "player-stats.h"
56 #include "potion.h"
57 #include "prompt.h"
58 #include "ranged-attack.h"
59 #include "religion.h"
60 #include "shout.h"
61 #include "spl-book.h"
62 #include "spl-clouds.h"
63 #include "spl-damage.h"
64 #include "spl-goditem.h"
65 #include "spl-monench.h"
66 #include "spl-summoning.h"
67 #include "spl-transloc.h"
68 #include "spl-util.h"
69 #include "spl-zap.h"
70 #include "state.h"
71 #include "stepdown.h"
72 #include "stringutil.h"
73 #include "target.h"
74 #include "teleport.h"
75 #include "terrain.h"
76 #include "throw.h"
77 #ifdef USE_TILE
78  #include "tilepick.h"
79 #endif
80 #include "tiles-build-specific.h"
81 #include "transform.h"
82 #include "traps.h"
83 #include "viewchar.h"
84 #include "view.h"
85 #include "xom.h"
86 
87 #define SAP_MAGIC_CHANCE() x_chance_in_y(7, 10)
88 
89 // Helper functions (some of these should probably be public).
90 static void _ench_animation(int flavour, const monster* mon = nullptr,
91                             bool force = false);
92 static beam_type _chaos_beam_flavour(bolt* beam);
93 static string _beam_type_name(beam_type type);
94 int _ench_pow_to_dur(int pow);
95 
tracer_info()96 tracer_info::tracer_info()
97 {
98     reset();
99 }
100 
reset()101 void tracer_info::reset()
102 {
103     count = power = hurt = helped = 0;
104     dont_stop = false;
105 }
106 
operator +=(const tracer_info & other)107 const tracer_info& tracer_info::operator+=(const tracer_info &other)
108 {
109     count  += other.count;
110     power  += other.power;
111     hurt   += other.hurt;
112     helped += other.helped;
113 
114     dont_stop = dont_stop || other.dont_stop;
115 
116     return *this;
117 }
118 
bolt()119 bolt::bolt() : animate(bool(Options.use_animations & UA_BEAM)) {}
120 
is_blockable() const121 bool bolt::is_blockable() const
122 {
123     // BEAM_ELECTRICITY is added here because chain lightning is not
124     // a true beam (stops at the first target it gets to and redirects
125     // from there)... but we don't want it shield blockable.
126     return !pierce && !is_explosion && flavour != BEAM_ELECTRICITY
127            && hit != AUTOMATIC_HIT && flavour != BEAM_VISUAL;
128 }
129 
130 /// Can 'omnireflection' (from the Warlock's Mirror) potentially reflect this?
is_omnireflectable() const131 bool bolt::is_omnireflectable() const
132 {
133     return !is_explosion && flavour != BEAM_VISUAL
134             && origin_spell != SPELL_GLACIATE;
135 }
136 
emit_message(const char * m)137 void bolt::emit_message(const char* m)
138 {
139     const string message = m;
140     if (!message_cache.count(message))
141         mpr(m);
142 
143     message_cache.insert(message);
144 }
145 
whose_kill() const146 kill_category bolt::whose_kill() const
147 {
148     if (YOU_KILL(thrower) || source_id == MID_YOU_FAULTLESS)
149         return KC_YOU;
150     else if (MON_KILL(thrower))
151     {
152         if (source_id == MID_ANON_FRIEND)
153             return KC_FRIENDLY;
154         const monster* mon = monster_by_mid(source_id);
155         if (mon && mon->friendly())
156             return KC_FRIENDLY;
157     }
158     return KC_OTHER;
159 }
160 
161 // A simple animated flash from Rupert Smith (expanded to be more
162 // generic).
_zap_animation(int colour,const monster * mon=nullptr,bool force=false)163 static void _zap_animation(int colour, const monster* mon = nullptr,
164                            bool force = false)
165 {
166     coord_def p = you.pos();
167 
168     if (mon)
169     {
170         if (!force && !mon->visible_to(&you))
171             return;
172 
173         p = mon->pos();
174     }
175 
176     if (!you.see_cell(p))
177         return;
178 
179     const coord_def drawp = grid2view(p);
180 
181     if (in_los_bounds_v(drawp))
182     {
183 #ifdef USE_TILE
184         view_add_tile_overlay(p, tileidx_zap(colour));
185 #endif
186 #ifndef USE_TILE_LOCAL
187         view_add_glyph_overlay(p, {dchar_glyph(DCHAR_FIRED_ZAP),
188                                    static_cast<unsigned short>(colour)});
189 #endif
190         viewwindow(false);
191         update_screen();
192         scaled_delay(50);
193     }
194 }
195 
196 // Special front function for zap_animation to interpret enchantment flavours.
_ench_animation(int flavour,const monster * mon,bool force)197 static void _ench_animation(int flavour, const monster* mon, bool force)
198 {
199     element_type elem;
200     switch (flavour)
201     {
202     case BEAM_HEALING:
203         elem = ETC_HEAL;
204         break;
205     case BEAM_INFESTATION:
206     case BEAM_PAIN:
207     case BEAM_AGONY:
208     case BEAM_VILE_CLUTCH:
209     case BEAM_VAMPIRIC_DRAINING:
210         elem = ETC_UNHOLY;
211         break;
212     case BEAM_DISPEL_UNDEAD:
213         elem = ETC_HOLY;
214         break;
215     case BEAM_POLYMORPH:
216     case BEAM_MALMUTATE:
217         elem = ETC_MUTAGENIC;
218         break;
219     case BEAM_CHAOS:
220         elem = ETC_RANDOM;
221         break;
222     case BEAM_TELEPORT:
223     case BEAM_BANISH:
224     case BEAM_BLINK:
225     case BEAM_BLINK_CLOSE:
226     case BEAM_BECKONING:
227         elem = ETC_WARP;
228         break;
229     case BEAM_MAGIC:
230         elem = ETC_MAGIC;
231         break;
232     default:
233         elem = ETC_ENCHANT;
234         break;
235     }
236 
237     _zap_animation(element_colour(elem), mon, force);
238 }
239 
240 // If needs_tracer is true, we need to check the beam path for friendly
241 // monsters.
zapping(zap_type ztype,int power,bolt & pbolt,bool needs_tracer,const char * msg,bool fail)242 spret zapping(zap_type ztype, int power, bolt &pbolt,
243                    bool needs_tracer, const char* msg, bool fail)
244 {
245     dprf(DIAG_BEAM, "zapping: power=%d", power);
246 
247     pbolt.thrower = KILL_YOU_MISSILE;
248 
249     // Check whether tracer goes through friendlies.
250     // NOTE: Whenever zapping() is called with a randomised value for power
251     // (or effect), player_tracer should be called directly with the highest
252     // power possible respecting current skill, experience level, etc.
253     if (needs_tracer && !player_tracer(ztype, power, pbolt))
254         return spret::abort;
255 
256     fail_check();
257     // Fill in the bolt structure.
258     zappy(ztype, power, false, pbolt);
259 
260     if (msg)
261         mpr(msg);
262 
263     if (ztype == ZAP_LIGHTNING_BOLT)
264     {
265         noisy(spell_effect_noise(SPELL_LIGHTNING_BOLT),
266                you.pos(), "You hear a mighty clap of thunder!");
267         pbolt.heard = true;
268     }
269 
270     if (ztype == ZAP_DIG)
271         pbolt.aimed_at_spot = false;
272 
273     pbolt.fire();
274 
275     return spret::success;
276 }
277 
278 // Returns true if the path is considered "safe", and false if there are
279 // monsters in the way the player doesn't want to hit.
player_tracer(zap_type ztype,int power,bolt & pbolt,int range)280 bool player_tracer(zap_type ztype, int power, bolt &pbolt, int range)
281 {
282     // Non-controlleable during confusion.
283     // (We'll shoot in a different direction anyway.)
284     if (you.confused())
285         return true;
286 
287     zappy(ztype, power, false, pbolt);
288 
289     pbolt.is_tracer     = true;
290     pbolt.source        = you.pos();
291     pbolt.source_id     = MID_PLAYER;
292     pbolt.attitude      = ATT_FRIENDLY;
293     pbolt.thrower       = KILL_YOU_MISSILE;
294 
295 
296     // Init tracer variables.
297     pbolt.friend_info.reset();
298     pbolt.foe_info.reset();
299 
300     pbolt.foe_ratio        = 100;
301     pbolt.beam_cancelled   = false;
302     pbolt.dont_stop_player = false;
303     pbolt.dont_stop_trees  = false;
304 
305     // Clear misc
306     pbolt.seen          = false;
307     pbolt.heard         = false;
308     pbolt.reflections   = 0;
309     pbolt.bounces       = 0;
310 
311     // Save range before overriding it
312     const int old_range = pbolt.range;
313     if (range)
314         pbolt.range = range;
315 
316     pbolt.fire();
317 
318     if (range)
319         pbolt.range = old_range;
320 
321     // Should only happen if the player answered 'n' to one of those
322     // "Fire through friendly?" prompts.
323     if (pbolt.beam_cancelled)
324     {
325         dprf(DIAG_BEAM, "Beam cancelled.");
326         you.turn_is_over = false;
327         return false;
328     }
329 
330     // Set to non-tracing for actual firing.
331     pbolt.is_tracer = false;
332     return true;
333 }
334 
335 // Returns true if the player wants / needs to abort based on god displeasure
336 // with targeting this target with this spell. Returns false otherwise.
_stop_because_god_hates_target_prompt(monster * mon,spell_type spell)337 static bool _stop_because_god_hates_target_prompt(monster* mon,
338                                                   spell_type spell)
339 {
340     if (spell == SPELL_TUKIMAS_DANCE)
341     {
342         const item_def * const first = mon->weapon(0);
343         const item_def * const second = mon->weapon(1);
344         bool prompt = first && god_hates_item(*first)
345                       || second && god_hates_item(*second);
346         if (prompt
347             && !yesno("Animating this weapon would place you under penance. "
348             "Really cast this spell?", false, 'n'))
349         {
350             return true;
351         }
352     }
353 
354     return false;
355 }
356 
357 template<typename T>
358 class power_deducer
359 {
360 public:
361     virtual T operator()(int pow, bool random = true) const = 0;
~power_deducer()362     virtual ~power_deducer() {}
363 };
364 
365 typedef power_deducer<int> tohit_deducer;
366 
367 template<int adder, int mult_num = 0, int mult_denom = 1>
368 class tohit_calculator : public tohit_deducer
369 {
370 public:
operator ()(int pow,bool) const371     int operator()(int pow, bool /*random*/) const override
372     {
373         return adder + pow * mult_num / mult_denom;
374     }
375 };
376 
377 typedef power_deducer<dice_def> dam_deducer;
378 
379 template<int numdice, int adder, int mult_num, int mult_denom>
380 class dicedef_calculator : public dam_deducer
381 {
382 public:
operator ()(int pow,bool) const383     dice_def operator()(int pow, bool /*random*/) const override
384     {
385         return dice_def(numdice, adder + pow * mult_num / mult_denom);
386     }
387 };
388 
389 template<int numdice, int adder, int mult_num, int mult_denom>
390 class calcdice_calculator : public dam_deducer
391 {
392 public:
operator ()(int pow,bool random) const393     dice_def operator()(int pow, bool random) const override
394     {
395         return calc_dice(numdice, adder + pow * mult_num / mult_denom, random);
396     }
397 };
398 
399 struct zap_info
400 {
401     zap_type ztype;
402     const char* name;           // nullptr means handled specially
403     int player_power_cap;
404     dam_deducer* player_damage;
405     tohit_deducer* player_tohit;    // Enchantments have power modifier here
406     dam_deducer* monster_damage;
407     tohit_deducer* monster_tohit;
408     colour_t colour;
409     bool is_enchantment;
410     beam_type flavour;
411     dungeon_char_type glyph;
412     bool can_beam;
413     bool is_explosion;
414     int hit_loudness;
415 };
416 
417 #include "zap-data.h"
418 
419 static int zap_index[NUM_ZAPS];
420 
init_zap_index()421 void init_zap_index()
422 {
423     for (int i = 0; i < NUM_ZAPS; ++i)
424         zap_index[i] = -1;
425 
426     for (unsigned int i = 0; i < ARRAYSZ(zap_data); ++i)
427         zap_index[zap_data[i].ztype] = i;
428 }
429 
_seek_zap(zap_type z_type)430 static const zap_info* _seek_zap(zap_type z_type)
431 {
432     ASSERT_RANGE(z_type, 0, NUM_ZAPS);
433     if (zap_index[z_type] == -1)
434         return nullptr;
435     else
436         return &zap_data[zap_index[z_type]];
437 }
438 
zap_explodes(zap_type z_type)439 bool zap_explodes(zap_type z_type)
440 {
441     const zap_info* zinfo = _seek_zap(z_type);
442     return zinfo && zinfo->is_explosion;
443 }
444 
zap_is_enchantment(zap_type z_type)445 bool zap_is_enchantment(zap_type z_type)
446 {
447     const zap_info* zinfo = _seek_zap(z_type);
448     return zinfo && zinfo->is_enchantment;
449 }
450 
zap_to_hit(zap_type z_type,int power,bool is_monster)451 int zap_to_hit(zap_type z_type, int power, bool is_monster)
452 {
453     const zap_info* zinfo = _seek_zap(z_type);
454     if (!zinfo)
455         return 0;
456     const tohit_deducer* hit_calc = is_monster ? zinfo->monster_tohit
457                                                : zinfo->player_tohit;
458     if (zinfo->is_enchantment)
459         return 0;
460     ASSERT(hit_calc);
461     const int hit = (*hit_calc)(power);
462     if (hit != AUTOMATIC_HIT && !is_monster && crawl_state.need_save)
463         return max(0, hit - 5 * you.inaccuracy());
464     return hit;
465 }
466 
zap_damage(zap_type z_type,int power,bool is_monster,bool random)467 dice_def zap_damage(zap_type z_type, int power, bool is_monster, bool random)
468 {
469     const zap_info* zinfo = _seek_zap(z_type);
470     if (!zinfo)
471         return dice_def(0,0);
472     const dam_deducer* dam_calc = is_monster ? zinfo->monster_damage
473                                              : zinfo->player_damage;
474     return dam_calc ? (*dam_calc)(power, random) : dice_def(0,0);
475 }
476 
zap_colour(zap_type z_type)477 colour_t zap_colour(zap_type z_type)
478 {
479     const zap_info* zinfo = _seek_zap(z_type);
480     if (!zinfo)
481         return BLACK;
482     return zinfo->colour;
483 }
484 
zap_power_cap(zap_type z_type)485 int zap_power_cap(zap_type z_type)
486 {
487     const zap_info* zinfo = _seek_zap(z_type);
488 
489     return zinfo ? zinfo->player_power_cap : 0;
490 }
491 
zap_ench_power(zap_type z_type,int pow,bool is_monster)492 int zap_ench_power(zap_type z_type, int pow, bool is_monster)
493 {
494     const zap_info* zinfo = _seek_zap(z_type);
495     if (!zinfo)
496         return pow;
497 
498     if (zinfo->player_power_cap > 0 && !is_monster)
499         pow = min(zinfo->player_power_cap, pow);
500 
501     tohit_deducer* ench_calc = is_monster ? zinfo->monster_tohit
502                                           : zinfo->player_tohit;
503     if (zinfo->is_enchantment && ench_calc)
504         return (*ench_calc)(pow);
505     else
506         return pow;
507 }
508 
zappy(zap_type z_type,int power,bool is_monster,bolt & pbolt)509 void zappy(zap_type z_type, int power, bool is_monster, bolt &pbolt)
510 {
511     const zap_info* zinfo = _seek_zap(z_type);
512 
513     // None found?
514     if (zinfo == nullptr)
515     {
516         dprf("Couldn't find zap type %d", z_type);
517         return;
518     }
519 
520     // Fill
521     pbolt.name           = zinfo->name;
522     pbolt.flavour        = zinfo->flavour;
523     pbolt.real_flavour   = zinfo->flavour;
524     pbolt.colour         = zinfo->colour;
525     pbolt.glyph          = dchar_glyph(zinfo->glyph);
526     pbolt.pierce         = zinfo->can_beam;
527     pbolt.is_explosion   = zinfo->is_explosion;
528 
529     if (zinfo->player_power_cap > 0 && !is_monster)
530         power = min(zinfo->player_power_cap, power);
531 
532     ASSERT(zinfo->is_enchantment == pbolt.is_enchantment());
533 
534     pbolt.ench_power = zap_ench_power(z_type, power, is_monster);
535 
536     if (zinfo->is_enchantment)
537         pbolt.hit = AUTOMATIC_HIT;
538     else
539         pbolt.hit = zap_to_hit(z_type, power, is_monster);
540 
541     pbolt.damage = zap_damage(z_type, power, is_monster);
542 
543     if (pbolt.origin_spell == SPELL_NO_SPELL)
544         pbolt.origin_spell = zap_to_spell(z_type);
545 
546     if (pbolt.loudness == 0)
547         pbolt.loudness = zinfo->hit_loudness;
548 }
549 
can_affect_actor(const actor * act) const550 bool bolt::can_affect_actor(const actor *act) const
551 {
552     // Blinkbolt doesn't hit its caster, since they are the bolt.
553     if (origin_spell == SPELL_BLINKBOLT && act->mid == source_id)
554         return false;
555     auto cnt = hit_count.find(act->mid);
556     if (cnt != hit_count.end() && cnt->second >= 2)
557     {
558         // Note: this is done for balance, even if it hurts realism a bit.
559         // It is arcane knowledge which wall patterns will cause lightning
560         // to bounce thrice, double damage for ordinary bounces is enough.
561 #ifdef DEBUG_DIAGNOSTICS
562         if (!quiet_debug)
563             dprf(DIAG_BEAM, "skipping beam hit, affected them twice already");
564 #endif
565         return false;
566     }
567 
568     return !act->submerged();
569 }
570 
571 // Choose the beam effect for BEAM_CHAOS that's analogous to the effect used by
572 // SPWPN_CHAOS, with weightings similar to those use by that brand. XXX: Rework
573 // this and SPWPN_CHAOS to use the same tables.
_chaos_beam_flavour(bolt * beam)574 static beam_type _chaos_beam_flavour(bolt* beam)
575 {
576     UNUSED(beam);
577 
578     beam_type flavour;
579     flavour = random_choose_weighted(
580          // SPWPN_CHAOS randomizes to brands analogous to these beam effects
581          // with similar weights.
582          70, BEAM_FIRE,
583          70, BEAM_COLD,
584          70, BEAM_ELECTRICITY,
585          70, BEAM_POISON,
586          // Combined weight from drain + vamp.
587          70, BEAM_NEG,
588          35, BEAM_HOLY,
589          14, BEAM_CONFUSION,
590          // We don't have a distortion beam, so choose from the three effects
591          // we can use, based on the lower weight distortion has.
592           5, BEAM_BANISH,
593           5, BEAM_BLINK,
594           5, BEAM_TELEPORT,
595          // From here are beam effects analogous to effects that happen when
596          // SPWPN_CHAOS chooses itself again as the ego (roughly 1/7 chance).
597          // Weights similar to those from chaos_effects in attack.cc
598          10, BEAM_SLOW,
599          10, BEAM_HASTE,
600          10, BEAM_INVISIBILITY,
601           5, BEAM_PARALYSIS,
602           5, BEAM_PETRIFY,
603           5, BEAM_BERSERK,
604          // Combined weight for poly, clone, and "shapeshifter" effects.
605           5, BEAM_POLYMORPH,
606          // Seen through miscast effects.
607           5, BEAM_ACID,
608           5, BEAM_DAMNATION,
609           5, BEAM_STICKY_FLAME,
610           5, BEAM_MINDBURST,
611          // These are not actualy used by SPWPN_CHAOS, but are here to augment
612          // the list of effects, since not every SPWN_CHAOS effect has an
613          // analogous BEAM_ type.
614           4, BEAM_MIGHT,
615           4, BEAM_HEALING,
616           4, BEAM_AGILITY,
617           4, BEAM_ENSNARE);
618 
619     return flavour;
620 }
621 
visible() const622 bool bolt::visible() const
623 {
624     return !is_tracer && glyph != 0 && !is_enchantment();
625 }
626 
initialise_fire()627 void bolt::initialise_fire()
628 {
629     // Fix some things which the tracer might have set.
630     extra_range_used   = 0;
631     in_explosion_phase = false;
632     use_target_as_pos  = false;
633     hit_count.clear();
634 
635     if (special_explosion != nullptr)
636     {
637         ASSERT(!is_explosion);
638         ASSERT(special_explosion->is_explosion);
639         ASSERT(special_explosion->special_explosion == nullptr);
640         special_explosion->in_explosion_phase = false;
641         special_explosion->use_target_as_pos  = false;
642     }
643 
644     if (chose_ray)
645     {
646         ASSERT_IN_BOUNDS(ray.pos());
647 
648         if (source == coord_def())
649             source = ray.pos();
650     }
651 
652     if (target == source)
653     {
654         range             = 0;
655         aimed_at_feet     = true;
656         auto_hit          = true;
657         aimed_at_spot     = true;
658         use_target_as_pos = true;
659     }
660 
661     ASSERT_IN_BOUNDS(source);
662     ASSERT_RANGE(flavour, BEAM_NONE + 1, BEAM_FIRST_PSEUDO);
663     ASSERT(!drop_item || item && item->defined());
664     ASSERTM(range >= 0, "beam '%s', source '%s', item '%s'; has range -1",
665             name.c_str(),
666             (source_id == MID_PLAYER ? "player" :
667                           monster_by_mid(source_id) ?
668                              monster_by_mid(source_id)->name(DESC_PLAIN, true) :
669                           "unknown").c_str(),
670             (item ? item->name(DESC_PLAIN, false, true) : "none").c_str());
671     ASSERT(!aimed_at_feet || source == target);
672 
673     real_flavour = flavour;
674 
675     message_cache.clear();
676 
677     // seen might be set by caller to suppress this.
678     if (!seen && you.see_cell(source) && range > 0 && visible())
679     {
680         seen = true;
681         const monster* mon = monster_at(source);
682 
683         if (flavour != BEAM_VISUAL
684             && !YOU_KILL(thrower)
685             && !crawl_state.is_god_acting()
686             && (!mon || !mon->observable()))
687         {
688             mprf("%s appears from out of thin air!",
689                  article_a(name, false).c_str());
690         }
691     }
692 
693     // Visible self-targeted beams are always seen, even though they don't
694     // leave a path.
695     if (you.see_cell(source) && target == source && visible())
696         seen = true;
697 
698     // The agent may die during the beam's firing, need to save these now.
699     // If the beam was reflected, assume it can "see" anything, since neither
700     // the reflector nor the original source was particularly aiming for this
701     // target. WARNING: if you change this logic, keep in mind that
702     // env.mons[YOU_FAULTLESS] cannot be safely queried for properties like
703     // can_see_invisible.
704     if (reflections > 0)
705         nightvision = can_see_invis = true;
706     else
707     {
708         // XXX: Should non-agents count as seeing invisible?
709         nightvision = agent() && agent()->nightvision();
710         can_see_invis = agent() && agent()->can_see_invisible();
711     }
712 
713 #ifdef DEBUG_DIAGNOSTICS
714     // Not a "real" tracer, merely a range/reachability check.
715     if (quiet_debug)
716         return;
717 
718     dprf(DIAG_BEAM, "%s%s%s [%s] (%d,%d) to (%d,%d): "
719           "gl=%d col=%d flav=%d hit=%d dam=%dd%d range=%d",
720           (pierce) ? "beam" : "missile",
721           (is_explosion) ? "*" :
722           (is_big_cloud()) ? "+" : "",
723           (is_tracer) ? " tracer" : "",
724           name.c_str(),
725           source.x, source.y,
726           target.x, target.y,
727           glyph, colour, flavour,
728           hit, damage.num, damage.size,
729           range);
730 #endif
731 }
732 
apply_beam_conducts()733 void bolt::apply_beam_conducts()
734 {
735     if (!is_tracer && YOU_KILL(thrower))
736     {
737         switch (flavour)
738         {
739         case BEAM_DAMNATION:
740         {
741             const int level = 2 + random2(3);
742             did_god_conduct(DID_EVIL, level, god_cares());
743             break;
744         }
745         default:
746             break;
747         }
748     }
749 }
750 
choose_ray()751 void bolt::choose_ray()
752 {
753     if ((!chose_ray || reflections > 0)
754         && !find_ray(source, target, ray, opc_solid_see)
755         // If fire is blocked, at least try a visible path so the
756         // error message is better.
757         && !find_ray(source, target, ray, opc_default))
758     {
759         fallback_ray(source, target, ray);
760     }
761 }
762 
763 // Draw the bolt at p if needed.
draw(const coord_def & p,bool force_refresh)764 void bolt::draw(const coord_def& p, bool force_refresh)
765 {
766     if (is_tracer || is_enchantment() || !you.see_cell(p))
767         return;
768 
769     // We don't clean up the old position.
770     // First, most people like to see the full path,
771     // and second, it is hard to do it right with
772     // respect to killed monsters, cloud trails, etc.
773 
774     const coord_def drawpos = grid2view(p);
775 
776     if (!in_los_bounds_v(drawpos))
777         return;
778 
779 #ifdef USE_TILE
780     if (tile_beam == -1)
781         tile_beam = tileidx_bolt(*this);
782 
783     if (tile_beam != -1)
784     {
785         int dist = (p - source).rdist();
786         view_add_tile_overlay(p, vary_bolt_tile(tile_beam, dist));
787     }
788 #endif
789 #ifndef USE_TILE_LOCAL
790     const unsigned short c = colour == BLACK ? random_colour(true)
791                                              : element_colour(colour);
792     view_add_glyph_overlay(p, {glyph, c});
793 #endif
794     // to avoid redraws, set force_refresh = false, and draw_delay = 0. This
795     // will still force a refresh if there is a draw_delay regardless of the
796     // param, because a delay on drawing is pretty meaningless without a
797     // redraw.
798     if (force_refresh || draw_delay > 0)
799     {
800         viewwindow(false);
801         update_screen();
802         scaled_delay(draw_delay);
803     }
804 }
805 
806 // Bounce a bolt off a solid feature.
807 // The ray is assumed to have just been advanced into
808 // the feature.
bounce()809 void bolt::bounce()
810 {
811     ASSERT(cell_is_solid(ray.pos()));
812     // Don't bounce player tracers off unknown cells, or cells that we
813     // incorrectly thought were non-bouncy.
814     if (is_tracer && agent() == &you)
815     {
816         const dungeon_feature_type feat = env.map_knowledge(ray.pos()).feat();
817 
818         if (feat == DNGN_UNSEEN || !feat_is_solid(feat) || !is_bouncy(feat))
819         {
820             ray.regress();
821             finish_beam();
822             return;
823         }
824     }
825 
826     do
827     {
828         ray.regress();
829     }
830     while (cell_is_solid(ray.pos()));
831 
832     extra_range_used += range_used(true);
833     bounce_pos = ray.pos();
834     bounces++;
835     reflect_grid rg;
836     for (adjacent_iterator ai(ray.pos(), false); ai; ++ai)
837         rg(*ai - ray.pos()) = cell_is_solid(*ai);
838     ray.bounce(rg);
839     extra_range_used += 2;
840 
841     // Keep length of bounced spells constant despite reduced los (scarf of
842     // shadows, Robe of Night)
843     if (bounces == 1)
844     {
845         extra_range_used -= spell_range(origin_spell, ench_power, true, true)
846                             - range;
847     }
848 
849     ASSERT(!cell_is_solid(ray.pos()));
850 }
851 
fake_flavour()852 void bolt::fake_flavour()
853 {
854     if (real_flavour == BEAM_RANDOM)
855         flavour = static_cast<beam_type>(random_range(BEAM_FIRE, BEAM_ACID));
856     else if (real_flavour == BEAM_CHAOS)
857         flavour = _chaos_beam_flavour(this);
858     else if (real_flavour == BEAM_CRYSTAL && flavour == BEAM_CRYSTAL)
859     {
860         flavour = random_choose(BEAM_FIRE, BEAM_COLD);
861         hit_verb = (flavour == BEAM_FIRE) ? "burns" :
862                    (flavour == BEAM_COLD) ? "freezes"
863                                           : "bugs";
864     }
865 }
866 
digging_wall_effect()867 void bolt::digging_wall_effect()
868 {
869     if (env.markers.property_at(pos(), MAT_ANY, "veto_destroy") == "veto")
870     {
871         finish_beam();
872         return;
873     }
874 
875     const dungeon_feature_type feat = env.grid(pos());
876     if (feat_is_diggable(feat))
877     {
878         destroy_wall(pos());
879         if (!msg_generated)
880         {
881             if (!you.see_cell(pos()))
882             {
883                 if (!silenced(you.pos()))
884                 {
885                     mprf(MSGCH_SOUND, "You hear a grinding noise.");
886                     obvious_effect = true; // You may still see the caster.
887                     msg_generated = true;
888                 }
889                 return;
890             }
891 
892             obvious_effect = true;
893             msg_generated = true;
894 
895             string wall;
896             if (feat == DNGN_GRATE)
897             {
898                 // XXX: should this change for monsters?
899                 mpr("The damaged grate falls apart.");
900                 return;
901             }
902             else if (feat == DNGN_SLIMY_WALL)
903                 wall = "slime";
904             else if (player_in_branch(BRANCH_PANDEMONIUM))
905                 wall = "weird stuff";
906             else
907                 wall = "rock";
908 
909             mprf("%s %s shatters into small pieces.",
910                  agent() && agent()->is_player() ? "The" : "Some",
911                  wall.c_str());
912         }
913     }
914     else if (feat_is_wall(feat))
915         finish_beam();
916 }
917 
burn_wall_effect()918 void bolt::burn_wall_effect()
919 {
920     dungeon_feature_type feat = env.grid(pos());
921     // Fire only affects things that can burn.
922     if (!feat_is_flammable(feat)
923         || env.markers.property_at(pos(), MAT_ANY, "veto_destroy") == "veto"
924         || !can_burn_trees()) // sanity
925     {
926         finish_beam();
927         return;
928     }
929 
930     // Destroy the wall.
931     destroy_wall(pos());
932     if (you.see_cell(pos()))
933     {
934         if (feat == DNGN_TREE)
935             emit_message("The tree burns like a torch!");
936         else if (feat == DNGN_MANGROVE)
937             emit_message("The mangrove smoulders and burns.");
938         else if (feat == DNGN_DEMONIC_TREE)
939             emit_message("The demonic tree burns, releasing chaotic energy.");
940     }
941     else if (you.can_smell())
942         emit_message("You smell burning wood.");
943     if (whose_kill() == KC_YOU)
944         did_god_conduct(DID_KILL_PLANT, 1, god_cares());
945     else if (whose_kill() == KC_FRIENDLY && !crawl_state.game_is_arena())
946         did_god_conduct(DID_KILL_PLANT, 1, god_cares());
947 
948     if (feat == DNGN_TREE)
949         place_cloud(CLOUD_FOREST_FIRE, pos(), random2(30)+25, agent());
950     // Mangroves do not burn so readily.
951     else if (feat == DNGN_MANGROVE)
952         place_cloud(CLOUD_FIRE, pos(), random2(12)+5, agent());
953     // Demonic trees produce a chaos cloud instead of fire.
954     else if (feat == DNGN_DEMONIC_TREE)
955         place_cloud(CLOUD_CHAOS, pos(), random2(30)+25, agent());
956 
957     obvious_effect = true;
958 
959     finish_beam();
960 }
961 
range_used(bool leg_only) const962 int bolt::range_used(bool leg_only) const
963 {
964     const int leg_length = pos().distance_from(leg_source());
965     return leg_only ? leg_length : leg_length + extra_range_used;
966 }
967 
finish_beam()968 void bolt::finish_beam()
969 {
970     extra_range_used = BEAM_STOP;
971 }
972 
affect_wall()973 void bolt::affect_wall()
974 {
975     if (is_tracer)
976     {
977         if (!in_bounds(pos()) || !can_affect_wall(pos(), true))
978             finish_beam();
979 
980         // potentially warn about offending your god by burning trees
981         const bool god_relevant = you.religion == GOD_FEDHAS
982                                   && can_burn_trees();
983         const bool vetoed =
984             !feat_is_flammable(env.grid(pos())) &&
985             env.markers.property_at(pos(), MAT_ANY, "veto_destroy") == "veto";
986 
987         if (god_relevant && feat_is_tree(env.map_knowledge(pos()).feat())
988             && !vetoed && !is_targeting && YOU_KILL(thrower)
989             && !dont_stop_trees)
990         {
991             const string prompt =
992                 make_stringf("Are you sure you want to burn %s?",
993                              feature_description_at(pos(), false, DESC_THE).c_str());
994 
995             if (yesno(prompt.c_str(), false, 'n'))
996                 dont_stop_trees = true;
997             else
998             {
999                 canned_msg(MSG_OK);
1000                 beam_cancelled = true;
1001                 finish_beam();
1002             }
1003         }
1004 
1005         // The only thing that doesn't stop at walls.
1006         if (flavour != BEAM_DIGGING)
1007             finish_beam();
1008         return;
1009     }
1010     if (in_bounds(pos()))
1011     {
1012         if (flavour == BEAM_DIGGING)
1013             digging_wall_effect();
1014         else if (can_burn_trees())
1015             burn_wall_effect();
1016     }
1017     if (cell_is_solid(pos()))
1018         finish_beam();
1019 }
1020 
pos() const1021 coord_def bolt::pos() const
1022 {
1023     if (in_explosion_phase || use_target_as_pos)
1024         return target;
1025     else
1026         return ray.pos();
1027 }
1028 
need_regress() const1029 bool bolt::need_regress() const
1030 {
1031     // XXX: The affects_wall check probably makes some of the
1032     //      others obsolete.
1033     return (is_explosion && !in_explosion_phase)
1034            || drop_item
1035            || cell_is_solid(pos()) && !can_affect_wall(pos())
1036            || origin_spell == SPELL_PRIMAL_WAVE;
1037 }
1038 
affect_cell()1039 void bolt::affect_cell()
1040 {
1041     fake_flavour();
1042 
1043     // Note that this can change the solidity of the wall.
1044     if (cell_is_solid(pos()))
1045         affect_wall();
1046 
1047     if (origin_spell == SPELL_CHAIN_LIGHTNING && pos() != target)
1048         return;
1049 
1050     // If the player can ever walk through walls, this will need
1051     // special-casing too.
1052     bool hit_player = found_player() && !ignores_player();
1053     if (hit_player && can_affect_actor(&you))
1054     {
1055         const int prev_reflections = reflections;
1056         affect_player();
1057         if (reflections != prev_reflections)
1058             return;
1059         if (hit == AUTOMATIC_HIT && !pierce)
1060             finish_beam();
1061     }
1062 
1063     // Stop single target beams from affecting a monster if they already
1064     // affected the player on this square. -cao
1065     if (!hit_player || pierce || is_explosion)
1066     {
1067         monster *m = monster_at(pos());
1068         if (m && can_affect_actor(m))
1069         {
1070             const bool ignored = ignores_monster(m);
1071             affect_monster(m);
1072             if (hit == AUTOMATIC_HIT && !pierce && !ignored
1073                 // Assumes tracers will always have an agent!
1074                 && (!is_tracer || m->visible_to(agent())))
1075             {
1076                 finish_beam();
1077             }
1078         }
1079     }
1080 
1081     if (!cell_is_solid(pos()))
1082         affect_ground();
1083 }
1084 
_undo_tracer(bolt & orig,bolt & copy)1085 static void _undo_tracer(bolt &orig, bolt &copy)
1086 {
1087     // FIXME: we should have a better idea of what gets changed!
1088     orig.target           = copy.target;
1089     orig.source           = copy.source;
1090     orig.aimed_at_spot    = copy.aimed_at_spot;
1091     orig.extra_range_used = copy.extra_range_used;
1092     orig.auto_hit         = copy.auto_hit;
1093     orig.ray              = copy.ray;
1094     orig.colour           = copy.colour;
1095     orig.flavour          = copy.flavour;
1096     orig.real_flavour     = copy.real_flavour;
1097     orig.bounces          = copy.bounces;
1098     orig.bounce_pos       = copy.bounce_pos;
1099 }
1100 
1101 // This saves some important things before calling fire().
fire()1102 void bolt::fire()
1103 {
1104     path_taken.clear();
1105 
1106     if (special_explosion)
1107         special_explosion->is_tracer = is_tracer;
1108 
1109     if (is_tracer)
1110     {
1111         bolt boltcopy = *this;
1112         if (special_explosion != nullptr)
1113             boltcopy.special_explosion = new bolt(*special_explosion);
1114 
1115         do_fire();
1116 
1117         if (special_explosion != nullptr)
1118         {
1119             _undo_tracer(*special_explosion, *boltcopy.special_explosion);
1120             delete boltcopy.special_explosion;
1121         }
1122 
1123         _undo_tracer(*this, boltcopy);
1124     }
1125     else
1126         do_fire();
1127 
1128     //XXX: suspect, but code relies on path_taken being non-empty
1129     if (path_taken.empty())
1130         path_taken.push_back(source);
1131 
1132     if (special_explosion != nullptr)
1133     {
1134         seen           = seen  || special_explosion->seen;
1135         heard          = heard || special_explosion->heard;
1136     }
1137 }
1138 
do_fire()1139 void bolt::do_fire()
1140 {
1141     initialise_fire();
1142 
1143     if (range < extra_range_used && range > 0)
1144     {
1145 #ifdef DEBUG
1146         dprf(DIAG_BEAM, "fire_beam() called on already done beam "
1147              "'%s' (item = '%s')", name.c_str(),
1148              item ? item->name(DESC_PLAIN).c_str() : "none");
1149 #endif
1150         return;
1151     }
1152 
1153     apply_beam_conducts();
1154     cursor_control coff(false);
1155 
1156 #ifdef USE_TILE
1157     tile_beam = -1;
1158 
1159     if (item && !is_tracer && (flavour == BEAM_MISSILE
1160                                || flavour == BEAM_VISUAL))
1161     {
1162         const coord_def diff = target - source;
1163         tile_beam = tileidx_item_throw(
1164                                 get_item_known_info(*item), diff.x, diff.y);
1165     }
1166 #endif
1167 
1168     msg_generated = false;
1169     if (!aimed_at_feet)
1170     {
1171         choose_ray();
1172         // Take *one* step, so as not to hurt the source.
1173         ray.advance();
1174     }
1175 
1176     // Note: nothing but this loop should be changing the ray.
1177     while (map_bounds(pos()))
1178     {
1179         if (range_used() > range)
1180         {
1181             ray.regress();
1182             extra_range_used++;
1183             ASSERT(range_used() >= range);
1184             break;
1185         }
1186 
1187         const dungeon_feature_type feat = env.grid(pos());
1188 
1189         if (in_bounds(target)
1190             // Starburst beams are essentially untargeted; some might even hit
1191             // a victim if others have LOF blocked.
1192             && origin_spell != SPELL_STARBURST
1193             // We ran into a solid wall with a real beam...
1194             && (feat_is_solid(feat)
1195                 && flavour != BEAM_DIGGING && flavour <= BEAM_LAST_REAL
1196                 && !cell_is_solid(target)
1197             // or visible firewood with a non-penetrating beam...
1198                 || !pierce
1199                    && monster_at(pos())
1200                    && you.can_see(*monster_at(pos()))
1201                    && !ignores_monster(monster_at(pos()))
1202                    && mons_is_firewood(*monster_at(pos())))
1203             // and it's a player tracer...
1204             // (!is_targeting so you don't get prompted while adjusting the aim)
1205             && is_tracer && !is_targeting && YOU_KILL(thrower)
1206             // and we're actually between you and the target...
1207             && !passed_target && pos() != target && pos() != source
1208             // ?
1209             && foe_info.count == 0 && bounces == 0 && reflections == 0
1210             // and you aren't shooting out of LOS.
1211             && you.see_cell(target))
1212         {
1213             // Okay, with all those tests passed, this is probably an instance
1214             // of the player manually targeting something whose line of fire
1215             // is blocked, even though its line of sight isn't blocked. Give
1216             // a warning about this fact.
1217             string prompt = "Your line of fire to ";
1218             const monster* mon = monster_at(target);
1219 
1220             if (mon && mon->observable())
1221                 prompt += mon->name(DESC_THE);
1222             else
1223             {
1224                 prompt += "the targeted "
1225                         + feature_description_at(target, false, DESC_PLAIN);
1226             }
1227 
1228             prompt += " is blocked by "
1229                     + (feat_is_solid(feat) ?
1230                         feature_description_at(pos(), false, DESC_A) :
1231                         monster_at(pos())->name(DESC_A));
1232 
1233             prompt += ". Continue anyway?";
1234 
1235             if (!yesno(prompt.c_str(), false, 'n'))
1236             {
1237                 canned_msg(MSG_OK);
1238                 beam_cancelled = true;
1239                 finish_beam();
1240                 return;
1241             }
1242 
1243             // Well, we warned them.
1244         }
1245 
1246         // digging is taken care of in affect_cell
1247         if (feat_is_solid(feat) && !can_affect_wall(pos())
1248                                                     && flavour != BEAM_DIGGING)
1249         {
1250             if (is_bouncy(feat))
1251             {
1252                 bounce();
1253                 // see comment in bounce(); the beam will be cancelled if this
1254                 // is a tracer and showing the bounce would be an info leak.
1255                 // In that case, we have to break early to avoid adding this
1256                 // square to path_taken twice, which would make it look like a
1257                 // a bounce ANYWAY.
1258                 if (range_used() > range)
1259                     break;
1260             }
1261             else
1262             {
1263                 // Regress for explosions: blow up in an open grid (if regressing
1264                 // makes any sense). Also regress when dropping items.
1265                 if (pos() != source && need_regress())
1266                 {
1267                     do
1268                     {
1269                         ray.regress();
1270                     }
1271                     while (ray.pos() != source && cell_is_solid(ray.pos()));
1272 
1273                     // target is where the explosion is centered, so update it.
1274                     if (is_explosion && !is_tracer)
1275                         target = ray.pos();
1276                 }
1277                 break;
1278             }
1279         }
1280 
1281         path_taken.push_back(pos());
1282 
1283         if (!affects_nothing)
1284             affect_cell();
1285 
1286         if (range_used() > range)
1287             break;
1288 
1289         if (beam_cancelled)
1290             return;
1291 
1292         // Weapons of returning should find an inverse ray
1293         // through find_ray and setup_retrace, but they didn't
1294         // always in the past, and we don't want to crash
1295         // if they accidentally pass through a corner.
1296         // Dig tracers continue through unseen cells.
1297         ASSERT(!cell_is_solid(pos())
1298                || is_tracer && can_affect_wall(pos(), true)
1299                || affects_nothing); // returning weapons
1300 
1301         const bool was_seen = seen;
1302         if (!was_seen && range > 0 && visible() && you.see_cell(pos()))
1303             seen = true;
1304 
1305         if (flavour != BEAM_VISUAL && !was_seen && seen && !is_tracer)
1306         {
1307             mprf("%s appears from out of your range of vision.",
1308                  article_a(name, false).c_str());
1309         }
1310 
1311         // Reset chaos beams so that it won't be considered an invisible
1312         // enchantment beam for the purposes of animation.
1313         if (real_flavour == BEAM_CHAOS)
1314             flavour = real_flavour;
1315 
1316         // Actually draw the beam/missile/whatever, if the player can see
1317         // the cell.
1318         if (animate)
1319             draw(pos(), redraw_per_cell);
1320 
1321         if (pos() == target)
1322         {
1323             passed_target = true;
1324             if (stop_at_target())
1325                 break;
1326         }
1327 
1328         noise_generated = false;
1329 
1330         ray.advance();
1331     }
1332 
1333     if (!map_bounds(pos()))
1334     {
1335         ASSERT(!aimed_at_spot);
1336 
1337         int tries = max(GXM, GYM);
1338         while (!map_bounds(ray.pos()) && tries-- > 0)
1339             ray.regress();
1340 
1341         // Something bizarre happening if we can't get back onto the map.
1342         ASSERT(map_bounds(pos()));
1343     }
1344 
1345     // The beam has terminated.
1346     if (!affects_nothing)
1347         affect_endpoint();
1348 
1349     // Tracers need nothing further.
1350     if (is_tracer || affects_nothing)
1351         return;
1352 
1353     // Canned msg for enchantments that affected no-one, but only if the
1354     // enchantment is yours (and it wasn't a chaos beam, since with chaos
1355     // enchantments are entirely random, and if it randomly attempts
1356     // something which ends up having no obvious effect then the player
1357     // isn't going to realise it).
1358     if (!msg_generated && !obvious_effect && is_enchantment()
1359         && real_flavour != BEAM_CHAOS
1360         && YOU_KILL(thrower))
1361     {
1362         canned_msg(MSG_NOTHING_HAPPENS);
1363     }
1364 
1365     // Reactions if a monster zapped the beam.
1366     if (monster_by_mid(source_id))
1367     {
1368         if (foe_info.hurt == 0 && friend_info.hurt > 0)
1369             xom_is_stimulated(100);
1370         else if (foe_info.helped > 0 && friend_info.helped == 0)
1371             xom_is_stimulated(100);
1372 
1373         // Allow friendlies to react to projectiles, except when in
1374         // sanctuary when pet_target can only be explicitly changed by
1375         // the player.
1376         const monster* mon = monster_by_mid(source_id);
1377         if (foe_info.hurt > 0 && !mon->wont_attack() && !crawl_state.game_is_arena()
1378             && you.pet_target == MHITNOT && env.sanctuary_time <= 0)
1379         {
1380             you.pet_target = mon->mindex();
1381         }
1382     }
1383 }
1384 
1385 // Returns damage taken by a monster from a "flavoured" (fire, ice, etc.)
1386 // attack -- damage from clouds and branded weapons handled elsewhere.
mons_adjust_flavoured(monster * mons,bolt & pbolt,int hurted,bool doFlavouredEffects)1387 int mons_adjust_flavoured(monster* mons, bolt &pbolt, int hurted,
1388                           bool doFlavouredEffects)
1389 {
1390     // If we're not doing flavoured effects, must be preliminary
1391     // damage check only.
1392     // Do not print messages or apply any side effects!
1393     int original = hurted;
1394 
1395     switch (pbolt.flavour)
1396     {
1397     case BEAM_FIRE:
1398     case BEAM_STEAM:
1399         hurted = resist_adjust_damage(mons, pbolt.flavour, hurted);
1400 
1401         if (!hurted)
1402         {
1403             if (original > 0 && doFlavouredEffects)
1404                 simple_monster_message(*mons, " completely resists.");
1405         }
1406         else if (original > hurted)
1407         {
1408             if (doFlavouredEffects)
1409                 simple_monster_message(*mons, " resists.");
1410         }
1411         else if (original < hurted && doFlavouredEffects)
1412         {
1413             if (mons->is_icy())
1414                 simple_monster_message(*mons, " melts!");
1415             else if (mons_species(mons->type) == MONS_BUSH
1416                      && mons->res_fire() < 0)
1417             {
1418                 simple_monster_message(*mons, " is on fire!");
1419             }
1420             else if (pbolt.flavour == BEAM_FIRE)
1421                 simple_monster_message(*mons, " is burned terribly!");
1422             else
1423                 simple_monster_message(*mons, " is scalded terribly!");
1424         }
1425         break;
1426 
1427     case BEAM_WATER:
1428         hurted = resist_adjust_damage(mons, pbolt.flavour, hurted);
1429         if (hurted > original && doFlavouredEffects)
1430             simple_monster_message(*mons, " is doused terribly!");
1431         break;
1432 
1433     case BEAM_COLD:
1434         hurted = resist_adjust_damage(mons, pbolt.flavour, hurted);
1435         if (!hurted)
1436         {
1437             if (original > 0 && doFlavouredEffects)
1438                 simple_monster_message(*mons, " completely resists.");
1439         }
1440         else if (original > hurted)
1441         {
1442             if (doFlavouredEffects)
1443                 simple_monster_message(*mons, " resists.");
1444         }
1445         else if (original < hurted)
1446         {
1447             if (doFlavouredEffects)
1448                 simple_monster_message(*mons, " is frozen!");
1449         }
1450         break;
1451 
1452     case BEAM_STUN_BOLT:
1453     case BEAM_ELECTRICITY:
1454     case BEAM_THUNDER:
1455         hurted = resist_adjust_damage(mons, pbolt.flavour, hurted);
1456         if (!hurted)
1457         {
1458             if (original > 0 && doFlavouredEffects)
1459                 simple_monster_message(*mons, " completely resists.");
1460         }
1461         else if (original > hurted)
1462         {
1463             if (doFlavouredEffects)
1464                 simple_monster_message(*mons, " resists.");
1465         }
1466         else if (original < hurted)
1467         {
1468             if (doFlavouredEffects)
1469                 simple_monster_message(*mons, " is electrocuted!");
1470         }
1471         break;
1472 
1473     case BEAM_ACID:
1474     {
1475         hurted = resist_adjust_damage(mons, pbolt.flavour, hurted);
1476         if (!hurted)
1477         {
1478             if (original > 0 && doFlavouredEffects)
1479                 simple_monster_message(*mons, " completely resists.");
1480         }
1481         else if (mons->res_acid() <= 0 && doFlavouredEffects)
1482             mons->splash_with_acid(pbolt.agent());
1483         break;
1484     }
1485 
1486     case BEAM_POISON:
1487     {
1488         hurted = resist_adjust_damage(mons, pbolt.flavour, hurted);
1489 
1490         if (!hurted && doFlavouredEffects && original > 0)
1491             simple_monster_message(*mons, " completely resists.");
1492         else if (doFlavouredEffects && !one_chance_in(3))
1493         {
1494             if (pbolt.origin_spell == SPELL_SPIT_POISON &&
1495                 pbolt.agent(true)->is_monster() &&
1496                 pbolt.agent(true)->as_monster()->has_ench(ENCH_CONCENTRATE_VENOM))
1497             {
1498                 // set the cause to the reflected agent for piety &c.
1499                 curare_actor(pbolt.agent(), mons, 2, "concentrated venom",
1500                              pbolt.agent(true)->name(DESC_PLAIN));
1501             }
1502             else
1503                 poison_monster(mons, pbolt.agent());
1504         }
1505 
1506         break;
1507     }
1508 
1509     case BEAM_POISON_ARROW:
1510     {
1511         hurted = resist_adjust_damage(mons, pbolt.flavour, hurted);
1512         const int stacks = pbolt.origin_spell == SPELL_STING ? 1 : 4;
1513         if (hurted < original)
1514         {
1515             if (doFlavouredEffects)
1516             {
1517                 simple_monster_message(*mons, " partially resists.");
1518                 poison_monster(mons, pbolt.agent(), div_rand_round(stacks, 2),
1519                                true);
1520             }
1521         }
1522         else if (doFlavouredEffects)
1523             poison_monster(mons, pbolt.agent(), stacks , true);
1524 
1525         break;
1526     }
1527 
1528     case BEAM_NEG:
1529         if (mons->res_negative_energy() == 3)
1530         {
1531             if (doFlavouredEffects)
1532                 simple_monster_message(*mons, " completely resists.");
1533 
1534             hurted = 0;
1535         }
1536         else
1537         {
1538             hurted = resist_adjust_damage(mons, pbolt.flavour, hurted);
1539 
1540             // Early out if no side effects.
1541             if (!doFlavouredEffects)
1542                 return hurted;
1543 
1544             if (original > hurted)
1545                 simple_monster_message(*mons, " resists.");
1546             else if (original < hurted)
1547                 simple_monster_message(*mons, " is drained terribly!");
1548 
1549             if (mons->observable())
1550                 pbolt.obvious_effect = true;
1551 
1552             mons->drain(pbolt.agent());
1553 
1554             if (YOU_KILL(pbolt.thrower))
1555                 did_god_conduct(DID_EVIL, 2, pbolt.god_cares());
1556         }
1557         break;
1558 
1559     case BEAM_MIASMA:
1560         if (mons->res_miasma())
1561         {
1562             if (doFlavouredEffects)
1563                 simple_monster_message(*mons, " completely resists.");
1564 
1565             hurted = 0;
1566         }
1567         else
1568         {
1569             // Early out for tracer/no side effects.
1570             if (!doFlavouredEffects)
1571                 return hurted;
1572 
1573             miasma_monster(mons, pbolt.agent());
1574 
1575             if (YOU_KILL(pbolt.thrower))
1576                 did_god_conduct(DID_UNCLEAN, 2, pbolt.god_cares());
1577         }
1578         break;
1579 
1580     case BEAM_HOLY:
1581     {
1582         hurted = resist_adjust_damage(mons, pbolt.flavour, hurted);
1583         if (doFlavouredEffects && original > 0
1584             && (!hurted || hurted != original))
1585         {
1586             simple_monster_message(*mons, hurted == 0 ? " completely resists." :
1587                                     hurted < original ? " resists." :
1588                                     " writhes in agony!");
1589 
1590         }
1591         break;
1592     }
1593 
1594     case BEAM_ICE:
1595         // ice - 40% of damage is cold, other 60% is impact and
1596         // can't be resisted (except by AC, of course)
1597         hurted = resist_adjust_damage(mons, pbolt.flavour, hurted);
1598         if (hurted < original)
1599         {
1600             if (doFlavouredEffects)
1601                 simple_monster_message(*mons, " partially resists.");
1602         }
1603         else if (hurted > original)
1604         {
1605             if (doFlavouredEffects)
1606                 simple_monster_message(*mons, " is frozen!");
1607         }
1608         break;
1609 
1610     case BEAM_LAVA:
1611         hurted = resist_adjust_damage(mons, pbolt.flavour, hurted);
1612 
1613         if (hurted < original)
1614         {
1615             if (doFlavouredEffects)
1616                 simple_monster_message(*mons, " partially resists.");
1617         }
1618         else if (hurted > original)
1619         {
1620             if (mons->is_icy())
1621             {
1622                 if (doFlavouredEffects)
1623                     simple_monster_message(*mons, " melts!");
1624             }
1625             else
1626             {
1627                 if (doFlavouredEffects)
1628                     simple_monster_message(*mons, " is burned terribly!");
1629             }
1630         }
1631         break;
1632 
1633     case BEAM_DAMNATION:
1634         if (mons->res_damnation())
1635         {
1636             if (doFlavouredEffects)
1637                 simple_monster_message(*mons, " completely resists.");
1638 
1639             hurted = 0;
1640         }
1641         break;
1642 
1643     case BEAM_MEPHITIC:
1644         if (mons->res_poison() > 0)
1645         {
1646             if (original > 0 && doFlavouredEffects)
1647                 simple_monster_message(*mons, " completely resists.");
1648 
1649             hurted = 0;
1650         }
1651         break;
1652 
1653     case BEAM_SPORE:
1654         if (mons->type == MONS_BALLISTOMYCETE)
1655             hurted = 0;
1656         break;
1657 
1658     case BEAM_ENSNARE:
1659         if (doFlavouredEffects)
1660             ensnare(mons);
1661         break;
1662 
1663     default:
1664         break;
1665     }
1666 
1667     if (doFlavouredEffects)
1668     {
1669         const int burn_power = (pbolt.is_explosion) ? 5 :
1670                                (pbolt.pierce)       ? 3
1671                                                     : 2;
1672         mons->expose_to_element(pbolt.flavour, burn_power, false);
1673     }
1674 
1675     return hurted;
1676 }
1677 
_monster_resists_mass_enchantment(monster * mons,enchant_type wh_enchant,int pow,bool * did_msg)1678 static bool _monster_resists_mass_enchantment(monster* mons,
1679                                               enchant_type wh_enchant,
1680                                               int pow,
1681                                               bool* did_msg)
1682 {
1683     // Assuming that the only mass charm is control undead.
1684     if (wh_enchant == ENCH_FEAR)
1685     {
1686         if (mons->friendly())
1687             return true;
1688 
1689         if (!mons->can_feel_fear(true))
1690         {
1691             if (simple_monster_message(*mons, " is unaffected."))
1692                 *did_msg = true;
1693             return true;
1694         }
1695 
1696         int res_margin = mons->check_willpower(pow);
1697         if (res_margin > 0)
1698         {
1699             if (simple_monster_message(*mons,
1700                     mons->resist_margin_phrase(res_margin).c_str()))
1701             {
1702                 *did_msg = true;
1703             }
1704             return true;
1705         }
1706     }
1707     else if (wh_enchant == ENCH_INSANE
1708              || mons->holiness() & MH_NATURAL)
1709     {
1710 
1711         if (wh_enchant == ENCH_INSANE
1712             && !mons->can_go_frenzy())
1713         {
1714             return true;
1715         }
1716 
1717         int res_margin = mons->check_willpower(pow);
1718         if (res_margin > 0)
1719         {
1720             if (simple_monster_message(*mons,
1721                     mons->resist_margin_phrase(res_margin).c_str()))
1722             {
1723                 *did_msg = true;
1724             }
1725             return true;
1726         }
1727     }
1728     // Mass enchantments around lots of plants/fungi shouldn't cause a flood
1729     // of "is unaffected" messages. --Eino
1730     else if (mons_is_firewood(*mons))
1731         return true;
1732     else  // trying to enchant an unnatural creature doesn't work
1733     {
1734         if (simple_monster_message(*mons, " is unaffected."))
1735             *did_msg = true;
1736         return true;
1737     }
1738 
1739     // If monster was affected, then there was a message.
1740     *did_msg = true;
1741     return false;
1742 }
1743 
1744 // Enchants all monsters in player's sight.
1745 // If m_succumbed is non-nullptr, will be set to the number of monsters that
1746 // were enchanted. If m_attempted is not nullptr, will be set to the number of
1747 // monsters that we tried to enchant.
mass_enchantment(enchant_type wh_enchant,int pow,bool fail)1748 spret mass_enchantment(enchant_type wh_enchant, int pow, bool fail)
1749 {
1750     fail_check();
1751     bool did_msg = false;
1752 
1753     // Give mass enchantments a power multiplier.
1754     pow *= 3;
1755     pow /= 2;
1756 
1757     pow = min(pow, 200);
1758 
1759     for (monster_iterator mi; mi; ++mi)
1760     {
1761         if (!you.see_cell_no_trans(mi->pos()))
1762             continue;
1763 
1764         if (mi->has_ench(wh_enchant))
1765             continue;
1766 
1767         bool resisted = _monster_resists_mass_enchantment(*mi, wh_enchant,
1768                                                           pow, &did_msg);
1769 
1770         if (resisted)
1771             continue;
1772 
1773         if ((wh_enchant == ENCH_INSANE && mi->go_frenzy(&you))
1774             || (wh_enchant == ENCH_CHARM && mi->has_ench(ENCH_HEXED))
1775             || (wh_enchant != ENCH_INSANE
1776                 && mi->add_ench(mon_enchant(wh_enchant, 0, &you))))
1777         {
1778             // Do messaging.
1779             const char* msg;
1780             switch (wh_enchant)
1781             {
1782             case ENCH_FEAR:      msg = " looks frightened!";      break;
1783             case ENCH_CHARM:     msg = " submits to your will.";  break;
1784             default:             msg = nullptr;                   break;
1785             }
1786             if (msg && simple_monster_message(**mi, msg))
1787                 did_msg = true;
1788 
1789             // Reassert control over hexed undead.
1790             if (wh_enchant == ENCH_CHARM && mi->has_ench(ENCH_HEXED))
1791                 mi->del_ench(ENCH_HEXED);
1792 
1793             // Extra check for fear (monster needs to reevaluate behaviour).
1794             if (wh_enchant == ENCH_FEAR)
1795                 behaviour_event(*mi, ME_SCARE, &you);
1796         }
1797     }
1798 
1799     if (!did_msg)
1800         canned_msg(MSG_NOTHING_HAPPENS);
1801 
1802     if (wh_enchant == ENCH_INSANE)
1803         did_god_conduct(DID_HASTY, 8, true);
1804 
1805     return spret::success;
1806 }
1807 
apply_bolt_paralysis(monster * mons)1808 void bolt::apply_bolt_paralysis(monster* mons)
1809 {
1810     if (mons->paralysed() || mons->stasis())
1811         return;
1812     // asleep monsters can still be paralysed (and will be always woken by
1813     // trying to resist); the message might seem wrong but paralysis is
1814     // always visible.
1815     if (!mons_is_immotile(*mons)
1816         && simple_monster_message(*mons, " suddenly stops moving!"))
1817     {
1818         mons->stop_directly_constricting_all();
1819         obvious_effect = true;
1820     }
1821 
1822     mons->add_ench(mon_enchant(ENCH_PARALYSIS, 0, agent(),
1823                                _ench_pow_to_dur(ench_power)));
1824 }
1825 
1826 // Petrification works in two stages. First the monster is slowed down in
1827 // all of its actions, and when that times out it remains properly petrified
1828 // (no movement or actions). The second part is similar to paralysis,
1829 // except that insubstantial monsters can't be affected and damage is
1830 // drastically reduced.
apply_bolt_petrify(monster * mons)1831 void bolt::apply_bolt_petrify(monster* mons)
1832 {
1833     if (mons->petrified())
1834         return;
1835 
1836     if (mons->petrifying())
1837     {
1838         // If the petrifying is not yet finished, we can force it to happen
1839         // right away by casting again. Otherwise, the spell has no further
1840         // effect.
1841         mons->del_ench(ENCH_PETRIFYING, true, false);
1842         // del_ench() would do it, but let's call it ourselves for proper agent
1843         // blaming and messaging.
1844         if (mons->fully_petrify(agent()))
1845             obvious_effect = true;
1846     }
1847     else if (mons->add_ench(mon_enchant(ENCH_PETRIFYING, 0, agent())))
1848     {
1849         if (!mons_is_immotile(*mons)
1850             && simple_monster_message(*mons, " is moving more slowly."))
1851         {
1852             obvious_effect = true;
1853         }
1854     }
1855 }
1856 
_curare_hits_monster(actor * agent,monster * mons,int levels)1857 static bool _curare_hits_monster(actor *agent, monster* mons, int levels)
1858 {
1859     if (!mons->alive())
1860         return false;
1861 
1862     if (mons->res_poison() > 0)
1863         return false;
1864 
1865     poison_monster(mons, agent, levels, false);
1866 
1867     int hurted = roll_dice(levels, 6);
1868 
1869     if (hurted)
1870     {
1871         simple_monster_message(*mons, " convulses.");
1872         mons->hurt(agent, hurted, BEAM_POISON);
1873     }
1874 
1875     if (mons->alive())
1876     {
1877         if (!mons->cannot_move())
1878         {
1879             simple_monster_message(*mons, mons->has_ench(ENCH_SLOW)
1880                                          ? " seems to be slow for longer."
1881                                          : " seems to slow down.");
1882         }
1883         // FIXME: calculate the slow duration more cleanly
1884         mon_enchant me(ENCH_SLOW, 0, agent);
1885         levels -= 2;
1886         while (levels > 0)
1887         {
1888             mon_enchant me2(ENCH_SLOW, 0, agent);
1889             me.set_duration(mons, &me2);
1890             levels -= 2;
1891         }
1892         mons->add_ench(me);
1893     }
1894 
1895     return hurted > 0;
1896 }
1897 
1898 // Actually poisons a monster (with message).
poison_monster(monster * mons,const actor * who,int levels,bool force,bool verbose)1899 bool poison_monster(monster* mons, const actor *who, int levels,
1900                     bool force, bool verbose)
1901 {
1902     if (!mons->alive() || levels <= 0)
1903         return false;
1904 
1905     if (monster_resists_this_poison(*mons, force))
1906         return false;
1907 
1908     const mon_enchant old_pois = mons->get_ench(ENCH_POISON);
1909     mons->add_ench(mon_enchant(ENCH_POISON, levels, who));
1910     const mon_enchant new_pois = mons->get_ench(ENCH_POISON);
1911 
1912     // Actually do the poisoning. The order is important here.
1913     if (new_pois.degree > old_pois.degree
1914         || new_pois.degree >= MAX_ENCH_DEGREE_DEFAULT)
1915     {
1916         if (verbose)
1917         {
1918             const char* msg;
1919             if (new_pois.degree >= MAX_ENCH_DEGREE_DEFAULT)
1920                 msg = " looks as sick as possible!";
1921             else if (old_pois.degree > 0)
1922                 msg = " looks even sicker.";
1923             else
1924                 msg = " is poisoned.";
1925 
1926             simple_monster_message(*mons, msg);
1927         }
1928     }
1929 
1930     return new_pois.duration > old_pois.duration;
1931 }
1932 
1933 // Actually poisons, drains, and/or slows a monster with miasma (with
1934 // message).
miasma_monster(monster * mons,const actor * who)1935 bool miasma_monster(monster* mons, const actor* who)
1936 {
1937     if (!mons->alive())
1938         return false;
1939 
1940     if (mons->res_miasma())
1941         return false;
1942 
1943     bool success = poison_monster(mons, who);
1944 
1945     if (who && who->is_player()
1946         && is_good_god(you.religion)
1947         && !(success && you_worship(GOD_SHINING_ONE))) // already penalized
1948     {
1949         did_god_conduct(DID_EVIL, 5 + random2(3));
1950     }
1951 
1952     if (one_chance_in(3))
1953     {
1954         bolt beam;
1955         beam.flavour = BEAM_SLOW;
1956         beam.apply_enchantment_to_monster(mons);
1957         success = true;
1958     }
1959 
1960     return success;
1961 }
1962 
1963 // Actually napalms a monster (with message).
napalm_monster(monster * mons,const actor * who,int levels,bool verbose)1964 bool napalm_monster(monster* mons, const actor *who, int levels, bool verbose)
1965 {
1966     if (!mons->alive())
1967         return false;
1968 
1969     if (mons->res_sticky_flame() || levels <= 0 || mons->has_ench(ENCH_WATER_HOLD))
1970         return false;
1971 
1972     const mon_enchant old_flame = mons->get_ench(ENCH_STICKY_FLAME);
1973     mons->add_ench(mon_enchant(ENCH_STICKY_FLAME, levels, who));
1974     const mon_enchant new_flame = mons->get_ench(ENCH_STICKY_FLAME);
1975 
1976     // Actually do the napalming. The order is important here.
1977     if (new_flame.degree > old_flame.degree)
1978     {
1979         if (verbose)
1980             simple_monster_message(*mons, " is covered in liquid flames!");
1981         if (who)
1982             behaviour_event(mons, ME_WHACK, who);
1983     }
1984 
1985     return new_flame.degree > old_flame.degree;
1986 }
1987 
_curare_hits_player(actor * agent,int levels,string name,string source_name)1988 static bool _curare_hits_player(actor* agent, int levels, string name,
1989                                 string source_name)
1990 {
1991     ASSERT(!crawl_state.game_is_arena());
1992 
1993     if (player_res_poison() >= 3
1994         || player_res_poison() > 0 && !one_chance_in(3))
1995     {
1996         return false;
1997     }
1998 
1999     poison_player(roll_dice(levels, 12) + 1, source_name, name);
2000 
2001     int hurted = roll_dice(levels, 6);
2002 
2003     if (hurted)
2004     {
2005         mpr("You have difficulty breathing.");
2006         ouch(hurted, KILLED_BY_CURARE, agent->mid,
2007              "curare-induced apnoea");
2008     }
2009 
2010     slow_player(10 + random2(levels + random2(3 * levels)));
2011 
2012     return hurted > 0;
2013 }
2014 
2015 
curare_actor(actor * source,actor * target,int levels,string name,string source_name)2016 bool curare_actor(actor* source, actor* target, int levels, string name,
2017                   string source_name)
2018 {
2019     if (target->is_player())
2020         return _curare_hits_player(source, levels, name, source_name);
2021     else
2022         return _curare_hits_monster(source, target->as_monster(), levels);
2023 }
2024 
2025 // XXX: This is a terrible place for this, but it at least does go with
2026 // curare_actor().
silver_damages_victim(actor * victim,int damage,string & dmg_msg)2027 int silver_damages_victim(actor* victim, int damage, string &dmg_msg)
2028 {
2029     int ret = 0;
2030     if (victim->how_chaotic()
2031         || victim->is_player() && player_is_shapechanged())
2032     {
2033         ret = damage * 3 / 4;
2034     }
2035     else if (victim->is_player())
2036     {
2037         // For mutation damage, we want to count innate mutations for
2038         // demonspawn but not other species.
2039         int multiplier = 5 * you.how_mutated(you.species == SP_DEMONSPAWN, true);
2040         if (multiplier == 0)
2041             return 0;
2042 
2043         if (multiplier > 75)
2044             multiplier = 75;
2045 
2046         ret = damage * multiplier / 100;
2047     }
2048     else
2049         return 0;
2050 
2051     dmg_msg = "The silver sears " + victim->name(DESC_THE) + "!";
2052     return ret;
2053 }
2054 
2055 //  Used by monsters in "planning" which spell to cast. Fires off a "tracer"
2056 //  which tells the monster what it'll hit if it breathes/casts etc.
2057 //
2058 //  The output from this tracer function is written into the
2059 //  tracer_info variables (friend_info and foe_info).
2060 //
2061 //  Note that beam properties must be set, as the tracer will take them
2062 //  into account, as well as the monster's intelligence.
fire_tracer(const monster * mons,bolt & pbolt,bool explode_only,bool explosion_hole)2063 void fire_tracer(const monster* mons, bolt &pbolt, bool explode_only,
2064                  bool explosion_hole)
2065 {
2066     // Don't fiddle with any input parameters other than tracer stuff!
2067     pbolt.is_tracer     = true;
2068     pbolt.source        = mons->pos();
2069     pbolt.source_id     = mons->mid;
2070     pbolt.attitude      = mons_attitude(*mons);
2071 
2072     // Init tracer variables.
2073     pbolt.foe_info.reset();
2074     pbolt.friend_info.reset();
2075 
2076     // Clear misc
2077     pbolt.reflections   = 0;
2078     pbolt.bounces       = 0;
2079 
2080     // If there's a specifically requested foe_ratio, honour it.
2081     if (!pbolt.foe_ratio)
2082     {
2083         pbolt.foe_ratio     = 80;        // default - see mons_should_fire()
2084 
2085         if (mons_is_hepliaklqana_ancestor(mons->type))
2086             pbolt.foe_ratio = 100; // do not harm the player!
2087         // Foe ratio for summoning greater demons & undead -- they may be
2088         // summoned, but they're hostile and would love nothing better
2089         // than to nuke the player and his minions.
2090         else if (mons_att_wont_attack(pbolt.attitude)
2091             && !mons_att_wont_attack(mons->attitude))
2092         {
2093             pbolt.foe_ratio = 25;
2094         }
2095     }
2096 
2097     pbolt.in_explosion_phase = false;
2098 
2099     // Fire!
2100     if (explode_only)
2101         pbolt.explode(false, explosion_hole);
2102     else
2103         pbolt.fire();
2104 
2105     // Unset tracer flag (convenience).
2106     pbolt.is_tracer = false;
2107 }
2108 
create_feat_splash(coord_def center,int radius,int number,int duration)2109 vector<coord_def> create_feat_splash(coord_def center,
2110                                 int radius,
2111                                 int number,
2112                                 int duration)
2113 {
2114     vector<coord_def> splash_coords;
2115 
2116     for (distance_iterator di(center, true, false, radius); di && number > 0; ++di)
2117     {
2118         const dungeon_feature_type feat = env.grid(*di);
2119         if ((feat == DNGN_FLOOR || feat == DNGN_SHALLOW_WATER)
2120             && cell_see_cell(center, *di, LOS_NO_TRANS))
2121         {
2122             number--;
2123             int time = random_range(duration, duration * 3 / 2) - (di.radius() * 20);
2124             temp_change_terrain(*di, DNGN_SHALLOW_WATER, time,
2125                                 TERRAIN_CHANGE_FLOOD);
2126             splash_coords.push_back(*di);
2127         }
2128     }
2129 
2130     return splash_coords;
2131 }
2132 
bolt_parent_init(const bolt & parent,bolt & child)2133 void bolt_parent_init(const bolt &parent, bolt &child)
2134 {
2135     child.name           = parent.name;
2136     child.short_name     = parent.short_name;
2137     child.aux_source     = parent.aux_source;
2138     child.source_id      = parent.source_id;
2139     child.origin_spell   = parent.origin_spell;
2140     child.glyph          = parent.glyph;
2141     child.colour         = parent.colour;
2142 
2143     child.flavour        = parent.flavour;
2144 
2145     // We don't copy target since that is often overriden.
2146     child.thrower        = parent.thrower;
2147     child.source         = parent.source;
2148     child.source_name    = parent.source_name;
2149     child.attitude       = parent.attitude;
2150 
2151     child.pierce         = parent.pierce ;
2152     child.is_explosion   = parent.is_explosion;
2153     child.ex_size        = parent.ex_size;
2154     child.foe_ratio      = parent.foe_ratio;
2155 
2156     child.is_tracer      = parent.is_tracer;
2157     child.is_targeting   = parent.is_targeting;
2158 
2159     child.range          = parent.range;
2160     child.hit            = parent.hit;
2161     child.damage         = parent.damage;
2162     if (parent.ench_power != -1)
2163         child.ench_power = parent.ench_power;
2164 
2165     child.friend_info.dont_stop = parent.friend_info.dont_stop;
2166     child.foe_info.dont_stop    = parent.foe_info.dont_stop;
2167     child.dont_stop_player      = parent.dont_stop_player;
2168     child.dont_stop_trees       = parent.dont_stop_trees;
2169 
2170 #ifdef DEBUG_DIAGNOSTICS
2171     child.quiet_debug    = parent.quiet_debug;
2172 #endif
2173 }
2174 
_malign_offering_effect(actor * victim,const actor * agent,int damage)2175 static void _malign_offering_effect(actor* victim, const actor* agent, int damage)
2176 {
2177     if (!agent || damage < 1)
2178         return;
2179 
2180     // The victim may die.
2181     coord_def c = victim->pos();
2182 
2183     mprf("%s life force is offered up.", victim->name(DESC_ITS).c_str());
2184     damage = victim->hurt(agent, damage, BEAM_MALIGN_OFFERING, KILLED_BY_BEAM,
2185                           "", "by a malign offering");
2186 
2187     // Actors that had LOS to the victim (blocked by glass, clouds, etc),
2188     // even if they couldn't actually see each other because of blindness
2189     // or invisibility.
2190     for (actor_near_iterator ai(c, LOS_NO_TRANS); ai; ++ai)
2191     {
2192         if (mons_aligned(agent, *ai) && !(ai->holiness() & MH_NONLIVING)
2193             && *ai != victim)
2194         {
2195             if (ai->heal(max(1, damage * 2 / 3)) && you.can_see(**ai))
2196             {
2197                 mprf("%s %s healed.", ai->name(DESC_THE).c_str(),
2198                                       ai->conj_verb("are").c_str());
2199             }
2200         }
2201     }
2202 }
2203 
_vampiric_draining_effect(actor & victim,actor & agent,int damage)2204 static void _vampiric_draining_effect(actor& victim, actor& agent, int damage)
2205 {
2206     if (damage < 1 || !actor_is_susceptible_to_vampirism(victim))
2207         return;
2208 
2209     if (you.can_see(victim) || you.can_see(agent))
2210     {
2211         mprf("%s %s life force from %s%s",
2212              agent.name(DESC_THE).c_str(),
2213              agent.conj_verb("draw").c_str(),
2214              victim.name(DESC_THE).c_str(),
2215              attack_strength_punctuation(damage).c_str());
2216     }
2217 
2218     const int drain_amount = victim.hurt(&agent, damage,
2219                                          BEAM_VAMPIRIC_DRAINING,
2220                                          KILLED_BY_BEAM, "",
2221                                          "by vampiric draining");
2222 
2223     if (agent.is_monster())
2224     {
2225         const int hp_gain = drain_amount * 2 / 3;
2226         if (agent.heal(hp_gain))
2227             simple_monster_message(*agent.as_monster(), " is healed by the life force!");
2228     }
2229     else
2230     {
2231         if (you.duration[DUR_DEATHS_DOOR] || you.hp == you.hp_max)
2232             return;
2233 
2234         const int hp_gain = div_rand_round(drain_amount, 2);
2235         if (hp_gain)
2236         {
2237             mprf("You feel life coursing into your body%s",
2238                  attack_strength_punctuation(hp_gain).c_str());
2239             inc_hp(hp_gain);
2240         }
2241     }
2242 }
2243 
2244 /**
2245  * Turn a BEAM_UNRAVELLING beam into a BEAM_UNRAVELLED_MAGIC beam, and make
2246  * it explode appropriately.
2247  *
2248  * @param[in,out] beam      The beam being turned into an explosion.
2249  */
_unravelling_explode(bolt & beam)2250 static void _unravelling_explode(bolt &beam)
2251 {
2252     beam.colour       = ETC_MUTAGENIC;
2253     beam.flavour      = BEAM_UNRAVELLED_MAGIC;
2254     beam.ex_size      = 1;
2255     beam.is_explosion = true;
2256     // and it'll explode 'naturally' a little later.
2257 }
2258 
is_bouncy(dungeon_feature_type feat) const2259 bool bolt::is_bouncy(dungeon_feature_type feat) const
2260 {
2261     // Don't bounce off open sea.
2262     if (feat_is_endless(feat))
2263         return false;
2264 
2265     if (real_flavour == BEAM_CHAOS
2266         && feat_is_solid(feat))
2267     {
2268         return true;
2269     }
2270 
2271     if ((flavour == BEAM_CRYSTAL || real_flavour == BEAM_CRYSTAL)
2272         && feat_is_solid(feat)
2273         && !feat_is_tree(feat))
2274     {
2275         return true;
2276     }
2277 
2278     if (is_enchantment())
2279         return false;
2280 
2281     if (flavour == BEAM_ELECTRICITY
2282         && origin_spell != SPELL_BLINKBOLT
2283         && !feat_is_metal(feat)
2284         && !feat_is_tree(feat))
2285     {
2286         return true;
2287     }
2288 
2289     if ((flavour == BEAM_FIRE || flavour == BEAM_COLD)
2290         && feat == DNGN_CRYSTAL_WALL)
2291     {
2292         return true;
2293     }
2294 
2295     return false;
2296 }
2297 
get_cloud_type() const2298 cloud_type bolt::get_cloud_type() const
2299 {
2300     if (origin_spell == SPELL_NOXIOUS_CLOUD)
2301         return CLOUD_MEPHITIC;
2302 
2303     if (origin_spell == SPELL_POISONOUS_CLOUD)
2304         return CLOUD_POISON;
2305 
2306     if (origin_spell == SPELL_HOLY_BREATH)
2307         return CLOUD_HOLY;
2308 
2309     if (origin_spell == SPELL_FLAMING_CLOUD)
2310         return CLOUD_FIRE;
2311 
2312     if (origin_spell == SPELL_CHAOS_BREATH)
2313         return CLOUD_CHAOS;
2314 
2315     if (origin_spell == SPELL_MIASMA_BREATH)
2316         return CLOUD_MIASMA;
2317 
2318     if (origin_spell == SPELL_FREEZING_CLOUD)
2319         return CLOUD_COLD;
2320 
2321     if (origin_spell == SPELL_SPECTRAL_CLOUD)
2322         return CLOUD_SPECTRAL;
2323 
2324     return CLOUD_NONE;
2325 }
2326 
get_cloud_pow() const2327 int bolt::get_cloud_pow() const
2328 {
2329     if (origin_spell == SPELL_FREEZING_CLOUD
2330         || origin_spell == SPELL_POISONOUS_CLOUD)
2331     {
2332         return random_range(10, 15);
2333     }
2334 
2335     if (origin_spell == SPELL_SPECTRAL_CLOUD)
2336         return random_range(12, 20);
2337 
2338     return 0;
2339 }
2340 
get_cloud_size(bool min,bool max) const2341 int bolt::get_cloud_size(bool min, bool max) const
2342 {
2343     if (origin_spell == SPELL_MEPHITIC_CLOUD
2344         || origin_spell == SPELL_MIASMA_BREATH
2345         || origin_spell == SPELL_FREEZING_CLOUD)
2346     {
2347         return 10;
2348     }
2349 
2350     if (min)
2351         return 8;
2352     if (max)
2353         return 12;
2354 
2355     return 8 + random2(5);
2356 }
2357 
affect_endpoint()2358 void bolt::affect_endpoint()
2359 {
2360     // hack: we use hit_verb to communicate whether a ranged
2361     // attack hit. (And ranged attacks should only explode if
2362     // they hit the target, to avoid silliness with . targeting.)
2363     if (special_explosion && (is_tracer || !item || !hit_verb.empty()))
2364     {
2365         special_explosion->target = pos();
2366         special_explosion->refine_for_explosion();
2367         special_explosion->explode();
2368 
2369         // XXX: we're significantly overcounting here.
2370         foe_info      += special_explosion->foe_info;
2371         friend_info   += special_explosion->friend_info;
2372         beam_cancelled = beam_cancelled || special_explosion->beam_cancelled;
2373     }
2374 
2375     // Leave an object, if applicable.
2376     if (drop_item && item)
2377         drop_object();
2378 
2379     if (is_explosion)
2380     {
2381         target = pos();
2382         refine_for_explosion();
2383         explode();
2384         return;
2385     }
2386 
2387     const cloud_type cloud = get_cloud_type();
2388 
2389     if (is_tracer)
2390     {
2391         if (cloud == CLOUD_NONE)
2392             return;
2393 
2394         targeter_cloud tgt(agent(), range, get_cloud_size(true),
2395                             get_cloud_size(false, true));
2396         tgt.set_aim(pos());
2397         for (const auto &entry : tgt.seen)
2398         {
2399             if (entry.second != AFF_YES && entry.second != AFF_MAYBE)
2400                 continue;
2401 
2402             if (entry.first == you.pos())
2403                 tracer_affect_player();
2404             else if (monster* mon = monster_at(entry.first))
2405                 if (!ignores_monster(mon))
2406                     tracer_affect_monster(mon);
2407 
2408             if (agent()->is_player() && beam_cancelled)
2409                 return;
2410         }
2411 
2412         return;
2413     }
2414 
2415     if (!is_explosion && !noise_generated && loudness)
2416     {
2417         // Digging can target squares on the map boundary, though it
2418         // won't remove them of course.
2419         const coord_def noise_position = clamp_in_bounds(pos());
2420         noisy(loudness, noise_position, source_id);
2421         noise_generated = true;
2422     }
2423 
2424     if (cloud != CLOUD_NONE)
2425         big_cloud(cloud, agent(), pos(), get_cloud_pow(), get_cloud_size());
2426 
2427     // you like special cases, right?
2428     switch (origin_spell)
2429     {
2430     case SPELL_PRIMAL_WAVE:
2431     {
2432         if (you.see_cell(pos()))
2433         {
2434             mpr("The wave splashes down.");
2435             noisy(spell_effect_noise(SPELL_PRIMAL_WAVE), pos());
2436         }
2437         else
2438         {
2439             noisy(spell_effect_noise(SPELL_PRIMAL_WAVE),
2440                   pos(), "You hear a splash.");
2441         }
2442         const bool is_player = agent() && agent()->is_player();
2443         const int num = is_player ? div_rand_round(ench_power * 3, 20) + 3 + random2(7)
2444                                   : random_range(3, 12, 2);
2445         const int dur = div_rand_round(ench_power * 4, 3) + 66;
2446         vector<coord_def> splash_coords = create_feat_splash(pos(), is_player ? 2 : 1, num, dur);
2447         dprf(DIAG_BEAM, "Creating pool at %d,%d with %d tiles of water for %d auts.", pos().x, pos().y, num, dur);
2448         if (is_player)
2449         {
2450             for (const coord_def &coord : splash_coords)
2451             {
2452                 monster* mons = monster_at(coord);
2453                 if (mons && mons->res_water_drowning() <= 0)
2454                 {
2455                     simple_monster_message(*mons, " is engulfed in water.");
2456                     mons->add_ench(mon_enchant(ENCH_WATERLOGGED, 0, &you,
2457                                                    random_range(dur, dur * 3 / 2) - 20 * coord.distance_from(pos())));
2458                 }
2459             }
2460         }
2461         break;
2462     }
2463     case SPELL_HURL_SLUDGE:
2464     {
2465         // short duration to limit the ability of the player to use it
2466         // against enemies
2467         const int dur = random_range(20, 30);
2468         // TODO: deduplicate with noxious_bog_cell
2469         if (env.grid(pos()) == DNGN_DEEP_WATER || env.grid(pos()) == DNGN_LAVA)
2470             break;
2471 
2472         // intentionally don't use TERRAIN_CHANGE_BOG to avoid it vanishing
2473         // when out of LOS
2474         temp_change_terrain(pos(), DNGN_TOXIC_BOG, dur,
2475                             TERRAIN_CHANGE_FLOOD,
2476                             agent() ? agent()->as_monster() : nullptr);
2477         break;
2478     }
2479     case SPELL_BLINKBOLT:
2480     {
2481         actor *act = agent(true); // use orig actor even when reflected
2482         if (!act || !act->alive())
2483             return;
2484 
2485         for (vector<coord_def>::reverse_iterator citr = path_taken.rbegin();
2486              citr != path_taken.rend(); ++citr)
2487         {
2488             if (act->is_habitable(*citr) && (!act->is_player() || !monster_at(*citr))
2489                 && act->blink_to(*citr, false))
2490             {
2491                 return;
2492             }
2493         }
2494         return;
2495     }
2496 
2497     case SPELL_SEARING_BREATH:
2498         if (!path_taken.empty())
2499             place_cloud(CLOUD_FIRE, pos(), 5 + random2(5), agent());
2500 
2501     default:
2502         break;
2503     }
2504 }
2505 
stop_at_target() const2506 bool bolt::stop_at_target() const
2507 {
2508     // the pos check is to avoid a ray.cc assert for a ray that goes nowhere
2509     return is_explosion || is_big_cloud() ||
2510             (source_id == MID_PLAYER && origin_spell == SPELL_BLINKBOLT) ||
2511             (aimed_at_spot && (pos() == source || flavour != BEAM_DIGGING));
2512 }
2513 
drop_object(bool allow_mulch)2514 void bolt::drop_object(bool allow_mulch)
2515 {
2516     ASSERT(item != nullptr);
2517     ASSERT(item->defined());
2518 
2519     // Conditions: beam is missile and not tracer.
2520     if (is_tracer || !was_missile)
2521         return;
2522 
2523     // Summoned creatures' thrown items disappear.
2524     if (item->flags & ISFLAG_SUMMONED)
2525     {
2526         if (you.see_cell(pos()))
2527         {
2528             mprf("%s %s!",
2529                  item->name(DESC_THE).c_str(),
2530                  summoned_poof_msg(agent() ? agent()->as_monster() : nullptr,
2531                                    *item).c_str());
2532         }
2533         item_was_destroyed(*item);
2534         return;
2535     }
2536 
2537     // Hack alert: we use hit_verb to determine whether a ranged attack hit.
2538     const bool damned = item->props.exists(DAMNATION_BOLT_KEY) && !hit_verb.empty();
2539     if (!allow_mulch || (!damned && !thrown_object_destroyed(item)))
2540     {
2541         if (item->sub_type == MI_THROWING_NET)
2542         {
2543             monster* m = monster_at(pos());
2544             // Player or monster at position is caught in net.
2545             if (you.pos() == pos() && you.attribute[ATTR_HELD]
2546                 || m && m->caught())
2547             {
2548                 // If no trapping net found mark this one.
2549                 if (get_trapping_net(pos(), true) == NON_ITEM)
2550                     set_net_stationary(*item);
2551             }
2552         }
2553 
2554         copy_item_to_grid(*item, pos(), 1);
2555     }
2556     else
2557         item_was_destroyed(*item);
2558 }
2559 
2560 // Returns true if the beam hits the player, fuzzing the beam if necessary
2561 // for monsters without see invis firing tracers at the player.
found_player() const2562 bool bolt::found_player() const
2563 {
2564     const bool needs_fuzz = is_tracer
2565             && !can_see_invis && you.invisible()
2566             && !YOU_KILL(thrower)
2567             // No point in fuzzing to a position that could never be hit.
2568             && you.see_cell_no_trans(pos());
2569     const int dist = needs_fuzz? 2 : 0;
2570 
2571     return grid_distance(pos(), you.pos()) <= dist;
2572 }
2573 
affect_ground()2574 void bolt::affect_ground()
2575 {
2576     // Explosions only have an effect during their explosion phase.
2577     // Special cases can be handled here.
2578     if (is_explosion && !in_explosion_phase)
2579         return;
2580 
2581     if (is_tracer)
2582         return;
2583 
2584     affect_place_clouds();
2585 }
2586 
is_fiery() const2587 bool bolt::is_fiery() const
2588 {
2589     return flavour == BEAM_FIRE || flavour == BEAM_LAVA;
2590 }
2591 
2592 /// Can this bolt burn trees it hits?
can_burn_trees() const2593 bool bolt::can_burn_trees() const
2594 {
2595     // XXX: rethink this
2596     return origin_spell == SPELL_LIGHTNING_BOLT
2597            || origin_spell == SPELL_BOLT_OF_FIRE
2598            || origin_spell == SPELL_BOLT_OF_MAGMA
2599            || origin_spell == SPELL_FIREBALL
2600            || origin_spell == SPELL_FIRE_STORM
2601            || origin_spell == SPELL_IGNITION
2602            || origin_spell == SPELL_INNER_FLAME
2603            || origin_spell == SPELL_STARBURST;
2604 }
2605 
can_affect_wall(const coord_def & p,bool map_knowledge) const2606 bool bolt::can_affect_wall(const coord_def& p, bool map_knowledge) const
2607 {
2608     dungeon_feature_type wall = env.grid(p);
2609 
2610     // digging might affect unseen squares, as far as the player knows
2611     if (map_knowledge && flavour == BEAM_DIGGING &&
2612                                         !env.map_knowledge(pos()).seen())
2613     {
2614         return true;
2615     }
2616 
2617     // Temporary trees (from Summon Forest) can't be burned.
2618     if (feat_is_tree(wall) && is_temp_terrain(p))
2619         return false;
2620 
2621     // digging
2622     if (flavour == BEAM_DIGGING && feat_is_diggable(wall))
2623         return true;
2624 
2625     if (can_burn_trees())
2626         return feat_is_flammable(wall);
2627 
2628     // Lee's Rapid Deconstruction
2629     if (flavour == BEAM_FRAG)
2630         return true; // smite targeting, we don't care
2631 
2632     return false;
2633 }
2634 
affect_place_clouds()2635 void bolt::affect_place_clouds()
2636 {
2637     if (in_explosion_phase)
2638         affect_place_explosion_clouds();
2639 
2640     const coord_def p = pos();
2641 
2642     // Is there already a cloud here?
2643     if (cloud_struct* cloud = cloud_at(p))
2644     {
2645         // fire cancelling cold & vice versa
2646         if ((cloud->type == CLOUD_COLD
2647              && (flavour == BEAM_FIRE || flavour == BEAM_LAVA))
2648             || (cloud->type == CLOUD_FIRE && flavour == BEAM_COLD))
2649         {
2650             if (player_can_hear(p))
2651                 mprf(MSGCH_SOUND, "You hear a sizzling sound!");
2652 
2653             delete_cloud(p);
2654             extra_range_used += 5;
2655         }
2656         return;
2657     }
2658 
2659     // No clouds here, free to make new ones.
2660     const dungeon_feature_type feat = env.grid(p);
2661 
2662     if (origin_spell == SPELL_POISONOUS_CLOUD)
2663         place_cloud(CLOUD_POISON, p, random2(5) + 3, agent());
2664 
2665     if (origin_spell == SPELL_HOLY_BREATH)
2666         place_cloud(CLOUD_HOLY, p, random2(4) + 2, agent());
2667 
2668     if (origin_spell == SPELL_FLAMING_CLOUD)
2669         place_cloud(CLOUD_FIRE, p, random2(4) + 2, agent());
2670 
2671     // Fire/cold over water/lava
2672     if (feat == DNGN_LAVA && flavour == BEAM_COLD
2673         || feat_is_watery(feat) && is_fiery())
2674     {
2675         place_cloud(CLOUD_STEAM, p, 2 + random2(5), agent(), 11);
2676     }
2677 
2678     if (feat_is_watery(feat) && flavour == BEAM_COLD
2679         && damage.num * damage.size > 35)
2680     {
2681         place_cloud(CLOUD_COLD, p, damage.num * damage.size / 30 + 1, agent());
2682     }
2683 
2684     if (flavour == BEAM_MIASMA)
2685         place_cloud(CLOUD_MIASMA, p, random2(5) + 2, agent());
2686 
2687     //XXX: these use the name for a gameplay effect.
2688     if (name == "ball of steam")
2689         place_cloud(CLOUD_STEAM, p, random2(5) + 2, agent());
2690 
2691     if (name == "poison gas")
2692         place_cloud(CLOUD_POISON, p, random2(4) + 3, agent());
2693 
2694     if (name == "blast of choking fumes")
2695         place_cloud(CLOUD_MEPHITIC, p, random2(4) + 3, agent());
2696 
2697     if (name == "trail of fire")
2698         place_cloud(CLOUD_FIRE, p, random2(ench_power) + ench_power, agent());
2699 
2700     if (origin_spell == SPELL_PETRIFYING_CLOUD)
2701         place_cloud(CLOUD_PETRIFY, p, random2(4) + 4, agent());
2702 
2703     if (origin_spell == SPELL_SPECTRAL_CLOUD)
2704         place_cloud(CLOUD_SPECTRAL, p, random2(6) + 5, agent());
2705 
2706     if (origin_spell == SPELL_DEATH_RATTLE)
2707         place_cloud(CLOUD_MIASMA, p, random2(4) + 4, agent());
2708 }
2709 
affect_place_explosion_clouds()2710 void bolt::affect_place_explosion_clouds()
2711 {
2712     const coord_def p = pos();
2713 
2714     // First check: fire/cold over water/lava.
2715     if (env.grid(p) == DNGN_LAVA && flavour == BEAM_COLD
2716         || feat_is_watery(env.grid(p)) && is_fiery())
2717     {
2718         place_cloud(CLOUD_STEAM, p, 2 + random2(5), agent());
2719         return;
2720     }
2721 
2722     if (flavour == BEAM_MEPHITIC)
2723     {
2724         const coord_def center = (aimed_at_feet ? source : ray.pos());
2725         if (p == center || x_chance_in_y(125 + ench_power, 225))
2726         {
2727             place_cloud(CLOUD_MEPHITIC, p, roll_dice(2, 3 + ench_power / 20),
2728                         agent());
2729         }
2730     }
2731 
2732     if (origin_spell == SPELL_FIRE_STORM)
2733     {
2734         place_cloud(CLOUD_FIRE, p, 2 + random2avg(5,2), agent());
2735 
2736         // XXX: affect other open spaces?
2737         if (env.grid(p) == DNGN_FLOOR && !monster_at(p) && one_chance_in(4))
2738         {
2739             const god_type god =
2740                 (crawl_state.is_god_acting()) ? crawl_state.which_god_acting()
2741                                               : GOD_NO_GOD;
2742             const beh_type att =
2743                 (whose_kill() == KC_OTHER ? BEH_HOSTILE : BEH_FRIENDLY);
2744 
2745             actor* summ = agent();
2746             mgen_data mg(MONS_FIRE_VORTEX, att, p, MHITNOT, MG_NONE, god);
2747             mg.set_summoned(summ, 1, SPELL_FIRE_STORM);
2748 
2749             // Spell-summoned monsters need to have a live summoner.
2750             if (summ == nullptr || !summ->alive())
2751             {
2752                 if (!source_name.empty())
2753                     mg.non_actor_summoner = source_name;
2754                 else if (god != GOD_NO_GOD)
2755                     mg.non_actor_summoner = god_name(god);
2756             }
2757 
2758             mons_place(mg);
2759         }
2760     }
2761 }
2762 
2763 // A little helper function to handle the calling of ouch()...
internal_ouch(int dam)2764 void bolt::internal_ouch(int dam)
2765 {
2766     monster* monst = monster_by_mid(source_id);
2767 
2768     const char *what = aux_source.empty() ? name.c_str() : aux_source.c_str();
2769 
2770     if (YOU_KILL(thrower) && you.duration[DUR_QUAD_DAMAGE])
2771         dam *= 4;
2772 
2773     // The order of this is important.
2774     if (monst && mons_is_wrath_avatar(*monst))
2775     {
2776         ouch(dam, KILLED_BY_DIVINE_WRATH, MID_NOBODY,
2777              aux_source.empty() ? nullptr : aux_source.c_str(), true,
2778              source_name.empty() ? nullptr : source_name.c_str());
2779     }
2780     else if (is_death_effect)
2781     {
2782         ouch(dam, KILLED_BY_DEATH_EXPLOSION, source_id,
2783              aux_source.c_str(), true,
2784              source_name.empty() ? nullptr : source_name.c_str());
2785     }
2786     else if (flavour == BEAM_MINDBURST || flavour == BEAM_DEVASTATION)
2787     {
2788         ouch(dam, KILLED_BY_DISINT, source_id, what, true,
2789              source_name.empty() ? nullptr : source_name.c_str());
2790     }
2791     else if (YOU_KILL(thrower) && aux_source.empty())
2792     {
2793         if (reflections > 0)
2794             ouch(dam, KILLED_BY_REFLECTION, reflector, name.c_str());
2795         else if (bounces > 0)
2796             ouch(dam, KILLED_BY_BOUNCE, MID_PLAYER, name.c_str());
2797         else
2798         {
2799             if (aimed_at_feet && effect_known)
2800                 ouch(dam, KILLED_BY_SELF_AIMED, MID_PLAYER, name.c_str());
2801             else
2802                 ouch(dam, KILLED_BY_TARGETING, MID_PLAYER, name.c_str());
2803         }
2804     }
2805     else if (MON_KILL(thrower))
2806         ouch(dam, KILLED_BY_BEAM, source_id,
2807              aux_source.c_str(), true,
2808              source_name.empty() ? nullptr : source_name.c_str());
2809     else // KILL_MISC || (YOU_KILL && aux_source)
2810         ouch(dam, KILLED_BY_WILD_MAGIC, source_id, aux_source.c_str());
2811 }
2812 
2813 // [ds] Apply a fuzz if the monster lacks see invisible and is trying to target
2814 // an invisible player. This makes invisibility slightly more powerful.
fuzz_invis_tracer()2815 bool bolt::fuzz_invis_tracer()
2816 {
2817     // Did the monster have a rough idea of where you are?
2818     int dist = grid_distance(target, you.pos());
2819 
2820     // No, ditch this.
2821     if (dist > 2)
2822         return false;
2823 
2824     // Apply fuzz now.
2825     coord_def fuzz;
2826     fuzz.x = random_range(-2, 2);
2827     fuzz.y = random_range(-2, 2);
2828     coord_def newtarget = target + fuzz;
2829 
2830     if (in_bounds(newtarget))
2831         target = newtarget;
2832 
2833     // Fire away!
2834     return true;
2835 }
2836 
2837 // A first step towards to-hit sanity for beams. We're still being
2838 // very kind to the player, but it should be fairer to monsters than
2839 // 4.0.
_test_beam_hit(int attack,int defence,bool pierce,bool repel,defer_rand & r)2840 static bool _test_beam_hit(int attack, int defence, bool pierce,
2841                            bool repel, defer_rand &r)
2842 {
2843     if (attack == AUTOMATIC_HIT)
2844         return true;
2845 
2846     if (pierce)
2847     {
2848         if (repel && attack >= 2) // don't increase acc of 0
2849             attack = r[0].random_range((attack + 1) / 2 + 1, attack);
2850     }
2851     else if (repel)
2852         attack = r[0].random2(attack);
2853 
2854     dprf(DIAG_BEAM, "Beam attack: %d, defence: %d", attack, defence);
2855 
2856     attack = r[1].random2(attack);
2857     defence = r[2].random2avg(defence, 2);
2858 
2859     dprf(DIAG_BEAM, "Beam new attack: %d, defence: %d", attack, defence);
2860 
2861     return attack >= defence;
2862 }
2863 
is_harmless(const monster * mon) const2864 bool bolt::is_harmless(const monster* mon) const
2865 {
2866     // For enchantments, this is already handled in nasty_to().
2867     if (is_enchantment())
2868         return !nasty_to(mon);
2869 
2870     // The others are handled here.
2871     switch (flavour)
2872     {
2873     case BEAM_VISUAL:
2874     case BEAM_DIGGING:
2875         return true;
2876 
2877     case BEAM_HOLY:
2878         return mon->res_holy_energy() >= 3;
2879 
2880     case BEAM_STEAM:
2881         return mon->res_steam() >= 3;
2882 
2883     case BEAM_FIRE:
2884         return mon->res_fire() >= 3;
2885 
2886     case BEAM_COLD:
2887         return mon->res_cold() >= 3;
2888 
2889     case BEAM_MIASMA:
2890         return mon->res_miasma();
2891 
2892     case BEAM_NEG:
2893         return mon->res_negative_energy() == 3;
2894 
2895     case BEAM_ELECTRICITY:
2896         return mon->res_elec() >= 3;
2897 
2898     case BEAM_POISON:
2899         return mon->res_poison() >= 3;
2900 
2901     case BEAM_ACID:
2902         return mon->res_acid() >= 3;
2903 
2904     case BEAM_MEPHITIC:
2905         return mon->res_poison() > 0;
2906 
2907     default:
2908         return false;
2909     }
2910 }
2911 
2912 // N.b. only called for player-originated beams; if that is changed,
2913 // be sure to adjust various assumptions based on the spells/abilities
2914 // available to the player.
harmless_to_player() const2915 bool bolt::harmless_to_player() const
2916 {
2917     dprf(DIAG_BEAM, "beam flavour: %d", flavour);
2918 
2919     if (you.cloud_immune() && is_big_cloud())
2920         return true;
2921 
2922     switch (flavour)
2923     {
2924     case BEAM_VISUAL:
2925     case BEAM_DIGGING:
2926         return true;
2927 
2928     // Positive enchantments.
2929     case BEAM_HASTE:
2930     case BEAM_HEALING:
2931     case BEAM_MIGHT:
2932     case BEAM_AGILITY:
2933     case BEAM_INVISIBILITY:
2934     case BEAM_RESISTANCE:
2935         return true;
2936 
2937     case BEAM_HOLY:
2938         return you.res_holy_energy() >= 3;
2939 
2940     case BEAM_MIASMA:
2941         return you.res_miasma();
2942 
2943     case BEAM_NEG:
2944         return player_prot_life(false) >= 3;
2945 
2946     case BEAM_POISON:
2947         return player_res_poison(false) >= 3
2948                || is_big_cloud() && player_res_poison(false) > 0;
2949 
2950     case BEAM_MEPHITIC:
2951         // With clarity, meph still does a tiny amount of damage (1d3 - 1).
2952         // Normally we'd just ignore it, but we shouldn't let a player
2953         // kill themselves without a warning.
2954         return player_res_poison(false) > 0
2955                || you.clarity(false) && you.hp > 2;
2956 
2957     case BEAM_PETRIFY:
2958         return you.res_petrify() || you.petrified();
2959 
2960 #if TAG_MAJOR_VERSION == 34
2961     case BEAM_COLD:
2962         return is_big_cloud() && you.has_mutation(MUT_FREEZING_CLOUD_IMMUNITY);
2963 #endif
2964 
2965     case BEAM_VIRULENCE:
2966         return player_res_poison(false) >= 3;
2967 
2968     default:
2969         return false;
2970     }
2971 }
2972 
is_reflectable(const actor & whom) const2973 bool bolt::is_reflectable(const actor &whom) const
2974 {
2975     if (range_used() > range)
2976         return false;
2977 
2978     const item_def *it = whom.shield();
2979     return (it && is_shield(*it) && shield_reflects(*it)) || whom.reflection();
2980 }
2981 
is_big_cloud() const2982 bool bolt::is_big_cloud() const
2983 {
2984     return testbits(get_spell_flags(origin_spell), spflag::cloud);
2985 }
2986 
leg_source() const2987 coord_def bolt::leg_source() const
2988 {
2989     if (bounces > 0 && map_bounds(bounce_pos))
2990         return bounce_pos;
2991     else
2992         return source;
2993 }
2994 
2995 // Reflect a beam back the direction it came. This is used
2996 // by shields of reflection.
reflect()2997 void bolt::reflect()
2998 {
2999     reflections++;
3000 
3001     target = leg_source();
3002     source = pos();
3003 
3004     // Reset bounce_pos, so that if we somehow reflect again before reaching
3005     // the wall that we won't keep heading towards the wall.
3006     bounce_pos.reset();
3007 
3008     if (pos() == you.pos())
3009     {
3010         reflector = MID_PLAYER;
3011         count_action(CACT_BLOCK, -1, BLOCK_REFLECT);
3012     }
3013     else if (monster* m = monster_at(pos()))
3014         reflector = m->mid;
3015     else
3016     {
3017         reflector = MID_NOBODY;
3018 #ifdef DEBUG
3019         dprf(DIAG_BEAM, "Bolt reflected by neither player nor "
3020              "monster (bolt = %s, item = %s)", name.c_str(),
3021              item ? item->name(DESC_PLAIN).c_str() : "none");
3022 #endif
3023     }
3024 
3025     flavour = real_flavour;
3026     choose_ray();
3027 }
3028 
tracer_affect_player()3029 void bolt::tracer_affect_player()
3030 {
3031     if (flavour == BEAM_UNRAVELLING && player_is_debuffable())
3032         is_explosion = true;
3033 
3034     // Check whether thrower can see player, unless thrower == player.
3035     if (YOU_KILL(thrower))
3036     {
3037         if (!dont_stop_player && !harmless_to_player())
3038         {
3039             string prompt = make_stringf("That %s is likely to hit you. Continue anyway?",
3040                                          item ? name.c_str() : "beam");
3041 
3042             if (yesno(prompt.c_str(), false, 'n'))
3043             {
3044                 friend_info.count++;
3045                 friend_info.power += you.experience_level;
3046                 // Don't ask about aiming at ourself twice.
3047                 dont_stop_player = true;
3048             }
3049             else
3050             {
3051                 canned_msg(MSG_OK);
3052                 beam_cancelled = true;
3053                 finish_beam();
3054             }
3055         }
3056     }
3057     else if (can_see_invis || !you.invisible() || fuzz_invis_tracer())
3058     {
3059         if (mons_att_wont_attack(attitude))
3060         {
3061             friend_info.count++;
3062             friend_info.power += you.experience_level;
3063         }
3064         else
3065         {
3066             foe_info.count++;
3067             foe_info.power += you.experience_level;
3068         }
3069     }
3070 
3071     extra_range_used += range_used_on_hit();
3072 }
3073 
apply_lighting(int base_hit,const actor & targ) const3074 int bolt::apply_lighting(int base_hit, const actor &targ) const
3075 {
3076     if (targ.invisible() && !can_see_invis)
3077         base_hit /= 2;
3078 
3079     // We multiply these lighting effects by 2, since they're applied
3080     // before rolling to-hit (and hence will get halved later)
3081 
3082     if (targ.backlit(false))
3083         base_hit += BACKLIGHT_TO_HIT_BONUS * 2;
3084 
3085     if (!nightvision && targ.umbra())
3086         base_hit -= UMBRA_TO_HIT_MALUS * 2;
3087 
3088     return base_hit;
3089 }
3090 
3091 /* Determine whether the beam hit or missed the player, and tell them if it
3092  * missed.
3093  *
3094  * @return  true if the beam missed, false if the beam hit the player.
3095  */
misses_player()3096 bool bolt::misses_player()
3097 {
3098     if (flavour == BEAM_VISUAL)
3099         return true;
3100 
3101     if ((is_explosion || auto_hit || aimed_at_feet)
3102         && origin_spell != SPELL_CALL_DOWN_LIGHTNING)
3103     {
3104         return false;
3105     }
3106 
3107     const int dodge = you.evasion();
3108     int real_tohit  = hit;
3109 
3110     if (real_tohit != AUTOMATIC_HIT)
3111         real_tohit = apply_lighting(real_tohit, you);
3112 
3113     const int SH = player_shield_class();
3114     if ((player_omnireflects() && is_omnireflectable()
3115          || is_blockable())
3116         && you.shielded()
3117         && !aimed_at_feet
3118         && SH > 0)
3119     {
3120         bool blocked = false;
3121         if (hit == AUTOMATIC_HIT)
3122         {
3123             // 50% chance of blocking ench-type effects at 10 displayed sh
3124             blocked = x_chance_in_y(SH, omnireflect_chance_denom(SH));
3125 
3126             dprf(DIAG_BEAM, "%smnireflected: %d/%d chance",
3127                  blocked ? "O" : "Not o", SH, omnireflect_chance_denom(SH));
3128         }
3129         else
3130         {
3131             // We use the original to-hit here.
3132             // (so that effects increasing dodge chance don't increase
3133             // block...?)
3134             const int testhit = random2(hit * 130 / 100
3135                                         + you.shield_block_penalty());
3136 
3137             const int block = you.shield_bonus();
3138 
3139             dprf(DIAG_BEAM, "Beamshield: hit: %d, block %d", testhit, block);
3140             blocked = testhit < block;
3141         }
3142 
3143         if (blocked)
3144         {
3145             const string refl_name = name.empty() &&
3146                                      origin_spell != SPELL_NO_SPELL ?
3147                                         spell_title(origin_spell) :
3148                                         name;
3149 
3150             const item_def *shield = you.shield();
3151             if (is_reflectable(you))
3152             {
3153                 if (shield && is_shield(*shield) && shield_reflects(*shield))
3154                 {
3155                     mprf("Your %s reflects the %s!",
3156                             shield->name(DESC_PLAIN).c_str(),
3157                             refl_name.c_str());
3158                 }
3159                 else
3160                 {
3161                     mprf("The %s reflects off an invisible shield around you!",
3162                             refl_name.c_str());
3163                 }
3164                 reflect();
3165             }
3166             else
3167             {
3168                 mprf("You block the %s.", name.c_str());
3169                 finish_beam();
3170             }
3171             you.shield_block_succeeded();
3172             return true;
3173         }
3174 
3175         // Some training just for the "attempt".
3176         practise_shield_block(false);
3177     }
3178 
3179     if (is_enchantment())
3180         return false;
3181 
3182     if (!aimed_at_feet)
3183         practise_being_shot_at();
3184 
3185     defer_rand r;
3186 
3187     bool repel = you.missile_repulsion();
3188 
3189     if (!_test_beam_hit(real_tohit, dodge, pierce, 0, r))
3190     {
3191         mprf("The %s misses you.", name.c_str());
3192         count_action(CACT_DODGE, DODGE_EVASION);
3193     }
3194     else if (repel && !_test_beam_hit(real_tohit, dodge, pierce, repel, r))
3195     {
3196         mprf("The %s is repelled.", name.c_str());
3197         count_action(CACT_DODGE, DODGE_REPEL);
3198     }
3199     else
3200         return false;
3201 
3202     return true;
3203 }
3204 
affect_player_enchantment(bool resistible)3205 void bolt::affect_player_enchantment(bool resistible)
3206 {
3207     if (resistible
3208         && has_saving_throw()
3209         && you.check_willpower(ench_power) > 0)
3210     {
3211         // You resisted it.
3212 
3213         // Give a message.
3214         bool need_msg = true;
3215         if (thrower != KILL_YOU_MISSILE)
3216         {
3217             const monster* mon = monster_by_mid(source_id);
3218             if (mon && !mon->observable())
3219             {
3220                 mprf("Something tries to affect you, but you %s.",
3221                      you.willpower() == WILL_INVULN ? "are unaffected"
3222                                                    : "resist");
3223                 need_msg = false;
3224             }
3225         }
3226         if (need_msg)
3227         {
3228             if (you.willpower() == WILL_INVULN)
3229                 canned_msg(MSG_YOU_UNAFFECTED);
3230             else
3231             {
3232                 // the message reflects the level of difficulty resisting.
3233                 const int margin = you.willpower() - ench_power;
3234                 mprf("You%s", you.resist_margin_phrase(margin).c_str());
3235             }
3236         }
3237 
3238         // punish the monster if we're a willful demon
3239         if (you.get_mutation_level(MUT_DEMONIC_WILL))
3240         {
3241             monster* mon = monster_by_mid(source_id);
3242             if (mon && mon->alive())
3243             {
3244                 const int dam = 3 + random2(1 + div_rand_round(ench_power, 10));
3245                 mprf("Your will lashes out at %s%s",
3246                      mon->name(DESC_THE).c_str(),
3247                      attack_strength_punctuation(dam).c_str());
3248                 mon->hurt(&you, dam);
3249             }
3250         }
3251 
3252         // You *could* have gotten a free teleportation in the Abyss,
3253         // but no, you resisted.
3254         if (flavour == BEAM_TELEPORT && player_in_branch(BRANCH_ABYSS))
3255             xom_is_stimulated(200);
3256 
3257         extra_range_used += range_used_on_hit();
3258         return;
3259     }
3260 
3261     // Never affects the player.
3262     if (flavour == BEAM_INFESTATION || flavour == BEAM_VILE_CLUTCH)
3263         return;
3264 
3265     // You didn't resist it.
3266     if (animate)
3267         _ench_animation(effect_known ? real_flavour : BEAM_MAGIC);
3268 
3269     bool nasty = true, nice = false;
3270 
3271     const bool blame_player = god_cares() && YOU_KILL(thrower);
3272 
3273     switch (flavour)
3274     {
3275     case BEAM_HIBERNATION:
3276     case BEAM_SLEEP:
3277         you.put_to_sleep(nullptr, ench_power, flavour == BEAM_HIBERNATION);
3278         break;
3279 
3280     case BEAM_CORONA:
3281         you.backlight();
3282         obvious_effect = true;
3283         break;
3284 
3285     case BEAM_POLYMORPH:
3286         obvious_effect = you.polymorph(ench_power);
3287         break;
3288 
3289     case BEAM_MALMUTATE:
3290     case BEAM_UNRAVELLED_MAGIC:
3291         mpr("Strange energies course through your body.");
3292         you.malmutate(aux_source.empty() ? get_source_name() :
3293                       (get_source_name() + "/" + aux_source));
3294         obvious_effect = true;
3295         break;
3296 
3297     case BEAM_SLOW:
3298         slow_player(10 + random2(ench_power));
3299         obvious_effect = true;
3300         break;
3301 
3302     case BEAM_HASTE:
3303         haste_player(40 + random2(ench_power));
3304         did_god_conduct(DID_HASTY, 10, blame_player);
3305         obvious_effect = true;
3306         nasty = false;
3307         nice  = true;
3308         break;
3309 
3310     case BEAM_HEALING:
3311         potionlike_effect(POT_HEAL_WOUNDS, ench_power, true);
3312         obvious_effect = true;
3313         nasty = false;
3314         nice  = true;
3315         break;
3316 
3317     case BEAM_MIGHT:
3318         potionlike_effect(POT_MIGHT, ench_power);
3319         obvious_effect = true;
3320         nasty = false;
3321         nice  = true;
3322         break;
3323 
3324     case BEAM_INVISIBILITY:
3325         potionlike_effect(POT_INVISIBILITY, ench_power);
3326         contaminate_player(1000 + random2(1000), blame_player);
3327         obvious_effect = true;
3328         nasty = false;
3329         nice  = true;
3330         break;
3331 
3332     case BEAM_PARALYSIS:
3333         you.paralyse(agent(), 2 + random2(6));
3334         obvious_effect = true;
3335         break;
3336 
3337     case BEAM_PETRIFY:
3338         you.petrify(agent());
3339         obvious_effect = true;
3340         break;
3341 
3342     case BEAM_CONFUSION:
3343         confuse_player(5 + random2(3));
3344         obvious_effect = true;
3345         break;
3346 
3347     case BEAM_TELEPORT:
3348         you_teleport();
3349 
3350         // An enemy helping you escape while in the Abyss, or an
3351         // enemy stabilizing a teleport that was about to happen.
3352         if (!mons_att_wont_attack(attitude) && player_in_branch(BRANCH_ABYSS))
3353             xom_is_stimulated(200);
3354 
3355         obvious_effect = true;
3356         break;
3357 
3358     case BEAM_BLINK:
3359         uncontrolled_blink();
3360         obvious_effect = true;
3361         break;
3362 
3363     case BEAM_BLINK_CLOSE:
3364         blink_other_close(&you, source);
3365         obvious_effect = true;
3366         break;
3367 
3368     case BEAM_BECKONING:
3369         obvious_effect = beckon(you, *this);
3370         break;
3371 
3372     case BEAM_CHARM:
3373         mprf(MSGCH_WARN, "Your will is overpowered!");
3374         confuse_player(5 + random2(3));
3375         obvious_effect = true;
3376         break;     // charming - confusion?
3377 
3378     case BEAM_BANISH:
3379         if (YOU_KILL(thrower))
3380         {
3381             mpr("This spell isn't strong enough to banish yourself.");
3382             break;
3383         }
3384         you.banish(agent(), get_source_name(),
3385                    agent()->get_experience_level());
3386         obvious_effect = true;
3387         break;
3388 
3389     case BEAM_PAIN:
3390     {
3391         if (aux_source.empty())
3392             aux_source = "by nerve-wracking pain";
3393 
3394         const int dam = resist_adjust_damage(&you, flavour, damage.roll());
3395         if (dam)
3396         {
3397             mpr("Pain shoots through your body!");
3398             internal_ouch(dam);
3399             obvious_effect = true;
3400         }
3401         else
3402             canned_msg(MSG_YOU_UNAFFECTED);
3403         break;
3404     }
3405 
3406     case BEAM_AGONY:
3407         torment_player(agent(), TORMENT_AGONY);
3408         obvious_effect = true;
3409         break;
3410 
3411     case BEAM_DISPEL_UNDEAD:
3412         if (you.undead_state() == US_ALIVE)
3413         {
3414             canned_msg(MSG_YOU_UNAFFECTED);
3415             break;
3416         }
3417 
3418         mpr("You convulse!");
3419 
3420         if (aux_source.empty())
3421             aux_source = "by dispel undead";
3422 
3423         internal_ouch(damage.roll());
3424         obvious_effect = true;
3425         break;
3426 
3427     case BEAM_MINDBURST:
3428         mpr("Your mind is blasted!");
3429 
3430         if (aux_source.empty())
3431             aux_source = "mindburst bolt";
3432 
3433         {
3434             int amt = damage.roll();
3435             internal_ouch(amt);
3436 
3437             if (you.can_bleed())
3438                 blood_spray(you.pos(), MONS_PLAYER, amt / 5);
3439         }
3440 
3441         obvious_effect = true;
3442         break;
3443 
3444     case BEAM_PORKALATOR:
3445         if (!transform(ench_power, transformation::pig, true))
3446         {
3447             mpr("You feel a momentary urge to oink.");
3448             break;
3449         }
3450 
3451         you.transform_uncancellable = true;
3452         obvious_effect = true;
3453         break;
3454 
3455     case BEAM_BERSERK:
3456         you.go_berserk(blame_player);
3457         obvious_effect = true;
3458         break;
3459 
3460     case BEAM_SENTINEL_MARK:
3461         you.sentinel_mark();
3462         obvious_effect = true;
3463         break;
3464 
3465     case BEAM_DIMENSION_ANCHOR:
3466         mprf("You feel %sfirmly anchored in space.",
3467              you.duration[DUR_DIMENSION_ANCHOR] ? "more " : "");
3468         you.increase_duration(DUR_DIMENSION_ANCHOR, 12 + random2(15), 50);
3469         if (you.duration[DUR_TELEPORT])
3470         {
3471             you.duration[DUR_TELEPORT] = 0;
3472             mpr("Your teleport is interrupted.");
3473         }
3474         you.redraw_evasion = true;
3475         obvious_effect = true;
3476         break;
3477 
3478     case BEAM_VULNERABILITY:
3479         if (!you.duration[DUR_LOWERED_WL])
3480             mpr("Your willpower is stripped away!");
3481         you.increase_duration(DUR_LOWERED_WL, 12 + random2(18), 50);
3482         obvious_effect = true;
3483         break;
3484 
3485     case BEAM_MALIGN_OFFERING:
3486     {
3487         const int dam = resist_adjust_damage(&you, flavour, damage.roll());
3488         if (dam)
3489         {
3490             _malign_offering_effect(&you, agent(), dam);
3491             obvious_effect = true;
3492         }
3493         else
3494             canned_msg(MSG_YOU_UNAFFECTED);
3495         break;
3496     }
3497 
3498     case BEAM_VAMPIRIC_DRAINING:
3499     {
3500         const int dam = resist_adjust_damage(&you, flavour, damage.roll());
3501         if (dam && actor_is_susceptible_to_vampirism(you))
3502         {
3503             _vampiric_draining_effect(you, *agent(), dam);
3504             obvious_effect = true;
3505         }
3506         else
3507             canned_msg(MSG_YOU_UNAFFECTED);
3508         break;
3509     }
3510 
3511     case BEAM_VIRULENCE:
3512         // Those completely immune cannot be made more susceptible this way
3513         if (you.res_poison(false) >= 3)
3514         {
3515             canned_msg(MSG_YOU_UNAFFECTED);
3516             break;
3517         }
3518 
3519         mpr("You feel yourself grow more vulnerable to poison.");
3520         you.increase_duration(DUR_POISON_VULN, 12 + random2(18), 50);
3521         obvious_effect = true;
3522         break;
3523 
3524     case BEAM_AGILITY:
3525         you.be_agile(ench_power);
3526         obvious_effect = true;
3527         nasty = false;
3528         nice  = true;
3529         break;
3530 
3531     case BEAM_SAP_MAGIC:
3532         if (!SAP_MAGIC_CHANCE())
3533         {
3534             canned_msg(MSG_NOTHING_HAPPENS);
3535             break;
3536         }
3537         mprf(MSGCH_WARN, "Your magic feels %stainted.",
3538              you.duration[DUR_SAP_MAGIC] ? "more " : "");
3539         you.increase_duration(DUR_SAP_MAGIC, random_range(20, 30), 50);
3540         break;
3541 
3542     case BEAM_DRAIN_MAGIC:
3543     {
3544         int amount = min(you.magic_points, random2avg(ench_power / 8, 3));
3545         if (!amount)
3546             break;
3547         mprf(MSGCH_WARN, "You feel your power leaking away.");
3548         drain_mp(amount);
3549         if (agent() && agent()->type == MONS_GHOST_MOTH)
3550             agent()->heal(amount);
3551         obvious_effect = true;
3552         break;
3553     }
3554 
3555     case BEAM_TUKIMAS_DANCE:
3556         cast_tukimas_dance(ench_power, &you);
3557         obvious_effect = true;
3558         break;
3559 
3560     case BEAM_RESISTANCE:
3561         potionlike_effect(POT_RESISTANCE, min(ench_power, 200));
3562         obvious_effect = true;
3563         nasty = false;
3564         nice  = true;
3565         break;
3566 
3567     case BEAM_UNRAVELLING:
3568         if (!player_is_debuffable())
3569             break;
3570 
3571         debuff_player();
3572         _unravelling_explode(*this);
3573         obvious_effect = true;
3574         break;
3575 
3576     default:
3577         // _All_ enchantments should be enumerated here!
3578         mpr("Software bugs nibble your toes!");
3579         break;
3580     }
3581 
3582     if (nasty)
3583     {
3584         if (mons_att_wont_attack(attitude))
3585         {
3586             friend_info.hurt++;
3587             if (source_id == MID_PLAYER)
3588             {
3589                 // Beam from player rebounded and hit player.
3590                 if (!aimed_at_feet)
3591                     xom_is_stimulated(200);
3592             }
3593             else
3594             {
3595                 // Beam from an ally or neutral.
3596                 xom_is_stimulated(100);
3597             }
3598         }
3599         else
3600             foe_info.hurt++;
3601     }
3602     else if (nice)
3603     {
3604         if (mons_att_wont_attack(attitude))
3605             friend_info.helped++;
3606         else
3607         {
3608             foe_info.helped++;
3609             xom_is_stimulated(100);
3610         }
3611     }
3612 
3613     // Regardless of effect, we need to know if this is a stopper
3614     // or not - it seems all of the above are.
3615     extra_range_used += range_used_on_hit();
3616 }
3617 
affect_actor(actor * act)3618 void bolt::affect_actor(actor *act)
3619 {
3620     if (act->is_monster())
3621         affect_monster(act->as_monster());
3622     else
3623         affect_player();
3624 }
3625 
3626 struct pie_effect
3627 {
3628     const char* desc;
3629     function<bool(const actor& def)> valid;
3630     function<void (actor& def, const bolt &beam)> effect;
3631     int weight;
3632 };
3633 
3634 static const vector<pie_effect> pie_effects = {
3635     {
3636         "plum",
__anonb1d1125b0102() 3637         [](const actor &defender) {
3638             return defender.is_player();
3639         },
__anonb1d1125b0202() 3640         [](actor &/*defender*/, const bolt &/*beam*/) {
3641             if (you.duration[DUR_VERTIGO])
3642                 mpr("You feel your light-headedness will last longer.");
3643             else
3644                 mpr("You feel light-headed.");
3645 
3646             you.increase_duration(DUR_VERTIGO, 10 + random2(11), 50);
3647         },
3648         10
3649     },
3650     {
3651         "lemon",
__anonb1d1125b0302() 3652         [](const actor &defender) {
3653             return defender.is_player() && (you.can_drink()
3654                                             || you.duration[DUR_NO_POTIONS]); // allow stacking
3655         },
__anonb1d1125b0402() 3656         [](actor &/*defender*/, const bolt &/*beam*/) {
3657             if (you.duration[DUR_NO_POTIONS])
3658                 mpr("You feel your inability to drink will last longer.");
3659             else
3660                 mpr("You feel unable to drink.");
3661 
3662             you.increase_duration(DUR_NO_POTIONS, 10 + random2(11), 50);
3663         },
3664         10
3665     },
3666     {
3667         "blueberry",
3668         nullptr,
__anonb1d1125b0502() 3669         [](actor &defender, const bolt &beam) {
3670             if (defender.is_monster())
3671             {
3672                 monster *mons = defender.as_monster();
3673                 simple_monster_message(*mons, " loses the ability to speak.");
3674                 mons->add_ench(mon_enchant(ENCH_MUTE, 0, beam.agent(),
3675                             4 + random2(7) * BASELINE_DELAY));
3676             }
3677             else
3678             {
3679                 if (you.duration[DUR_SILENCE])
3680                     mpr("You feel your silence will last longer.");
3681                 else
3682                     mpr("An unnatural silence engulfs you.");
3683 
3684                 you.increase_duration(DUR_SILENCE, 4 + random2(7), 10);
3685                 invalidate_agrid(true);
3686 
3687                 if (you.beheld())
3688                     you.update_beholders();
3689             }
3690         },
3691         10
3692     },
3693     {
3694         "raspberry",
__anonb1d1125b0602() 3695         [](const actor &defender) {
3696             return defender.is_player();
3697         },
__anonb1d1125b0702() 3698         [](actor &/*defender*/, const bolt &/*beam*/) {
3699             for (int i = 0; i < NUM_STATS; ++i)
3700                 lose_stat(static_cast<stat_type>(i), 1 + random2(3));
3701         },
3702         10
3703     },
3704     {
3705         "cherry",
__anonb1d1125b0802() 3706         [](const actor &defender) {
3707             return defender.is_player() || defender.res_fire() < 3;
3708         },
__anonb1d1125b0902() 3709         [](actor &defender, const bolt &beam) {
3710             if (defender.is_monster())
3711             {
3712                 monster *mons = defender.as_monster();
3713                 simple_monster_message(*mons,
3714                         " looks more vulnerable to fire.");
3715                 mons->add_ench(mon_enchant(ENCH_FIRE_VULN, 0,
3716                              beam.agent(),
3717                              15 + random2(11) * BASELINE_DELAY));
3718             }
3719             else
3720             {
3721                 if (you.duration[DUR_FIRE_VULN])
3722                 {
3723                     mpr("You feel your vulnerability to fire will last "
3724                         "longer.");
3725                 }
3726                 else
3727                     mpr("Cherry-coloured flames burn away your fire "
3728                         "resistance!");
3729 
3730                 you.increase_duration(DUR_FIRE_VULN, 15 + random2(11), 50);
3731             }
3732         },
3733         6
3734     },
3735     {
3736         "moon pie",
__anonb1d1125b0a02() 3737         [](const actor &defender) {
3738             return defender.can_polymorph();
3739         },
__anonb1d1125b0b02() 3740         [](actor &defender, const bolt &/*beam*/) {
3741             defender.polymorph(100, false);
3742         },
3743         4
3744     },
3745 };
3746 
_random_pie_effect(const actor & defender)3747 static pie_effect _random_pie_effect(const actor &defender)
3748 {
3749     vector<pair<const pie_effect&, int>> weights;
3750     for (const pie_effect &effect : pie_effects)
3751         if (!effect.valid || effect.valid(defender))
3752             weights.push_back({effect, effect.weight});
3753 
3754     ASSERT(!weights.empty());
3755 
3756     return *random_choose_weighted(weights);
3757 }
3758 
affect_player()3759 void bolt::affect_player()
3760 {
3761     hit_count[MID_PLAYER]++;
3762 
3763     if (ignores_player())
3764         return;
3765 
3766     // Explosions only have an effect during their explosion phase.
3767     // Special cases can be handled here.
3768     if (is_explosion && !in_explosion_phase)
3769     {
3770         // Trigger the explosion.
3771         finish_beam();
3772         return;
3773     }
3774 
3775     if (is_tracer)
3776     {
3777         tracer_affect_player();
3778         return;
3779     }
3780 
3781     // Trigger an interrupt, so travel will stop on misses which
3782     // generate smoke.
3783     if (!YOU_KILL(thrower))
3784     {
3785         if (agent() && agent()->is_monster())
3786         {
3787             interrupt_activity(activity_interrupt::monster_attacks,
3788                                agent()->as_monster());
3789         }
3790         else
3791             interrupt_activity(activity_interrupt::monster_attacks);
3792     }
3793 
3794     if (flavour == BEAM_MISSILE && item)
3795     {
3796         ranged_attack attk(agent(true), &you, item, use_target_as_pos, agent());
3797         attk.attack();
3798         // fsim purposes - throw_it detects if an attack connected through
3799         // hit_verb
3800         if (attk.ev_margin >= 0 && hit_verb.empty())
3801             hit_verb = attk.attack_verb;
3802         if (attk.reflected)
3803             reflect();
3804         extra_range_used += attk.range_used;
3805         return;
3806     }
3807 
3808     // Visible beams reveal invisible monsters; otherwise animations confer
3809     // an information advantage for sighted players
3810     if (visible() && agent() && agent()->is_monster())
3811         agent()->as_monster()->unseen_pos = agent()->pos();
3812 
3813     if (misses_player())
3814         return;
3815 
3816     const bool engulfs = is_explosion || is_big_cloud();
3817 
3818     if (is_enchantment())
3819     {
3820         if (real_flavour == BEAM_CHAOS || real_flavour == BEAM_RANDOM)
3821         {
3822             if (hit_verb.empty())
3823                 hit_verb = engulfs ? "engulfs" : "hits";
3824             mprf("The %s %s %s!", name.c_str(), hit_verb.c_str(),
3825                 you.hp > 0 ? "you" : "your lifeless body");
3826         }
3827 
3828         affect_player_enchantment();
3829         return;
3830     }
3831 
3832     msg_generated = true;
3833 
3834     // FIXME: Lots of duplicated code here (compare handling of
3835     // monsters)
3836     int pre_ac_dam = 0;
3837 
3838     // Roll the damage.
3839     if (!(origin_spell == SPELL_FLASH_FREEZE && you.duration[DUR_FROZEN]))
3840         pre_ac_dam += damage.roll();
3841 
3842     int pre_res_dam = apply_AC(&you, pre_ac_dam);
3843 
3844 #ifdef DEBUG_DIAGNOSTICS
3845     dprf(DIAG_BEAM, "Player damage: before AC=%d; after AC=%d",
3846                     pre_ac_dam, pre_res_dam);
3847 #endif
3848 
3849     practise_being_shot();
3850 
3851     bool was_affected = false;
3852     int  old_hp       = you.hp;
3853 
3854     pre_res_dam = max(0, pre_res_dam);
3855 
3856     // If the beam is an actual missile or of the MMISSILE type (Earth magic)
3857     // we might bleed on the floor.
3858     if (!engulfs
3859         && (flavour == BEAM_MISSILE || flavour == BEAM_MMISSILE))
3860     {
3861         // assumes DVORP_PIERCING, factor: 0.5
3862         int blood = min(you.hp, pre_res_dam / 2);
3863         bleed_onto_floor(you.pos(), MONS_PLAYER, blood, true);
3864     }
3865 
3866     // Apply resistances to damage, but don't print "You resist" messages yet
3867     int final_dam = check_your_resists(pre_res_dam, flavour, "", this, false);
3868 
3869     // Tell the player the beam hit
3870     if (hit_verb.empty())
3871         hit_verb = engulfs ? "engulfs" : "hits";
3872 
3873     if (flavour != BEAM_VISUAL && !is_enchantment())
3874     {
3875         mprf("The %s %s %s%s%s", name.c_str(), hit_verb.c_str(),
3876              you.hp > 0 ? "you" : "your lifeless body",
3877              final_dam ? "" : " but does no damage",
3878              attack_strength_punctuation(final_dam).c_str());
3879     }
3880 
3881     // Now print the messages associated with checking resistances, so that
3882     // these come after the beam actually hitting.
3883     // Note that this must be called with the pre-resistance damage, so that
3884     // poison effects etc work properly.
3885     check_your_resists(pre_res_dam, flavour, "", this, true);
3886 
3887     if (flavour == BEAM_STUN_BOLT
3888         && !you.duration[DUR_PARALYSIS]
3889         && (you.res_elec() <= 0 || coinflip()))
3890     {
3891         const bool was_parad = you.duration[DUR_PARALYSIS];
3892         you.paralyse(agent(), 1);
3893         was_affected = (!was_parad && you.duration[DUR_PARALYSIS]) || was_affected;
3894 
3895     }
3896 
3897     if (flavour == BEAM_MIASMA && final_dam > 0)
3898         was_affected = miasma_player(agent(), name);
3899 
3900     if (flavour == BEAM_DEVASTATION) // MINDBURST already handled
3901         blood_spray(you.pos(), MONS_PLAYER, final_dam / 5);
3902 
3903     // Confusion effect for spore explosions
3904     if (flavour == BEAM_SPORE && final_dam
3905         && !(you.holiness() & MH_UNDEAD)
3906         && !you.is_unbreathing())
3907     {
3908         confuse_player(2 + random2(3));
3909     }
3910 
3911     if (flavour == BEAM_UNRAVELLED_MAGIC)
3912         affect_player_enchantment();
3913 
3914     // handling of missiles
3915     if (item && item->base_type == OBJ_MISSILES)
3916     {
3917         if (item->sub_type == MI_THROWING_NET)
3918         {
3919             if (player_caught_in_net())
3920             {
3921                 if (monster_by_mid(source_id))
3922                     xom_is_stimulated(50);
3923                 was_affected = true;
3924             }
3925         }
3926         else if (item->brand == SPMSL_CURARE)
3927         {
3928             if (x_chance_in_y(90 - 3 * you.armour_class(), 100))
3929             {
3930                 curare_actor(agent(), (actor*) &you, 2, name, source_name);
3931                 was_affected = true;
3932             }
3933         }
3934 
3935         if (you.has_mutation(MUT_JELLY_MISSILE)
3936             && you.hp < you.hp_max
3937             && !you.duration[DUR_DEATHS_DOOR]
3938             && item_is_jelly_edible(*item)
3939             && coinflip())
3940         {
3941             mprf("Your attached jelly eats %s!", item->name(DESC_THE).c_str());
3942             inc_hp(random2(final_dam / 2));
3943             canned_msg(MSG_GAIN_HEALTH);
3944             drop_item = false;
3945         }
3946     }
3947 
3948     // Sticky flame.
3949     if (origin_spell == SPELL_STICKY_FLAME
3950         || origin_spell == SPELL_STICKY_FLAME_RANGE)
3951     {
3952         if (!player_res_sticky_flame())
3953         {
3954             napalm_player(random2avg(7, 3) + 1, get_source_name(), aux_source);
3955             was_affected = true;
3956         }
3957     }
3958 
3959     // need to trigger qaz resists after reducing damage from ac/resists.
3960     //    for some reason, strength 2 is the standard. This leads to qaz's
3961     //    resists triggering 2 in 5 times at max piety.
3962     //    perhaps this should scale with damage?
3963     // what to do for hybrid damage?  E.g. bolt of magma, icicle, poison arrow?
3964     // Right now just ignore the physical component.
3965     // what about acid?
3966     you.expose_to_element(flavour, 2, false);
3967 
3968 
3969     // Manticore spikes
3970     if (origin_spell == SPELL_THROW_BARBS && final_dam > 0)
3971     {
3972         mpr("The barbed spikes become lodged in your body.");
3973         if (!you.duration[DUR_BARBS])
3974             you.set_duration(DUR_BARBS, random_range(4, 8));
3975         else
3976             you.increase_duration(DUR_BARBS, random_range(2, 4), 12);
3977 
3978         if (you.attribute[ATTR_BARBS_POW])
3979         {
3980             you.attribute[ATTR_BARBS_POW] =
3981                 min(6, you.attribute[ATTR_BARBS_POW]++);
3982         }
3983         else
3984             you.attribute[ATTR_BARBS_POW] = 4;
3985     }
3986 
3987     if (flavour == BEAM_ENSNARE)
3988         was_affected = ensnare(&you) || was_affected;
3989 
3990     if (origin_spell == SPELL_QUICKSILVER_BOLT)
3991         debuff_player();
3992 
3993     if (origin_spell == SPELL_THROW_PIE && final_dam > 0)
3994     {
3995         const pie_effect effect = _random_pie_effect(you);
3996         mprf("%s!", effect.desc);
3997         effect.effect(you, *this);
3998     }
3999 
4000     dprf(DIAG_BEAM, "Damage: %d", final_dam);
4001 
4002     if (final_dam > 0 || old_hp < you.hp || was_affected)
4003     {
4004         if (mons_att_wont_attack(attitude))
4005         {
4006             friend_info.hurt++;
4007 
4008             // Beam from player rebounded and hit player.
4009             // Xom's amusement at the player's being damaged is handled
4010             // elsewhere.
4011             if (source_id == MID_PLAYER)
4012             {
4013                 if (!aimed_at_feet)
4014                     xom_is_stimulated(200);
4015             }
4016             else if (was_affected)
4017                 xom_is_stimulated(100);
4018         }
4019         else
4020             foe_info.hurt++;
4021     }
4022 
4023     internal_ouch(final_dam);
4024 
4025     // Acid. (Apply this afterward, to avoid bad message ordering.)
4026     if (flavour == BEAM_ACID)
4027         you.splash_with_acid(agent(), 5, true);
4028 
4029     extra_range_used += range_used_on_hit();
4030 
4031     knockback_actor(&you, final_dam);
4032     pull_actor(&you, final_dam);
4033 
4034     if (origin_spell == SPELL_FLASH_FREEZE
4035         || origin_spell == SPELL_CREEPING_FROST
4036         || name == "blast of ice"
4037         || origin_spell == SPELL_GLACIATE && !is_explosion)
4038     {
4039         if (you.duration[DUR_FROZEN])
4040         {
4041             if (origin_spell == SPELL_FLASH_FREEZE)
4042                 canned_msg(MSG_YOU_UNAFFECTED);
4043         }
4044         else
4045         {
4046             mprf(MSGCH_WARN, "You are encased in ice.");
4047             you.duration[DUR_FROZEN] = (2 + random2(3)) * BASELINE_DELAY;
4048         }
4049     }
4050 }
4051 
ignores_player() const4052 bool bolt::ignores_player() const
4053 {
4054 
4055     // Digging -- don't care.
4056     if (flavour == BEAM_DIGGING)
4057         return true;
4058 
4059     if (agent() && agent()->is_monster()
4060         && mons_is_hepliaklqana_ancestor(agent()->as_monster()->type))
4061     {
4062         // friends!
4063         return true;
4064     }
4065 
4066     return false;
4067 }
4068 
apply_AC(const actor * victim,int hurted)4069 int bolt::apply_AC(const actor *victim, int hurted)
4070 {
4071     switch (flavour)
4072     {
4073     case BEAM_DAMNATION:
4074         ac_rule = ac_type::none; break;
4075     case BEAM_ELECTRICITY:
4076     case BEAM_THUNDER:
4077         ac_rule = ac_type::half; break;
4078     case BEAM_FRAG:
4079         ac_rule = ac_type::triple; break;
4080     default: ;
4081     }
4082 
4083     // beams don't obey GDR -> max_damage is 0
4084     return victim->apply_ac(hurted, 0, ac_rule, !is_tracer);
4085 }
4086 
update_hurt_or_helped(monster * mon)4087 void bolt::update_hurt_or_helped(monster* mon)
4088 {
4089     if (!mons_atts_aligned(attitude, mons_attitude(*mon)))
4090     {
4091         if (nasty_to(mon))
4092             foe_info.hurt++;
4093         else if (nice_to(monster_info(mon)))
4094         {
4095             foe_info.helped++;
4096             // Accidentally helped a foe.
4097             if (!is_tracer && !effect_known && mons_is_threatening(*mon))
4098             {
4099                 const int interest =
4100                     (flavour == BEAM_INVISIBILITY && can_see_invis) ? 25 : 100;
4101                 xom_is_stimulated(interest);
4102             }
4103         }
4104     }
4105     else
4106     {
4107         if (nasty_to(mon))
4108         {
4109             friend_info.hurt++;
4110 
4111             // Harmful beam from this monster rebounded and hit the monster.
4112             if (!is_tracer && mon->mid == source_id)
4113                 xom_is_stimulated(100);
4114         }
4115         else if (nice_to(monster_info(mon)))
4116             friend_info.helped++;
4117     }
4118 }
4119 
tracer_enchantment_affect_monster(monster * mon)4120 void bolt::tracer_enchantment_affect_monster(monster* mon)
4121 {
4122     // Only count tracers as hitting creatures they could potentially affect
4123     if (ench_flavour_affects_monster(flavour, mon, true)
4124         && !(has_saving_throw() && mons_invuln_will(*mon)))
4125     {
4126         // Update friend or foe encountered.
4127         if (!mons_atts_aligned(attitude, mons_attitude(*mon)))
4128         {
4129             foe_info.count++;
4130             foe_info.power += mon->get_experience_level();
4131         }
4132         else
4133         {
4134             friend_info.count++;
4135             friend_info.power += mon->get_experience_level();
4136         }
4137     }
4138 
4139     handle_stop_attack_prompt(mon);
4140     if (!beam_cancelled)
4141         extra_range_used += range_used_on_hit();
4142 }
4143 
4144 // Return false if we should skip handling this monster.
determine_damage(monster * mon,int & preac,int & postac,int & final)4145 bool bolt::determine_damage(monster* mon, int& preac, int& postac, int& final)
4146 {
4147     preac = postac = final = 0;
4148 
4149     const bool freeze_immune =
4150         origin_spell == SPELL_FLASH_FREEZE && mon->has_ench(ENCH_FROZEN);
4151 
4152     // [ds] Changed how tracers determined damage: the old tracer
4153     // model took the average damage potential, subtracted the average
4154     // AC damage reduction and called that the average damage output.
4155     // This could easily predict an average damage output of 0 for
4156     // high AC monsters, with the result that monsters often didn't
4157     // bother using ranged attacks at high AC targets.
4158     //
4159     // The new model rounds up average damage at every stage, so it
4160     // will predict a damage output of 1 even if the average damage
4161     // expected is much closer to 0. This will allow monsters to use
4162     // ranged attacks vs high AC targets.
4163       // [1KB] What ds' code actually does is taking the max damage minus
4164       // average AC. This does work well, even using no AC would. An
4165       // attack that _usually_ does no damage but can possibly do some means
4166       // we'll ultimately get it through. And monsters with weak ranged
4167       // almost always would do no better in melee.
4168     //
4169     // This is not an entirely beneficial change; the old tracer
4170     // damage system would make monsters with weak ranged attacks
4171     // close in to their foes, while the new system will make it more
4172     // likely that such monsters will hang back and make ineffective
4173     // ranged attacks. Thus the new tracer damage calculation will
4174     // hurt monsters with low-damage ranged attacks and high-damage
4175     // melee attacks. I judge this an acceptable compromise (for now).
4176     //
4177     const int preac_max_damage =
4178         (freeze_immune) ? 0
4179                         : damage.num * damage.size;
4180 
4181     // preac: damage before AC modifier
4182     // postac: damage after AC modifier
4183     // final: damage after AC and resists
4184     // All these are invalid if we return false.
4185 
4186     if (is_tracer)
4187     {
4188         // Was mean between min and max;
4189         preac = preac_max_damage;
4190     }
4191     else if (!freeze_immune)
4192         preac = damage.roll();
4193 
4194     int tracer_postac_max = preac_max_damage;
4195 
4196     postac = apply_AC(mon, preac);
4197 
4198     if (is_tracer)
4199     {
4200         postac = div_round_up(tracer_postac_max, 2);
4201 
4202         const int adjusted_postac_max =
4203             mons_adjust_flavoured(mon, *this, tracer_postac_max, false);
4204 
4205         final = div_round_up(adjusted_postac_max, 2);
4206     }
4207     else
4208     {
4209         postac = max(0, postac);
4210         // Don't do side effects (beam might miss or be a tracer).
4211         final = mons_adjust_flavoured(mon, *this, postac, false);
4212     }
4213 
4214     // Sanity check. Importantly for
4215     // tracer_nonenchantment_affect_monster, final > 0
4216     // implies preac > 0.
4217     ASSERT(0 <= postac);
4218     ASSERT(postac <= preac);
4219     ASSERT(0 <= final);
4220     ASSERT(preac > 0 || final == 0);
4221 
4222     return true;
4223 }
4224 
handle_stop_attack_prompt(monster * mon)4225 void bolt::handle_stop_attack_prompt(monster* mon)
4226 {
4227     if (thrower != KILL_YOU_MISSILE && thrower != KILL_YOU
4228         || is_harmless(mon)
4229         || friend_info.dont_stop && foe_info.dont_stop)
4230     {
4231         return;
4232     }
4233 
4234     bool prompted = false;
4235 
4236     if (stop_attack_prompt(mon, true, target, &prompted)
4237         || _stop_because_god_hates_target_prompt(mon, origin_spell))
4238     {
4239         beam_cancelled = true;
4240         finish_beam();
4241     }
4242     // Handle enslaving monsters when a nasty dur is up: give a prompt for
4243     // attempting to enslave monsters that might be affected.
4244     else if (flavour == BEAM_CHARM)
4245     {
4246         string verb = make_stringf("charm %s", mon->name(DESC_THE).c_str());
4247         if (rude_stop_summoning_prompt(verb))
4248         {
4249             beam_cancelled = true;
4250             finish_beam();
4251             prompted = true;
4252         }
4253     }
4254 
4255     if (prompted)
4256     {
4257         friend_info.dont_stop = true;
4258         foe_info.dont_stop = true;
4259     }
4260 }
4261 
tracer_nonenchantment_affect_monster(monster * mon)4262 void bolt::tracer_nonenchantment_affect_monster(monster* mon)
4263 {
4264     int preac, post, final;
4265 
4266     if (!determine_damage(mon, preac, post, final))
4267         return;
4268 
4269     // Check only if actual damage and the monster is worth caring about.
4270     if (final > 0 && (mons_is_threatening(*mon) || mons_class_is_test(mon->type)))
4271     {
4272         ASSERT(preac > 0);
4273 
4274         // Monster could be hurt somewhat, but only apply the
4275         // monster's power based on how badly it is affected.
4276         // For example, if a fire giant (power 16) threw a
4277         // fireball at another fire giant, and it only took
4278         // 1/3 damage, then power of 5 would be applied.
4279 
4280         // Counting foes is only important for monster tracers.
4281         if (!mons_atts_aligned(attitude, mons_attitude(*mon)))
4282         {
4283             foe_info.power += 2 * final * mon->get_experience_level() / preac;
4284             foe_info.count++;
4285         }
4286         else
4287         {
4288             // Discourage summoned monsters firing on their summoner.
4289             const monster* mon_source = agent()->as_monster();
4290             if (mon_source && mon_source->summoner == mon->mid)
4291                 friend_info.power = 100;
4292             else
4293             {
4294                 friend_info.power
4295                     += 2 * final * mon->get_experience_level() / preac;
4296             }
4297             friend_info.count++;
4298         }
4299     }
4300 
4301     // Maybe the user wants to cancel at this point.
4302     handle_stop_attack_prompt(mon);
4303     if (beam_cancelled)
4304         return;
4305 
4306     // Either way, we could hit this monster, so update range used.
4307     extra_range_used += range_used_on_hit();
4308 }
4309 
tracer_affect_monster(monster * mon)4310 void bolt::tracer_affect_monster(monster* mon)
4311 {
4312     // Ignore unseen monsters.
4313     if (!agent() || !agent()->can_see(*mon))
4314         return;
4315 
4316     if (flavour == BEAM_UNRAVELLING && monster_is_debuffable(*mon))
4317         is_explosion = true;
4318 
4319     // Trigger explosion on exploding beams.
4320     if (is_explosion && !in_explosion_phase)
4321     {
4322         finish_beam();
4323         return;
4324     }
4325 
4326     // Special explosions (current exploding missiles) aren't
4327     // auto-hit, so we need to explode them at every possible
4328     // end-point?
4329     if (special_explosion)
4330     {
4331         bolt orig = *special_explosion;
4332         affect_endpoint();
4333         *special_explosion = orig;
4334     }
4335 
4336     if (is_enchantment())
4337         tracer_enchantment_affect_monster(mon);
4338     else
4339         tracer_nonenchantment_affect_monster(mon);
4340 }
4341 
enchantment_affect_monster(monster * mon)4342 void bolt::enchantment_affect_monster(monster* mon)
4343 {
4344     god_conduct_trigger conducts[3];
4345 
4346     bool hit_woke_orc = false;
4347 
4348     // Nasty enchantments will annoy the monster, and are considered
4349     // naughty (even if a monster might resist).
4350     if (nasty_to(mon))
4351     {
4352         if (YOU_KILL(thrower))
4353         {
4354             set_attack_conducts(conducts, *mon, you.can_see(*mon));
4355 
4356             if (have_passive(passive_t::convert_orcs)
4357                 && mons_genus(mon->type) == MONS_ORC
4358                 && mon->asleep() && you.see_cell(mon->pos()))
4359             {
4360                 hit_woke_orc = true;
4361             }
4362         }
4363         behaviour_event(mon, ME_ANNOY, agent());
4364     }
4365     else if (flavour != BEAM_HIBERNATION || !mon->asleep())
4366         behaviour_event(mon, ME_ALERT, agent());
4367 
4368     // Doing this here so that the player gets to see monsters
4369     // "flicker and vanish" when turning invisible....
4370     if (animate)
4371     {
4372         _ench_animation(effect_known ? real_flavour
4373                                      : BEAM_MAGIC,
4374                         mon, effect_known);
4375     }
4376 
4377     // Try to hit the monster with the enchantment. The behaviour_event above
4378     // may have caused a pacified monster to leave the level, so only try to
4379     // enchant it if it's still here. If the monster did leave the level, set
4380     // obvious_effect so we don't get "Nothing appears to happen".
4381     int res_margin = 0;
4382     const mon_resist_type ench_result = mon->alive()
4383                                       ? try_enchant_monster(mon, res_margin)
4384                                       : (obvious_effect = true, MON_OTHER);
4385 
4386     if (mon->alive())           // Aftereffects.
4387     {
4388         // Message or record the success/failure.
4389         switch (ench_result)
4390         {
4391         case MON_RESIST:
4392             if (simple_monster_message(*mon,
4393                                    mon->resist_margin_phrase(res_margin).c_str()))
4394             {
4395                 msg_generated = true;
4396             }
4397             break;
4398         case MON_UNAFFECTED:
4399             if (simple_monster_message(*mon, " is unaffected."))
4400                 msg_generated = true;
4401             break;
4402         case MON_AFFECTED:
4403         case MON_OTHER:         // Should this really be here?
4404             update_hurt_or_helped(mon);
4405             break;
4406         }
4407 
4408         if (hit_woke_orc)
4409             beogh_follower_convert(mon, true);
4410     }
4411 
4412     extra_range_used += range_used_on_hit();
4413 }
4414 
glaciate_freeze(monster * mon,killer_type englaciator,int kindex)4415 void glaciate_freeze(monster* mon, killer_type englaciator,
4416                              int kindex)
4417 {
4418     const coord_def where = mon->pos();
4419     const monster_type pillar_type =
4420         mons_is_zombified(*mon) ? mons_zombie_base(*mon)
4421                                 : mons_species(mon->type);
4422     const int hd = mon->get_experience_level();
4423 
4424     bool goldify = have_passive(passive_t::goldify_corpses);
4425 
4426     if (goldify)
4427         simple_monster_message(*mon, " shatters and turns to gold!");
4428     else
4429         simple_monster_message(*mon, " is frozen into a solid block of ice!");
4430 
4431     // If the monster leaves a corpse when it dies, destroy the corpse.
4432     item_def* corpse = monster_die(*mon, englaciator, kindex);
4433     // Unless it was turned into gold, in which case don't make an ice statue.
4434     if (goldify)
4435         return;
4436 
4437     if (corpse)
4438         destroy_item(corpse->index());
4439 
4440     if (monster *pillar = create_monster(
4441                         mgen_data(MONS_BLOCK_OF_ICE,
4442                                   BEH_HOSTILE,
4443                                   where,
4444                                   MHITNOT,
4445                                   MG_FORCE_PLACE).set_base(pillar_type),
4446                                   false))
4447     {
4448         // Enemies with more HD leave longer-lasting blocks of ice.
4449         int time_left = (random2(8) + hd) * BASELINE_DELAY;
4450         mon_enchant temp_en(ENCH_SLOWLY_DYING, 1, 0, time_left);
4451         pillar->update_ench(temp_en);
4452     }
4453 }
4454 
monster_post_hit(monster * mon,int dmg)4455 void bolt::monster_post_hit(monster* mon, int dmg)
4456 {
4457     // Suppress the message for tremorstones.
4458     if (YOU_KILL(thrower) && you.see_cell(mon->pos())
4459         && name != "burst of rock shards")
4460     {
4461         print_wounds(*mon);
4462     }
4463 
4464     // Don't annoy friendlies or good neutrals if the player's beam
4465     // did no damage. Hostiles will still take umbrage.
4466     if (dmg > 0 || !mon->wont_attack() || !YOU_KILL(thrower))
4467     {
4468         special_missile_type m_brand = SPMSL_FORBID_BRAND;
4469         if (item && item->base_type == OBJ_MISSILES)
4470             m_brand = get_ammo_brand(*item);
4471 
4472         // Don't immediately turn insane monsters hostile.
4473         if (m_brand != SPMSL_FRENZY)
4474         {
4475             behaviour_event(mon, ME_ANNOY, agent());
4476             // behaviour_event can make a monster leave the level or vanish.
4477             if (!mon->alive())
4478                 return;
4479         }
4480     }
4481 
4482     if (YOU_KILL(thrower) && !mon->wont_attack() && !mons_is_firewood(*mon))
4483         you.pet_target = mon->mindex();
4484 
4485     // Sticky flame.
4486     if (origin_spell == SPELL_STICKY_FLAME
4487         || origin_spell == SPELL_STICKY_FLAME_RANGE)
4488     {
4489         const int levels = min(4, 1 + random2(dmg) / 2);
4490         napalm_monster(mon, agent(), levels);
4491     }
4492 
4493     // Acid splash from yellow draconians / acid dragons
4494     if (origin_spell == SPELL_ACID_SPLASH)
4495     {
4496         // the acid can splash onto adjacent targets
4497         for (adjacent_iterator ai(mon->pos()); ai; ++ai)
4498         {
4499             if (actor *victim = actor_at(*ai))
4500             {
4501                 if (victim == agent())
4502                     continue;
4503 
4504                 if (you.see_cell(*ai))
4505                 {
4506                     mprf("The acid splashes onto %s!",
4507                          victim->name(DESC_THE).c_str());
4508                 }
4509 
4510                 victim->splash_with_acid(agent(), 3);
4511             }
4512         }
4513     }
4514 
4515     // Handle missile effects.
4516     if (item && item->base_type == OBJ_MISSILES
4517         && item->brand == SPMSL_CURARE && ench_power == AUTOMATIC_HIT)
4518     {
4519         curare_actor(agent(), mon, 2, name, source_name);
4520     }
4521 
4522     // purple draconian breath
4523     if (origin_spell == SPELL_QUICKSILVER_BOLT)
4524         debuff_monster(*mon);
4525 
4526     if (dmg)
4527         beogh_follower_convert(mon, true);
4528 
4529     knockback_actor(mon, dmg);
4530 
4531     if (origin_spell == SPELL_FLASH_FREEZE
4532              || name == "blast of ice"
4533              || origin_spell == SPELL_GLACIATE && !is_explosion)
4534     {
4535         if (mon->has_ench(ENCH_FROZEN))
4536         {
4537             if (origin_spell == SPELL_FLASH_FREEZE)
4538                 simple_monster_message(*mon, " is unaffected.");
4539         }
4540         else
4541         {
4542             simple_monster_message(*mon, " is flash-frozen.");
4543             mon->add_ench(ENCH_FROZEN);
4544         }
4545     }
4546 
4547     if (origin_spell == SPELL_THROW_BARBS && dmg > 0
4548         && !(mon->is_insubstantial() || mons_genus(mon->type) == MONS_JELLY))
4549     {
4550         mon->add_ench(mon_enchant(ENCH_BARBS, 1, agent(),
4551                                   random_range(5, 7) * BASELINE_DELAY));
4552     }
4553 
4554     if (origin_spell == SPELL_THROW_PIE && dmg > 0)
4555     {
4556         const pie_effect effect = _random_pie_effect(*mon);
4557         if (you.see_cell(mon->pos()))
4558             mprf("%s!", effect.desc);
4559         effect.effect(*mon, *this);
4560     }
4561 
4562     if (flavour == BEAM_STUN_BOLT
4563         && !mon->has_ench(ENCH_PARALYSIS)
4564         && mon->res_elec() <= 0
4565         && !mon->stasis())
4566     {
4567         simple_monster_message(*mon, " is paralysed.");
4568         mon->add_ench(mon_enchant(ENCH_PARALYSIS, 1, agent(), BASELINE_DELAY));
4569     }
4570 }
4571 
knockback_actor(actor * act,int dam)4572 void bolt::knockback_actor(actor *act, int dam)
4573 {
4574     if (!act || !can_knockback(*act, dam))
4575         return;
4576 
4577     const int distance =
4578         (origin_spell == SPELL_FORCE_LANCE
4579          || origin_spell == SPELL_ISKENDERUNS_MYSTIC_BLAST)
4580             ? 2 + div_rand_round(ench_power, 50) :
4581         (origin_spell == SPELL_CHILLING_BREATH) ? 2 : 1;
4582 
4583     const int roll = origin_spell == SPELL_FORCE_LANCE
4584                      ? 7 + 0.27 * ench_power
4585                      : 17;
4586     const int weight = max_corpse_chunks(act->is_monster() ? act->type :
4587                                                             you.mons_species());
4588 
4589     const coord_def oldpos = act->pos();
4590 
4591     if (act->is_stationary())
4592         return;
4593     // We can't do knockback if the beam starts and ends on the same space
4594     if (source == oldpos)
4595         return;
4596     ASSERT(ray.pos() == oldpos);
4597 
4598     coord_def newpos = oldpos;
4599     for (int dist_travelled = 0; dist_travelled < distance; ++dist_travelled)
4600     {
4601         if (x_chance_in_y(weight, roll))
4602             continue;
4603 
4604         const ray_def oldray(ray);
4605 
4606         ray.advance();
4607 
4608         newpos = ray.pos();
4609         if (newpos == oldray.pos()
4610             || cell_is_solid(newpos)
4611             || actor_at(newpos)
4612             || !act->can_pass_through(newpos)
4613             || !act->is_habitable(newpos))
4614         {
4615             ray = oldray;
4616             break;
4617         }
4618 
4619         act->move_to_pos(newpos);
4620         if (act->is_player())
4621             stop_delay(true);
4622     }
4623 
4624     if (newpos == oldpos)
4625         return;
4626 
4627     if (you.can_see(*act))
4628     {
4629         if (origin_spell == SPELL_CHILLING_BREATH)
4630         {
4631             mprf("%s %s blown backwards by the freezing wind.",
4632                  act->name(DESC_THE).c_str(),
4633                  act->conj_verb("are").c_str());
4634         }
4635         else
4636         {
4637             mprf("%s %s knocked back by the %s.",
4638                  act->name(DESC_THE).c_str(),
4639                  act->conj_verb("are").c_str(),
4640                  name.c_str());
4641         }
4642     }
4643 
4644     if (act->pos() != newpos)
4645         act->collide(newpos, agent(), ench_power);
4646 
4647     // Stun the monster briefly so that it doesn't look as though it wasn't
4648     // knocked back at all
4649     if (act->is_monster())
4650         act->as_monster()->speed_increment -= random2(6) + 4;
4651 
4652     act->apply_location_effects(oldpos, killer(),
4653                                 actor_to_death_source(agent()));
4654 }
4655 
pull_actor(actor * act,int dam)4656 void bolt::pull_actor(actor *act, int dam)
4657 {
4658     if (!act || !can_pull(*act, dam))
4659         return;
4660 
4661     // How far we'll try to pull the actor to make them adjacent to the source.
4662     const int distance = (act->pos() - source).rdist() - 1;
4663     ASSERT(distance > 0);
4664 
4665     const coord_def oldpos = act->pos();
4666     ASSERT(ray.pos() == oldpos);
4667 
4668     coord_def newpos = oldpos;
4669     for (int dist_travelled = 0; dist_travelled < distance; ++dist_travelled)
4670     {
4671         const ray_def oldray(ray);
4672 
4673         ray.regress();
4674 
4675         newpos = ray.pos();
4676         if (newpos == oldray.pos()
4677             || cell_is_solid(newpos)
4678             || actor_at(newpos)
4679             || !act->can_pass_through(newpos)
4680             || !act->is_habitable(newpos))
4681         {
4682             ray = oldray;
4683             break;
4684         }
4685 
4686         act->move_to_pos(newpos);
4687         if (act->is_player())
4688             stop_delay(true);
4689     }
4690 
4691     if (newpos == oldpos)
4692         return;
4693 
4694     if (you.can_see(*act))
4695     {
4696         mprf("%s %s yanked forward by the %s.", act->name(DESC_THE).c_str(),
4697              act->conj_verb("are").c_str(), name.c_str());
4698     }
4699 
4700     if (act->pos() != newpos)
4701         act->collide(newpos, agent(), ench_power);
4702 
4703     act->apply_location_effects(oldpos, killer(),
4704                                 actor_to_death_source(agent()));
4705 }
4706 
4707 // Return true if the player's god will be unforgiving about the effects
4708 // of this beam.
god_cares() const4709 bool bolt::god_cares() const
4710 {
4711     return effect_known || effect_wanton;
4712 }
4713 
4714 // Return true if the block succeeded (including reflections.)
attempt_block(monster * mon)4715 bool bolt::attempt_block(monster* mon)
4716 {
4717     const int shield_block = mon->shield_bonus();
4718     if (shield_block <= 0)
4719         return false;
4720 
4721     const int sh_hit = random2(hit * 130 / 100 + mon->shield_block_penalty());
4722     if (sh_hit >= shield_block)
4723         return false;
4724 
4725     item_def *shield = mon->mslot_item(MSLOT_SHIELD);
4726     if (is_reflectable(*mon))
4727     {
4728         if (mon->observable())
4729         {
4730             if (shield && is_shield(*shield) && shield_reflects(*shield))
4731             {
4732                 mprf("%s reflects the %s off %s %s!",
4733                      mon->name(DESC_THE).c_str(),
4734                      name.c_str(),
4735                      mon->pronoun(PRONOUN_POSSESSIVE).c_str(),
4736                      shield->name(DESC_PLAIN).c_str());
4737                 ident_reflector(shield);
4738             }
4739             else
4740             {
4741                 mprf("The %s reflects off an invisible shield around %s!",
4742                      name.c_str(),
4743                      mon->name(DESC_THE).c_str());
4744 
4745                 item_def *amulet = mon->mslot_item(MSLOT_JEWELLERY);
4746                 if (amulet)
4747                     ident_reflector(amulet);
4748             }
4749         }
4750         else if (you.see_cell(pos()))
4751             mprf("The %s bounces off of thin air!", name.c_str());
4752 
4753         reflect();
4754     }
4755     else if (you.see_cell(pos()))
4756     {
4757         mprf("%s blocks the %s.",
4758              mon->name(DESC_THE).c_str(), name.c_str());
4759         finish_beam();
4760     }
4761 
4762     mon->shield_block_succeeded();
4763     return true;
4764 }
4765 
4766 /// Is the given monster a bush or bush-like 'monster', and can the given beam
4767 /// travel through it without harm?
bush_immune(const monster & mons) const4768 bool bolt::bush_immune(const monster &mons) const
4769 {
4770     return
4771         (mons_species(mons.type) == MONS_BUSH || mons.type == MONS_BRIAR_PATCH)
4772         && !pierce && !is_explosion
4773         && !is_enchantment()
4774         && target != mons.pos()
4775         && origin_spell != SPELL_STICKY_FLAME
4776         && origin_spell != SPELL_STICKY_FLAME_RANGE
4777         && origin_spell != SPELL_CHAIN_LIGHTNING;
4778 }
4779 
affect_monster(monster * mon)4780 void bolt::affect_monster(monster* mon)
4781 {
4782     // Don't hit dead or fake monsters.
4783     if (!mon->alive() || mon->type == MONS_PLAYER_SHADOW)
4784         return;
4785 
4786     hit_count[mon->mid]++;
4787 
4788     if (shoot_through_monster(*this, mon) && !is_tracer)
4789     {
4790         if (you.see_cell(mon->pos()))
4791         {
4792             if (testbits(mon->flags, MF_DEMONIC_GUARDIAN))
4793                 mpr("Your demonic guardian avoids your attack.");
4794             else
4795                 god_protects(agent(), mon, false); // messaging
4796         }
4797     }
4798 
4799     if (flavour == BEAM_WATER && mon->type == MONS_WATER_ELEMENTAL && !is_tracer)
4800     {
4801         if (you.see_cell(mon->pos()))
4802             mprf("The %s passes through %s.", name.c_str(), mon->name(DESC_THE).c_str());
4803     }
4804 
4805     if (ignores_monster(mon))
4806         return;
4807 
4808     // Handle tracers separately.
4809     if (is_tracer)
4810     {
4811         tracer_affect_monster(mon);
4812         return;
4813     }
4814 
4815     // Visual - wake monsters.
4816     if (flavour == BEAM_VISUAL)
4817     {
4818         behaviour_event(mon, ME_DISTURB, agent(), source);
4819         return;
4820     }
4821 
4822     if (flavour == BEAM_MISSILE && item)
4823     {
4824         actor *ag = agent(true);
4825         // if the immediate agent is now dead, check to see if we can get a
4826         // usable agent by factoring in reflections.
4827         // At this point, it is possible that the agent is the dummy monster
4828         // associated with YOU_FAULTLESS. This case will cause
4829         // "INVALID YOU_FAULTLESS" to show up in dprfs and mess up the to-hit,
4830         // but it otherwise works.
4831         // TODO: is there a good way of handling the to-hit correctly? (And why
4832         // should the to-hit be affected by reflections at all?)
4833         // An alternative would be to stop the missile at this point.
4834         if (!ag)
4835             ag = agent(false);
4836         // if that didn't work, blanket fall back on YOU_FAULTLESS. This covers
4837         // a number of other weird penetration cases.
4838         if (!ag)
4839             ag = &env.mons[YOU_FAULTLESS];
4840         ASSERT(ag);
4841         ranged_attack attk(ag, mon, item, use_target_as_pos, agent());
4842         attk.attack();
4843         // fsim purposes - throw_it detects if an attack connected through
4844         // hit_verb
4845         if (attk.ev_margin >= 0 && hit_verb.empty())
4846             hit_verb = attk.attack_verb;
4847         if (attk.reflected)
4848             reflect();
4849         extra_range_used += attk.range_used;
4850         return;
4851     }
4852 
4853     // Explosions always 'hit'.
4854     const bool engulfs = (is_explosion || is_big_cloud());
4855 
4856     if (is_enchantment())
4857     {
4858         if (real_flavour == BEAM_CHAOS || real_flavour == BEAM_RANDOM)
4859         {
4860             if (hit_verb.empty())
4861                 hit_verb = engulfs ? "engulfs" : "hits";
4862             if (you.see_cell(mon->pos()))
4863             {
4864                 mprf("The %s %s %s.", name.c_str(), hit_verb.c_str(),
4865                      mon->name(DESC_THE).c_str());
4866             }
4867             else if (heard && !hit_noise_msg.empty())
4868                 mprf(MSGCH_SOUND, "%s", hit_noise_msg.c_str());
4869         }
4870         // no to-hit check
4871         enchantment_affect_monster(mon);
4872         return;
4873     }
4874 
4875     if (is_explosion && !in_explosion_phase)
4876     {
4877         // It hit a monster, so the beam should terminate.
4878         // Don't actually affect the monster; the explosion
4879         // will take care of that.
4880         finish_beam();
4881         return;
4882     }
4883 
4884     // We need to know how much the monster _would_ be hurt by this,
4885     // before we decide if it actually hits.
4886     int preac, postac, final;
4887     if (!determine_damage(mon, preac, postac, final))
4888         return;
4889 
4890 #ifdef DEBUG_DIAGNOSTICS
4891     dprf(DIAG_BEAM, "Monster: %s; Damage: pre-AC: %d; post-AC: %d; post-resist: %d",
4892          mon->name(DESC_PLAIN).c_str(), preac, postac, final);
4893 #endif
4894 
4895     // Player beams which hit friendlies or good neutrals will annoy
4896     // them and be considered naughty if they do damage (this is so as
4897     // not to penalise players that fling fireballs into a melee with
4898     // fire elementals on their side - the elementals won't give a sh*t,
4899     // after all).
4900 
4901     god_conduct_trigger conducts[3];
4902 
4903     if (nasty_to(mon))
4904     {
4905         if (YOU_KILL(thrower) && final > 0)
4906             set_attack_conducts(conducts, *mon, you.can_see(*mon));
4907     }
4908 
4909     if (engulfs && flavour == BEAM_SPORE // XXX: engulfs is redundant?
4910         && mon->holiness() & MH_NATURAL
4911         && !mon->is_unbreathing())
4912     {
4913         apply_enchantment_to_monster(mon);
4914     }
4915 
4916     if (flavour == BEAM_UNRAVELLED_MAGIC)
4917     {
4918         int unused; // res_margin
4919         try_enchant_monster(mon, unused);
4920     }
4921 
4922     // Make a copy of the to-hit before we modify it.
4923     int beam_hit = hit;
4924 
4925     if (beam_hit != AUTOMATIC_HIT)
4926         beam_hit = apply_lighting(beam_hit, *mon);
4927 
4928     // The monster may block the beam.
4929     if (!engulfs && is_blockable() && attempt_block(mon))
4930         return;
4931 
4932     defer_rand r;
4933     int rand_ev = random2(mon->evasion());
4934     bool repel = mon->missile_repulsion();
4935 
4936     // FIXME: We're randomising mon->evasion, which is further
4937     // randomised inside test_beam_hit. This is so we stay close to the
4938     // 4.0 to-hit system (which had very little love for monsters).
4939     if (!engulfs && !_test_beam_hit(beam_hit, rand_ev, pierce, repel, r))
4940     {
4941         // If the PLAYER cannot see the monster, don't tell them anything!
4942         if (mon->observable() && name != "burst of metal fragments")
4943         {
4944             // if it would have hit otherwise...
4945             if (_test_beam_hit(beam_hit, rand_ev, pierce, 0, r))
4946             {
4947                 msg::stream << mon->name(DESC_THE) << " "
4948                             << "repels the " << name
4949                             << '!' << endl;
4950             }
4951             else
4952             {
4953                 msg::stream << "The " << name << " misses "
4954                             << mon->name(DESC_THE) << '.' << endl;
4955             }
4956         }
4957         return;
4958     }
4959 
4960     update_hurt_or_helped(mon);
4961 
4962     // We'll say ballistomycete spore explosions don't trigger the ally attack
4963     // conduct for Fedhas worshipers. Mostly because you can accidentally blow
4964     // up a group of 8 plants and get placed under penance until the end of
4965     // time otherwise. I'd prefer to do this elsewhere but the beam information
4966     // goes out of scope.
4967     if (you_worship(GOD_FEDHAS) && flavour == BEAM_SPORE)
4968         conducts[0].set();
4969 
4970     if (!is_explosion && !noise_generated)
4971     {
4972         heard = noisy(loudness, pos(), source_id) || heard;
4973         noise_generated = true;
4974     }
4975 
4976     if (!mon->alive())
4977         return;
4978 
4979     // The beam hit.
4980     if (you.see_cell(mon->pos()))
4981     {
4982         // Monsters are never currently helpless in ranged combat.
4983         if (hit_verb.empty())
4984             hit_verb = engulfs ? "engulfs" : "hits";
4985 
4986         // If the beam did no damage because of resistances,
4987         // mons_adjust_flavoured below will print "%s completely resists", so
4988         // no need to also say "does no damage" here.
4989         mprf("The %s %s %s%s%s",
4990              name.c_str(),
4991              hit_verb.c_str(),
4992              mon->name(DESC_THE).c_str(),
4993              postac ? "" : " but does no damage",
4994              attack_strength_punctuation(final).c_str());
4995 
4996     }
4997     else if (heard && !hit_noise_msg.empty())
4998         mprf(MSGCH_SOUND, "%s", hit_noise_msg.c_str());
4999     // The player might hear something, if _they_ fired a missile
5000     // (not magic beam).
5001     else if (!silenced(you.pos()) && flavour == BEAM_MISSILE
5002              && YOU_KILL(thrower))
5003     {
5004         mprf(MSGCH_SOUND, "The %s hits something.", name.c_str());
5005     }
5006 
5007     // Spell vampirism
5008     if (agent() && agent()->is_player() && is_player_book_spell(origin_spell))
5009         majin_bo_vampirism(*mon, min(final, mon->stat_hp()));
5010 
5011     // Apply flavoured specials.
5012     mons_adjust_flavoured(mon, *this, postac, true);
5013 
5014     // mons_adjust_flavoured may kill the monster directly.
5015     if (mon->alive())
5016     {
5017         // If the beam is an actual missile or of the MMISSILE type
5018         // (Earth magic) we might bleed on the floor.
5019         if (!engulfs
5020             && (flavour == BEAM_MISSILE || flavour == BEAM_MMISSILE)
5021             && !mon->is_summoned())
5022         {
5023             // Using raw_damage instead of the flavoured one!
5024             // assumes DVORP_PIERCING, factor: 0.5
5025             const int blood = min(postac/2, mon->hit_points);
5026             bleed_onto_floor(mon->pos(), mon->type, blood, true);
5027         }
5028         // Now hurt monster.
5029         mon->hurt(agent(), final, flavour, KILLED_BY_BEAM, "", "", false);
5030     }
5031 
5032     if (mon->alive())
5033         monster_post_hit(mon, final);
5034     // The monster (e.g. a spectral weapon) might have self-destructed in its
5035     // behaviour_event called from mon->hurt() above. If that happened, it
5036     // will have been cleaned up already (and is therefore invalid now).
5037     else if (!invalid_monster(mon))
5038     {
5039         // Preserve name of the source monster if it winds up killing
5040         // itself.
5041         if (mon->mid == source_id && source_name.empty())
5042             source_name = mon->name(DESC_A, true);
5043 
5044         int kindex = actor_to_death_source(agent());
5045         if (origin_spell == SPELL_GLACIATE
5046             && !mon->is_insubstantial()
5047             && x_chance_in_y(3, 5))
5048         {
5049             // Includes monster_die as part of converting to block of ice.
5050             glaciate_freeze(mon, thrower, kindex);
5051         }
5052         // Prevent spore explosions killing plants from being registered
5053         // as a Fedhas misconduct. Deaths can trigger the ally dying or
5054         // plant dying conducts, but spore explosions shouldn't count
5055         // for either of those.
5056         //
5057         // FIXME: Should be a better way of doing this. For now, we are
5058         // just falsifying the death report... -cao
5059         else if (flavour == BEAM_SPORE && god_protects(mon) && fedhas_protects(mon))
5060         {
5061             if (mon->attitude == ATT_FRIENDLY)
5062                 mon->attitude = ATT_HOSTILE;
5063             monster_die(*mon, KILL_MON, kindex);
5064         }
5065         else
5066         {
5067             killer_type ref_killer = thrower;
5068             if (!YOU_KILL(thrower) && reflector == MID_PLAYER)
5069             {
5070                 ref_killer = KILL_YOU_MISSILE;
5071                 kindex = YOU_FAULTLESS;
5072             }
5073             monster_die(*mon, ref_killer, kindex);
5074         }
5075     }
5076 
5077     extra_range_used += range_used_on_hit();
5078 }
5079 
ignores_monster(const monster * mon) const5080 bool bolt::ignores_monster(const monster* mon) const
5081 {
5082     // Digging doesn't affect monsters (should it harm earth elementals?).
5083     if (flavour == BEAM_DIGGING)
5084         return true;
5085 
5086     // The targeters might call us with nullptr in the event of a remembered
5087     // monster that is no longer there. Treat it as opaque.
5088     if (!mon)
5089         return false;
5090 
5091     // All kinds of beams go past orbs of destruction and friendly
5092     // battlespheres.
5093     if (always_shoot_through_monster(agent(), *mon))
5094         return true;
5095 
5096     // Missiles go past bushes and briar patches, unless aimed directly at them
5097     if (bush_immune(*mon))
5098         return true;
5099 
5100     if (shoot_through_monster(*this, mon))
5101         return true;
5102 
5103     // Fire storm creates these, so we'll avoid affecting them.
5104     if (origin_spell == SPELL_FIRE_STORM && mon->type == MONS_FIRE_VORTEX)
5105         return true;
5106 
5107     // Don't blow up blocks of ice with the spell that creates them.
5108     if (origin_spell == SPELL_GLACIATE && mon->type == MONS_BLOCK_OF_ICE)
5109         return true;
5110 
5111     if (flavour == BEAM_WATER && mon->type == MONS_WATER_ELEMENTAL)
5112         return true;
5113 
5114     return false;
5115 }
5116 
has_saving_throw() const5117 bool bolt::has_saving_throw() const
5118 {
5119     switch (flavour)
5120     {
5121     case BEAM_HASTE:
5122     case BEAM_MIGHT:
5123     case BEAM_BERSERK:
5124     case BEAM_HEALING:
5125     case BEAM_INVISIBILITY:
5126     case BEAM_DISPEL_UNDEAD:
5127     case BEAM_BLINK_CLOSE:
5128     case BEAM_BLINK:
5129     case BEAM_BECKONING:
5130     case BEAM_MALIGN_OFFERING:
5131     case BEAM_AGILITY:
5132     case BEAM_RESISTANCE:
5133     case BEAM_MALMUTATE:
5134     case BEAM_SAP_MAGIC:
5135     case BEAM_UNRAVELLING:
5136     case BEAM_UNRAVELLED_MAGIC:
5137     case BEAM_INFESTATION:
5138     case BEAM_IRRESISTIBLE_CONFUSION:
5139     case BEAM_VILE_CLUTCH:
5140     case BEAM_VAMPIRIC_DRAINING:
5141     case BEAM_CONCENTRATE_VENOM:
5142         return false;
5143     case BEAM_POLYMORPH:
5144         return !aimed_at_feet; // Self-poly doesn't check will
5145     case BEAM_VULNERABILITY:
5146         return !one_chance_in(3);  // Ignores will 1/3 of the time
5147     case BEAM_PARALYSIS:        // Giant eyeball paralysis is irresistible
5148         return !(agent() && agent()->type == MONS_FLOATING_EYE);
5149     default:
5150         return true;
5151     }
5152 }
5153 
ench_flavour_affects_monster(beam_type flavour,const monster * mon,bool intrinsic_only)5154 bool ench_flavour_affects_monster(beam_type flavour, const monster* mon,
5155                                   bool intrinsic_only)
5156 {
5157     bool rc = true;
5158     switch (flavour)
5159     {
5160     case BEAM_MALMUTATE:
5161     case BEAM_UNRAVELLED_MAGIC:
5162         rc = mon->can_mutate();
5163         break;
5164 
5165     case BEAM_SLOW:
5166     case BEAM_HASTE:
5167     case BEAM_PARALYSIS:
5168         rc = !mon->stasis();
5169         break;
5170 
5171     case BEAM_POLYMORPH:
5172         rc = mon->can_polymorph();
5173         break;
5174 
5175     case BEAM_DISPEL_UNDEAD:
5176         rc = bool(mon->holiness() & MH_UNDEAD);
5177         break;
5178 
5179     case BEAM_PAIN:
5180         rc = mon->res_negative_energy(intrinsic_only) < 3;
5181         break;
5182 
5183     case BEAM_AGONY:
5184         rc = !mon->res_torment();
5185         break;
5186 
5187     case BEAM_HIBERNATION:
5188         rc = mon->can_hibernate(false, intrinsic_only);
5189         break;
5190 
5191     case BEAM_PORKALATOR:
5192         rc = (mon->holiness() & MH_DEMONIC && mon->type != MONS_HELL_HOG)
5193               || (mon->holiness() & MH_NATURAL && mon->type != MONS_HOG)
5194               || (mon->holiness() & MH_HOLY && mon->type != MONS_HOLY_SWINE);
5195         break;
5196 
5197     case BEAM_SENTINEL_MARK:
5198         rc = false;
5199         break;
5200 
5201     case BEAM_MALIGN_OFFERING:
5202         rc = (mon->res_negative_energy(intrinsic_only) < 3);
5203         break;
5204 
5205     case BEAM_VAMPIRIC_DRAINING:
5206         rc = actor_is_susceptible_to_vampirism(*mon);
5207         break;
5208 
5209     case BEAM_VIRULENCE:
5210         rc = (mon->res_poison() < 3);
5211         break;
5212 
5213     case BEAM_DRAIN_MAGIC:
5214         rc = mon->antimagic_susceptible();
5215         break;
5216 
5217     case BEAM_INNER_FLAME:
5218         rc = !mon->has_ench(ENCH_INNER_FLAME);
5219         break;
5220 
5221     case BEAM_PETRIFY:
5222         rc = !mon->res_petrify();
5223         break;
5224 
5225     case BEAM_INFESTATION:
5226         rc = mons_gives_xp(*mon, you) && !mon->has_ench(ENCH_INFESTATION);
5227         break;
5228 
5229     case BEAM_VILE_CLUTCH:
5230         rc = !mons_aligned(&you, mon) && you.can_constrict(mon, false);
5231         break;
5232 
5233     // These are special allies whose loyalty can't be so easily bent
5234     case BEAM_CHARM:
5235         rc = !(god_protects(mon)
5236                || testbits(mon->flags, MF_DEMONIC_GUARDIAN));
5237         break;
5238 
5239     case BEAM_MINDBURST:
5240         rc = mons_intel(*mon) > I_BRAINLESS;
5241         break;
5242 
5243     default:
5244         break;
5245     }
5246 
5247     return rc;
5248 }
5249 
enchant_actor_with_flavour(actor * victim,const actor * foe,beam_type flavour,int powc)5250 bool enchant_actor_with_flavour(actor* victim, const actor *foe,
5251                                 beam_type flavour, int powc)
5252 {
5253     bolt dummy;
5254     dummy.flavour = flavour;
5255     dummy.ench_power = powc;
5256     dummy.set_agent(foe);
5257     dummy.animate = false;
5258     if (victim->is_player())
5259         dummy.affect_player_enchantment(false);
5260     else
5261         dummy.apply_enchantment_to_monster(victim->as_monster());
5262     return dummy.obvious_effect;
5263 }
5264 
enchant_monster_invisible(monster * mon,const string & how)5265 bool enchant_monster_invisible(monster* mon, const string &how)
5266 {
5267     // Store the monster name before it becomes an "it". - bwr
5268     const string monster_name = mon->name(DESC_THE);
5269     const bool could_see = you.can_see(*mon);
5270 
5271     if (mon->has_ench(ENCH_INVIS) || !mon->add_ench(ENCH_INVIS))
5272         return false;
5273 
5274     if (could_see)
5275     {
5276         const bool is_visible = mon->visible_to(&you);
5277 
5278         // Can't use simple_monster_message(*) here, since it checks
5279         // for visibility of the monster (and it's now invisible).
5280         // - bwr
5281         mprf("%s %s%s",
5282              monster_name.c_str(),
5283              how.c_str(),
5284              is_visible ? " for a moment."
5285                         : "!");
5286 
5287         if (!is_visible && !mons_is_safe(mon))
5288             autotoggle_autopickup(true);
5289     }
5290 
5291     return true;
5292 }
5293 
try_enchant_monster(monster * mon,int & res_margin)5294 mon_resist_type bolt::try_enchant_monster(monster* mon, int &res_margin)
5295 {
5296     // Early out if the enchantment is meaningless.
5297     if (!ench_flavour_affects_monster(flavour, mon))
5298         return MON_UNAFFECTED;
5299 
5300     // Check willpower.
5301     if (has_saving_throw())
5302     {
5303         if (mons_invuln_will(*mon))
5304             return MON_UNAFFECTED;
5305 
5306         // (Very) ugly things and shapeshifters will never resist
5307         // polymorph beams.
5308         if (flavour == BEAM_POLYMORPH
5309             && (mon->type == MONS_UGLY_THING
5310                 || mon->type == MONS_VERY_UGLY_THING
5311                 || mon->is_shapeshifter()))
5312         {
5313             ;
5314         }
5315         // Chaos effects don't get a resistance check to match melee chaos.
5316         else if (real_flavour != BEAM_CHAOS)
5317         {
5318             if (mon->check_willpower(ench_power) > 0)
5319             {
5320                 // Note only actually used by messages in this case.
5321                 res_margin = mon->willpower() - ench_power_stepdown(ench_power);
5322                 return MON_RESIST;
5323             }
5324         }
5325     }
5326 
5327     return apply_enchantment_to_monster(mon);
5328 }
5329 
apply_enchantment_to_monster(monster * mon)5330 mon_resist_type bolt::apply_enchantment_to_monster(monster* mon)
5331 {
5332     // Gigantic-switches-R-Us
5333     switch (flavour)
5334     {
5335     case BEAM_TELEPORT:
5336         if (mon->no_tele())
5337             return MON_UNAFFECTED;
5338         if (mon->observable())
5339             obvious_effect = true;
5340         monster_teleport(mon, false);
5341         return MON_AFFECTED;
5342 
5343     case BEAM_BLINK:
5344         if (mon->no_tele())
5345             return MON_UNAFFECTED;
5346         if (mon->observable())
5347             obvious_effect = true;
5348         monster_blink(mon);
5349         return MON_AFFECTED;
5350 
5351     case BEAM_BLINK_CLOSE:
5352         if (mon->no_tele())
5353             return MON_UNAFFECTED;
5354         if (mon->observable())
5355             obvious_effect = true;
5356         blink_other_close(mon, source);
5357         return MON_AFFECTED;
5358 
5359     case BEAM_BECKONING:
5360         obvious_effect = beckon(*mon, *this);
5361         return obvious_effect ? MON_AFFECTED : MON_OTHER; // ?
5362 
5363     case BEAM_POLYMORPH:
5364         if (mon->polymorph(ench_power))
5365             obvious_effect = true;
5366         if (YOU_KILL(thrower))
5367         {
5368             const int level = 2 + random2(3);
5369             did_god_conduct(DID_DELIBERATE_MUTATING, level, god_cares());
5370         }
5371         return MON_AFFECTED;
5372 
5373     case BEAM_MALMUTATE:
5374     case BEAM_UNRAVELLED_MAGIC:
5375         if (mon->malmutate("")) // exact source doesn't matter
5376             obvious_effect = true;
5377         if (YOU_KILL(thrower))
5378         {
5379             const int level = 2 + random2(3);
5380             did_god_conduct(DID_DELIBERATE_MUTATING, level, god_cares());
5381         }
5382         return MON_AFFECTED;
5383 
5384     case BEAM_BANISH:
5385         mon->banish(agent());
5386         obvious_effect = true;
5387         return MON_AFFECTED;
5388 
5389     case BEAM_DISPEL_UNDEAD:
5390     {
5391         const int dam = damage.roll();
5392         if (you.see_cell(mon->pos()))
5393         {
5394             mprf("%s is dispelled%s",
5395                  mon->name(DESC_THE).c_str(),
5396                  attack_strength_punctuation(dam).c_str());
5397             obvious_effect = true;
5398         }
5399         mon->hurt(agent(), dam);
5400         return MON_AFFECTED;
5401     }
5402 
5403     case BEAM_VAMPIRIC_DRAINING:
5404     {
5405         const int dam = resist_adjust_damage(mon, flavour, damage.roll());
5406         if (dam && actor_is_susceptible_to_vampirism(*mon))
5407         {
5408             _vampiric_draining_effect(*mon, *agent(), dam);
5409             obvious_effect = true;
5410             return MON_AFFECTED;
5411         }
5412         else
5413             return MON_UNAFFECTED;
5414     }
5415 
5416     case BEAM_PAIN:
5417     {
5418         const int dam = resist_adjust_damage(mon, flavour, damage.roll());
5419         if (dam)
5420         {
5421             if (you.see_cell(mon->pos()))
5422             {
5423                 mprf("%s writhes in agony%s",
5424                      mon->name(DESC_THE).c_str(),
5425                      attack_strength_punctuation(dam).c_str());
5426                 obvious_effect = true;
5427             }
5428             mon->hurt(agent(), dam, flavour);
5429             return MON_AFFECTED;
5430         }
5431         return MON_UNAFFECTED;
5432     }
5433 
5434     case BEAM_AGONY:
5435         torment_cell(mon->pos(), agent(), TORMENT_AGONY);
5436         obvious_effect = true;
5437         return MON_AFFECTED;
5438 
5439     case BEAM_MINDBURST:
5440     {
5441         const int dam = damage.roll();
5442         if (you.see_cell(mon->pos()))
5443         {
5444             const bool plural = mon->heads() > 1;
5445             mprf("%s mind%s blasted%s",
5446                  mon->name(DESC_ITS).c_str(),
5447                  plural ? "s are" : " is",
5448                  attack_strength_punctuation(dam).c_str());
5449             obvious_effect = true;
5450         }
5451         mon->hurt(agent(), dam, flavour);
5452         return MON_AFFECTED;
5453     }
5454 
5455     case BEAM_HIBERNATION:
5456         if (mon->can_hibernate())
5457         {
5458             if (simple_monster_message(*mon, " looks drowsy..."))
5459                 obvious_effect = true;
5460             mon->put_to_sleep(agent(), ench_power, true);
5461             return MON_AFFECTED;
5462         }
5463         return MON_UNAFFECTED;
5464 
5465     case BEAM_CORONA:
5466         if (backlight_monster(mon))
5467         {
5468             obvious_effect = true;
5469             return MON_AFFECTED;
5470         }
5471         return MON_UNAFFECTED;
5472 
5473     case BEAM_SLOW:
5474         obvious_effect = do_slow_monster(*mon, agent(),
5475                                          ench_power * BASELINE_DELAY);
5476         return MON_AFFECTED;
5477 
5478     case BEAM_HASTE:
5479         if (YOU_KILL(thrower))
5480             did_god_conduct(DID_HASTY, 6, god_cares());
5481 
5482         if (mon->stasis())
5483             return MON_AFFECTED;
5484 
5485         if (!mon->has_ench(ENCH_HASTE)
5486             && !mon->is_stationary()
5487             && mon->add_ench(ENCH_HASTE))
5488         {
5489             if (!mons_is_immotile(*mon)
5490                 && simple_monster_message(*mon, " seems to speed up."))
5491             {
5492                 obvious_effect = true;
5493             }
5494         }
5495         return MON_AFFECTED;
5496 
5497     case BEAM_MIGHT:
5498         if (!mon->has_ench(ENCH_MIGHT)
5499             && !mon->is_stationary()
5500             && mon->add_ench(ENCH_MIGHT))
5501         {
5502             if (simple_monster_message(*mon, " seems to grow stronger."))
5503                 obvious_effect = true;
5504         }
5505         return MON_AFFECTED;
5506 
5507     case BEAM_BERSERK:
5508         if (!mon->berserk_or_insane())
5509         {
5510             // currently from potion, hence voluntary
5511             mon->go_berserk(true);
5512             // can't return this from go_berserk, unfortunately
5513             obvious_effect = you.can_see(*mon);
5514         }
5515         return MON_AFFECTED;
5516 
5517     case BEAM_HEALING:
5518         // No KILL_YOU_CONF, or we get "You heal ..."
5519         if (thrower == KILL_YOU || thrower == KILL_YOU_MISSILE)
5520         {
5521             const int pow = min(50, 3 + damage.roll());
5522             const int amount = pow + roll_dice(2, pow) - 2;
5523             if (heal_monster(*mon, amount))
5524                 obvious_effect = true;
5525             msg_generated = true; // to avoid duplicate "nothing happens"
5526         }
5527         else if (mon->heal(3 + damage.roll()))
5528         {
5529             if (mon->hit_points == mon->max_hit_points)
5530             {
5531                 if (simple_monster_message(*mon, "'s wounds heal themselves!"))
5532                     obvious_effect = true;
5533             }
5534             else if (simple_monster_message(*mon, " is healed somewhat."))
5535                 obvious_effect = true;
5536         }
5537         return MON_AFFECTED;
5538 
5539     case BEAM_PARALYSIS:
5540         apply_bolt_paralysis(mon);
5541         return MON_AFFECTED;
5542 
5543     case BEAM_PETRIFY:
5544         if (mon->res_petrify())
5545             return MON_UNAFFECTED;
5546 
5547         apply_bolt_petrify(mon);
5548         return MON_AFFECTED;
5549 
5550     case BEAM_SPORE:
5551     case BEAM_CONFUSION:
5552     case BEAM_IRRESISTIBLE_CONFUSION:
5553         if (mon->clarity())
5554         {
5555             if (you.can_see(*mon))
5556                 obvious_effect = true;
5557             return MON_AFFECTED;
5558         }
5559         {
5560             // irresistible confusion has a shorter duration and is weaker
5561             // against strong monsters
5562             int dur = ench_power;
5563             if (flavour == BEAM_IRRESISTIBLE_CONFUSION)
5564                 dur = max(10, dur - mon->get_hit_dice());
5565             else
5566                 dur = _ench_pow_to_dur(dur);
5567 
5568             if (mon->add_ench(mon_enchant(ENCH_CONFUSION, 0, agent(), dur)))
5569             {
5570                 // FIXME: Put in an exception for things you won't notice
5571                 // becoming confused.
5572                 if (simple_monster_message(*mon, " appears confused."))
5573                     obvious_effect = true;
5574             }
5575         }
5576         return MON_AFFECTED;
5577 
5578     case BEAM_SLEEP:
5579         if (mons_just_slept(*mon))
5580             return MON_UNAFFECTED;
5581 
5582         mon->put_to_sleep(agent(), ench_power);
5583         if (simple_monster_message(*mon, " falls asleep!"))
5584             obvious_effect = true;
5585 
5586         return MON_AFFECTED;
5587 
5588     case BEAM_INVISIBILITY:
5589     {
5590         if (enchant_monster_invisible(mon, "flickers and vanishes"))
5591             obvious_effect = true;
5592 
5593         return MON_AFFECTED;
5594     }
5595 
5596     case BEAM_CHARM:
5597         if (agent() && agent()->is_monster())
5598         {
5599             enchant_type good = (agent()->wont_attack()) ? ENCH_CHARM
5600                                                          : ENCH_HEXED;
5601             enchant_type bad  = (agent()->wont_attack()) ? ENCH_HEXED
5602                                                          : ENCH_CHARM;
5603 
5604             const bool could_see = you.can_see(*mon);
5605             if (mon->has_ench(bad))
5606             {
5607                 obvious_effect = mon->del_ench(bad);
5608                 return MON_AFFECTED;
5609             }
5610             if (simple_monster_message(*mon, " is charmed!"))
5611                 obvious_effect = true;
5612             mon->add_ench(mon_enchant(good, 0, agent()));
5613             if (!obvious_effect && could_see && !you.can_see(*mon))
5614                 obvious_effect = true;
5615             return MON_AFFECTED;
5616         }
5617 
5618         // Being a puppet on magic strings is a nasty thing.
5619         // Mindless creatures shouldn't probably mind, but because of complex
5620         // behaviour of enslaved neutrals, let's disallow that for now.
5621         mon->attitude = ATT_HOSTILE;
5622 
5623         // XXX: Another hackish thing for Pikel's band neutrality.
5624         if (mons_is_mons_class(mon, MONS_PIKEL))
5625             pikel_band_neutralise();
5626 
5627         if (simple_monster_message(*mon, " is charmed."))
5628             obvious_effect = true;
5629         mon->add_ench(ENCH_CHARM);
5630         if (you.can_see(*mon))
5631             obvious_effect = true;
5632         return MON_AFFECTED;
5633 
5634     case BEAM_PORKALATOR:
5635     {
5636         // Monsters which use the ghost structure can't be properly
5637         // restored from hog form.
5638         if (mons_is_ghost_demon(mon->type))
5639             return MON_UNAFFECTED;
5640 
5641         monster orig_mon(*mon);
5642         if (monster_polymorph(mon, mon->holiness() & MH_DEMONIC ?
5643                       MONS_HELL_HOG : mon->holiness() & MH_HOLY ?
5644                       MONS_HOLY_SWINE : MONS_HOG))
5645         {
5646             obvious_effect = true;
5647 
5648             // Don't restore items to monster if it reverts.
5649             orig_mon.inv = mon->inv;
5650 
5651             // monsters can't cast spells in hog form either -doy
5652             mon->spells.clear();
5653 
5654             // For monster reverting to original form.
5655             mon->props[ORIG_MONSTER_KEY] = orig_mon;
5656         }
5657 
5658         return MON_AFFECTED;
5659     }
5660 
5661     case BEAM_INNER_FLAME:
5662         if (!mon->has_ench(ENCH_INNER_FLAME)
5663             && mon->add_ench(mon_enchant(ENCH_INNER_FLAME, 0, agent())))
5664         {
5665             if (simple_monster_message(*mon,
5666                                        (mon->body_size(PSIZE_BODY) > SIZE_BIG)
5667                                         ? " is filled with an intense inner flame!"
5668                                         : " is filled with an inner flame."))
5669             {
5670                 obvious_effect = true;
5671             }
5672         }
5673         return MON_AFFECTED;
5674 
5675     case BEAM_DIMENSION_ANCHOR:
5676         if (!mon->has_ench(ENCH_DIMENSION_ANCHOR)
5677             && mon->add_ench(mon_enchant(ENCH_DIMENSION_ANCHOR, 0, agent(),
5678                                          random_range(20, 30) * BASELINE_DELAY)))
5679         {
5680             if (simple_monster_message(*mon, " is firmly anchored in space."))
5681                 obvious_effect = true;
5682         }
5683         return MON_AFFECTED;
5684 
5685     case BEAM_VULNERABILITY:
5686         if (!mon->has_ench(ENCH_LOWERED_WL)
5687             && mon->add_ench(mon_enchant(ENCH_LOWERED_WL, 0, agent(),
5688                                          random_range(20, 30) * BASELINE_DELAY)))
5689         {
5690             if (you.can_see(*mon))
5691             {
5692                 mprf("%s willpower is stripped away.",
5693                      mon->name(DESC_ITS).c_str());
5694                 obvious_effect = true;
5695             }
5696         }
5697         return MON_AFFECTED;
5698 
5699     case BEAM_MALIGN_OFFERING:
5700     {
5701         const int dam = resist_adjust_damage(mon, flavour, damage.roll());
5702         if (dam)
5703         {
5704             _malign_offering_effect(mon, agent(), dam);
5705             obvious_effect = true;
5706             return MON_AFFECTED;
5707         }
5708         else
5709         {
5710             simple_monster_message(*mon, " is unaffected.");
5711             return MON_UNAFFECTED;
5712         }
5713     }
5714 
5715     case BEAM_VIRULENCE:
5716         if (!mon->has_ench(ENCH_POISON_VULN)
5717             && mon->add_ench(mon_enchant(ENCH_POISON_VULN, 0, agent(),
5718                                          random_range(20, 30) * BASELINE_DELAY)))
5719         {
5720             if (simple_monster_message(*mon,
5721                                        " grows more vulnerable to poison."))
5722             {
5723                 obvious_effect = true;
5724             }
5725         }
5726         return MON_AFFECTED;
5727 
5728     case BEAM_AGILITY:
5729         if (!mon->has_ench(ENCH_AGILE)
5730             && !mon->is_stationary()
5731             && mon->add_ench(ENCH_AGILE))
5732         {
5733             if (simple_monster_message(*mon, " suddenly seems more agile."))
5734                 obvious_effect = true;
5735         }
5736         return MON_AFFECTED;
5737 
5738     case BEAM_SAP_MAGIC:
5739         if (!SAP_MAGIC_CHANCE())
5740         {
5741             if (you.can_see(*mon))
5742                 canned_msg(MSG_NOTHING_HAPPENS);
5743             break;
5744         }
5745         if (!mon->has_ench(ENCH_SAP_MAGIC)
5746             && mon->add_ench(mon_enchant(ENCH_SAP_MAGIC, 0, agent())))
5747         {
5748             if (you.can_see(*mon))
5749             {
5750                 mprf("%s seems less certain of %s magic.",
5751                      mon->name(DESC_THE).c_str(), mon->pronoun(PRONOUN_POSSESSIVE).c_str());
5752                 obvious_effect = true;
5753             }
5754         }
5755         return MON_AFFECTED;
5756 
5757     case BEAM_DRAIN_MAGIC:
5758     {
5759         if (!mon->antimagic_susceptible())
5760             break;
5761 
5762         const int dur =
5763             random2(div_rand_round(ench_power, mon->get_hit_dice()) + 1)
5764                     * BASELINE_DELAY;
5765         mon->add_ench(mon_enchant(ENCH_ANTIMAGIC, 0,
5766                                   agent(), // doesn't matter
5767                                   dur));
5768         if (you.can_see(*mon))
5769         {
5770             mprf("%s magic leaks into the air.",
5771                  apostrophise(mon->name(DESC_THE)).c_str());
5772         }
5773 
5774         if (agent() && agent()->type == MONS_GHOST_MOTH)
5775             agent()->heal(dur / BASELINE_DELAY);
5776         obvious_effect = true;
5777         break;
5778     }
5779 
5780     case BEAM_TUKIMAS_DANCE:
5781         cast_tukimas_dance(ench_power, mon);
5782         obvious_effect = true;
5783         break;
5784 
5785     case BEAM_RESISTANCE:
5786         if (!mon->has_ench(ENCH_RESISTANCE)
5787             && mon->add_ench(ENCH_RESISTANCE))
5788         {
5789             if (simple_monster_message(*mon, " suddenly seems more resistant."))
5790                 obvious_effect = true;
5791         }
5792         return MON_AFFECTED;
5793 
5794     case BEAM_UNRAVELLING:
5795         if (!monster_is_debuffable(*mon))
5796             return MON_UNAFFECTED;
5797 
5798         debuff_monster(*mon);
5799         _unravelling_explode(*this);
5800         return MON_AFFECTED;
5801 
5802     case BEAM_INFESTATION:
5803     {
5804         const int dur = (5 + random2avg(ench_power / 2, 2)) * BASELINE_DELAY;
5805         mon->add_ench(mon_enchant(ENCH_INFESTATION, 0, &you, dur));
5806         if (simple_monster_message(*mon, " is infested!"))
5807             obvious_effect = true;
5808         return MON_AFFECTED;
5809     }
5810 
5811     case BEAM_VILE_CLUTCH:
5812     {
5813         const int dur = (4 + random2avg(div_rand_round(ench_power, 10), 2))
5814             * BASELINE_DELAY;
5815         dprf("Vile clutch duration: %d", dur);
5816         mon->add_ench(mon_enchant(ENCH_VILE_CLUTCH, 0, &you, dur));
5817         obvious_effect = true;
5818         return MON_AFFECTED;
5819     }
5820 
5821     case BEAM_CONCENTRATE_VENOM:
5822         dprf("trying to ench");
5823         if (!mon->has_ench(ENCH_CONCENTRATE_VENOM)
5824             && mon->add_ench(ENCH_CONCENTRATE_VENOM))
5825         {
5826             if (simple_monster_message(*mon, " seems to grow toxic."))
5827                 obvious_effect = true;
5828         }
5829         return MON_AFFECTED;
5830 
5831     default:
5832         break;
5833     }
5834 
5835     return MON_AFFECTED;
5836 }
5837 
5838 // Extra range used on hit.
range_used_on_hit() const5839 int bolt::range_used_on_hit() const
5840 {
5841     if (is_tracer && source_id == MID_PLAYER && hit < AUTOMATIC_HIT)
5842         return 0;
5843 
5844     // Non-beams can only affect one thing (player/monster).
5845     if (!pierce)
5846         return BEAM_STOP;
5847     // These beams fully penetrate regardless of anything else.
5848     if (flavour == BEAM_DAMNATION
5849         || flavour == BEAM_DIGGING
5850         || flavour == BEAM_VILE_CLUTCH)
5851     {
5852         return 0;
5853     }
5854     // explosions/clouds and enchants that aren't Line Pass stop.
5855     if (is_enchantment() && name != "line pass"
5856         || is_explosion
5857         || is_big_cloud())
5858     {
5859         return BEAM_STOP;
5860     }
5861     // Lightning that isn't an explosion goes through things.
5862     return 0;
5863 }
5864 
5865 // Information for how various explosions look & sound.
5866 struct explosion_sfx
5867 {
5868     // A message printed when the player sees the explosion.
5869     const char *seeMsg;
5870     // What the player hears when the explosion goes off unseen.
5871     const char *sound;
5872 };
5873 
5874 // A map from origin_spells to special explosion info for each.
5875 const map<spell_type, explosion_sfx> spell_explosions = {
5876     { SPELL_HURL_DAMNATION, {
5877         "The sphere of damnation explodes!",
5878         "the wailing of the damned",
5879     } },
5880     { SPELL_CALL_DOWN_DAMNATION, {
5881         "The sphere of damnation explodes!",
5882         "the wailing of the damned",
5883     } },
5884     { SPELL_FIREBALL, {
5885         "The fireball explodes!",
5886         "an explosion",
5887     } },
5888     { SPELL_ORB_OF_ELECTRICITY, {
5889         "The orb of electricity explodes!",
5890         "a clap of thunder",
5891     } },
5892     { SPELL_FIRE_STORM, {
5893         "A raging storm of fire appears!",
5894         "a raging storm",
5895     } },
5896     { SPELL_MEPHITIC_CLOUD, {
5897         "The ball explodes into a vile cloud!",
5898         "a loud \'bang\'",
5899     } },
5900     { SPELL_GHOSTLY_FIREBALL, {
5901         "The ghostly flame explodes!",
5902         "the shriek of haunting fire",
5903     } },
5904     { SPELL_VIOLENT_UNRAVELLING, {
5905         "The enchantments explode!",
5906         "a sharp crackling", // radiation = geiger counter
5907     } },
5908     { SPELL_ICEBLAST, {
5909         "The mass of ice explodes!",
5910         "an explosion",
5911     } },
5912     { SPELL_GHOSTLY_SACRIFICE, {
5913         "The ghostly flame explodes!",
5914         "the shriek of haunting fire",
5915     } },
5916 };
5917 
5918 // Takes a bolt and refines it for use in the explosion function.
5919 // Explosions which do not follow from beams bypass this function.
refine_for_explosion()5920 void bolt::refine_for_explosion()
5921 {
5922     ASSERT(!special_explosion);
5923 
5924     string seeMsg;
5925     string hearMsg;
5926 
5927     if (ex_size == 0)
5928         ex_size = 1;
5929     glyph   = dchar_glyph(DCHAR_FIRED_BURST);
5930 
5931     // Assume that the player can see/hear the explosion, or
5932     // gets burned by it anyway.  :)
5933     msg_generated = true;
5934 
5935     if (item != nullptr)
5936     {
5937         seeMsg  = "The " + item->name(DESC_PLAIN, false, false, false)
5938                   + " explodes!";
5939         hearMsg = "You hear an explosion!";
5940     }
5941     else
5942     {
5943         const explosion_sfx *explosion = map_find(spell_explosions,
5944                                                   origin_spell);
5945         if (explosion)
5946         {
5947             seeMsg = explosion->seeMsg;
5948             hearMsg = make_stringf("You hear %s!", explosion->sound);
5949         }
5950         else
5951         {
5952             seeMsg  = "The beam explodes into a cloud of software bugs!";
5953             hearMsg = "You hear the sound of one hand!";
5954         }
5955     }
5956 
5957     if (origin_spell == SPELL_ORB_OF_ELECTRICITY)
5958     {
5959         colour     = LIGHTCYAN;
5960         ex_size    = 2;
5961     }
5962 
5963     if (!is_tracer && !seeMsg.empty() && !hearMsg.empty())
5964     {
5965         heard = player_can_hear(target);
5966         // Check for see/hear/no msg.
5967         if (you.see_cell(target) || target == you.pos())
5968             mpr(seeMsg);
5969         else
5970         {
5971             if (!heard)
5972                 msg_generated = false;
5973             else
5974                 mprf(MSGCH_SOUND, "%s", hearMsg.c_str());
5975         }
5976     }
5977 }
5978 
5979 typedef vector< vector<coord_def> > sweep_type;
5980 
_radial_sweep(int r)5981 static sweep_type _radial_sweep(int r)
5982 {
5983     sweep_type result;
5984 
5985     // Center first.
5986     result.emplace_back(1, coord_def(0,0));
5987 
5988     for (int rad = 1; rad <= r; ++rad)
5989     {
5990         sweep_type::value_type work;
5991 
5992         for (int d = -rad; d <= rad; ++d)
5993         {
5994             // Don't put the corners in twice!
5995             if (d != rad && d != -rad)
5996             {
5997                 work.emplace_back(-rad, d);
5998                 work.emplace_back(+rad, d);
5999             }
6000 
6001             work.emplace_back(d, -rad);
6002             work.emplace_back(d, +rad);
6003         }
6004         result.push_back(work);
6005     }
6006     return result;
6007 }
6008 
6009 /** How much noise does an explosion this big make?
6010  *
6011  *  @param the size of the explosion (radius, not diamater)
6012  *  @returns how much noise it would make.
6013  */
explosion_noise(int rad)6014 int explosion_noise(int rad)
6015 {
6016     return 10 + rad * 5;
6017 }
6018 
6019 #define MAX_EXPLOSION_RADIUS 9
6020 // Returns true if we saw something happening.
explode(bool show_more,bool hole_in_the_middle)6021 bool bolt::explode(bool show_more, bool hole_in_the_middle)
6022 {
6023     ASSERT(!special_explosion);
6024     ASSERT(!in_explosion_phase);
6025     ASSERT(ex_size >= 0);
6026 
6027     // explode() can be called manually without setting real_flavour.
6028     // FIXME: The entire flavour/real_flavour thing needs some
6029     // rewriting!
6030     if (real_flavour == BEAM_CHAOS
6031         || real_flavour == BEAM_RANDOM
6032         || real_flavour == BEAM_CRYSTAL)
6033     {
6034         flavour = real_flavour;
6035     }
6036     else
6037         real_flavour = flavour;
6038 
6039     const int r = min(ex_size, MAX_EXPLOSION_RADIUS);
6040     in_explosion_phase = true;
6041     // being hit by bounces doesn't exempt you from the explosion (not that it
6042     // currently ever matters)
6043     hit_count.clear();
6044 
6045     if (is_sanctuary(pos()) && flavour != BEAM_VISUAL)
6046     {
6047         if (!is_tracer && you.see_cell(pos()) && !name.empty())
6048         {
6049             mprf(MSGCH_GOD, "By Zin's power, the %s is contained.",
6050                  name.c_str());
6051             return true;
6052         }
6053         return false;
6054     }
6055 
6056 #ifdef DEBUG_DIAGNOSTICS
6057     if (!quiet_debug)
6058     {
6059         dprf(DIAG_BEAM, "explosion at (%d, %d) : g=%d c=%d f=%d hit=%d dam=%dd%d r=%d",
6060              pos().x, pos().y, glyph, colour, flavour, hit, damage.num, damage.size, r);
6061     }
6062 #endif
6063 
6064     if (!is_tracer && flavour != BEAM_VISUAL)
6065     {
6066         loudness = explosion_noise(r);
6067 
6068         // Not an "explosion", but still a bit noisy at the target location.
6069         if (origin_spell == SPELL_INFESTATION
6070             || origin_spell == SPELL_DAZZLING_FLASH)
6071         {
6072             loudness = spell_effect_noise(origin_spell);
6073         }
6074 
6075         // Make bloated husks quieter, both for balance (they're waking up
6076         // whole levels!) and for theme (it's not a huge fireball, it's a big
6077         // gas leak).
6078         if (name == "blast of putrescent gases")
6079             loudness = loudness * 2 / 3;
6080 
6081         // Lee's Rapid Deconstruction can target the tiles on the map
6082         // boundary.
6083         const coord_def noise_position = clamp_in_bounds(pos());
6084         bool heard_expl = noisy(loudness, noise_position, source_id);
6085 
6086         heard = heard || heard_expl;
6087 
6088         if (heard_expl && !explode_noise_msg.empty() && !you.see_cell(pos()))
6089             mprf(MSGCH_SOUND, "%s", explode_noise_msg.c_str());
6090     }
6091 
6092     // Run DFS to determine which cells are influenced
6093     explosion_map exp_map;
6094     exp_map.init(INT_MAX);
6095     determine_affected_cells(exp_map, coord_def(), 0, r, true, true);
6096 
6097     // We get a bit fancy, drawing all radius 0 effects, then radius
6098     // 1, radius 2, etc. It looks a bit better that way.
6099     const vector< vector<coord_def> > sweep = _radial_sweep(r);
6100     const coord_def centre(9,9);
6101 
6102     // Draw pass.
6103     if (!is_tracer && animate)
6104     {
6105         for (const auto &line : sweep)
6106         {
6107             bool pass_visible = false;
6108             for (const coord_def &delta : line)
6109             {
6110                 if (delta.origin() && hole_in_the_middle)
6111                     continue;
6112 
6113                 if (exp_map(delta + centre) < INT_MAX)
6114                     pass_visible |= explosion_draw_cell(delta + pos());
6115             }
6116             // redraw for an entire explosion block, so redraw_per_cell is not
6117             // relevant
6118             if (pass_visible)
6119             {
6120                 viewwindow(false);
6121                 update_screen();
6122                 scaled_delay(explode_delay);
6123             }
6124         }
6125     }
6126 
6127     // Affect pass.
6128     int cells_seen = 0;
6129     for (const auto &line : sweep)
6130     {
6131         for (const coord_def &delta : line)
6132         {
6133             if (delta.origin() && hole_in_the_middle)
6134                 continue;
6135 
6136             if (exp_map(delta + centre) < INT_MAX)
6137             {
6138                 if (you.see_cell(delta + pos()))
6139                     ++cells_seen;
6140 
6141                 explosion_affect_cell(delta + pos());
6142 
6143                 if (beam_cancelled) // don't spam prompts
6144                     return false;
6145             }
6146         }
6147     }
6148 
6149     // Delay after entire explosion has been drawn.
6150     if (!is_tracer && cells_seen > 0 && show_more && animate)
6151         scaled_delay(explode_delay * 3);
6152 
6153     return cells_seen > 0;
6154 }
6155 
6156 /**
6157  * Draw one tile of an explosion, if that cell is visible.
6158  *
6159  * @param p The cell to draw, in grid coordinates.
6160  * @return True if the cell was actually drawn.
6161  */
explosion_draw_cell(const coord_def & p)6162 bool bolt::explosion_draw_cell(const coord_def& p)
6163 {
6164     if (you.see_cell(p))
6165     {
6166         const coord_def drawpos = grid2view(p);
6167         // bounds check
6168         if (in_los_bounds_v(drawpos))
6169         {
6170 #ifdef USE_TILE
6171             int dist = (p - source).rdist();
6172             tileidx_t tile = tileidx_bolt(*this);
6173             view_add_tile_overlay(p, vary_bolt_tile(tile, dist));
6174 #endif
6175 #ifndef USE_TILE_LOCAL
6176             const unsigned short c = colour == BLACK ?
6177                     random_colour(true) : element_colour(colour, false, p);
6178             view_add_glyph_overlay(p, {dchar_glyph(DCHAR_EXPLOSION), c});
6179 #endif
6180             return true;
6181         }
6182     }
6183     return false;
6184 }
6185 
explosion_affect_cell(const coord_def & p)6186 void bolt::explosion_affect_cell(const coord_def& p)
6187 {
6188     // pos() = target during an explosion, so restore it after affecting
6189     // the cell.
6190     const coord_def orig_pos = target;
6191 
6192     fake_flavour();
6193     target = p;
6194     affect_cell();
6195     flavour = real_flavour;
6196 
6197     target = orig_pos;
6198 }
6199 
6200 // Uses DFS
determine_affected_cells(explosion_map & m,const coord_def & delta,int count,int r,bool stop_at_statues,bool stop_at_walls)6201 void bolt::determine_affected_cells(explosion_map& m, const coord_def& delta,
6202                                     int count, int r,
6203                                     bool stop_at_statues, bool stop_at_walls)
6204 {
6205     const coord_def centre(9,9);
6206     const coord_def loc = pos() + delta;
6207 
6208     // A bunch of tests for edge cases.
6209     if (delta.rdist() > centre.rdist()
6210         || delta.rdist() > r
6211         || count > 10*r
6212         || !map_bounds(loc)
6213         || is_sanctuary(loc) && flavour != BEAM_VISUAL)
6214     {
6215         return;
6216     }
6217 
6218     const dungeon_feature_type dngn_feat = env.grid(loc);
6219 
6220     bool at_wall = false;
6221 
6222     // Check to see if we're blocked by a wall or a tree. Can't use
6223     // feat_is_solid here, since that includes statues which are a separate
6224     // check, nor feat_is_opaque, since that excludes transparent walls, which
6225     // we want. -ebering
6226     // XXX: We could just include trees as wall features, but this currently
6227     // would have some unintended side-effects. Would be ideal to deal with
6228     // those and simplify feat_is_wall() to return true for trees. -gammafunk
6229     if (feat_is_wall(dngn_feat)
6230         || feat_is_tree(dngn_feat)
6231            && (!feat_is_flammable(dngn_feat) || !can_burn_trees())
6232         || feat_is_closed_door(dngn_feat))
6233     {
6234         // Special case: explosion originates from rock/statue
6235         // (e.g. Lee's Rapid Deconstruction) - in this case, ignore
6236         // solid cells at the center of the explosion.
6237         if (stop_at_walls && !(delta.origin() && can_affect_wall(loc)))
6238             return;
6239         // But remember that we are at a wall.
6240         if (flavour != BEAM_DIGGING)
6241             at_wall = true;
6242     }
6243 
6244     if (feat_is_solid(dngn_feat) && !feat_is_wall(dngn_feat)
6245         && !can_affect_wall(loc) && stop_at_statues)
6246     {
6247         return;
6248     }
6249 
6250     m(delta + centre) = min(count, m(delta + centre));
6251 
6252     // Now recurse in every direction.
6253     for (int i = 0; i < 8; ++i)
6254     {
6255         const coord_def new_delta = delta + Compass[i];
6256 
6257         if (new_delta.rdist() > centre.rdist())
6258             continue;
6259 
6260         // If we were at a wall, only move to visible squares.
6261         coord_def caster_pos = actor_by_mid(source_id) ?
6262                                    actor_by_mid(source_id)->pos() :
6263                                    you.pos();
6264 
6265         if (at_wall && !cell_see_cell(caster_pos, loc + Compass[i], LOS_NO_TRANS))
6266             continue;
6267 
6268         int cadd = 5;
6269         // Circling around the center is always free.
6270         if (delta.rdist() == 1 && new_delta.rdist() == 1)
6271             cadd = 0;
6272         // Otherwise changing direction (e.g. looking around a wall) costs more.
6273         else if (delta.x * Compass[i].x < 0 || delta.y * Compass[i].y < 0)
6274             cadd = 17;
6275 
6276         // Is that cell already covered?
6277         if (m(new_delta + centre) <= count + cadd)
6278             continue;
6279 
6280         determine_affected_cells(m, new_delta, count + cadd, r,
6281                                  stop_at_statues, stop_at_walls);
6282     }
6283 }
6284 
6285 // Returns true if the beam is harmful ((mostly) ignoring monster
6286 // resists) -- mon is given for 'special' cases where,
6287 // for example, "Heal" might actually hurt undead, or
6288 // "Holy Word" being ignored by holy monsters, etc.
6289 //
6290 // Only enchantments should need the actual monster type
6291 // to determine this; non-enchantments are pretty
6292 // straightforward.
nasty_to(const monster * mon) const6293 bool bolt::nasty_to(const monster* mon) const
6294 {
6295     // Cleansing flame.
6296     if (flavour == BEAM_HOLY)
6297         return mon->res_holy_energy() < 3;
6298 
6299     // The orbs are made of pure disintegration energy. This also has the side
6300     // effect of not stopping us from firing further orbs when the previous one
6301     // is still flying.
6302     if (flavour == BEAM_DEVASTATION)
6303         return mon->type != MONS_ORB_OF_DESTRUCTION;
6304 
6305     // Take care of other non-enchantments.
6306     if (!is_enchantment())
6307         return true;
6308 
6309     // Positive effects.
6310     if (nice_to(monster_info(mon)))
6311         return false;
6312 
6313     switch (flavour)
6314     {
6315         case BEAM_DIGGING:
6316             return false;
6317         case BEAM_INNER_FLAME:
6318             // Co-aligned inner flame is fine.
6319             return !mons_aligned(mon, agent());
6320         case BEAM_TELEPORT:
6321         case BEAM_BECKONING:
6322         case BEAM_INFESTATION:
6323         case BEAM_VILE_CLUTCH:
6324         case BEAM_SLOW:
6325         case BEAM_PARALYSIS:
6326         case BEAM_PETRIFY:
6327         case BEAM_POLYMORPH:
6328         case BEAM_DISPEL_UNDEAD:
6329         case BEAM_PAIN:
6330         case BEAM_AGONY:
6331         case BEAM_HIBERNATION:
6332         case BEAM_MINDBURST:
6333         case BEAM_VAMPIRIC_DRAINING:
6334             return ench_flavour_affects_monster(flavour, mon);
6335         case BEAM_TUKIMAS_DANCE:
6336             return tukima_affects(*mon); // XXX: move to ench_flavour_affects?
6337         case BEAM_UNRAVELLING:
6338             return monster_is_debuffable(*mon); // XXX: as tukima's
6339         default:
6340             break;
6341     }
6342 
6343     // everything else is considered nasty by everyone
6344     return true;
6345 }
6346 
6347 // Return true if the bolt is considered nice by mon.
6348 // This is not the inverse of nasty_to(): the bolt needs to be
6349 // actively positive.
nice_to(const monster_info & mi) const6350 bool bolt::nice_to(const monster_info& mi) const
6351 {
6352     // Polymorphing a (very) ugly thing will mutate it into a different
6353     // (very) ugly thing.
6354     if (flavour == BEAM_POLYMORPH)
6355     {
6356         return mi.type == MONS_UGLY_THING
6357                || mi.type == MONS_VERY_UGLY_THING;
6358     }
6359 
6360     if (flavour == BEAM_HASTE
6361         || flavour == BEAM_HEALING
6362         || flavour == BEAM_MIGHT
6363         || flavour == BEAM_AGILITY
6364         || flavour == BEAM_INVISIBILITY
6365         || flavour == BEAM_RESISTANCE
6366         || flavour == BEAM_CONCENTRATE_VENOM)
6367     {
6368         return true;
6369     }
6370 
6371     return false;
6372 }
6373 
6374 ////////////////////////////////////////////////////////////////////////////
6375 // bolt
6376 // TODO: Eventually it'd be nice to have a proper factory for these things
6377 // (extended from setup_mons_cast() and zapping() which act as limited ones).
6378 
killer() const6379 killer_type bolt::killer() const
6380 {
6381     if (flavour == BEAM_BANISH)
6382         return KILL_BANISHED;
6383 
6384     switch (thrower)
6385     {
6386     case KILL_YOU:
6387     case KILL_YOU_MISSILE:
6388         return (flavour == BEAM_PARALYSIS
6389                 || flavour == BEAM_PETRIFY) ? KILL_YOU : KILL_YOU_MISSILE;
6390 
6391     case KILL_MON:
6392     case KILL_MON_MISSILE:
6393         return KILL_MON_MISSILE;
6394 
6395     case KILL_YOU_CONF:
6396         return KILL_YOU_CONF;
6397 
6398     default:
6399         return KILL_MON_MISSILE;
6400     }
6401 }
6402 
set_target(const dist & d)6403 void bolt::set_target(const dist &d)
6404 {
6405     if (!d.isValid)
6406         return;
6407 
6408     target = d.target;
6409 
6410     chose_ray = d.choseRay;
6411     if (d.choseRay)
6412         ray = d.ray;
6413 
6414     if (d.isEndpoint)
6415         aimed_at_spot = true;
6416 }
6417 
setup_retrace()6418 void bolt::setup_retrace()
6419 {
6420     if (pos().x && pos().y)
6421         target = pos();
6422 
6423     swap(source, target);
6424     chose_ray        = false;
6425     affects_nothing  = true;
6426     aimed_at_spot    = true;
6427     extra_range_used = 0;
6428 }
6429 
set_agent(const actor * actor)6430 void bolt::set_agent(const actor *actor)
6431 {
6432     // nullptr actor is fine by us.
6433     if (!actor)
6434         return;
6435 
6436     source_id = actor->mid;
6437 
6438     if (actor->is_player())
6439         thrower = KILL_YOU_MISSILE;
6440     else
6441         thrower = KILL_MON_MISSILE;
6442 }
6443 
6444 /**
6445  * Who caused this beam?
6446  *
6447  * @param ignore_reflection If true, look all the way back to the original
6448  *                          source; if false (the default), treat the latest
6449  *                          actor to reflect this as the source.
6450  * @returns The actor that can be treated as the source. May be null if
6451  *          it's a now-dead monster, or if neither the player nor a monster
6452  *          caused it (for example, divine retribution).
6453  */
agent(bool ignore_reflection) const6454 actor* bolt::agent(bool ignore_reflection) const
6455 {
6456     killer_type nominal_ktype = thrower;
6457     mid_t nominal_source = source_id;
6458 
6459     // If the beam was reflected report a different point of origin
6460     if (reflections > 0 && !ignore_reflection)
6461     {
6462         if (reflector == MID_PLAYER || source_id == MID_PLAYER)
6463             return &env.mons[YOU_FAULTLESS];
6464         nominal_source = reflector;
6465     }
6466 
6467     // Check for whether this is actually a dith shadow, not you
6468     if (monster* shadow = monster_at(you.pos()))
6469         if (shadow->type == MONS_PLAYER_SHADOW && nominal_source == MID_PLAYER)
6470             return shadow;
6471 
6472     if (YOU_KILL(nominal_ktype))
6473         return &you;
6474     else
6475         return actor_by_mid(nominal_source);
6476 }
6477 
is_enchantment() const6478 bool bolt::is_enchantment() const
6479 {
6480     return flavour >= BEAM_FIRST_ENCHANTMENT
6481            && flavour <= BEAM_LAST_ENCHANTMENT;
6482 }
6483 
get_short_name() const6484 string bolt::get_short_name() const
6485 {
6486     if (!short_name.empty())
6487         return short_name;
6488 
6489     if (item != nullptr && item->defined())
6490     {
6491         return item->name(DESC_A, false, false, false, false,
6492                           ISFLAG_IDENT_MASK | ISFLAG_COSMETIC_MASK);
6493     }
6494 
6495     if (real_flavour == BEAM_RANDOM
6496         || real_flavour == BEAM_CHAOS
6497         || real_flavour == BEAM_CRYSTAL)
6498     {
6499         return _beam_type_name(real_flavour);
6500     }
6501 
6502     if (flavour == BEAM_FIRE
6503         && (origin_spell == SPELL_STICKY_FLAME
6504             || origin_spell == SPELL_STICKY_FLAME_RANGE))
6505     {
6506         return "sticky fire";
6507     }
6508 
6509     if (flavour == BEAM_ELECTRICITY && pierce)
6510         return "lightning";
6511 
6512     if (name == "bolt of dispelling energy")
6513         return "dispelling energy";
6514 
6515     if (flavour == BEAM_NONE || flavour == BEAM_MISSILE
6516         || flavour == BEAM_MMISSILE)
6517     {
6518         return name;
6519     }
6520 
6521     return _beam_type_name(flavour);
6522 }
6523 
_beam_type_name(beam_type type)6524 static string _beam_type_name(beam_type type)
6525 {
6526     switch (type)
6527     {
6528     case BEAM_NONE:                  return "none";
6529     case BEAM_MISSILE:               return "missile";
6530     case BEAM_MMISSILE:              return "magic missile";
6531     case BEAM_FIRE:                  return "fire";
6532     case BEAM_COLD:                  return "cold";
6533     case BEAM_WATER:                 return "water";
6534     case BEAM_MAGIC:                 return "magic";
6535     case BEAM_ELECTRICITY:           return "electricity";
6536     case BEAM_MEPHITIC:              return "noxious fumes";
6537     case BEAM_POISON:                return "poison";
6538     case BEAM_NEG:                   return "negative energy";
6539     case BEAM_ACID:                  return "acid";
6540     case BEAM_MIASMA:                return "miasma";
6541     case BEAM_SPORE:                 return "spores";
6542     case BEAM_POISON_ARROW:          return "poison sting";
6543     case BEAM_DAMNATION:             return "damnation";
6544     case BEAM_STICKY_FLAME:          return "sticky fire";
6545     case BEAM_STEAM:                 return "steam";
6546     case BEAM_ENERGY:                return "energy";
6547     case BEAM_HOLY:                  return "cleansing flame";
6548     case BEAM_FRAG:                  return "fragments";
6549     case BEAM_LAVA:                  return "magma";
6550     case BEAM_ICE:                   return "ice";
6551     case BEAM_THUNDER:               return "thunder";
6552     case BEAM_STUN_BOLT:             return "stunning bolt";
6553     case BEAM_DEVASTATION:           return "devastation";
6554     case BEAM_RANDOM:                return "random";
6555     case BEAM_CHAOS:                 return "chaos";
6556     case BEAM_SLOW:                  return "slow";
6557     case BEAM_HASTE:                 return "haste";
6558     case BEAM_MIGHT:                 return "might";
6559     case BEAM_HEALING:               return "healing";
6560     case BEAM_PARALYSIS:             return "paralysis";
6561     case BEAM_CONFUSION:             return "confusion";
6562     case BEAM_INVISIBILITY:          return "invisibility";
6563     case BEAM_DIGGING:               return "digging";
6564     case BEAM_TELEPORT:              return "teleportation";
6565     case BEAM_POLYMORPH:             return "polymorph";
6566     case BEAM_MALMUTATE:             return "malmutation";
6567     case BEAM_CHARM:                 return "charming";
6568     case BEAM_BANISH:                return "banishment";
6569     case BEAM_PAIN:                  return "pain";
6570     case BEAM_AGONY:                 return "agony";
6571     case BEAM_DISPEL_UNDEAD:         return "dispel undead";
6572     case BEAM_MINDBURST:             return "mindburst";
6573     case BEAM_BLINK:                 return "blink";
6574     case BEAM_BLINK_CLOSE:           return "blink close";
6575     case BEAM_BECKONING:             return "beckoning";
6576     case BEAM_PETRIFY:               return "petrify";
6577     case BEAM_CORONA:                return "backlight";
6578     case BEAM_PORKALATOR:            return "porkalator";
6579     case BEAM_HIBERNATION:           return "hibernation";
6580     case BEAM_SLEEP:                 return "sleep";
6581     case BEAM_BERSERK:               return "berserk";
6582     case BEAM_VISUAL:                return "visual effects";
6583     case BEAM_TORMENT_DAMAGE:        return "torment damage";
6584     case BEAM_AIR:                   return "air";
6585     case BEAM_INNER_FLAME:           return "inner flame";
6586     case BEAM_PETRIFYING_CLOUD:      return "calcifying dust";
6587     case BEAM_ENSNARE:               return "magic web";
6588     case BEAM_SENTINEL_MARK:         return "sentinel's mark";
6589     case BEAM_DIMENSION_ANCHOR:      return "dimension anchor";
6590     case BEAM_VULNERABILITY:         return "vulnerability";
6591     case BEAM_MALIGN_OFFERING:       return "malign offering";
6592     case BEAM_VIRULENCE:             return "virulence";
6593     case BEAM_AGILITY:               return "agility";
6594     case BEAM_SAP_MAGIC:             return "sap magic";
6595     case BEAM_CRYSTAL:               return "crystal bolt";
6596     case BEAM_DRAIN_MAGIC:           return "drain magic";
6597     case BEAM_TUKIMAS_DANCE:         return "tukima's dance";
6598     case BEAM_DEATH_RATTLE:          return "breath of the dead";
6599     case BEAM_RESISTANCE:            return "resistance";
6600     case BEAM_UNRAVELLING:           return "unravelling";
6601     case BEAM_UNRAVELLED_MAGIC:      return "unravelled magic";
6602     case BEAM_SHARED_PAIN:           return "shared pain";
6603     case BEAM_IRRESISTIBLE_CONFUSION:return "confusion";
6604     case BEAM_INFESTATION:           return "infestation";
6605     case BEAM_VILE_CLUTCH:           return "vile clutch";
6606     case BEAM_VAMPIRIC_DRAINING:     return "vampiric draining";
6607     case BEAM_CONCENTRATE_VENOM:     return "concentrate venom";
6608 
6609     case NUM_BEAMS:                  die("invalid beam type");
6610     }
6611     die("unknown beam type");
6612 }
6613 
get_source_name() const6614 string bolt::get_source_name() const
6615 {
6616     if (!source_name.empty())
6617         return source_name;
6618     const actor *a = agent();
6619     if (a)
6620         return a->name(DESC_A, true);
6621     return "";
6622 }
6623 
6624 /**
6625  * Can this bolt knock back an actor?
6626  *
6627  * The bolts that knockback flying actors or actors only when damage is dealt
6628  * will return true when conditions are met.
6629  *
6630  * @param act The target actor. Check if the actor is flying for bolts that
6631  *            knockback flying actors.
6632  * @param dam The damage dealt. If non-negative, check that dam > 0 for bolts
6633  *             like force bolt that only push back upon damage.
6634  * @return True if the bolt could knockback the actor, false otherwise.
6635 */
can_knockback(const actor & act,int dam) const6636 bool bolt::can_knockback(const actor &act, int dam) const
6637 {
6638     if (act.is_stationary())
6639         return false;
6640 
6641     return flavour == BEAM_WATER && origin_spell == SPELL_PRIMAL_WAVE
6642            || origin_spell == SPELL_CHILLING_BREATH && dam
6643            || origin_spell == SPELL_FORCE_LANCE && dam
6644            || origin_spell == SPELL_ISKENDERUNS_MYSTIC_BLAST && dam;
6645 }
6646 
6647 /**
6648  * Can this bolt pull an actor?
6649  *
6650  * If a bolt is capable of pulling actors and the given actor can be pulled,
6651  * return true.
6652  *
6653  * @param act The target actor. Check if the actor is non-stationary and not
6654  *            already adjacent.
6655  * @param dam The damage dealt. Check that dam > 0.
6656  * @return True if the bolt could pull the actor, false otherwise.
6657 */
can_pull(const actor & act,int dam) const6658 bool bolt::can_pull(const actor &act, int dam) const
6659 {
6660     if (act.is_stationary() || adjacent(source, act.pos()))
6661         return false;
6662 
6663     return origin_spell == SPELL_HARPOON_SHOT && dam;
6664 }
6665 
clear_zap_info_on_exit()6666 void clear_zap_info_on_exit()
6667 {
6668     for (const zap_info &zap : zap_data)
6669     {
6670         delete zap.player_damage;
6671         delete zap.player_tohit;
6672         delete zap.monster_damage;
6673         delete zap.monster_tohit;
6674     }
6675 }
6676 
ench_power_stepdown(int pow)6677 int ench_power_stepdown(int pow)
6678 {
6679     return stepdown_value(pow, 30, 40, 100, 120);
6680 }
6681 
6682 /// Translate a given ench power to a duration, in aut.
_ench_pow_to_dur(int pow)6683 int _ench_pow_to_dur(int pow)
6684 {
6685     // ~15 turns at 25 pow, ~21 turns at 50 pow, ~27 turns at 100 pow
6686     return stepdown(pow * BASELINE_DELAY, 70);
6687 }
6688 
6689 // Do all beams skip past a particular monster?
6690 // see also shoot_through_monsters
6691 // can these be consolidated? Some checks there don't need a bolt arg
always_shoot_through_monster(const actor * originator,const monster & victim)6692 bool always_shoot_through_monster(const actor *originator, const monster &victim)
6693 {
6694     return mons_is_projectile(victim)
6695         || (mons_is_avatar(victim.type)
6696             && originator && mons_aligned(originator, &victim));
6697 }
6698 
6699 // Can a particular beam go through a particular monster?
6700 // Fedhas worshipers can shoot through non-hostile plants,
6701 // and players can shoot through their demonic guardians.
shoot_through_monster(const bolt & beam,const monster * victim)6702 bool shoot_through_monster(const bolt& beam, const monster* victim)
6703 {
6704     actor *originator = beam.agent();
6705     if (!victim || !originator)
6706         return false;
6707     return god_protects(originator, victim)
6708            || (originator->is_player()
6709                && testbits(victim->flags, MF_DEMONIC_GUARDIAN));
6710 }
6711 
6712 /**
6713  * Given some shield value, what is the chance that omnireflect will activate
6714  * on an AUTOMATIC_HIT attack?
6715  *
6716  * E.g., if 40 is returned, there is a SH in 40 chance of a given attack being
6717  * reflected.
6718  *
6719  * @param SH        The SH (shield) value of the omnireflect user.
6720  * @return          A denominator to the chance of omnireflect activating.
6721  */
omnireflect_chance_denom(int SH)6722 int omnireflect_chance_denom(int SH)
6723 {
6724     return SH + 20;
6725 }
6726 
6727 /// Set up a beam aiming from the given monster to their target.
setup_targetting_beam(const monster & mons)6728 bolt setup_targetting_beam(const monster &mons)
6729 {
6730     bolt beem;
6731 
6732     beem.source    = mons.pos();
6733     beem.target    = mons.target;
6734     beem.source_id = mons.mid;
6735 
6736     return beem;
6737 }
6738