text: /*
   *  File:       attack.cc
   *  Summary:    Methods of the attack class, generalized functions which may
   *              be overloaded by inheriting classes.
   *  Written by: Robert Burnham
   */
  
  #include "AppHdr.h"
  
  #include "attack.h"
  
  #include <algorithm>
  #include <cmath>
  #include <cstdio>
  #include <cstdlib>
  #include <cstring>
  #include <functional>
  
  #include "art-enum.h"
  #include "chardump.h"
  #include "delay.h"
  #include "english.h"
  #include "env.h"
  #include "exercise.h"
  #include "fight.h"
  #include "fineff.h"
  #include "god-conduct.h"
  #include "god-passive.h" // passive_t::no_haste
  #include "item-name.h"
  #include "item-prop.h"
  #include "message.h"
  #include "mon-behv.h"
  #include "mon-clone.h"
  #include "mon-death.h"
  #include "nearby-danger.h"
  #include "pronoun-type.h"
  #include "religion.h"
  #include "spl-util.h"
  #include "state.h"
  #include "stepdown.h"
  #include "stringutil.h"
  #include "tag-version.h"
  #include "transform.h"
  #include "xom.h"
  
  /*
   **************************************************
   *             BEGIN PUBLIC FUNCTIONS             *
   **************************************************
  */
  attack::attack(actor *attk, actor *defn, actor *blame)
      : attacker(attk), defender(defn), responsible(blame ? blame : attk),
        attack_occurred(false), cancel_attack(false), did_hit(false),
        needs_message(false), attacker_visible(false), defender_visible(false),
        perceived_attack(false), obvious_effect(false), to_hit(0),
        damage_done(0), special_damage(0), aux_damage(0), min_delay(0),
        final_attack_delay(0), special_damage_flavour(BEAM_NONE),
        stab_attempt(false), stab_bonus(0), ev_margin(0), weapon(nullptr),
        damage_brand(SPWPN_NORMAL), wpn_skill(SK_UNARMED_COMBAT),
        shield(nullptr), art_props(0), unrand_entry(nullptr),
        attacker_to_hit_penalty(0), attack_verb("bug"), verb_degree(),
        no_damage_message(), special_damage_message(), aux_attack(), aux_verb(),
        attacker_armour_tohit_penalty(0), attacker_shield_tohit_penalty(0),
        defender_shield(nullptr), fake_chaos_attack(false), simu(false),
        aux_source(""), kill_type(KILLED_BY_MONSTER)
  {
      // No effective code should execute, we'll call init_attack again from
      // the child class, since initializing an attack will vary based the within
      // type of attack actually being made (melee, ranged, etc.)
  }
  
  bool attack::handle_phase_attempted()
  {
      return true;
  }
  
  bool attack::handle_phase_blocked()
  {
      damage_done = 0;
  
      if (attacker->is_player())
          behaviour_event(defender->as_monster(), ME_WHACK, attacker);
  
      return true;
  }
  
  bool attack::handle_phase_damaged()
  {
      // We have to check in_bounds() because removed kraken tentacles are
      // temporarily returned to existence (without a position) when they
      // react to damage.
      if (defender->can_bleed()
          && !defender->is_summoned()
          && !defender->submerged()
          && in_bounds(defender->pos())
          && !simu)
      {
          int blood = damage_done;
          if (blood > defender->stat_hp())
              blood = defender->stat_hp();
          if (blood)
              blood_fineff::schedule(defender, defender->pos(), blood);
      }
  
      announce_hit();
      // Inflict stored damage
      damage_done = inflict_damage(damage_done);
  
      // TODO: Unify these, added here so we can get rid of player_attack
      if (attacker->is_player())
      {
          if (damage_done)
              player_exercise_combat_skills();
      }
      else
      {
          if (!mons_attack_effects())
              return false;
      }
  
      // It's okay if a monster took lethal damage, but we should stop
      // the combat if it was already reset (e.g. a spectral weapon that
      // took damage and then noticed that its caster is gone).
      return defender->is_player() || !invalid_monster(defender->as_monster());
  }
  
  bool attack::handle_phase_killed()
  {
      monster* mon = defender->as_monster();
      if (!invalid_monster(mon))
      {
          // Was this a reflected missile from the player?
          if (responsible->mid == MID_YOU_FAULTLESS)
              monster_die(*mon, KILL_YOU_MISSILE, YOU_FAULTLESS);
          else
              monster_die(*mon, attacker);
      }
  
      return true;
  }
  
  bool attack::handle_phase_end()
  {
      return true;
  }
  
  /**
   * Calculate to-hit penalties from the attacker's armour and shield, if any.
   * Set them into the appropriate fields.
   *
   * @param random If false, calculate average to-hit penalties deterministically.
   */
  void attack::calc_encumbrance_penalties(bool random)
  {
      attacker_armour_tohit_penalty =
          maybe_random_div(attacker->armour_tohit_penalty(true, 20), 20, random);
      attacker_shield_tohit_penalty =
          maybe_random_div(attacker->shield_tohit_penalty(true, 20), 20, random);
  }
  
  /**
   * Calculate the to-hit for an attacker before the main die roll.
   *
   * @param random If false, calculate average to-hit deterministically.
   */
  int attack::calc_pre_roll_to_hit(bool random)
  {
      if (using_weapon()
          && (is_unrandom_artefact(*weapon, UNRAND_WOE)
              || is_unrandom_artefact(*weapon, UNRAND_SNIPER)))
      {
          return AUTOMATIC_HIT;
      }
  
      int mhit = attacker->is_player() ?
                  15 + (you.dex() / 2)
                : calc_mon_to_hit_base();
  
      // This if statement is temporary, it should be removed when the
      // implementation of a more universal (and elegant) to-hit calculation
      // is designed. The actual code is copied from the old mons_to_hit and
      // player_to_hit methods.
      if (attacker->is_player())
      {
          // fighting contribution
          mhit += maybe_random_div(you.skill(SK_FIGHTING, 100), 100, random);
  
          // weapon skill contribution
          if (using_weapon())
          {
              if (wpn_skill != SK_FIGHTING)
              {
                  if (you.skill(wpn_skill) < 1 && player_in_a_dangerous_place() && random)
                      xom_is_stimulated(10); // Xom thinks that is mildly amusing.
  
                  mhit += maybe_random_div(you.skill(wpn_skill, 100), 100,
                                           random);
              }
          }
          else if (you.form_uses_xl())
              mhit += maybe_random_div(you.experience_level * 100, 100, random);
          else
          {
              // Claws give a slight bonus to accuracy when active
              mhit += (you.get_mutation_level(MUT_CLAWS) > 0
                       && wpn_skill == SK_UNARMED_COMBAT) ? 4 : 2;
  
              mhit += maybe_random_div(you.skill(wpn_skill, 100), 100,
                                       random);
          }
  
          // weapon bonus contribution
          if (using_weapon())
          {
              if (weapon->base_type == OBJ_WEAPONS)
              {
                  mhit += weapon->plus;
                  mhit += property(*weapon, PWPN_HIT);
              }
              else if (weapon->base_type == OBJ_STAVES)
                  mhit += property(*weapon, PWPN_HIT);
          }
  
          // slaying bonus
          mhit += slaying_bonus(wpn_skill == SK_THROWING
                                || (weapon && is_range_weapon(*weapon)
                                           && using_weapon()));
  
          // armour penalty (already calculated if random is true)
          if (!random)
              calc_encumbrance_penalties(random);
          mhit -= (attacker_armour_tohit_penalty + attacker_shield_tohit_penalty);
  
          // vertigo penalty
          if (you.duration[DUR_VERTIGO])
              mhit -= 5;
  
          // mutation
          if (you.get_mutation_level(MUT_EYEBALLS))
              mhit += 2 * you.get_mutation_level(MUT_EYEBALLS) + 1;
      }
      else    // Monster to-hit.
      {
          if (using_weapon())
              mhit += weapon->plus + property(*weapon, PWPN_HIT);
  
          const int jewellery = attacker->as_monster()->inv[MSLOT_JEWELLERY];
          if (jewellery != NON_ITEM
              && env.item[jewellery].is_type(OBJ_JEWELLERY, RING_SLAYING))
          {
              mhit += env.item[jewellery].plus;
          }
  
          mhit += attacker->scan_artefacts(ARTP_SLAYING);
      }
  
      return mhit;
  }
  
  /**
   * Calculate to-hit modifiers for an attacker that apply after the player's roll.
   *
   * @param mhit The post-roll player's to-hit value.
   */
  int attack::post_roll_to_hit_modifiers(int mhit, bool /*random*/)
  {
      int modifiers = 0;
  
      // Penalties for both players and monsters:
      modifiers -= 5 * attacker->inaccuracy();
  
      if (attacker->confused())
          modifiers += CONFUSION_TO_HIT_MALUS;
  
      // If no defender, we're calculating to-hit for debug-display
      // purposes, so don't drop down to defender code below
      if (defender == nullptr)
          return modifiers;
  
      if (!defender->visible_to(attacker))
      {
          if (attacker->is_player())
              modifiers -= 6;
          else
              modifiers -= mhit * 35 / 100;
      }
      else
      {
          // This can only help if you're visible!
          const int how_transparent = you.get_mutation_level(MUT_TRANSLUCENT_SKIN);
          if (defender->is_player() && how_transparent)
              modifiers += TRANSLUCENT_SKIN_TO_HIT_MALUS * how_transparent;
  
          // defender backlight bonus and umbra penalty.
          if (defender->backlit(false))
              modifiers += BACKLIGHT_TO_HIT_BONUS;
          if (!attacker->nightvision() && defender->umbra())
              modifiers += UMBRA_TO_HIT_MALUS;
      }
  
      return modifiers;
  }
  
  /**
   * Calculate the to-hit for an attacker
   *
   * @param random If false, calculate average to-hit deterministically.
   */
  int attack::calc_to_hit(bool random)
  {
      int mhit = calc_pre_roll_to_hit(random);
      if (mhit == AUTOMATIC_HIT)
          return AUTOMATIC_HIT;
  
      // hit roll
      if (attacker->is_player())
          mhit = maybe_random2(mhit, random);
  
      mhit += post_roll_to_hit_modifiers(mhit, random);
  
      // We already did this roll for players.
      if (!attacker->is_player())
          mhit = maybe_random2(mhit + 1, random);
  
      dprf(DIAG_COMBAT, "%s: to-hit: %d",
           attacker->name(DESC_PLAIN).c_str(), mhit);
  
      return mhit;
  }
  
  /* Returns an actor's name
   *
   * Takes into account actor visibility/invisibility and the type of description
   * to be used (capitalization, possessiveness, etc.)
   */
  string attack::actor_name(const actor *a, description_level_type desc,
                            bool actor_visible)
  {
      return actor_visible ? a->name(desc) : anon_name(desc);
  }
  
  /* Returns an actor's pronoun
   *
   * Takes into account actor visibility
   */
  string attack::actor_pronoun(const actor *a, pronoun_type pron,
                               bool actor_visible)
  {
      return actor_visible ? a->pronoun(pron) : anon_pronoun(pron);
  }
  
  /* Returns an anonymous actor's name
   *
   * Given the actor visible or invisible, returns the
   * appropriate possessive pronoun.
   */
  string attack::anon_name(description_level_type desc)
  {
      switch (desc)
      {
      case DESC_NONE:
          return "";
      case DESC_YOUR:
      case DESC_ITS:
          return "something's";
      case DESC_THE:
      case DESC_A:
      case DESC_PLAIN:
      default:
          return "something";
      }
  }
  
  /* Returns an anonymous actor's pronoun
   *
   * Given invisibility (whether out of LOS or just invisible), returns the
   * appropriate possessive, inflexive, capitalised pronoun.
   */
  string attack::anon_pronoun(pronoun_type pron)
  {
      return decline_pronoun(GENDER_NEUTER, pron);
  }
  
  /* Initializes an attack, setting up base variables and values
   *
   * Does not make any changes to any actors, items, or the environment,
   * in case the attack is cancelled or otherwise fails. Only initializations
   * that are universal to all types of attacks should go into this method,
   * any initialization properties that are specific to one attack or another
   * should go into their respective init_attack.
   *
   * Although this method will get overloaded by the derived class, we are
   * calling it from attack::attack(), before the overloading has taken place.
   */
  void attack::init_attack(skill_type unarmed_skill, int attack_number)
  {
      ASSERT(attacker);
      weapon          = attacker->weapon(attack_number);
  
      wpn_skill       = weapon ? item_attack_skill(*weapon) : unarmed_skill;
      if (attacker->is_player() && you.form_uses_xl())
          wpn_skill = SK_FIGHTING; // for stabbing, mostly
  
      calc_encumbrance_penalties(true);
      to_hit          = calc_to_hit(true);
  
      shield = attacker->shield();
      defender_shield = defender ? defender->shield() : defender_shield;
  
      if (weapon && weapon->base_type == OBJ_WEAPONS && is_artefact(*weapon))
      {
          artefact_properties(*weapon, art_props);
          if (is_unrandom_artefact(*weapon))
              unrand_entry = get_unrand_entry(weapon->unrand_idx);
      }
  
      attacker_visible   = attacker->observable();
      defender_visible   = defender && defender->observable();
      needs_message      = (attacker_visible || defender_visible);
  
      if (attacker->is_monster())
      {
          mon_attack_def mon_attk = mons_attack_spec(*attacker->as_monster(),
                                                     attack_number,
                                                     false);
  
          attk_type       = mon_attk.type;
          attk_flavour    = mon_attk.flavour;
  
          // Don't scale damage for YOU_FAULTLESS etc.
          if (attacker->get_experience_level() == 0)
              attk_damage = mon_attk.damage;
          else
          {
              attk_damage = div_rand_round(mon_attk.damage
                                               * attacker->get_hit_dice(),
                                           attacker->get_experience_level());
          }
  
          if (attk_type == AT_WEAP_ONLY)
          {
              int weap = attacker->as_monster()->inv[MSLOT_WEAPON];
              if (weap == NON_ITEM || is_range_weapon(env.item[weap]))
                  attk_type = AT_NONE;
              else
                  attk_type = AT_HIT;
          }
          else if (attk_type == AT_TRUNK_SLAP && attacker->type == MONS_SKELETON)
          {
              // Elephant trunks have no bones inside.
              attk_type = AT_NONE;
          }
      }
      else
      {
          attk_type    = AT_HIT;
          attk_flavour = AF_PLAIN;
      }
  }
  
  void attack::alert_defender()
  {
      // Allow monster attacks to draw the ire of the defender. Player
      // attacks are handled elsewhere.
      if (perceived_attack
          && defender->is_monster()
          && attacker->is_monster()
          && attacker->alive() && defender->alive()
          && (defender->as_monster()->foe == MHITNOT || one_chance_in(3)))
      {
          behaviour_event(defender->as_monster(), ME_WHACK, attacker);
      }
  
      // If an enemy attacked a friend, set the pet target if it isn't set
      // already, but not if sanctuary is in effect (pet target must be
      // set explicitly by the player during sanctuary).
      if (perceived_attack && attacker->alive()
          && (defender->is_player() || defender->as_monster()->friendly())
          && !attacker->is_player()
          && !crawl_state.game_is_arena()
          && !attacker->as_monster()->wont_attack())
      {
          if (defender->is_player())
          {
              interrupt_activity(activity_interrupt::monster_attacks,
                                 attacker->as_monster());
          }
          if (you.pet_target == MHITNOT && env.sanctuary_time <= 0)
              you.pet_target = attacker->mindex();
      }
  }
  
  bool attack::distortion_affects_defender()
  {
      enum disto_effect
      {
          SMALL_DMG,
          BIG_DMG,
          BANISH,
          BLINK,
          NONE
      };
  
      const disto_effect choice = random_choose_weighted(35, SMALL_DMG,
                                                         25, BIG_DMG,
                                                         5,  BANISH,
                                                         20, BLINK,
                                                         15,  NONE);
  
      if (simu && !(choice == SMALL_DMG || choice == BIG_DMG))
          return false;
  
      switch (choice)
      {
      case SMALL_DMG:
          special_damage += 1 + random2avg(7, 2);
          special_damage_message = make_stringf("Space bends around %s%s",
                                                defender_name(false).c_str(),
                                                attack_strength_punctuation(special_damage).c_str());
          break;
      case BIG_DMG:
          special_damage += 3 + random2avg(24, 2);
          special_damage_message =
              make_stringf("Space warps horribly around %s%s",
                           defender_name(false).c_str(),
                           attack_strength_punctuation(special_damage).c_str());
          break;
      case BLINK:
          if (defender_visible)
              obvious_effect = true;
          if (!defender->no_tele(true, false))
              blink_fineff::schedule(defender);
          break;
      case BANISH:
          if (defender_visible)
              obvious_effect = true;
          defender->banish(attacker, attacker->name(DESC_PLAIN, true),
                           attacker->get_experience_level());
          return true;
      case NONE:
          // Do nothing
          break;
      }
  
      return false;
  }
  
  void attack::antimagic_affects_defender(int pow)
  {
      obvious_effect =
          enchant_actor_with_flavour(defender, nullptr, BEAM_DRAIN_MAGIC, pow);
  }
  
  /// Whose skill should be used for a pain-weapon effect?
  static actor* _pain_weapon_user(actor* attacker)
  {
      if (attacker->type != MONS_SPECTRAL_WEAPON)
          return attacker;
  
      const mid_t summoner_mid = attacker->as_monster()->summoner;
      if (summoner_mid == MID_NOBODY)
          return attacker;
  
      actor* summoner = actor_by_mid(attacker->as_monster()->summoner);
      if (!summoner || !summoner->alive())
          return attacker;
      return summoner;
  }
  
  void attack::pain_affects_defender()
  {
      actor* user = _pain_weapon_user(attacker);
      if (!one_chance_in(user->skill_rdiv(SK_NECROMANCY) + 1))
      {
          special_damage += resist_adjust_damage(defender, BEAM_NEG,
                                random2(1 + user->skill_rdiv(SK_NECROMANCY)));
  
          if (special_damage && defender_visible)
          {
              special_damage_message =
                  make_stringf("%s %s in agony%s",
                               defender->name(DESC_THE).c_str(),
                               defender->conj_verb("writhe").c_str(),
                             attack_strength_punctuation(special_damage).c_str());
          }
      }
  }
  
  static bool _is_chaos_polyable(const actor &defender)
  {
      if (!defender.can_safely_mutate())
          return false;  // no polymorphing undead
  
      const monster* mon = defender.as_monster();
      if (!mon)
          return true;
  
      return !mons_is_firewood(*mon) && !mons_invuln_will(*mon);
  }
  
  static bool _is_chaos_slowable(const actor &defender)
  {
      const monster* mon = defender.as_monster();
      if (!mon)
          return true;
  
      return !mons_is_firewood(*mon) && !mon->is_stationary();
  }
  
  struct chaos_effect
  {
      string name;
      int chance;
      function<bool(const actor& def)> valid;
      beam_type flavour;
      function<bool(attack &attack)> misc_effect;
  };
  
  static const vector<chaos_effect> chaos_effects = {
      {
          "clone", 1, [](const actor &d) {
              return d.is_monster() && mons_clonable(d.as_monster(), true);
          },
          BEAM_NONE, [](attack &attack) {
              actor &defender = *attack.defender;
              ASSERT(defender.is_monster());
              monster *clone = clone_mons(defender.as_monster(), true);
              if (!clone)
                  return false;
  
              const bool obvious_effect = you.can_see(defender) && you.can_see(*clone);
  
              if (one_chance_in(3))
                  clone->attitude = coinflip() ? ATT_FRIENDLY : ATT_NEUTRAL;
  
              // The player shouldn't get new permanent followers from cloning.
              if (clone->attitude == ATT_FRIENDLY && !clone->is_summoned())
                  clone->mark_summoned(6, true, MON_SUMM_CLONE);
  
              // Monsters being cloned is interesting.
              xom_is_stimulated(clone->friendly() ? 12 : 25);
              return obvious_effect;
          },
      },
      {
          "polymorph", 2, _is_chaos_polyable, BEAM_POLYMORPH,
      },
      {
          "shifter", 1, [](const actor &defender)
          {
              const monster *mon = defender.as_monster();
              return _is_chaos_polyable(defender)
                     && mon && !mon->is_shapeshifter()
                     && defender.holiness() & MH_NATURAL;
          },
          BEAM_NONE, [](attack &attack) {
              monster* mon = attack.defender->as_monster();
              ASSERT(_is_chaos_polyable(*attack.defender));
              ASSERT(mon);
              ASSERT(!mon->is_shapeshifter());
  
              const bool obvious_effect = you.can_see(*attack.defender);
              mon->add_ench(one_chance_in(3) ? ENCH_GLOWING_SHAPESHIFTER
                                             : ENCH_SHAPESHIFTER);
              // Immediately polymorph monster, just to make the effect obvious.
              mon->polymorph();
  
              // Xom loves it if this happens!
              const int friend_factor = mon->friendly() ? 1 : 2;
              const int glow_factor   = mon->has_ench(ENCH_SHAPESHIFTER) ? 1 : 2;
              xom_is_stimulated(64 * friend_factor * glow_factor);
  
              return obvious_effect;
          },
      },
      {
          "rage", 5, [](const actor &defender) {
              return defender.can_go_berserk();
          }, BEAM_NONE, [](attack &attack) {
              attack.defender->go_berserk(false);
              return you.can_see(*attack.defender);
          },
      },
      { "hasting", 10, _is_chaos_slowable, BEAM_HASTE },
      { "mighting", 10, nullptr, BEAM_MIGHT },
      { "agilitying", 10, nullptr, BEAM_AGILITY },
      { "invisible", 10, nullptr, BEAM_INVISIBILITY, },
      { "slowing", 10, _is_chaos_slowable, BEAM_SLOW },
      {
          "paralysis", 5, [](const actor &defender) {
              return !defender.is_monster()
                      || !mons_is_firewood(*defender.as_monster());
          }, BEAM_PARALYSIS,
      },
      {
          "petrify", 5, [](const actor &defender) {
              return _is_chaos_slowable(defender) && !defender.res_petrify();
          }, BEAM_PETRIFY,
      },
  };
  
  void attack::chaos_affects_defender()
  {
      ASSERT(defender);
  
      vector<pair<const chaos_effect&, int>> weights;
      for (const chaos_effect &effect : chaos_effects)
          if (!effect.valid || effect.valid(*defender))
              weights.push_back({effect, effect.chance});
  
      const chaos_effect &effect = *random_choose_weighted(weights);
  
  #ifdef NOTE_DEBUG_CHAOS_EFFECTS
      take_note(Note(NOTE_MESSAGE, 0, 0, "CHAOS effect: " + effect.name), true);
  #endif
  
      if (effect.misc_effect && effect.misc_effect(*this))
          obvious_effect = true;
  
      bolt beam;
      beam.flavour = effect.flavour;
      if (beam.flavour != BEAM_NONE)
      {
          if (defender->is_player() && have_passive(passive_t::no_haste)
              && beam.flavour == BEAM_HASTE)
          {
              simple_god_message(" protects you from inadvertent hurry.");
              obvious_effect = true;
              return;
          }
  
          beam.glyph        = 0;
          beam.range        = 0;
          beam.colour       = BLACK;
          beam.effect_known = false;
          // Wielded brand is always known, but maybe this was from a call
          // to chaos_affect_actor.
          beam.effect_wanton = !fake_chaos_attack;
  
          if (using_weapon() && you.can_see(*attacker))
          {
              beam.name = wep_name(DESC_YOUR);
              beam.item = weapon;
          }
          else
              beam.name = atk_name(DESC_THE);
  
          beam.thrower =
              (attacker->is_player())           ? KILL_YOU
              : attacker->as_monster()->confused_by_you() ? KILL_YOU_CONF
                                                          : KILL_MON;
  
          if (beam.thrower == KILL_YOU || attacker->as_monster()->friendly())
              beam.attitude = ATT_FRIENDLY;
  
          beam.source_id = attacker->mid;
  
          beam.source = defender->pos();
          beam.target = defender->pos();
  
          beam.damage = dice_def(damage_done + special_damage + aux_damage, 1);
  
          beam.ench_power = beam.damage.num;
  
          const bool you_could_see = you.can_see(*defender);
          beam.fire();
  
          if (you_could_see)
              obvious_effect = beam.obvious_effect;
      }
  
      if (!you.can_see(*attacker))
          obvious_effect = false; // XXX: VERY dubious!
  }
  
  struct chaos_attack_type
  {
      attack_flavour flavour;
      brand_type brand;
      int chance;
      function<bool(const actor& def)> valid;
  };
  
  // Chaos melee attacks randomly choose a brand from here, with brands that
  // definitely won't affect the target being invalid. Chaos itself should
  // always be a valid option, triggering a more unpredictable chaos_effect
  // instead of a normal attack brand when selected.
  static const vector<chaos_attack_type> chaos_types = {
      { AF_FIRE,      SPWPN_FLAMING,       10,
        [](const actor &d) { return !d.is_fiery(); } },
      { AF_COLD,      SPWPN_FREEZING,      10,
        [](const actor &d) { return !d.is_icy(); } },
      { AF_ELEC,      SPWPN_ELECTROCUTION, 10,
        nullptr },
      { AF_POISON,    SPWPN_VENOM,         10,
        [](const actor &d) {
            return !(d.holiness() & (MH_UNDEAD | MH_NONLIVING)); } },
      { AF_CHAOTIC,   SPWPN_CHAOS,         10,
        nullptr },
      { AF_DRAIN,  SPWPN_DRAINING,      5,
        [](const actor &d) {
            return bool(d.holiness() & (MH_NATURAL | MH_PLANT)); } },
      { AF_VAMPIRIC,  SPWPN_VAMPIRISM,     5,
        [](const actor &d) {
            return !d.is_summoned()
                   && bool(d.holiness() & (MH_NATURAL | MH_PLANT)); } },
      { AF_HOLY,      SPWPN_HOLY_WRATH,    5,
        [](const actor &d) { return d.holy_wrath_susceptible(); } },
      { AF_ANTIMAGIC, SPWPN_ANTIMAGIC,     5,
        [](const actor &d) { return d.antimagic_susceptible(); } },
      { AF_CONFUSE,   SPWPN_CONFUSE,       2,
        [](const actor &d) { return !d.clarity(); } },
      { AF_DISTORT,   SPWPN_DISTORTION,    2,
        nullptr },
  };
  
  brand_type attack::random_chaos_brand()
  {
      vector<pair<brand_type, int>> weights;
      for (const chaos_attack_type &choice : chaos_types)
          if (!choice.valid || choice.valid(*defender))
              weights.push_back({choice.brand, choice.chance});
  
      ASSERT(!weights.empty());
  
      brand_type brand = *random_choose_weighted(weights);
  
  #ifdef NOTE_DEBUG_CHAOS_BRAND
      string brand_name = "CHAOS brand: ";
      switch (brand)
      {
      case SPWPN_FLAMING:         brand_name += "flaming"; break;
      case SPWPN_FREEZING:        brand_name += "freezing"; break;
      case SPWPN_HOLY_WRATH:      brand_name += "holy wrath"; break;
      case SPWPN_ELECTROCUTION:   brand_name += "electrocution"; break;
      case SPWPN_VENOM:           brand_name += "venom"; break;
      case SPWPN_DRAINING:        brand_name += "draining"; break;
      case SPWPN_DISTORTION:      brand_name += "distortion"; break;
      case SPWPN_VAMPIRISM:       brand_name += "vampirism"; break;
      case SPWPN_ANTIMAGIC:       brand_name += "antimagic"; break;
      case SPWPN_CHAOS:           brand_name += "chaos"; break;
      case SPWPN_CONFUSE:         brand_name += "confusion"; break;
      default:                    brand_name += "BUGGY"; break;
      }
  
      // Pretty much duplicated by the chaos effect note,
      // which will be much more informative.
      if (brand != SPWPN_CHAOS)
          take_note(Note(NOTE_MESSAGE, 0, 0, brand_name), true);
  #endif
      return brand;
  }
  
  attack_flavour attack::random_chaos_attack_flavour()
  {
      vector<pair<attack_flavour, int>> weights;
      for (const chaos_attack_type &choice : chaos_types)
          if (!choice.valid || choice.valid(*defender))
              weights.push_back({choice.flavour, choice.chance});
  
      ASSERT(!weights.empty());
  
      return *random_choose_weighted(weights);
  }
  
  void attack::drain_defender()
  {
      if (defender->is_monster() && coinflip())
          return;
  
      if (!(defender->holiness() & (MH_NATURAL | MH_PLANT)))
          return;
  
      special_damage = resist_adjust_damage(defender, BEAM_NEG,
                                            (1 + random2(damage_done)) / 2);
  
      if (defender->drain(attacker, true, 1 + damage_done))
      {
          if (defender->is_player())
              obvious_effect = true;
          else if (defender_visible)
          {
              special_damage_message =
                  make_stringf(
                      "%s %s %s%s",
                      atk_name(DESC_THE).c_str(),
                      attacker->conj_verb("drain").c_str(),
                      defender_name(true).c_str(),
                      attack_strength_punctuation(special_damage).c_str());
          }
      }
  }
  
  void attack::drain_defender_speed()
  {
      if (needs_message)
      {
          mprf("%s %s %s vigour!",
               atk_name(DESC_THE).c_str(),
               attacker->conj_verb("drain").c_str(),
               def_name(DESC_ITS).c_str());
      }
      defender->slow_down(attacker, 5 + random2(7));
  }
  
  int attack::inflict_damage(int dam, beam_type flavour, bool clean)
  {
      if (flavour == NUM_BEAMS)
          flavour = special_damage_flavour;
      // Auxes temporarily clear damage_brand so we don't need to check
      if (damage_brand == SPWPN_REAPING
          || damage_brand == SPWPN_CHAOS && one_chance_in(100))
      {
          defender->props[REAPING_DAMAGE_KEY].get_int() += dam;
          // With two reapers of different friendliness, the most recent one
          // gets the zombie. Too rare a case to care any more.
          defender->props[REAPER_KEY].get_int() = attacker->mid;
      }
      return defender->hurt(responsible, dam, flavour, kill_type,
                            "", aux_source.c_str(), clean);
  }
  
  /* If debug, return formatted damage done
   *
   */
  string attack::debug_damage_number()
  {
  #ifdef DEBUG_DIAGNOSTICS
      return make_stringf(" for %d", damage_done);
  #else
      return "";
  #endif
  }
  
  /* Returns standard attack punctuation
   *
   * Used in player / monster (both primary and aux) attacks
   */
  string attack_strength_punctuation(int dmg)
  {
      if (dmg < HIT_WEAK)
          return ".";
      else if (dmg < HIT_MED)
          return "!";
      else if (dmg < HIT_STRONG)
          return "!!";
      else
          return string(3 + (int) log2(dmg / HIT_STRONG), '!');
  }
  
  /* Returns evasion adverb
   *
   */
  string attack::evasion_margin_adverb()
  {
      return (ev_margin <= -20) ? " completely" :
             (ev_margin <= -12) ? "" :
             (ev_margin <= -6)  ? " closely"
                                : " barely";
  }
  
  void attack::stab_message()
  {
      defender->props[HELPLESS_KEY] = true;
  
      switch (stab_bonus)
      {
      case 6:     // big melee, monster surrounded/not paying attention
          if (coinflip())
          {
              mprf("You %s %s from a blind spot!",
                    you.has_mutation(MUT_PAWS) ? "pounce on" : "strike",
                    defender->name(DESC_THE).c_str());
          }
          else
          {
              mprf("You catch %s momentarily off-guard.",
                    defender->name(DESC_THE).c_str());
          }
          break;
      case 4:     // confused/fleeing
          if (!one_chance_in(3))
          {
              mprf("You catch %s completely off-guard!",
                    defender->name(DESC_THE).c_str());
          }
          else
          {
              mprf("You %s %s from behind!",
                    you.has_mutation(MUT_PAWS) ? "pounce on" : "strike",
                    defender->name(DESC_THE).c_str());
          }
          break;
      case 2:
      case 1:
          if (you.has_mutation(MUT_PAWS) && coinflip())
          {
              mprf("You pounce on the unaware %s!",
                   defender->name(DESC_PLAIN).c_str());
              break;
          }
          mprf("%s fails to defend %s.",
                defender->name(DESC_THE).c_str(),
                defender->pronoun(PRONOUN_REFLEXIVE).c_str());
          break;
      }
  
      defender->props.erase(HELPLESS_KEY);
  }
  
  /* Returns the attacker's name
   *
   * Helper method to easily access the attacker's name
   */
  string attack::atk_name(description_level_type desc)
  {
      return actor_name(attacker, desc, attacker_visible);
  }
  
  /* Returns the defender's name
   *
   * Helper method to easily access the defender's name
   */
  string attack::def_name(description_level_type desc)
  {
      return actor_name(defender, desc, defender_visible);
  }
  
  /* Returns the attacking weapon's name
   *
   * Sets upthe appropriate descriptive level and obtains the name of a weapon
   * based on if the attacker is a player or non-player (non-players use a
   * plain name and a manually entered pronoun)
   */
  string attack::wep_name(description_level_type desc, iflags_t ignre_flags)
  {
      ASSERT(weapon != nullptr);
  
      if (attacker->is_player())
          return weapon->name(desc, false, false, false, false, ignre_flags);
  
      string name;
      bool possessive = false;
      if (desc == DESC_YOUR)
      {
          desc       = DESC_THE;
          possessive = true;
      }
  
      if (possessive)
          name = apostrophise(atk_name(desc)) + " ";
  
      name += weapon->name(DESC_PLAIN, false, false, false, false, ignre_flags);
  
      return name;
  }
  
  /* TODO: Remove this!
   * Removing it may not really be practical, in retrospect. Its only used
   * below, in calc_elemental_brand_damage, which is called for both frost and
   * flame brands for both players and monsters.
   */
  string attack::defender_name(bool allow_reflexive)
  {
      if (allow_reflexive && attacker == defender)
          return actor_pronoun(attacker, PRONOUN_REFLEXIVE, attacker_visible);
      else
          return def_name(DESC_THE);
  }
  
  int attack::player_stat_modify_damage(int damage)
  {
      // At 10 strength, damage is multiplied by 1.0
      // Each point of strength over 10 increases this by 0.025 (2.5%),
      // strength below 10 reduces the multiplied by the same amount.
      // Minimum multiplier is 0.01 (1%) (reached at -30 str).
      damage *= max(1.0, 75 + 2.5 * you.strength());
      damage /= 100;
  
      return damage;
  }
  
  int attack::player_apply_weapon_skill(int damage)
  {
      if (using_weapon())
      {
          damage *= 2500 + (random2(you.skill(wpn_skill, 100) + 1));
          damage /= 2500;
      }
  
      return damage;
  }
  
  int attack::player_apply_fighting_skill(int damage, bool aux)
  {
      const int base = aux? 40 : 30;
  
      damage *= base * 100 + (random2(you.skill(SK_FIGHTING, 100) + 1));
      damage /= base * 100;
  
      return damage;
  }
  
  int attack::player_apply_misc_modifiers(int damage)
  {
      return damage;
  }
  
  /**
   * Get the damage bonus from a weapon's enchantment.
   */
  int attack::get_weapon_plus()
  {
      if (weapon->base_type == OBJ_STAVES
  #if TAG_MAJOR_VERSION == 34
          || weapon->sub_type == WPN_BLOWGUN
          || weapon->base_type == OBJ_RODS
  #endif
         )
      {
          return 0;
      }
      return weapon->plus;
  }
  
  static int _core_apply_slaying(int damage, int plus)
  {
      // +0 is random2(1) (which is 0)
      if (plus >= 0)
          return damage + random2(1 + plus);
      else
          return damage - random2(1 - plus);
  }
  
  // Slaying and weapon enchantment. Apply this for slaying even if not
  // using a weapon to attack.
  int attack::player_apply_slaying_bonuses(int damage, bool aux)
  {
      int damage_plus = 0;
      if (!aux && using_weapon())
          damage_plus = get_weapon_plus();
  
      damage_plus -= 4 * you.corrosion_amount();
      damage_plus += slaying_bonus(!weapon && wpn_skill == SK_THROWING
                                   || (weapon && is_range_weapon(*weapon)
                                              && using_weapon()));
  
      return _core_apply_slaying(damage, damage_plus);
  }
  
  int attack::player_apply_final_multipliers(int damage)
  {
      // Can't affect much of anything as a shadow.
      if (you.form == transformation::shadow)
          damage = div_rand_round(damage, 2);
  
      return damage;
  }
  
  void attack::player_exercise_combat_skills()
  {
  }
  
  /* Returns attacker base unarmed damage
   *
   * Scales for current mutations and unarmed effects
   * TODO: Complete symmetry for base_unarmed damage
   * between monsters and players.
   */
  int attack::calc_base_unarmed_damage()
  {
      // Should only get here if we're not wielding something that's a weapon.
      // If there's a non-weapon in hand, it has no base damage.
      if (weapon)
          return 0;
  
      if (!attacker->is_player())
          return 0;
  
      int damage = get_form()->get_base_unarmed_damage();
  
      // Claw damage only applies for bare hands.
      if (you.has_usable_claws())
          damage += you.has_claws() * 2;
  
      if (you.form_uses_xl())
          damage += div_rand_round(you.experience_level, 3);
      else
          damage += you.skill_rdiv(wpn_skill);
  
      if (damage < 0)
          damage = 0;
  
      return damage;
  }
  
  int attack::calc_damage()
  {
      if (attacker->is_monster())
      {
          int damage = 0;
          int damage_max = 0;
          if (using_weapon() || wpn_skill == SK_THROWING)
          {
              damage_max = weapon_damage();
              damage += random2(damage_max);
  
              int wpn_damage_plus = 0;
              if (weapon) // can be 0 for throwing projectiles
                  wpn_damage_plus = get_weapon_plus();
  
              const int jewellery = attacker->as_monster()->inv[MSLOT_JEWELLERY];
              if (jewellery != NON_ITEM
                  && env.item[jewellery].is_type(OBJ_JEWELLERY, RING_SLAYING))
              {
                  wpn_damage_plus += env.item[jewellery].plus;
              }
  
              wpn_damage_plus += attacker->scan_artefacts(ARTP_SLAYING);
  
              damage = _core_apply_slaying(damage, wpn_damage_plus);
          }
  
          damage_max += attk_damage;
          damage     += 1 + random2(attk_damage);
  
          damage = apply_damage_modifiers(damage);
  
          set_attack_verb(damage);
          return apply_defender_ac(damage, damage_max);
      }
      else
      {
          int potential_damage, damage;
  
          potential_damage = using_weapon() || wpn_skill == SK_THROWING
              ? weapon_damage() : calc_base_unarmed_damage();
  
          potential_damage = player_stat_modify_damage(potential_damage);
  
          damage = random2(potential_damage+1);
  
          damage = player_apply_weapon_skill(damage);
          damage = player_apply_fighting_skill(damage, false);
          damage = player_apply_misc_modifiers(damage);
          damage = player_apply_slaying_bonuses(damage, false);
          damage = player_stab(damage);
          // A failed stab may have awakened monsters, but that could have
          // caused the defender to cease to exist (spectral weapons with
          // missing summoners; or pacified monsters on a stair). FIXME:
          // The correct thing to do would be either to delay the call to
          // alert_nearby_monsters (currently in player_stab) until later
          // in the attack; or to avoid removing monsters in handle_behaviour.
          if (!defender->alive())
              return 0;
          damage = player_apply_final_multipliers(damage);
          damage = apply_defender_ac(damage);
  
          damage = max(0, damage);
          set_attack_verb(damage);
  
          return damage;
      }
  
      return 0;
  }
  
  int attack::test_hit(int to_land, int ev, bool randomise_ev)
  {
      int margin = AUTOMATIC_HIT;
      if (randomise_ev)
          ev = random2avg(2*ev, 2);
      if (to_land >= AUTOMATIC_HIT)
          return true;
      else if (x_chance_in_y(MIN_HIT_MISS_PERCENTAGE, 100))
          margin = (random2(2) ? 1 : -1) * AUTOMATIC_HIT;
      else
          margin = to_land - ev;
  
  #ifdef DEBUG_DIAGNOSTICS
      dprf(DIAG_COMBAT, "to hit: %d; ev: %d; result: %s (%d)",
           to_hit, ev, (margin >= 0) ? "hit" : "miss", margin);
  #endif
  
      return margin;
  }
  
  int attack::apply_defender_ac(int damage, int damage_max, ac_type ac_rule) const
  {
      ASSERT(defender);
      int after_ac = defender->apply_ac(damage, damage_max, ac_rule);
      dprf(DIAG_COMBAT, "AC: att: %s, def: %s, ac: %d, gdr: %d, dam: %d -> %d",
                   attacker->name(DESC_PLAIN, true).c_str(),
                   defender->name(DESC_PLAIN, true).c_str(),
                   defender->armour_class(),
                   defender->gdr_perc(),
                   damage,
                   after_ac);
  
      return after_ac;
  }
  
  /* Determine whether a block occurred
   *
   * No blocks if defender is incapacitated, would be nice to eventually expand
   * this method to handle partial blocks as well as full blocks (although this
   * would serve as a nerf to shields and - while more realistic - may not be a
   * good mechanic for shields.
   *
   * Returns (block_occurred)
   */
  bool attack::attack_shield_blocked(bool verbose)
  {
      if (defender == attacker)
          return false; // You can't block your own attacks!
  
      if (defender->incapacitated())
          return false;
  
      const int con_block = random2(attacker->shield_bypass_ability(to_hit)
                                    + defender->shield_block_penalty());
      int pro_block = defender->shield_bonus();
  
      if (!attacker->visible_to(defender))
          pro_block /= 3;
  
      dprf(DIAG_COMBAT, "Defender: %s, Pro-block: %d, Con-block: %d",
           def_name(DESC_PLAIN).c_str(), pro_block, con_block);
  
      if (pro_block >= con_block)
      {
          perceived_attack = true;
  
          if (ignores_shield(verbose))
              return false;
  
          if (needs_message && verbose)
          {
              mprf("%s %s %s attack.",
                   defender_name(false).c_str(),
                   defender->conj_verb("block").c_str(),
                   attacker == defender ? "its own"
                                        : atk_name(DESC_ITS).c_str());
          }
  
          defender->shield_block_succeeded();
  
          return true;
      }
  
      return false;
  }
  
  bool attack::apply_poison_damage_brand()
  {
      if (!one_chance_in(4))
      {
          int old_poison;
  
          if (defender->is_player())
              old_poison = you.duration[DUR_POISONING];
          else
          {
              old_poison =
                  (defender->as_monster()->get_ench(ENCH_POISON)).degree;
          }
  
          defender->poison(attacker, 6 + random2(8) + random2(damage_done * 3 / 2));
  
          if (defender->is_player()
                 && old_poison < you.duration[DUR_POISONING]
              || !defender->is_player()
                 && old_poison <
                    (defender->as_monster()->get_ench(ENCH_POISON)).degree)
          {
              return true;
          }
      }
      return false;
  }
  
  bool attack::apply_damage_brand(const char *what)
  {
      bool brand_was_known = false;
      int brand = 0;
      bool ret = false;
  
      if (using_weapon())
          brand_was_known = item_brand_known(*weapon);
  
      special_damage = 0;
      obvious_effect = false;
      brand = damage_brand == SPWPN_CHAOS ? random_chaos_brand() : damage_brand;
  
      if (brand != SPWPN_FLAMING && brand != SPWPN_FREEZING
          && brand != SPWPN_ELECTROCUTION && brand != SPWPN_VAMPIRISM
          && brand != SPWPN_PROTECTION && !defender->alive())
      {
          // Most brands have no extra effects on just killed enemies, and the
          // effect would be often inappropriate.
          return false;
      }
  
      if (!damage_done
          && (brand == SPWPN_FLAMING || brand == SPWPN_FREEZING
              || brand == SPWPN_HOLY_WRATH || brand == SPWPN_ANTIMAGIC
              || brand == SPWPN_VORPAL || brand == SPWPN_VAMPIRISM))
      {
          // These brands require some regular damage to function.
          return false;
      }
  
      switch (brand)
      {
      case SPWPN_PROTECTION:
          if (attacker->is_player())
          {
              const monster* mon = defender->as_monster();
              if (mon && !mons_is_firewood(*mon))
                  refresh_weapon_protection();
          }
          break;
  
      case SPWPN_FLAMING:
          calc_elemental_brand_damage(BEAM_FIRE,
                                      defender->is_icy() ? "melt" : "burn",
                                      what);
          defender->expose_to_element(BEAM_FIRE, 2);
          if (defender->is_player())
              maybe_melt_player_enchantments(BEAM_FIRE, special_damage);
          break;
  
      case SPWPN_FREEZING:
          calc_elemental_brand_damage(BEAM_COLD, "freeze", what);
          defender->expose_to_element(BEAM_COLD, 2);
          break;
  
      case SPWPN_HOLY_WRATH:
          if (defender->holy_wrath_susceptible())
              special_damage = 1 + (random2(damage_done * 15) / 10);
  
          if (special_damage && defender_visible)
          {
              special_damage_message =
                  make_stringf(
                      "%s %s%s",
                      defender_name(false).c_str(),
                      defender->conj_verb("convulse").c_str(),
                      attack_strength_punctuation(special_damage).c_str());
          }
          break;
  
      case SPWPN_ELECTROCUTION:
          if (defender->res_elec() > 0)
              break;
          else if (one_chance_in(3))
          {
              special_damage = 8 + random2(13);
              const string punctuation =
                      attack_strength_punctuation(special_damage);
              special_damage_message =
                  defender->is_player()
                  ? make_stringf("You are electrocuted%s", punctuation.c_str())
                  : make_stringf("Lightning courses through %s%s",
                                 defender->name(DESC_THE).c_str(),
                                 punctuation.c_str());
              special_damage_flavour = BEAM_ELECTRICITY;
              defender->expose_to_element(BEAM_ELECTRICITY, 2);
          }
  
          break;
  
      case SPWPN_VENOM:
          obvious_effect = apply_poison_damage_brand();
          break;
  
      case SPWPN_DRAINING:
          drain_defender();
          break;
  
      case SPWPN_VORPAL:
          special_damage = 1 + random2(damage_done) / 3;
          // Note: Leaving special_damage_message empty because there isn't one.
          break;
  
      case SPWPN_VAMPIRISM:
      {
          if (!weapon
              || damage_done < 1
              || !actor_is_susceptible_to_vampirism(*defender)
              || attacker->stat_hp() == attacker->stat_maxhp()
              || attacker->is_player() && you.duration[DUR_DEATHS_DOOR]
              || x_chance_in_y(2, 5)
                 && !is_unrandom_artefact(*weapon, UNRAND_LEECH))
          {
              break;
          }
  
          int hp_boost = is_unrandom_artefact(*weapon, UNRAND_VAMPIRES_TOOTH)
                         ? damage_done : 1 + random2(damage_done);
          hp_boost = resist_adjust_damage(defender, BEAM_NEG, hp_boost);
  
          if (hp_boost)
          {
              obvious_effect = true;
  
              if (attacker->is_player())
                  canned_msg(MSG_GAIN_HEALTH);
              else if (attacker_visible)
              {
                  if (defender->is_player())
                  {
                      mprf("%s draws strength from your wounds!",
                           attacker->name(DESC_THE).c_str());
                  }
                  else
                  {
                      mprf("%s is healed.",
                           attacker->name(DESC_THE).c_str());
                  }
              }
  
              dprf(DIAG_COMBAT, "Vampiric Healing: damage %d, healed %d",
                   damage_done, hp_boost);
              attacker->heal(hp_boost);
          }
          break;
      }
      case SPWPN_PAIN:
          pain_affects_defender();
          break;
  
      case SPWPN_DISTORTION:
          ret = distortion_affects_defender();
          break;
  
      case SPWPN_CONFUSE:
      {
          // If a monster with a chaos weapon gets this brand, act like
          // AF_CONFUSE.
          if (attacker->is_monster())
          {
              if (one_chance_in(3))
              {
                  defender->confuse(attacker,
                                    1 + random2(3+attacker->get_hit_dice()));
              }
              break;
          }
  
          // Also used for players in fungus form.
          if (attacker->is_player()
              && you.form == transformation::fungus
              && !you.duration[DUR_CONFUSING_TOUCH]
              && defender->is_unbreathing())
          {
              break;
          }
  
          // Declaring these just to pass to the enchant function.
          bolt beam_temp;
          beam_temp.thrower   = attacker->is_player() ? KILL_YOU : KILL_MON;
          beam_temp.flavour   = BEAM_CONFUSION;
          beam_temp.source_id = attacker->mid;
  
          if (attacker->is_player() && damage_brand == SPWPN_CONFUSE
              && you.duration[DUR_CONFUSING_TOUCH])
          {
              beam_temp.ench_power = you.props[CONFUSING_TOUCH_KEY].get_int();
              int margin;
              monster* mon = defender->as_monster();
              if (beam_temp.try_enchant_monster(mon, margin) == MON_AFFECTED)
              {
                  you.duration[DUR_CONFUSING_TOUCH] = 0;
                  obvious_effect = false;
              }
              else if (!ench_flavour_affects_monster(beam_temp.flavour, mon)
                       || mons_invuln_will(*mon))
              {
                  mprf("%s is completely immune to your confusing touch!",
                       mon->name(DESC_THE).c_str());
                  you.duration[DUR_CONFUSING_TOUCH] = 1;
              }
          }
          else if (!x_chance_in_y(melee_confuse_chance(defender->get_hit_dice()),
                                                       100)
                   || defender->as_monster()->clarity())
          {
              beam_temp.apply_enchantment_to_monster(defender->as_monster());
              obvious_effect = beam_temp.obvious_effect;
              break;
          }
  
          break;
      }
  
      case SPWPN_CHAOS:
          chaos_affects_defender();
          break;
  
      case SPWPN_ANTIMAGIC:
          antimagic_affects_defender(damage_done * 8);
          break;
  
      case SPWPN_ACID:
          defender->splash_with_acid(attacker, 3);
          break;
  
  
      default:
          if (using_weapon() && is_unrandom_artefact(*weapon, UNRAND_DAMNATION))
              attacker->god_conduct(DID_EVIL, 2 + random2(3));
          break;
      }
  
      if (damage_brand == SPWPN_CHAOS)
      {
          if (responsible->is_player())
              did_god_conduct(DID_CHAOS, 2 + random2(3), brand_was_known);
      }
  
      if (!obvious_effect)
          obvious_effect = !special_damage_message.empty();
  
      if (needs_message && !special_damage_message.empty())
      {
          mpr(special_damage_message);
  
          special_damage_message.clear();
      }
  
      // Preserve Nessos's brand stacking in a hacky way -- but to be fair, it
      // was always a bit of a hack.
      if (attacker->type == MONS_NESSOS && weapon && is_range_weapon(*weapon))
          apply_poison_damage_brand();
  
      if (special_damage > 0)
          inflict_damage(special_damage, special_damage_flavour);
  
      if (obvious_effect && attacker_visible && using_weapon())
      {
          if (is_artefact(*weapon))
              artefact_learn_prop(*weapon, ARTP_BRAND);
          else
              set_ident_flags(*weapon, ISFLAG_KNOW_TYPE);
      }
  
      return ret;
  }
  
  /* Calculates special damage, prints appropriate combat text
   *
   * Applies a particular damage brand to the current attack, the setup and
   * calculation of base damage and other effects varies based on the type
   * of attack, but the calculation of elemental damage should be consistent.
   */
  void attack::calc_elemental_brand_damage(beam_type flavour,
                                           const char *verb,
                                           const char *what)
  {
      special_damage = resist_adjust_damage(defender, flavour,
                                            random2(damage_done) / 2 + 1);
  
      if (needs_message && special_damage > 0 && verb)
      {
          // XXX: assumes "what" is singular
          special_damage_message = make_stringf(
              "%s %s %s%s",
              what ? what : atk_name(DESC_THE).c_str(),
              what ? conjugate_verb(verb, false).c_str()
                   : attacker->conj_verb(verb).c_str(),
              // Don't allow reflexive if the subject wasn't the attacker.
              defender_name(!what).c_str(),
              attack_strength_punctuation(special_damage).c_str());
      }
  }
  
  int attack::player_stab_weapon_bonus(int damage)
  {
      int stab_skill = you.skill(wpn_skill, 50) + you.skill(SK_STEALTH, 50);
  
      if (player_good_stab())
      {
          // We might be unarmed if we're using the hood of the Assassin.
          const bool extra_good = using_weapon() && weapon->sub_type == WPN_DAGGER;
          int bonus = you.dex() * (stab_skill + 100) / (extra_good ? 500 : 1000);
  
          bonus   = stepdown_value(bonus, 10, 10, 30, 30);
          damage += bonus;
          damage *= 10 + div_rand_round(stab_skill, 100 * stab_bonus);
          damage /= 10;
      }
  
      // There's both a flat and multiplicative component to
      // stab bonus damage.
  
      damage *= 12 + div_rand_round(stab_skill, 100 * stab_bonus);
      damage /= 12;
  
      // The flat component is loosely based on the old stab_bypass bonus.
      // Essentially, it's an extra quarter-point of damage for every
      // point of weapon + stealth skill, divided by stab_bonus - that is,
      // quartered again if the target isn't sleeping, paralysed, or petrified.
      damage += random2(div_rand_round(stab_skill, 200 * stab_bonus));
  
      return damage;
  }
  
  int attack::player_stab(int damage)
  {
      // The stabbing message looks better here:
      if (stab_attempt)
      {
          // Construct reasonable message.
          stab_message();
          practise_stabbing();
      }
      else
      {
          stab_bonus = 0;
          // Ok.. if you didn't backstab, you wake up the neighborhood.
          // I can live with that.
          alert_nearby_monsters();
      }
  
      if (stab_bonus)
      {
          // Let's make sure we have some damage to work with...
          damage = max(1, damage);
  
          damage = player_stab_weapon_bonus(damage);
      }
  
      return damage;
  }
  
  /* Check for stab and prepare combat for stab-values
   *
   * Grant an automatic stab if paralysed or sleeping (with highest damage value)
   * stab_bonus is used as the divisor in damage calculations, so lower values
   * will yield higher damage. Normal stab chance is (stab_skill + dex + 1 / roll)
   * This averages out to about 1/3 chance for a non extended-endgame stabber.
   */
  void attack::player_stab_check()
  {
      // XXX: move into find_stab_type?
      if (you.duration[DUR_CLUMSY] || you.confused())
      {
          stab_attempt = false;
          stab_bonus = 0;
          return;
      }
  
      stab_type st = find_stab_type(&you, *defender);
      // Find stab type is also used for displaying information about monsters,
      // so upgrade the stab type for !stab and the Spriggan's Knife here
      if (using_weapon()
          && is_unrandom_artefact(*weapon, UNRAND_SPRIGGANS_KNIFE)
          && st != STAB_NO_STAB)
      {
          st = STAB_SLEEPING;
      }
      stab_attempt = st != STAB_NO_STAB;
      stab_bonus = stab_bonus_denom(st);
  
      // See if we need to roll against dexterity / stabbing.
      if (stab_attempt && stab_bonus > 1)
      {
          stab_attempt = x_chance_in_y(you.skill_rdiv(wpn_skill, 1, 2)
                                       + you.skill_rdiv(SK_STEALTH, 1, 2)
                                       + you.dex() + 1,
                                       100);
      }
  
      if (stab_attempt)
          count_action(CACT_STAB, st);
  }
  