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:
261 mhit -= 5 * attacker->inaccuracy();
263 // If you can't see yourself, you're a little less accurate.
264 if (!attacker->visible_to(attacker))
267 if (attacker->confused())
270 if (using_weapon() && is_unrandom_artefact(*weapon, UNRAND_WOE))
271 return AUTOMATIC_HIT;
273 // If no defender, we're calculating to-hit for debug-display
274 // purposes, so don't drop down to defender code below
275 if (defender == nullptr)
278 if (!defender->visible_to(attacker))
279 if (attacker->is_player())
282 mhit = mhit * 65 / 100;
285 // This can only help if you're visible!
286 const int how_transparent = player_mutation_level(MUT_TRANSLUCENT_SKIN);
287 if (defender->is_player() && how_transparent)
288 mhit -= 2 * how_transparent;
290 if (defender->backlit(false))
291 mhit += 2 + random2(8);
292 else if (!attacker->nightvision()
293 && defender->umbra())
294 mhit -= 2 + random2(4);
296 // Don't delay doing this roll until test_hit().
297 if (!attacker->is_player())
298 mhit = random2(mhit + 1);
300 dprf(DIAG_COMBAT, "%s: Base to-hit: %d, Final to-hit: %d",
301 attacker->name(DESC_PLAIN).c_str(),
307 /* Returns an actor's name
309 * Takes into account actor visibility/invisibility and the type of description
310 * to be used (capitalization, possessiveness, etc.)
312 string attack::actor_name(const actor *a, description_level_type desc,
315 return actor_visible ? a->name(desc) : anon_name(desc);
318 /* Returns an actor's pronoun
320 * Takes into account actor visibility
322 string attack::actor_pronoun(const actor *a, pronoun_type pron,
325 return actor_visible ? a->pronoun(pron) : anon_pronoun(pron);
328 /* Returns an anonymous actor's name
330 * Given the actor visible or invisible, returns the
331 * appropriate possessive pronoun.
333 string attack::anon_name(description_level_type desc)
350 /* Returns an anonymous actor's pronoun
352 * Given invisibility (whether out of LOS or just invisible), returns the
353 * appropriate possessive, inflexive, capitalised pronoun.
355 string attack::anon_pronoun(pronoun_type pron)
357 return decline_pronoun(GENDER_NEUTER, pron);
360 /* Initializes an attack, setting up base variables and values
362 * Does not make any changes to any actors, items, or the environment,
363 * in case the attack is cancelled or otherwise fails. Only initializations
364 * that are universal to all types of attacks should go into this method,
365 * any initialization properties that are specific to one attack or another
366 * should go into their respective init_attack.
368 * Although this method will get overloaded by the derived class, we are
369 * calling it from attack::attack(), before the overloading has taken place.
371 void attack::init_attack(skill_type unarmed_skill, int attack_number)
373 weapon = attacker->weapon(attack_number);
375 wpn_skill = weapon ? item_attack_skill(*weapon) : unarmed_skill;
376 if (attacker->is_player() && you.form_uses_xl())
377 wpn_skill = SK_FIGHTING; // for stabbing, mostly
379 attacker_armour_tohit_penalty =
380 div_rand_round(attacker->armour_tohit_penalty(true, 20), 20);
381 attacker_shield_tohit_penalty =
382 div_rand_round(attacker->shield_tohit_penalty(true, 20), 20);
383 to_hit = calc_to_hit(true);
385 shield = attacker->shield();
386 defender_shield = defender ? defender->shield() : defender_shield;
388 if (weapon && weapon->base_type == OBJ_WEAPONS && is_artefact(*weapon))
390 artefact_properties(*weapon, art_props);
391 if (is_unrandom_artefact(*weapon))
392 unrand_entry = get_unrand_entry(weapon->special);
395 attacker_visible = attacker->observable();
396 defender_visible = defender && defender->observable();
397 needs_message = (attacker_visible || defender_visible);
399 if (attacker->is_monster())
401 mon_attack_def mon_attk = mons_attack_spec(attacker->as_monster(),
404 attk_type = mon_attk.type;
405 attk_flavour = mon_attk.flavour;
407 // Don't scale damage for YOU_FAULTLESS etc.
408 if (attacker->get_experience_level() == 0)
409 attk_damage = mon_attk.damage;
412 attk_damage = div_rand_round(mon_attk.damage
413 * attacker->get_hit_dice(),
414 attacker->get_experience_level());
417 if (attk_type == AT_WEAP_ONLY)
419 int weap = attacker->as_monster()->inv[MSLOT_WEAPON];
420 if (weap == NON_ITEM || is_range_weapon(mitm[weap]))
425 else if (attk_type == AT_TRUNK_SLAP && attacker->type == MONS_SKELETON)
427 // Elephant trunks have no bones inside.
434 attk_flavour = AF_PLAIN;
438 void attack::alert_defender()
440 // Allow monster attacks to draw the ire of the defender. Player
441 // attacks are handled elsewhere.
443 && defender->is_monster()
444 && attacker->is_monster()
445 && attacker->alive() && defender->alive()
446 && (defender->as_monster()->foe == MHITNOT || one_chance_in(3)))
448 behaviour_event(defender->as_monster(), ME_WHACK, attacker);
451 // If an enemy attacked a friend, set the pet target if it isn't set
452 // already, but not if sanctuary is in effect (pet target must be
453 // set explicitly by the player during sanctuary).
454 if (perceived_attack && attacker->alive()
455 && (defender->is_player() || defender->as_monster()->friendly())
456 && !attacker->is_player()
457 && !crawl_state.game_is_arena()
458 && !attacker->as_monster()->wont_attack())
460 if (defender->is_player())
461 interrupt_activity(AI_MONSTER_ATTACKS, attacker->as_monster());
462 if (you.pet_target == MHITNOT && env.sanctuary_time <= 0)
463 you.pet_target = attacker->mindex();
467 bool attack::distortion_affects_defender()
469 //jmf: blink frogs *like* distortion
470 // I think could be amended to let blink frogs "grow" like
472 if (defender->is_monster()
473 && mons_genus(defender->type) == MONS_BLINK_FROG)
475 if (one_chance_in(5))
477 if (defender_visible)
479 special_damage_message =
480 make_stringf("%s %s in the distortional energy.",
481 defender_name(false).c_str(),
482 defender->conj_verb("bask").c_str());
485 defender->heal(1 + random2avg(7, 2), true); // heh heh
490 if (one_chance_in(3))
492 special_damage_message =
493 make_stringf("Space bends around %s.",
494 defender_name(false).c_str());
495 special_damage += 1 + random2avg(7, 2);
499 if (one_chance_in(3))
501 special_damage_message =
502 make_stringf("Space warps horribly around %s!",
503 defender_name(false).c_str());
504 special_damage += 3 + random2avg(24, 2);
511 if (one_chance_in(3))
513 if (defender_visible)
514 obvious_effect = true;
515 if (!defender->no_tele(true, false))
516 blink_fineff::schedule(defender);
520 if (!one_chance_in(3))
522 if (defender_visible)
523 obvious_effect = true;
524 if (crawl_state.game_is_sprint() && defender->is_player()
525 || defender->no_tele())
527 if (defender->is_player())
528 canned_msg(MSG_STRANGE_STASIS);
531 distortion_tele_fineff::schedule(defender);
533 defender->teleport();
537 if (!player_in_branch(BRANCH_ABYSS) && coinflip())
539 if (defender_visible)
540 obvious_effect = true;
542 defender->banish(attacker, attacker->name(DESC_PLAIN, true));
549 void attack::antimagic_affects_defender(int pow)
552 enchant_actor_with_flavour(defender, nullptr, BEAM_DRAIN_MAGIC, pow);
555 void attack::pain_affects_defender()
557 if (defender->res_negative_energy())
560 if (!one_chance_in(attacker->skill_rdiv(SK_NECROMANCY) + 1))
562 if (defender_visible)
564 special_damage_message =
565 make_stringf("%s %s in agony.",
566 defender->name(DESC_THE).c_str(),
567 defender->conj_verb("writhe").c_str());
569 special_damage += random2(1 + attacker->skill_rdiv(SK_NECROMANCY));
572 attacker->god_conduct(DID_NECROMANCY, 4);
575 // TODO: Move this somewhere sane
593 // XXX: We might want to vary the probabilities for the various effects
594 // based on whether the source is a weapon of chaos or a monster with
596 void attack::chaos_affects_defender()
598 monster * const mon = defender->as_monster();
599 const bool firewood = mon && mons_is_firewood(mon);
600 const bool immune = mon && mons_immune_magic(mon);
601 const bool is_natural = mon && defender->holiness() == MH_NATURAL;
602 const bool is_shifter = mon && mon->is_shapeshifter();
603 const bool can_clone = mon && mons_clonable(mon, true);
604 const bool can_poly = is_shifter || (defender->can_safely_mutate()
605 && !immune && !firewood);
606 const bool can_rage = defender->can_go_berserk();
607 const bool can_slow = !firewood;
608 const bool can_petrify= mon && !defender->res_petrify();
610 int clone_chance = can_clone ? 1 : 0;
611 int poly_chance = can_poly ? 1 : 0;
612 int poly_up_chance = can_poly && mon ? 1 : 0;
613 int shifter_chance = can_poly && is_natural && mon ? 1 : 0;
614 int rage_chance = can_rage ? 10 : 0;
615 int miscast_chance = 10;
616 int slowpara_chance= can_slow ? 10 : 0;
617 int petrify_chance = can_slow && can_petrify ? 10 : 0;
619 // Already a shifter?
623 // A chaos self-attack increases the chance of certain effects,
624 // due to a short-circuit/feedback/resonance/whatever.
625 if (attacker == defender)
633 // Inform player that something is up.
634 if (!fake_chaos_attack && you.see_cell(defender->pos()))
636 if (defender->is_player())
637 mpr("You give off a flash of multicoloured light!");
638 else if (you.can_see(defender))
640 simple_monster_message(mon, " gives off a flash of"
641 " multicoloured light!");
644 mpr("There is a flash of multicoloured light!");
648 // NOTE: Must appear in exact same order as in chaos_type enumeration.
649 int probs[NUM_CHAOS_TYPES] =
651 clone_chance, // CHAOS_CLONE
652 poly_chance, // CHAOS_POLY
653 poly_up_chance, // CHAOS_POLY_UP
654 shifter_chance, // CHAOS_MAKE_SHIFTER
655 miscast_chance, // CHAOS_MISCAST
656 rage_chance, // CHAOS_RAGE
659 slowpara_chance,// CHAOS_HASTE
662 slowpara_chance,// CHAOS_SLOW
663 slowpara_chance,// CHAOS_PARALYSIS
664 petrify_chance, // CHAOS_PETRIFY
668 beam.flavour = BEAM_NONE;
670 int choice = choose_random_weighted(probs, probs + NUM_CHAOS_TYPES);
671 #ifdef NOTE_DEBUG_CHAOS_EFFECTS
672 string chaos_effect = "CHAOS effect: ";
675 case CHAOS_CLONE: chaos_effect += "clone"; break;
676 case CHAOS_POLY: chaos_effect += "polymorph"; break;
677 case CHAOS_POLY_UP: chaos_effect += "polymorph PPT_MORE"; break;
678 case CHAOS_MAKE_SHIFTER: chaos_effect += "shifter"; break;
679 case CHAOS_MISCAST: chaos_effect += "miscast"; break;
680 case CHAOS_RAGE: chaos_effect += "berserk"; break;
681 case CHAOS_HEAL: chaos_effect += "healing"; break;
682 case CHAOS_HASTE: chaos_effect += "hasting"; break;
683 case CHAOS_INVIS: chaos_effect += "invisible"; break;
684 case CHAOS_SLOW: chaos_effect += "slowing"; break;
685 case CHAOS_PARALYSIS: chaos_effect += "paralysis"; break;
686 case CHAOS_PETRIFY: chaos_effect += "petrify"; break;
687 default: chaos_effect += "(other)"; break;
690 take_note(Note(NOTE_MESSAGE, 0, 0, chaos_effect.c_str()), true);
693 switch (static_cast<chaos_type>(choice))
698 ASSERT(clone_chance > 0);
699 ASSERT(defender->is_monster());
701 if (monster *clone = clone_mons(mon, true, &obvious_effect))
705 special_damage_message =
706 make_stringf("%s is duplicated!",
707 defender_name(false).c_str());
710 // The player shouldn't get new permanent followers from cloning.
711 if (clone->attitude == ATT_FRIENDLY && !clone->is_summoned())
712 clone->mark_summoned(6, true, MON_SUMM_CLONE);
714 // Monsters being cloned is interesting.
715 xom_is_stimulated(clone->friendly() ? 12 : 25);
722 ASSERT(poly_chance > 0);
723 beam.flavour = BEAM_POLYMORPH;
728 ASSERT(poly_up_chance > 0);
729 ASSERT(defender->is_monster());
731 obvious_effect = you.can_see(defender);
732 monster_polymorph(mon, RANDOM_MONSTER, PPT_MORE);
735 case CHAOS_MAKE_SHIFTER:
738 ASSERT(shifter_chance > 0);
740 ASSERT(defender->is_monster());
742 obvious_effect = you.can_see(defender);
743 mon->add_ench(one_chance_in(3) ? ENCH_GLOWING_SHAPESHIFTER
744 : ENCH_SHAPESHIFTER);
745 // Immediately polymorph monster, just to make the effect obvious.
746 monster_polymorph(mon, RANDOM_MONSTER);
748 // Xom loves it if this happens!
749 const int friend_factor = mon->friendly() ? 1 : 2;
750 const int glow_factor = mon->has_ench(ENCH_SHAPESHIFTER) ? 1 : 2;
751 xom_is_stimulated(64 * friend_factor * glow_factor);
756 int level = defender->get_hit_dice();
758 // At level == 27 there's a 13.9% chance of a level 3 miscast.
759 int level0_chance = level;
760 int level1_chance = max(0, level - 7);
761 int level2_chance = max(0, level - 12);
762 int level3_chance = max(0, level - 17);
764 level = random_choose_weighted(
771 miscast_level = level;
772 miscast_type = SPTYP_RANDOM;
773 miscast_target = defender;
779 ASSERT(rage_chance > 0);
780 defender->go_berserk(false);
781 obvious_effect = you.can_see(defender);
784 // For these, obvious_effect is computed during beam.fire() below.
786 beam.flavour = BEAM_HEALING;
789 beam.flavour = BEAM_HASTE;
792 beam.flavour = BEAM_INVISIBILITY;
795 beam.flavour = BEAM_SLOW;
797 case CHAOS_PARALYSIS:
798 beam.flavour = BEAM_PARALYSIS;
801 beam.flavour = BEAM_PETRIFY;
805 die("Invalid chaos effect type");
809 if (beam.flavour != BEAM_NONE)
814 beam.effect_known = false;
815 // Wielded brand is always known, but maybe this was from a call
816 // to chaos_affect_actor.
817 beam.effect_wanton = !fake_chaos_attack;
819 if (using_weapon() && you.can_see(attacker))
821 beam.name = wep_name(DESC_YOUR);
825 beam.name = atk_name(DESC_THE);
828 (attacker->is_player()) ? KILL_YOU
829 : attacker->as_monster()->confused_by_you() ? KILL_YOU_CONF
832 if (beam.thrower == KILL_YOU || attacker->as_monster()->friendly())
833 beam.attitude = ATT_FRIENDLY;
835 beam.source_id = attacker->mid;
837 beam.source = defender->pos();
838 beam.target = defender->pos();
840 beam.damage = dice_def(damage_done + special_damage + aux_damage, 1);
842 beam.ench_power = beam.damage.num;
844 const bool you_could_see = you.can_see(defender);
848 obvious_effect = beam.obvious_effect;
851 if (!you.can_see(attacker))
852 obvious_effect = false;
855 // NOTE: random_chaos_brand() and random_chaos_attack_flavour() should
856 // return a set of effects that are roughly the same, to make it easy
857 // for chaos_affects_defender() not to do duplicate effects caused
858 // by the non-chaos brands/flavours they return.
859 brand_type attack::random_chaos_brand()
861 brand_type brand = SPWPN_NORMAL;
862 // Assuming the chaos to be mildly intelligent, try to avoid brands
863 // that clash with the most basic resists of the defender,
864 // i.e. its holiness.
867 brand = (random_choose_weighted(
871 10, SPWPN_ELECTROCUTION,
882 if (one_chance_in(3))
885 bool susceptible = true;
889 if (defender->is_fiery())
893 if (defender->is_icy())
897 if (defender->holiness() == MH_UNDEAD)
900 case SPWPN_VAMPIRISM:
901 if (defender->is_summoned())
906 // intentional fall-through
908 if (defender->holiness() != MH_NATURAL)
911 case SPWPN_HOLY_WRATH:
912 if (!defender->holy_wrath_susceptible())
916 if (defender->holiness() == MH_NONLIVING
917 || defender->holiness() == MH_PLANT)
922 case SPWPN_ANTIMAGIC:
923 if (!defender->antimagic_susceptible())
933 #ifdef NOTE_DEBUG_CHAOS_BRAND
934 string brand_name = "CHAOS brand: ";
937 case SPWPN_NORMAL: brand_name += "(plain)"; break;
938 case SPWPN_FLAMING: brand_name += "flaming"; break;
939 case SPWPN_FREEZING: brand_name += "freezing"; break;
940 case SPWPN_HOLY_WRATH: brand_name += "holy wrath"; break;
941 case SPWPN_ELECTROCUTION: brand_name += "electrocution"; break;
942 case SPWPN_VENOM: brand_name += "venom"; break;
943 case SPWPN_DRAINING: brand_name += "draining"; break;
944 case SPWPN_DISTORTION: brand_name += "distortion"; break;
945 case SPWPN_VAMPIRISM: brand_name += "vampirism"; break;
946 case SPWPN_VORPAL: brand_name += "vorpal"; break;
947 case SPWPN_ANTIMAGIC: brand_name += "antimagic"; break;
949 // both ranged and non-ranged
950 case SPWPN_CHAOS: brand_name += "chaos"; break;
951 case SPWPN_CONFUSE: brand_name += "confusion"; break;
952 default: brand_name += "(other)"; break;
955 // Pretty much duplicated by the chaos effect note,
956 // which will be much more informative.
957 if (brand != SPWPN_CHAOS)
958 take_note(Note(NOTE_MESSAGE, 0, 0, brand_name.c_str()), true);
963 void attack::do_miscast()
965 if (miscast_level == -1)
968 ASSERT(miscast_target != nullptr);
969 ASSERT_RANGE(miscast_level, 0, 4);
970 ASSERT(count_bits(miscast_type) == 1);
972 if (!miscast_target->alive())
975 if (miscast_target->is_player() && you.banished)
978 const bool chaos_brand =
979 using_weapon() && get_weapon_brand(*weapon) == SPWPN_CHAOS;
981 // If the miscast is happening on the attacker's side and is due to
982 // a chaos weapon then make smoke/sand/etc pour out of the weapon
983 // instead of the attacker's hands.
986 string cause = atk_name(DESC_THE);
988 const int ignore_mask = ISFLAG_KNOW_CURSE | ISFLAG_KNOW_PLUSES;
990 if (attacker->is_player())
994 cause = "a chaos effect from ";
995 // Ignore a lot of item flags to make cause as short as possible,
996 // so it will (hopefully) fit onto a single line in the death
998 cause += wep_name(DESC_YOUR, ignore_mask | ISFLAG_COSMETIC_MASK);
1000 if (miscast_target == attacker)
1001 hand_str = wep_name(DESC_PLAIN, ignore_mask);
1006 if (chaos_brand && miscast_target == attacker
1007 && you.can_see(attacker))
1009 hand_str = wep_name(DESC_PLAIN, ignore_mask);
1013 MiscastEffect(miscast_target, attacker, MELEE_MISCAST,
1014 (spschool_flag_type) miscast_type, miscast_level, cause,
1015 NH_NEVER, 0, hand_str, false);
1017 // Don't do miscast twice for one attack.
1021 void attack::drain_defender()
1023 if (defender->is_monster() && coinflip())
1026 if (defender->holiness() != MH_NATURAL)
1029 special_damage = resist_adjust_damage(defender, BEAM_NEG,
1030 (1 + random2(damage_done)) / 2);
1032 if (defender->drain_exp(attacker, true, 20 + min(35, damage_done)))
1034 if (defender->is_player())
1035 obvious_effect = true;
1036 else if (defender_visible)
1038 special_damage_message =
1041 atk_name(DESC_THE).c_str(),
1042 attacker->conj_verb("drain").c_str(),
1043 defender_name(true).c_str());
1046 attacker->god_conduct(DID_NECROMANCY, 2);
1050 void attack::drain_defender_speed()
1052 if (!defender->res_negative_energy())
1056 mprf("%s %s %s vigour!",
1057 atk_name(DESC_THE).c_str(),
1058 attacker->conj_verb("drain").c_str(),
1059 def_name(DESC_ITS).c_str());
1062 special_damage = 1 + random2(damage_done) / 2;
1063 defender->slow_down(attacker, 5 + random2(7));
1067 int attack::inflict_damage(int dam, beam_type flavour, bool clean)
1069 if (flavour == NUM_BEAMS)
1070 flavour = special_damage_flavour;
1071 // Auxes temporarily clear damage_brand so we don't need to check
1072 if (damage_brand == SPWPN_REAPING ||
1073 damage_brand == SPWPN_CHAOS && one_chance_in(100))
1075 defender->props["reaping_damage"].get_int() += dam;
1076 // With two reapers of different friendliness, the most recent one
1077 // gets the zombie. Too rare a case to care any more.
1078 defender->props["reaper"].get_int() = attacker->mid;
1080 return defender->hurt(responsible, dam, flavour, kill_type,
1081 "", aux_source.c_str(), clean);
1084 /* If debug, return formatted damage done
1087 string attack::debug_damage_number()
1089 #ifdef DEBUG_DIAGNOSTICS
1090 return make_stringf(" for %d", damage_done);
1096 /* Returns standard attack punctuation
1098 * Used in player / monster (both primary and aux) attacks
1100 string attack::attack_strength_punctuation(int dmg)
1104 else if (dmg < HIT_MED)
1106 else if (dmg < HIT_STRONG)
1111 int tmpdamage = dmg;
1112 while (tmpdamage >= 2*HIT_STRONG)
1121 /* Returns evasion adverb
1124 string attack::evasion_margin_adverb()
1126 return (ev_margin <= -20) ? " completely" :
1127 (ev_margin <= -12) ? "" :
1128 (ev_margin <= -6) ? " closely"
1132 void attack::stab_message()
1134 defender->props["helpless"] = true;
1138 case 6: // big melee, monster surrounded/not paying attention
1141 mprf("You %s %s from a blind spot!",
1142 (you.species == SP_FELID) ? "pounce on" : "strike",
1143 defender->name(DESC_THE).c_str());
1147 mprf("You catch %s momentarily off-guard.",
1148 defender->name(DESC_THE).c_str());
1151 case 4: // confused/fleeing
1152 if (!one_chance_in(3))
1154 mprf("You catch %s completely off-guard!",
1155 defender->name(DESC_THE).c_str());
1159 mprf("You %s %s from behind!",
1160 (you.species == SP_FELID) ? "pounce on" : "strike",
1161 defender->name(DESC_THE).c_str());
1166 if (you.species == SP_FELID && coinflip())
1168 mprf("You pounce on the unaware %s!",
1169 defender->name(DESC_PLAIN).c_str());
1172 mprf("%s fails to defend %s.",
1173 defender->name(DESC_THE).c_str(),
1174 defender->pronoun(PRONOUN_REFLEXIVE).c_str());
1178 defender->props.erase("helpless");
1181 /* Returns the attacker's name
1183 * Helper method to easily access the attacker's name
1185 string attack::atk_name(description_level_type desc)
1187 return actor_name(attacker, desc, attacker_visible);
1190 /* Returns the defender's name
1192 * Helper method to easily access the defender's name
1194 string attack::def_name(description_level_type desc)
1196 return actor_name(defender, desc, defender_visible);
1199 /* Returns the attacking weapon's name
1201 * Sets upthe appropriate descriptive level and obtains the name of a weapon
1202 * based on if the attacker is a player or non-player (non-players use a
1203 * plain name and a manually entered pronoun)
1205 string attack::wep_name(description_level_type desc, iflags_t ignre_flags)
1207 ASSERT(weapon != nullptr);
1209 if (attacker->is_player())
1210 return weapon->name(desc, false, false, false, false, ignre_flags);
1213 bool possessive = false;
1214 if (desc == DESC_YOUR)
1221 name = apostrophise(atk_name(desc)) + " ";
1223 name += weapon->name(DESC_PLAIN, false, false, false, false, ignre_flags);
1228 /* TODO: Remove this!
1229 * Removing it may not really be practical, in retrospect. Its only used
1230 * below, in calc_elemental_brand_damage, which is called for both frost and
1231 * flame brands for both players and monsters.
1233 string attack::defender_name(bool allow_reflexive)
1235 if (allow_reflexive && attacker == defender)
1236 return actor_pronoun(attacker, PRONOUN_REFLEXIVE, attacker_visible);
1238 return def_name(DESC_THE);
1241 int attack::player_stat_modify_damage(int damage)
1244 const int dam_stat_val = calc_stat_to_dam_base();
1246 if (dam_stat_val > 11)
1247 dammod += (random2(dam_stat_val - 11) * 2);
1248 else if (dam_stat_val < 9)
1249 dammod -= (random2(9 - dam_stat_val) * 3);
1257 int attack::player_apply_weapon_skill(int damage)
1261 damage *= 2500 + (random2(you.skill(wpn_skill, 100) + 1));
1268 int attack::player_apply_fighting_skill(int damage, bool aux)
1270 const int base = aux? 40 : 30;
1272 damage *= base * 100 + (random2(you.skill(SK_FIGHTING, 100) + 1));
1273 damage /= base * 100;
1278 int attack::player_apply_misc_modifiers(int damage)
1284 * Get the damage bonus from a weapon's enchantment.
1286 int attack::get_weapon_plus()
1288 if (weapon->base_type == OBJ_RODS)
1289 return weapon->special;
1290 if (weapon->base_type == OBJ_STAVES || weapon->sub_type == WPN_BLOWGUN)
1292 return weapon->plus;
1295 // Slaying and weapon enchantment. Apply this for slaying even if not
1296 // using a weapon to attack.
1297 int attack::player_apply_slaying_bonuses(int damage, bool aux)
1299 int damage_plus = 0;
1300 if (!aux && using_weapon())
1302 damage_plus = get_weapon_plus();
1303 if (you.duration[DUR_CORROSION])
1304 damage_plus -= 3 * you.props["corrosion_amount"].get_int();
1306 damage_plus += slaying_bonus(!weapon && wpn_skill == SK_THROWING
1307 || (weapon && is_range_weapon(*weapon)
1308 && using_weapon()));
1310 damage += (damage_plus > -1) ? (random2(1 + damage_plus))
1311 : (-random2(1 - damage_plus));
1315 int attack::player_apply_final_multipliers(int damage)
1317 // Can't affect much of anything as a shadow.
1318 if (you.form == TRAN_SHADOW)
1319 damage = div_rand_round(damage, 2);
1324 void attack::player_exercise_combat_skills()
1328 /* Returns attacker base unarmed damage
1330 * Scales for current mutations and unarmed effects
1331 * TODO: Complete symmetry for base_unarmed damage
1332 * between monsters and players.
1334 int attack::calc_base_unarmed_damage()
1336 // Should only get here if we're not wielding something that's a weapon.
1337 // If there's a non-weapon in hand, it has no base damage.
1341 if (!attacker->is_player())
1344 int damage = get_form()->get_base_unarmed_damage();
1346 if (you.has_usable_claws())
1348 // Claw damage only applies for bare hands.
1349 damage += you.has_claws(false) * 2;
1350 apply_bleeding = true;
1353 if (you.form_uses_xl())
1354 damage += you.experience_level;
1355 else if (you.form == TRAN_BAT || you.form == TRAN_PORCUPINE)
1357 // Bats really don't do a lot of damage.
1358 damage += you.skill_rdiv(wpn_skill, 1, 5);
1361 damage += you.skill_rdiv(wpn_skill);
1369 // TODO: Potentially remove, consider integrating with other to-hit or stat
1370 // calculating methods
1371 // weighted average of strength and dex, between (str+dex)/2 and dex
1372 int attack::calc_stat_to_hit_base()
1374 const int weight = weapon ? weapon_str_weight(*weapon) : 4;
1376 // dex is modified by strength towards the average, by the
1377 // weighted amount weapon_str_weight() / 20.
1378 return you.dex() + (you.strength() - you.dex()) * weight / 20;
1381 // weighted average of strength and dex, between str and (str+dex)/2
1382 int attack::calc_stat_to_dam_base()
1384 const int weight = weapon ? 10 - weapon_str_weight(*weapon) : 6;
1385 return you.strength() + (you.dex() - you.strength()) * weight / 20;
1388 int attack::calc_damage()
1390 if (attacker->is_monster())
1394 if (using_weapon() || wpn_skill == SK_THROWING)
1396 damage_max = weapon_damage();
1397 damage += random2(damage_max);
1399 int wpn_damage_plus = 0;
1400 if (weapon) // can be 0 for throwing projectiles
1401 wpn_damage_plus = get_weapon_plus();
1403 const int jewellery = attacker->as_monster()->inv[MSLOT_JEWELLERY];
1404 if (jewellery != NON_ITEM
1405 && mitm[jewellery].is_type(OBJ_JEWELLERY, RING_SLAYING))
1407 wpn_damage_plus += mitm[jewellery].plus;
1410 wpn_damage_plus += attacker->scan_artefacts(ARTP_SLAYING);
1412 if (wpn_damage_plus >= 0)
1413 damage += random2(wpn_damage_plus);
1415 damage -= random2(1 - wpn_damage_plus);
1417 damage -= 1 + random2(3);
1420 damage_max += attk_damage;
1421 damage += 1 + random2(attk_damage);
1423 damage = apply_damage_modifiers(damage, damage_max);
1425 set_attack_verb(damage);
1426 return apply_defender_ac(damage, damage_max);
1430 int potential_damage, damage;
1432 potential_damage = using_weapon() || wpn_skill == SK_THROWING
1433 ? weapon_damage() : calc_base_unarmed_damage();
1435 potential_damage = player_stat_modify_damage(potential_damage);
1437 damage = random2(potential_damage+1);
1439 damage = player_apply_weapon_skill(damage);
1440 damage = player_apply_fighting_skill(damage, false);
1441 damage = player_apply_misc_modifiers(damage);
1442 damage = player_apply_slaying_bonuses(damage, false);
1443 damage = player_stab(damage);
1444 // A failed stab may have awakened monsters, but that could have
1445 // caused the defender to cease to exist (spectral weapons with
1446 // missing summoners; or pacified monsters on a stair). FIXME:
1447 // The correct thing to do would be either to delay the call to
1448 // alert_nearby_monsters (currently in player_stab) until later
1449 // in the attack; or to avoid removing monsters in handle_behaviour.
1450 if (!defender->alive())
1452 damage = player_apply_final_multipliers(damage);
1453 damage = apply_defender_ac(damage);
1455 damage = max(0, damage);
1456 set_attack_verb(damage);
1464 int attack::test_hit(int to_land, int ev, bool randomise_ev)
1466 int margin = AUTOMATIC_HIT;
1468 ev = random2avg(2*ev, 2);
1469 if (to_land >= AUTOMATIC_HIT)
1471 else if (x_chance_in_y(MIN_HIT_MISS_PERCENTAGE, 100))
1472 margin = (random2(2) ? 1 : -1) * AUTOMATIC_HIT;
1474 margin = to_land - ev;
1476 #ifdef DEBUG_DIAGNOSTICS
1477 dprf(DIAG_COMBAT, "to hit: %d; ev: %d; result: %s (%d)",
1478 to_hit, ev, (margin >= 0) ? "hit" : "miss", margin);
1484 int attack::apply_defender_ac(int damage, int damage_max) const
1487 int stab_bypass = 0;
1490 stab_bypass = you.skill(wpn_skill, 50) + you.skill(SK_STEALTH, 50);
1491 stab_bypass = random2(div_rand_round(stab_bypass, 100 * stab_bonus));
1493 int after_ac = defender->apply_ac(damage, damage_max,
1494 AC_NORMAL, stab_bypass);
1495 dprf(DIAG_COMBAT, "AC: att: %s, def: %s, ac: %d, gdr: %d, dam: %d -> %d",
1496 attacker->name(DESC_PLAIN, true).c_str(),
1497 defender->name(DESC_PLAIN, true).c_str(),
1498 defender->armour_class(),
1499 defender->gdr_perc(),
1506 bool attack::attack_warded_off()
1508 const int WARDING_CHECK = 60;
1510 if (defender->warding()
1511 && attacker->is_summoned()
1512 && attacker->as_monster()->check_res_magic(WARDING_CHECK) <= 0)
1516 mprf("%s attack is warded away.",
1517 attacker->name(DESC_ITS).c_str());
1525 /* Determine whether a block occurred
1527 * No blocks if defender is incapacitated, would be nice to eventually expand
1528 * this method to handle partial blocks as well as full blocks (although this
1529 * would serve as a nerf to shields and - while more realistic - may not be a
1530 * good mechanic for shields.
1532 * Returns (block_occurred)
1534 bool attack::attack_shield_blocked(bool verbose)
1536 if (defender == attacker)
1537 return false; // You can't block your own attacks!
1539 if (defender->incapacitated())
1542 const int con_block = random2(attacker->shield_bypass_ability(to_hit)
1543 + defender->shield_block_penalty());
1544 int pro_block = defender->shield_bonus();
1546 if (!attacker->visible_to(defender))
1549 dprf(DIAG_COMBAT, "Defender: %s, Pro-block: %d, Con-block: %d",
1550 def_name(DESC_PLAIN).c_str(), pro_block, con_block);
1552 if (pro_block >= con_block)
1554 perceived_attack = true;
1556 if (attack_ignores_shield(verbose))
1559 if (needs_message && verbose)
1561 mprf("%s %s %s attack.",
1562 defender_name(false).c_str(),
1563 defender->conj_verb("block").c_str(),
1564 attacker == defender ? "its own"
1565 : atk_name(DESC_ITS).c_str());
1568 defender->shield_block_succeeded(attacker);
1576 attack_flavour attack::random_chaos_attack_flavour()
1578 attack_flavour flavours[] =
1579 {AF_FIRE, AF_COLD, AF_ELEC, AF_POISON, AF_VAMPIRIC, AF_DISTORT,
1580 AF_CONFUSE, AF_CHAOS};
1581 return RANDOM_ELEMENT(flavours);
1584 bool attack::apply_damage_brand(const char *what)
1586 bool brand_was_known = false;
1592 if (is_artefact(*weapon))
1593 brand_was_known = artefact_known_property(*weapon, ARTP_BRAND);
1595 brand_was_known = item_type_known(*weapon);
1599 obvious_effect = false;
1600 brand = damage_brand == SPWPN_CHAOS ? random_chaos_brand() : damage_brand;
1602 if (brand != SPWPN_FLAMING && brand != SPWPN_FREEZING
1603 && brand != SPWPN_ELECTROCUTION && brand != SPWPN_VAMPIRISM
1604 && !defender->alive())
1606 // Most brands have no extra effects on just killed enemies, and the
1607 // effect would be often inappropriate.
1612 && (brand == SPWPN_FLAMING || brand == SPWPN_FREEZING
1613 || brand == SPWPN_HOLY_WRATH || brand == SPWPN_ANTIMAGIC
1614 || brand == SPWPN_VORPAL || brand == SPWPN_VAMPIRISM))
1616 // These brands require some regular damage to function.
1623 calc_elemental_brand_damage(BEAM_FIRE,
1624 defender->is_icy() ? "melt" : "burn",
1626 attacker->god_conduct(DID_FIRE, 1);
1629 case SPWPN_FREEZING:
1630 calc_elemental_brand_damage(BEAM_COLD, "freeze", what);
1633 case SPWPN_HOLY_WRATH:
1634 if (defender->holy_wrath_susceptible())
1635 special_damage = 1 + (random2(damage_done * 15) / 10);
1637 if (special_damage && defender_visible)
1639 special_damage_message =
1642 defender_name(false).c_str(),
1643 defender->conj_verb("convulse").c_str(),
1644 attack_strength_punctuation(special_damage).c_str());
1648 case SPWPN_ELECTROCUTION:
1649 if (defender->res_elec() > 0)
1651 else if (one_chance_in(3))
1653 special_damage_message =
1654 defender->is_player()?
1655 "You are electrocuted!"
1656 : "There is a sudden explosion of sparks!";
1657 special_damage = 8 + random2(13);
1658 special_damage_flavour = BEAM_ELECTRICITY;
1664 if (!one_chance_in(4))
1668 if (defender->is_player())
1669 old_poison = you.duration[DUR_POISONING];
1673 (defender->as_monster()->get_ench(ENCH_POISON)).degree;
1676 defender->poison(attacker, 6 + random2(8) + random2(damage_done * 3 / 2));
1678 if (defender->is_player()
1679 && old_poison < you.duration[DUR_POISONING]
1680 || !defender->is_player()
1682 (defender->as_monster()->get_ench(ENCH_POISON)).degree)
1684 obvious_effect = true;
1690 case SPWPN_DRAINING:
1695 special_damage = 1 + random2(damage_done) / 3;
1696 // Note: Leaving special_damage_message empty because there isn't one.
1699 case SPWPN_VAMPIRISM:
1702 || defender->holiness() != MH_NATURAL
1703 || defender->res_negative_energy()
1705 || attacker->stat_hp() == attacker->stat_maxhp()
1706 || !defender->is_player()
1707 && defender->as_monster()->is_summoned()
1708 || attacker->is_player() && you.duration[DUR_DEATHS_DOOR]
1709 || !attacker->is_player()
1710 && attacker->as_monster()->has_ench(ENCH_DEATHS_DOOR)
1711 || x_chance_in_y(2, 5) && !is_unrandom_artefact(*weapon, UNRAND_LEECH))
1716 obvious_effect = true;
1718 // Handle weapon effects.
1719 // We only get here if we've done base damage, so no
1720 // worries on that score.
1721 if (attacker->is_player())
1722 mpr("You feel better.");
1723 else if (attacker_visible)
1725 if (defender->is_player())
1727 mprf("%s draws strength from your injuries!",
1728 attacker->name(DESC_THE).c_str());
1732 mprf("%s is healed.",
1733 attacker->name(DESC_THE).c_str());
1737 int hp_boost = is_unrandom_artefact(*weapon, UNRAND_VAMPIRES_TOOTH)
1738 ? damage_done : 1 + random2(damage_done);
1740 dprf(DIAG_COMBAT, "Vampiric Healing: damage %d, healed %d",
1741 damage_done, hp_boost);
1742 attacker->heal(hp_boost);
1744 attacker->god_conduct(DID_NECROMANCY, 2);
1748 pain_affects_defender();
1751 case SPWPN_DISTORTION:
1752 ret = distortion_affects_defender();
1757 // This was originally for confusing touch and it doesn't really
1758 // work on the player, but a monster with a chaos weapon will
1759 // occasionally come up with this brand. -cao
1760 if (defender->is_player())
1763 // Also used for players in fungus form.
1764 if (attacker->is_player()
1765 && you.form == TRAN_FUNGUS
1766 && !you.duration[DUR_CONFUSING_TOUCH]
1767 && defender->is_unbreathing())
1773 (defender->holiness() == MH_NATURAL ? random2(30) : random2(22));
1775 if (hdcheck < defender->get_hit_dice()
1777 || defender->as_monster()->check_clarity(false))
1782 // Declaring these just to pass to the enchant function.
1784 beam_temp.thrower = attacker->is_player() ? KILL_YOU : KILL_MON;
1785 beam_temp.flavour = BEAM_CONFUSION;
1786 beam_temp.source_id = attacker->mid;
1787 beam_temp.apply_enchantment_to_monster(defender->as_monster());
1788 obvious_effect = beam_temp.obvious_effect;
1790 if (attacker->is_player() && damage_brand == SPWPN_CONFUSE
1791 && you.duration[DUR_CONFUSING_TOUCH])
1793 you.duration[DUR_CONFUSING_TOUCH] = 1;
1794 obvious_effect = false;
1800 chaos_affects_defender();
1803 case SPWPN_ANTIMAGIC:
1804 antimagic_affects_defender(damage_done * 8);
1808 if (using_weapon() && is_unrandom_artefact(*weapon, UNRAND_HELLFIRE))
1810 calc_elemental_brand_damage(BEAM_HELLFIRE,
1811 defender->is_icy() ? "melt" : "burn",
1813 defender->expose_to_element(BEAM_HELLFIRE);
1814 attacker->god_conduct(DID_UNHOLY, 2 + random2(3));
1815 attacker->god_conduct(DID_FIRE, 10 + random2(5));
1820 if (damage_brand == SPWPN_CHAOS)
1822 if (brand != SPWPN_CHAOS && !ret
1823 && miscast_level == -1 && one_chance_in(20))
1826 miscast_type = SPTYP_RANDOM;
1827 miscast_target = coinflip() ? attacker : defender;
1830 if (responsible->is_player())
1831 did_god_conduct(DID_CHAOS, 2 + random2(3), brand_was_known);
1834 if (!obvious_effect)
1835 obvious_effect = !special_damage_message.empty();
1837 if (needs_message && !special_damage_message.empty())
1839 mpr(special_damage_message);
1841 special_damage_message.clear();
1842 // Don't do message-only miscasts along with a special
1844 if (miscast_level == 0)
1848 if (special_damage > 0)
1849 inflict_damage(special_damage, special_damage_flavour);
1851 if (defender->alive())
1856 defender->expose_to_element(BEAM_FIRE);
1859 case SPWPN_FREEZING:
1860 defender->expose_to_element(BEAM_COLD, 2);
1867 if (obvious_effect && attacker_visible && using_weapon())
1869 if (is_artefact(*weapon))
1870 artefact_learn_prop(*weapon, ARTP_BRAND);
1872 set_ident_flags(*weapon, ISFLAG_KNOW_TYPE);
1878 /* Calculates special damage, prints appropriate combat text
1880 * Applies a particular damage brand to the current attack, the setup and
1881 * calculation of base damage and other effects varies based on the type
1882 * of attack, but the calculation of elemental damage should be consistent.
1884 void attack::calc_elemental_brand_damage(beam_type flavour,
1888 special_damage = resist_adjust_damage(defender, flavour,
1889 random2(damage_done) / 2 + 1);
1891 if (needs_message && special_damage > 0 && verb)
1893 // XXX: assumes "what" is singular
1894 special_damage_message = make_stringf(
1896 what ? what : atk_name(DESC_THE).c_str(),
1897 what ? conjugate_verb(verb, false).c_str()
1898 : attacker->conj_verb(verb).c_str(),
1899 // Don't allow reflexive if the subject wasn't the attacker.
1900 defender_name(!what).c_str(),
1901 attack_strength_punctuation(special_damage).c_str());
1905 int attack::player_stab_weapon_bonus(int damage)
1907 int stab_skill = you.skill(wpn_skill, 50) + you.skill(SK_STEALTH, 50);
1909 if (player_good_stab())
1911 // We might be unarmed if we're using the boots of the Assassin.
1912 const bool extra_good = using_weapon() && weapon->sub_type == WPN_DAGGER;
1913 int bonus = you.dex() * (stab_skill + 100) / (extra_good ? 500 : 1000);
1915 bonus = stepdown_value(bonus, 10, 10, 30, 30);
1917 damage *= 10 + div_rand_round(stab_skill, 100 * stab_bonus);
1921 damage *= 12 + div_rand_round(stab_skill, 100 * stab_bonus);
1927 int attack::player_stab(int damage)
1929 // The stabbing message looks better here:
1932 // Construct reasonable message.
1935 practise(EX_WILL_STAB);
1940 // Ok.. if you didn't backstab, you wake up the neighborhood.
1941 // I can live with that.
1942 alert_nearby_monsters();
1947 // Let's make sure we have some damage to work with...
1948 damage = max(1, damage);
1950 damage = player_stab_weapon_bonus(damage);
1956 /* Check for stab and prepare combat for stab-values
1958 * Grant an automatic stab if paralyzed or sleeping (with highest damage value)
1959 * stab_bonus is used as the divisor in damage calculations, so lower values
1960 * will yield higher damage. Normal stab chance is (stab_skill + dex + 1 / roll)
1961 * This averages out to about 1/3 chance for a non extended-endgame stabber.
1963 void attack::player_stab_check()
1965 if (you.duration[DUR_CLUMSY] || you.confused())
1967 stab_attempt = false;
1972 const stab_type st = find_stab_type(&you, defender);
1973 stab_attempt = (st != STAB_NO_STAB);
1974 const bool roll_needed = (st != STAB_SLEEPING && st != STAB_PARALYSED);
1977 if (st == STAB_INVISIBLE)
1987 case STAB_PARALYSED:
1990 case STAB_HELD_IN_NET:
1991 case STAB_PETRIFYING:
1992 case STAB_PETRIFIED:
1995 case STAB_INVISIBLE:
2001 case STAB_DISTRACTED:
2006 // See if we need to roll against dexterity / stabbing.
2007 if (stab_attempt && roll_needed)
2009 stab_attempt = x_chance_in_y(you.skill_rdiv(wpn_skill, 1, 2)
2010 + you.skill_rdiv(SK_STEALTH, 1, 2)
2016 count_action(CACT_STAB, st);