3 * Summary: Methods of the attack class, generalized functions which may
4 * be overloaded by inheriting classes.
5 * Written by: Robert Burnham
24 #include "godconduct.h"
30 #include "mon-clone.h"
31 #include "mon-death.h"
33 #include "spl-miscast.h"
36 #include "stringutil.h"
37 #include "transform.h"
41 **************************************************
42 * BEGIN PUBLIC FUNCTIONS *
43 **************************************************
45 attack::attack(actor *attk, actor *defn, actor *blame)
46 : attacker(attk), defender(defn), responsible(blame ? blame : attk),
47 attack_occurred(false), cancel_attack(false), did_hit(false),
48 needs_message(false), attacker_visible(false), defender_visible(false),
49 perceived_attack(false), obvious_effect(false), to_hit(0),
50 damage_done(0), special_damage(0), aux_damage(0), min_delay(0),
51 final_attack_delay(0), special_damage_flavour(BEAM_NONE),
52 stab_attempt(false), stab_bonus(0), apply_bleeding(false),
53 ev_margin(0), weapon(nullptr),
54 damage_brand(SPWPN_NORMAL), wpn_skill(SK_UNARMED_COMBAT),
55 shield(nullptr), art_props(0), unrand_entry(nullptr),
56 attacker_to_hit_penalty(0), attack_verb("bug"), verb_degree(),
57 no_damage_message(), special_damage_message(), aux_attack(), aux_verb(),
58 attacker_armour_tohit_penalty(0), attacker_shield_tohit_penalty(0),
59 defender_shield(nullptr), miscast_level(-1), miscast_type(SPTYP_NONE),
60 miscast_target(nullptr), fake_chaos_attack(false), simu(false),
61 aux_source(""), kill_type(KILLED_BY_MONSTER)
63 // No effective code should execute, we'll call init_attack again from
64 // the child class, since initializing an attack will vary based the within
65 // type of attack actually being made (melee, ranged, etc.)
68 bool attack::handle_phase_attempted()
73 bool attack::handle_phase_blocked()
79 bool attack::handle_phase_damaged()
81 // We have to check in_bounds() because removed kraken tentacles are
82 // temporarily returned to existence (without a position) when they
84 if (defender->can_bleed()
85 && !defender->is_summoned()
86 && !defender->submerged()
87 && in_bounds(defender->pos())
90 int blood = damage_done;
91 if (blood > defender->stat_hp())
92 blood = defender->stat_hp();
94 blood_fineff::schedule(defender, defender->pos(), blood);
98 // Inflict stored damage
99 damage_done = inflict_damage(damage_done);
101 // TODO: Unify these, added here so we can get rid of player_attack
102 if (attacker->is_player())
105 player_exercise_combat_skills();
107 if (defender->alive())
109 // Actually apply the bleeding effect, this can come from an
110 // aux claw or a main hand claw attack and up to now has not
111 // actually happened.
112 const int degree = you.has_usable_claws();
113 if (apply_bleeding && defender->can_bleed()
114 && degree > 0 && damage_done > 0)
116 defender->as_monster()->bleed(attacker,
117 3 + roll_dice(degree, 3),
124 if (!mons_attack_effects())
128 // It's okay if a monster took lethal damage, but we should stop
129 // the combat if it was already reset (e.g. a spectral weapon that
130 // took damage and then noticed that its caster is gone).
131 return defender->is_player() || !invalid_monster(defender->as_monster());
134 bool attack::handle_phase_killed()
136 monster * const mon = defender->as_monster();
137 if (!invalid_monster(mon))
138 monster_die(mon, attacker);
143 bool attack::handle_phase_end()
145 // This may invalidate both the attacker and defender.
146 fire_final_effects();
152 * Calculate the to-hit for an attacker
154 * @param random If false, calculate average to-hit deterministically.
156 int attack::calc_to_hit(bool random)
158 int mhit = attacker->is_player() ?
159 15 + (calc_stat_to_hit_base() / 2)
160 : calc_mon_to_hit_base();
162 #ifdef DEBUG_DIAGNOSTICS
163 const int base_hit = mhit;
166 // This if statement is temporary, it should be removed when the
167 // implementation of a more universal (and elegant) to-hit calculation
168 // is designed. The actual code is copied from the old mons_to_hit and
169 // player_to_hit methods.
170 if (attacker->is_player())
172 // fighting contribution
173 mhit += maybe_random_div(you.skill(SK_FIGHTING, 100), 100, random);
175 // weapon skill contribution
178 if (wpn_skill != SK_FIGHTING)
180 if (you.skill(wpn_skill) < 1 && player_in_a_dangerous_place())
181 xom_is_stimulated(10); // Xom thinks that is mildly amusing.
183 mhit += maybe_random_div(you.skill(wpn_skill, 100), 100,
187 else if (you.form_uses_xl())
188 mhit += maybe_random_div(you.experience_level * 100, 100, random);
190 { // ...you must be unarmed or throwing
191 // Members of clawed species have presumably been using the claws,
192 // making them more practiced and thus more accurate in unarmed
193 // combat. They keep this benefit even when the claws are covered
194 // (or missing because of a change in form).
195 mhit += you.innate_mutation[MUT_CLAWS]
196 && wpn_skill == SK_UNARMED_COMBAT ? 4 : 2;
198 mhit += maybe_random_div(you.skill(wpn_skill, 100), 100,
202 // weapon bonus contribution
205 if (weapon->base_type == OBJ_WEAPONS)
207 mhit += weapon->plus;
208 mhit += property(*weapon, PWPN_HIT);
210 else if (weapon->base_type == OBJ_STAVES)
211 mhit += property(*weapon, PWPN_HIT);
212 else if (weapon->base_type == OBJ_RODS)
214 mhit += property(*weapon, PWPN_HIT);
215 mhit += weapon->special;
220 mhit += slaying_bonus(wpn_skill == SK_THROWING
221 || (weapon && is_range_weapon(*weapon)
225 if (you.duration[DUR_HORROR])
226 mhit -= you.props[HORROR_PENALTY_KEY].get_int();
229 if (you.hunger_state == HS_STARVING)
233 mhit -= (attacker_armour_tohit_penalty + attacker_shield_tohit_penalty);
236 if (player_mutation_level(MUT_EYEBALLS))
237 mhit += 2 * player_mutation_level(MUT_EYEBALLS) + 1;
240 mhit = maybe_random2(mhit, random);
242 else // Monster to-hit.
245 mhit += weapon->plus + property(*weapon, PWPN_HIT);
247 const int jewellery = attacker->as_monster()->inv[MSLOT_JEWELLERY];
248 if (jewellery != NON_ITEM
249 && mitm[jewellery].is_type(OBJ_JEWELLERY, RING_SLAYING))
251 mhit += mitm[jewellery].plus;
254 mhit += attacker->scan_artefacts(ARTP_SLAYING);
256 if (using_weapon() && weapon->base_type == OBJ_RODS)
257 mhit += weapon->special;
260 // Penalties for both players and monsters:
262 if (attacker->inaccuracy())
265 // If you can't see yourself, you're a little less accurate.
266 if (!attacker->visible_to(attacker))
269 if (attacker->confused())
272 if (using_weapon() && is_unrandom_artefact(*weapon, UNRAND_WOE))
273 return AUTOMATIC_HIT;
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)
280 if (!defender->visible_to(attacker))
281 if (attacker->is_player())
284 mhit = mhit * 65 / 100;
287 // This can only help if you're visible!
288 const int how_transparent = player_mutation_level(MUT_TRANSLUCENT_SKIN);
289 if (defender->is_player() && how_transparent)
290 mhit -= 2 * how_transparent;
292 if (defender->backlit(false))
293 mhit += 2 + random2(8);
294 else if (!attacker->nightvision()
295 && defender->umbra())
296 mhit -= 2 + random2(4);
298 // Don't delay doing this roll until test_hit().
299 if (!attacker->is_player())
300 mhit = random2(mhit + 1);
302 dprf(DIAG_COMBAT, "%s: Base to-hit: %d, Final to-hit: %d",
303 attacker->name(DESC_PLAIN).c_str(),
309 /* Returns an actor's name
311 * Takes into account actor visibility/invisibility and the type of description
312 * to be used (capitalization, possessiveness, etc.)
314 string attack::actor_name(const actor *a, description_level_type desc,
317 return actor_visible ? a->name(desc) : anon_name(desc);
320 /* Returns an actor's pronoun
322 * Takes into account actor visibility
324 string attack::actor_pronoun(const actor *a, pronoun_type pron,
327 return actor_visible ? a->pronoun(pron) : anon_pronoun(pron);
330 /* Returns an anonymous actor's name
332 * Given the actor visible or invisible, returns the
333 * appropriate possessive pronoun.
335 string attack::anon_name(description_level_type desc)
352 /* Returns an anonymous actor's pronoun
354 * Given invisibility (whether out of LOS or just invisible), returns the
355 * appropriate possessive, inflexive, capitalised pronoun.
357 string attack::anon_pronoun(pronoun_type pron)
359 return decline_pronoun(GENDER_NEUTER, pron);
362 /* Initializes an attack, setting up base variables and values
364 * Does not make any changes to any actors, items, or the environment,
365 * in case the attack is cancelled or otherwise fails. Only initializations
366 * that are universal to all types of attacks should go into this method,
367 * any initialization properties that are specific to one attack or another
368 * should go into their respective init_attack.
370 * Although this method will get overloaded by the derived class, we are
371 * calling it from attack::attack(), before the overloading has taken place.
373 void attack::init_attack(skill_type unarmed_skill, int attack_number)
375 weapon = attacker->weapon(attack_number);
377 wpn_skill = weapon ? item_attack_skill(*weapon) : unarmed_skill;
378 if (attacker->is_player() && you.form_uses_xl())
379 wpn_skill = SK_FIGHTING; // for stabbing, mostly
381 attacker_armour_tohit_penalty =
382 div_rand_round(attacker->armour_tohit_penalty(true, 20), 20);
383 attacker_shield_tohit_penalty =
384 div_rand_round(attacker->shield_tohit_penalty(true, 20), 20);
385 to_hit = calc_to_hit(true);
387 shield = attacker->shield();
388 defender_shield = defender ? defender->shield() : defender_shield;
390 if (weapon && weapon->base_type == OBJ_WEAPONS && is_artefact(*weapon))
392 artefact_properties(*weapon, art_props);
393 if (is_unrandom_artefact(*weapon))
394 unrand_entry = get_unrand_entry(weapon->special);
397 attacker_visible = attacker->observable();
398 defender_visible = defender && defender->observable();
399 needs_message = (attacker_visible || defender_visible);
401 if (attacker->is_monster())
403 mon_attack_def mon_attk = mons_attack_spec(attacker->as_monster(),
406 attk_type = mon_attk.type;
407 attk_flavour = mon_attk.flavour;
409 // Don't scale damage for YOU_FAULTLESS etc.
410 if (attacker->get_experience_level() == 0)
411 attk_damage = mon_attk.damage;
414 attk_damage = div_rand_round(mon_attk.damage
415 * attacker->get_hit_dice(),
416 attacker->get_experience_level());
419 if (attk_type == AT_WEAP_ONLY)
421 int weap = attacker->as_monster()->inv[MSLOT_WEAPON];
422 if (weap == NON_ITEM || is_range_weapon(mitm[weap]))
427 else if (attk_type == AT_TRUNK_SLAP && attacker->type == MONS_SKELETON)
429 // Elephant trunks have no bones inside.
436 attk_flavour = AF_PLAIN;
440 void attack::alert_defender()
442 // Allow monster attacks to draw the ire of the defender. Player
443 // attacks are handled elsewhere.
445 && defender->is_monster()
446 && attacker->is_monster()
447 && attacker->alive() && defender->alive()
448 && (defender->as_monster()->foe == MHITNOT || one_chance_in(3)))
450 behaviour_event(defender->as_monster(), ME_WHACK, attacker);
453 // If an enemy attacked a friend, set the pet target if it isn't set
454 // already, but not if sanctuary is in effect (pet target must be
455 // set explicitly by the player during sanctuary).
456 if (perceived_attack && attacker->alive()
457 && (defender->is_player() || defender->as_monster()->friendly())
458 && !attacker->is_player()
459 && !crawl_state.game_is_arena()
460 && !attacker->as_monster()->wont_attack())
462 if (defender->is_player())
463 interrupt_activity(AI_MONSTER_ATTACKS, attacker->as_monster());
464 if (you.pet_target == MHITNOT && env.sanctuary_time <= 0)
465 you.pet_target = attacker->mindex();
469 bool attack::distortion_affects_defender()
471 //jmf: blink frogs *like* distortion
472 // I think could be amended to let blink frogs "grow" like
474 if (defender->is_monster()
475 && mons_genus(defender->type) == MONS_BLINK_FROG)
477 if (one_chance_in(5))
479 if (defender_visible)
481 special_damage_message =
482 make_stringf("%s %s in the distortional energy.",
483 defender_name(false).c_str(),
484 defender->conj_verb("bask").c_str());
487 defender->heal(1 + random2avg(7, 2), true); // heh heh
492 if (one_chance_in(3))
494 special_damage_message =
495 make_stringf("Space bends around %s.",
496 defender_name(false).c_str());
497 special_damage += 1 + random2avg(7, 2);
501 if (one_chance_in(3))
503 special_damage_message =
504 make_stringf("Space warps horribly around %s!",
505 defender_name(false).c_str());
506 special_damage += 3 + random2avg(24, 2);
513 if (one_chance_in(3))
515 if (defender_visible)
516 obvious_effect = true;
517 if (!defender->no_tele(true, false))
518 blink_fineff::schedule(defender);
522 if (!one_chance_in(3))
524 if (defender_visible)
525 obvious_effect = true;
526 if (crawl_state.game_is_sprint() && defender->is_player()
527 || defender->no_tele())
529 if (defender->is_player())
530 canned_msg(MSG_STRANGE_STASIS);
533 distortion_tele_fineff::schedule(defender);
535 defender->teleport();
539 if (!player_in_branch(BRANCH_ABYSS) && coinflip())
541 if (defender_visible)
542 obvious_effect = true;
544 defender->banish(attacker, attacker->name(DESC_PLAIN, true));
551 void attack::antimagic_affects_defender(int pow)
554 enchant_actor_with_flavour(defender, nullptr, BEAM_DRAIN_MAGIC, pow);
557 void attack::pain_affects_defender()
559 if (defender->res_negative_energy())
562 if (!one_chance_in(attacker->skill_rdiv(SK_NECROMANCY) + 1))
564 if (defender_visible)
566 special_damage_message =
567 make_stringf("%s %s in agony.",
568 defender->name(DESC_THE).c_str(),
569 defender->conj_verb("writhe").c_str());
571 special_damage += random2(1 + attacker->skill_rdiv(SK_NECROMANCY));
574 attacker->god_conduct(DID_NECROMANCY, 4);
577 // TODO: Move this somewhere sane
595 // XXX: We might want to vary the probabilities for the various effects
596 // based on whether the source is a weapon of chaos or a monster with
598 void attack::chaos_affects_defender()
600 monster * const mon = defender->as_monster();
601 const bool firewood = mon && mons_is_firewood(mon);
602 const bool immune = mon && mons_immune_magic(mon);
603 const bool is_natural = mon && defender->holiness() == MH_NATURAL;
604 const bool is_shifter = mon && mon->is_shapeshifter();
605 const bool can_clone = mon && mons_clonable(mon, true);
606 const bool can_poly = is_shifter || (defender->can_safely_mutate()
607 && !immune && !firewood);
608 const bool can_rage = defender->can_go_berserk();
609 const bool can_slow = !firewood;
610 const bool can_petrify= mon && !defender->res_petrify();
612 int clone_chance = can_clone ? 1 : 0;
613 int poly_chance = can_poly ? 1 : 0;
614 int poly_up_chance = can_poly && mon ? 1 : 0;
615 int shifter_chance = can_poly && is_natural && mon ? 1 : 0;
616 int rage_chance = can_rage ? 10 : 0;
617 int miscast_chance = 10;
618 int slowpara_chance= can_slow ? 10 : 0;
619 int petrify_chance = can_slow && can_petrify ? 10 : 0;
621 // Already a shifter?
625 // A chaos self-attack increases the chance of certain effects,
626 // due to a short-circuit/feedback/resonance/whatever.
627 if (attacker == defender)
635 // Inform player that something is up.
636 if (!fake_chaos_attack && you.see_cell(defender->pos()))
638 if (defender->is_player())
639 mpr("You give off a flash of multicoloured light!");
640 else if (you.can_see(defender))
642 simple_monster_message(mon, " gives off a flash of"
643 " multicoloured light!");
646 mpr("There is a flash of multicoloured light!");
650 // NOTE: Must appear in exact same order as in chaos_type enumeration.
651 int probs[NUM_CHAOS_TYPES] =
653 clone_chance, // CHAOS_CLONE
654 poly_chance, // CHAOS_POLY
655 poly_up_chance, // CHAOS_POLY_UP
656 shifter_chance, // CHAOS_MAKE_SHIFTER
657 miscast_chance, // CHAOS_MISCAST
658 rage_chance, // CHAOS_RAGE
661 slowpara_chance,// CHAOS_HASTE
664 slowpara_chance,// CHAOS_SLOW
665 slowpara_chance,// CHAOS_PARALYSIS
666 petrify_chance, // CHAOS_PETRIFY
670 beam.flavour = BEAM_NONE;
672 int choice = choose_random_weighted(probs, probs + NUM_CHAOS_TYPES);
673 #ifdef NOTE_DEBUG_CHAOS_EFFECTS
674 string chaos_effect = "CHAOS effect: ";
677 case CHAOS_CLONE: chaos_effect += "clone"; break;
678 case CHAOS_POLY: chaos_effect += "polymorph"; break;
679 case CHAOS_POLY_UP: chaos_effect += "polymorph PPT_MORE"; break;
680 case CHAOS_MAKE_SHIFTER: chaos_effect += "shifter"; break;
681 case CHAOS_MISCAST: chaos_effect += "miscast"; break;
682 case CHAOS_RAGE: chaos_effect += "berserk"; break;
683 case CHAOS_HEAL: chaos_effect += "healing"; break;
684 case CHAOS_HASTE: chaos_effect += "hasting"; break;
685 case CHAOS_INVIS: chaos_effect += "invisible"; break;
686 case CHAOS_SLOW: chaos_effect += "slowing"; break;
687 case CHAOS_PARALYSIS: chaos_effect += "paralysis"; break;
688 case CHAOS_PETRIFY: chaos_effect += "petrify"; break;
689 default: chaos_effect += "(other)"; break;
692 take_note(Note(NOTE_MESSAGE, 0, 0, chaos_effect.c_str()), true);
695 switch (static_cast<chaos_type>(choice))
700 ASSERT(clone_chance > 0);
701 ASSERT(defender->is_monster());
703 if (monster *clone = clone_mons(mon, true, &obvious_effect))
707 special_damage_message =
708 make_stringf("%s is duplicated!",
709 defender_name(false).c_str());
712 // The player shouldn't get new permanent followers from cloning.
713 if (clone->attitude == ATT_FRIENDLY && !clone->is_summoned())
714 clone->mark_summoned(6, true, MON_SUMM_CLONE);
716 // Monsters being cloned is interesting.
717 xom_is_stimulated(clone->friendly() ? 12 : 25);
724 ASSERT(poly_chance > 0);
725 beam.flavour = BEAM_POLYMORPH;
730 ASSERT(poly_up_chance > 0);
731 ASSERT(defender->is_monster());
733 obvious_effect = you.can_see(defender);
734 monster_polymorph(mon, RANDOM_MONSTER, PPT_MORE);
737 case CHAOS_MAKE_SHIFTER:
740 ASSERT(shifter_chance > 0);
742 ASSERT(defender->is_monster());
744 obvious_effect = you.can_see(defender);
745 mon->add_ench(one_chance_in(3) ? ENCH_GLOWING_SHAPESHIFTER
746 : ENCH_SHAPESHIFTER);
747 // Immediately polymorph monster, just to make the effect obvious.
748 monster_polymorph(mon, RANDOM_MONSTER);
750 // Xom loves it if this happens!
751 const int friend_factor = mon->friendly() ? 1 : 2;
752 const int glow_factor = mon->has_ench(ENCH_SHAPESHIFTER) ? 1 : 2;
753 xom_is_stimulated(64 * friend_factor * glow_factor);
758 int level = defender->get_hit_dice();
760 // At level == 27 there's a 13.9% chance of a level 3 miscast.
761 int level0_chance = level;
762 int level1_chance = max(0, level - 7);
763 int level2_chance = max(0, level - 12);
764 int level3_chance = max(0, level - 17);
766 level = random_choose_weighted(
773 miscast_level = level;
774 miscast_type = SPTYP_RANDOM;
775 miscast_target = defender;
781 ASSERT(rage_chance > 0);
782 defender->go_berserk(false);
783 obvious_effect = you.can_see(defender);
786 // For these, obvious_effect is computed during beam.fire() below.
788 beam.flavour = BEAM_HEALING;
791 beam.flavour = BEAM_HASTE;
794 beam.flavour = BEAM_INVISIBILITY;
797 beam.flavour = BEAM_SLOW;
799 case CHAOS_PARALYSIS:
800 beam.flavour = BEAM_PARALYSIS;
803 beam.flavour = BEAM_PETRIFY;
807 die("Invalid chaos effect type");
811 if (beam.flavour != BEAM_NONE)
816 beam.effect_known = false;
817 // Wielded brand is always known, but maybe this was from a call
818 // to chaos_affect_actor.
819 beam.effect_wanton = !fake_chaos_attack;
821 if (using_weapon() && you.can_see(attacker))
823 beam.name = wep_name(DESC_YOUR);
827 beam.name = atk_name(DESC_THE);
830 (attacker->is_player()) ? KILL_YOU
831 : attacker->as_monster()->confused_by_you() ? KILL_YOU_CONF
834 if (beam.thrower == KILL_YOU || attacker->as_monster()->friendly())
835 beam.attitude = ATT_FRIENDLY;
837 beam.source_id = attacker->mid;
839 beam.source = defender->pos();
840 beam.target = defender->pos();
842 beam.damage = dice_def(damage_done + special_damage + aux_damage, 1);
844 beam.ench_power = beam.damage.num;
846 const bool you_could_see = you.can_see(defender);
850 obvious_effect = beam.obvious_effect;
853 if (!you.can_see(attacker))
854 obvious_effect = false;
857 // NOTE: random_chaos_brand() and random_chaos_attack_flavour() should
858 // return a set of effects that are roughly the same, to make it easy
859 // for chaos_affects_defender() not to do duplicate effects caused
860 // by the non-chaos brands/flavours they return.
861 brand_type attack::random_chaos_brand()
863 brand_type brand = SPWPN_NORMAL;
864 // Assuming the chaos to be mildly intelligent, try to avoid brands
865 // that clash with the most basic resists of the defender,
866 // i.e. its holiness.
869 brand = (random_choose_weighted(
873 10, SPWPN_ELECTROCUTION,
884 if (one_chance_in(3))
887 bool susceptible = true;
891 if (defender->is_fiery())
895 if (defender->is_icy())
899 if (defender->holiness() == MH_UNDEAD)
902 case SPWPN_VAMPIRISM:
903 if (defender->is_summoned())
908 // intentional fall-through
910 if (defender->holiness() != MH_NATURAL)
913 case SPWPN_HOLY_WRATH:
914 if (!defender->holy_wrath_susceptible())
918 if (defender->holiness() == MH_NONLIVING
919 || defender->holiness() == MH_PLANT)
924 case SPWPN_ANTIMAGIC:
925 if (!defender->antimagic_susceptible())
935 #ifdef NOTE_DEBUG_CHAOS_BRAND
936 string brand_name = "CHAOS brand: ";
939 case SPWPN_NORMAL: brand_name += "(plain)"; break;
940 case SPWPN_FLAMING: brand_name += "flaming"; break;
941 case SPWPN_FREEZING: brand_name += "freezing"; break;
942 case SPWPN_HOLY_WRATH: brand_name += "holy wrath"; break;
943 case SPWPN_ELECTROCUTION: brand_name += "electrocution"; break;
944 case SPWPN_VENOM: brand_name += "venom"; break;
945 case SPWPN_DRAINING: brand_name += "draining"; break;
946 case SPWPN_DISTORTION: brand_name += "distortion"; break;
947 case SPWPN_VAMPIRISM: brand_name += "vampirism"; break;
948 case SPWPN_VORPAL: brand_name += "vorpal"; break;
949 case SPWPN_ANTIMAGIC: brand_name += "antimagic"; break;
951 // both ranged and non-ranged
952 case SPWPN_CHAOS: brand_name += "chaos"; break;
953 case SPWPN_CONFUSE: brand_name += "confusion"; break;
954 default: brand_name += "(other)"; break;
957 // Pretty much duplicated by the chaos effect note,
958 // which will be much more informative.
959 if (brand != SPWPN_CHAOS)
960 take_note(Note(NOTE_MESSAGE, 0, 0, brand_name.c_str()), true);
965 void attack::do_miscast()
967 if (miscast_level == -1)
970 ASSERT(miscast_target != nullptr);
971 ASSERT_RANGE(miscast_level, 0, 4);
972 ASSERT(count_bits(miscast_type) == 1);
974 if (!miscast_target->alive())
977 if (miscast_target->is_player() && you.banished)
980 const bool chaos_brand =
981 using_weapon() && get_weapon_brand(*weapon) == SPWPN_CHAOS;
983 // If the miscast is happening on the attacker's side and is due to
984 // a chaos weapon then make smoke/sand/etc pour out of the weapon
985 // instead of the attacker's hands.
988 string cause = atk_name(DESC_THE);
990 const int ignore_mask = ISFLAG_KNOW_CURSE | ISFLAG_KNOW_PLUSES;
992 if (attacker->is_player())
996 cause = "a chaos effect from ";
997 // Ignore a lot of item flags to make cause as short as possible,
998 // so it will (hopefully) fit onto a single line in the death
1000 cause += wep_name(DESC_YOUR, ignore_mask | ISFLAG_COSMETIC_MASK);
1002 if (miscast_target == attacker)
1003 hand_str = wep_name(DESC_PLAIN, ignore_mask);
1008 if (chaos_brand && miscast_target == attacker
1009 && you.can_see(attacker))
1011 hand_str = wep_name(DESC_PLAIN, ignore_mask);
1015 MiscastEffect(miscast_target, attacker, MELEE_MISCAST,
1016 (spschool_flag_type) miscast_type, miscast_level, cause,
1017 NH_NEVER, 0, hand_str, false);
1019 // Don't do miscast twice for one attack.
1023 void attack::drain_defender()
1025 if (defender->is_monster() && coinflip())
1028 if (defender->holiness() != MH_NATURAL)
1031 special_damage = resist_adjust_damage(defender, BEAM_NEG,
1032 (1 + random2(damage_done)) / 2);
1034 if (defender->drain_exp(attacker, true, 20 + min(35, damage_done)))
1036 if (defender->is_player())
1037 obvious_effect = true;
1038 else if (defender_visible)
1040 special_damage_message =
1043 atk_name(DESC_THE).c_str(),
1044 attacker->conj_verb("drain").c_str(),
1045 defender_name(true).c_str());
1048 attacker->god_conduct(DID_NECROMANCY, 2);
1052 void attack::drain_defender_speed()
1054 if (!defender->res_negative_energy())
1058 mprf("%s %s %s vigour!",
1059 atk_name(DESC_THE).c_str(),
1060 attacker->conj_verb("drain").c_str(),
1061 def_name(DESC_ITS).c_str());
1064 special_damage = 1 + random2(damage_done) / 2;
1065 defender->slow_down(attacker, 5 + random2(7));
1069 int attack::inflict_damage(int dam, beam_type flavour, bool clean)
1071 if (flavour == NUM_BEAMS)
1072 flavour = special_damage_flavour;
1073 // Auxes temporarily clear damage_brand so we don't need to check
1074 if (damage_brand == SPWPN_REAPING ||
1075 damage_brand == SPWPN_CHAOS && one_chance_in(100))
1077 defender->props["reaping_damage"].get_int() += dam;
1078 // With two reapers of different friendliness, the most recent one
1079 // gets the zombie. Too rare a case to care any more.
1080 defender->props["reaper"].get_int() = attacker->mid;
1082 return defender->hurt(responsible, dam, flavour, kill_type,
1083 "", aux_source.c_str(), clean);
1086 /* If debug, return formatted damage done
1089 string attack::debug_damage_number()
1091 #ifdef DEBUG_DIAGNOSTICS
1092 return make_stringf(" for %d", damage_done);
1098 /* Returns standard attack punctuation
1100 * Used in player / monster (both primary and aux) attacks
1102 string attack::attack_strength_punctuation(int dmg)
1106 else if (dmg < HIT_MED)
1108 else if (dmg < HIT_STRONG)
1113 int tmpdamage = dmg;
1114 while (tmpdamage >= 2*HIT_STRONG)
1123 /* Returns evasion adverb
1126 string attack::evasion_margin_adverb()
1128 return (ev_margin <= -20) ? " completely" :
1129 (ev_margin <= -12) ? "" :
1130 (ev_margin <= -6) ? " closely"
1134 void attack::stab_message()
1136 defender->props["helpless"] = true;
1140 case 6: // big melee, monster surrounded/not paying attention
1143 mprf("You %s %s from a blind spot!",
1144 (you.species == SP_FELID) ? "pounce on" : "strike",
1145 defender->name(DESC_THE).c_str());
1149 mprf("You catch %s momentarily off-guard.",
1150 defender->name(DESC_THE).c_str());
1153 case 4: // confused/fleeing
1154 if (!one_chance_in(3))
1156 mprf("You catch %s completely off-guard!",
1157 defender->name(DESC_THE).c_str());
1161 mprf("You %s %s from behind!",
1162 (you.species == SP_FELID) ? "pounce on" : "strike",
1163 defender->name(DESC_THE).c_str());
1168 if (you.species == SP_FELID && coinflip())
1170 mprf("You pounce on the unaware %s!",
1171 defender->name(DESC_PLAIN).c_str());
1174 mprf("%s fails to defend %s.",
1175 defender->name(DESC_THE).c_str(),
1176 defender->pronoun(PRONOUN_REFLEXIVE).c_str());
1180 defender->props.erase("helpless");
1183 /* Returns the attacker's name
1185 * Helper method to easily access the attacker's name
1187 string attack::atk_name(description_level_type desc)
1189 return actor_name(attacker, desc, attacker_visible);
1192 /* Returns the defender's name
1194 * Helper method to easily access the defender's name
1196 string attack::def_name(description_level_type desc)
1198 return actor_name(defender, desc, defender_visible);
1201 /* Returns the attacking weapon's name
1203 * Sets upthe appropriate descriptive level and obtains the name of a weapon
1204 * based on if the attacker is a player or non-player (non-players use a
1205 * plain name and a manually entered pronoun)
1207 string attack::wep_name(description_level_type desc, iflags_t ignre_flags)
1209 ASSERT(weapon != nullptr);
1211 if (attacker->is_player())
1212 return weapon->name(desc, false, false, false, false, ignre_flags);
1215 bool possessive = false;
1216 if (desc == DESC_YOUR)
1223 name = apostrophise(atk_name(desc)) + " ";
1225 name += weapon->name(DESC_PLAIN, false, false, false, false, ignre_flags);
1230 /* TODO: Remove this!
1231 * Removing it may not really be practical, in retrospect. Its only used
1232 * below, in calc_elemental_brand_damage, which is called for both frost and
1233 * flame brands for both players and monsters.
1235 string attack::defender_name(bool allow_reflexive)
1237 if (allow_reflexive && attacker == defender)
1238 return actor_pronoun(attacker, PRONOUN_REFLEXIVE, attacker_visible);
1240 return def_name(DESC_THE);
1243 int attack::player_stat_modify_damage(int damage)
1246 const int dam_stat_val = calc_stat_to_dam_base();
1248 if (dam_stat_val > 11)
1249 dammod += (random2(dam_stat_val - 11) * 2);
1250 else if (dam_stat_val < 9)
1251 dammod -= (random2(9 - dam_stat_val) * 3);
1259 int attack::player_apply_weapon_skill(int damage)
1263 damage *= 2500 + (random2(you.skill(wpn_skill, 100) + 1));
1270 int attack::player_apply_fighting_skill(int damage, bool aux)
1272 const int base = aux? 40 : 30;
1274 damage *= base * 100 + (random2(you.skill(SK_FIGHTING, 100) + 1));
1275 damage /= base * 100;
1280 int attack::player_apply_misc_modifiers(int damage)
1286 * Get the damage bonus from a weapon's enchantment.
1288 int attack::get_weapon_plus()
1290 if (weapon->base_type == OBJ_RODS)
1291 return weapon->special;
1292 if (weapon->base_type == OBJ_STAVES || weapon->sub_type == WPN_BLOWGUN)
1294 return weapon->plus;
1297 // Slaying and weapon enchantment. Apply this for slaying even if not
1298 // using a weapon to attack.
1299 int attack::player_apply_slaying_bonuses(int damage, bool aux)
1301 int damage_plus = 0;
1302 if (!aux && using_weapon())
1304 damage_plus = get_weapon_plus();
1305 if (you.duration[DUR_CORROSION])
1306 damage_plus -= 3 * you.props["corrosion_amount"].get_int();
1308 damage_plus += slaying_bonus(!weapon && wpn_skill == SK_THROWING
1309 || (weapon && is_range_weapon(*weapon)
1310 && using_weapon()));
1312 damage += (damage_plus > -1) ? (random2(1 + damage_plus))
1313 : (-random2(1 - damage_plus));
1317 int attack::player_apply_final_multipliers(int damage)
1319 // Can't affect much of anything as a shadow.
1320 if (you.form == TRAN_SHADOW)
1321 damage = div_rand_round(damage, 2);
1326 void attack::player_exercise_combat_skills()
1330 /* Returns attacker base unarmed damage
1332 * Scales for current mutations and unarmed effects
1333 * TODO: Complete symmetry for base_unarmed damage
1334 * between monsters and players.
1336 int attack::calc_base_unarmed_damage()
1338 // Should only get here if we're not wielding something that's a weapon.
1339 // If there's a non-weapon in hand, it has no base damage.
1343 if (!attacker->is_player())
1346 int damage = get_form()->get_base_unarmed_damage();
1348 if (you.has_usable_claws())
1350 // Claw damage only applies for bare hands.
1351 damage += you.has_claws(false) * 2;
1352 apply_bleeding = true;
1355 if (you.form_uses_xl())
1356 damage += you.experience_level;
1357 else if (you.form == TRAN_BAT || you.form == TRAN_PORCUPINE)
1359 // Bats really don't do a lot of damage.
1360 damage += you.skill_rdiv(wpn_skill, 1, 5);
1363 damage += you.skill_rdiv(wpn_skill);
1371 // TODO: Potentially remove, consider integrating with other to-hit or stat
1372 // calculating methods
1373 // weighted average of strength and dex, between (str+dex)/2 and dex
1374 int attack::calc_stat_to_hit_base()
1376 const int weight = weapon ? weapon_str_weight(*weapon) : 4;
1378 // dex is modified by strength towards the average, by the
1379 // weighted amount weapon_str_weight() / 20.
1380 return you.dex() + (you.strength() - you.dex()) * weight / 20;
1383 // weighted average of strength and dex, between str and (str+dex)/2
1384 int attack::calc_stat_to_dam_base()
1386 const int weight = weapon ? 10 - weapon_str_weight(*weapon) : 6;
1387 return you.strength() + (you.dex() - you.strength()) * weight / 20;
1390 int attack::calc_damage()
1392 if (attacker->is_monster())
1396 if (using_weapon() || wpn_skill == SK_THROWING)
1398 damage_max = weapon_damage();
1399 damage += random2(damage_max);
1401 int wpn_damage_plus = 0;
1402 if (weapon) // can be 0 for throwing projectiles
1403 wpn_damage_plus = get_weapon_plus();
1405 const int jewellery = attacker->as_monster()->inv[MSLOT_JEWELLERY];
1406 if (jewellery != NON_ITEM
1407 && mitm[jewellery].is_type(OBJ_JEWELLERY, RING_SLAYING))
1409 wpn_damage_plus += mitm[jewellery].plus;
1412 wpn_damage_plus += attacker->scan_artefacts(ARTP_SLAYING);
1414 if (wpn_damage_plus >= 0)
1415 damage += random2(wpn_damage_plus);
1417 damage -= random2(1 - wpn_damage_plus);
1419 damage -= 1 + random2(3);
1422 damage_max += attk_damage;
1423 damage += 1 + random2(attk_damage);
1425 damage = apply_damage_modifiers(damage, damage_max);
1427 set_attack_verb(damage);
1428 return apply_defender_ac(damage, damage_max);
1432 int potential_damage, damage;
1434 potential_damage = using_weapon() || wpn_skill == SK_THROWING
1435 ? weapon_damage() : calc_base_unarmed_damage();
1437 potential_damage = player_stat_modify_damage(potential_damage);
1439 damage = random2(potential_damage+1);
1441 damage = player_apply_weapon_skill(damage);
1442 damage = player_apply_fighting_skill(damage, false);
1443 damage = player_apply_misc_modifiers(damage);
1444 damage = player_apply_slaying_bonuses(damage, false);
1445 damage = player_stab(damage);
1446 // A failed stab may have awakened monsters, but that could have
1447 // caused the defender to cease to exist (spectral weapons with
1448 // missing summoners; or pacified monsters on a stair). FIXME:
1449 // The correct thing to do would be either to delay the call to
1450 // alert_nearby_monsters (currently in player_stab) until later
1451 // in the attack; or to avoid removing monsters in handle_behaviour.
1452 if (!defender->alive())
1454 damage = player_apply_final_multipliers(damage);
1455 damage = apply_defender_ac(damage);
1457 damage = max(0, damage);
1458 set_attack_verb(damage);
1466 int attack::test_hit(int to_land, int ev, bool randomise_ev)
1468 int margin = AUTOMATIC_HIT;
1470 ev = random2avg(2*ev, 2);
1471 if (to_land >= AUTOMATIC_HIT)
1473 else if (x_chance_in_y(MIN_HIT_MISS_PERCENTAGE, 100))
1474 margin = (random2(2) ? 1 : -1) * AUTOMATIC_HIT;
1476 margin = to_land - ev;
1478 #ifdef DEBUG_DIAGNOSTICS
1479 dprf(DIAG_COMBAT, "to hit: %d; ev: %d; result: %s (%d)",
1480 to_hit, ev, (margin >= 0) ? "hit" : "miss", margin);
1486 int attack::apply_defender_ac(int damage, int damage_max) const
1489 int stab_bypass = 0;
1492 stab_bypass = you.skill(wpn_skill, 50) + you.skill(SK_STEALTH, 50);
1493 stab_bypass = random2(div_rand_round(stab_bypass, 100 * stab_bonus));
1495 int after_ac = defender->apply_ac(damage, damage_max,
1496 AC_NORMAL, stab_bypass);
1497 dprf(DIAG_COMBAT, "AC: att: %s, def: %s, ac: %d, gdr: %d, dam: %d -> %d",
1498 attacker->name(DESC_PLAIN, true).c_str(),
1499 defender->name(DESC_PLAIN, true).c_str(),
1500 defender->armour_class(),
1501 defender->gdr_perc(),
1508 bool attack::attack_warded_off()
1510 const int WARDING_CHECK = 60;
1512 if (defender->warding()
1513 && attacker->is_summoned()
1514 && attacker->as_monster()->check_res_magic(WARDING_CHECK) <= 0)
1518 mprf("%s attack is warded away.",
1519 attacker->name(DESC_ITS).c_str());
1527 /* Determine whether a block occurred
1529 * No blocks if defender is incapacitated, would be nice to eventually expand
1530 * this method to handle partial blocks as well as full blocks (although this
1531 * would serve as a nerf to shields and - while more realistic - may not be a
1532 * good mechanic for shields.
1534 * Returns (block_occurred)
1536 bool attack::attack_shield_blocked(bool verbose)
1538 if (defender == attacker)
1539 return false; // You can't block your own attacks!
1541 if (defender->incapacitated())
1544 const int con_block = random2(attacker->shield_bypass_ability(to_hit)
1545 + defender->shield_block_penalty());
1546 int pro_block = defender->shield_bonus();
1548 if (!attacker->visible_to(defender))
1551 dprf(DIAG_COMBAT, "Defender: %s, Pro-block: %d, Con-block: %d",
1552 def_name(DESC_PLAIN).c_str(), pro_block, con_block);
1554 if (pro_block >= con_block)
1556 perceived_attack = true;
1558 if (attack_ignores_shield(verbose))
1561 if (needs_message && verbose)
1563 mprf("%s %s %s attack.",
1564 defender_name(false).c_str(),
1565 defender->conj_verb("block").c_str(),
1566 attacker == defender ? "its own"
1567 : atk_name(DESC_ITS).c_str());
1570 defender->shield_block_succeeded(attacker);
1578 attack_flavour attack::random_chaos_attack_flavour()
1580 attack_flavour flavours[] =
1581 {AF_FIRE, AF_COLD, AF_ELEC, AF_POISON, AF_VAMPIRIC, AF_DISTORT,
1582 AF_CONFUSE, AF_CHAOS};
1583 return RANDOM_ELEMENT(flavours);
1586 bool attack::apply_damage_brand(const char *what)
1588 bool brand_was_known = false;
1594 if (is_artefact(*weapon))
1595 brand_was_known = artefact_known_property(*weapon, ARTP_BRAND);
1597 brand_was_known = item_type_known(*weapon);
1601 obvious_effect = false;
1602 brand = damage_brand == SPWPN_CHAOS ? random_chaos_brand() : damage_brand;
1604 if (brand != SPWPN_FLAMING && brand != SPWPN_FREEZING
1605 && brand != SPWPN_ELECTROCUTION && brand != SPWPN_VAMPIRISM
1606 && !defender->alive())
1608 // Most brands have no extra effects on just killed enemies, and the
1609 // effect would be often inappropriate.
1614 && (brand == SPWPN_FLAMING || brand == SPWPN_FREEZING
1615 || brand == SPWPN_HOLY_WRATH || brand == SPWPN_ANTIMAGIC
1616 || brand == SPWPN_VORPAL || brand == SPWPN_VAMPIRISM))
1618 // These brands require some regular damage to function.
1625 calc_elemental_brand_damage(BEAM_FIRE,
1626 defender->is_icy() ? "melt" : "burn",
1628 attacker->god_conduct(DID_FIRE, 1);
1631 case SPWPN_FREEZING:
1632 calc_elemental_brand_damage(BEAM_COLD, "freeze", what);
1635 case SPWPN_HOLY_WRATH:
1636 if (defender->holy_wrath_susceptible())
1637 special_damage = 1 + (random2(damage_done * 15) / 10);
1639 if (special_damage && defender_visible)
1641 special_damage_message =
1644 defender_name(false).c_str(),
1645 defender->conj_verb("convulse").c_str(),
1646 attack_strength_punctuation(special_damage).c_str());
1650 case SPWPN_ELECTROCUTION:
1651 if (defender->res_elec() > 0)
1653 else if (one_chance_in(3))
1655 special_damage_message =
1656 defender->is_player()?
1657 "You are electrocuted!"
1658 : "There is a sudden explosion of sparks!";
1659 special_damage = 8 + random2(13);
1660 special_damage_flavour = BEAM_ELECTRICITY;
1666 if (!one_chance_in(4))
1670 if (defender->is_player())
1671 old_poison = you.duration[DUR_POISONING];
1675 (defender->as_monster()->get_ench(ENCH_POISON)).degree;
1678 defender->poison(attacker, 6 + random2(8) + random2(damage_done * 3 / 2));
1680 if (defender->is_player()
1681 && old_poison < you.duration[DUR_POISONING]
1682 || !defender->is_player()
1684 (defender->as_monster()->get_ench(ENCH_POISON)).degree)
1686 obvious_effect = true;
1692 case SPWPN_DRAINING:
1697 special_damage = 1 + random2(damage_done) / 3;
1698 // Note: Leaving special_damage_message empty because there isn't one.
1701 case SPWPN_VAMPIRISM:
1704 || defender->holiness() != MH_NATURAL
1705 || defender->res_negative_energy()
1707 || attacker->stat_hp() == attacker->stat_maxhp()
1708 || !defender->is_player()
1709 && defender->as_monster()->is_summoned()
1710 || attacker->is_player() && you.duration[DUR_DEATHS_DOOR]
1711 || !attacker->is_player()
1712 && attacker->as_monster()->has_ench(ENCH_DEATHS_DOOR)
1713 || x_chance_in_y(2, 5) && !is_unrandom_artefact(*weapon, UNRAND_LEECH))
1718 obvious_effect = true;
1720 // Handle weapon effects.
1721 // We only get here if we've done base damage, so no
1722 // worries on that score.
1723 if (attacker->is_player())
1724 mpr("You feel better.");
1725 else if (attacker_visible)
1727 if (defender->is_player())
1729 mprf("%s draws strength from your injuries!",
1730 attacker->name(DESC_THE).c_str());
1734 mprf("%s is healed.",
1735 attacker->name(DESC_THE).c_str());
1739 int hp_boost = is_unrandom_artefact(*weapon, UNRAND_VAMPIRES_TOOTH)
1740 ? damage_done : 1 + random2(damage_done);
1742 dprf(DIAG_COMBAT, "Vampiric Healing: damage %d, healed %d",
1743 damage_done, hp_boost);
1744 attacker->heal(hp_boost);
1746 attacker->god_conduct(DID_NECROMANCY, 2);
1750 pain_affects_defender();
1753 case SPWPN_DISTORTION:
1754 ret = distortion_affects_defender();
1759 // This was originally for confusing touch and it doesn't really
1760 // work on the player, but a monster with a chaos weapon will
1761 // occasionally come up with this brand. -cao
1762 if (defender->is_player())
1765 // Also used for players in fungus form.
1766 if (attacker->is_player()
1767 && you.form == TRAN_FUNGUS
1768 && !you.duration[DUR_CONFUSING_TOUCH]
1769 && defender->is_unbreathing())
1775 (defender->holiness() == MH_NATURAL ? random2(30) : random2(22));
1777 if (hdcheck < defender->get_hit_dice()
1779 || defender->as_monster()->check_clarity(false))
1784 // Declaring these just to pass to the enchant function.
1786 beam_temp.thrower = attacker->is_player() ? KILL_YOU : KILL_MON;
1787 beam_temp.flavour = BEAM_CONFUSION;
1788 beam_temp.source_id = attacker->mid;
1789 beam_temp.apply_enchantment_to_monster(defender->as_monster());
1790 obvious_effect = beam_temp.obvious_effect;
1792 if (attacker->is_player() && damage_brand == SPWPN_CONFUSE
1793 && you.duration[DUR_CONFUSING_TOUCH])
1795 you.duration[DUR_CONFUSING_TOUCH] = 1;
1796 obvious_effect = false;
1802 chaos_affects_defender();
1805 case SPWPN_ANTIMAGIC:
1806 antimagic_affects_defender(damage_done * 8);
1810 if (using_weapon() && is_unrandom_artefact(*weapon, UNRAND_HELLFIRE))
1812 calc_elemental_brand_damage(BEAM_HELLFIRE,
1813 defender->is_icy() ? "melt" : "burn",
1815 defender->expose_to_element(BEAM_HELLFIRE);
1816 attacker->god_conduct(DID_UNHOLY, 2 + random2(3));
1817 attacker->god_conduct(DID_FIRE, 10 + random2(5));
1822 if (damage_brand == SPWPN_CHAOS)
1824 if (brand != SPWPN_CHAOS && !ret
1825 && miscast_level == -1 && one_chance_in(20))
1828 miscast_type = SPTYP_RANDOM;
1829 miscast_target = coinflip() ? attacker : defender;
1832 if (responsible->is_player())
1833 did_god_conduct(DID_CHAOS, 2 + random2(3), brand_was_known);
1836 if (!obvious_effect)
1837 obvious_effect = !special_damage_message.empty();
1839 if (needs_message && !special_damage_message.empty())
1841 mpr(special_damage_message);
1843 special_damage_message.clear();
1844 // Don't do message-only miscasts along with a special
1846 if (miscast_level == 0)
1850 if (special_damage > 0)
1851 inflict_damage(special_damage, special_damage_flavour);
1853 if (defender->alive())
1858 defender->expose_to_element(BEAM_FIRE);
1861 case SPWPN_FREEZING:
1862 defender->expose_to_element(BEAM_COLD, 2);
1869 if (obvious_effect && attacker_visible && using_weapon())
1871 if (is_artefact(*weapon))
1872 artefact_learn_prop(*weapon, ARTP_BRAND);
1874 set_ident_flags(*weapon, ISFLAG_KNOW_TYPE);
1880 /* Calculates special damage, prints appropriate combat text
1882 * Applies a particular damage brand to the current attack, the setup and
1883 * calculation of base damage and other effects varies based on the type
1884 * of attack, but the calculation of elemental damage should be consistent.
1886 void attack::calc_elemental_brand_damage(beam_type flavour,
1890 special_damage = resist_adjust_damage(defender, flavour,
1891 random2(damage_done) / 2 + 1);
1893 if (needs_message && special_damage > 0 && verb)
1895 // XXX: assumes "what" is singular
1896 special_damage_message = make_stringf(
1898 what ? what : atk_name(DESC_THE).c_str(),
1899 what ? conjugate_verb(verb, false).c_str()
1900 : attacker->conj_verb(verb).c_str(),
1901 // Don't allow reflexive if the subject wasn't the attacker.
1902 defender_name(!what).c_str(),
1903 attack_strength_punctuation(special_damage).c_str());
1907 int attack::player_stab_weapon_bonus(int damage)
1909 int stab_skill = you.skill(wpn_skill, 50) + you.skill(SK_STEALTH, 50);
1911 if (player_good_stab())
1913 // We might be unarmed if we're using the boots of the Assassin.
1914 const bool extra_good = using_weapon() && weapon->sub_type == WPN_DAGGER;
1915 int bonus = you.dex() * (stab_skill + 100) / (extra_good ? 500 : 1000);
1917 bonus = stepdown_value(bonus, 10, 10, 30, 30);
1919 damage *= 10 + div_rand_round(stab_skill, 100 * stab_bonus);
1923 damage *= 12 + div_rand_round(stab_skill, 100 * stab_bonus);
1929 int attack::player_stab(int damage)
1931 // The stabbing message looks better here:
1934 // Construct reasonable message.
1937 practise(EX_WILL_STAB);
1942 // Ok.. if you didn't backstab, you wake up the neighborhood.
1943 // I can live with that.
1944 alert_nearby_monsters();
1949 // Let's make sure we have some damage to work with...
1950 damage = max(1, damage);
1952 damage = player_stab_weapon_bonus(damage);
1958 /* Check for stab and prepare combat for stab-values
1960 * Grant an automatic stab if paralyzed or sleeping (with highest damage value)
1961 * stab_bonus is used as the divisor in damage calculations, so lower values
1962 * will yield higher damage. Normal stab chance is (stab_skill + dex + 1 / roll)
1963 * This averages out to about 1/3 chance for a non extended-endgame stabber.
1965 void attack::player_stab_check()
1967 if (you.duration[DUR_CLUMSY] || you.confused())
1969 stab_attempt = false;
1974 const stab_type st = find_stab_type(&you, defender);
1975 stab_attempt = (st != STAB_NO_STAB);
1976 const bool roll_needed = (st != STAB_SLEEPING && st != STAB_PARALYSED);
1979 if (st == STAB_INVISIBLE)
1989 case STAB_PARALYSED:
1992 case STAB_HELD_IN_NET:
1993 case STAB_PETRIFYING:
1994 case STAB_PETRIFIED:
1997 case STAB_INVISIBLE:
2003 case STAB_DISTRACTED:
2008 // See if we need to roll against dexterity / stabbing.
2009 if (stab_attempt && roll_needed)
2011 stab_attempt = x_chance_in_y(you.skill_rdiv(wpn_skill, 1, 2)
2012 + you.skill_rdiv(SK_STEALTH, 1, 2)
2018 count_action(CACT_STAB, st);