Example #1
0
/**
 * This is a helper function used by do_cmd_throw and do_cmd_fire.
 *
 * It abstracts out the projectile path, display code, identify and clean up
 * logic, while using the 'attack' parameter to do work particular to each
 * kind of attack.
 */
static void ranged_helper(int item, int dir, int range, int shots, ranged_attack attack) {
	/* Get the ammo */
	object_type *o_ptr = object_from_item_idx(item);

	int i, j;
	byte missile_attr = object_attr(o_ptr);
	char missile_char = object_char(o_ptr);

	object_type object_type_body;
	object_type *i_ptr = &object_type_body;

	char o_name[80];

	int path_n;
	u16b path_g[256];

	int msec = op_ptr->delay_factor;

	/* Start at the player */
	int x = p_ptr->px;
	int y = p_ptr->py;

	/* Predict the "target" location */
	s16b ty = y + 99 * ddy[dir];
	s16b tx = x + 99 * ddx[dir];

	bool hit_target = FALSE;

	/* Check for target validity */
	if ((dir == 5) && target_okay()) {
		int taim;
		char msg[80];
		target_get(&tx, &ty);
		taim = distance(y, x, ty, tx);
		if (taim > range) {
			sprintf (msg, "Target out of range by %d squares. Fire anyway? ",
				taim - range);
			if (!get_check(msg)) return;
		}
	}

	/* Sound */
	sound(MSG_SHOOT);

	object_notice_on_firing(o_ptr);

	/* Describe the object */
	object_desc(o_name, sizeof(o_name), o_ptr, ODESC_FULL | ODESC_SINGULAR);

	/* Actually "fire" the object -- Take a partial turn */
	p_ptr->energy_use = (100 / shots);

	/* Calculate the path */
	path_n = project_path(path_g, range, y, x, ty, tx, 0);

	/* Hack -- Handle stuff */
	handle_stuff(p_ptr);

	/* Start at the player */
	x = p_ptr->px;
	y = p_ptr->py;

	/* Project along the path */
	for (i = 0; i < path_n; ++i) {
		int ny = GRID_Y(path_g[i]);
		int nx = GRID_X(path_g[i]);

		/* Hack -- Stop before hitting walls */
		if (!cave_floor_bold(ny, nx)) break;

		/* Advance */
		x = nx;
		y = ny;

		/* Only do visuals if the player can "see" the missile */
		if (player_can_see_bold(y, x)) {
			print_rel(missile_char, missile_attr, y, x);
			move_cursor_relative(y, x);

			Term_fresh();
			if (p_ptr->redraw) redraw_stuff(p_ptr);

			Term_xtra(TERM_XTRA_DELAY, msec);
			cave_light_spot(cave, y, x);

			Term_fresh();
			if (p_ptr->redraw) redraw_stuff(p_ptr);
		} else {
			/* Delay anyway for consistency */
			Term_xtra(TERM_XTRA_DELAY, msec);
		}

		/* Handle monster */
		if (cave->m_idx[y][x] > 0) break;
	}

	/* Try the attack on the monster at (x, y) if any */
	if (cave->m_idx[y][x] > 0) {
		monster_type *m_ptr = cave_monster(cave, cave->m_idx[y][x]);
		monster_race *r_ptr = &r_info[m_ptr->r_idx];
		int visible = m_ptr->ml;

		bool fear = FALSE;
		char m_name[80];
		const char *note_dies = monster_is_unusual(r_ptr) ? " is destroyed." : " dies.";

		struct attack_result result = attack(o_ptr, y, x);
		int dmg = result.dmg;
		u32b msg_type = result.msg_type;
		const char *hit_verb = result.hit_verb;

		if (result.success) {
			hit_target = TRUE;

			/* Get "the monster" or "it" */
			monster_desc(m_name, sizeof(m_name), m_ptr, 0);
		
			object_notice_attack_plusses(o_ptr);
		
			/* No negative damage; change verb if no damage done */
			if (dmg <= 0) {
				dmg = 0;
				hit_verb = "fail to harm";
			}
		
			if (!visible) {
				/* Invisible monster */
				msgt(MSG_SHOOT_HIT, "The %s finds a mark.", o_name);
			} else {
				/* Visible monster */
				if (msg_type == MSG_SHOOT_HIT)
					msgt(MSG_SHOOT_HIT, "The %s %s %s.", o_name, hit_verb, m_name);
				else if (msg_type == MSG_HIT_GOOD) {
					msgt(MSG_HIT_GOOD, "The %s %s %s. %s", o_name, hit_verb, m_name, "It was a good hit!");
				} else if (msg_type == MSG_HIT_GREAT) {
					msgt(MSG_HIT_GREAT, "The %s %s %s. %s", o_name, hit_verb, m_name,
						 "It was a great hit!");
				} else if (msg_type == MSG_HIT_SUPERB) {
					msgt(MSG_HIT_SUPERB, "The %s %s %s. %s", o_name, hit_verb, m_name,
						 "It was a superb hit!");
				}
		
				/* Track this monster */
				if (m_ptr->ml) monster_race_track(m_ptr->r_idx);
				if (m_ptr->ml) health_track(p_ptr, cave->m_idx[y][x]);
			}
		
			/* Complex message */
			if (p_ptr->wizard)
				msg("You do %d (out of %d) damage.", dmg, m_ptr->hp);
		
			/* Hit the monster, check for death */
			if (!mon_take_hit(cave->m_idx[y][x], dmg, &fear, note_dies)) {
				message_pain(cave->m_idx[y][x], dmg);
				if (fear && m_ptr->ml)
					add_monster_message(m_name, cave->m_idx[y][x], MON_MSG_FLEE_IN_TERROR, TRUE);
			}
		}
	}

	/* Obtain a local object */
	object_copy(i_ptr, o_ptr);
	object_split(i_ptr, o_ptr, 1);

	/* See if the ammunition broke or not */
	j = breakage_chance(i_ptr, hit_target);

	/* Drop (or break) near that location */
	drop_near(cave, i_ptr, j, y, x, TRUE);

	if (item >= 0) {
		/* The ammo is from the inventory */
		inven_item_increase(item, -1);
		inven_item_describe(item);
		inven_item_optimize(item);
	} else {
		/* The ammo is from the floor */
		floor_item_increase(0 - item, -1);
		floor_item_optimize(0 - item);
	}
}
Example #2
0
/*
 * Fire an object from the pack or floor.
 *
 * You may only fire items that "match" your missile launcher.
 *
 * See "calc_bonuses()" for more calculations and such.
 *
 * Note that "firing" a missile is MUCH better than "throwing" it.
 *
 * Note: "unseen" monsters are very hard to hit.
 *
 * Objects are more likely to break if they "attempt" to hit a monster.
 *
 * Rangers (with Bows) and Anyone (with "Extra Shots") get extra shots.
 * The "extra shot" code works by decreasing the amount of energy
 * required to make each shot, spreading the shots out over time.
 *
 * Note that when firing missiles, the launcher multiplier is applied
 * after all the bonuses are added in, making multipliers very useful.
 *
 * Note that Bows of "Extra Might" get extra range and an extra bonus
 * for the damage multiplier.
 */
