text: #include "AppHdr.h"
  
  #include "god-passive.h"
  
  #include <algorithm>
  #include <cmath>
  
  #include "act-iter.h"
  #include "artefact.h"
  #include "art-enum.h"
  #include "branch.h"
  #include "chardump.h"
  #include "cloud.h"
  #include "coordit.h"
  #include "directn.h"
  #include "env.h"
  #include "eq-type-flags.h"
  #include "fight.h"
  #include "files.h"
  #include "fprop.h"
  #include "god-abil.h"
  #include "god-prayer.h"
  #include "invent.h" // in_inventory
  #include "item-name.h"
  #include "item-prop.h"
  #include "item-status-flag-type.h"
  #include "items.h"
  #include "libutil.h"
  #include "map-knowledge.h"
  #include "melee-attack.h"
  #include "message.h"
  #include "mon-cast.h"
  #include "mon-place.h"
  #include "mon-util.h"
  #include "ouch.h"
  #include "output.h"
  #include "player.h"
  #include "player-stats.h"
  #include "religion.h"
  #include "shout.h"
  #include "skills.h"
  #include "spl-clouds.h"
  #include "state.h"
  #include "stringutil.h"
  #include "terrain.h"
  #include "throw.h"
  #include "unwind.h"
  
  // TODO: template out the differences between this and god_power.
  // TODO: use the display method rather than dummy powers in god_powers.
  // TODO: finish using these for implementing passive abilities.
  struct god_passive
  {
      // 1-6 means it unlocks at that many stars of piety;
      // 0 means it is always present when in good standing with the god;
      // -1 means it is present even under penance;
      int rank;
      passive_t pasv;
      /** Message to be given on gain of this passive.
       *
       * If the string begins with an uppercase letter, it is treated as
       * a complete sentence. Otherwise, if it contains the substring " NOW ",
       * the string "You " is prepended. Otherwise, the string "You NOW " is
       * prepended to the message, with the " NOW " then being replaced.
       *
       * The substring "GOD" is replaced with the name of the god.
       * The substring " NOW " is replaced with " now " for messages about
       * gaining the ability, or " " for messages about having the ability.
       *
       * Examples:
       *   "have super powers"
       *     => "You have super powers", "You now have super powers."
       *   "are NOW powerful"
       *     => "You are powerful", "You are now powerful."
       *   "Your power is NOW great"
       *     => "Your power is great", "Your power is now great"
       *   "GOD NOW makes you powerful"
       *     => "Moloch makes you powerful", "Moloch now makes you powerful"
       *   "GOD grants you a boon"
       *     => "Moloch grants you a boon" (in all situations)
       *
       * FIXME: This member is currently unused.
       *
       * @see god_passive::loss.
       */
      const char* gain;
      /** Message to be given on loss of this passive.
       *
       * If empty, use the gain message. This makes sense only if the message
       * contains " NOW ", either explicitly or implicitly through not
       * beginning with a capital letter.
       *
       * The substring "GOD" is replaced with the name of the god.
       * The substring " NOW " is replaced with " no longer ".
       *
       * Examples:
       *   "have super powers"
       *     => "You no longer have super powers"
       *   "are NOW powerful"
       *     => "You are no longer powerful"
       *   "Your power is NOW great"
       *     => "Your power is no longer great"
       *   "GOD NOW makes you powerful"
       *     => "Moloch no longer makes you powerful"
       *   "GOD grants you a boon"
       *     => "Moloch grants you a boon" (probably incorrect)
       *
       * FIXME: This member is currently unused.
       *
       * @see god_passive::gain.
       */
      const char* loss;
  
      god_passive(int rank_, passive_t pasv_, const char* gain_,
                  const char* loss_ = "")
          : rank{rank_}, pasv{pasv_}, gain{gain_}, loss{*loss_ ? loss_ : gain_}
      { }
  
      god_passive(int rank_, const char* gain_, const char* loss_ = "")
          : god_passive(rank_, passive_t::none, gain_, loss_)
      { }
  
      void display(bool gaining, const char* fmt) const
      {
          const char * const str = gaining ? gain : loss;
          if (isupper(str[0]))
              god_speaks(you.religion, str);
          else
              god_speaks(you.religion, make_stringf(fmt, str).c_str());
      }
  };
  
  static const vector<god_passive> god_passives[] =
  {
      // no god
      { },
  
      // Zin
      {
          { -1, passive_t::protect_from_harm,
                "GOD sometimes watches over you",
                "GOD no longer watches over you"
          },
          { -1, passive_t::resist_mutation,
                "GOD can shield you from mutations",
                "GOD NOW shields you from mutations"
          },
          { -1, passive_t::resist_polymorph,
                "GOD can protect you from unnatural transformations",
                "GOD NOW protects you from unnatural transformations",
          },
          { -1, passive_t::resist_hell_effects,
                "GOD can protect you from effects of Hell",
                "GOD NOW protects you from effects of Hell"
          },
          { -1, passive_t::warn_shapeshifter,
                "GOD will NOW warn you about shapeshifters"
          }
      },
  
      // TSO
      {
          { -1, passive_t::protect_from_harm,
                "GOD sometimes watches over you",
                "GOD no longer watches over you"
          },
          { -1, passive_t::abjuration_protection_hd,
                "GOD NOW protects your summons from abjuration" },
          { -1, passive_t::bless_followers_vs_evil,
                "GOD NOW blesses your followers when they kill evil beings" },
          { -1, passive_t::restore_hp_mp_vs_evil,
                "gain health and magic from killing evil beings" },
          { -1, passive_t::no_stabbing,
                "are NOW prevented from stabbing unaware creatures" },
          {  0, passive_t::halo, "are NOW surrounded by divine halo" },
      },
  
      // Kikubaaqudgha
      {
          {  2, passive_t::miscast_protection_necromancy,
                "GOD NOW protects you from necromancy miscasts"
                " and mummy death curses"
          },
          {  4, passive_t::resist_torment,
                "GOD NOW protects you from torment" },
      },
  
      // Yredelemnul
      {
          {  3, passive_t::nightvision, "can NOW see well in the dark" },
      },
  
      // Xom
      { },
  
      // Vehumet
      {
          {  1, passive_t::spells_success,
                "are NOW less likely to miscast destructive spells" },
          {  2, passive_t::mp_on_kill,
                "have a chance to gain magical power from killing" },
          {  4, passive_t::spells_range,
                "can NOW cast destructive spells farther" },
      },
  
      // Okawaru
      {
          // None
      },
  
      // Makhleb
      {
          { -1, passive_t::restore_hp, "gain health from killing" },
      },
  
      // Sif Muna
      {
          {  2, passive_t::miscast_protection,
                "GOD NOW protects you from miscasts" },
      },
  
      // Trog
      {
          { -1, passive_t::abjuration_protection,
                "GOD NOW protects your allies from abjuration"
          },
          {  0, passive_t::extend_berserk,
                "can NOW maintain berserk longer, and are less likely to pass out",
                "can NOW maintain berserk as long, and are more likely to pass out"
          },
      },
  
      // Nemelex
      {
          // None
      },
  
      // Elyvilon
      {
          { -1, passive_t::protect_from_harm,
                "GOD sometimes watches over you",
                "GOD no longer watches over you"
          },
          { -1, passive_t::protect_ally,
                "GOD can protect the life of your allies",
                "GOD NOW protects the life of your allies"
          },
      },
  
      // Lugonu
      {
          { -1, passive_t::safe_distortion,
                "are NOW protected from distortion unwield effects" },
          { -1, passive_t::map_rot_res_abyss,
                "remember the shape of the Abyss better" },
          {  3, passive_t::translocation_success,
                "are NOW less likely to miscast Translocation spells" },
          {  5, passive_t::attract_abyssal_rune,
                "GOD will NOW help you find the Abyssal rune" },
      },
  
      // Beogh
      {
          { -1, passive_t::share_exp, "share experience with your followers" },
          {  3, passive_t::convert_orcs, "inspire orcs to join your side" },
          {  3, passive_t::bless_followers,
                "GOD will bless your followers",
                "GOD will no longer bless your followers"
          },
          {  5, passive_t::water_walk, "walk on water" },
      },
  
      // Jiyva
      {
          { -1, passive_t::neutral_slimes,
                "Slimes and eye monsters are NOW neutral towards you" },
          { -1, passive_t::jelly_eating,
                "GOD NOW allows jellies to devour items" },
          {  0, passive_t::slime_wall_immune,
                "are NOW immune to slime covered walls" },
          {  2, passive_t::resist_corrosion,
                "GOD NOW protects you from acid" },
          {  3, passive_t::jiyva_AC,
                "GOD NOW toughens your body as you become more mutated" },
          {  5, passive_t::spawn_slimes_on_hit,
                "spawn slimes when struck by massive blows" },
          {  6, passive_t::unlock_slime_vaults,
                "GOD NOW grants you access to the hidden treasures"
                " of the Slime Pits"
          },
      },
  
      // Fedhas
      {
          { -1, passive_t::pass_through_plants,
                "can NOW walk through plants" },
          { -1, passive_t::shoot_through_plants,
                "can NOW safely fire through allied plants" },
          {  0, passive_t::friendly_plants,
                "Allied plants are NOW friendly towards you" },
      },
  
      // Cheibriados
      {
          { -1, passive_t::no_haste,
                "are NOW protected from inadvertent hurry" },
          { -1, passive_t::slowed, "move less quickly" },
          {  0, passive_t::slow_orb_run,
                "GOD will NOW aid your escape with the Orb of Zot",
          },
          {  0, passive_t::stat_boost,
                "GOD NOW supports your attributes"
          },
          {  0, passive_t::slow_abyss,
                "GOD will NOW slow the Abyss"
          },
          {  1, passive_t::slow_metabolism, "have a slowed metabolism" },
          {  1, passive_t::long_potions, "potions that augment you last longer" },
          {  1, passive_t::clarity, "are NOW clear of mind" },
      },
  
      // Ashenzari
      {
          { -1, passive_t::want_curses, "prefer cursed items" },
          { -1, passive_t::detect_portals, "sense portals" },
          { -1, passive_t::identify_items, "sense the properties of items" },
          {  0, passive_t::auto_map, "have improved mapping abilities" },
          {  0, passive_t::detect_montier, "sense threats" },
          {  0, passive_t::detect_items, "sense items" },
          {  0, passive_t::search_traps,
                "are NOW better at searching for traps" },
          {  2, passive_t::bondage_skill_boost,
                "get a skill boost from cursed items" },
          {  3, passive_t::sinv, "are NOW clear of vision" },
          {  4, passive_t::clarity, "are NOW clear of mind" },
      },
  
      // Dithmenos
      {
          {  1, passive_t::nightvision, "can NOW see well in the dark" },
          {  1, passive_t::umbra, "are NOW surrounded by an umbra" },
          // TODO: this one should work regardless of penance.
          {  3, passive_t::hit_smoke, "emit smoke when hit" },
      },
  
      // Gozag
      {
          { -1, passive_t::detect_gold, "detect gold" },
          {  0, passive_t::goldify_corpses,
                "GOD NOW turns all corpses to gold" },
          {  0, passive_t::gold_aura, "have a gold aura" },
      },
  
      // Qazlal
      {
          {  0, passive_t::cloud_immunity, "and your divine allies are ADV immune to clouds" },
          {  1, passive_t::storm_shield,
                "generate elemental clouds to protect yourself" },
          {  4, passive_t::upgraded_storm_shield,
                "Your chances to be struck by projectiles are NOW reduced" },
          {  5, passive_t::elemental_adaptation,
                "Elemental attacks NOW leave you somewhat more resistant"
                " to them"
          }
      },
  
      // Ru
      {
          {  1, passive_t::aura_of_power,
                "Your enemies will sometimes fail their attack or even hit themselves",
                "Your enemies will NOW fail their attack or hit themselves"
          },
          {  2, passive_t::upgraded_aura_of_power,
                "Enemies that inflict damage upon you will sometimes receive"
                " a detrimental status effect",
                "Enemies that inflict damage upon you will NOW receive"
                " a detrimental status effect"
          },
      },
  
      // Demigod
      {
          { 1, passive_t::pass_through_plants,
                "can NOW walk through plants" },
      },
  
      // Uskayaw
      {
          { 2, passive_t::wu_jian_lunge, "do at least one heavy attack when moving towards foes." },
          { 2, passive_t::pain_bond, "do at least one heavy attack when moving towards foes." },
      },
  
      // Hepliaklqana
      {
          { -1, passive_t::frail,
                "GOD NOW siphons a part of your essence into your ancestor" },
      },
  
      // Wu Jian
      {
          { 0, passive_t::wu_jian_lunge, "do at least one heavy attack when moving towards foes." },
          { 0, passive_t::wu_jian_whirlwind, "attack enemies by moving around them." },
          { 1, passive_t::wu_jian_wall_jump, "perform airborne attacks by moving against a solid obstacle." },
      },
  };
  COMPILE_CHECK(ARRAYSZ(god_passives) == NUM_GODS);
  
  static bool _god_gives_passive_if(god_type god, passive_t passive,
                                    function<bool(const god_passive&)> condition)
  {
      const auto &pasvec = god_passives[god];
      return any_of(begin(pasvec), end(pasvec),
                    [&] (const god_passive &p) -> bool
                    { return p.pasv == passive && condition(p); });
  }
  
  bool god_gives_passive(god_type god, passive_t passive)
  {
    return _god_gives_passive_if(god, passive,
                                 [] (god_passive /*p*/) { return true; });
  }
  
  bool have_passive(passive_t passive)
  {
      return _god_gives_passive_if(you.religion, passive,
                                   [] (god_passive p)
                                   {
                                       return piety_rank() >= p.rank
                                           && (!player_under_penance()
                                               || p.rank < 0);
                                   });
  }
  
  bool will_have_passive(passive_t passive)
  {
    return _god_gives_passive_if(you.religion, passive,
                                 [] (god_passive/*p*/) { return true; });
  }
  
  // Returns a large number (10) if we will never get this passive.
  int rank_for_passive(passive_t passive)
  {
      const auto &pasvec = god_passives[you.religion];
      const auto found = find_if(begin(pasvec), end(pasvec),
                                [passive] (const god_passive &p) -> bool
                                {
                                    return p.pasv == passive;
                                });
      return found == end(pasvec) ? 10 : found->rank;
  }
  
  int chei_stat_boost(int piety)
  {
      if (!have_passive(passive_t::stat_boost))
          return 0;
      if (piety < piety_breakpoint(0))  // Since you've already begun to slow down.
          return 1;
      if (piety >= piety_breakpoint(5))
          return 15;
      return (piety - 10) / 10;
  }
  
  void ash_init_bondage(player *y)
  {
      y->bondage_level = 0;
      for (int i = ET_WEAPON; i < NUM_ET; ++i)
          y->bondage[i] = 0;
  }
  
  static bool _two_handed()
  {
      const item_def* wpn = you.slot_item(EQ_WEAPON, true);
      if (!wpn)
          return false;
  
      hands_reqd_type wep_type = you.hands_reqd(*wpn, true);
      return wep_type == HANDS_TWO;
  }
  
  void ash_check_bondage(bool msg)
  {
      if (!will_have_passive(passive_t::bondage_skill_boost))
          return;
  
      int cursed[NUM_ET] = {0}, slots[NUM_ET] = {0};
  
      for (int j = EQ_FIRST_EQUIP; j < NUM_EQUIP; j++)
      {
          const equipment_type i = static_cast<equipment_type>(j);
          eq_type s;
          if (i == EQ_WEAPON)
              s = ET_WEAPON;
          else if (i == EQ_SHIELD)
              s = ET_SHIELD;
          else if (i <= EQ_MAX_ARMOUR)
              s = ET_ARMOUR;
          // Missing hands mean fewer rings
          else if (you.species != SP_OCTOPODE && i == EQ_LEFT_RING
                   && you.get_mutation_level(MUT_MISSING_HAND))
          {
              continue;
          }
          // Octopodes don't count these slots:
          else if (you.species == SP_OCTOPODE
                   && ((i == EQ_LEFT_RING || i == EQ_RIGHT_RING)
                       || (i == EQ_RING_EIGHT
                           && you.get_mutation_level(MUT_MISSING_HAND))))
          {
              continue;
          }
          // *Only* octopodes count these slots:
          else if (you.species != SP_OCTOPODE
                   && i >= EQ_RING_ONE && i <= EQ_RING_EIGHT)
          {
              continue;
          }
          // The macabre finger necklace's extra slot does count if equipped.
          else if (!player_equip_unrand(UNRAND_FINGER_AMULET)
                   && i == EQ_RING_AMULET)
          {
              continue;
          }
          else
              s = ET_JEWELS;
  
          // transformed away slots are still considered to be possibly bound
          if (you_can_wear(i))
          {
              slots[s]++;
              if (you.equip[i] != -1)
              {
                  const item_def& item = you.inv[you.equip[i]];
                  if (item.cursed() && (i != EQ_WEAPON || is_weapon(item)))
                  {
                      if (s == ET_WEAPON
                          && (_two_handed()
                              || you.get_mutation_level(MUT_MISSING_HAND)))
                      {
                          cursed[ET_WEAPON] = 3;
                          cursed[ET_SHIELD] = 3;
                      }
                      else
                      {
                          cursed[s]++;
                          if (i == EQ_BODY_ARMOUR && is_unrandom_artefact(item, UNRAND_LEAR))
                              cursed[s] += 3;
                      }
                  }
              }
          }
      }
  
      int8_t new_bondage[NUM_ET];
      int old_level = you.bondage_level;
      for (int s = ET_WEAPON; s < NUM_ET; s++)
      {
          if (slots[s] == 0)
              new_bondage[s] = -1;
          // That's only for 2 handed weapons.
          else if (cursed[s] > slots[s])
              new_bondage[s] = 3;
          else if (cursed[s] == slots[s])
              new_bondage[s] = 2;
          else if (cursed[s] > slots[s] / 2)
              new_bondage[s] = 1;
          else
              new_bondage[s] = 0;
      }
  
      you.bondage_level = 0;
      // kittehs don't obey hoomie rules!
      if (you.species == SP_FELID)
      {
          for (int i = EQ_LEFT_RING; i <= EQ_AMULET; ++i)
              if (you.equip[i] != -1 && you.inv[you.equip[i]].cursed())
                  ++you.bondage_level;
  
          // Allow full bondage when all available slots are cursed.
          if (you.bondage_level == 3)
              ++you.bondage_level;
      }
      else
          for (int i = ET_WEAPON; i < NUM_ET; ++i)
              if (new_bondage[i] > 0)
                  ++you.bondage_level;
  
      int flags = 0;
      if (msg)
      {
          for (int s = ET_WEAPON; s < NUM_ET; s++)
              if (new_bondage[s] != you.bondage[s])
                  flags |= 1 << s;
      }
  
      you.skill_boost.clear();
      for (int s = ET_WEAPON; s < NUM_ET; s++)
      {
          you.bondage[s] = new_bondage[s];
          map<skill_type, int8_t> boosted_skills = ash_get_boosted_skills(eq_type(s));
          for (const auto &entry : boosted_skills)
          {
              you.skill_boost[entry.first] += entry.second;
              if (you.skill_boost[entry.first] > 3)
                  you.skill_boost[entry.first] = 3;
          }
  
      }
  
      if (msg)
      {
          string desc = ash_describe_bondage(flags, you.bondage_level != old_level);
          if (!desc.empty())
              mprf(MSGCH_GOD, "%s", desc.c_str());
      }
  }
  
  string ash_describe_bondage(int flags, bool level)
  {
      string desc;
      if (flags & ETF_WEAPON && flags & ETF_SHIELD
          && you.bondage[ET_WEAPON] != -1)
      {
          if (you.bondage[ET_WEAPON] == you.bondage[ET_SHIELD])
          {
              const string verb = make_stringf("are%s",
                                               you.bondage[ET_WEAPON] ? ""
                                                                      : " not");
              desc = you.hands_act(verb, "bound.\n");
          }
          else
          {
              // FIXME: what if you sacrificed a hand?
              desc = make_stringf("Your %s %s is bound but not your %s %s.\n",
                                  you.bondage[ET_WEAPON] ? "weapon" : "shield",
                                  you.hand_name(false).c_str(),
                                  you.bondage[ET_WEAPON] ? "shield" : "weapon",
                                  you.hand_name(false).c_str());
          }
      }
      else if (flags & ETF_WEAPON && you.bondage[ET_WEAPON] != -1)
      {
          desc = make_stringf("Your weapon %s is %sbound.\n",
                              you.hand_name(false).c_str(),
                              you.bondage[ET_WEAPON] ? "" : "not ");
      }
      else if (flags & ETF_SHIELD && you.bondage[ET_SHIELD] != -1)
      {
          desc = make_stringf("Your shield %s is %sbound.\n",
                              you.hand_name(false).c_str(),
                              you.bondage[ET_SHIELD] ? "" : "not ");
      }
  
      if (flags & ETF_ARMOUR && flags & ETF_JEWELS
          && you.bondage[ET_ARMOUR] == you.bondage[ET_JEWELS]
          && you.bondage[ET_ARMOUR] != -1)
      {
          desc += make_stringf("You are %s bound in armour %s jewellery.\n",
                               you.bondage[ET_ARMOUR] == 0 ? "not" :
                               you.bondage[ET_ARMOUR] == 1 ? "partially"
                                                           : "fully",
                               you.bondage[ET_ARMOUR] == 0 ? "or" : "and");
      }
      else
      {
          if (flags & ETF_ARMOUR && you.bondage[ET_ARMOUR] != -1)
          {
              desc += make_stringf("You are %s bound in armour.\n",
                                   you.bondage[ET_ARMOUR] == 0 ? "not" :
                                   you.bondage[ET_ARMOUR] == 1 ? "partially"
                                                               : "fully");
          }
  
          if (flags & ETF_JEWELS && you.bondage[ET_JEWELS] != -1)
          {
              desc += make_stringf("You are %s bound in jewellery.\n",
                                   you.bondage[ET_JEWELS] == 0 ? "not" :
                                   you.bondage[ET_JEWELS] == 1 ? "partially"
                                                               : "fully");
          }
      }
  
      if (level)
      {
          desc += make_stringf("You are %s bound.",
                               you.bondage_level == 0 ? "not" :
                               you.bondage_level == 1 ? "slightly" :
                               you.bondage_level == 2 ? "moderately" :
                               you.bondage_level == 3 ? "seriously" :
                               you.bondage_level == 4 ? "fully"
                                                      : "buggily");
      }
  
      return trim_string(desc);
  }
  
  static bool _is_slot_cursed(equipment_type eq)
  {
      const item_def *worn = you.slot_item(eq, true);
      if (!worn || !worn->cursed())
          return false;
  
      if (eq == EQ_WEAPON)
          return is_weapon(*worn);
      return true;
  }
  
  bool god_id_item(item_def& item, bool silent)
  {
      if (fully_identified(item))
          return false;
      
      iflags_t old_ided = item.flags & ISFLAG_IDENT_MASK;
      iflags_t ided = 0;
  
      if (have_passive(passive_t::identify_items))
      {
          // Ashenzari (and other gods with both identify_items and want_curses)
          // ties identification of weapon/armour plusses to cursed slots.
          const bool ash = have_passive(passive_t::want_curses);
  
          // Don't identify runes or the orb, since this has no gameplay purpose
          // and might mess up other things.
          if (item.base_type == OBJ_RUNES || item_is_orb(item))
              return false;
  
          ided = ISFLAG_KNOW_CURSE;
  
          if ((item.base_type == OBJ_JEWELLERY || item.base_type == OBJ_STAVES)
              && item_needs_autopickup(item))
          {
              item.props["needs_autopickup"] = true;
          }
  
          if (is_weapon(item) || item.base_type == OBJ_ARMOUR)
              ided |= ISFLAG_KNOW_PROPERTIES | ISFLAG_KNOW_TYPE;
  
          if (item.base_type == OBJ_JEWELLERY)
              ided |= ISFLAG_IDENT_MASK;
  
          if (item.base_type == OBJ_ARMOUR
              && (!ash || _is_slot_cursed(get_armour_slot(item))))
          {
              ided |= ISFLAG_KNOW_PLUSES;
          }
  
          if (is_weapon(item)
              && (!ash || _is_slot_cursed(EQ_WEAPON)))
          {
              ided |= ISFLAG_KNOW_PLUSES;
          }
      }
  
      if (ided & ~old_ided)
      {
          if (ided & ISFLAG_KNOW_TYPE)
              set_ident_type(item, true);
          set_ident_flags(item, ided);
  
          if (item.props.exists("needs_autopickup") && is_useless_item(item))
              item.props.erase("needs_autopickup");
  
          if (&item == you.weapon())
              you.wield_change = true;
  
          if (!silent)
              mprf_nocap("%s", item.name(DESC_INVENTORY_EQUIP).c_str());
  
          seen_item(item);
          if (in_inventory(item))
              auto_assign_item_slot(item);
          return true;
      }
  
      // nothing new
      return false;
  }
  
  static bool is_ash_portal(dungeon_feature_type feat)
  {
      if (feat_is_portal_entrance(feat))
          return true;
      switch (feat)
      {
      case DNGN_ENTER_HELL:
      case DNGN_ENTER_ABYSS: // for completeness
      case DNGN_EXIT_THROUGH_ABYSS:
      case DNGN_EXIT_ABYSS:
      case DNGN_ENTER_PANDEMONIUM:
      case DNGN_EXIT_PANDEMONIUM:
      // DNGN_TRANSIT_PANDEMONIUM is too mundane
          return true;
      default:
          return false;
      }
  }
  
  // Yay for rectangle_iterator and radius_iterator not sharing a base type
  static bool _check_portal(coord_def where)
  {
      const dungeon_feature_type feat = grd(where);
      if (feat != env.map_knowledge(where).feat() && is_ash_portal(feat))
      {
          env.map_knowledge(where).set_feature(feat);
          set_terrain_mapped(where);
  
          if (!testbits(env.pgrid(where), FPROP_SEEN_OR_NOEXP))
          {
              env.pgrid(where) |= FPROP_SEEN_OR_NOEXP;
              if (!you.see_cell(where))
                  return true;
          }
      }
      return false;
  }
  
  int ash_detect_portals(bool all)
  {
      if (!have_passive(passive_t::detect_portals))
          return 0;
  
      int portals_found = 0;
      const int map_radius = LOS_DEFAULT_RANGE + 1;
  
      if (all)
      {
          for (rectangle_iterator ri(0); ri; ++ri)
          {
              if (_check_portal(*ri))
                  portals_found++;
          }
      }
      else
      {
          for (radius_iterator ri(you.pos(), map_radius, C_SQUARE); ri; ++ri)
          {
              if (_check_portal(*ri))
                  portals_found++;
          }
      }
  
      you.seen_portals += portals_found;
      return portals_found;
  }
  
  monster_type ash_monster_tier(const monster *mon)
  {
      return monster_type(MONS_SENSED_TRIVIAL + monster_info(mon).threat);
  }
  
  map<skill_type, int8_t> ash_get_boosted_skills(eq_type type)
  {
      const int bondage = you.bondage[type];
      map<skill_type, int8_t> boost;
      if (bondage <= 0)
          return boost;
  
      // Include melded.
      const item_def* wpn = you.slot_item(EQ_WEAPON, true);
      const item_def* armour = you.slot_item(EQ_BODY_ARMOUR, true);
      const int evp = armour ? -property(*armour, PARM_EVASION) / 10 : 0;
      switch (type)
      {
      case (ET_WEAPON):
          ASSERT(wpn);
  
          // Boost weapon skill. Plain "staff" means an unrand magical staff,
          // boosted later.
          if (wpn->base_type == OBJ_WEAPONS && wpn->sub_type != WPN_STAFF)
          {
              boost[item_attack_skill(*wpn)] = bondage;
          }
  
          // staves with an active evokable ability
          if (is_weapon(*wpn) && item_is_evokable(*wpn, false, false, false, false))
              boost[SK_EVOCATIONS] = 2;
          // other magical staves
          else if (wpn->base_type == OBJ_STAVES)
          {
              boost[SK_STAVES] = 1;
              boost[SK_SPELLCASTING] = 1;
          }
          break;
  
      case (ET_SHIELD):
          if (bondage == 2)
              boost[SK_SHIELDS] = 1;
          break;
  
      // Bonus for bounded armour depends on body armour type.
      case (ET_ARMOUR):
          if (evp < 6)
          {
              boost[SK_STEALTH] = bondage;
              boost[SK_DODGING] = bondage;
          }
          else if (evp < 12)
          {
              boost[SK_DODGING] = bondage;
              boost[SK_ARMOUR] = bondage;
          }
          else
              boost[SK_ARMOUR] = bondage + 1;
          break;
  
      // Boost all spell schools and evoc (to give some appeal to melee).
      case (ET_JEWELS):
          for (skill_type sk = SK_FIRST_MAGIC_SCHOOL; sk <= SK_LAST_MAGIC; ++sk)
              boost[sk] = bondage;
          boost[SK_EVOCATIONS] = bondage;
          break;
  
      default:
          die("Unknown equipment type.");
      }
  
      return boost;
  }
  
  /**
   * Does the player have an ash skill boost for a particular skill?
   */
  bool ash_has_skill_boost(skill_type sk)
  {
      return have_passive(passive_t::bondage_skill_boost)
             && you.skill_boost.count(sk) && you.skill_boost.find(sk)->second;
  }
  
  /**
   * Calculate the ash skill point boost for skill sk.
   *
   * @param scaled_skill the skill level to calculate it for, scaled by 10.
   *
   * @return the skill point bonus to use.
   */
  unsigned int ash_skill_point_boost(skill_type sk, int scaled_skill)
  {
      double apt_factor = species_apt_factor(sk);
      unsigned int skill_points = (you.skill_boost[sk] * 2 + 1) * max(scaled_skill, 1)
              * sqrt(sqrt(apt_factor) * ((double) min(161, (int)you.piety)) / 2) * apt_factor;
  
      return skill_points;
  }
  
  int ash_skill_boost(skill_type sk, int scale)
  {
      // It gives a bonus to skill points. The formula is:
      // factor * (piety_rank + 1) * skill_level
      // low bonus    -> factor = 3
      // medium bonus -> factor = 5
      // high bonus   -> factor = 7
  
      unsigned int skill_points = you.skill_points[sk]
                    + get_crosstrain_points(sk)
                    + ash_skill_point_boost(sk, you.skill(sk, 10, true));
  
      int level = you.skills[sk];
      while (level < MAX_SKILL_LEVEL && skill_points >= skill_exp_needed(level + 1, sk))
          ++level;
  
      level = level * scale + get_skill_progress(sk, level, skill_points, scale);
  
      return min(level, MAX_SKILL_LEVEL * scale);
  }
  
  int gozag_gold_in_los(actor *whom)
  {
      if (!have_passive(passive_t::gold_aura))
          return 0;
  
      int gold_count = 0;
  
      for (radius_iterator ri(whom->pos(), LOS_RADIUS, C_SQUARE, LOS_DEFAULT);
           ri; ++ri)
      {
          for (stack_iterator j(*ri); j; ++j)
          {
              if (j->base_type == OBJ_GOLD)
                  ++gold_count;
          }
      }
  
      return gold_count;
  }
  
  void gozag_detect_level_gold(bool count)
  {
      vector<item_def *> gold_piles;
      vector<coord_def> gold_places;
      int gold = 0;
      for (rectangle_iterator ri(0); ri; ++ri)
      {
          for (stack_iterator j(*ri); j; ++j)
          {
              if (j->base_type == OBJ_GOLD && !(j->flags & ISFLAG_UNOBTAINABLE))
              {
                  gold += j->quantity;
                  gold_piles.push_back(&(*j));
                  gold_places.push_back(*ri);
              }
          }
      }
  
      if (!player_in_branch(BRANCH_ABYSS) && count)
          you.attribute[ATTR_GOLD_GENERATED] += gold;
  
      if (have_passive(passive_t::detect_gold))
      {
          for (unsigned int i = 0; i < gold_places.size(); i++)
          {
              bool detected = false;
              int dummy = gold_piles[i]->index();
              coord_def &pos = gold_places[i];
              unlink_item(dummy);
              move_item_to_grid(&dummy, pos, true);
              if (!env.map_knowledge(pos).item()
                  || env.map_knowledge(pos).item()->base_type != OBJ_GOLD)
              {
                  detected = true;
                  update_item_at(pos, true);
              }
              // the pile can still remain undetected if it is not in
              // you.visible_igrd, for example if it is under deep water and the
              // player will not be able to see it.
              if (detected && env.map_knowledge(pos).item())
              {
                  env.map_knowledge(pos).flags |= MAP_DETECTED_ITEM;
  #ifdef USE_TILE
                  // force an update for gold generated during Abyss shifts
                  tiles.update_minimap(pos);
  #endif
              }
          }
      }
  }
  
  int qazlal_sh_boost(int piety)
  {
      if (!have_passive(passive_t::storm_shield))
          return 0;
  
      return min(piety, piety_breakpoint(5)) / 10;
  }
  
  // Not actually passive, but placing it here so that it can be easily compared
  // with Qazlal's boost.
  int tso_sh_boost()
  {
      if (!you.duration[DUR_DIVINE_SHIELD])
          return 0;
  
      return you.attribute[ATTR_DIVINE_SHIELD];
  }
  
  void qazlal_storm_clouds()
  {
      if (!have_passive(passive_t::storm_shield))
          return;
  
      // You are a *storm*. You are pretty loud!
      noisy(min((int)you.piety, piety_breakpoint(5)) / 10, you.pos());
  
      const int radius = you.piety >= piety_breakpoint(3) ? 2 : 1;
  
      vector<coord_def> candidates;
      for (radius_iterator ri(you.pos(), radius, C_SQUARE, LOS_SOLID, true);
           ri; ++ri)
      {
          int count = 0;
          if (cell_is_solid(*ri) || cloud_at(*ri))
              continue;
  
          // Don't create clouds over firewood
          const monster * mon = monster_at(*ri);
          if (mon != nullptr && mons_is_firewood(*mon))
              continue;
  
          // No clouds in corridors.
          for (adjacent_iterator ai(*ri); ai; ++ai)
              if (!cell_is_solid(*ai))
                  count++;
  
          if (count >= 5)
              candidates.push_back(*ri);
      }
      const int count =
          div_rand_round(min((int)you.piety, piety_breakpoint(5))
                         * candidates.size() * you.time_taken,
                         piety_breakpoint(5) * 7 * BASELINE_DELAY);
      if (count < 0)
          return;
      shuffle_array(candidates);
      int placed = 0;
      for (unsigned int i = 0; placed < count && i < candidates.size(); i++)
      {
          bool water = false;
          for (adjacent_iterator ai(candidates[i]); ai; ++ai)
          {
              if (feat_is_watery(grd(*ai)))
                  water = true;
          }
  
          // No flame clouds over water to avoid steam generation.
          cloud_type ctype;
          do
          {
              ctype = random_choose(CLOUD_FIRE, CLOUD_COLD, CLOUD_STORM,
                                    CLOUD_DUST);
          } while (water && ctype == CLOUD_FIRE);
  
          place_cloud(ctype, candidates[i], random_range(3, 5), &you);
          placed++;
      }
  }
  
  /**
   * Handle Qazlal's elemental adaptation.
   * This should be called (exactly once) for physical, fire, cold, and electrical damage.
   * Right now, it is called only from expose_player_to_element. This may merit refactoring.
   *
   * @param flavour the beam type.
   * @param strength The adaptations will trigger strength in (11 - piety_rank()) times. In practice, this is mostly called with a value of 2.
   */
  void qazlal_element_adapt(beam_type flavour, int strength)
  {
      if (strength <= 0
          || !have_passive(passive_t::elemental_adaptation)
          || !x_chance_in_y(strength, 11 - piety_rank()))
      {
          return;
      }
  
      beam_type what = BEAM_NONE;
      duration_type dur = NUM_DURATIONS;
      string descript = "";
      switch (flavour)
      {
          case BEAM_FIRE:
          case BEAM_LAVA:
          case BEAM_STICKY_FLAME:
          case BEAM_STEAM:
              what = BEAM_FIRE;
              dur = DUR_QAZLAL_FIRE_RES;
              descript = "fire";
              break;
          case BEAM_COLD:
          case BEAM_ICE:
              what = BEAM_COLD;
              dur = DUR_QAZLAL_COLD_RES;
              descript = "cold";
              break;
          case BEAM_ELECTRICITY:
              what = BEAM_ELECTRICITY;
              dur = DUR_QAZLAL_ELEC_RES;
              descript = "electricity";
              break;
          case BEAM_MMISSILE: // for LCS, iron shot
          case BEAM_MISSILE:
          case BEAM_FRAG:
              what = BEAM_MISSILE;
              dur = DUR_QAZLAL_AC;
              descript = "physical attacks";
              break;
          default:
              return;
      }
  
      if (what != BEAM_FIRE && you.duration[DUR_QAZLAL_FIRE_RES])
      {
          mprf(MSGCH_DURATION, "Your resistance to fire fades away.");
          you.duration[DUR_QAZLAL_FIRE_RES] = 0;
      }
  
      if (what != BEAM_COLD && you.duration[DUR_QAZLAL_COLD_RES])
      {
          mprf(MSGCH_DURATION, "Your resistance to cold fades away.");
          you.duration[DUR_QAZLAL_COLD_RES] = 0;
      }
  
      if (what != BEAM_ELECTRICITY && you.duration[DUR_QAZLAL_ELEC_RES])
      {
          mprf(MSGCH_DURATION, "Your resistance to electricity fades away.");
          you.duration[DUR_QAZLAL_ELEC_RES] = 0;
      }
  
      if (what != BEAM_MISSILE && you.duration[DUR_QAZLAL_AC])
      {
          mprf(MSGCH_DURATION, "Your resistance to physical damage fades away.");
          you.duration[DUR_QAZLAL_AC] = 0;
          you.redraw_armour_class = true;
      }
  
      mprf(MSGCH_GOD, "You feel %sprotected from %s.",
           you.duration[dur] > 0 ? "more " : "", descript.c_str());
  
      // was scaled by 10 * strength. But the strength parameter is used so inconsistently that
      // it seems like a constant would be better, based on the typical value of 2.
      you.increase_duration(dur, 20, 80);
  
      if (what == BEAM_MISSILE)
          you.redraw_armour_class = true;
  }
  
  /**
   * Determine whether a Ru worshipper will attempt to interfere with an attack
   * against the player.
   *
   * @return bool Whether or not whether the worshipper will attempt to interfere.
   */
  bool does_ru_wanna_redirect(monster* mon)
  {
      return have_passive(passive_t::aura_of_power)
              && !mon->friendly()
              && you.see_cell_no_trans(mon->pos())
              && !mons_is_firewood(*mon)
              && !mon->submerged()
              && !mons_is_projectile(mon->type);
  }
  
  /**
   * Determine which, if any, action Ru takes on a possible attack.
   *
   * @return ru_interference
   */
  ru_interference get_ru_attack_interference_level()
  {
      int r = random2(100);
      int chance = div_rand_round(you.piety, 16);
  
      // 10% chance of stopping any attack at max piety
      if (r < chance)
          return DO_BLOCK_ATTACK;
  
      // 5% chance of redirect at max piety
      else if (r < chance + div_rand_round(chance, 2))
          return DO_REDIRECT_ATTACK;
  
      else
          return DO_NOTHING;
  }
  
  monster* shadow_monster(bool equip)
  {
      if (monster_at(you.pos()))
          return nullptr;
  
      int wpn_index  = NON_ITEM;
  
      // Do a basic clone of the weapon.
      item_def* wpn = you.weapon();
      if (equip
          && wpn
          && is_weapon(*wpn))
      {
          wpn_index = get_mitm_slot(10);
          if (wpn_index == NON_ITEM)
              return nullptr;
          item_def& new_item = mitm[wpn_index];
          if (wpn->base_type == OBJ_STAVES)
          {
              new_item.base_type = OBJ_WEAPONS;
              new_item.sub_type  = WPN_STAFF;
          }
          else
          {
              new_item.base_type = wpn->base_type;
              new_item.sub_type  = wpn->sub_type;
          }
          new_item.quantity = 1;
          new_item.rnd = 1;
          new_item.flags   |= ISFLAG_SUMMONED;
      }
  
      monster* mon = get_free_monster();
      if (!mon)
      {
          if (wpn_index)
              destroy_item(wpn_index);
          return nullptr;
      }
  
      mon->type       = MONS_PLAYER_SHADOW;
      mon->behaviour  = BEH_SEEK;
      mon->attitude   = ATT_FRIENDLY;
      mon->flags      = MF_NO_REWARD | MF_JUST_SUMMONED | MF_SEEN
                      | MF_WAS_IN_VIEW | MF_HARD_RESET;
      mon->hit_points = you.hp;
      mon->set_hit_dice(min(27, max(1,
                                    you.skill_rdiv(wpn_index != NON_ITEM
                                                   ? item_attack_skill(mitm[wpn_index])
                                                   : SK_UNARMED_COMBAT, 10, 20)
                                    + you.skill_rdiv(SK_FIGHTING, 10, 20))));
      mon->set_position(you.pos());
      mon->mid        = MID_PLAYER;
      mon->inv[MSLOT_WEAPON]  = wpn_index;
      mon->inv[MSLOT_MISSILE] = NON_ITEM;
  
      mgrd(you.pos()) = mon->mindex();
  
      return mon;
  }
  
  void shadow_monster_reset(monster *mon)
  {
      if (mon->inv[MSLOT_WEAPON] != NON_ITEM)
          destroy_item(mon->inv[MSLOT_WEAPON]);
      // in case the shadow unwields for some reason, e.g. you clumsily bash with
      // a ranged weapon:
      if (mon->inv[MSLOT_ALT_WEAPON] != NON_ITEM)
          destroy_item(mon->inv[MSLOT_ALT_WEAPON]);
      if (mon->inv[MSLOT_MISSILE] != NON_ITEM)
          destroy_item(mon->inv[MSLOT_MISSILE]);
  
      mon->reset();
  }
  
  static void _wu_jian_trigger_serpents_lash(const coord_def& old_pos,
                                             bool wall_jump)
  {
      if (you.attribute[ATTR_SERPENTS_LASH] == 0)
         return;
  
      if (wall_jump && you.attribute[ATTR_SERPENTS_LASH] == 1)
      {
          // No turn manipulation, since we are only refunding half a wall jump's
          // time (the walk speed modifier for this special case is already
          // factored in main.cc)
          you.attribute[ATTR_SERPENTS_LASH] = 0;
      }
      else
      {
          you.turn_is_over = false;
          you.elapsed_time_at_last_input = you.elapsed_time;
          you.attribute[ATTR_SERPENTS_LASH] -= wall_jump ? 2 : 1;
          you.redraw_status_lights = true;
          update_turn_count();
      }
  
      if (you.attribute[ATTR_SERPENTS_LASH] == 0)
      {
          int mv = player_movement_speed();
          you.increase_duration(DUR_EXHAUSTED, div_rand_round(mv*7, 10));
          drain_player(div_rand_round(mv*400, you.piety), false, true);
          mpr("Your supernatural speed expires.");
      }
  
      if (!cell_is_solid(old_pos))
          check_place_cloud(CLOUD_DUST, old_pos, 2 + random2(3) , &you, 1, -1);
  }
  
  void wu_jian_heaven_tick()
  {
      if (you.attribute[ATTR_HEAVENLY_STORM] == 0)
          return;
  
      if (you.attribute[ATTR_HEAVENLY_STORM] <= 2)
          you.attribute[ATTR_HEAVENLY_STORM] -= 1;
      else you.attribute[ATTR_HEAVENLY_STORM] -= 2;
  
      for (radius_iterator ai(you.pos(), 2, C_SQUARE, LOS_SOLID); ai; ++ai)
      {
          if (!cell_is_solid(*ai))
              place_cloud(CLOUD_GOLD_DUST, *ai, 5 + random2(5), &you);
      }
  
      noisy(15, you.pos());
  
      if (you.attribute[ATTR_HEAVENLY_STORM] == 0)
          end_heavenly_storm();
      else
          you.duration[DUR_HEAVENLY_STORM] = WU_JIAN_HEAVEN_TICK_TIME;
  }
  
  void end_heavenly_storm()
  {
      you.attribute[ATTR_HEAVENLY_STORM] = 0;
      mprf(MSGCH_GOD, "The heavenly storm settles.");
  }
  
  bool wu_jian_has_momentum(wu_jian_attack_type attack_type)
  {
      return you.attribute[ATTR_SERPENTS_LASH]
             && attack_type != WU_JIAN_ATTACK_NONE
             && attack_type != WU_JIAN_ATTACK_TRIGGERED_AUX;
  }
  
  static bool _can_attack_martial(const monster* mons)
  {
      return !(mons->wont_attack()
               || mons_is_firewood(*mons)
               || mons_is_projectile(mons->type)
               || !you.can_see(*mons));
  }
  
  // A mismatch between attack speed and move speed may cause any particular
  // martial attack to be doubled, tripled, or not happen at all. Given enough
  // time moving, you would have made the same amount of attacks as tabbing.
  static int _wu_jian_number_of_attacks(bool wall_jump)
  {
      // Under the effect of serpent's lash, move delay is normalized to
      // 10 aut for every character, to avoid punishing fast races.
      const int move_delay = you.attribute[ATTR_SERPENTS_LASH]
                             ? 100
                             : player_movement_speed() * player_speed();
  
      int attack_delay;
  
      {
          // attack_delay() is dependent on you.time_taken, which won't be set
          // appropriately during a movement turn. This temporarily resets
          // you.time_taken to the initial value (see `_prep_input`) used for
          // basic, simple, melee attacks.
          // TODO: can `attack_delay` be changed to not depend on you.time_taken?
          unwind_var<int> reset_speed(you.time_taken, player_speed());
          attack_delay = you.attack_delay().roll();
      }
  
      return div_rand_round(wall_jump ? 2 * move_delay : move_delay,
                            attack_delay * BASELINE_DELAY);
  }
  
  static bool _wu_jian_lunge(const coord_def& old_pos)
  {
      coord_def lunge_direction = (you.pos() - old_pos).sgn();
      coord_def potential_target = you.pos() + lunge_direction;
      monster* mons = monster_at(potential_target);
  
      if (!mons || !_can_attack_martial(mons) || !mons->alive())
          return false;
  
      if (you.attribute[ATTR_HEAVENLY_STORM] > 0)
          you.attribute[ATTR_HEAVENLY_STORM] += 2;
  
      you.apply_berserk_penalty = false;
  
      int number_of_attacks = _wu_jian_number_of_attacks(false);
      number_of_attacks = max(1, number_of_attacks);
  
      mprf("You lunge%s at %s%s.",
           wu_jian_has_momentum(WU_JIAN_ATTACK_LUNGE) ?
               " with incredible momentum" : "",
           mons->name(DESC_THE).c_str(),
           number_of_attacks > 1 ? ", in a flurry of attacks" : "");
  
      count_action(CACT_INVOKE, ABIL_WU_JIAN_LUNGE);
  
      for (int i = 0; i < number_of_attacks; i++)
      {
          if (!mons->alive())
              break;
          melee_attack lunge(&you, mons);
          lunge.wu_jian_attack = WU_JIAN_ATTACK_LUNGE;
          lunge.attack();
      }
  
      return true;
  }
  
  // Monsters adjacent to the given pos that are valid targets for whirlwind.
  static vector<monster*> _get_whirlwind_targets(coord_def pos)
  {
      vector<monster*> targets;
      for (adjacent_iterator ai(pos, true); ai; ++ai)
          if (monster_at(*ai) && _can_attack_martial(monster_at(*ai)))
              targets.push_back(monster_at(*ai));
      sort(targets.begin(), targets.end());
      return targets;
  }
  
  static bool _wu_jian_whirlwind(const coord_def& old_pos)
  {
      bool did_at_least_one_attack = false;
  
      const vector<monster*> targets = _get_whirlwind_targets(you.pos());
      if (targets.empty())
          return did_at_least_one_attack;
  
      const vector<monster*> old_targets = _get_whirlwind_targets(old_pos);
      vector<monster*> common_targets;
      set_intersection(targets.begin(), targets.end(),
                       old_targets.begin(), old_targets.end(),
                       back_inserter(common_targets));
  
      for (auto mons : common_targets)
      {
          if (!mons->alive())
              continue;
  
          if (you.attribute[ATTR_HEAVENLY_STORM] > 0)
              you.attribute[ATTR_HEAVENLY_STORM] += 2;
  
          // Pin has a longer duration than one player turn, but gets cleared
          // before its duration expires by wu_jian_end_of_turn_effects. This is
          // necessary to make sure it works well with Wall Jump's longer aut
          // count.
          if(wu_jian_has_momentum(WU_JIAN_ATTACK_WHIRLWIND))
          {
              mons->del_ench(ENCH_WHIRLWIND_PINNED);
              mons->add_ench(mon_enchant(ENCH_WHIRLWIND_PINNED, 2, nullptr,
                                         BASELINE_DELAY * 5));
          }
  
          you.apply_berserk_penalty = false;
  
          const int number_of_attacks = _wu_jian_number_of_attacks(false);
          if (number_of_attacks == 0)
          {
              mprf("You spin to attack %s, but your attack speed is too slow for "
                   "a blow to land.", mons->name(DESC_THE).c_str());
              continue;
          }
          else
          {
              mprf("You spin and attack %s%s%s.",
                   mons->name(DESC_THE).c_str(),
                   number_of_attacks > 1 ? " repeatedly" : "",
                   wu_jian_has_momentum(WU_JIAN_ATTACK_WHIRLWIND) ?
                       ", with incredible momentum" : "");
          }
  
          count_action(CACT_INVOKE, ABIL_WU_JIAN_WHIRLWIND);
  
          for (int i = 0; i < number_of_attacks; i++)
          {
              if (!mons->alive())
                  break;
              melee_attack whirlwind(&you, mons);
              whirlwind.wu_jian_attack = WU_JIAN_ATTACK_WHIRLWIND;
              whirlwind.wu_jian_number_of_targets = common_targets.size();
              whirlwind.attack();
              if (!did_at_least_one_attack)
                did_at_least_one_attack = true;
          }
      }
  
      return did_at_least_one_attack;
  }
  
  static bool _wu_jian_trigger_martial_arts(const coord_def& old_pos)
  {
      bool did_wu_jian_attacks = false;
  
      if (you.pos() == old_pos || you.duration[DUR_CONF])
          return did_wu_jian_attacks;
  
      if (have_passive(passive_t::wu_jian_lunge))
          did_wu_jian_attacks = _wu_jian_lunge(old_pos);
  
      if (have_passive(passive_t::wu_jian_whirlwind))
          did_wu_jian_attacks |= _wu_jian_whirlwind(old_pos);
  
      return did_wu_jian_attacks;
  }
  
  void wu_jian_wall_jump_effects(const coord_def& old_pos)
  {
      vector<monster*> targets;
      int target_count = 0;
      for (adjacent_iterator ai(you.pos(), true); ai; ++ai)
      {
          monster* target = monster_at(*ai);
          if (target && _can_attack_martial(target) && target->alive())
          {
              targets.push_back(target);
              target_count++;
          }
  
          if (!cell_is_solid(*ai))
              check_place_cloud(CLOUD_DUST, *ai, 1 + random2(3) , &you, 0, -1);
      }
      
      if (target_count == 0 && you.duration[DUR_AGILITY] <= 0)
      {
          you.increase_duration(DUR_AGILITY, 3, 100);
          notify_stat_change(STAT_DEX, 5, true);
      }
  
      for (auto target : targets)
      {
          if (!target->alive())
              continue;
  
          if (you.attribute[ATTR_HEAVENLY_STORM] > 0)
              you.attribute[ATTR_HEAVENLY_STORM] += 2;
  
          you.apply_berserk_penalty = false;
  
          // Twice the attacks as Wall Jump spends twice the time
          const int number_of_attacks = _wu_jian_number_of_attacks(true);
          if (number_of_attacks == 0)
          {
              mprf("You attack %s from above, but your attack speed is too slow"
                   " for a blow to land.", target->name(DESC_THE).c_str());
              continue;
          }
          else
          {
              mprf("You %sattack %s from above%s.",
                   number_of_attacks > 1 ? "repeatedly " : "",
                   target->name(DESC_THE).c_str(),
                   wu_jian_has_momentum(WU_JIAN_ATTACK_WALL_JUMP) ?
                       ", with incredible momentum" : "");
          }
  
          for (int i = 0; i < number_of_attacks; i++)
          {
              if (!target->alive())
                  break;
  
              melee_attack aerial(&you, target);
              aerial.wu_jian_attack = WU_JIAN_ATTACK_WALL_JUMP;
              aerial.wu_jian_number_of_targets = targets.size();
              aerial.attack();
          }
      }
  }
  
  void wu_jian_end_of_turn_effects()
  {
      // This guarantees that the whirlwind pin status is capped to one turn of
      // monster movement.
      for (monster_iterator mi; mi; ++mi)
          if (mi->has_ench(ENCH_WHIRLWIND_PINNED)
              && !you.attribute[ATTR_SERPENTS_LASH])
          {
              mi->lose_ench_levels(mi->get_ench(ENCH_WHIRLWIND_PINNED), 1, true);
          }
  
      you.attribute[ATTR_WALL_JUMP_READY] = 0;
  }
  
  bool wu_jian_post_move_effects(bool did_wall_jump,
                                 const coord_def& initial_position)
  {
      bool did_wu_jian_attacks = false;
  
      if (!did_wall_jump)
          did_wu_jian_attacks = _wu_jian_trigger_martial_arts(initial_position);
  
      if (you.turn_is_over)
          _wu_jian_trigger_serpents_lash(initial_position, did_wall_jump);
  
      return did_wu_jian_attacks;
  }
  
  /**
   * check if the monster in this cell exists and is a valid target for Uskayaw
   */
  static int _check_for_uskayaw_targets(coord_def where)
  {
      if (!cell_has_valid_target(where))
          return 0;
      monster* mons = monster_at(where);
      ASSERT(mons);
  
      if (mons_is_firewood(*mons))
          return 0;
  
      return 1;
  }
  
  /**
   * Paralyze the monster in this cell, assuming one exists.
   *
   * Duration increases with invocations and experience level, and decreases
   * with target HD. The duration is pretty low, maxing out at 40 AUT.
   */
  static int _prepare_audience(coord_def where)
  {
      if (!_check_for_uskayaw_targets(where))
          return 0;
  
      monster* mons = monster_at(where);
  
      int power =  max(1, random2(1 + you.skill(SK_INVOCATIONS, 2))
                   + you.experience_level - mons->get_hit_dice());
      int duration = min(max(10, 5 + power), 40);
      mons->add_ench(mon_enchant(ENCH_PARALYSIS, 1, &you, duration));
  
      return 1;
  }
  
  /**
   * On hitting *** piety, all the monsters are paralysed by their appreciation
   * for your dance.
   */
  void uskayaw_prepares_audience()
  {
      int count = apply_area_visible(_check_for_uskayaw_targets, you.pos());
      if (count > 0)
      {
          simple_god_message(" prepares the audience for your solo!");
          apply_area_visible(_prepare_audience, you.pos());
  
          // Increment a delay timer to prevent players from spamming this ability
          // via piety loss and gain. Timer is in AUT.
          you.props[USKAYAW_AUDIENCE_TIMER] = 300 + random2(201);
      }
      else // Reset the timer because we didn't actually execute.
          you.props[USKAYAW_AUDIENCE_TIMER] = 0;
  }
  