text: /**
   * @file
   * @brief Tracking permallies granted by Yred and Beogh.
  **/
  
  #include "AppHdr.h"
  
  #include "god-companions.h"
  
  #include <algorithm>
  
  #include "branch.h"
  #include "dgn-overview.h"
  #include "message.h"
  #include "mon-death.h"
  #include "mon-util.h"
  #include "religion.h"
  #include "spl-other.h"
  #include "tag-version.h"
  
  map<mid_t, companion> companion_list;
  
  companion::companion(const monster& m)
      : mons(follower(m)), level(level_id::current()), timestamp(you.elapsed_time)
  {
  }
  
  void init_companions()
  {
      companion_list.clear();
  }
  
  void add_companion(monster* mons)
  {
      ASSERT(mons->alive());
      // Right now this is a special case for Saint Roka, but
      // future orcish uniques should behave in the same way.
      mons->props["no_annotate"] = true;
      remove_unique_annotation(mons);
      companion_list[mons->mid] = companion(*mons);
  }
  
  void remove_companion(monster* mons)
  {
      mons->props["no_annotate"] = false;
      set_unique_annotation(mons);
      companion_list.erase(mons->mid);
  }
  
  void remove_enslaved_soul_companion()
  {
      for (auto &entry : companion_list)
      {
          monster* mons = monster_by_mid(entry.first);
          if (!mons)
              mons = &entry.second.mons.mons;
          if (mons_enslaved_soul(*mons))
          {
              remove_companion(mons);
              return;
          }
      }
  }
  
  void remove_all_companions(god_type god)
  {
      for (auto i = companion_list.begin(); i != companion_list.end();)
      {
          monster* mons = monster_by_mid(i->first);
          if (!mons)
              mons = &i->second.mons.mons;
          if (mons_is_god_gift(*mons, god))
              companion_list.erase(i++);
          else
              ++i;
      }
  }
  
  void move_companion_to(const monster* mons, const level_id lid)
  {
      // If it's taking stairs, that means the player is heading ahead of it,
      // so we shouldn't relocate the monster until it actually arrives
      // (or we can clone things on the other end)
      if (!(mons->flags & MF_TAKING_STAIRS))
      {
          companion_list[mons->mid].level = lid;
          companion_list[mons->mid].mons = follower(*mons);
          companion_list[mons->mid].timestamp = you.elapsed_time;
      }
  }
  
  void update_companions()
  {
      for (auto &entry : companion_list)
      {
          monster* mons = monster_by_mid(entry.first);
          if (mons)
          {
              if (mons->is_divine_companion())
              {
                  ASSERT(mons->alive());
                  entry.second.mons = follower(*mons);
                  entry.second.timestamp = you.elapsed_time;
              }
          }
      }
  }
  
  void populate_offlevel_recall_list(vector<pair<mid_t, int> > &recall_list)
  {
      for (auto &entry : companion_list)
      {
          int mid = entry.first;
          companion &comp = entry.second;
          if (companion_is_elsewhere(mid, true))
          {
              // Recall can't pull monsters out of the Abyss
              if (comp.level.branch == BRANCH_ABYSS)
                  continue;
  
              recall_list.emplace_back(mid, comp.mons.mons.get_experience_level());
          }
      }
  }
  
  /**
   * Attempt to recall an ally from offlevel.
   *
   * @param mid   The ID of the monster to be recalled.
   * @return      Whether the monster was successfully recalled onto the level.
   * Note that the monster may not still be alive or onlevel, due to shafts, etc,
   * but they were here at least briefly!
   */
  bool recall_offlevel_ally(mid_t mid)
  {
      if (!companion_is_elsewhere(mid, true))
          return false;
  
      companion* comp = &companion_list[mid];
      monster* mons = comp->mons.place(true);
      if (!mons)
          return false;
  
      // The monster is now on this level
      remove_monster_from_transit(comp->level, mid);
      comp->level = level_id::current();
      simple_monster_message(*mons, " is recalled.");
  
      // Now that the monster is onlevel, we can safely apply traps to it.
      // old location isn't very meaningful, so use current loc
      mons->apply_location_effects(mons->pos());
      // check if it was killed/shafted by a trap...
      if (!mons->alive())
          return true; // still successfully recalled!
  
      // Catch up time for off-level monsters
      // (We move the player away so that we don't get expiry
      // messages for things that supposed wore off ages ago)
      const coord_def old_pos = you.pos();
      you.moveto(coord_def(0, 0));
  
      int turns = you.elapsed_time - comp->timestamp;
      // Note: these are auts, not turns, thus healing is 10 times as fast as
      // for other monsters, confusion goes away after a single turn, etc.
  
      mons->heal(div_rand_round(turns * mons->off_level_regen_rate(), 100));
  
      if (turns >= 10 && mons->alive())
      {
          // Remove confusion manually (so that the monster
          // doesn't blink after being recalled)
          mons->del_ench(ENCH_CONFUSION, true);
          mons->timeout_enchantments(turns / 10);
      }
      you.moveto(old_pos);
      // Do this after returning the player to the proper position
      // because it uses player position
      recall_orders(mons);
  
      return true;
  }
  
  bool companion_is_elsewhere(mid_t mid, bool must_exist)
  {
      if (companion_list.count(mid))
      {
          return companion_list[mid].level != level_id::current()
                 || (player_in_branch(BRANCH_PANDEMONIUM)
                     && companion_list[mid].level.branch == BRANCH_PANDEMONIUM
                     && !monster_by_mid(mid));
      }
  
      return !must_exist;
  }
  
  void wizard_list_companions()
  {
      if (companion_list.size() == 0)
      {
          mpr("You have no companions.");
          return;
      }
  
      for (auto &entry : companion_list)
      {
          companion &comp = entry.second;
          monster &mon = comp.mons.mons;
          mprf("%s (%d)(%s:%d)", mon.name(DESC_PLAIN, true).c_str(), mon.mid,
               branches[comp.level.branch].abbrevname, comp.level.depth);
      }
  }
  
  /**
   * Returns the mid of the current ancestor granted by Hepliaklqana, if any. If none
   * exists, returns MID_NOBODY.
   *
   * The ancestor is *not* guaranteed to be on-level, even if it exists; check
   * the companion_list before doing anything rash!
   *
   * @return  The mid_t of the player's ancestor, or MID_NOBODY if none exists.
   */
  mid_t hepliaklqana_ancestor()
  {
      for (auto &entry : companion_list)
          if (mons_is_hepliaklqana_ancestor(entry.second.mons.mons.type))
              return entry.first;
      return MID_NOBODY;
  }
  
  /**
   * Returns the a pointer to the current ancestor granted by Hepliaklqana, if
   * any. If none exists, returns null.
   *
   * The ancestor is *not* guaranteed to be on-level, even if it exists; check
   * the companion_list before doing anything rash!
   *
   * @return  The player's ancestor, or nullptr if none exists.
   */
  monster* hepliaklqana_ancestor_mon()
  {
      const mid_t ancestor_mid = hepliaklqana_ancestor();
      if (ancestor_mid == MID_NOBODY)
          return nullptr;
  
      monster* ancestor = monster_by_mid(ancestor_mid);
      if (ancestor)
          return ancestor;
  
      for (auto &entry : companion_list)
          if (mons_is_hepliaklqana_ancestor(entry.second.mons.mons.type))
              return &entry.second.mons.mons;
      // should never reach this...
      return nullptr;
  }
  
  /**
   * @return true if the Hepliaklqana ancestor is at full HP and the player can
   * see this, or if the ancestor is out of sight or does not exist.
   */
  bool ancestor_full_hp()
  {
      if (you.religion == GOD_HEPLIAKLQANA)
      {
          monster* ancestor = monster_by_mid(hepliaklqana_ancestor());
          if (ancestor == nullptr)
              return true;
          return !you.can_see(*ancestor)
              || ancestor->hit_points == ancestor->max_hit_points;
      }
      return true;
  }
  
  #if TAG_MAJOR_VERSION == 34
  // A temporary routine to clean up some references to invalid companions and
  // prevent crashes on load. Should be unnecessary once the cloning bugs that
  // allow the creation of these invalid companions are fully mopped up
  void fixup_bad_companions()
  {
      for (auto i = companion_list.begin(); i != companion_list.end();)
      {
          if (invalid_monster_type(i->second.mons.mons.type))
              companion_list.erase(i++);
          else
              ++i;
      }
  }
  
  bool maybe_bad_priest_monster(monster &mons)
  {
      // prior to e6d7efa92cb0, if a follower got polymorphed to a form that
      // satisfied is_priest, its god got cleared. This resulted in Beogh
      // followers potentially getting cloned on level load, resulting in
      // duplicate mids or a corrupted mid cache depending on ordering. This is
      // now fixed up in tag_read_level_load.
      return mons.alive() && mons.attitude == ATT_FRIENDLY
                          && mons.god == GOD_NAMELESS;
  }
  
  void fixup_bad_priest_monster(monster &mons)
  {
      if (!maybe_bad_priest_monster(mons))
          return;
      mprf(MSGCH_ERROR, "Removing corrupted ex-follower from level: %s.",
                                              mons.full_name(DESC_PLAIN).c_str());
      monster_die(mons, KILL_RESET, -1, true, false);
  }
  #endif
  