void do_cmd_fire(cmd_code code, cmd_arg args[])
{
	int dir, item;
	int i, j, y, x;
	s16b ty, tx;
	int tdam, tdis, thits;
	int bonus, chance;

	object_type *o_ptr;
	object_type *j_ptr;

	object_type *i_ptr;
	object_type object_type_body;

	bool hit_body = FALSE;

	byte missile_attr;
	char missile_char;

	char o_name[80];

	u32b msg_type = 0;

	int path_n;
	u16b path_g[256];

	int msec = op_ptr->delay_factor * op_ptr->delay_factor;

	/* Get the "bow" */
	j_ptr = &p_ptr->inventory[INVEN_BOW];

	/* Require a usable launcher */
	if (!j_ptr->tval || !p_ptr->state.ammo_tval)
	{
		msg_print("You have nothing to fire with.");
		return;
	}

	/* Get item to fire and direction to fire in. */
	item = args[0].item;
	dir = args[1].direction;

	/* Check the item being fired is usable by the player. */
	if (!item_is_available(item, NULL, (USE_EQUIP | USE_INVEN | USE_FLOOR)))
	{
		msg_format("That item is not within your reach.");
		return;
	}

	/* Get the object for the ammo */
	o_ptr = object_from_item_idx(item);

	/* Check the ammo can be used with the launcher */
	if (o_ptr->tval != p_ptr->state.ammo_tval)
	{
		msg_format("That ammo cannot be fired by your current weapon.");
		return;
	}

	/* Base range XXX XXX */
	tdis = 6 + 2 * p_ptr->state.ammo_mult;

	/* Start at the player */
	x = p_ptr->px;
	y = p_ptr->py;

	/* Predict the "target" location */
	ty = y + 99 * ddy[dir];
	tx = x + 99 * ddx[dir];

	/* Check for target validity */
	if ((dir == 5) && target_okay())
	{
		target_get(&tx, &ty);
		if (distance(y, x, ty, tx) > tdis)
		{
			if (!get_check("Target out of range.  Fire anyway? "))
				return;
		}
	}

	/* Sound */
	sound(MSG_SHOOT);

	object_notice_on_firing(o_ptr);

	/* Describe the object */
	object_desc(o_name, sizeof(o_name), o_ptr, ODESC_FULL | ODESC_SINGULAR);

	/* Find the color and symbol for the object for throwing */
	missile_attr = object_attr(o_ptr);
	missile_char = object_char(o_ptr);

	/* Use the proper number of shots */
	thits = p_ptr->state.num_fire;

	/* Actually "fire" the object */
	bonus = (p_ptr->state.to_h + o_ptr->to_h + j_ptr->to_h);
	chance = p_ptr->state.skills[SKILL_TO_HIT_BOW] +
			(bonus * BTH_PLUS_ADJ);

	/* Take a (partial) turn */
	p_ptr->energy_use = (100 / thits);

	/* Calculate the path */
	path_n = project_path(path_g, tdis, y, x, ty, tx, 0);

	/* Hack -- Handle stuff */
	handle_stuff();

	/* Project along the path */
	for (i = 0; i < path_n; ++i)
	{
		int ny = GRID_Y(path_g[i]);
		int nx = GRID_X(path_g[i]);

		/* Hack -- Stop before hitting walls */
		if (!cave_floor_bold(ny, nx)) break;

		/* Advance */
		x = nx;
		y = ny;

		/* Only do visuals if the player can "see" the missile */
		if (player_can_see_bold(y, x))
		{
			/* Visual effects */
			print_rel(missile_char, missile_attr, y, x);
			move_cursor_relative(y, x);

			Term_fresh();
			if (p_ptr->redraw) redraw_stuff();

			Term_xtra(TERM_XTRA_DELAY, msec);
			light_spot(y, x);

			Term_fresh();
			if (p_ptr->redraw) redraw_stuff();
		}

		/* Delay anyway for consistency */
		else
		{
			/* Pause anyway, for consistancy */
			Term_xtra(TERM_XTRA_DELAY, msec);
		}

		/* Handle monster */
		if (cave_m_idx[y][x] > 0)
		{
			monster_type *m_ptr = &mon_list[cave_m_idx[y][x]];
			monster_race *r_ptr = &r_info[m_ptr->r_idx];

			int chance2 = chance - distance(p_ptr->py, p_ptr->px, y, x);
			int visible = m_ptr->ml;
			int multiplier = 1;

			const char *hit_verb = "hits";
			const slay_t *best_s_ptr = NULL;

			/* Note the collision */
			hit_body = TRUE;

			/* Did we hit it (penalize distance travelled) */
			if (test_hit(chance2, r_ptr->ac, m_ptr->ml))
			{
				bool fear = FALSE;

				/* Assume a default death */
				cptr note_dies = " dies.";

				improve_attack_modifier(o_ptr, m_ptr, &best_s_ptr);
				improve_attack_modifier(j_ptr, m_ptr, &best_s_ptr);
				if (best_s_ptr != NULL)
					hit_verb = best_s_ptr->range_verb;

				/* Some monsters get "destroyed" */
				if (monster_is_unusual(r_ptr))
				{
					/* Special note at death */
					note_dies = " is destroyed.";
				}

				/* Calculate multiplier */
				multiplier = p_ptr->state.ammo_mult;
				if (best_s_ptr != NULL) multiplier += best_s_ptr->mult;

				/* Apply damage: multiplier, slays, criticals, bonuses */
				tdam = damroll(o_ptr->dd, o_ptr->ds);
				tdam += o_ptr->to_d + j_ptr->to_d;
				tdam *= multiplier;
				tdam = critical_shot(o_ptr->weight, o_ptr->to_h, tdam, &msg_type);

				object_notice_attack_plusses(o_ptr);
				object_notice_attack_plusses(&p_ptr->inventory[INVEN_BOW]);

				/* No negative damage; change verb if no damage done */
				if (tdam <= 0)
				{
					tdam = 0;
					hit_verb = "fail to harm";
				}

				/* Handle unseen monster */
				if (!visible)
				{
					/* Invisible monster */
					message_format(MSG_SHOOT_HIT, 0, "The %s finds a mark.", o_name);
				}

				/* Handle visible monster */
				else
				{
					char m_name[80];

					/* Get "the monster" or "it" */
					monster_desc(m_name, sizeof(m_name), m_ptr, 0);

					/* Tell the player what happened */
					if (msg_type == MSG_SHOOT_HIT)
						message_format(MSG_SHOOT_HIT, 0, "The %s %s %s.", o_name, hit_verb, m_name);
					else
					{
						if (msg_type == MSG_HIT_GOOD)
						{
							message_format(MSG_HIT_GOOD, 0, "The %s %s %s. %s", o_name, hit_verb, m_name,
										   "It was a good hit!");
						}
						else if (msg_type == MSG_HIT_GREAT)
						{
							message_format(MSG_HIT_GREAT, 0, "The %s %s %s. %s", o_name, hit_verb, m_name,
										   "It was a great hit!");
						}
						else if (msg_type == MSG_HIT_SUPERB)
						{
							message_format(MSG_HIT_SUPERB, 0, "The %s %s %s. %s", o_name, hit_verb, m_name,
										   "It was a superb hit!");
						}
					}
					/* Hack -- Track this monster race */
					if (m_ptr->ml) monster_race_track(m_ptr->r_idx);

					/* Hack -- Track this monster */
					if (m_ptr->ml) health_track(cave_m_idx[y][x]);

				}

				/* Complex message */
				if (p_ptr->wizard)
				{
					msg_format("You do %d (out of %d) damage.",
					           tdam, m_ptr->hp);
				}

				/* Hit the monster, check for death */
				if (mon_take_hit(cave_m_idx[y][x], tdam, &fear, note_dies))
				{
					/* Dead monster */
				}

				/* No death */
				else
				{
					/* Message */
					message_pain(cave_m_idx[y][x], tdam);

					/* Take note */
					if (fear && m_ptr->ml)
					{
						char m_name[80];

						/* Get the monster name (or "it") */
						monster_desc(m_name, sizeof(m_name), m_ptr, 0);

						/* Message */
						message_format(MSG_FLEE, m_ptr->r_idx,
						               "%^s flees in terror!", m_name);
					}
				}
			}

			/* Stop looking */
			break;
		}
	}



	/* Get local object */
	i_ptr = &object_type_body;

	/* Obtain a local object */
	object_copy(i_ptr, o_ptr);

	/* Single object */
	i_ptr->number = 1;

	if (item >= 0)
	{
		inven_item_increase(item, -1);
		inven_item_describe(item);
		inven_item_optimize(item);
	}

	/* Reduce and describe floor item */
	else
	{
		floor_item_increase(0 - item, -1);
		floor_item_optimize(0 - item);
	}


	/* Chance of breakage (during attacks) */
	j = (hit_body ? breakage_chance(i_ptr) : 0);

	/* Drop (or break) near that location */
	drop_near(i_ptr, j, y, x, TRUE);
}
Example #3
0
/**
 * This is a helper function used by do_cmd_throw and do_cmd_fire.
 *
 * It abstracts out the projectile path, display code, identify and clean up
 * logic, while using the 'attack' parameter to do work particular to each
 * kind of attack.
 */
