1 /*
2  *  File:       attack.cc
3  *  Summary:    Methods of the attack class, generalized functions which may
4  *              be overloaded by inheriting classes.
5  *  Written by: Robert Burnham
6  */
7 
8 #include "AppHdr.h"
9 
10 #include "attack.h"
11 
12 #include <algorithm>
13 #include <cmath>
14 #include <cstdio>
15 #include <cstdlib>
16 #include <cstring>
17 #include <functional>
18 
19 #include "art-enum.h"
20 #include "chardump.h"
21 #include "delay.h"
22 #include "english.h"
23 #include "env.h"
24 #include "exercise.h"
25 #include "fight.h"
26 #include "fineff.h"
27 #include "god-conduct.h"
28 #include "god-passive.h" // passive_t::no_haste
29 #include "item-name.h"
30 #include "item-prop.h"
31 #include "message.h"
32 #include "mon-behv.h"
33 #include "mon-clone.h"
34 #include "mon-death.h"
35 #include "nearby-danger.h"
36 #include "pronoun-type.h"
37 #include "religion.h"
38 #include "spl-util.h"
39 #include "state.h"
40 #include "stepdown.h"
41 #include "stringutil.h"
42 #include "tag-version.h"
43 #include "transform.h"
44 #include "xom.h"
45 
46 /*
47  **************************************************
48  *             BEGIN PUBLIC FUNCTIONS             *
49  **************************************************
50 */
attack(actor * attk,actor * defn,actor * blame)51 attack::attack(actor *attk, actor *defn, actor *blame)
52     : attacker(attk), defender(defn), responsible(blame ? blame : attk),
53       attack_occurred(false), cancel_attack(false), did_hit(false),
54       needs_message(false), attacker_visible(false), defender_visible(false),
55       perceived_attack(false), obvious_effect(false), to_hit(0),
56       damage_done(0), special_damage(0), aux_damage(0), min_delay(0),
57       final_attack_delay(0), special_damage_flavour(BEAM_NONE),
58       stab_attempt(false), stab_bonus(0), ev_margin(0), weapon(nullptr),
59       damage_brand(SPWPN_NORMAL), wpn_skill(SK_UNARMED_COMBAT),
60       shield(nullptr), art_props(0), unrand_entry(nullptr),
61       attacker_to_hit_penalty(0), attack_verb("bug"), verb_degree(),
62       no_damage_message(), special_damage_message(), aux_attack(), aux_verb(),
63       attacker_armour_tohit_penalty(0), attacker_shield_tohit_penalty(0),
64       defender_shield(nullptr), fake_chaos_attack(false), simu(false),
65       aux_source(""), kill_type(KILLED_BY_MONSTER)
66 {
67     // No effective code should execute, we'll call init_attack again from
68     // the child class, since initializing an attack will vary based the within
69     // type of attack actually being made (melee, ranged, etc.)
70 }
71 
handle_phase_attempted()72 bool attack::handle_phase_attempted()
73 {
74     return true;
75 }
76 
handle_phase_blocked()77 bool attack::handle_phase_blocked()
78 {
79     damage_done = 0;
80 
81     if (attacker->is_player())
82         behaviour_event(defender->as_monster(), ME_WHACK, attacker);
83 
84     return true;
85 }
86 
handle_phase_damaged()87 bool attack::handle_phase_damaged()
88 {
89     // We have to check in_bounds() because removed kraken tentacles are
90     // temporarily returned to existence (without a position) when they
91     // react to damage.
92     if (defender->can_bleed()
93         && !defender->is_summoned()
94         && !defender->submerged()
95         && in_bounds(defender->pos())
96         && !simu)
97     {
98         int blood = damage_done;
99         if (blood > defender->stat_hp())
100             blood = defender->stat_hp();
101         if (blood)
102             blood_fineff::schedule(defender, defender->pos(), blood);
103     }
104 
105     announce_hit();
106     // Inflict stored damage
107     damage_done = inflict_damage(damage_done);
108 
109     // TODO: Unify these, added here so we can get rid of player_attack
110     if (attacker->is_player())
111     {
112         if (damage_done)
113             player_exercise_combat_skills();
114     }
115     else
116     {
117         if (!mons_attack_effects())
118             return false;
119     }
120 
121     // It's okay if a monster took lethal damage, but we should stop
122     // the combat if it was already reset (e.g. a spectral weapon that
123     // took damage and then noticed that its caster is gone).
124     return defender->is_player() || !invalid_monster(defender->as_monster());
125 }
126 
handle_phase_killed()127 bool attack::handle_phase_killed()
128 {
129     monster* mon = defender->as_monster();
130     if (!invalid_monster(mon))
131     {
132         // Was this a reflected missile from the player?
133         if (responsible->mid == MID_YOU_FAULTLESS)
134             monster_die(*mon, KILL_YOU_MISSILE, YOU_FAULTLESS);
135         else
136             monster_die(*mon, attacker);
137     }
138 
139     return true;
140 }
141 
handle_phase_end()142 bool attack::handle_phase_end()
143 {
144     return true;
145 }
146 
147 /**
148  * Calculate to-hit penalties from the attacker's armour and shield, if any.
149  * Set them into the appropriate fields.
150  *
151  * @param random If false, calculate average to-hit penalties deterministically.
152  */
calc_encumbrance_penalties(bool random)153 void attack::calc_encumbrance_penalties(bool random)
154 {
155     attacker_armour_tohit_penalty =
156         maybe_random_div(attacker->armour_tohit_penalty(true, 20), 20, random);
157     attacker_shield_tohit_penalty =
158         maybe_random_div(attacker->shield_tohit_penalty(true, 20), 20, random);
159 }
160 
161 /**
162  * Calculate the to-hit for an attacker before the main die roll.
163  *
164  * @param random If false, calculate average to-hit deterministically.
165  */
calc_pre_roll_to_hit(bool random)166 int attack::calc_pre_roll_to_hit(bool random)
167 {
168     if (using_weapon()
169         && (is_unrandom_artefact(*weapon, UNRAND_WOE)
170             || is_unrandom_artefact(*weapon, UNRAND_SNIPER)))
171     {
172         return AUTOMATIC_HIT;
173     }
174 
175     int mhit = attacker->is_player() ?
176                 15 + (you.dex() / 2)
177               : calc_mon_to_hit_base();
178 
179     // This if statement is temporary, it should be removed when the
180     // implementation of a more universal (and elegant) to-hit calculation
181     // is designed. The actual code is copied from the old mons_to_hit and
182     // player_to_hit methods.
183     if (attacker->is_player())
184     {
185         // fighting contribution
186         mhit += maybe_random_div(you.skill(SK_FIGHTING, 100), 100, random);
187 
188         // weapon skill contribution
189         if (using_weapon())
190         {
191             if (wpn_skill != SK_FIGHTING)
192             {
193                 if (you.skill(wpn_skill) < 1 && player_in_a_dangerous_place() && random)
194                     xom_is_stimulated(10); // Xom thinks that is mildly amusing.
195 
196                 mhit += maybe_random_div(you.skill(wpn_skill, 100), 100,
197                                          random);
198             }
199         }
200         else if (you.form_uses_xl())
201             mhit += maybe_random_div(you.experience_level * 100, 100, random);
202         else
203         {
204             // Claws give a slight bonus to accuracy when active
205             mhit += (you.get_mutation_level(MUT_CLAWS) > 0
206                      && wpn_skill == SK_UNARMED_COMBAT) ? 4 : 2;
207 
208             mhit += maybe_random_div(you.skill(wpn_skill, 100), 100,
209                                      random);
210         }
211 
212         // weapon bonus contribution
213         if (using_weapon())
214         {
215             if (weapon->base_type == OBJ_WEAPONS)
216             {
217                 mhit += weapon->plus;
218                 mhit += property(*weapon, PWPN_HIT);
219             }
220             else if (weapon->base_type == OBJ_STAVES)
221                 mhit += property(*weapon, PWPN_HIT);
222         }
223 
224         // slaying bonus
225         mhit += slaying_bonus(wpn_skill == SK_THROWING
226                               || (weapon && is_range_weapon(*weapon)
227                                          && using_weapon()));
228 
229         // armour penalty (already calculated if random is true)
230         if (!random)
231             calc_encumbrance_penalties(random);
232         mhit -= (attacker_armour_tohit_penalty + attacker_shield_tohit_penalty);
233 
234         // vertigo penalty
235         if (you.duration[DUR_VERTIGO])
236             mhit -= 5;
237 
238         // mutation
239         if (you.get_mutation_level(MUT_EYEBALLS))
240             mhit += 2 * you.get_mutation_level(MUT_EYEBALLS) + 1;
241     }
242     else    // Monster to-hit.
243     {
244         if (using_weapon())
245             mhit += weapon->plus + property(*weapon, PWPN_HIT);
246 
247         const int jewellery = attacker->as_monster()->inv[MSLOT_JEWELLERY];
248         if (jewellery != NON_ITEM
249             && env.item[jewellery].is_type(OBJ_JEWELLERY, RING_SLAYING))
250         {
251             mhit += env.item[jewellery].plus;
252         }
253 
254         mhit += attacker->scan_artefacts(ARTP_SLAYING);
255     }
256 
257     return mhit;
258 }
259 
260 /**
261  * Calculate to-hit modifiers for an attacker that apply after the player's roll.
262  *
263  * @param mhit The post-roll player's to-hit value.
264  */
post_roll_to_hit_modifiers(int mhit,bool)265 int attack::post_roll_to_hit_modifiers(int mhit, bool /*random*/)
266 {
267     int modifiers = 0;
268 
269     // Penalties for both players and monsters:
270     modifiers -= 5 * attacker->inaccuracy();
271 
272     if (attacker->confused())
273         modifiers += CONFUSION_TO_HIT_MALUS;
274 
275     // If no defender, we're calculating to-hit for debug-display
276     // purposes, so don't drop down to defender code below
277     if (defender == nullptr)
278         return modifiers;
279 
280     if (!defender->visible_to(attacker))
281     {
282         if (attacker->is_player())
283             modifiers -= 6;
284         else
285             modifiers -= mhit * 35 / 100;
286     }
287     else
288     {
289         // This can only help if you're visible!
290         const int how_transparent = you.get_mutation_level(MUT_TRANSLUCENT_SKIN);
291         if (defender->is_player() && how_transparent)
292             modifiers += TRANSLUCENT_SKIN_TO_HIT_MALUS * how_transparent;
293 
294         // defender backlight bonus and umbra penalty.
295         if (defender->backlit(false))
296             modifiers += BACKLIGHT_TO_HIT_BONUS;
297         if (!attacker->nightvision() && defender->umbra())
298             modifiers += UMBRA_TO_HIT_MALUS;
299     }
300 
301     return modifiers;
302 }
303 
304 /**
305  * Calculate the to-hit for an attacker
306  *
307  * @param random If false, calculate average to-hit deterministically.
308  */
calc_to_hit(bool random)309 int attack::calc_to_hit(bool random)
310 {
311     int mhit = calc_pre_roll_to_hit(random);
312     if (mhit == AUTOMATIC_HIT)
313         return AUTOMATIC_HIT;
314 
315     // hit roll
316     if (attacker->is_player())
317         mhit = maybe_random2(mhit, random);
318 
319     mhit += post_roll_to_hit_modifiers(mhit, random);
320 
321     // We already did this roll for players.
322     if (!attacker->is_player())
323         mhit = maybe_random2(mhit + 1, random);
324 
325     dprf(DIAG_COMBAT, "%s: to-hit: %d",
326          attacker->name(DESC_PLAIN).c_str(), mhit);
327 
328     return mhit;
329 }
330 
331 /* Returns an actor's name
332  *
333  * Takes into account actor visibility/invisibility and the type of description
334  * to be used (capitalization, possessiveness, etc.)
335  */
actor_name(const actor * a,description_level_type desc,bool actor_visible)336 string attack::actor_name(const actor *a, description_level_type desc,
337                           bool actor_visible)
338 {
339     return actor_visible ? a->name(desc) : anon_name(desc);
340 }
341 
342 /* Returns an actor's pronoun
343  *
344  * Takes into account actor visibility
345  */
actor_pronoun(const actor * a,pronoun_type pron,bool actor_visible)346 string attack::actor_pronoun(const actor *a, pronoun_type pron,
347                              bool actor_visible)
348 {
349     return actor_visible ? a->pronoun(pron) : anon_pronoun(pron);
350 }
351 
352 /* Returns an anonymous actor's name
353  *
354  * Given the actor visible or invisible, returns the
355  * appropriate possessive pronoun.
356  */
anon_name(description_level_type desc)357 string attack::anon_name(description_level_type desc)
358 {
359     switch (desc)
360     {
361     case DESC_NONE:
362         return "";
363     case DESC_YOUR:
364     case DESC_ITS:
365         return "something's";
366     case DESC_THE:
367     case DESC_A:
368     case DESC_PLAIN:
369     default:
370         return "something";
371     }
372 }
373 
374 /* Returns an anonymous actor's pronoun
375  *
376  * Given invisibility (whether out of LOS or just invisible), returns the
377  * appropriate possessive, inflexive, capitalised pronoun.
378  */
anon_pronoun(pronoun_type pron)379 string attack::anon_pronoun(pronoun_type pron)
380 {
381     return decline_pronoun(GENDER_NEUTER, pron);
382 }
383 
384 /* Initializes an attack, setting up base variables and values
385  *
386  * Does not make any changes to any actors, items, or the environment,
387  * in case the attack is cancelled or otherwise fails. Only initializations
388  * that are universal to all types of attacks should go into this method,
389  * any initialization properties that are specific to one attack or another
390  * should go into their respective init_attack.
391  *
392  * Although this method will get overloaded by the derived class, we are
393  * calling it from attack::attack(), before the overloading has taken place.
394  */
init_attack(skill_type unarmed_skill,int attack_number)395 void attack::init_attack(skill_type unarmed_skill, int attack_number)
396 {
397     ASSERT(attacker);
398     weapon          = attacker->weapon(attack_number);
399 
400     wpn_skill       = weapon ? item_attack_skill(*weapon) : unarmed_skill;
401     if (attacker->is_player() && you.form_uses_xl())
402         wpn_skill = SK_FIGHTING; // for stabbing, mostly
403 
404     calc_encumbrance_penalties(true);
405     to_hit          = calc_to_hit(true);
406 
407     shield = attacker->shield();
408     defender_shield = defender ? defender->shield() : defender_shield;
409 
410     if (weapon && weapon->base_type == OBJ_WEAPONS && is_artefact(*weapon))
411     {
412         artefact_properties(*weapon, art_props);
413         if (is_unrandom_artefact(*weapon))
414             unrand_entry = get_unrand_entry(weapon->unrand_idx);
415     }
416 
417     attacker_visible   = attacker->observable();
418     defender_visible   = defender && defender->observable();
419     needs_message      = (attacker_visible || defender_visible);
420 
421     if (attacker->is_monster())
422     {
423         mon_attack_def mon_attk = mons_attack_spec(*attacker->as_monster(),
424                                                    attack_number,
425                                                    false);
426 
427         attk_type       = mon_attk.type;
428         attk_flavour    = mon_attk.flavour;
429 
430         // Don't scale damage for YOU_FAULTLESS etc.
431         if (attacker->get_experience_level() == 0)
432             attk_damage = mon_attk.damage;
433         else
434         {
435             attk_damage = div_rand_round(mon_attk.damage
436                                              * attacker->get_hit_dice(),
437                                          attacker->get_experience_level());
438         }
439 
440         if (attk_type == AT_WEAP_ONLY)
441         {
442             int weap = attacker->as_monster()->inv[MSLOT_WEAPON];
443             if (weap == NON_ITEM || is_range_weapon(env.item[weap]))
444                 attk_type = AT_NONE;
445             else
446                 attk_type = AT_HIT;
447         }
448         else if (attk_type == AT_TRUNK_SLAP && attacker->type == MONS_SKELETON)
449         {
450             // Elephant trunks have no bones inside.
451             attk_type = AT_NONE;
452         }
453     }
454     else
455     {
456         attk_type    = AT_HIT;
457         attk_flavour = AF_PLAIN;
458     }
459 }
460 
alert_defender()461 void attack::alert_defender()
462 {
463     // Allow monster attacks to draw the ire of the defender. Player
464     // attacks are handled elsewhere.
465     if (perceived_attack
466         && defender->is_monster()
467         && attacker->is_monster()
468         && attacker->alive() && defender->alive()
469         && (defender->as_monster()->foe == MHITNOT || one_chance_in(3)))
470     {
471         behaviour_event(defender->as_monster(), ME_WHACK, attacker);
472     }
473 
474     // If an enemy attacked a friend, set the pet target if it isn't set
475     // already, but not if sanctuary is in effect (pet target must be
476     // set explicitly by the player during sanctuary).
477     if (perceived_attack && attacker->alive()
478         && (defender->is_player() || defender->as_monster()->friendly())
479         && !attacker->is_player()
480         && !crawl_state.game_is_arena()
481         && !attacker->as_monster()->wont_attack())
482     {
483         if (defender->is_player())
484         {
485             interrupt_activity(activity_interrupt::monster_attacks,
486                                attacker->as_monster());
487         }
488         if (you.pet_target == MHITNOT && env.sanctuary_time <= 0)
489             you.pet_target = attacker->mindex();
490     }
491 }
492 
distortion_affects_defender()493 bool attack::distortion_affects_defender()
494 {
495     enum disto_effect
496     {
497         SMALL_DMG,
498         BIG_DMG,
499         BANISH,
500         BLINK,
501         NONE
502     };
503 
504     const disto_effect choice = random_choose_weighted(35, SMALL_DMG,
505                                                        25, BIG_DMG,
506                                                        5,  BANISH,
507                                                        20, BLINK,
508                                                        15,  NONE);
509 
510     if (simu && !(choice == SMALL_DMG || choice == BIG_DMG))
511         return false;
512 
513     switch (choice)
514     {
515     case SMALL_DMG:
516         special_damage += 1 + random2avg(7, 2);
517         special_damage_message = make_stringf("Space bends around %s%s",
518                                               defender_name(false).c_str(),
519                                               attack_strength_punctuation(special_damage).c_str());
520         break;
521     case BIG_DMG:
522         special_damage += 3 + random2avg(24, 2);
523         special_damage_message =
524             make_stringf("Space warps horribly around %s%s",
525                          defender_name(false).c_str(),
526                          attack_strength_punctuation(special_damage).c_str());
527         break;
528     case BLINK:
529         if (defender_visible)
530             obvious_effect = true;
531         if (!defender->no_tele(true, false))
532             blink_fineff::schedule(defender);
533         break;
534     case BANISH:
535         if (defender_visible)
536             obvious_effect = true;
537         defender->banish(attacker, attacker->name(DESC_PLAIN, true),
538                          attacker->get_experience_level());
539         return true;
540     case NONE:
541         // Do nothing
542         break;
543     }
544 
545     return false;
546 }
547 
antimagic_affects_defender(int pow)548 void attack::antimagic_affects_defender(int pow)
549 {
550     obvious_effect =
551         enchant_actor_with_flavour(defender, nullptr, BEAM_DRAIN_MAGIC, pow);
552 }
553 
554 /// Whose skill should be used for a pain-weapon effect?
_pain_weapon_user(actor * attacker)555 static actor* _pain_weapon_user(actor* attacker)
556 {
557     if (attacker->type != MONS_SPECTRAL_WEAPON)
558         return attacker;
559 
560     const mid_t summoner_mid = attacker->as_monster()->summoner;
561     if (summoner_mid == MID_NOBODY)
562         return attacker;
563 
564     actor* summoner = actor_by_mid(attacker->as_monster()->summoner);
565     if (!summoner || !summoner->alive())
566         return attacker;
567     return summoner;
568 }
569 
pain_affects_defender()570 void attack::pain_affects_defender()
571 {
572     actor* user = _pain_weapon_user(attacker);
573     if (!one_chance_in(user->skill_rdiv(SK_NECROMANCY) + 1))
574     {
575         special_damage += resist_adjust_damage(defender, BEAM_NEG,
576                               random2(1 + user->skill_rdiv(SK_NECROMANCY)));
577 
578         if (special_damage && defender_visible)
579         {
580             special_damage_message =
581                 make_stringf("%s %s in agony%s",
582                              defender->name(DESC_THE).c_str(),
583                              defender->conj_verb("writhe").c_str(),
584                            attack_strength_punctuation(special_damage).c_str());
585         }
586     }
587 }
588 
_is_chaos_polyable(const actor & defender)589 static bool _is_chaos_polyable(const actor &defender)
590 {
591     if (!defender.can_safely_mutate())
592         return false;  // no polymorphing undead
593 
594     const monster* mon = defender.as_monster();
595     if (!mon)
596         return true;
597 
598     return !mons_is_firewood(*mon) && !mons_invuln_will(*mon);
599 }
600 
_is_chaos_slowable(const actor & defender)601 static bool _is_chaos_slowable(const actor &defender)
602 {
603     const monster* mon = defender.as_monster();
604     if (!mon)
605         return true;
606 
607     return !mons_is_firewood(*mon) && !mon->is_stationary();
608 }
609 
610 struct chaos_effect
611 {
612     string name;
613     int chance;
614     function<bool(const actor& def)> valid;
615     beam_type flavour;
616     function<bool(attack &attack)> misc_effect;
617 };
618 
619 static const vector<chaos_effect> chaos_effects = {
620     {
__anon6c15597e0102() 621         "clone", 1, [](const actor &d) {
622             return d.is_monster() && mons_clonable(d.as_monster(), true);
623         },
__anon6c15597e0202() 624         BEAM_NONE, [](attack &attack) {
625             actor &defender = *attack.defender;
626             ASSERT(defender.is_monster());
627             monster *clone = clone_mons(defender.as_monster(), true);
628             if (!clone)
629                 return false;
630 
631             const bool obvious_effect = you.can_see(defender) && you.can_see(*clone);
632 
633             if (one_chance_in(3))
634                 clone->attitude = coinflip() ? ATT_FRIENDLY : ATT_NEUTRAL;
635 
636             // The player shouldn't get new permanent followers from cloning.
637             if (clone->attitude == ATT_FRIENDLY && !clone->is_summoned())
638                 clone->mark_summoned(6, true, MON_SUMM_CLONE);
639 
640             // Monsters being cloned is interesting.
641             xom_is_stimulated(clone->friendly() ? 12 : 25);
642             return obvious_effect;
643         },
644     },
645     {
646         "polymorph", 2, _is_chaos_polyable, BEAM_POLYMORPH,
647     },
648     {
649         "shifter", 1, [](const actor &defender)
__anon6c15597e0302() 650         {
651             const monster *mon = defender.as_monster();
652             return _is_chaos_polyable(defender)
653                    && mon && !mon->is_shapeshifter()
654                    && defender.holiness() & MH_NATURAL;
655         },
__anon6c15597e0402() 656         BEAM_NONE, [](attack &attack) {
657             monster* mon = attack.defender->as_monster();
658             ASSERT(_is_chaos_polyable(*attack.defender));
659             ASSERT(mon);
660             ASSERT(!mon->is_shapeshifter());
661 
662             const bool obvious_effect = you.can_see(*attack.defender);
663             mon->add_ench(one_chance_in(3) ? ENCH_GLOWING_SHAPESHIFTER
664                                            : ENCH_SHAPESHIFTER);
665             // Immediately polymorph monster, just to make the effect obvious.
666             mon->polymorph();
667 
668             // Xom loves it if this happens!
669             const int friend_factor = mon->friendly() ? 1 : 2;
670             const int glow_factor   = mon->has_ench(ENCH_SHAPESHIFTER) ? 1 : 2;
671             xom_is_stimulated(64 * friend_factor * glow_factor);
672 
673             return obvious_effect;
674         },
675     },
676     {
__anon6c15597e0502() 677         "rage", 5, [](const actor &defender) {
678             return defender.can_go_berserk();
679         }, BEAM_NONE, [](attack &attack) {
680             attack.defender->go_berserk(false);
681             return you.can_see(*attack.defender);
682         },
683     },
684     { "hasting", 10, _is_chaos_slowable, BEAM_HASTE },
685     { "mighting", 10, nullptr, BEAM_MIGHT },
686     { "agilitying", 10, nullptr, BEAM_AGILITY },
687     { "invisible", 10, nullptr, BEAM_INVISIBILITY, },
688     { "slowing", 10, _is_chaos_slowable, BEAM_SLOW },
689     {
__anon6c15597e0702() 690         "paralysis", 5, [](const actor &defender) {
691             return !defender.is_monster()
692                     || !mons_is_firewood(*defender.as_monster());
693         }, BEAM_PARALYSIS,
694     },
695     {
__anon6c15597e0802() 696         "petrify", 5, [](const actor &defender) {
697             return _is_chaos_slowable(defender) && !defender.res_petrify();
698         }, BEAM_PETRIFY,
699     },
700 };
701 
chaos_affects_defender()702 void attack::chaos_affects_defender()
703 {
704     ASSERT(defender);
705 
706     vector<pair<const chaos_effect&, int>> weights;
707     for (const chaos_effect &effect : chaos_effects)
708         if (!effect.valid || effect.valid(*defender))
709             weights.push_back({effect, effect.chance});
710 
711     const chaos_effect &effect = *random_choose_weighted(weights);
712 
713 #ifdef NOTE_DEBUG_CHAOS_EFFECTS
714     take_note(Note(NOTE_MESSAGE, 0, 0, "CHAOS effect: " + effect.name), true);
715 #endif
716 
717     if (effect.misc_effect && effect.misc_effect(*this))
718         obvious_effect = true;
719 
720     bolt beam;
721     beam.flavour = effect.flavour;
722     if (beam.flavour != BEAM_NONE)
723     {
724         if (defender->is_player() && have_passive(passive_t::no_haste)
725             && beam.flavour == BEAM_HASTE)
726         {
727             simple_god_message(" protects you from inadvertent hurry.");
728             obvious_effect = true;
729             return;
730         }
731 
732         beam.glyph        = 0;
733         beam.range        = 0;
734         beam.colour       = BLACK;
735         beam.effect_known = false;
736         // Wielded brand is always known, but maybe this was from a call
737         // to chaos_affect_actor.
738         beam.effect_wanton = !fake_chaos_attack;
739 
740         if (using_weapon() && you.can_see(*attacker))
741         {
742             beam.name = wep_name(DESC_YOUR);
743             beam.item = weapon;
744         }
745         else
746             beam.name = atk_name(DESC_THE);
747 
748         beam.thrower =
749             (attacker->is_player())           ? KILL_YOU
750             : attacker->as_monster()->confused_by_you() ? KILL_YOU_CONF
751                                                         : KILL_MON;
752 
753         if (beam.thrower == KILL_YOU || attacker->as_monster()->friendly())
754             beam.attitude = ATT_FRIENDLY;
755 
756         beam.source_id = attacker->mid;
757 
758         beam.source = defender->pos();
759         beam.target = defender->pos();
760 
761         beam.damage = dice_def(damage_done + special_damage + aux_damage, 1);
762 
763         beam.ench_power = beam.damage.num;
764 
765         const bool you_could_see = you.can_see(*defender);
766         beam.fire();
767 
768         if (you_could_see)
769             obvious_effect = beam.obvious_effect;
770     }
771 
772     if (!you.can_see(*attacker))
773         obvious_effect = false; // XXX: VERY dubious!
774 }
775 
776 struct chaos_attack_type
777 {
778     attack_flavour flavour;
779     brand_type brand;
780     int chance;
781     function<bool(const actor& def)> valid;
782 };
783 
784 // Chaos melee attacks randomly choose a brand from here, with brands that
785 // definitely won't affect the target being invalid. Chaos itself should
786 // always be a valid option, triggering a more unpredictable chaos_effect
787 // instead of a normal attack brand when selected.
788 static const vector<chaos_attack_type> chaos_types = {
789     { AF_FIRE,      SPWPN_FLAMING,       10,
__anon6c15597e0902() 790       [](const actor &d) { return !d.is_fiery(); } },
791     { AF_COLD,      SPWPN_FREEZING,      10,
__anon6c15597e0a02() 792       [](const actor &d) { return !d.is_icy(); } },
793     { AF_ELEC,      SPWPN_ELECTROCUTION, 10,
794       nullptr },
795     { AF_POISON,    SPWPN_VENOM,         10,
__anon6c15597e0b02() 796       [](const actor &d) {
797           return !(d.holiness() & (MH_UNDEAD | MH_NONLIVING)); } },
798     { AF_CHAOTIC,   SPWPN_CHAOS,         10,
799       nullptr },
800     { AF_DRAIN,  SPWPN_DRAINING,      5,
__anon6c15597e0c02() 801       [](const actor &d) { return bool(d.holiness() & MH_NATURAL); } },
802     { AF_VAMPIRIC,  SPWPN_VAMPIRISM,     5,
__anon6c15597e0d02() 803       [](const actor &d) {
804           return !d.is_summoned() && bool(d.holiness() & MH_NATURAL); } },
805     { AF_HOLY,      SPWPN_HOLY_WRATH,    5,
__anon6c15597e0e02() 806       [](const actor &d) { return d.holy_wrath_susceptible(); } },
807     { AF_ANTIMAGIC, SPWPN_ANTIMAGIC,     5,
__anon6c15597e0f02() 808       [](const actor &d) { return d.antimagic_susceptible(); } },
809     { AF_CONFUSE,   SPWPN_CONFUSE,       2,
__anon6c15597e1002() 810       [](const actor &d) { return !d.clarity(); } },
811     { AF_DISTORT,   SPWPN_DISTORTION,    2,
812       nullptr },
813 };
814 
random_chaos_brand()815 brand_type attack::random_chaos_brand()
816 {
817     vector<pair<brand_type, int>> weights;
818     for (const chaos_attack_type &choice : chaos_types)
819         if (!choice.valid || choice.valid(*defender))
820             weights.push_back({choice.brand, choice.chance});
821 
822     ASSERT(!weights.empty());
823 
824     brand_type brand = *random_choose_weighted(weights);
825 
826 #ifdef NOTE_DEBUG_CHAOS_BRAND
827     string brand_name = "CHAOS brand: ";
828     switch (brand)
829     {
830     case SPWPN_FLAMING:         brand_name += "flaming"; break;
831     case SPWPN_FREEZING:        brand_name += "freezing"; break;
832     case SPWPN_HOLY_WRATH:      brand_name += "holy wrath"; break;
833     case SPWPN_ELECTROCUTION:   brand_name += "electrocution"; break;
834     case SPWPN_VENOM:           brand_name += "venom"; break;
835     case SPWPN_DRAINING:        brand_name += "draining"; break;
836     case SPWPN_DISTORTION:      brand_name += "distortion"; break;
837     case SPWPN_VAMPIRISM:       brand_name += "vampirism"; break;
838     case SPWPN_ANTIMAGIC:       brand_name += "antimagic"; break;
839     case SPWPN_CHAOS:           brand_name += "chaos"; break;
840     case SPWPN_CONFUSE:         brand_name += "confusion"; break;
841     default:                    brand_name += "BUGGY"; break;
842     }
843 
844     // Pretty much duplicated by the chaos effect note,
845     // which will be much more informative.
846     if (brand != SPWPN_CHAOS)
847         take_note(Note(NOTE_MESSAGE, 0, 0, brand_name), true);
848 #endif
849     return brand;
850 }
851 
random_chaos_attack_flavour()852 attack_flavour attack::random_chaos_attack_flavour()
853 {
854     vector<pair<attack_flavour, int>> weights;
855     for (const chaos_attack_type &choice : chaos_types)
856         if (!choice.valid || choice.valid(*defender))
857             weights.push_back({choice.flavour, choice.chance});
858 
859     ASSERT(!weights.empty());
860 
861     return *random_choose_weighted(weights);
862 }
863 
drain_defender()864 void attack::drain_defender()
865 {
866     if (defender->is_monster() && coinflip())
867         return;
868 
869     if (!(defender->holiness() & MH_NATURAL))
870         return;
871 
872     special_damage = resist_adjust_damage(defender, BEAM_NEG,
873                                           (1 + random2(damage_done)) / 2);
874 
875     if (defender->drain(attacker, true, 1 + damage_done))
876     {
877         if (defender->is_player())
878             obvious_effect = true;
879         else if (defender_visible)
880         {
881             special_damage_message =
882                 make_stringf(
883                     "%s %s %s%s",
884                     atk_name(DESC_THE).c_str(),
885                     attacker->conj_verb("drain").c_str(),
886                     defender_name(true).c_str(),
887                     attack_strength_punctuation(special_damage).c_str());
888         }
889     }
890 }
891 
drain_defender_speed()892 void attack::drain_defender_speed()
893 {
894     if (needs_message)
895     {
896         mprf("%s %s %s vigour!",
897              atk_name(DESC_THE).c_str(),
898              attacker->conj_verb("drain").c_str(),
899              def_name(DESC_ITS).c_str());
900     }
901     defender->slow_down(attacker, 5 + random2(7));
902 }
903 
inflict_damage(int dam,beam_type flavour,bool clean)904 int attack::inflict_damage(int dam, beam_type flavour, bool clean)
905 {
906     if (flavour == NUM_BEAMS)
907         flavour = special_damage_flavour;
908     // Auxes temporarily clear damage_brand so we don't need to check
909     if (damage_brand == SPWPN_REAPING
910         || damage_brand == SPWPN_CHAOS && one_chance_in(100))
911     {
912         defender->props["reaping_damage"].get_int() += dam;
913         // With two reapers of different friendliness, the most recent one
914         // gets the zombie. Too rare a case to care any more.
915         defender->props["reaper"].get_int() = attacker->mid;
916     }
917     return defender->hurt(responsible, dam, flavour, kill_type,
918                           "", aux_source.c_str(), clean);
919 }
920 
921 /* If debug, return formatted damage done
922  *
923  */
debug_damage_number()924 string attack::debug_damage_number()
925 {
926 #ifdef DEBUG_DIAGNOSTICS
927     return make_stringf(" for %d", damage_done);
928 #else
929     return "";
930 #endif
931 }
932 
933 /* Returns standard attack punctuation
934  *
935  * Used in player / monster (both primary and aux) attacks
936  */
attack_strength_punctuation(int dmg)937 string attack_strength_punctuation(int dmg)
938 {
939     if (dmg < HIT_WEAK)
940         return ".";
941     else if (dmg < HIT_MED)
942         return "!";
943     else if (dmg < HIT_STRONG)
944         return "!!";
945     else
946         return string(3 + (int) log2(dmg / HIT_STRONG), '!');
947 }
948 
949 /* Returns evasion adverb
950  *
951  */
evasion_margin_adverb()952 string attack::evasion_margin_adverb()
953 {
954     return (ev_margin <= -20) ? " completely" :
955            (ev_margin <= -12) ? "" :
956            (ev_margin <= -6)  ? " closely"
957                               : " barely";
958 }
959 
stab_message()960 void attack::stab_message()
961 {
962     defender->props["helpless"] = true;
963 
964     switch (stab_bonus)
965     {
966     case 6:     // big melee, monster surrounded/not paying attention
967         if (coinflip())
968         {
969             mprf("You %s %s from a blind spot!",
970                   you.has_mutation(MUT_PAWS) ? "pounce on" : "strike",
971                   defender->name(DESC_THE).c_str());
972         }
973         else
974         {
975             mprf("You catch %s momentarily off-guard.",
976                   defender->name(DESC_THE).c_str());
977         }
978         break;
979     case 4:     // confused/fleeing
980         if (!one_chance_in(3))
981         {
982             mprf("You catch %s completely off-guard!",
983                   defender->name(DESC_THE).c_str());
984         }
985         else
986         {
987             mprf("You %s %s from behind!",
988                   you.has_mutation(MUT_PAWS) ? "pounce on" : "strike",
989                   defender->name(DESC_THE).c_str());
990         }
991         break;
992     case 2:
993     case 1:
994         if (you.has_mutation(MUT_PAWS) && coinflip())
995         {
996             mprf("You pounce on the unaware %s!",
997                  defender->name(DESC_PLAIN).c_str());
998             break;
999         }
1000         mprf("%s fails to defend %s.",
1001               defender->name(DESC_THE).c_str(),
1002               defender->pronoun(PRONOUN_REFLEXIVE).c_str());
1003         break;
1004     }
1005 
1006     defender->props.erase("helpless");
1007 }
1008 
1009 /* Returns the attacker's name
1010  *
1011  * Helper method to easily access the attacker's name
1012  */
atk_name(description_level_type desc)1013 string attack::atk_name(description_level_type desc)
1014 {
1015     return actor_name(attacker, desc, attacker_visible);
1016 }
1017 
1018 /* Returns the defender's name
1019  *
1020  * Helper method to easily access the defender's name
1021  */
def_name(description_level_type desc)1022 string attack::def_name(description_level_type desc)
1023 {
1024     return actor_name(defender, desc, defender_visible);
1025 }
1026 
1027 /* Returns the attacking weapon's name
1028  *
1029  * Sets upthe appropriate descriptive level and obtains the name of a weapon
1030  * based on if the attacker is a player or non-player (non-players use a
1031  * plain name and a manually entered pronoun)
1032  */
wep_name(description_level_type desc,iflags_t ignre_flags)1033 string attack::wep_name(description_level_type desc, iflags_t ignre_flags)
1034 {
1035     ASSERT(weapon != nullptr);
1036 
1037     if (attacker->is_player())
1038         return weapon->name(desc, false, false, false, false, ignre_flags);
1039 
1040     string name;
1041     bool possessive = false;
1042     if (desc == DESC_YOUR)
1043     {
1044         desc       = DESC_THE;
1045         possessive = true;
1046     }
1047 
1048     if (possessive)
1049         name = apostrophise(atk_name(desc)) + " ";
1050 
1051     name += weapon->name(DESC_PLAIN, false, false, false, false, ignre_flags);
1052 
1053     return name;
1054 }
1055 
1056 /* TODO: Remove this!
1057  * Removing it may not really be practical, in retrospect. Its only used
1058  * below, in calc_elemental_brand_damage, which is called for both frost and
1059  * flame brands for both players and monsters.
1060  */
defender_name(bool allow_reflexive)1061 string attack::defender_name(bool allow_reflexive)
1062 {
1063     if (allow_reflexive && attacker == defender)
1064         return actor_pronoun(attacker, PRONOUN_REFLEXIVE, attacker_visible);
1065     else
1066         return def_name(DESC_THE);
1067 }
1068 
player_stat_modify_damage(int damage)1069 int attack::player_stat_modify_damage(int damage)
1070 {
1071     // At 10 strength, damage is multiplied by 1.0
1072     // Each point of strength over 10 increases this by 0.025 (2.5%),
1073     // strength below 10 reduces the multiplied by the same amount.
1074     // Minimum multiplier is 0.01 (1%) (reached at -30 str).
1075     damage *= max(1.0, 75 + 2.5 * you.strength());
1076     damage /= 100;
1077 
1078     return damage;
1079 }
1080 
player_apply_weapon_skill(int damage)1081 int attack::player_apply_weapon_skill(int damage)
1082 {
1083     if (using_weapon())
1084     {
1085         damage *= 2500 + (random2(you.skill(wpn_skill, 100) + 1));
1086         damage /= 2500;
1087     }
1088 
1089     return damage;
1090 }
1091 
player_apply_fighting_skill(int damage,bool aux)1092 int attack::player_apply_fighting_skill(int damage, bool aux)
1093 {
1094     const int base = aux? 40 : 30;
1095 
1096     damage *= base * 100 + (random2(you.skill(SK_FIGHTING, 100) + 1));
1097     damage /= base * 100;
1098 
1099     return damage;
1100 }
1101 
player_apply_misc_modifiers(int damage)1102 int attack::player_apply_misc_modifiers(int damage)
1103 {
1104     return damage;
1105 }
1106 
1107 /**
1108  * Get the damage bonus from a weapon's enchantment.
1109  */
get_weapon_plus()1110 int attack::get_weapon_plus()
1111 {
1112     if (weapon->base_type == OBJ_STAVES
1113 #if TAG_MAJOR_VERSION == 34
1114         || weapon->sub_type == WPN_BLOWGUN
1115         || weapon->base_type == OBJ_RODS
1116 #endif
1117        )
1118     {
1119         return 0;
1120     }
1121     return weapon->plus;
1122 }
1123 
_core_apply_slaying(int damage,int plus)1124 static int _core_apply_slaying(int damage, int plus)
1125 {
1126     // +0 is random2(1) (which is 0)
1127     if (plus >= 0)
1128         return damage + random2(1 + plus);
1129     else
1130         return damage - random2(1 - plus);
1131 }
1132 
1133 // Slaying and weapon enchantment. Apply this for slaying even if not
1134 // using a weapon to attack.
player_apply_slaying_bonuses(int damage,bool aux)1135 int attack::player_apply_slaying_bonuses(int damage, bool aux)
1136 {
1137     int damage_plus = 0;
1138     if (!aux && using_weapon())
1139         damage_plus = get_weapon_plus();
1140     if (you.duration[DUR_CORROSION])
1141         damage_plus -= 4 * you.props["corrosion_amount"].get_int();
1142     damage_plus += slaying_bonus(!weapon && wpn_skill == SK_THROWING
1143                                  || (weapon && is_range_weapon(*weapon)
1144                                             && using_weapon()));
1145 
1146     return _core_apply_slaying(damage, damage_plus);
1147 }
1148 
player_apply_final_multipliers(int damage)1149 int attack::player_apply_final_multipliers(int damage)
1150 {
1151     // Can't affect much of anything as a shadow.
1152     if (you.form == transformation::shadow)
1153         damage = div_rand_round(damage, 2);
1154 
1155     return damage;
1156 }
1157 
player_exercise_combat_skills()1158 void attack::player_exercise_combat_skills()
1159 {
1160 }
1161 
1162 /* Returns attacker base unarmed damage
1163  *
1164  * Scales for current mutations and unarmed effects
1165  * TODO: Complete symmetry for base_unarmed damage
1166  * between monsters and players.
1167  */
calc_base_unarmed_damage()1168 int attack::calc_base_unarmed_damage()
1169 {
1170     // Should only get here if we're not wielding something that's a weapon.
1171     // If there's a non-weapon in hand, it has no base damage.
1172     if (weapon)
1173         return 0;
1174 
1175     if (!attacker->is_player())
1176         return 0;
1177 
1178     int damage = get_form()->get_base_unarmed_damage();
1179 
1180     // Claw damage only applies for bare hands.
1181     if (you.has_usable_claws())
1182         damage += you.has_claws() * 2;
1183 
1184     if (you.form_uses_xl())
1185         damage += div_rand_round(you.experience_level, 3);
1186     else
1187         damage += you.skill_rdiv(wpn_skill);
1188 
1189     if (damage < 0)
1190         damage = 0;
1191 
1192     return damage;
1193 }
1194 
calc_damage()1195 int attack::calc_damage()
1196 {
1197     if (attacker->is_monster())
1198     {
1199         int damage = 0;
1200         int damage_max = 0;
1201         if (using_weapon() || wpn_skill == SK_THROWING)
1202         {
1203             damage_max = weapon_damage();
1204             damage += random2(damage_max);
1205 
1206             int wpn_damage_plus = 0;
1207             if (weapon) // can be 0 for throwing projectiles
1208                 wpn_damage_plus = get_weapon_plus();
1209 
1210             const int jewellery = attacker->as_monster()->inv[MSLOT_JEWELLERY];
1211             if (jewellery != NON_ITEM
1212                 && env.item[jewellery].is_type(OBJ_JEWELLERY, RING_SLAYING))
1213             {
1214                 wpn_damage_plus += env.item[jewellery].plus;
1215             }
1216 
1217             wpn_damage_plus += attacker->scan_artefacts(ARTP_SLAYING);
1218 
1219             damage = _core_apply_slaying(damage, wpn_damage_plus);
1220         }
1221 
1222         damage_max += attk_damage;
1223         damage     += 1 + random2(attk_damage);
1224 
1225         damage = apply_damage_modifiers(damage);
1226 
1227         set_attack_verb(damage);
1228         return apply_defender_ac(damage, damage_max);
1229     }
1230     else
1231     {
1232         int potential_damage, damage;
1233 
1234         potential_damage = using_weapon() || wpn_skill == SK_THROWING
1235             ? weapon_damage() : calc_base_unarmed_damage();
1236 
1237         potential_damage = player_stat_modify_damage(potential_damage);
1238 
1239         damage = random2(potential_damage+1);
1240 
1241         damage = player_apply_weapon_skill(damage);
1242         damage = player_apply_fighting_skill(damage, false);
1243         damage = player_apply_misc_modifiers(damage);
1244         damage = player_apply_slaying_bonuses(damage, false);
1245         damage = player_stab(damage);
1246         // A failed stab may have awakened monsters, but that could have
1247         // caused the defender to cease to exist (spectral weapons with
1248         // missing summoners; or pacified monsters on a stair). FIXME:
1249         // The correct thing to do would be either to delay the call to
1250         // alert_nearby_monsters (currently in player_stab) until later
1251         // in the attack; or to avoid removing monsters in handle_behaviour.
1252         if (!defender->alive())
1253             return 0;
1254         damage = player_apply_final_multipliers(damage);
1255         damage = apply_defender_ac(damage);
1256 
1257         damage = max(0, damage);
1258         set_attack_verb(damage);
1259 
1260         return damage;
1261     }
1262 
1263     return 0;
1264 }
1265 
test_hit(int to_land,int ev,bool randomise_ev)1266 int attack::test_hit(int to_land, int ev, bool randomise_ev)
1267 {
1268     int margin = AUTOMATIC_HIT;
1269     if (randomise_ev)
1270         ev = random2avg(2*ev, 2);
1271     if (to_land >= AUTOMATIC_HIT)
1272         return true;
1273     else if (x_chance_in_y(MIN_HIT_MISS_PERCENTAGE, 100))
1274         margin = (random2(2) ? 1 : -1) * AUTOMATIC_HIT;
1275     else
1276         margin = to_land - ev;
1277 
1278 #ifdef DEBUG_DIAGNOSTICS
1279     dprf(DIAG_COMBAT, "to hit: %d; ev: %d; result: %s (%d)",
1280          to_hit, ev, (margin >= 0) ? "hit" : "miss", margin);
1281 #endif
1282 
1283     return margin;
1284 }
1285 
apply_defender_ac(int damage,int damage_max,ac_type ac_rule) const1286 int attack::apply_defender_ac(int damage, int damage_max, ac_type ac_rule) const
1287 {
1288     ASSERT(defender);
1289     int after_ac = defender->apply_ac(damage, damage_max, ac_rule);
1290     dprf(DIAG_COMBAT, "AC: att: %s, def: %s, ac: %d, gdr: %d, dam: %d -> %d",
1291                  attacker->name(DESC_PLAIN, true).c_str(),
1292                  defender->name(DESC_PLAIN, true).c_str(),
1293                  defender->armour_class(),
1294                  defender->gdr_perc(),
1295                  damage,
1296                  after_ac);
1297 
1298     return after_ac;
1299 }
1300 
1301 /* Determine whether a block occurred
1302  *
1303  * No blocks if defender is incapacitated, would be nice to eventually expand
1304  * this method to handle partial blocks as well as full blocks (although this
1305  * would serve as a nerf to shields and - while more realistic - may not be a
1306  * good mechanic for shields.
1307  *
1308  * Returns (block_occurred)
1309  */
attack_shield_blocked(bool verbose)1310 bool attack::attack_shield_blocked(bool verbose)
1311 {
1312     if (defender == attacker)
1313         return false; // You can't block your own attacks!
1314 
1315     if (defender->incapacitated())
1316         return false;
1317 
1318     const int con_block = random2(attacker->shield_bypass_ability(to_hit)
1319                                   + defender->shield_block_penalty());
1320     int pro_block = defender->shield_bonus();
1321 
1322     if (!attacker->visible_to(defender))
1323         pro_block /= 3;
1324 
1325     dprf(DIAG_COMBAT, "Defender: %s, Pro-block: %d, Con-block: %d",
1326          def_name(DESC_PLAIN).c_str(), pro_block, con_block);
1327 
1328     if (pro_block >= con_block)
1329     {
1330         perceived_attack = true;
1331 
1332         if (ignores_shield(verbose))
1333             return false;
1334 
1335         if (needs_message && verbose)
1336         {
1337             mprf("%s %s %s attack.",
1338                  defender_name(false).c_str(),
1339                  defender->conj_verb("block").c_str(),
1340                  attacker == defender ? "its own"
1341                                       : atk_name(DESC_ITS).c_str());
1342         }
1343 
1344         defender->shield_block_succeeded();
1345 
1346         return true;
1347     }
1348 
1349     return false;
1350 }
1351 
apply_poison_damage_brand()1352 bool attack::apply_poison_damage_brand()
1353 {
1354     if (!one_chance_in(4))
1355     {
1356         int old_poison;
1357 
1358         if (defender->is_player())
1359             old_poison = you.duration[DUR_POISONING];
1360         else
1361         {
1362             old_poison =
1363                 (defender->as_monster()->get_ench(ENCH_POISON)).degree;
1364         }
1365 
1366         defender->poison(attacker, 6 + random2(8) + random2(damage_done * 3 / 2));
1367 
1368         if (defender->is_player()
1369                && old_poison < you.duration[DUR_POISONING]
1370             || !defender->is_player()
1371                && old_poison <
1372                   (defender->as_monster()->get_ench(ENCH_POISON)).degree)
1373         {
1374             return true;
1375         }
1376     }
1377     return false;
1378 }
1379 
apply_damage_brand(const char * what)1380 bool attack::apply_damage_brand(const char *what)
1381 {
1382     bool brand_was_known = false;
1383     int brand = 0;
1384     bool ret = false;
1385 
1386     if (using_weapon())
1387         brand_was_known = item_brand_known(*weapon);
1388 
1389     special_damage = 0;
1390     obvious_effect = false;
1391     brand = damage_brand == SPWPN_CHAOS ? random_chaos_brand() : damage_brand;
1392 
1393     if (brand != SPWPN_FLAMING && brand != SPWPN_FREEZING
1394         && brand != SPWPN_ELECTROCUTION && brand != SPWPN_VAMPIRISM
1395         && brand != SPWPN_PROTECTION && !defender->alive())
1396     {
1397         // Most brands have no extra effects on just killed enemies, and the
1398         // effect would be often inappropriate.
1399         return false;
1400     }
1401 
1402     if (!damage_done
1403         && (brand == SPWPN_FLAMING || brand == SPWPN_FREEZING
1404             || brand == SPWPN_HOLY_WRATH || brand == SPWPN_ANTIMAGIC
1405             || brand == SPWPN_VORPAL || brand == SPWPN_VAMPIRISM))
1406     {
1407         // These brands require some regular damage to function.
1408         return false;
1409     }
1410 
1411     switch (brand)
1412     {
1413     case SPWPN_PROTECTION:
1414         if (attacker->is_player())
1415         {
1416             const monster* mon = defender->as_monster();
1417             if (mon && !mons_is_firewood(*mon))
1418                 refresh_weapon_protection();
1419         }
1420         break;
1421 
1422     case SPWPN_FLAMING:
1423         calc_elemental_brand_damage(BEAM_FIRE,
1424                                     defender->is_icy() ? "melt" : "burn",
1425                                     what);
1426         defender->expose_to_element(BEAM_FIRE, 2);
1427         if (defender->is_player())
1428             maybe_melt_player_enchantments(BEAM_FIRE, special_damage);
1429         break;
1430 
1431     case SPWPN_FREEZING:
1432         calc_elemental_brand_damage(BEAM_COLD, "freeze", what);
1433         defender->expose_to_element(BEAM_COLD, 2);
1434         break;
1435 
1436     case SPWPN_HOLY_WRATH:
1437         if (defender->holy_wrath_susceptible())
1438             special_damage = 1 + (random2(damage_done * 15) / 10);
1439 
1440         if (special_damage && defender_visible)
1441         {
1442             special_damage_message =
1443                 make_stringf(
1444                     "%s %s%s",
1445                     defender_name(false).c_str(),
1446                     defender->conj_verb("convulse").c_str(),
1447                     attack_strength_punctuation(special_damage).c_str());
1448         }
1449         break;
1450 
1451     case SPWPN_ELECTROCUTION:
1452         if (defender->res_elec() > 0)
1453             break;
1454         else if (one_chance_in(3))
1455         {
1456             special_damage = 8 + random2(13);
1457             const string punctuation =
1458                     attack_strength_punctuation(special_damage);
1459             special_damage_message =
1460                 defender->is_player()
1461                 ? make_stringf("You are electrocuted%s", punctuation.c_str())
1462                 : make_stringf("Lightning courses through %s%s",
1463                                defender->name(DESC_THE).c_str(),
1464                                punctuation.c_str());
1465             special_damage_flavour = BEAM_ELECTRICITY;
1466             defender->expose_to_element(BEAM_ELECTRICITY, 2);
1467         }
1468 
1469         break;
1470 
1471     case SPWPN_VENOM:
1472         obvious_effect = apply_poison_damage_brand();
1473         break;
1474 
1475     case SPWPN_DRAINING:
1476         drain_defender();
1477         break;
1478 
1479     case SPWPN_VORPAL:
1480         special_damage = 1 + random2(damage_done) / 3;
1481         // Note: Leaving special_damage_message empty because there isn't one.
1482         break;
1483 
1484     case SPWPN_VAMPIRISM:
1485     {
1486         if (!weapon
1487             || damage_done < 1
1488             || !actor_is_susceptible_to_vampirism(*defender)
1489             || attacker->stat_hp() == attacker->stat_maxhp()
1490             || attacker->is_player() && you.duration[DUR_DEATHS_DOOR]
1491             || x_chance_in_y(2, 5)
1492                && !is_unrandom_artefact(*weapon, UNRAND_LEECH))
1493         {
1494             break;
1495         }
1496 
1497         int hp_boost = is_unrandom_artefact(*weapon, UNRAND_VAMPIRES_TOOTH)
1498                        ? damage_done : 1 + random2(damage_done);
1499         hp_boost = resist_adjust_damage(defender, BEAM_NEG, hp_boost);
1500 
1501         if (hp_boost)
1502         {
1503             obvious_effect = true;
1504 
1505             if (attacker->is_player())
1506                 canned_msg(MSG_GAIN_HEALTH);
1507             else if (attacker_visible)
1508             {
1509                 if (defender->is_player())
1510                 {
1511                     mprf("%s draws strength from your wounds!",
1512                          attacker->name(DESC_THE).c_str());
1513                 }
1514                 else
1515                 {
1516                     mprf("%s is healed.",
1517                          attacker->name(DESC_THE).c_str());
1518                 }
1519             }
1520 
1521             dprf(DIAG_COMBAT, "Vampiric Healing: damage %d, healed %d",
1522                  damage_done, hp_boost);
1523             attacker->heal(hp_boost);
1524         }
1525         break;
1526     }
1527     case SPWPN_PAIN:
1528         pain_affects_defender();
1529         break;
1530 
1531     case SPWPN_DISTORTION:
1532         ret = distortion_affects_defender();
1533         break;
1534 
1535     case SPWPN_CONFUSE:
1536     {
1537         // If a monster with a chaos weapon gets this brand, act like
1538         // AF_CONFUSE.
1539         if (attacker->is_monster())
1540         {
1541             if (one_chance_in(3))
1542             {
1543                 defender->confuse(attacker,
1544                                   1 + random2(3+attacker->get_hit_dice()));
1545             }
1546             break;
1547         }
1548 
1549         // Also used for players in fungus form.
1550         if (attacker->is_player()
1551             && you.form == transformation::fungus
1552             && !you.duration[DUR_CONFUSING_TOUCH]
1553             && defender->is_unbreathing())
1554         {
1555             break;
1556         }
1557 
1558         // Declaring these just to pass to the enchant function.
1559         bolt beam_temp;
1560         beam_temp.thrower   = attacker->is_player() ? KILL_YOU : KILL_MON;
1561         beam_temp.flavour   = BEAM_CONFUSION;
1562         beam_temp.source_id = attacker->mid;
1563 
1564         if (attacker->is_player() && damage_brand == SPWPN_CONFUSE
1565             && you.duration[DUR_CONFUSING_TOUCH])
1566         {
1567             beam_temp.ench_power = you.props["confusing touch power"].get_int();
1568             int margin;
1569             if (beam_temp.try_enchant_monster(defender->as_monster(), margin)
1570                     == MON_AFFECTED)
1571             {
1572                 you.duration[DUR_CONFUSING_TOUCH] = 0;
1573                 obvious_effect = false;
1574             }
1575         }
1576         else if (!x_chance_in_y(melee_confuse_chance(defender->get_hit_dice()),
1577                                                      100)
1578                  || defender->as_monster()->clarity())
1579         {
1580             beam_temp.apply_enchantment_to_monster(defender->as_monster());
1581             obvious_effect = beam_temp.obvious_effect;
1582             break;
1583         }
1584 
1585         break;
1586     }
1587 
1588     case SPWPN_CHAOS:
1589         chaos_affects_defender();
1590         break;
1591 
1592     case SPWPN_ANTIMAGIC:
1593         antimagic_affects_defender(damage_done * 8);
1594         break;
1595 
1596     case SPWPN_ACID:
1597         defender->splash_with_acid(attacker, 3);
1598         break;
1599 
1600 
1601     default:
1602         if (using_weapon() && is_unrandom_artefact(*weapon, UNRAND_DAMNATION))
1603             attacker->god_conduct(DID_EVIL, 2 + random2(3));
1604         break;
1605     }
1606 
1607     if (damage_brand == SPWPN_CHAOS)
1608     {
1609         if (responsible->is_player())
1610             did_god_conduct(DID_CHAOS, 2 + random2(3), brand_was_known);
1611     }
1612 
1613     if (!obvious_effect)
1614         obvious_effect = !special_damage_message.empty();
1615 
1616     if (needs_message && !special_damage_message.empty())
1617     {
1618         mpr(special_damage_message);
1619 
1620         special_damage_message.clear();
1621     }
1622 
1623     // Preserve Nessos's brand stacking in a hacky way -- but to be fair, it
1624     // was always a bit of a hack.
1625     if (attacker->type == MONS_NESSOS && weapon && is_range_weapon(*weapon))
1626         apply_poison_damage_brand();
1627 
1628     if (special_damage > 0)
1629         inflict_damage(special_damage, special_damage_flavour);
1630 
1631     if (obvious_effect && attacker_visible && using_weapon())
1632     {
1633         if (is_artefact(*weapon))
1634             artefact_learn_prop(*weapon, ARTP_BRAND);
1635         else
1636             set_ident_flags(*weapon, ISFLAG_KNOW_TYPE);
1637     }
1638 
1639     return ret;
1640 }
1641 
1642 /* Calculates special damage, prints appropriate combat text
1643  *
1644  * Applies a particular damage brand to the current attack, the setup and
1645  * calculation of base damage and other effects varies based on the type
1646  * of attack, but the calculation of elemental damage should be consistent.
1647  */
calc_elemental_brand_damage(beam_type flavour,const char * verb,const char * what)1648 void attack::calc_elemental_brand_damage(beam_type flavour,
1649                                          const char *verb,
1650                                          const char *what)
1651 {
1652     special_damage = resist_adjust_damage(defender, flavour,
1653                                           random2(damage_done) / 2 + 1);
1654 
1655     if (needs_message && special_damage > 0 && verb)
1656     {
1657         // XXX: assumes "what" is singular
1658         special_damage_message = make_stringf(
1659             "%s %s %s%s",
1660             what ? what : atk_name(DESC_THE).c_str(),
1661             what ? conjugate_verb(verb, false).c_str()
1662                  : attacker->conj_verb(verb).c_str(),
1663             // Don't allow reflexive if the subject wasn't the attacker.
1664             defender_name(!what).c_str(),
1665             attack_strength_punctuation(special_damage).c_str());
1666     }
1667 }
1668 
player_stab_weapon_bonus(int damage)1669 int attack::player_stab_weapon_bonus(int damage)
1670 {
1671     int stab_skill = you.skill(wpn_skill, 50) + you.skill(SK_STEALTH, 50);
1672 
1673     if (player_good_stab())
1674     {
1675         // We might be unarmed if we're using the hood of the Assassin.
1676         const bool extra_good = using_weapon() && weapon->sub_type == WPN_DAGGER;
1677         int bonus = you.dex() * (stab_skill + 100) / (extra_good ? 500 : 1000);
1678 
1679         bonus   = stepdown_value(bonus, 10, 10, 30, 30);
1680         damage += bonus;
1681         damage *= 10 + div_rand_round(stab_skill, 100 * stab_bonus);
1682         damage /= 10;
1683     }
1684 
1685     // There's both a flat and multiplicative component to
1686     // stab bonus damage.
1687 
1688     damage *= 12 + div_rand_round(stab_skill, 100 * stab_bonus);
1689     damage /= 12;
1690 
1691     // The flat component is loosely based on the old stab_bypass bonus.
1692     // Essentially, it's an extra quarter-point of damage for every
1693     // point of weapon + stealth skill, divided by stab_bonus - that is,
1694     // quartered again if the target isn't sleeping, paralysed, or petrified.
1695     damage += random2(div_rand_round(stab_skill, 200 * stab_bonus));
1696 
1697     return damage;
1698 }
1699 
player_stab(int damage)1700 int attack::player_stab(int damage)
1701 {
1702     // The stabbing message looks better here:
1703     if (stab_attempt)
1704     {
1705         // Construct reasonable message.
1706         stab_message();
1707         practise_stabbing();
1708     }
1709     else
1710     {
1711         stab_bonus = 0;
1712         // Ok.. if you didn't backstab, you wake up the neighborhood.
1713         // I can live with that.
1714         alert_nearby_monsters();
1715     }
1716 
1717     if (stab_bonus)
1718     {
1719         // Let's make sure we have some damage to work with...
1720         damage = max(1, damage);
1721 
1722         damage = player_stab_weapon_bonus(damage);
1723     }
1724 
1725     return damage;
1726 }
1727 
1728 /* Check for stab and prepare combat for stab-values
1729  *
1730  * Grant an automatic stab if paralysed or sleeping (with highest damage value)
1731  * stab_bonus is used as the divisor in damage calculations, so lower values
1732  * will yield higher damage. Normal stab chance is (stab_skill + dex + 1 / roll)
1733  * This averages out to about 1/3 chance for a non extended-endgame stabber.
1734  */
player_stab_check()1735 void attack::player_stab_check()
1736 {
1737     // XXX: move into find_stab_type?
1738     if (you.duration[DUR_CLUMSY] || you.confused())
1739     {
1740         stab_attempt = false;
1741         stab_bonus = 0;
1742         return;
1743     }
1744 
1745     stab_type st = find_stab_type(&you, *defender);
1746     // Find stab type is also used for displaying information about monsters,
1747     // so upgrade the stab type for !stab and the Spriggan's Knife here
1748     if (using_weapon()
1749         && is_unrandom_artefact(*weapon, UNRAND_SPRIGGANS_KNIFE)
1750         && st != STAB_NO_STAB)
1751     {
1752         st = STAB_SLEEPING;
1753     }
1754     stab_attempt = st != STAB_NO_STAB;
1755     stab_bonus = stab_bonus_denom(st);
1756 
1757     // See if we need to roll against dexterity / stabbing.
1758     if (stab_attempt && stab_bonus > 1)
1759     {
1760         stab_attempt = x_chance_in_y(you.skill_rdiv(wpn_skill, 1, 2)
1761                                      + you.skill_rdiv(SK_STEALTH, 1, 2)
1762                                      + you.dex() + 1,
1763                                      100);
1764     }
1765 
1766     if (stab_attempt)
1767         count_action(CACT_STAB, st);
1768 }
1769