text: /**
   * @file
   * @brief Player quiver functionality
  **/
  
  #include "AppHdr.h"
  
  #include "quiver.h"
  
  #include <algorithm>
  
  #include "ability.h"
  #include "artefact.h"
  #include "art-enum.h"
  #include "describe.h" // describe_to_hit
  #include "directn.h"
  #include "env.h"
  #include "evoke.h"
  #include "fight.h"
  #include "invent.h"
  #include "item-prop.h"
  #include "item-use.h"
  #include "items.h"
  #include "macro.h"
  #include "message.h"
  #include "movement.h"
  #include "options.h"
  #include "player.h"
  #include "prompt.h"
  #include "religion.h"
  #include "sound.h"
  #include "spl-damage.h"
  #include "spl-transloc.h"
  #include "stringutil.h"
  #include "tags.h"
  #include "target.h"
  #include "terrain.h"
  #include "tilepick.h"
  #include "throw.h"
  #include "transform.h"
  #include "traps.h"
  #include "rltiles/tiledef-icons.h"
  
  static int _get_pack_slot(const item_def&);
  static bool _item_matches(const item_def &item, fire_type types,
                            const item_def* launcher, bool manual);
  static bool _items_similar(const item_def& a, const item_def& b,
                             bool force = true);
  
  // TODO: how should newquivers integrate with the local tiles UI?
  
  static vector<string> _desc_hit_chance(const monster_info &mi)
  {
      ostringstream result;
      describe_to_hit(mi, result);
      string str = result.str();
      if (str.empty())
          return vector<string>{};
      return vector<string>{str};
  }
  
  namespace quiver
  {
      // Returns the type of ammo used by the player's equipped weapon,
      // or AMMO_THROW if it's not a launcher.
      static launcher _get_weapon_ammo_type(const item_def* weapon)
      {
          if (weapon == nullptr)
              return AMMO_THROW;
          if (weapon->base_type != OBJ_WEAPONS)
              return AMMO_THROW;
  
          switch (weapon->sub_type)
          {
              case WPN_HUNTING_SLING:
              case WPN_FUSTIBALUS:
                  return AMMO_SLING;
              case WPN_SHORTBOW:
              case WPN_LONGBOW:
                  return AMMO_BOW;
              case WPN_HAND_CROSSBOW:
              case WPN_ARBALEST:
              case WPN_TRIPLE_CROSSBOW:
                  return AMMO_CROSSBOW;
              default:
                  return AMMO_THROW;
          }
      }
  
      static bool _quiver_inscription_ok(int slot)
      {
          if (slot < 0 || slot >= ENDOFPACK || !you.inv[slot].defined())
              return true;
          return !strstr(you.inv[slot].inscription.c_str(), "!Q");
      }
  
      static maybe_bool _fireorder_inscription_ok(int slot, bool cycling = false)
      {
          if (slot < 0 || slot >= ENDOFPACK || !you.inv[slot].defined())
              return MB_MAYBE;
          if (strstr(you.inv[slot].inscription.c_str(), cycling ? "=F" : "=f")
              || !_quiver_inscription_ok(slot))
          {
              return MB_FALSE;
          }
          if (strstr(you.inv[slot].inscription.c_str(), cycling ? "+F" : "+f"))
              return MB_TRUE;
          return MB_MAYBE;
      }
  
      void action::set_target(const dist &t)
      {
          target = t;
          if (!target.fire_context)
              target.fire_context = default_fire_context;
      }
  
      void action::reset()
      {
          set_target(dist());
      }
  
      /**
       * Does this action meet preconditions for triggering? Checks configurable
       * HP and MP thresholds, aimed at autofight commands.
       * @return true if triggering should be prevented
       */
      bool action::autofight_check() const
      {
          // don't do these checks if the action will lead to interactive targeting
          if (target.needs_targeting())
              return false;
          bool af_hp_check = false;
          bool af_mp_check = false;
          if (!clua.callfn("af_hp_is_low", ">b", &af_hp_check)
              || uses_mp()
                 && !clua.callfn("af_mp_is_low", ">b", &af_mp_check))
          {
              if (!clua.error.empty())
                  mprf(MSGCH_ERROR, "Lua error: %s", clua.error.c_str());
              return true;
          }
          if (af_hp_check)
              mpr("You are too injured to fight recklessly!");
          else if (af_mp_check)
          {
              mprf("You are too depleted to draw on your %s recklessly!",
                  you.has_mutation(MUT_HP_CASTING) ? "health" : "mana");
          }
          return af_hp_check || af_mp_check;
      }
  
      formatted_string action::quiver_description(bool short_desc) const
      {
          return formatted_string::parse_string(
                          short_desc ? "<darkgrey>Empty</darkgrey>"
                                     : "<darkgrey>Nothing quivered</darkgrey>");
      }
  
      vector<tile_def> action::get_tiles() const
      {
          // generic handling for items: this covers a bunch of subclasses
          const int i = get_item();
          if (i >= 0 && i < ENDOFPACK && you.inv[i].defined())
          {
              vector<tile_def> ret;
              if (get_tiles_for_item(you.inv[i], ret, false))
                  return ret;
          }
  
          // valid empty action
          if (is_valid())
              return { TILEI_DISABLED };
  
          // fallback in case a subclass fails to implement this
          return { TILE_ERROR };
      }
  
      shared_ptr<action> action::find_next(int dir, bool allow_disabled, bool loop) const
      {
          auto o = get_fire_order(allow_disabled);
          if (o.size() == 0)
              return nullptr; // or same type?
  
          if (dir < 0)
              reverse(o.begin(), o.end());
  
          if (!is_valid())
              return o[0];
  
          int i = 0;
          for (; i < static_cast<int>(o.size()); i++)
              if (*o[i] == *this)
                  break;
  
          // the current action is not in the fire order at all; perhaps it is
          // disabled, or skipped for action-specific reasons. Just start at
          // the beginning.
          if (i == static_cast<int>(o.size()))
              return o[0];
  
          i++;
          if (!loop && i >= static_cast<int>(o.size()))
              return nullptr;
  
          return o[i % o.size()];
      }
  
      /**
       * Generic inscription check: if this action uses an item, is it inscribed
       * in a way that allows firing? Returns true if no item or an invalid item
       * is used.
       */
      bool action::do_inscription_check() const
      {
          const int slot = get_item();
          if (slot <= 0 || slot >= ENDOFPACK || !you.inv[slot].defined())
              return true;
  
          return check_warning_inscriptions(you.inv[slot], OPER_FIRE);
      }
  
      shared_ptr<action> action_cycler::do_target()
      {
          // this might be better as an action method
          shared_ptr<action> a = get();
          if (!a || !a->is_valid())
              return nullptr;
  
          a->reset();
          a->target.target = coord_def(-1,-1);
          a->target.find_target = false;
          a->target.fire_context = this;
          a->target.interactive = true;
  
          if (a->is_targeted())
              a->trigger(a->target);
          else
          {
              untargeted_fire(*a);
              if (!a->target.isCancel)
                  a->trigger();
          }
          // TODO: does this cause dbl "ok then"s in some places?
          if (a->target.isCancel && a->target.cmd_result == CMD_NO_CMD)
              canned_msg(MSG_OK);
  
          // we return a; if it has become invalid (e.g. by running out of ammo),
          // it will no longer be accessible via get().
          return a;
      }
  
      string action_cycler::fire_key_hints() const
      {
          const bool no_other_items = *get() == *next();
          string key_hint = no_other_items
                              ? ", <w>%</w> - select action"
                              : ", <w>%</w> - select action, <w>%</w>/<w>%</w> - cycle";
          insert_commands(key_hint,
                          { CMD_TARGET_SELECT_ACTION,
                            CMD_TARGET_CYCLE_QUIVER_BACKWARD,
                            CMD_TARGET_CYCLE_QUIVER_FORWARD });
          return key_hint;
      }
  
      bool action_cycler::targeter_handles_key(command_type c) const
      {
          // TODO: factor these out of the menu code into methods on this class?
          switch (c)
          {
          case CMD_TARGET_SELECT_ACTION:
          case CMD_TARGET_CYCLE_QUIVER_FORWARD:
          case CMD_TARGET_CYCLE_QUIVER_BACKWARD:
              return true;
          default:
              return false;
          }
      }
  
      // Get a sorted list of items to show in the fire interface.
      //
      // If ignore_inscription_etc, ignore =f and Options.fire_items_start.
      // This is used for generating informational error messages, when the
      // fire order is empty.
      //
      // launcher determines what items match the 'launcher' fire_order type.
      static void _get_item_fire_order(vector<int>& order,
                                       bool ignore_inscription_etc,
                                       const item_def* launcher,
                                       bool manual)
      {
          const int inv_start = (ignore_inscription_etc ? 0
                                                        : Options.fire_items_start);
  
          for (int i_inv = inv_start; i_inv < ENDOFPACK; i_inv++)
          {
              const item_def& item = you.inv[i_inv];
              if (!item.defined())
                  continue;
  
              const auto l = is_launched(&you, launcher, item);
  
              // don't swap to throwing when you run out of launcher ammo. (The
              // converse case should be ruled out by _item_matches below.)
              // TODO: (a) is this the right thing to do, and (b) should it be
              // done in _item_matches? I don't fully understand the logic there.
              if (!manual
                  && _get_weapon_ammo_type(launcher) != AMMO_THROW
                  && l == launch_retval::THROWN)
              {
                  continue;
              }
  
              // =F prevents item from being in fire order.
              if (!ignore_inscription_etc
                      && _fireorder_inscription_ok(i_inv, true) == MB_FALSE)
              {
                  continue;
              }
  
              for (unsigned int i_flags = 0; i_flags < Options.fire_order.size();
                   i_flags++)
              {
                  if (_item_matches(item, (fire_type) Options.fire_order[i_flags],
                                    launcher, manual))
                  {
                      // this approach to sorting is pretty wtf
                      order.push_back((i_flags<<16) | (i_inv & 0xffff));
                      break;
                  }
              }
          }
  
          sort(order.begin(), order.end());
  
          for (unsigned int i = 0; i < order.size(); i++)
              order[i] &= 0xffff;
      }
  
      // class isn't intended for quivering per se. Rather, it's a wrapper on
      // targeted attacks involving melee weapons or unarmed fighting. This
      // covers regular 1-space melee attacks, as well as reaching attacks of
      // arbitrary distance (currently only going up to 2 in practice).
      struct melee_action : public action
      {
          melee_action() : action()
          {
          }
  
          void save(CrawlHashTable &save_target) const override;
  
          bool is_enabled() const override
          {
              // just disable the action for these cases. In the long run it
              // may be good to merge swing_at_target into here?
              return !you.caught()
                  && !you.confused();
          }
  
          bool is_valid() const override { return true; }
          bool is_targeted() const override { return true; }
  
          string quiver_verb() const
          {
              const item_def *weapon = you.weapon();
  
              if (!weapon)
              {
                  const auto form_verbs = get_form(you.form)->uc_attack_verbs;
                  if (form_verbs.medium) // we use med because it mostly has better flavor
                      return form_verbs.medium;
                  else
                  {
                      // this is actually a bitmask, but we will simplify quite a
                      // bit here and only use this for unarmed/forms. See
                      // melee_attack::set_attack_verb for the real thing.
                      const int dt = you.damage_type();
                      if (dt & DVORP_CLAWING || dt & DVORP_TENTACLE)
                          return "attack";
                  }
                  return "punch";
              }
  
              const launcher lt = _get_weapon_ammo_type(weapon);
              if (lt != AMMO_THROW)
                  return lt == AMMO_SLING ? "fire" : "shoot";
              else if (weapon_reach(*weapon) > REACH_NONE)
                  return "reach";
              else if (attack_cleaves(you))
                  return "cleave";
              else
                  return "hit"; // could use more subtype flavor Vs?
          }
  
          formatted_string quiver_description(bool short_desc=false) const override
          {
              if (!is_valid())
                  return action::quiver_description(short_desc);
  
              formatted_string qdesc;
              const item_def *weapon = you.weapon();
  
              // TODO: or just lightgrey?
              qdesc.textcolour(Options.status_caption_colour);
  
              if (!short_desc)
              {
                  string verb = you.confused() ? "confused " : "";
  
                  verb += quiver_verb();
                  qdesc.cprintf("%s: %c) ", uppercase_first(verb).c_str(),
                                  weapon ? index_to_letter(weapon->link) : '-');
              }
  
              const string prefix = weapon ? item_prefix(*weapon) : "";
  
              const int prefcol =
                  menu_colour(weapon ? weapon->name(DESC_PLAIN) : "", prefix, "stats");
              if (!is_enabled())
                  qdesc.textcolour(DARKGREY);
              else if (prefcol != -1)
                  qdesc.textcolour(prefcol);
              else
                  qdesc.textcolour(LIGHTGREY);
  
              qdesc += weapon ? weapon->name(DESC_PLAIN, true)
                              : you.unarmed_attack_name();
  
              return qdesc;
          }
  
          void trigger(dist &t) override
          {
              // TODO: does it actually make sense to have this monster in
              // quiver.cc?
  
              set_target(t);
  
              if (you.confused())
              {
                  if (you.is_stationary())
                  {
                      // XX duplicate code with movement.cc:move_player_action
                      if (cancel_confused_move(true))
                          return;
  
                      if (!one_chance_in(3))
                      {
                          coord_def move(random2(3) - 1, random2(3) - 1);
                          if (move.origin())
                          {
                              mpr("You nearly hit yourself!");
                              you.turn_is_over = true;
                              return;
                          }
                          // replace input target with a confused one
                          target.target = you.pos() + move;
                      }
                      // fallthrough
                  }
                  else
                  {
                      if (target.needs_targeting())
                          mpr("You're too confused to aim your attacks!");
                      else
                          mpr("You're too confused to attack without stumbling around!");
                      return;
                  }
              }
  
              if (you.caught())
              {
                  if (target.needs_targeting())
                      mprf("You cannot attack while %s.", held_status());
                  else
                  {
                      // assume that if a target was explicitly supplied, it was
                      // done intentionally
                      free_self_from_net();
                      you.turn_is_over = true;
                      return;
                  }
                  return;
              }
  
              bool targ_mid = false;
  
              const item_def *weapon = you.weapon();
  
              // TODO: is there any use case for allowing targeting in this case?
              // if this check isn't here, it is treated as a clumsy melee attack
              if (weapon && is_range_weapon(*weapon))
              {
                  mprf("You do not have any ammo quivered for %s.",
                                      you.weapon()->name(DESC_YOUR).c_str());
                  return;
              }
  
              // This is redundant with a later check in fight_melee; but, the
              // way this check works, if the player overrides it once it won't
              // give a warning until they switch weapons. UI-wise, if there is
              // going to be a targeter it makes sense to show it first.
              if (target.needs_targeting() && !wielded_weapon_check(weapon))
                  return;
  
              target.isEndpoint = true; // is this needed? imported from autofight code
              const reach_type reach_range = !weapon ? REACH_NONE
                                                      : weapon_reach(*weapon);
  
              direction_chooser_args args;
              args.restricts = DIR_TARGET;
              args.mode = TARG_HOSTILE;
              args.range = reach_range;
              args.top_prompt = quiver_description().tostring();
              args.self = confirm_prompt_type::cancel;
  
              unique_ptr<targeter> hitfunc;
              // Xom can give you cleaving status while wielding a reaching
              // weapon, just use the reach targeter for this case. (TODO:
              // show cleave effect in targeter.)
              if (attack_cleaves(you, -1) && reach_range < REACH_TWO)
                  hitfunc = make_unique<targeter_cleave>(&you, you.pos());
              else
                  hitfunc = make_unique<targeter_reach>(&you, reach_range);
              args.hitfunc = hitfunc.get();
              args.get_desc_func = bind(_desc_hit_chance, placeholders::_1);
  
              direction(target, args);
              t = target;
  
              if (!target.isValid)
              {
                  if (target.isCancel)
                      canned_msg(MSG_OK);
                  return;
              }
  
              if (target.isMe())
              {
                  canned_msg(MSG_UNTHINKING_ACT);
                  return;
              }
  
              const coord_def delta = target.target - you.pos();
              const int x_distance  = abs(delta.x);
              const int y_distance  = abs(delta.y);
              monster* mons = monster_at(target.target);
              // don't allow targeting of submerged monsters
              if (mons && mons->submerged())
                  mons = nullptr;
  
              if (x_distance > reach_range || y_distance > reach_range)
              {
                  mpr("Your weapon can't reach that far!");
                  return;
              }
  
              if (feat_is_solid(env.grid(target.target)))
              {
                  canned_msg(MSG_SOMETHING_IN_WAY);
                  return;
              }
  
              // Failing to hit someone due to a friend blocking is infuriating,
              // shadow-boxing empty space is not (and would be abusable to wait
              // with no penalty).
              if (mons)
                  you.apply_berserk_penalty = false;
  
              // Calculate attack delay now in case we have to apply it.
              const int attack_delay = you.attack_delay().roll();
  
              // Check for a monster in the way. If there is one, it blocks the reaching
              // attack 50% of the time, and the attack tries to hit it if it is hostile.
              // REACH_THREE entails smite targeting; this is a bit hacky in that
              // this is entirely for the sake of UNRAND_RIFT.
              if (reach_range < REACH_THREE && (x_distance > 1 || y_distance > 1))
              {
                  const int x_first_middle = you.pos().x + (delta.x) / 2;
                  const int y_first_middle = you.pos().y + (delta.y) / 2;
                  const int x_second_middle = target.target.x - (delta.x) / 2;
                  const int y_second_middle = target.target.y - (delta.y) / 2;
                  const coord_def first_middle(x_first_middle, y_first_middle);
                  const coord_def second_middle(x_second_middle, y_second_middle);
  
                  if (!feat_is_reachable_past(env.grid(first_middle))
                      && !feat_is_reachable_past(env.grid(second_middle)))
                  {
                      canned_msg(MSG_SOMETHING_IN_WAY);
                      return;
                  }
  
                  // Choose one of the two middle squares (which might be the same).
                  const coord_def middle =
                      !feat_is_reachable_past(env.grid(first_middle)) ? second_middle :
                      !feat_is_reachable_past(env.grid(second_middle)) ? first_middle :
                  random_choose(first_middle, second_middle);
  
                  bool success = true;
                  monster *midmons;
                  if ((midmons = monster_at(middle))
                      && !midmons->submerged()
                      && coinflip())
                  {
                      success = false;
                      target.target = middle;
                      mons = midmons;
                      targ_mid = true;
                      t = target;
                      if (mons->wont_attack())
                      {
                          // Let's assume friendlies cooperate.
                          mpr("You could not reach far enough!");
                          you.time_taken = attack_delay;
                          you.turn_is_over = true;
                          return;
                      }
                  }
  
                  if (success)
                      mpr("You reach to attack!");
                  else
                  {
                      mprf("%s is in the way.",
                           mons->observable() ? mons->name(DESC_THE).c_str()
                                              : "Something you can't see");
                  }
              }
  
              if (mons == nullptr)
              {
                  if (feat_is_solid(env.grid(target.target)) && !you.confused())
                      return;
  
                  if (!force_player_cleave(target.target) && !you.fumbles_attack())
                  {
                      if (x_distance <= 1 && y_distance <= 1)
                          mpr("You swing at nothing.");
                      else
                          mpr("You attack empty space.");
                  }
                  you.time_taken = attack_delay;
                  you.turn_is_over = true;
              }
              else
              {
                  // something to attack, let's do it:
                  you.turn_is_over = true;
                  if (!fight_melee(&you, mons) && targ_mid)
                  {
                      // turn_is_over may have been reset to false by fight_melee, but
                      // a failed attempt to reach further should not be free; instead,
                      // charge the same as a successful attempt.
                      you.time_taken = attack_delay;
                      you.turn_is_over = true;
                  }
                  you.berserk_penalty = 0;
                  you.apply_berserk_penalty = false;
              }
  
              return;
          }
  
          int get_item() const override
          {
              return you.equip[EQ_WEAPON];
          };
  
          vector<shared_ptr<action>> get_fire_order(bool allow_disabled=true, bool=false) const override
          {
              if (allow_disabled || is_enabled())
                  return { make_shared<melee_action>() };
              else
                  return { };
          }
      };
  
      /**
       * An ammo_action is an action that fires ammo from a slot in the
       * inventory. This covers throwing; tossing and launching are handled by
       * a subclass.
       */
      struct ammo_action : public action
      {
          // it could be simpler to have a distinct type for launcher ammo and
          // throwing ammo
          ammo_action(int slot=-1) : action(), ammo_slot(slot)
          {
          }
  
          virtual void save(CrawlHashTable &save_target) const override; // defined below
  
          bool equals(const action &other) const override
          {
              // type ensured in base class
              return ammo_slot == static_cast<const ammo_action &>(other).ammo_slot;
          }
  
          virtual item_def *get_launcher() const override
          {
              return nullptr;
          }
  
          /// does the launch type match the action type and current weapon?
          virtual bool launch_type_check() const
          {
              // will assert if ammo_slot is invalid
              // intentionally uses you.weapon() instead of get_launcher(): this
              // renders a throwing ammo invalid if the player is wielding a
              // launcher for it. There's no in principle reason to disallow
              // throwing stones while wielding a sling, but it's more confusing
              // than helpful.
              const launch_retval projected = is_launched(&you, you.weapon(),
                                                          you.inv[ammo_slot]);
              return projected == launch_retval::THROWN;
          }
  
          bool do_inscription_check() const override
          {
              // need to also check the launcher's inscription here, in addition
              // to ammo
              if (!is_valid()) // sanity check
                  return true;
              const item_def *weapon = get_launcher();
              const item_def& ammo = you.inv[ammo_slot];
              return action::do_inscription_check()
                  && (!weapon
                      || is_launched(&you, weapon, ammo) != launch_retval::LAUNCHED
                      || check_warning_inscriptions(*weapon, OPER_FIRE));
          }
  
          virtual bool is_enabled() const override
          {
              if (!is_valid())
                  return false;
  
              if (fire_warn_if_impossible(true, get_launcher()))
                  return false;
  
              // TODO: check inscriptions here? That code would need to be
              // refactored.
              return true;
          }
  
          virtual bool is_valid() const override
          {
              if (you.has_mutation(MUT_NO_GRASPING))
                  return false;
              if (ammo_slot < 0 || ammo_slot >= ENDOFPACK)
                  return false;
              const item_def& ammo = you.inv[ammo_slot];
              if (!ammo.defined())
                  return false;
  
              return launch_type_check();
          }
  
          bool is_targeted() const override
          {
              return !you.confused();
          }
  
          bool allow_autofight() const override
          {
              if (!is_enabled())
                  return false;
  
              // nets shouldn't be autofired with autofight_throw, only with
              // autofire.
              if (you.inv[ammo_slot].sub_type == MI_THROWING_NET)
                  return false;
  
              return true;
          }
  
          bool uses_mp() const override
          {
              return is_pproj_active();
          }
  
          bool affected_by_pproj() const override
          {
              return true;
          }
  
          void trigger(dist &t) override
          {
              set_target(t);
              if (!is_valid())
                  return;
              if (!is_enabled())
              {
                  fire_warn_if_impossible(false, get_launcher()); // for messaging (TODO refactor; message about inscriptions?)
                  return;
              }
              if (autofight_check() || !do_inscription_check())
                  return;
  
              // TODO: refactor throw_it into here?
              throw_it(*this);
  
              // Update the legacy quiver history data structure
              // TODO: eliminate this? History should be stored per quiver, not
              // globally
              you.m_quiver_history.on_item_fired(you.inv[ammo_slot],
                      !target.fire_context || !target.fire_context->autoswitched);
  
              t = target; // copy back, in case they are different
          }
  
          virtual formatted_string quiver_description(bool short_desc) const override
          {
              ASSERT_RANGE(ammo_slot, -1, ENDOFPACK);
              // or error?
              if (!is_valid())
                  return action::quiver_description(short_desc);
  
              formatted_string qdesc;
  
              const item_def& quiver = you.inv[ammo_slot];
              ASSERT(quiver.link != NON_ITEM);
              // TODO: or just lightgrey?
              qdesc.textcolour(Options.status_caption_colour);
              const launch_retval projected = is_launched(&you, get_launcher(),
                                                                      quiver);
              if (!short_desc)
              {
                  string verb = you.confused() ? "confused " : "";
                  switch (projected)
                  {
                      case launch_retval::FUMBLED:  verb += "toss (no damage)";  break;
                      case launch_retval::LAUNCHED: verb += "fire";  break;
                      case launch_retval::THROWN:   verb += "throw"; break;
                      case launch_retval::BUGGY:    verb += "bug";   break;
                  }
                  qdesc.cprintf("%s: ", uppercase_first(verb).c_str());
              }
  
              // TODO: I don't actually know what this prefix stuff is
              const string prefix = item_prefix(quiver);
  
              const int prefcol =
                  menu_colour(quiver.name(DESC_PLAIN), prefix, "stats");
              if (!is_enabled())
                  qdesc.textcolour(DARKGREY);
              else if (prefcol != -1)
                  qdesc.textcolour(prefcol);
              else
                  qdesc.textcolour(LIGHTGREY);
  
              if (short_desc && quiver.sub_type == MI_SLING_BULLET)
              {
                  qdesc.cprintf("%d bullet%s", quiver.quantity,
                                  quiver.quantity > 1 ? "s" : "");
              }
              else
                  qdesc += quiver.name(DESC_PLAIN, true);
  
              return qdesc;
          }
  
          int get_item() const override
          {
              return ammo_slot;
          }
  
          virtual shared_ptr<action> find_replacement() const override
          {
              return find_action_from_launcher(get_launcher());
          }
  
          vector<shared_ptr<action>> get_menu_fire_order(bool allow_disabled)
          {
              vector<shared_ptr<action>> result;
              for (int i_inv = 0; i_inv < ENDOFPACK; i_inv++)
              {
                  auto a = make_shared<ammo_action>(i_inv);
                  if (a->is_valid() && (allow_disabled || a->is_enabled()))
                      result.push_back(move(a));
              }
              return result;
          }
  
          vector<shared_ptr<action>> get_fire_order(
              bool allow_disabled=true, bool ignore_inscription=false) const override
          {
              vector<int> fire_order;
              _get_item_fire_order(fire_order, ignore_inscription, get_launcher(), false);
  
              vector<shared_ptr<action>> result;
  
              for (auto i : fire_order)
              {
                  auto a = make_shared<ammo_action>(i);
                  if (a->is_valid() && (allow_disabled || a->is_enabled()))
                      result.push_back(move(a));
                  // TODO: allow arbitrary items with +f to get added here? I
                  // have had some trouble getting this to work
              }
              return result;
          }
  
      protected:
          int ammo_slot;
      };
  
      struct launcher_ammo_action : public ammo_action
      {
          // it could be simpler to have a distinct type for launcher ammo and
          // throwing ammo
          launcher_ammo_action(int slot=-1) : ammo_action(slot)
          {
          }
  
          item_def *get_launcher() const override
          {
              return you.weapon();
          }
  
          virtual void save(CrawlHashTable &save_target) const override; // defined below
  
          bool launch_type_check() const override
          {
              if (ammo_slot < 0 || !get_launcher())
                  return false;
              return you.inv[ammo_slot].launched_by(*get_launcher());
          }
  
          vector<shared_ptr<action>> get_menu_fire_order(bool allow_disabled)
          {
              vector<shared_ptr<action>> result;
              for (int i_inv = 0; i_inv < ENDOFPACK; i_inv++)
              {
                  auto a = make_shared<launcher_ammo_action>(i_inv);
                  if (a->is_valid() && (allow_disabled || a->is_enabled()))
                      result.push_back(move(a));
              }
              return result;
          }
  
          vector<shared_ptr<action>> get_fire_order(
              bool allow_disabled=true, bool ignore_inscription=false) const override
          {
              vector<int> fire_order;
              _get_item_fire_order(fire_order, ignore_inscription, get_launcher(), false);
  
              vector<shared_ptr<action>> result;
  
              for (auto i : fire_order)
              {
                  auto a = make_shared<launcher_ammo_action>(i);
                  if (a->is_valid() && (allow_disabled || a->is_enabled()))
                      result.push_back(move(a));
              }
              return result;
          }
  
      };
  
      // for fumble throwing / tossing
      struct fumble_action : public ammo_action
      {
          fumble_action(int slot=-1) : ammo_action(slot)
          {
          }
  
          void save(CrawlHashTable &save_target) const override; // defined below
  
          // uses ammo_action fire order
  
          bool launch_type_check() const override
          {
              return true;
          }
  
          bool is_valid() const override
          {
              if (you.has_mutation(MUT_NO_GRASPING))
                  return false;
              if (ammo_slot < 0 || ammo_slot >= ENDOFPACK)
                  return false;
              const item_def& ammo = you.inv[ammo_slot];
              if (!ammo.defined())
                  return false;
  
              return true;
          }
      };
  
      static bool _spell_needs_manual_targeting(spell_type s)
      {
          switch (s)
          {
          case SPELL_FULMINANT_PRISM:
          case SPELL_GRAVITAS: // will autotarget to a monster if allowed, should we allow?
          case SPELL_PASSWALL: // targeted, but doesn't make sense with autotarget
          case SPELL_GOLUBRIAS_PASSAGE: // targeted, but doesn't make sense with autotarget
              return true;
          default:
              return false;
          }
      }
  
      // for spells that are targeted, but should skip the lua target selection
      // pass for one reason or another
      static bool _spell_no_autofight_targeting(spell_type s)
      {
          // XX perhaps all spells should just use direction chooser target
          // selection? This is how automagic.lua handled it.
          auto h = find_spell_targeter(s, 100, LOS_RADIUS); // dummy values
          // use smarter direction chooser target selection for spells that have
          // explosition or cloud patterning, like fireball. This allows them
          // to autoselect targets at the edge of their range, which autofire
          // wouldn't handle.
          if (!Options.simple_targeting && h && h->can_affect_outside_range())
              return true;
  
          switch (s)
          {
          case SPELL_LRD: // skip initial autotarget for LRD so that it doesn't
                          // fix on a close monster that can't be targeted. I'm
                          // not quite sure what the right thing to do is?
                          // An alternative would be to just error if the closest
                          // monster can't be autotargeted, or pop out to manual
                          // targeting for that case; the behavior involved in
                          // listing it here just finds the closest targetable
                          // monster.
          case SPELL_INVISIBILITY: // targeted, but not to enemies. (Should this allow quivering at all?)
          case SPELL_APPORTATION: // Apport doesn't target monsters at all
              return true;
          default:
              return _spell_needs_manual_targeting(s);
          }
      }
  
      struct spell_action : public action
      {
          spell_action(spell_type s = SPELL_NO_SPELL)
              : spell(s), enabled_cache(false), col_cache(COL_USELESS)
          {
              invalidate();
          };
  
          void save(CrawlHashTable &save_target) const override; // defined below
  
          bool equals(const action &other) const override
          {
              // type ensured in base class
              return spell == static_cast<const spell_action &>(other).spell;
          }
  
          bool is_dynamic_targeted() const
          {
              // TODO: what spells does this miss?
              return !!(get_spell_flags(spell) & spflag::targeting_mask);
          }
  
          void invalidate() override
          {
              // we cache enabled status and color because these calls are
              // extremely side-effect-y, and can crash if called at the wrong
              // time.
              enabled_cache = can_cast_spells(true)
                                      && !spell_is_useless(spell, true, false);
              // this imposes excommunication colors
              if (!enabled_cache)
                  col_cache = COL_USELESS;
              else
              {
                  col_cache = spell_highlight_by_utility(spell,
                                  failure_rate_colour(spell), true, false);
              }
          }
  
          bool is_enabled() const override
          {
              return enabled_cache;
          }
  
          bool is_valid() const override
          {
              return is_valid_spell(spell) && you.has_spell(spell);
          }
  
          bool is_targeted() const override
          {
              return is_dynamic_targeted() || spell_has_targeter(spell);
          }
  
          bool use_autofight_targeting() const override
          {
              return is_dynamic_targeted()
                                  && !_spell_no_autofight_targeting(spell);
          }
  
          bool allow_autofight() const override
          {
              if (!is_enabled())
                  return false;
              if (_spell_needs_manual_targeting(spell))
                  return false;
              if (spell_is_direct_attack(spell))
                  return true;
              if (spell == SPELL_FOXFIRE) // not a direct attack, but has sensible autofight behavior
                  return true;
              return false;
          }
  
          bool uses_mp() const override
          {
              return is_valid();
          }
  
          void trigger(dist &t) override
          {
              // note: we don't do the enabled check here, because cast_a_spell
              // duplicates it and does appropriate messaging
              // TODO refactor?
              if (!is_valid())
                  return;
  
              set_target(t);
  
              // don't do the range check check if doing manual firing.
              const bool do_range_check = !target.needs_targeting();
  
              if (_spell_needs_manual_targeting(spell))
              {
                  // force interactive mode no matter what:
                  target.target = coord_def(-1,-1);
                  target.find_target = false; // default, but here for clarity's sake
                  target.interactive = true;
              }
              else if (_spell_no_autofight_targeting(spell))
              {
                  // use direction chooser find_target behavior unless interactive
                  // is set before the call:
                  target.target = coord_def(-1,-1);
                  target.find_target = true;
              }
              else if (!is_dynamic_targeted())
              {
                  // this is a somewhat hacky way to allow non-interactive mode;
                  // the value of `target` doesn't matter for static targeters.
                  // Like the no-autofight case, if `interactive` is set
                  // before the call, will still pop up an interactive targeter.
                  target.target = you.pos();
              }
  
              if (autofight_check())
                  return;
  
              cast_a_spell(do_range_check, spell, &target);
  
              t = target; // copy back, in case they are different
          }
  
          int quiver_color() const override
          {
              return col_cache;
          }
  
          vector<tile_def> get_tiles() const override
          {
              return { tile_def(get_spell_tile(spell)) };
          }
  
          formatted_string quiver_description(bool short_desc) const override
          {
              if (!is_valid())
                  return action::quiver_description(short_desc);
  
              formatted_string qdesc;
  
              qdesc.textcolour(Options.status_caption_colour);
              qdesc.cprintf("Cast: ");
  
              qdesc.textcolour(quiver_color());
  
              // TODO: is showing the spell letter useful?
              qdesc.cprintf("%s", spell_title(spell));
              if (spell == SPELL_SANDBLAST)
                  qdesc.cprintf(" (stones: %d)", sandblast_find_ammo().first);
  
              if (fail_severity(spell) > 0)
              {
                  qdesc.cprintf(" (%s)",
                          failure_rate_to_string(raw_spell_fail(spell)).c_str());
              }
  
              return qdesc;
          }
  
          vector<shared_ptr<action>> get_fire_order(
              bool allow_disabled=true, bool=false) const override
          {
              // goes by letter order
              vector<shared_ptr<action>> result;
              for (int i = 0; i < 52; i++)
              {
                  const char letter = index_to_letter(i);
                  const spell_type s = get_spell_by_letter(letter);
                  auto a = make_shared<spell_action>(s);
                  if (a->is_valid()
                      && (allow_disabled || a->is_enabled())
                      // some extra stuff for fire order in particular: don't
                      // cycle to spells that are dangerous to past or forbidden.
                      // These can still be force-quivered.
                      && fail_severity(s) < Options.fail_severity_to_quiver
                      && spell_highlight_by_utility(s, COL_UNKNOWN) != COL_FORBIDDEN)
                  {
                      result.push_back(move(a));
                  }
              }
              return result;
          }
  
      private:
          spell_type spell;
          bool enabled_cache;
          int col_cache;
      };
  
      // stuff that is silly to quiver. Basically four (overlapping) cases:
      // * one-off things that are implemented as abilities because that's what
      //   you do with random stuff that doesn't fit neatly into any triggerable
      //   type. E.g. abandon religion, choose ancestor type, etc.
      // * "stop X" type abilities, these just clutter up the list
      // * capstone abilities + stuff with a significant cost (revivify etc)
      // * abilities that vanish when triggered. E.g. fly *might* make more sense
      //   if it was a toggle, but that's not how it's implemented
      static bool _pseudoability(ability_type a)
      {
          if (   static_cast<int>(a) >= ABIL_FIRST_SACRIFICE
                      && static_cast<int>(a) <= ABIL_FINAL_SACRIFICE
              || static_cast<int>(a) >= ABIL_HEPLIAKLQANA_FIRST_TYPE
                      && static_cast<int>(a) <= ABIL_HEPLIAKLQANA_LAST_TYPE)
          {
              return true;
          }
  
          switch (a)
          {
          case ABIL_END_TRANSFORMATION:
          case ABIL_CANCEL_PPROJ:
          case ABIL_EXSANGUINATE:
          case ABIL_REVIVIFY:
          case ABIL_EVOKE_TURN_VISIBLE:
          case ABIL_ZIN_DONATE_GOLD:
          case ABIL_TSO_BLESS_WEAPON:
          case ABIL_KIKU_BLESS_WEAPON:
          case ABIL_KIKU_GIFT_NECRONOMICON:
          case ABIL_SIF_MUNA_FORGET_SPELL:
          case ABIL_LUGONU_BLESS_WEAPON:
          case ABIL_BEOGH_GIFT_ITEM:
          case ABIL_ASHENZARI_CURSE:
          case ABIL_RU_REJECT_SACRIFICES:
          case ABIL_HEPLIAKLQANA_IDENTITY:
          case ABIL_STOP_RECALL:
          case ABIL_RENOUNCE_RELIGION:
          case ABIL_CONVERT_TO_BEOGH:
          // not entirely pseudo, but doesn't make a lot of sense to quiver:
          case ABIL_FLY:
          case ABIL_TRAN_BAT:
              return true;
          default:
              return false;
          }
      }
  
      static bool _ability_quiver_range_check(ability_type abil, bool quiet=true)
      {
          // Hacky: do some quiver-specific range checks for the sake of
          // autofight. We can't approach this like spells (which are marked
          // as useless with no enemies in range), because there is no
          // equivalent of `Z` to force-activate an ability that is indicating
          // temporary uselessness. This way, the player can still activate
          // it from the `a` menu, just not from the quiver.
          // (What abilities are missing here?)
  
          switch (abil)
          {
          case ABIL_MAKHLEB_MINOR_DESTRUCTION:
          {
              // can this be consolidated with spell range checks?
              // n.b. the range value here is used differently than in spell range
              // checks (for consistency with the ability implementation)
              const int range = min((int)you.current_vision, 5);
              if (get_dist_to_nearest_monster() <= range)
                  return true;
              if (!quiet)
                  mpr("You can't see any hostile targets that would be affected.");
              return false;
          }
          case ABIL_ROLLING_CHARGE:
              // Use a version of the palentonga charge range check that
              // ignores things like butterflies, so that autofight doesn't get
              // tripped up.
              return palentonga_charge_possible(quiet, false);
          default:
              return true;
          }
      }
  
      struct ability_action : public action
      {
          ability_action(ability_type a = ABIL_NON_ABILITY) : ability(a) { };
          void save(CrawlHashTable &save_target) const override; // defined below
  
          bool equals(const action &other) const override
          {
              // type ensured in base class
              return ability == static_cast<const ability_action &>(other).ability;
          }
  
          bool is_valid() const override
          {
              // special case usk abilities to ignore piety: they come and go so
              // rapidly that they are effectively unquiverable otherwise.
              // Should any other god powers get this treatment?
              if (is_religious_ability(ability) && you_worship(GOD_USKAYAW))
              {
                  auto *p = god_power_from_ability(ability);
                  return p && god_power_usable(*p, true, false);
              }
              return player_has_ability(ability, true);
          }
  
          bool is_enabled() const override
          {
              if (!is_valid())
                  return false;
  
              if (!_ability_quiver_range_check(ability))
                  return false;
  
              // TODO: _check_ability_dangerous?
              return check_ability_possible(ability, true);
          }
  
          bool is_targeted() const override
          {
              // hard-coded list of abilities that have a targeter
              // there is no general way of getting this?
              // TODO: implement static targeters for relevant abilities (which
              // is pretty much everything)
              switch (ability)
              {
              case ABIL_HOP:
              case ABIL_ROLLING_CHARGE:
              case ABIL_SPIT_POISON:
              case ABIL_BREATHE_ACID:
              case ABIL_BREATHE_FIRE:
              case ABIL_BREATHE_FROST:
              case ABIL_BREATHE_POISON:
              case ABIL_BREATHE_POWER:
              case ABIL_BREATHE_STEAM:
              case ABIL_BREATHE_MEPHITIC:
              case ABIL_DAMNATION:
              case ABIL_ZIN_IMPRISON:
              case ABIL_MAKHLEB_MINOR_DESTRUCTION:
              case ABIL_MAKHLEB_MAJOR_DESTRUCTION:
              case ABIL_LUGONU_BANISH:
              case ABIL_BEOGH_SMITING:
              case ABIL_DITHMENOS_SHADOW_STEP:
              case ABIL_QAZLAL_UPHEAVAL:
              case ABIL_RU_POWER_LEAP:
              case ABIL_USKAYAW_LINE_PASS:
              case ABIL_USKAYAW_GRAND_FINALE:
              case ABIL_WU_JIAN_WALLJUMP:
                  return true;
              default:
                  return false;
              }
          }
  
          bool allow_autofight() const override
          {
              if (!is_enabled())
                  return false;
              switch (ability)
              {
              case ABIL_ROLLING_CHARGE: // TODO: disable under nomove?
              case ABIL_RU_POWER_LEAP: // disable under nomove, or altogether?
              case ABIL_SPIT_POISON:
              case ABIL_BREATHE_ACID:
              case ABIL_BREATHE_FIRE:
              case ABIL_BREATHE_FROST:
              case ABIL_BREATHE_POISON:
              case ABIL_BREATHE_POWER:
              case ABIL_BREATHE_STEAM:
              case ABIL_BREATHE_MEPHITIC:
              case ABIL_DAMNATION:
              case ABIL_MAKHLEB_MINOR_DESTRUCTION:
              case ABIL_MAKHLEB_MAJOR_DESTRUCTION:
              case ABIL_LUGONU_BANISH:
              case ABIL_BEOGH_SMITING:
              case ABIL_QAZLAL_UPHEAVAL:
                  return true;
              default:
                  return false;
              }
          }
  
          bool use_autofight_targeting() const override
          {
              return false;
          }
  
          bool uses_mp() const override
          {
              return ability_mp_cost(ability) > 0;
          }
  
          void trigger(dist &t) override
          {
              if (!is_valid())
                  return;
  
              // TODO: for more uniform behavior with spells, this should skip the
              // range check in firing mode. However, there's no clean way to
              // do this right now, so I'm just leaving this comment.
              if (!is_enabled())
              {
                  // do some messaging
                  if (_ability_quiver_range_check(ability, false))
                      check_ability_possible(ability, false);
                  return;
              }
              set_target(t);
              target.find_target = true;
  
              if (autofight_check())
                  return;
  
              talent tal = get_talent(ability, false);
              activate_talent(tal, &target);
  
              // TODO: does non-targeted case come up?
              if (target.isCancel && !target.interactive && is_targeted())
                  mprf("No targets found!");
  
              t = target; // copy back, in case they are different
          }
  
          formatted_string quiver_description(bool short_desc) const override
          {
              if (!is_valid())
                  return action::quiver_description(short_desc);
  
              formatted_string qdesc;
  
              qdesc.textcolour(Options.status_caption_colour);
              qdesc.cprintf("Abil: ");
  
              qdesc.textcolour(quiver_color());
              qdesc.cprintf("%s", ability_name(ability));
  
              return qdesc;
          }
  
          vector<tile_def> get_tiles() const override
          {
              return { tile_def(tileidx_ability(ability)) };
          }
  
          vector<shared_ptr<action>> get_fire_order(
              bool allow_disabled=true, bool=false) const override
          {
              vector<talent> talents = your_talents(false, true, true);
              // goes by letter order
              vector<shared_ptr<action>> result;
  
              for (const auto &tal : talents)
              {
                  if (_pseudoability(tal.which))
                      continue;
                  auto a = make_shared<ability_action>(tal.which);
                  if (a->is_valid() && (allow_disabled || a->is_enabled()))
                      result.push_back(move(a));
              }
              return result;
          }
  
      private:
          ability_type ability;
      };
  
  
      // TODO: generalize to misc evokables? Code should be similar if targeting
      // can be easily implemented
      struct wand_action : public action
      {
          wand_action(int slot=-1) : action(), wand_slot(slot)
          {
          }
  
          virtual void save(CrawlHashTable &save_target) const override; // defined below
  
          bool equals(const action &other) const override
          {
              // type ensured in base class
              return wand_slot == static_cast<const wand_action &>(other).wand_slot;
          }
  
          bool is_enabled() const override
          {
              return evoke_check(wand_slot, true);
          }
  
          // n.b. implementing do_inscription_check for OPER_EVOKE is not needed
          // for this class as long as it is checked in evoke_item.
  
          virtual bool is_valid() const override
          {
              if (wand_slot < 0 || wand_slot >= ENDOFPACK)
                  return false;
              const item_def& wand = you.inv[wand_slot];
              if (!wand.defined() || wand.base_type != OBJ_WANDS)
                  return false;
              return true;
          }
  
          bool is_targeted() const override
          {
              return true;
          }
  
          bool allow_autofight() const override
          {
              if (!is_valid() || !is_enabled()) // need to check item validity
                  return false;
  
              switch (you.inv[wand_slot].sub_type)
              {
              case WAND_DIGGING:     // non-damaging wands
              case WAND_POLYMORPH:
              case WAND_CHARMING:
              case WAND_PARALYSIS:
                  return false;
              default:
                  return true;
              }
          }
  
          // TOOD: uses_mp for wand mp mutation? Because this mut no longer forces
          // mp use, the result is somewhat weird
  
          void trigger(dist &t) override
          {
              set_target(t);
              if (!is_valid())
                  return;
  
              if (!is_enabled())
              {
                  evoke_check(wand_slot); // for messaging
                  return;
              }
  
              // to apply smart targeting behavior for iceblast; should have no
              // impact on other wands
              target.find_target = true;
              if (autofight_check() || !do_inscription_check())
                  return;
  
              evoke_item(wand_slot, &target);
  
              t = target; // copy back, in case they are different
          }
  
          virtual string quiver_verb() const
          {
              return "Zap";
          }
  
          virtual formatted_string quiver_description(bool short_desc) const override
          {
              ASSERT_RANGE(wand_slot, -1, ENDOFPACK);
              if (!is_valid())
                  return action::quiver_description(short_desc);
              formatted_string qdesc;
  
              const item_def& quiver = you.inv[wand_slot];
              ASSERT(quiver.link != NON_ITEM);
              qdesc.textcolour(Options.status_caption_colour);
              qdesc.cprintf("%s: ", quiver_verb().c_str());
  
              qdesc.textcolour(quiver_color());
              qdesc += quiver.name(DESC_PLAIN, true);
              return qdesc;
          }
  
          int get_item() const override
          {
              return wand_slot;
          }
  
          virtual vector<shared_ptr<action>> get_fire_order(
              bool allow_disabled=true, bool ignore_inscription=false) const override
          {
              // go by pack order
              vector<shared_ptr<action>> result;
              for (int slot = 0; slot < ENDOFPACK; slot++)
              {
                  auto w = make_shared<wand_action>(slot);
                  if (w->is_valid()
                      && (allow_disabled || w->is_enabled())
                      // for fire order, treat =F inscription as disabling
                      && (ignore_inscription
                          || _fireorder_inscription_ok(slot, true) != MB_FALSE)
                      // skip digging for fire cycling, it seems kind of
                      // non-useful? Can still be force-quivered from inv.
                      // (Maybe do this with autoinscribe?)
                      && you.inv[slot].sub_type != WAND_DIGGING)
                  {
                      result.push_back(move(w));
                  }
              }
              return result;
          }
  
      protected:
          int wand_slot;
      };
  
      static bool _misc_needs_manual_targeting(int subtype)
      {
              // autotargeting seems less useful on the others. Maybe this should
              // be configurable somehow?
          return subtype != MISC_PHIAL_OF_FLOODS;
      }
  
      struct misc_action : public wand_action
      {
          misc_action(int slot=-1) : wand_action(slot)
          {
          }
  
          void save(CrawlHashTable &save_target) const override; // defined below
  
          bool is_valid() const override
          {
              if (wand_slot < 0 || wand_slot >= ENDOFPACK)
                  return false;
              const item_def& wand = you.inv[wand_slot];
              // MISC_ZIGGURAT is valid (so can be force quivered) but is skipped
              // in the fire order
              if (!wand.defined() || wand.base_type != OBJ_MISCELLANY)
                  return false;
              return true;
          }
  
          // equals should work without override
  
          bool use_autofight_targeting() const override
          {
              // all of these use the spell direction chooser
              return false;
          }
  
          bool allow_autofight() const override
          {
              if (!is_valid() || !is_enabled()) // need to check item validity
                  return false;
  
              switch (you.inv[wand_slot].sub_type)
              {
              case MISC_ZIGGURAT:     // non-damaging misc items
              case MISC_BOX_OF_BEASTS:
              case MISC_HORN_OF_GERYON:
              case MISC_QUAD_DAMAGE:
              case MISC_PHANTOM_MIRROR:
                  return false;
              default:
                  return true;
              }
          }
  
          void trigger(dist &t) override
          {
              if (is_valid()
                  && _misc_needs_manual_targeting(you.inv[wand_slot].sub_type))
              {
                  t.interactive = true;
              }
              wand_action::trigger(t);
          }
  
          string quiver_verb() const override
          {
              return "Evoke";
          }
  
          bool is_targeted() const override
          {
              if (!is_valid())
                  return false;
              switch (you.inv[wand_slot].sub_type)
              {
              case MISC_PHIAL_OF_FLOODS:
              case MISC_LIGHTNING_ROD:
              case MISC_PHANTOM_MIRROR:
                  return true;
              default:
                  return false;
              }
          }
  
          vector<shared_ptr<action>> get_fire_order(
              bool allow_disabled=true, bool ignore_inscription=false) const override
          {
              // go by pack order
              vector<shared_ptr<action>> result;
              for (int slot = 0; slot < ENDOFPACK; slot++)
              {
                  auto w = make_shared<misc_action>(slot);
                  if (w->is_valid()
                      && (allow_disabled || w->is_enabled())
                      && (ignore_inscription
                          || _fireorder_inscription_ok(slot, true) != MB_FALSE)
                      // TODO: autoinscribe =f?
                      && you.inv[slot].sub_type != MISC_ZIGGURAT)
                  {
                      result.push_back(move(w));
                  }
              }
              return result;
          }
      };
  
      struct artefact_evoke_action : public wand_action
      {
          artefact_evoke_action(int slot=-1) : wand_action(slot)
          {
          }
  
          // equals should work without override
  
          void save(CrawlHashTable &save_target) const override; // defined below
  
          bool is_valid() const override
          {
              if (wand_slot < 0 || wand_slot >= ENDOFPACK)
                  return false;
  
              const item_def& item = you.inv[wand_slot];
              if (!item.defined() || !is_unrandom_artefact(item)
                                  || !item_is_equipped(item))
              {
                  return false;
              }
  
              const unrandart_entry *entry = get_unrand_entry(item.unrand_idx);
  
              if (!entry || !(entry->evoke_func || entry->targeted_evoke_func))
                  return false;
  
              return true;
          }
  
          // TODO: there's no generic API for this, and it would be a pain to
          // add one...and there's only three of these. So we do a bit of dumb
          // code duplication. Maybe this could eventually be moved into
          // evoke_check?
          bool artefact_evoke_check(bool quiet) const
          {
              if (!is_valid())
                  return false;
  
              switch (you.inv[wand_slot].unrand_idx)
              {
              case UNRAND_DISPATER:
                  // TODO: code duplication...
                  if (you.has_mutation(MUT_HP_CASTING))
                      return enough_hp(18, quiet);
                  return enough_hp(14, quiet) && enough_mp(4, quiet);
              case UNRAND_OLGREB:
                  return enough_mp(4, quiet); // TODO: code duplication...
              default:
                  return true; // UNRAND_ASMODEUS has no up-front cost
              }
          }
  
          bool is_enabled() const override
          {
              return artefact_evoke_check(true);
          }
  
          bool use_autofight_targeting() const override
          {
              // all of these use the spell direction chooser
              return false;
          }
  
          bool allow_autofight() const override
          {
              if (!is_valid() || !is_enabled()) // need to check item validity
                  return false;
  
              switch (you.inv[wand_slot].unrand_idx)
              {
              case UNRAND_OLGREB: // only indirect damage
              case UNRAND_ASMODEUS:
                  return false;
              default:
                  return true;
              }
          }
  
          bool is_targeted() const override
          {
              if (!is_valid())
                  return false;
              // is_valid checks the preconditions for this:
              return get_unrand_entry(you.inv[wand_slot].unrand_idx)->targeted_evoke_func;
          }
  
          string quiver_verb() const override
          {
              return "Evoke";
          }
  
          void trigger(dist &t) override
          {
              set_target(t);
              if (!is_valid())
                  return;
  
              if (!artefact_evoke_check(false))
                  return;
  
              target.find_target = true;
              if (autofight_check() || !do_inscription_check())
                  return;
  
              evoke_item(wand_slot, &target);
  
              t = target; // copy back, in case they are different
          }
  
  
          vector<shared_ptr<action>> get_fire_order(
              bool allow_disabled=true, bool ignore_inscription=false) const override
          {
              // go by pack order
              vector<shared_ptr<action>> result;
              for (int slot = 0; slot < ENDOFPACK; slot++)
              {
                  auto w = make_shared<artefact_evoke_action>(slot);
                  if (w->is_valid()
                      && (allow_disabled || w->is_enabled())
                      && (ignore_inscription
                          || _fireorder_inscription_ok(slot, true) != MB_FALSE))
                  {
                      result.push_back(move(w));
                  }
              }
              return result;
          }
      };
  
  
      void action::save(CrawlHashTable &save_target) const
      {
          save_target["type"] = "action";
      }
  
      void ammo_action::save(CrawlHashTable &save_target) const
      {
          save_target["type"] = "ammo_action";
          save_target["param"] = ammo_slot;
      }
  
      void launcher_ammo_action::save(CrawlHashTable &save_target) const
      {
          save_target["type"] = "launcher_ammo_action";
          save_target["param"] = ammo_slot;
      }
  
      void fumble_action::save(CrawlHashTable &save_target) const
      {
          save_target["type"] = "fumble_action";
          save_target["param"] = ammo_slot;
      }
  
      void spell_action::save(CrawlHashTable &save_target) const
      {
          save_target["type"] = "spell_action";
          save_target["param"] = static_cast<int>(spell);
      }
  
      void ability_action::save(CrawlHashTable &save_target) const
      {
          save_target["type"] = "ability_action";
          save_target["param"] = static_cast<int>(ability);
      }
  
      void wand_action::save(CrawlHashTable &save_target) const
      {
          save_target["type"] = "wand_action";
          save_target["param"] = static_cast<int>(wand_slot);
      }
  
      void misc_action::save(CrawlHashTable &save_target) const
      {
          save_target["type"] = "misc_action";
          save_target["param"] = static_cast<int>(wand_slot);
      }
  
      void artefact_evoke_action::save(CrawlHashTable &save_target) const
      {
          save_target["type"] = "artefact_evoke_action";
          save_target["param"] = static_cast<int>(wand_slot);
      }
  
      // not currently used, but for completeness
      void melee_action::save(CrawlHashTable &save_target) const
      {
          save_target["type"] = "melee_action";
      }
  
      static shared_ptr<action> _load_action(CrawlHashTable &source)
      {
          // pretty minimal: but most actions that I can think of shouldn't need
          // a lot of effort to save. Something to tell you the type, and a
          // single value that is usually more or less an int. Using a hashtable
          // here is future proofing.
  
          // save compat (or bug compat): initialize to an invalid action if we
          // are missing the keys altogether
          if (!source.exists("type") || !source.exists("param"))
              return make_shared<launcher_ammo_action>(-1);
  
          const string &type = source["type"].get_string();
          const int param = source["param"].get_int();
  
          // is there something more elegant than this?
          if (type == "ammo_action")
              return make_shared<ammo_action>(param);
          else if (type == "launcher_ammo_action")
              return make_shared<launcher_ammo_action>(param);
          else if (type == "spell_action")
              return make_shared<spell_action>(static_cast<spell_type>(param));
          else if (type == "ability_action")
              return make_shared<ability_action>(static_cast<ability_type>(param));
          else if (type == "wand_action")
              return make_shared<wand_action>(param);
          else if (type == "misc_action")
              return make_shared<misc_action>(param);
          else if (type == "artefact_evoke_action")
              return make_shared<artefact_evoke_action>(param);
          else if (type == "fumble_action")
              return make_shared<fumble_action>(param);
          else if (type == "melee_action")
              return make_shared<melee_action>();
          else
              return make_shared<action>();
      }
  
      shared_ptr<action> find_action_from_launcher(const item_def *item)
      {
          // Felids have no use for launchers or ammo.
          if (you.has_mutation(MUT_NO_GRASPING))
              return make_shared<ammo_action>(-1);
  
          int slot = -1;
  
          const int cur_launcher_item = you.launcher_action.get()->get_item();
          const int cur_quiver_item = you.quiver_action.get()->get_item();
  
          if (cur_launcher_item >= 0 && you.inv[cur_launcher_item].defined()
              && _item_matches(you.inv[cur_launcher_item], FIRE_LAUNCHER, item, false))
          {
              // prefer to keep the current ammo if not changing weapon types
              slot = cur_launcher_item;
          }
          else if (cur_quiver_item >= 0 && you.inv[cur_quiver_item].defined()
              && _item_matches(you.inv[cur_quiver_item], FIRE_LAUNCHER, item, false))
          {
              // if the right item type is currently present in the main quiver,
              // use that
              // TODO: this logic may be slightly odd with throwing stones quivered?
              slot = cur_quiver_item;
          }
          else
          {
              // otherwise, find the last fired ammo for this launcher. (This is
              // an awful lot of effort to choose correctly between stones and
              // bullets...)
              int last = you.m_quiver_history.get_last_ammo(item);
              if (last >= 0 && you.inv[last].defined()
                  && _item_matches(you.inv[last], FIRE_LAUNCHER, item, false))
              {
                  slot = last;
              }
          }
  
          // Finally, try looking at the fire order.
          if (slot == -1)
          {
              vector<int> order;
              _get_item_fire_order(order, false, item, false);
              if (!order.empty())
                  slot = order[0];
          }
  
          auto result = item && is_range_weapon(*item)
                              ? make_shared<launcher_ammo_action>(slot)
                              : make_shared<ammo_action>(slot);
  
          return result;
      }
  
      action_cycler::action_cycler(shared_ptr<action> init)
          : autoswitched(false), current(init)
      { }
  
      // by default, initialize as invalid, not empty
      action_cycler::action_cycler()
          : action_cycler(make_shared<launcher_ammo_action>(-1))
      { }
  
      void action_cycler::save(const string key) const
      {
          auto &target = you.props[key].get_table();
          get()->save(target);
          CrawlVector &history_vec = you.props[key]["history"].get_vector();
          history_vec.clear();
          for (auto &a : history)
          {
              CrawlStoreValue v;
              a->save(v.get_table());
              history_vec.push_back(v);
          }
      }
  
      void action_cycler::load(const string key)
      {
          if (!you.props.exists(key))
          {
              // some light save compat: if there is no prop, attempt to fill
              // in the quiver from whatever is wielded -- will select launcher
              // ammo if applicable, or throwing.
              set(find_action_from_launcher(you.weapon()));
              if (!get()->is_valid())
                  cycle();
              save(key);
          }
  
          auto &target = you.props[key].get_table();
          set(_load_action(target));
          CrawlVector &history_vec = you.props[key]["history"].get_vector();
          history.clear();
          for (auto &val : history_vec)
          {
              auto a = _load_action(val.get_table());
              history.push_back(move(a));
          }
      }
  
      /**
       * Returns the most recent valid action in the quiver history. May return
       * nullptr if no valid actions are found.
       */
      shared_ptr<action> action_cycler::find_last_valid()
      {
          for (auto it = history.rbegin(); it != history.rend(); ++it)
          {
              if ((*it) && (*it)->is_valid())
                  return *it;
          }
          return nullptr;
      }
  
      /**
       * Set the current action for this action_cycler. If the provided action
       * is null, a valid `action` will be used (guaranteeing that the stored
       * action is never nullptr), corresponding to an empty quiver.
       *
       * @param new_act the action to fill in. nullptr is safe.
       * @return whether the action changed as a result of the call.
       */
      bool action_cycler::set(const shared_ptr<action> new_act, bool _autoswitched)
      {
          auto n = new_act ? new_act : make_shared<action>();
  
          const bool diff = *n != *get();
          auto old = move(current);
          current = move(n);
          autoswitched = _autoswitched;
  
          if (diff)
          {
              // only store the most recently used instance of any action in the
              // history
              history.erase(remove(history.begin(), history.end(), old), history.end());
              // this may push back an invalid action, which is useful for all
              // sorts of reasons
              history.push_back(move(old));
  
              if (history.size() > 6) // 6 chosen arbitrarily
                  history.erase(history.begin());
  
              // side effects, ugh. Update the fire history, and play a sound
              // if needed. TODO: refactor so this is less side-effect-y
              // somehow?
              if (!autoswitched)
              {
                  const int item_slot = get()->get_item();
                  if (item_slot >= 0 && you.inv[item_slot].defined())
                  {
                      const item_def &item = you.inv[item_slot];
  
                      quiver::launcher t = quiver::AMMO_THROW;
                      const item_def *weapon = you.weapon();
                      if (weapon && item.launched_by(*weapon))
                          t = quiver::_get_weapon_ammo_type(weapon);
  
                      you.m_quiver_history.set_quiver(you.inv[item_slot], t);
                  }
              }
  #ifdef USE_SOUND
              parse_sound(CHANGE_QUIVER_SOUND);
  #endif
          }
          set_needs_redraw();
          return diff;
      }
  
      static bool _is_currently_launched_ammo(shared_ptr<action> a)
      {
          auto la = dynamic_pointer_cast<launcher_ammo_action>(a);
          return la && la->is_valid();
      }
  
      launcher_action_cycler::launcher_action_cycler()
          : action_cycler(make_shared<launcher_ammo_action>(-1))
      { }
  
      // only reacts to ammo launched by the current weapon, or empty quiver
      // note that the action may still be valid on its own terms when this
      // returns true...
      bool launcher_action_cycler::is_empty() const
      {
          if (action_cycler::is_empty())
              return true;
          return !_is_currently_launched_ammo(get());
      }
  
      bool launcher_action_cycler::set(const shared_ptr<action> new_act, bool _autoswitched)
      {
          if (new_act
              && (_is_currently_launched_ammo(new_act)
                  || *new_act == action()))
          {
              return action_cycler::set(new_act, _autoswitched);
          }
          else
              set_needs_redraw();
          return false;
      }
  
      void launcher_action_cycler::set_needs_redraw()
      {
          action_cycler::set_needs_redraw();
          you.wield_change = true;
      }
  
      /**
       * Set the action for this object based on another action_cycler. Guarantees
       * not null.
       *
       * @return whether the action changed.
       */
      bool action_cycler::set(const action_cycler &other)
      {
          const bool diff = current != other.current;
          // don't use regular set: avoid all the side effects when importing
          // from another action cycler. (Used in targeting.)
          current = other.get();
          autoswitched = false;
          set_needs_redraw();
          return diff;
      }
  
      /**
       * Get the currently chosen action for this action_cycler.
       * @return a shared_ptr of the action. Guarantees not null.
       */
      shared_ptr<action> action_cycler::get() const
      {
          ASSERT(current); // sanity check: `set` prevents nullptr
  
          // ugh. (n.b. this is ok with const because the pointer itself isn't modified)
          current->default_fire_context = this;
          return current;
      }
  
      bool action_cycler::spell_is_quivered(spell_type s) const
      {
          // validity check??
          return *get() == spell_action(s);
      }
  
  
      bool action_cycler::item_is_quivered(const item_def &item)
      {
          return in_inventory(item) && item_is_quivered(item.link);
      }
  
      bool action_cycler::item_is_quivered(int item_slot) const
      {
          return item_slot >= 0 && item_slot < ENDOFPACK
                                && get()->is_valid()
                                && get()->get_item() == item_slot;
      }
  
      // convert fire_type bitfields to action types
      static void _flag_to_action_types(vector<shared_ptr<action>> &action_types, int flag)
      {
          // what to do with inscribed?
          if (flag & FIRE_LAUNCHER)
              action_types.push_back(make_shared<launcher_ammo_action>(-1));
          if (flag & FIRE_THROWING) // don't differentiate these, handled internal to ammo_action
              action_types.push_back(make_shared<ammo_action>(-1));
          if (flag & FIRE_SPELL)
              action_types.push_back(make_shared<spell_action>(SPELL_NO_SPELL));
          if (flag & FIRE_EVOKABLE)
          {
              action_types.push_back(make_shared<wand_action>(-1));
              action_types.push_back(make_shared<misc_action>(-1));
              action_types.push_back(make_shared<artefact_evoke_action>(-1));
          }
          if (flag & FIRE_ABILITY)
              action_types.push_back(make_shared<ability_action>(ABIL_NON_ABILITY));
      }
  
      static void _check_and_add_actions(vector<shared_ptr<action>> &action_types,
          int f, int flag, int &done)
      {
          if ((f & flag) && !(flag & done))
          {
              _flag_to_action_types(action_types, flag);
              done |= flag;
          }
      }
  
      static shared_ptr<action> _get_next_action_type(shared_ptr<action> a,
          int dir, bool allow_disabled)
      {
          // this all seems a bit messy
  
          // Construct the type order from Options.fire_order:
          vector<shared_ptr<action>> action_types;
          int done = 0x0;
          // Produce an ordering of the broad classes, each corresponding to one
          // or more subtypes of ammo_action. Classes are responsible for ordering
          // within this, though currently only launchers and throwing implement
          // sub-ordering.
          // This doesn't support ordering ammo subtypes interleaved with anything
          // else
          vector<int> flags_to_check =
              { FIRE_LAUNCHER, FIRE_THROWING, FIRE_SPELL, FIRE_EVOKABLE,
                  FIRE_ABILITY };
          for (auto f : Options.fire_order)
              for (auto flag : flags_to_check)
                  _check_and_add_actions(action_types, f, flag, done);
  
          if (dir < 0)
              reverse(action_types.begin(), action_types.end());
  
          size_t i = 0;
          // skip_first: true just in case the current action is valid and we
          // need to move on from it.
          bool skip_first = true;
  
          if (!a)
              skip_first = false; // and i = 0
          else
          {
              // find the type of a
              auto &a_ref = *a;
              for (i = 0; i < action_types.size(); i++)
              {
                  auto rep = action_types[i];
                  if (!rep) // should be impossible
                      continue;
                  // we do it this way in order to silence some clang warnings
                  auto &rep_ref = *rep;
                  if (typeid(rep_ref) == typeid(a_ref))
                      break;
              }
              // unknown action type -- treat it like null. (Handles `action`.)
              if (i >= action_types.size())
              {
                  i = 0;
                  skip_first = false;
              }
          }
  
          // TODO: this logic could probably be reimplemented using only mod
          // math, but using rotate does make the final iteration very clean
          if (skip_first)
              i = (i + 1) % action_types.size();
          rotate(action_types.begin(), action_types.begin() + i, action_types.end());
  
          // now find the first result that is valid in this order. Will cycle
          // back to the current action type if nothing else works.
          for (auto result : action_types)
          {
              auto n = result->find_next(dir, allow_disabled, false);
              if (n && n->is_valid())
                  return n;
          }
  
          // we've gone through everything -- somehow there are no valid actions,
          // not even using a
          return nullptr;
      }
  
      /**
       * Find the next item in the fire order relative to the current item.
       * @param dir negative for reverse, non-negative for forward
       * @param allow_disabled include valid, but disabled items in the fire order
       * @return the resulting action, which may be invalid or an empty quiver.
       *         Guaranteed to be not nullptr
       */
      shared_ptr<action> action_cycler::next(int dir, bool allow_disabled) const
      {
          // first try the next action of the same type
          shared_ptr<action> result = get()->find_next(dir, allow_disabled, false);
          // then, try to find a different action type
          if (!result || !result->is_valid())
              result = _get_next_action_type(get(), dir, allow_disabled);
  
          // no valid actions, return an (invalid) empty-quiver action
          if (!result)
              return make_shared<launcher_ammo_action>(-1);
  
          return result;
      }
  
      /**
       * Cycle to the next action in the fire order. Guaranteed to result in a
       * non-nullptr action being quivered, but it may be an empty quiver or
       * invalid.
       * @param allow_disabled include valid, but disabled items in the fire order
       * @return whether the action changed
       */
      bool action_cycler::cycle(int dir, bool allow_disabled)
      {
          return set(next(dir, allow_disabled));
      }
  
      void action_cycler::on_actions_changed(bool check_autoswitch)
      {
          if (!get()->is_valid())
          {
              auto r = get()->find_replacement();
              if (r && r->is_valid())
                  set(r, true);
          }
          else if (check_autoswitch && autoswitched)
          {
              auto r = ammo_to_action(you.m_quiver_history.get_last_ammo(get()->get_launcher()));
              if (r && r->is_valid())
                  set(r);
          }
          set_needs_redraw();
      }
  
      void action_cycler::set_needs_redraw()
      {
          get()->invalidate();
          // TODO: abstract from `you`
          you.redraw_quiver = true;
      }
  
      /**
       * Given an inventory slot containing ammo, return an appropriate throwing
       * or firing action. If `slot` doesn't contain ammo, returns either an
       * invalid action or a fumble action based on `force`.
       *
       * @param slot the inventory slot number to use.
       * @param force whether to force non-ammo items to be throwable via
       *              fumble throwing.
       * @return the resulting action. May be invalid, or nullptr on an error.
       */
      shared_ptr<action> ammo_to_action(int slot, bool force)
      {
          if (slot < 0 || slot >= ENDOFPACK || !you.inv[slot].defined())
              return nullptr;
  
          // is this legacy(?) check needed? Maybe only relevant for fumble throwing?
          for (int i = EQ_MIN_ARMOUR; i <= EQ_MAX_WORN; i++)
              if (you.equip[i] == slot)
                  return make_shared<ammo_action>(-1);
  
          shared_ptr<action> a = nullptr;
          if (you.weapon() && is_range_weapon(*you.weapon()))
              a = make_shared<launcher_ammo_action>(slot);
          // use ammo as the fallback -- may well end up invalid
          if (!a || !a->is_valid())
              a = make_shared<ammo_action>(slot);
          if (force && (!a || !a->is_valid()))
              return make_shared<fumble_action>(slot);
          return a;
      }
  
      /**
       * Given an inventory slot, return an appropriate action if possible.
       * For a weapon with both an attack and an evokable ability, this
       * will always give the latter. If not possible, this will return an invalid
       * action or nullptr.
       *
       * @param slot the inventory slot number to use.
       * @param force whether to force non-throwable, non-evokable items to be
       *              throwable via fumble throwing.
       * @return the resulting action. May be invalid, or nullptr on an error.
       */
      shared_ptr<action> slot_to_action(int slot, bool force)
      {
          if (slot < 0 || slot >= ENDOFPACK || !you.inv[slot].defined())
              return nullptr;
  
          if (you.inv[slot].base_type == OBJ_WANDS)
              return make_shared<wand_action>(slot);
          else if (you.inv[slot].base_type == OBJ_MISCELLANY)
              return make_shared<misc_action>(slot);
          else if (is_unrandom_artefact(you.inv[slot]))
          {
              auto a = make_shared<artefact_evoke_action>(slot);
              if (a->is_valid() || !force)
                  return a;
              // otherwise, fall back on ammo
          }
  
          // use ammo as the fallback -- may well end up invalid. This means that
          // by this call, it isn't possible to get toss actions for anything
          // handled in the above conditional.
          return ammo_to_action(slot, force);
      }
  
      /**
       * Return an action corresponding to the wielded item. This action will
       * handle melee attacks, reaching attacks, or firing depending on what is
       * wielded. If nothing is wielded, the action will handle unarmed combat.
       * This action is guaranteed to be valid (hopefully).
       *
       * @return the resulting action
       */
      shared_ptr<action> get_primary_action()
      {
          if (you.launcher_action.is_empty())
              return make_shared<melee_action>(); // always valid
          else
              return you.launcher_action.get();
      }
  
      /**
       * Return the quivered action. Simply a convenience wrapper around
       * you.quiver_action.
       *
       * @return the resulting action. May be invalid.
       */
      shared_ptr<action> get_secondary_action()
      {
          auto a = you.quiver_action.get();
          ASSERT(a); // should be redundant
          return a;
      }
  
      /**
       * Return an action corresponding to a spell.
       *
       * @param spell the spell to use
       * @return the resulting action. May be invalid.
       */
      shared_ptr<action> spell_to_action(spell_type spell)
      {
          // could just expose spell_action outside this file?
          return make_shared<spell_action>(spell);
      }
  
      /**
       * Return an action corresponding to an ability.
       *
       * @abil the abilty to use
       * @return the resulting action. May be invalid.
       */
      shared_ptr<action> ability_to_action(ability_type abil)
      {
          // could just expose spell_action outside this file?
          return make_shared<ability_action>(abil);
      }
  
      /**
       * Set the quiver from an inventory slot. May result in an invalid action,
       * but guarantees a non-nullptr action.
       *
       * @param slot an inventory slot to use. An invalid slot (e.g. -1) results
       *             in an invalid action.
       */
      bool action_cycler::set_from_slot(int slot)
      {
          return set(slot_to_action(slot));
      }
  
      /**
       * Clear the quiver. This results in a valid, but inert action filling the
       * quiver.
       * @return whether the action changed
       */
      bool action_cycler::clear()
      {
          return(set(make_shared<action>()));
      }
  
      // note for editing this: Menu::action is defined and will take precedence
      // over quiver::action unless the quiver namespace is explicit.
      class ActionSelectMenu : public Menu
      {
      public:
          ActionSelectMenu(action_cycler &_quiver, bool _allow_empty)
              : Menu(MF_SINGLESELECT | MF_ALLOW_FORMATTING),
                cur_quiver(_quiver), allow_empty(_allow_empty),
                any_spells(you.spell_no),
                any_abilities(your_talents(true, true).size() > 0),
                // regular species can force-quiver any (non-equipped) item, but
                // felids have a more limited selection, so we need to directly
                // calculate it.
                any_items(you.has_mutation(MUT_NO_GRASPING)
                  ? any_items_of_type(OSEL_QUIVER_ACTION_FORCE)
                  : inv_count() > 0)
          {
              set_tag("actions");
              action_cycle = Menu::CYCLE_TOGGLE;
              menu_action  = Menu::ACT_EXECUTE;
          }
  
          action_cycler &cur_quiver;
          bool allow_empty;
  
          bool set_to_quiver(shared_ptr<quiver::action> s)
          {
              if (s && s->is_valid()
                  && (allow_empty || *s != quiver::action()))
              {
                  if (!_quiver_inscription_ok(s->get_item()))
                  {
                      const string prompt = make_stringf("Really quiver %s?",
                          you.inv[s->get_item()].name(DESC_INVENTORY).c_str());
                      if (!yesno(prompt.c_str(), true, 'n'))
                          return false;
                  }
                  cur_quiver.set(s);
                  // a bit hacky:
                  if (&cur_quiver == &you.quiver_action)
                      you.launcher_action.set(s);
                  return true;
              }
              return false;
          }
  
          bool pointless()
          {
              return !(any_spells || any_abilities || any_items);
          }
  
      protected:
          bool _choose_from_inv()
          {
              int slot = prompt_invent_item(allow_empty
                                              ? "Quiver which item? (- for none, * to toggle full inventory)"
                                              : "Quiver which item? (* to toggle full inventory)",
                                            menu_type::invlist, OSEL_QUIVER_ACTION,
                                            OPER_QUIVER, invprompt_flag::hide_known, '-');
  
              if (prompt_failed(slot))
                  return true;
  
              if (slot == PROMPT_GOT_SPECIAL)  // '-' or empty quiver
              {
                  if (!allow_empty)
                      return true;
  
                  cur_quiver.clear();
                  return false;
              }
  
              // At this point, an item has been selected, which will lead to
              // either an invalid or valid action. Either way, exit, so that
              // the message can  be seen.
              // TODO: in failure it would be better to set the more with an
              // error instead of exiting the menu
              set_to_quiver(slot_to_action(slot, true));
              return false;
          }
  
          bool _choose_from_abilities()
          {
              // currently doesn't show usk disabled abilities even though they
              // are shown for Q, is this confusing?
              vector<talent> talents = your_talents(false, true);
              // TODO: better handling for no abilities?
              int selected = choose_ability_menu(talents);
  
              return selected >= 0 && selected < static_cast<int>(talents.size())
                  && !set_to_quiver(make_shared<ability_action>(talents[selected].which));
          }
  
          bool process_key(int key) override
          {
              // TODO: some kind of view action option?
              if (allow_empty && key == '-')
              {
                  set_to_quiver(make_shared<quiver::action>());
                  // TODO maybe drop this messaging?
                  mprf("Clearing quiver.");
                  return false;
              }
              else if ((key == '*' || key == '%') && any_items)
                  return _choose_from_inv();
              else if (key == '&' && any_spells)
              {
                  const int skey = list_spells(false, false, false,
                                                      "Select a spell to quiver");
                  if (skey == 0)
                      return true;
                  if (isalpha(skey))
                  {
                      auto s = make_shared<spell_action>(
                              static_cast<spell_type>(get_spell_by_letter(skey)));
                      return !set_to_quiver(s);
                  }
                  return false;
              }
              else if (key == '^' && any_abilities)
                  return _choose_from_abilities();
              return Menu::process_key(key);
          }
  
          virtual formatted_string calc_title() override
          {
              string s = "Quiver which action? ";
              vector<string> extra_cmds;
  
              if (allow_empty)
                  extra_cmds.push_back("<w>-</w>: none");
              if (any_items)
                  extra_cmds.push_back("<w>*/%</w>: inventory");
              if (any_spells)
                  extra_cmds.push_back("<w>&</w>: spells");
              if (any_abilities)
                  extra_cmds.push_back("<w>^</w>: abilities");
              if (extra_cmds.size())
                  s += "(" + join_strings(extra_cmds.begin(), extra_cmds.end(), ", ") + ")";
              return formatted_string::parse_string(s);
          }
  
          bool any_spells;
          bool any_abilities;
          bool any_items;
      };
  
      /**
       * Do interactive targeting for the currently selected action. Allows the
       * player to change actions.
       */
      void action_cycler::target()
      {
          // This is a somewhat indirect interface that allows cycling between
          // arbitrary code paths that call a direction chooser. Because the
          // setup for direction choosers is so varied and complicated, we can't
          // implement the cycling internal to a direction chooser interface
          // (at least without a major refactor), so this UI takes the strategy
          // of rebuilding the direction chooser each time, but making it look
          // seamless from a user perspective. Each call to `do_target` leads to
          // the specific custom targeting code path for a particular action,
          // which then builds the direction chooser. The three CMD_TARGET...
          // commands below are not handled in a direction chooser, but rather
          // passed back via the `dist`. In addition, the `dist` object will
          // pass down a pointer to this that the direction chooser will use for
          // some custom prompt stuff. Obviously, it would better if this
          // message passing happened more directly, and in the long run perhaps
          // it would be possible to gradually refactor the various code paths
          // involved to support that, but for now that project is too
          // impractical, because each code path (except throwing) is called from
          // many places.
          shared_ptr<action> initial = get();
          clear_messages(); // this kind of looks better as a force clear, but
                            // for consistency with direct targeting commands,
                            // I will leave it as non-force
          msgwin_temporary_mode tmp;
          bool force_restore_initial;
  
          command_type what_happened = CMD_NO_CMD;
          do
          {
              flush_prev_message();
              msgwin_clear_temporary();
              force_restore_initial = false;
              auto a = do_target();
  
              // the point of this: if you cycle to or select some item, fire it,
              // and it becomes invalid (e.g. by using up ammo), this will try to
              // restore the initial quiver value rather than ending up with the
              // next in fire order item after the selected action
              if (!a || !a->is_valid())
                  force_restore_initial = true;
  
              what_happened =
                  a && targeter_handles_key(
                                  static_cast<command_type>(a->target.cmd_result))
                          ? static_cast<command_type>(a->target.cmd_result)
                          : CMD_NO_CMD;
  
              switch (what_happened)
              {
              case CMD_TARGET_CYCLE_QUIVER_FORWARD:
                  cycle(1, false);
                  break;
              case CMD_TARGET_CYCLE_QUIVER_BACKWARD:
                  cycle(-1, false);
                  break;
              case CMD_TARGET_SELECT_ACTION:
                  // choosing a disabled action here may exit the prompt
                  // depending on the spell, it's a bit inconsistent.
                  choose(*this, false);
                  break;
              default:
                  what_happened = CMD_NO_CMD; // shouldn't happen
                  // fallthrough
              case CMD_FIRE:
              case CMD_NO_CMD:
                  break;
              }
              if (!crawl_state.is_replaying_keys())
                  flush_input_buffer(FLUSH_BEFORE_COMMAND);
              // right now this resets targeting on cycle; would it be better to
              // save it?
          } while (what_happened != CMD_NO_CMD && what_happened != CMD_FIRE);
  
          // Restore the quiver on cancel -- backwards compatible behavior.
          // Is it really the best behavior?
          if ((what_happened == CMD_NO_CMD || force_restore_initial)
              && initial && initial->is_valid())
          {
              set(initial);
          }
      }
  
      static vector<shared_ptr<action>> _menu_quiver_order()
      {
          vector<shared_ptr<action>> actions;
          // TODO: this is kind of ugly
          auto tmp = ammo_action(-1).get_menu_fire_order(true);
          actions.insert(actions.end(), tmp.begin(), tmp.end());
          tmp = launcher_ammo_action(-1).get_menu_fire_order(true);
          actions.insert(actions.end(), tmp.begin(), tmp.end());
          tmp = wand_action(-1).get_fire_order(true, true);
          actions.insert(actions.end(), tmp.begin(), tmp.end());
          tmp = misc_action(-1).get_fire_order(true, true);
          actions.insert(actions.end(), tmp.begin(), tmp.end());
          tmp = artefact_evoke_action(-1).get_fire_order(true, true);
          actions.insert(actions.end(), tmp.begin(), tmp.end());
          tmp = spell_action(SPELL_NO_SPELL).get_fire_order();
          actions.insert(actions.end(), tmp.begin(), tmp.end());
          tmp = ability_action(ABIL_NON_ABILITY).get_fire_order();
          actions.insert(actions.end(), tmp.begin(), tmp.end());
          return actions;
      }
  
      int menu_size()
      {
          return _menu_quiver_order().size();
      }
  
      /**
       * Presents an interface for the player to choose an action to quiver from
       * a list of options.
       * @param cur_quiver a quiver to use for context
       * @param allow_empty whether to allow the player to set the empty quiver
       */
      void choose(action_cycler &cur_quiver, bool allow_empty)
      {
          // should be action_cycler method?
          // TODO: dividers or subtitles for each category?
          ActionSelectMenu menu(cur_quiver, allow_empty);
  
          auto actions = _menu_quiver_order();
  
          if (actions.size() == 0 && menu.pointless())
          {
              mpr("You have nothing to quiver.");
              return;
          }
  
          menu.set_title(new MenuEntry("", MEL_TITLE));
  
          menu_letter hotkey;
          if (actions.size() == 0)
              menu.set_more(formatted_string::parse_string("<lightred>No regular actions available to quiver.</lightred>"));
  
          for (const auto &a : actions)
          {
              if (!a || !a->is_valid())
                  continue;
              string action_desc = a->quiver_description();
              if (*you.launcher_action.get() == *a)
                  action_desc += " (quivered ammo)";
              else if (you.quiver_action.item_is_quivered(a->get_item()))
                  action_desc += " (quivered)";
              MenuEntry *me = new MenuEntry(action_desc,
                                                  MEL_ITEM, 1,
                                                  (int) hotkey);
              // TODO: is there a way to show formatting in menu items?
              me->colour = a->quiver_color();
              me->data = (void *) &a; // pointer to vector element - don't change the vector!
  #ifdef USE_TILE
              for (auto t : a->get_tiles())
                  me->add_tile(t);
  #endif
              menu.add_entry(me);
              hotkey++;
          }
  
          menu.on_single_selection = [&menu](const MenuEntry& item)
          {
              const shared_ptr<action> *a = static_cast<shared_ptr<action> *>(item.data);
              return !menu.set_to_quiver(*a);
          };
          menu.show();
      }
  
      // this class is largely legacy code -- can it be done away with?
      // or refactored to use actions.
      // TODO: auto switch to last action when swapping away from a launcher --
      // right now it goes to an ammo only in that case.
      ammo_history::ammo_history()
      {
          COMPILE_CHECK(ARRAYSZ(m_last_used_of_type) == quiver::NUM_LAUNCHERS);
      }
  
      int ammo_history::get_last_ammo(const item_def *launcher) const
      {
          return get_last_ammo(quiver::_get_weapon_ammo_type(launcher));
      }
  
      int ammo_history::get_last_ammo(quiver::launcher type) const
      {
          const int slot = _get_pack_slot(m_last_used_of_type[type]);
          ASSERT(slot < ENDOFPACK && (slot == -1 || you.inv[slot].defined()));
          return slot;
      }
  
      void ammo_history::set_quiver(const item_def &item, quiver::launcher ammo_type)
      {
          m_last_used_of_type[ammo_type] = item;
          m_last_used_of_type[ammo_type].quantity = 1;
          you.redraw_quiver = true;
      }
  
      // Notification that item was fired
      void ammo_history::on_item_fired(const item_def& item, bool explicitly_chosen)
      {
          if (!explicitly_chosen)
          {
              // If the item was not actively chosen, i.e. just automatically
              // passed into the quiver, don't add it to the quiver history for
              // this ammo type. This lets us revert back to an actively chosen
              // ammo on pickup.
              you.redraw_quiver = true;
              return;
          }
          // If item matches the launcher, put it in that launcher's last-used item.
          // Otherwise, it goes into last hand-thrown item.
  
          const item_def *weapon = you.weapon();
  
          if (weapon && item.launched_by(*weapon))
          {
              const quiver::launcher t = quiver::_get_weapon_ammo_type(weapon);
              m_last_used_of_type[t] = item; // makes a copy
              m_last_used_of_type[t].quantity = 1;    // 0 makes it invalid :(
          }
          else
          {
              const launch_retval projected = is_launched(&you, you.weapon(),
                                                          item);
  
              // Don't do anything if this item is not really fit for throwing.
              if (projected == launch_retval::FUMBLED)
                  return;
  
              m_last_used_of_type[quiver::AMMO_THROW] = item;
              m_last_used_of_type[quiver::AMMO_THROW].quantity = 1;
          }
  
          you.redraw_quiver = true;
      }
  
      // ----------------------------------------------------------------------
      // Save/load
      // ----------------------------------------------------------------------
  
      // legacy marshalling code, still semi-used
      static const short QUIVER_COOKIE = short(0xb015);
      void ammo_history::save(writer& outf) const
      {
          marshallShort(outf, QUIVER_COOKIE);
  
          marshallItem(outf, item_def()); // was: m_last_weapon
          marshallInt(outf, 0); // was: m_last_used_type
          marshallInt(outf, ARRAYSZ(m_last_used_of_type));
  
          for (unsigned int i = 0; i < ARRAYSZ(m_last_used_of_type); i++)
              marshallItem(outf, m_last_used_of_type[i]);
      }
  
      // legacy unmarshalling code, still semi-used
      void ammo_history::load(reader& inf)
      {
          // warning: this is called in the unmarshalling sequence before the
          // inventory is actually in place
          const short cooky = unmarshallShort(inf);
          ASSERT(cooky == QUIVER_COOKIE); (void)cooky;
  
          auto dummy = item_def();
          unmarshallItem(inf, dummy); // was: m_last_weapon
          unmarshallInt(inf); // was: m_last_used_type;
  
          const unsigned int count = unmarshallInt(inf);
          ASSERT(count <= ARRAYSZ(m_last_used_of_type));
  
          for (unsigned int i = 0; i < count; i++)
              unmarshallItem(inf, m_last_used_of_type[i]);
      }
  
      void on_actions_changed(bool check_autoswitch)
      {
          you.quiver_action.on_actions_changed(check_autoswitch);
          you.launcher_action.on_actions_changed(check_autoswitch);
      }
  
      void set_needs_redraw()
      {
          you.quiver_action.set_needs_redraw();
          you.launcher_action.set_needs_redraw();
      }
  
      // Called when the player has switched weapons
      // Some cases of interest:
      // * player picks up a new weapon and wields it
      // * player is swapping between a launcher and melee
      void on_weapon_changed()
      {
          const item_def* weapon = you.weapon();
          you.launcher_action.set(quiver::find_action_from_launcher(weapon));
  
          // if the new weapon is a launcher with ammo, and the autoquiver option
          // is set, quiver that ammo in the main quiver
          if (!you.launcher_action.is_empty() && Options.launcher_autoquiver)
              you.quiver_action.set(you.launcher_action.get());
          else if (weapon && is_unrandom_artefact(*weapon)
                                              && Options.launcher_autoquiver)
          {
              // apply similar logic to the (few) evokable weapons
              auto a = make_shared<artefact_evoke_action>(weapon->link);
              if (a->is_valid())
                  you.quiver_action.set(a);
          }
  
  
          // If that failed, see if there's anything valid in the quiver history.
          // This is aimed at using the quiver history when switching away from
          // a weapon.
          if (!you.quiver_action.get()->is_valid())
          {
              auto a = you.quiver_action.find_last_valid();
              if (a)
                  you.quiver_action.set(a);
          }
      }
  }
  
  
  // ----------------------------------------------------------------------
  // Helpers
  // ----------------------------------------------------------------------
  
  // Helper for ammo _get_fire_order. Ammo only.
  // Types may actually contain more than one fire_type.
  static bool _item_matches(const item_def &item, fire_type types,
                            const item_def* launcher, bool manual)
  {
      // TODO: refactor into something less annoying? This is all a semi-duplicate
      // of is_valid code...
      ASSERT(item.defined());
  
      if (types & FIRE_INSCRIBED)
          if (item.inscription.find(manual ? "+F" : "+f", 0) != string::npos)
              return true;
  
      if (item.base_type != OBJ_MISSILES)
          return false;
  
      if ((types & FIRE_STONE) && item.sub_type == MI_STONE)
          return true;
      if ((types & FIRE_JAVELIN) && item.sub_type == MI_JAVELIN)
          return true;
      if ((types & FIRE_ROCK) && item.sub_type == MI_LARGE_ROCK)
          return true;
      if ((types & FIRE_NET) && item.sub_type == MI_THROWING_NET)
          return true;
      if ((types & FIRE_BOOMERANG) && item.sub_type == MI_BOOMERANG)
          return true;
      if ((types & FIRE_DART) && item.sub_type == MI_DART)
          return true;
  
      if (types & FIRE_LAUNCHER)
      {
          if (launcher && item.launched_by(*launcher))
              return true;
      }
  
      return false;
  }
  
  // Returns inv slot that contains an item that looks like item,
  // or -1 if not in inv.
  static int _get_pack_slot(const item_def& item)
  {
      if (!item.defined())
          return -1;
  
      if (in_inventory(item) && _items_similar(item, you.inv[item.link], false))
          return item.link;
  
      // First try to find the exact same item.
      for (int i = 0; i < ENDOFPACK; i++)
      {
          const item_def &inv_item = you.inv[i];
          if (inv_item.quantity && _items_similar(item, inv_item, false))
              return i;
      }
  
      // If that fails, try to find an item sufficiently similar.
      for (int i = 0; i < ENDOFPACK; i++)
      {
          const item_def &inv_item = you.inv[i];
          if (inv_item.quantity && _items_similar(item, inv_item, true))
          {
              // =f prevents item from being autoquivered.
              if (quiver::_fireorder_inscription_ok(i, false) == MB_FALSE)
                  continue;
  
              return i;
          }
      }
  
      return -1;
  }
  
  static bool _items_similar(const item_def& a, const item_def& b, bool force)
  {
      return items_similar(a, b) && (force || a.slot == b.slot);
  }
  