static void ranged_helper(struct object *obj, int dir, int range, int shots,
						  ranged_attack attack)
{
	int i, j;

	char o_name[80];

	int path_n;
	struct loc path_g[256];

	/* Start at the player */
	int x = player->px;
	int y = player->py;

	/* Predict the "target" location */
	int ty = y + 99 * ddy[dir];
	int tx = x + 99 * ddx[dir];

	bool hit_target = FALSE;
	bool none_left = FALSE;

	struct object *missile;

	/* Check for target validity */
	if ((dir == 5) && target_okay()) {
		int taim;
		target_get(&tx, &ty);
		taim = distance(y, x, ty, tx);
		if (taim > range) {
			char msg[80];
			strnfmt(msg, sizeof(msg),
					"Target out of range by %d squares. Fire anyway? ",
				taim - range);
			if (!get_check(msg)) return;
		}
	}

	/* Sound */
	sound(MSG_SHOOT);

	/* Describe the object */
	object_desc(o_name, sizeof(o_name), obj, ODESC_FULL | ODESC_SINGULAR);

	/* Actually "fire" the object -- Take a partial turn */
	player->upkeep->energy_use = (z_info->move_energy / shots);

	/* Calculate the path */
	path_n = project_path(path_g, range, y, x, ty, tx, 0);

	/* Hack -- Handle stuff */
	handle_stuff(player);

	/* Start at the player */
	x = player->px;
	y = player->py;

	/* Project along the path */
	for (i = 0; i < path_n; ++i) {
		struct monster *mon = NULL;
		int ny = path_g[i].y;
		int nx = path_g[i].x;
		bool see = square_isseen(cave, ny, nx);

		/* Stop before hitting walls */
		if (!(square_ispassable(cave, ny, nx)) &&
			!(square_isprojectable(cave, ny, nx)))
			break;

		/* Advance */
		x = nx;
		y = ny;

		/* Tell the UI to display the missile */
		event_signal_missile(EVENT_MISSILE, obj, see, y, x);

		/* Try the attack on the monster at (x, y) if any */
		mon = square_monster(cave, y, x);
		if (mon) {
			int visible = mflag_has(mon->mflag, MFLAG_VISIBLE);

			bool fear = FALSE;
			const char *note_dies = monster_is_unusual(mon->race) ? 
				" is destroyed." : " dies.";

			struct attack_result result = attack(obj, y, x);
			int dmg = result.dmg;
			u32b msg_type = result.msg_type;
			char hit_verb[20];
			my_strcpy(hit_verb, result.hit_verb, sizeof(hit_verb));
			mem_free(result.hit_verb);

			if (result.success) {
				hit_target = TRUE;

				object_notice_attack_plusses(obj);

				/* Learn by use for other equipped items */
				equip_notice_to_hit_on_attack(player);

				/* No negative damage; change verb if no damage done */
				if (dmg <= 0) {
					dmg = 0;
					msg_type = MSG_MISS;
					my_strcpy(hit_verb, "fails to harm", sizeof(hit_verb));
				}

				if (!visible) {
					/* Invisible monster */
					msgt(MSG_SHOOT_HIT, "The %s finds a mark.", o_name);
				} else {
					for (j = 0; j < (int)N_ELEMENTS(ranged_hit_types); j++) {
						char m_name[80];
						const char *dmg_text = "";

						if (msg_type != ranged_hit_types[j].msg)
							continue;

						if (OPT(show_damage))
							dmg_text = format(" (%d)", dmg);

						monster_desc(m_name, sizeof(m_name), mon, MDESC_OBJE);

						if (ranged_hit_types[j].text)
							msgt(msg_type, "Your %s %s %s%s. %s", o_name, 
								 hit_verb, m_name, dmg_text, 
								 ranged_hit_types[j].text);
						else
							msgt(msg_type, "Your %s %s %s%s.", o_name, hit_verb,
								 m_name, dmg_text);
					}
					
					/* Track this monster */
					if (mflag_has(mon->mflag, MFLAG_VISIBLE)) {
						monster_race_track(player->upkeep, mon->race);
						health_track(player->upkeep, mon);
					}
				}

				/* Hit the monster, check for death */
				if (!mon_take_hit(mon, dmg, &fear, note_dies)) {
					message_pain(mon, dmg);
					if (fear && mflag_has(mon->mflag, MFLAG_VISIBLE)) {
						char m_name[80];
						monster_desc(m_name, sizeof(m_name), mon, 
									 MDESC_DEFAULT);
						add_monster_message(m_name, mon, 
											MON_MSG_FLEE_IN_TERROR, TRUE);
					}
				}
			}
			/* Stop the missile */
			break;
		}

		/* Stop if non-projectable but passable */
		if (!(square_isprojectable(cave, ny, nx))) 
			break;
	}

	/* Get the missile */
	if (object_is_carried(player, obj))
		missile = gear_object_for_use(obj, 1, TRUE, &none_left);
	else
		missile = floor_object_for_use(obj, 1, TRUE, &none_left);

	/* Drop (or break) near that location */
	drop_near(cave, missile, breakage_chance(missile, hit_target), y, x, TRUE);
}
Example #4
0
/*
 * Throw an object from the pack or floor.
 *
 * Note: "unseen" monsters are very hard to hit.
 *
 * Should throwing a weapon do full damage?  Should it allow the magic
 * to hit bonus of the weapon to have an effect?  Should it ever cause
 * the item to be destroyed?  Should it do any damage at all?
 */
