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