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 ©)
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