text: /**
   * @file
   * @brief Throwing and launching stuff.
  **/
  
  #include "AppHdr.h"
  
  #include "throw.h"
  
  #include <cmath>
  #include <sstream>
  
  #include "art-enum.h"
  #include "artefact.h"
  #include "chardump.h"
  #include "command.h"
  #include "coordit.h"
  #include "directn.h"
  #include "english.h"
  #include "env.h"
  #include "exercise.h"
  #include "fight.h"
  #include "god-conduct.h"
  #include "god-passive.h" // passive_t::shadow_attacks
  #include "hints.h"
  #include "invent.h"
  #include "item-prop.h"
  #include "item-status-flag-type.h"
  #include "items.h"
  #include "item-use.h"
  #include "macro.h"
  #include "message.h"
  #include "mutation.h"
  #include "mon-behv.h"
  #include "output.h"
  #include "prompt.h"
  #include "religion.h"
  #include "rot.h"
  #include "shout.h"
  #include "showsymb.h"
  #include "skills.h"
  #include "sound.h"
  #include "state.h"
  #include "stringutil.h"
  #include "terrain.h"
  #include "transform.h"
  #include "traps.h"
  #include "viewchar.h"
  #include "view.h"
  
  static int  _fire_prompt_for_item();
  static bool _fire_validate_item(int selected, string& err);
  static int  _get_dart_chance(const int hd);
  
  bool is_penetrating_attack(const actor& attacker, const item_def* weapon,
                             const item_def& projectile)
  {
      return is_launched(&attacker, weapon, projectile) != launch_retval::FUMBLED
              && projectile.base_type == OBJ_MISSILES
              && projectile.sub_type == MI_JAVELIN
             || weapon
                && is_launched(&attacker, weapon, projectile) == launch_retval::LAUNCHED
                && (get_weapon_brand(*weapon) == SPWPN_PENETRATION
                    || is_unrandom_artefact(*weapon, UNRAND_STORM_BOW));
  }
  
  bool item_is_quivered(const item_def &item)
  {
      return in_inventory(item) && item.link == you.m_quiver.get_fire_item();
  }
  
  int get_next_fire_item(int current, int direction)
  {
      vector<int> fire_order;
      you.m_quiver.get_fire_order(fire_order, true);
  
      if (fire_order.empty())
          return -1;
  
      int next = direction > 0 ? 0 : -1;
      for (unsigned i = 0; i < fire_order.size(); i++)
      {
          if (fire_order[i] == current)
          {
              next = i + direction;
              break;
          }
      }
  
      next = (next + fire_order.size()) % fire_order.size();
      return fire_order[next];
  }
  
  class fire_target_behaviour : public targeting_behaviour
  {
  public:
      fire_target_behaviour()
          : chosen_ammo(false),
            selected_from_inventory(false),
            need_redraw(false)
      {
          m_slot = you.m_quiver.get_fire_item(&m_noitem_reason);
          set_prompt();
      }
  
      // targeting_behaviour API
      virtual command_type get_command(int key = -1) override;
      virtual bool should_redraw() const override { return need_redraw; }
      virtual void clear_redraw()        override { need_redraw = false; }
      virtual void update_top_prompt(string* p_top_prompt) override;
      virtual vector<string> get_monster_desc(const monster_info& mi) override;
  
  public:
      const item_def* active_item() const;
      // FIXME: these should be privatized and given accessors.
      int m_slot;
      bool chosen_ammo;
  
  private:
      void set_prompt();
      void cycle_fire_item(bool forward);
      void pick_fire_item_from_inventory();
      void display_help();
  
      string prompt;
      string m_noitem_reason;
      string internal_prompt;
      bool selected_from_inventory;
      bool need_redraw;
  };
  
  void fire_target_behaviour::update_top_prompt(string* p_top_prompt)
  {
      *p_top_prompt = internal_prompt;
  }
  
  const item_def* fire_target_behaviour::active_item() const
  {
      if (m_slot == -1)
          return nullptr;
      else
          return &you.inv[m_slot];
  }
  
  void fire_target_behaviour::set_prompt()
  {
      string old_prompt = internal_prompt; // Keep for comparison at the end.
      internal_prompt.clear();
  
      // Figure out if we have anything else to cycle to.
      const int next_item = get_next_fire_item(m_slot, +1);
      const bool no_other_items = (next_item == -1 || next_item == m_slot);
  
      ostringstream msg;
  
      // Build the action.
      if (!active_item())
          msg << "Firing ";
      else
      {
          const launch_retval projected = is_launched(&you, you.weapon(),
                                                      *active_item());
          switch (projected)
          {
          case launch_retval::FUMBLED:  msg << "Tossing away "; break;
          case launch_retval::LAUNCHED: msg << "Firing ";             break;
          case launch_retval::THROWN:   msg << "Throwing ";           break;
          case launch_retval::BUGGY:    msg << "Bugging "; break;
          }
      }
  
      // And a key hint.
      string key_hint = no_other_items
                          ? "(<w>%</w> - inventory) "
                          : "(<w>%</w> - inventory. <w>%</w>/<w>%</w> - cycle) ";
      insert_commands(key_hint,
                      { CMD_DISPLAY_INVENTORY,
                        CMD_CYCLE_QUIVER_BACKWARD,
                        CMD_CYCLE_QUIVER_FORWARD });
      msg << key_hint;
  
      // Describe the selected item for firing.
      if (!active_item())
          msg << "<red>" << m_noitem_reason << "</red>";
      else
      {
          const char* colour = (selected_from_inventory ? "lightgrey" : "w");
          msg << "<" << colour << ">"
              << active_item()->name(DESC_INVENTORY_EQUIP)
              << "</" << colour << ">";
      }
  
      // Write it out.
      internal_prompt += msg.str();
  
      // Never unset need_redraw here, because we might have cleared the
      // screen or something else which demands a redraw.
      if (internal_prompt != old_prompt)
          need_redraw = true;
  }
  
  // Cycle to the next (forward == true) or previous (forward == false)
  // fire item.
  void fire_target_behaviour::cycle_fire_item(bool forward)
  {
      const int next = get_next_fire_item(m_slot, forward ? 1 : -1);
      if (next != m_slot && next != -1)
      {
          m_slot = next;
          selected_from_inventory = false;
          chosen_ammo = true;
      }
      set_prompt();
  }
  
  void fire_target_behaviour::pick_fire_item_from_inventory()
  {
      need_redraw = true;
      string err;
      const int selected = _fire_prompt_for_item();
      if (selected >= 0 && _fire_validate_item(selected, err))
      {
          m_slot = selected;
          selected_from_inventory = true;
          chosen_ammo = true;
      }
      else if (!err.empty())
      {
          mpr(err);
          more();
      }
      set_prompt();
  }
  
  void fire_target_behaviour::display_help()
  {
      show_targeting_help();
      redraw_screen();
      need_redraw = true;
      set_prompt();
  }
  
  command_type fire_target_behaviour::get_command(int key)
  {
      if (key == -1)
          key = get_key();
  
      if (key == CMD_TARGET_CANCEL)
          chosen_ammo = false;
      else if (!(-key > CMD_NO_CMD && -key < CMD_MIN_SYNTHETIC)
                      || context_for_command((command_type) -key) == KMC_DEFAULT)
      {
          // that check is really hacky, but if we don't do it mouse targeting
          // produces all sorts of errors in the call below because the context
          // isn't right; really we are in a targeting context now, and the use of
          // KMC_DEFAULT below is also a hack. This whole context system could use
          // some serious refactoring if commands are really supposed to work in
          // multiple contexts.
          switch (key_to_command(key, KMC_DEFAULT))
          {
          case CMD_CYCLE_QUIVER_BACKWARD: cycle_fire_item(true);  return CMD_NO_CMD;
          case CMD_CYCLE_QUIVER_FORWARD: cycle_fire_item(false); return CMD_NO_CMD;
          case CMD_DISPLAY_INVENTORY: pick_fire_item_from_inventory(); return CMD_NO_CMD;
          case CMD_DISPLAY_COMMANDS: display_help(); return CMD_NO_CMD;
          default: break;
          }
      }
  
      return targeting_behaviour::get_command(key);
  }
  
  vector<string> fire_target_behaviour::get_monster_desc(const monster_info& mi)
  {
      vector<string> descs;
      if (const item_def* item = active_item())
      {
          if (get_ammo_brand(*item) == SPMSL_SILVER && mi.is(MB_CHAOTIC))
              descs.emplace_back("chaotic");
          if (item->is_type(OBJ_MISSILES, MI_THROWING_NET)
              && (mi.body_size() >= SIZE_GIANT
                  || mons_class_is_stationary(mi.type)
                  || mons_class_flag(mi.type, M_INSUBSTANTIAL)))
          {
              descs.emplace_back("immune to nets");
          }
  
          // Display the chance for a dart of para/confuse/sleep/frenzy
          // to affect monster
          if (item->is_type(OBJ_MISSILES, MI_DART))
          {
              special_missile_type brand = get_ammo_brand(*item);
              if (brand == SPMSL_FRENZY || brand == SPMSL_BLINDING)
              {
                  int chance = _get_dart_chance(mi.hd);
                  bool immune = false;
                  if (mi.holi & (MH_UNDEAD | MH_NONLIVING))
                      immune = true;
  
                  string verb = brand == SPMSL_FRENZY ? "frenzy" : "blind";
  
                  string chance_string = immune ? "immune to darts" :
                                         make_stringf("chance to %s on hit: %d%%",
                                                      verb.c_str(), chance);
                  descs.emplace_back(chance_string);
              }
          }
      }
      return descs;
  }
  
  /**
   *  Chance for a dart fired by the player to affect a monster of a particular
   *  hit dice, given the player's throwing skill.
   *
   *    @param hd     The monster's hit dice.
   *    @return       The percentage chance for the player to affect the monster,
   *                  rounded down.
   *
   *  This chance is rolled in ranged_attack::dart_check using this formula for
   *  success:
   *      if hd < 15, fixed 3% chance to succeed regardless of roll
   *      else, or if the 3% chance fails,
   *            succeed if 2 + random2(4 + (2/3)*(throwing + stealth) ) >= hd
   */
  static int _get_dart_chance(const int hd)
  {
      const int pow = (2 * (you.skill_rdiv(SK_THROWING)
                            + you.skill_rdiv(SK_STEALTH))) / 3;
  
      int chance = 10000 - 10000 * (hd - 2) / (4 + pow);
      chance = min(max(chance, 0), 10000);
      if (hd < 15)
      {
          chance *= 97;
          chance /= 100;
          chance += 300; // 3% chance to ignore HD and affect enemy anyway
      }
      return chance / 100;
  }
  
  /**
   *  Validate any item selected to be fired, and choose a target to fire at.
   *
   *  @param slot         The slot the item to be fired is in, or -1 if
   *                      an item has not yet been chosen.
   *  @param target       An empty variable of the dist class to store the
   *                      target information in.
   *  @param teleport     Does the player have portal projectile active?
   *  @param fired_normally  True if the projectile was fired through the f
   *                      command, false if fired through the F command.
   *                      If true, if the player changes their mind about which
   *                      item to fire, update the quivered item accordingly.
   *  @return             Whether the item validation and target selection
   *                      was successful.
   */
  static bool _fire_choose_item_and_target(int& slot, dist& target,
                                           bool teleport = false,
                                           bool fired_normally = true)
  {
      fire_target_behaviour beh;
      const bool was_chosen = (slot != -1);
  
      if (was_chosen)
      {
          string warn;
          if (!_fire_validate_item(slot, warn))
          {
              mpr(warn);
              return false;
          }
          // Force item to be the prechosen one.
          beh.m_slot = slot;
      }
  
      direction_chooser_args args;
      args.mode = TARG_HOSTILE;
      args.needs_path = !teleport;
      args.behaviour = &beh;
  
      direction(target, args);
  
      if (!beh.active_item())
      {
          canned_msg(MSG_OK);
          return false;
      }
      if (!target.isValid)
      {
          if (target.isCancel)
              canned_msg(MSG_OK);
          return false;
      }
      if (teleport && cell_is_solid(target.target))
      {
          const char *feat = feat_type_name(grd(target.target));
          mprf("There is %s there.", article_a(feat).c_str());
          return false;
      }
  
      if (fired_normally)
      {
          you.m_quiver.on_item_fired(*beh.active_item(), beh.chosen_ammo);
          you.redraw_quiver = true;
      }
      slot = beh.m_slot;
  
      return true;
  }
  
  // Bring up an inventory screen and have user choose an item.
  // Returns an item slot, or -1 on abort/failure
  // On failure, returns error text, if any.
  static int _fire_prompt_for_item()
  {
      if (inv_count() < 1)
          return -1;
  
      int slot = prompt_invent_item("Fire/throw which item? (* to show all)",
                                     menu_type::invlist,
                                     OSEL_THROWABLE, OPER_FIRE);
  
      if (slot == PROMPT_ABORT || slot == PROMPT_NOTHING)
          return -1;
  
      return slot;
  }
  
  // Returns false and err text if this item can't be fired.
  static bool _fire_validate_item(int slot, string &err)
  {
      if (slot == you.equip[EQ_WEAPON]
          && is_weapon(you.inv[slot])
          && you.inv[slot].cursed())
      {
          err = "That weapon is stuck to your " + you.hand_name(false) + "!";
          return false;
      }
      else if (item_is_worn(slot))
      {
          err = "You are wearing that object!";
          return false;
      }
      return true;
  }
  
  // Returns true if warning is given.
  bool fire_warn_if_impossible(bool silent)
  {
      if (you.species == SP_FELID || you.species == SP_BUTTERFLY)
      {
          if (!silent)
              mpr("You can't grasp things well enough to throw them.");
          return true;
      }
  
      // If you can't wield it, you can't throw it.
      if (!form_can_wield())
      {
          if (!silent)
              canned_msg(MSG_PRESENT_FORM);
          return true;
      }
  
      if (you.attribute[ATTR_HELD])
      {
          const item_def *weapon = you.weapon();
          if (!weapon || !is_range_weapon(*weapon))
          {
              if (!silent)
                  mprf("You cannot throw anything while %s.", held_status());
              return true;
          }
          else
  #if TAG_MAJOR_VERSION == 34
               if (weapon->sub_type != WPN_BLOWGUN)
  #endif
          {
              if (!silent)
              {
                  mprf("You cannot shoot with your %s while %s.",
                       weapon->name(DESC_BASENAME).c_str(), held_status());
              }
              return true;
          }
          // Else shooting is possible.
      }
      if (you.berserk())
      {
          if (!silent)
              canned_msg(MSG_TOO_BERSERK);
          return true;
      }
      return false;
  }
  
  static bool _autoswitch_to_ranged()
  {
      int item_slot;
      if (you.equip[EQ_WEAPON] == letter_to_index('a'))
          item_slot = letter_to_index('b');
      else if (you.equip[EQ_WEAPON] == letter_to_index('b'))
          item_slot = letter_to_index('a');
      else
          return false;
  
      const item_def& launcher = you.inv[item_slot];
      if (!is_range_weapon(launcher))
          return false;
      if (none_of(you.inv.begin(), you.inv.end(), [&launcher](const item_def& it)
                  { return it.launched_by(launcher);}))
      {
          return false;
      }
  
      if (!wield_weapon(true, item_slot))
          return false;
  
      you.turn_is_over = true;
      //XXX Hacky. Should use a delay instead.
      macro_buf_add(command_to_key(CMD_FIRE));
      return true;
  }
  
  int get_ammo_to_shoot(int item, dist &target, bool teleport)
  {
      if (fire_warn_if_impossible())
      {
          flush_input_buffer(FLUSH_ON_FAILURE);
          return -1;
      }
  
      if (Options.auto_switch && you.m_quiver.get_fire_item() == -1
         && _autoswitch_to_ranged())
      {
          return -1;
      }
  
      if (!_fire_choose_item_and_target(item, target, teleport))
          return -1;
  
      string warn;
      if (!_fire_validate_item(item, warn))
      {
          mpr(warn);
          return -1;
      }
      return item;
  }
  
  // Portal Projectile requires MP per shot.
  bool is_pproj_active()
  {
      return !you.confused() && you.duration[DUR_PORTAL_PROJECTILE]
             && enough_mp(1, true, false);
  }
  
  // If item == -1, prompt the user.
  // If item passed, it will be put into the quiver.
  void fire_thing(int item)
  {
  #ifdef USE_SOUND
      parse_sound(FIRE_PROMPT_SOUND);
  #endif
  
      dist target;
      item = get_ammo_to_shoot(item, target, is_pproj_active());
      if (item == -1)
          return;
  
      if (check_warning_inscriptions(you.inv[item], OPER_FIRE)
          && (!you.weapon()
              || is_launched(&you, you.weapon(), you.inv[item]) != launch_retval::LAUNCHED
              || check_warning_inscriptions(*you.weapon(), OPER_FIRE)))
      {
          bolt beam;
          throw_it(beam, item, &target);
      }
  }
  
  // Basically does what throwing used to do: throw an item without changing
  // the quiver.
  void throw_item_no_quiver()
  {
      if (fire_warn_if_impossible())
      {
          flush_input_buffer(FLUSH_ON_FAILURE);
          return;
      }
  
      if (inv_count() < 1)
      {
          canned_msg(MSG_NOTHING_CARRIED);
          return;
      }
  
      string warn;
      int slot = _fire_prompt_for_item();
  
      if (slot == -1)
      {
          canned_msg(MSG_OK);
          return;
      }
  
      if (!_fire_validate_item(slot, warn))
      {
          mpr(warn);
          return;
      }
  
      dist target;
      if (!_fire_choose_item_and_target(slot, target, is_pproj_active(), false))
          return;
  
      bolt beam;
      throw_it(beam, slot, &target);
  }
  
  static bool _setup_missile_beam(const actor *agent, bolt &beam, item_def &item,
                                  string &ammo_name, bool &returning)
  {
      const auto cglyph = get_item_glyph(item);
      beam.glyph  = cglyph.ch;
      beam.colour = cglyph.col;
      beam.was_missile = true;
  
      item_def *launcher  = agent->weapon(0);
      if (launcher && !item.launched_by(*launcher))
          launcher = nullptr;
  
      if (agent->is_player())
      {
          beam.attitude      = ATT_FRIENDLY;
          beam.thrower       = KILL_YOU_MISSILE;
      }
      else
      {
          const monster* mon = agent->as_monster();
  
          beam.attitude      = mons_attitude(*mon);
          beam.thrower       = KILL_MON_MISSILE;
      }
  
      beam.range        = you.current_vision;
      beam.source_id    = agent->mid;
      beam.item         = &item;
      beam.source       = agent->pos();
      beam.flavour      = BEAM_MISSILE;
      beam.pierce       = is_penetrating_attack(*agent, launcher, item);
      beam.aux_source.clear();
  
      beam.name = item.name(DESC_PLAIN, false, false, false);
      ammo_name = item.name(DESC_PLAIN);
  
      const unrandart_entry* entry = launcher && is_unrandom_artefact(*launcher)
          ? get_unrand_entry(launcher->unrand_idx) : nullptr;
  
      if (entry && entry->launch)
      {
          setup_missile_type sm =
              entry->launch(launcher, &beam, &ammo_name,
                                       &returning);
  
          switch (sm)
          {
          case SM_CONTINUE:
              break;
          case SM_FINISHED:
              return false;
          case SM_CANCEL:
              return true;
          }
      }
  
      returning = item.base_type == OBJ_MISSILES
                  && item.sub_type == MI_BOOMERANG;
  
      if (item.base_type == OBJ_MISSILES
          && get_ammo_brand(item) == SPMSL_EXPLODING)
      {
          bolt *expl = new bolt(beam);
  
          expl->is_explosion = true;
          expl->damage       = dice_def(2, 5);
          expl->ex_size      = 1;
  
          if (beam.flavour == BEAM_MISSILE)
          {
              expl->flavour = BEAM_FRAG;
              expl->name   += " fragments";
  
              const string short_name =
                  item.name(DESC_BASENAME, true, false, false, false,
                            ISFLAG_IDENT_MASK | ISFLAG_COSMETIC_MASK);
  
              expl->name = replace_all(expl->name, item.name(DESC_PLAIN),
                                       short_name);
          }
          expl->name = "explosion of " + expl->name;
  
          beam.special_explosion = expl;
      }
  
      if (!is_artefact(item))
          ammo_name = article_a(ammo_name, true);
      else
          ammo_name = "the " + ammo_name;
  
      return false;
  }
  
  static void _throw_noise(actor* act, const item_def &ammo)
  {
      ASSERT(act); // XXX: change to actor &act
      const item_def* launcher = act->weapon();
  
      if (launcher == nullptr || launcher->base_type != OBJ_WEAPONS)
          return;
  
      if (is_launched(act, launcher, ammo) != launch_retval::LAUNCHED)
          return;
  
      int         level = 0;
      const char* msg   = nullptr;
  
      switch (launcher->sub_type)
      {
      case WPN_HUNTING_SLING:
          level = 1;
          msg   = "You hear a whirring sound.";
          break;
      case WPN_FUSTIBALUS:
          level = 3;
          msg   = "You hear a loud whirring sound.";
          break;
      case WPN_SHORTBOW:
          level = 5;
          msg   = "You hear a twanging sound.";
          break;
      case WPN_LONGBOW:
          level = 6;
          msg   = "You hear a loud twanging sound.";
          break;
      case WPN_HAND_CROSSBOW:
          level = 2;
          msg   = "You hear a quiet thunk.";
          break;
      case WPN_ARBALEST:
          level = 7;
          msg   = "You hear a thunk.";
          break;
      case WPN_TRIPLE_CROSSBOW:
          level = 9;
          msg   = "You hear a triplet of thunks.";
          break;
  
      default:
          die("Invalid launcher '%s'",
                   launcher->name(DESC_PLAIN).c_str());
          return;
      }
      if (act->is_player() || you.can_see(*act))
          msg = nullptr;
  
      noisy(level, act->pos(), msg, act->mid);
  }
  
  // throw_it - currently handles player throwing only. Monster
  // throwing is handled in mon-act:_mons_throw()
  // Note: If teleport is true, assume that pbolt is already set up,
  // and teleport the projectile onto the square.
  //
  // Return value is only relevant if dummy_target is non-nullptr, and returns
  // true if dummy_target is hit.
  bool throw_it(bolt &pbolt, int throw_2, dist *target)
  {
      dist thr;
      bool returning   = false;    // Item can return to pack.
      bool did_return  = false;    // Returning item actually does return to pack.
      const bool teleport = is_pproj_active();
  
      if (you.confused())
      {
          thr.target = you.pos();
          thr.target.x += random2(13) - 6;
          thr.target.y += random2(13) - 6;
          thr.isValid = true;
      }
      else if (target)
          thr = *target;
      else if (pbolt.target.zero())
      {
          direction_chooser_args args;
          args.mode = TARG_HOSTILE;
          direction(thr, args);
  
          if (!thr.isValid)
          {
              if (thr.isCancel)
                  canned_msg(MSG_OK);
  
              return false;
          }
      }
      pbolt.set_target(thr);
  
      item_def& thrown = you.inv[throw_2];
      ASSERT(thrown.defined());
  
      // Figure out if we're thrown or launched.
      const launch_retval projected = is_launched(&you, you.weapon(), thrown);
  
      // Making a copy of the item: changed only for venom launchers.
      item_def item = thrown;
      item.quantity = 1;
      item.slot     = index_to_letter(item.link);
  
      string ammo_name;
  
      if (_setup_missile_beam(&you, pbolt, item, ammo_name, returning))
      {
          you.turn_is_over = false;
          return false;
      }
  
      // Get the ammo/weapon type. Convenience.
      const object_class_type wepClass = thrown.base_type;
      const int               wepType  = thrown.sub_type;
  
      // Don't trace at all when confused.
      // Give the player a chance to be warned about helpless targets when using
      // Portaled Projectile, but obviously don't trace a path.
      bool cancelled = false;
      if (!you.confused())
      {
          // Kludgy. Ideally this would handled by the same code.
          // Perhaps some notion of a zero length bolt, with the source and
          // target both set to the target?
          if (teleport)
          {
              // This block is roughly equivalent to bolt::affect_cell for
              // normal projectiles.
              monster *m = monster_at(thr.target);
              if (m)
                  cancelled = stop_attack_prompt(m, false, thr.target);
  
              if (!cancelled && (pbolt.is_explosion || pbolt.special_explosion))
              {
                  for (adjacent_iterator ai(thr.target); ai; ++ai)
                  {
                      if (cancelled)
                          break;
                      monster *am = monster_at(*ai);
                      if (am)
                          cancelled = stop_attack_prompt(am, false, *ai);
                      else if (*ai == you.pos())
                      {
                          cancelled = !yesno("That is likely to hit you. Continue anyway?",
                                             false, 'n');
                      }
                  }
              }
          }
          else
          {
              // Set values absurdly high to make sure the tracer will
              // complain if we're attempting to fire through allies.
              pbolt.damage = dice_def(1, 100);
  
              // Init tracer variables.
              pbolt.foe_info.reset();
              pbolt.friend_info.reset();
              pbolt.foe_ratio = 100;
              pbolt.is_tracer = true;
  
              pbolt.fire();
  
              cancelled = pbolt.beam_cancelled;
  
              pbolt.hit    = 0;
              pbolt.damage = dice_def();
          }
      }
  
      // Should only happen if the player answered 'n' to one of those
      // "Fire through friendly?" prompts.
      if (cancelled)
      {
          you.turn_is_over = false;
          return false;
      }
  
      pbolt.is_tracer = false;
  
      bool unwielded = false;
      if (throw_2 == you.equip[EQ_WEAPON] && thrown.quantity == 1)
      {
          if (!wield_weapon(true, SLOT_BARE_HANDS, true, false, true, false))
              return false;
  
          if (!thrown.quantity)
              return false; // destroyed when unequipped (fragile)
  
          unwielded = true;
      }
  
      // Now start real firing!
      origin_set_unknown(item);
  
      // bloodpots & chunks need special handling.
      if (thrown.quantity > 1 && is_perishable_stack(item))
      {
          // Initialise thrown item with oldest item in stack.
          const int rot_timer = remove_oldest_perishable_item(thrown)
                                - you.elapsed_time;
          item.props.clear();
          init_perishable_stack(item, rot_timer);
      }
  
      // Even though direction is allowed, we're throwing so we
      // want to use tx, ty to make the missile fly to map edge.
      if (!teleport)
          pbolt.set_target(thr);
  
      const int bow_brand = (projected == launch_retval::LAUNCHED)
                            ? get_weapon_brand(*you.weapon())
                            : SPWPN_NORMAL;
      const int ammo_brand = get_ammo_brand(item);
  
      switch (projected)
      {
      case launch_retval::LAUNCHED:
      {
          const item_def *launcher = you.weapon();
          ASSERT(launcher);
          practise_launching(*launcher);
          if (is_unrandom_artefact(*launcher)
              && get_unrand_entry(launcher->unrand_idx)->type_name)
          {
              count_action(CACT_FIRE, launcher->unrand_idx);
          }
          else
              count_action(CACT_FIRE, launcher->sub_type);
          break;
      }
      case launch_retval::THROWN:
          practise_throwing((missile_type)wepType);
          count_action(CACT_THROW, wepType, OBJ_MISSILES);
          break;
      case launch_retval::FUMBLED:
          break;
      case launch_retval::BUGGY:
          dprf("Unknown launch type for weapon."); // should never happen :)
          break;
      }
  
      // check for returning ammo
      if (teleport)
          returning = false;
  
      you.time_taken = you.attack_delay(&item).roll();
  
      // Create message.
      mprf("You %s%s %s.",
            teleport ? "magically " : "",
            (projected == launch_retval::FUMBLED ? "toss away" :
             projected == launch_retval::LAUNCHED ? "shoot" : 
             you.has_mutation(MUT_DAB_MASTER) ? "yeet" : "throw"),
            ammo_name.c_str());
  
      // Ensure we're firing a 'missile'-type beam.
      pbolt.pierce    = false;
      pbolt.is_tracer = false;
  
      pbolt.loudness = item.base_type == OBJ_MISSILES
                     ? ammo_type_damage(item.sub_type) / 3
                     : 0; // Maybe not accurate, but reflects the damage.
  
      // Mark this item as thrown if it's a missile, so that we'll pick it up
      // when we walk over it.
      if (wepClass == OBJ_MISSILES || wepClass == OBJ_WEAPONS)
          item.flags |= ISFLAG_THROWN;
  
      pbolt.hit = teleport ? random2(you.attribute[ATTR_PORTAL_PROJECTILE] / 4)
                           : 0;
  
      bool hit = false;
      if (teleport)
      {
          // Violating encapsulation somewhat...oh well.
          pbolt.use_target_as_pos = true;
          pbolt.affect_cell();
          pbolt.affect_endpoint();
          if (!did_return)
              pbolt.drop_object();
          // Costs 1 MP per shot.
          dec_mp(1);
      }
      else
      {
          if (crawl_state.game_is_hints())
              Hints.hints_throw_counter++;
  
          // Dropping item copy, since the launched item might be different.
          pbolt.drop_item = !returning;
          pbolt.fire();
  
          hit = !pbolt.hit_verb.empty();
  
          // The item can be destroyed before returning.
          if (returning && thrown_object_destroyed(&item))
              returning = false;
      }
  
      if (bow_brand == SPWPN_CHAOS || ammo_brand == SPMSL_CHAOS)
          did_god_conduct(DID_CHAOS, 2 + random2(3), bow_brand == SPWPN_CHAOS);
  
      if (bow_brand == SPWPN_SPEED)
          did_god_conduct(DID_HASTY, 1, true);
  
      if (ammo_brand == SPMSL_FRENZY)
          did_god_conduct(DID_HASTY, 6 + random2(3), true);
  
      if (returning)
      {
          // Fire beam in reverse.
          pbolt.setup_retrace();
          viewwindow();
          pbolt.fire();
      }
      else
      {
          dec_inv_item_quantity(throw_2, 1);
          if (unwielded)
              canned_msg(MSG_EMPTY_HANDED_NOW);
      }
  
      _throw_noise(&you, thrown);
  
      // ...any monster nearby can see that something has been thrown, even
      // if it didn't make any noise.
      alert_nearby_monsters();
  
      you.turn_is_over = true;
  
      if (pbolt.special_explosion != nullptr)
          delete pbolt.special_explosion;
  
      if (!teleport
          && projected != launch_retval::FUMBLED
          && will_have_passive(passive_t::shadow_attacks)
          && thrown.base_type == OBJ_MISSILES
          && thrown.sub_type != MI_DART)
      {
          dithmenos_shadow_throw(thr, item);
      }
  
      return hit;
  }
  
  void setup_monster_throw_beam(monster* mons, bolt &beam)
  {
      beam.range = you.current_vision;
      beam.source_id = mons->mid;
  
      beam.glyph   = dchar_glyph(DCHAR_FIRED_MISSILE);
      beam.flavour = BEAM_MISSILE;
      beam.thrower = KILL_MON_MISSILE;
      beam.aux_source.clear();
      beam.pierce  = false;
  }
  
  // msl is the item index of the thrown missile (or weapon).
  bool mons_throw(monster* mons, bolt &beam, int msl, bool teleport)
  {
      string ammo_name;
  
      bool returning = false;
  
      // Some initial convenience & initializations.
      ASSERT(mitm[msl].base_type == OBJ_MISSILES);
  
      const int weapon    = mons->inv[MSLOT_WEAPON];
  
      mon_inv_type slot = get_mon_equip_slot(mons, mitm[msl]);
      ASSERT(slot != NUM_MONSTER_SLOTS);
  
      // Energy is already deducted for the spell cast, if using portal projectile
      // FIXME: should it use this delay and not the spell delay?
      if (!teleport)
      {
          const int energy = mons->action_energy(EUT_MISSILE);
          const int delay = mons->attack_delay(&mitm[msl]).roll();
          ASSERT(energy > 0);
          ASSERT(delay > 0);
          mons->speed_increment -= div_rand_round(energy * delay, 10);
      }
  
      // Dropping item copy, since the launched item might be different.
      item_def item = mitm[msl];
      item.quantity = 1;
  
      if (_setup_missile_beam(mons, beam, item, ammo_name, returning))
          return false;
  
      beam.aimed_at_spot |= returning;
  
      const launch_retval projected =
          is_launched(mons, mons->mslot_item(MSLOT_WEAPON),
                      mitm[msl]);
  
      if (projected == launch_retval::THROWN)
          returning = returning && !teleport;
  
      // Identify before throwing, so we don't get different
      // messages for first and subsequent missiles.
      if (mons->observable())
      {
          if (projected == launch_retval::LAUNCHED
                 && item_type_known(mitm[weapon])
              || projected == launch_retval::THROWN
                 && mitm[msl].base_type == OBJ_MISSILES)
          {
              set_ident_flags(mitm[msl], ISFLAG_KNOW_TYPE);
              set_ident_flags(item, ISFLAG_KNOW_TYPE);
          }
      }
  
      // Now, if a monster is, for some reason, throwing something really
      // stupid, it will have baseHit of 0 and damage of 0. Ah well.
      string msg = mons->name(DESC_THE);
      if (teleport)
          msg += " magically";
      msg += ((projected == launch_retval::LAUNCHED) ? " shoots " : " throws ");
  
      if (!beam.name.empty() && projected == launch_retval::LAUNCHED)
          msg += article_a(beam.name);
      else
      {
          // build shoot message
          msg += item.name(DESC_A, false, false, false);
  
          // build beam name
          beam.name = item.name(DESC_PLAIN, false, false, false);
      }
      msg += ".";
  
      if (mons->observable())
      {
          mons->flags |= MF_SEEN_RANGED;
          mpr(msg);
      }
  
      _throw_noise(mons, item);
  
      beam.drop_item = !returning;
  
      // Redraw the screen before firing, in case the monster just
      // came into view and the screen hasn't been updated yet.
      viewwindow();
      if (teleport)
      {
          beam.use_target_as_pos = true;
          beam.affect_cell();
          beam.affect_endpoint();
          if (!returning)
              beam.drop_object();
      }
      else
      {
          beam.fire();
  
          // The item can be destroyed before returning.
          if (returning && thrown_object_destroyed(&item))
              returning = false;
      }
  
      if (returning)
      {
          // Fire beam in reverse.
          beam.setup_retrace();
          viewwindow();
          beam.fire();
  
          // Only print a message if you can see the target or the thrower.
          // Otherwise we get "The weapon returns whence it came from!" regardless.
          if (you.see_cell(beam.target) || you.can_see(*mons))
          {
              msg::stream << "The weapon returns "
                          << (you.can_see(*mons)?
                                ("to " + mons->name(DESC_THE))
                              : "from whence it came")
                          << "!" << endl;
          }
  
          // Player saw the item return.
          if (!is_artefact(item))
              set_ident_flags(mitm[msl], ISFLAG_KNOW_TYPE);
      }
      else if (dec_mitm_item_quantity(msl, 1))
          mons->inv[slot] = NON_ITEM;
  
      if (beam.special_explosion != nullptr)
          delete beam.special_explosion;
  
      return true;
  }
  
  bool thrown_object_destroyed(item_def *item)
  {
      ASSERT(item != nullptr);
  
      if (item->base_type != OBJ_MISSILES)
          return false;
  
      if (ammo_always_destroyed(*item))
          return true;
  
      if (ammo_never_destroyed(*item))
          return false;
  
      const int base_chance = ammo_type_destroy_chance(item->sub_type);
      const int brand = get_ammo_brand(*item);
  
      // Inflate by 2 to avoid rounding errors.
      const int mult = 2;
      int chance = base_chance * mult;
  
      if (brand == SPMSL_CURARE)
          chance /= 2;
  
      dprf("mulch chance: %d in %d", mult, chance);
  
      return x_chance_in_y(mult, chance);
  }
  