void do_cmd_throw(cmd_code code, cmd_arg args[])
{
	int dir, item;
	int i, j, y, x;
	s16b ty, tx;
	int chance, tdam, tdis;
	int weight;

	object_type *o_ptr;

	object_type *i_ptr;
	object_type object_type_body;

	bool hit_body = FALSE;

	byte missile_attr;
	char missile_char;

	char o_name[80];

	u32b msg_type = 0;

	int path_n;
	u16b path_g[256];

	int msec = op_ptr->delay_factor * op_ptr->delay_factor;

	/* Get item to throw and direction in which to throw it. */
	item = args[0].item;
	dir = args[1].direction;

	/* Make sure the player isn't throwing wielded items */
	if (item >= INVEN_WIELD && item < QUIVER_START)
	{
		msg_print("You have cannot throw wielded items.");
		return;
	}

	/* Check the item being thrown is usable by the player. */
	if (!item_is_available(item, NULL, (USE_EQUIP | USE_INVEN | USE_FLOOR)))
	{
		msg_format("That item is not within your reach.");
		return;
	}

	/* Get the object */
	o_ptr = object_from_item_idx(item);
	object_notice_on_firing(o_ptr);

	/* Get local object */
	i_ptr = &object_type_body;

	/* Obtain a local object */
	object_copy(i_ptr, o_ptr);

	/* Distribute the charges of rods/wands/staves between the stacks */
	distribute_charges(o_ptr, i_ptr, 1);

	/* Single object */
	i_ptr->number = 1;

	/* Reduce and describe inventory */
	if (item >= 0)
	{
		inven_item_increase(item, -1);
		inven_item_describe(item);
		inven_item_optimize(item);
	}

	/* Reduce and describe floor item */
	else
	{
		floor_item_increase(0 - item, -1);
		floor_item_optimize(0 - item);
	}


	/* Description */
	object_desc(o_name, sizeof(o_name), i_ptr, ODESC_FULL);

	/* Find the color and symbol for the object for throwing */
	missile_attr = object_attr(i_ptr);
	missile_char = object_char(i_ptr);


	/* Enforce a minimum "weight" of one pound */
	weight = ((i_ptr->weight > 10) ? i_ptr->weight : 10);

	/* Hack -- Distance -- Reward strength, penalize weight */
	tdis = (adj_str_blow[p_ptr->state.stat_ind[A_STR]] + 20) * 10 / weight;

	/* Max distance of 10 */
	if (tdis > 10) tdis = 10;

	/* Hack -- Base damage from thrown object */
	tdam = damroll(i_ptr->dd, i_ptr->ds);
	if (!tdam) tdam = 1;
	tdam += i_ptr->to_d;

	/* Chance of hitting */
	chance = (p_ptr->state.skills[SKILL_TO_HIT_THROW] + (p_ptr->state.to_h * BTH_PLUS_ADJ));


	/* Take a turn */
	p_ptr->energy_use = 100;


	/* Start at the player */
	y = p_ptr->py;
	x = p_ptr->px;

	/* Predict the "target" location */
	ty = p_ptr->py + 99 * ddy[dir];
	tx = p_ptr->px + 99 * ddx[dir];

	/* Check for "target request" */
	if ((dir == 5) && target_okay())
	{
		target_get(&tx, &ty);
	}

	/* Calculate the path */
	path_n = project_path(path_g, tdis, p_ptr->py, p_ptr->px, ty, tx, 0);


	/* Hack -- Handle stuff */
	handle_stuff();

	/* Project along the path */
	for (i = 0; i < path_n; ++i)
	{
		int ny = GRID_Y(path_g[i]);
		int nx = GRID_X(path_g[i]);

		/* Hack -- Stop before hitting walls */
		if (!cave_floor_bold(ny, nx)) break;

		/* Advance */
		x = nx;
		y = ny;

		/* Only do visuals if the player can "see" the missile */
		if (player_can_see_bold(y, x))
		{
			/* Visual effects */
			print_rel(missile_char, missile_attr, y, x);
			move_cursor_relative(y, x);

			Term_fresh();
			if (p_ptr->redraw) redraw_stuff();

			Term_xtra(TERM_XTRA_DELAY, msec);
			light_spot(y, x);

			Term_fresh();
			if (p_ptr->redraw) redraw_stuff();
		}

		/* Delay anyway for consistency */
		else
		{
			/* Pause anyway, for consistancy */
			Term_xtra(TERM_XTRA_DELAY, msec);
		}

		/* Handle monster */
		if (cave_m_idx[y][x] > 0)
		{
			monster_type *m_ptr = &mon_list[cave_m_idx[y][x]];
			monster_race *r_ptr = &r_info[m_ptr->r_idx];

			int chance2 = chance - distance(p_ptr->py, p_ptr->px, y, x);

			int visible = m_ptr->ml;

			/* Note the collision */
			hit_body = TRUE;

			/* Did we hit it (penalize range) */
			if (test_hit(chance2, r_ptr->ac, m_ptr->ml))
			{
				const char *hit_verb = "hits";
				bool fear = FALSE;
				const slay_t *best_s_ptr = NULL;

				/* Assume a default death */
				cptr note_dies = " dies.";

				/* Some monsters get "destroyed" */
				if (monster_is_unusual(r_ptr))
				{
					/* Special note at death */
					note_dies = " is destroyed.";
				}

				/* Apply special damage  - brought forward to fill in hit_verb XXX XXX XXX */
				improve_attack_modifier(i_ptr, m_ptr, &best_s_ptr);
				if (best_s_ptr != NULL)
				{
					tdam *= best_s_ptr->mult;
					hit_verb = best_s_ptr->range_verb;
				}
				/* Apply special damage XXX XXX XXX */
				tdam = critical_shot(i_ptr->weight, i_ptr->to_h, tdam, &msg_type);

				/* No negative damage; change verb if no damage done */
				if (tdam <= 0)
				{
					tdam = 0;
					hit_verb = "fail to harm";
				}

				/* Handle unseen monster */
				if (!visible)
				{
					/* Invisible monster */
					msg_format("The %s finds a mark.", o_name);
				}

				/* Handle visible monster */
				else
				{
					char m_name[80];

					/* Get "the monster" or "it" */
					monster_desc(m_name, sizeof(m_name), m_ptr, 0);

					/* Tell the player what happened */
					if (msg_type == MSG_SHOOT_HIT)
						message_format(MSG_SHOOT_HIT, 0, "The %s %s %s.", o_name, hit_verb, m_name);
					else
					{
						if (msg_type == MSG_HIT_GOOD)
						{
							message_format(MSG_HIT_GOOD, 0, "The %s %s %s. %s", o_name, hit_verb, m_name,
										   "It was a good hit!");
						}
						else if (msg_type == MSG_HIT_GREAT)
						{
							message_format(MSG_HIT_GREAT, 0, "The %s %s %s. %s", o_name, hit_verb, m_name,
										   "It was a great hit!");
						}
						else if (msg_type == MSG_HIT_SUPERB)
						{
							message_format(MSG_HIT_SUPERB, 0, "The %s %s %s. %s", o_name, hit_verb, m_name,
										   "It was a superb hit!");
						}
					}
					/* Hack -- Track this monster race */
					if (m_ptr->ml) monster_race_track(m_ptr->r_idx);

					/* Hack -- Track this monster */
					if (m_ptr->ml) health_track(cave_m_idx[y][x]);
				}

				/* Learn the bonuses */
				/* XXX Eddie This is messed up, better done for firing, */
				/* should use that method [split last] instead */
				/* check if inven_optimize removed what o_ptr referenced */
				if (object_similar(i_ptr, o_ptr, OSTACK_PACK))
					object_notice_attack_plusses(o_ptr);
				object_notice_attack_plusses(i_ptr);

				/* Complex message */
				if (p_ptr->wizard)
					msg_format("You do %d (out of %d) damage.",
							   tdam, m_ptr->hp);

				/* Hit the monster, check for death */
				if (mon_take_hit(cave_m_idx[y][x], tdam, &fear, note_dies))
				{
					/* Dead monster */
				}

				/* No death */
				else
				{
					/* Message */
					message_pain(cave_m_idx[y][x], tdam);

					/* Take note */
					if (fear && m_ptr->ml)
					{
						char m_name[80];

						/* Get the monster name (or "it") */
						monster_desc(m_name, sizeof(m_name), m_ptr, 0);

						/* Message */
						message_format(MSG_FLEE, m_ptr->r_idx,
						               "%^s flees in terror!", m_name);
					}
				}
			}

			/* Stop looking */
			break;
		}
	}

	/* Chance of breakage (during attacks) */
	j = (hit_body ? breakage_chance(i_ptr) : 0);

	/* Drop (or break) near that location */
	drop_near(i_ptr, j, y, x, TRUE);
}