/*	$NetBSD: shots.c,v 1.16 2021/05/02 12:50:45 rillig Exp $	*/
/*
 * Copyright (c) 1983-2003, Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * + Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * + Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 * + Neither the name of the University of California, San Francisco nor
 *   the names of its contributors may be used to endorse or promote
 *   products derived from this software without specific prior written
 *   permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/cdefs.h>
#ifndef lint
__RCSID("$NetBSD: shots.c,v 1.16 2021/05/02 12:50:45 rillig Exp $");
#endif /* not lint */

#include <err.h>
#include <signal.h>
#include <stdlib.h>
#include "hunt.h"

#define PLUS_DELTA(x, max)	if (x < max) x++; else x--
#define MINUS_DELTA(x, min)	if (x > min) x--; else x++

static void chkshot(BULLET *, BULLET *);
static void chkslime(BULLET *, BULLET *);
static void explshot(BULLET *, int, int);
static void find_under(BULLET *, BULLET *);
static bool iswall(int, int);
static void mark_boot(BULLET *);
static void mark_player(BULLET *);
#ifdef DRONE
static void move_drone(BULLET *);
#endif
static void move_flyer(PLAYER *);
static int move_normal_shot(BULLET *);
static void move_slime(BULLET *, int, BULLET *);
static void save_bullet(BULLET *);
static void zapshot(BULLET *, BULLET *);

/*
 * moveshots:
 *	Move the shots already in the air, taking explosions into account
 */
void
moveshots(void)
{
	BULLET *bp, *next;
	PLAYER *pp;
	int x, y;
	BULLET *blist;

	rollexpl();
	if (Bullets == NULL)
		goto ret;

	/*
	 * First we move through the bullet list BULSPD times, looking
	 * for things we may have run into.  If we do run into
	 * something, we set up the explosion and disappear, checking
	 * for damage to any player who got in the way.
	 */

	blist = Bullets;
	Bullets = NULL;
	for (bp = blist; bp != NULL; bp = next) {
		next = bp->b_next;
		x = bp->b_x;
		y = bp->b_y;
		Maze[y][x] = bp->b_over;
		for (pp = Player; pp < End_player; pp++)
			check(pp, y, x);
#ifdef MONITOR
		for (pp = Monitor; pp < End_monitor; pp++)
			check(pp, y, x);
#endif

		switch (bp->b_type) {
		  case SHOT:
		  case GRENADE:
		  case SATCHEL:
		  case BOMB:
			if (move_normal_shot(bp)) {
				bp->b_next = Bullets;
				Bullets = bp;
			}
			break;
#ifdef OOZE
		  case SLIME:
			if (bp->b_expl || move_normal_shot(bp)) {
				bp->b_next = Bullets;
				Bullets = bp;
			}
			break;
#endif
#ifdef DRONE
		  case DSHOT:
			if (move_drone(bp)) {
				bp->b_next = Bullets;
				Bullets = bp;
			}
			break;
#endif
		  default:
			bp->b_next = Bullets;
			Bullets = bp;
			break;
		}
	}

	blist = Bullets;
	Bullets = NULL;
	for (bp = blist; bp != NULL; bp = next) {
		next = bp->b_next;
		if (!bp->b_expl) {
			save_bullet(bp);
#ifdef MONITOR
			for (pp = Monitor; pp < End_monitor; pp++)
				check(pp, bp->b_y, bp->b_x);
#endif
#ifdef DRONE
			if (bp->b_type == DSHOT)
				for (pp = Player; pp < End_player; pp++)
					if (pp->p_scan >= 0)
						check(pp, bp->b_y, bp->b_x);
#endif
			continue;
		}

		chkshot(bp, next);
		free(bp);
	}

	for (pp = Player; pp < End_player; pp++)
		Maze[pp->p_y][pp->p_x] = pp->p_face;

ret:
#ifdef BOOTS
	for (pp = Boot; pp < &Boot[NBOOTS]; pp++)
		if (pp->p_flying >= 0)
			move_flyer(pp);
#endif
	for (pp = Player; pp < End_player; pp++) {
#ifdef FLY
		if (pp->p_flying >= 0)
			move_flyer(pp);
#endif
		sendcom(pp, REFRESH);	/* Flush out the explosions */
		look(pp);
		sendcom(pp, REFRESH);
	}
#ifdef MONITOR
	for (pp = Monitor; pp < End_monitor; pp++)
		sendcom(pp, REFRESH);
#endif

	return;
}

/*
 * move_normal_shot:
 *	Move a normal shot along its trajectory
 */
static int
move_normal_shot(BULLET *bp)
{
	int i, x, y;
	PLAYER *pp;

	for (i = 0; i < BULSPD; i++) {
		if (bp->b_expl)
			break;

		x = bp->b_x;
		y = bp->b_y;

		switch (bp->b_face) {
		  case LEFTS:
			x--;
			break;
		  case RIGHT:
			x++;
			break;
		  case ABOVE:
			y--;
			break;
		  case BELOW:
			y++;
			break;
		}

		switch (Maze[y][x]) {
		  case SHOT:
			if (rand_num(100) < 5) {
				zapshot(Bullets, bp);
				zapshot(bp->b_next, bp);
			}
			break;
		  case GRENADE:
			if (rand_num(100) < 10) {
				zapshot(Bullets, bp);
				zapshot(bp->b_next, bp);
			}
			break;
#ifdef REFLECT
		  case WALL4:	/* reflecting walls */
			switch (bp->b_face) {
			  case LEFTS:
				bp->b_face = BELOW;
				break;
			  case RIGHT:
				bp->b_face = ABOVE;
				break;
			  case ABOVE:
				bp->b_face = RIGHT;
				break;
			  case BELOW:
				bp->b_face = LEFTS;
				break;
			}
			Maze[y][x] = WALL5;
#ifdef MONITOR
			for (pp = Monitor; pp < End_monitor; pp++)
				check(pp, y, x);
#endif
			break;
		  case WALL5:
			switch (bp->b_face) {
			  case LEFTS:
				bp->b_face = ABOVE;
				break;
			  case RIGHT:
				bp->b_face = BELOW;
				break;
			  case ABOVE:
				bp->b_face = LEFTS;
				break;
			  case BELOW:
				bp->b_face = RIGHT;
				break;
			}
			Maze[y][x] = WALL4;
#ifdef MONITOR
			for (pp = Monitor; pp < End_monitor; pp++)
				check(pp, y, x);
#endif
			break;
#endif
#ifdef RANDOM
		  case DOOR:
			switch (rand_num(4)) {
			  case 0:
				bp->b_face = ABOVE;
				break;
			  case 1:
				bp->b_face = BELOW;
				break;
			  case 2:
				bp->b_face = LEFTS;
				break;
			  case 3:
				bp->b_face = RIGHT;
				break;
			}
			break;
#endif
#ifdef FLY
		  case FLYER:
			pp = play_at(y, x);
			message(pp, "Zing!");
			break;
#endif
		  case LEFTS:
		  case RIGHT:
		  case BELOW:
		  case ABOVE:
			/*
			 * give the person a chance to catch a
			 * grenade if s/he is facing it
			 */
			pp = play_at(y, x);
			pp->p_ident->i_shot += bp->b_charge;
			if (opposite(bp->b_face, Maze[y][x])) {
			    if (rand_num(100) < 10) {
				if (bp->b_owner != NULL)
					message(bp->b_owner,
					    "Your charge was absorbed!");
				if (bp->b_score != NULL)
					bp->b_score->i_robbed += bp->b_charge;
				pp->p_ammo += bp->b_charge;
				if (pp->p_damage + bp->b_size * MINDAM
				    > pp->p_damcap)
					pp->p_ident->i_saved++;
				message(pp, "Absorbed charge (good shield!)");
				pp->p_ident->i_absorbed += bp->b_charge;
				free(bp);
				(void) snprintf(Buf, sizeof(Buf),
						"%3d", pp->p_ammo);
				cgoto(pp, STAT_AMMO_ROW, STAT_VALUE_COL);
				outstr(pp, Buf, 3);
				return false;
			    }
			    pp->p_ident->i_faced += bp->b_charge;
			}
			/*
			 * Small chance that the bullet just misses the
			 * person.  If so, the bullet just goes on its
			 * merry way without exploding.
			 */
			if (rand_num(100) < 5) {
				pp->p_ident->i_ducked += bp->b_charge;
				if (pp->p_damage + bp->b_size * MINDAM
				    > pp->p_damcap)
					pp->p_ident->i_saved++;
				if (bp->b_score != NULL)
					bp->b_score->i_missed += bp->b_charge;
				message(pp, "Zing!");
				if (bp->b_owner == NULL)
					break;
				message(bp->b_owner, bp->b_score &&
					((bp->b_score->i_missed & 0x7) == 0x7) ?
					"My!  What a bad shot you are!" :
					"Missed him");
				break;
			}
			/*
			 * The shot hit that sucker!  Blow it up.
			 */
#ifndef RANDOM
			/* FALLTHROUGH */
		  case DOOR:
#endif
			/* FALLTHROUGH */
		  case WALL1:
		  case WALL2:
		  case WALL3:
			bp->b_expl = true;
			break;
		}

		bp->b_x = x;
		bp->b_y = y;
	}
	return true;
}

#ifdef DRONE
/*
 * move_drone:
 *	Move the drone to the next square
 */
static void
move_drone(BULLET *bp)
{
	int mask, count;
	int n, dir;
	PLAYER *pp;

	/*
	 * See if we can give someone a blast
	 */
	if (isplayer(Maze[bp->b_y][bp->b_x - 1])) {
		dir = WEST;
		goto drone_move;
	}
	if (isplayer(Maze[bp->b_y - 1][bp->b_x])) {
		dir = NORTH;
		goto drone_move;
	}
	if (isplayer(Maze[bp->b_y + 1][bp->b_x])) {
		dir = SOUTH;
		goto drone_move;
	}
	if (isplayer(Maze[bp->b_y][bp->b_x + 1])) {
		dir = EAST;
		goto drone_move;
	}

	/*
	 * Find out what directions are clear
	 */
	mask = count = 0;
	if (!iswall(bp->b_y, bp->b_x - 1))
		mask |= WEST, count++;
	if (!iswall(bp->b_y - 1, bp->b_x))
		mask |= NORTH, count++;
	if (!iswall(bp->b_y + 1, bp->b_x))
		mask |= SOUTH, count++;
	if (!iswall(bp->b_y, bp->b_x + 1))
		mask |= EAST, count++;

	/*
	 * All blocked up, just you wait
	 */
	if (count == 0)
		return true;

	/*
	 * Only one way to go.
	 */
	if (count == 1) {
		dir = mask;
		goto drone_move;
	}

	/*
	 * Get rid of the direction that we came from
	 */
	switch (bp->b_face) {
	  case LEFTS:
		if (mask & EAST)
			mask &= ~EAST, count--;
		break;
	  case RIGHT:
		if (mask & WEST)
			mask &= ~WEST, count--;
		break;
	  case ABOVE:
		if (mask & SOUTH)
			mask &= ~SOUTH, count--;
		break;
	  case BELOW:
		if (mask & NORTH)
			mask &= ~NORTH, count--;
		break;
	}

	/*
	 * Pick one of the remaining directions
	 */
	n = rand_num(count);
	if (n >= 0 && mask & NORTH)
		dir = NORTH, n--;
	if (n >= 0 && mask & SOUTH)
		dir = SOUTH, n--;
	if (n >= 0 && mask & EAST)
		dir = EAST, n--;
	if (n >= 0 && mask & WEST)
		dir = WEST, n--;

	/*
	 * Now that we know the direction of movement,
	 * just update the position of the drone
	 */
drone_move:
	switch (dir) {
	  case WEST:
		bp->b_x--;
		bp->b_face = LEFTS;
		break;
	  case EAST:
		bp->b_x++;
		bp->b_face = RIGHT;
		break;
	  case NORTH:
		bp->b_y--;
		bp->b_face = ABOVE;
		break;
	  case SOUTH:
		bp->b_y++;
		bp->b_face = BELOW;
		break;
	}
	switch (Maze[bp->b_y][bp->b_x]) {
	  case LEFTS:
	  case RIGHT:
	  case BELOW:
	  case ABOVE:
		/*
		 * give the person a chance to catch a
		 * drone if s/he is facing it
		 */
		if (rand_num(100) < 1 &&
		opposite(bp->b_face, Maze[bp->b_y][bp->b_x])) {
			pp = play_at(bp->b_y, bp->b_x);
			pp->p_ammo += bp->b_charge;
			message(pp, "**** Absorbed drone ****");
			free(bp);
			(void) snprintf(Buf, sizeof(buf), "%3d", pp->p_ammo);
			cgoto(pp, STAT_AMMO_ROW, STAT_VALUE_COL);
			outstr(pp, Buf, 3);
			return false;
		}
		bp->b_expl = true;
		break;
	}
	return true;
}
#endif

/*
 * save_bullet:
 *	Put this bullet back onto the bullet list
 */
static void
save_bullet(BULLET *bp)
{
	bp->b_over = Maze[bp->b_y][bp->b_x];
	switch (bp->b_over) {
	  case SHOT:
	  case GRENADE:
	  case SATCHEL:
	  case BOMB:
#ifdef OOZE
	  case SLIME:
#ifdef VOLCANO
	  case LAVA:
#endif
#endif
#ifdef DRONE
	  case DSHOT:
#endif
		find_under(Bullets, bp);
		break;
	}

	switch (bp->b_over) {
	  case LEFTS:
	  case RIGHT:
	  case ABOVE:
	  case BELOW:
#ifdef FLY
	  case FLYER:
#endif
		mark_player(bp);
		break;
#ifdef BOOTS
	  case BOOT:
	  case BOOT_PAIR:
		mark_boot(bp);
		break;
#endif

	  default:
		Maze[bp->b_y][bp->b_x] = bp->b_type;
		break;
	}

	bp->b_next = Bullets;
	Bullets = bp;
}

/*
 * move_flyer:
 *	Update the position of a player in flight
 */
static void
move_flyer(PLAYER *pp)
{
	int x, y;

	if (pp->p_undershot) {
		fixshots(pp->p_y, pp->p_x, pp->p_over);
		pp->p_undershot = false;
	}
	Maze[pp->p_y][pp->p_x] = pp->p_over;
	x = pp->p_x + pp->p_flyx;
	y = pp->p_y + pp->p_flyy;
	if (x < 1) {
		x = 1 - x;
		pp->p_flyx = -pp->p_flyx;
	}
	else if (x > WIDTH - 2) {
		x = (WIDTH - 2) - (x - (WIDTH - 2));
		pp->p_flyx = -pp->p_flyx;
	}
	if (y < 1) {
		y = 1 - y;
		pp->p_flyy = -pp->p_flyy;
	}
	else if (y > HEIGHT - 2) {
		y = (HEIGHT - 2) - (y - (HEIGHT - 2));
		pp->p_flyy = -pp->p_flyy;
	}
again:
	switch (Maze[y][x]) {
	  default:
		switch (rand_num(4)) {
		  case 0:
			PLUS_DELTA(x, WIDTH - 2);
			break;
		  case 1:
			MINUS_DELTA(x, 1);
			break;
		  case 2:
			PLUS_DELTA(y, HEIGHT - 2);
			break;
		  case 3:
			MINUS_DELTA(y, 1);
			break;
		}
		goto again;
	  case WALL1:
	  case WALL2:
	  case WALL3:
#ifdef REFLECT
	  case WALL4:
	  case WALL5:
#endif
#ifdef RANDOM
	  case DOOR:
#endif
		if (pp->p_flying == 0)
			pp->p_flying++;
		break;
	  case SPACE:
		break;
	}
	pp->p_y = y;
	pp->p_x = x;
	if (pp->p_flying-- == 0) {
#ifdef BOOTS
		if (pp->p_face != BOOT && pp->p_face != BOOT_PAIR) {
#endif
			checkdam(pp, NULL, NULL,
				rand_num(pp->p_damage / 5), FALL);
			pp->p_face = rand_dir();
			showstat(pp);
#ifdef BOOTS
		}
		else {
			if (Maze[y][x] == BOOT)
				pp->p_face = BOOT_PAIR;
			Maze[y][x] = SPACE;
		}
#endif
	}
	pp->p_over = Maze[y][x];
	Maze[y][x] = pp->p_face;
	showexpl(y, x, pp->p_face);
}

/*
 * chkshot
 *	Handle explosions
 */
static void
chkshot(BULLET *bp, BULLET *next)
{
	int y, x;
	int dy, dx, absdy;
	int delta, damage;
	char expl;
	PLAYER *pp;

	delta = 0;
	switch (bp->b_type) {
	  case SHOT:
	  case MINE:
	  case GRENADE:
	  case GMINE:
	  case SATCHEL:
	  case BOMB:
		delta = bp->b_size - 1;
		break;
#ifdef OOZE
	  case SLIME:
#ifdef VOLCANO
	  case LAVA:
#endif
		chkslime(bp, next);
		return;
#endif
#ifdef DRONE
	  case DSHOT:
		bp->b_type = SLIME;
		chkslime(bp, next);
		return;
#endif
	}
	for (y = bp->b_y - delta; y <= bp->b_y + delta; y++) {
		if (y < 0 || y >= HEIGHT)
			continue;
		dy = y - bp->b_y;
		absdy = (dy < 0) ? -dy : dy;
		for (x = bp->b_x - delta; x <= bp->b_x + delta; x++) {
			if (x < 0 || x >= WIDTH)
				continue;
			dx = x - bp->b_x;
			if (dx == 0)
				expl = (dy == 0) ? '*' : '|';
			else if (dy == 0)
				expl = '-';
			else if (dx == dy)
				expl = '\\';
			else if (dx == -dy)
				expl = '/';
			else
				expl = '*';
			showexpl(y, x, expl);
			switch (Maze[y][x]) {
			  case LEFTS:
			  case RIGHT:
			  case ABOVE:
			  case BELOW:
#ifdef FLY
			  case FLYER:
#endif
				if (dx < 0)
					dx = -dx;
				if (absdy > dx)
					damage = bp->b_size - absdy;
				else
					damage = bp->b_size - dx;
				pp = play_at(y, x);
				checkdam(pp, bp->b_owner, bp->b_score,
					damage * MINDAM, bp->b_type);
				break;
			  case GMINE:
			  case MINE:
				add_shot((Maze[y][x] == GMINE) ?
					GRENADE : SHOT,
					y, x, LEFTS,
					(Maze[y][x] == GMINE) ?
					GRENREQ : BULREQ,
					NULL, true, SPACE);
				Maze[y][x] = SPACE;
				break;
			}
		}
	}
}

#ifdef OOZE
/*
 * chkslime:
 *	handle slime shot exploding
 */
static void
chkslime(BULLET *bp, BULLET *next)
{
	BULLET	*nbp;

	switch (Maze[bp->b_y][bp->b_x]) {
	  case WALL1:
	  case WALL2:
	  case WALL3:
#ifdef REFLECT
	  case WALL4:
	  case WALL5:
#endif
#ifdef RANDOM
	  case DOOR:
#endif
		switch (bp->b_face) {
		  case LEFTS:
			bp->b_x++;
			break;
		  case RIGHT:
			bp->b_x--;
			break;
		  case ABOVE:
			bp->b_y++;
			break;
		  case BELOW:
			bp->b_y--;
			break;
		}
		break;
	}
	nbp = malloc(sizeof(*nbp));
	*nbp = *bp;
#ifdef VOLCANO
	move_slime(nbp, nbp->b_type == SLIME ? SLIMESPEED : LAVASPEED, next);
#else
	move_slime(nbp, SLIMESPEED, next);
#endif
}

/*
 * move_slime:
 *	move the given slime shot speed times and add it back if
 *	it hasn't fizzled yet
 */
static void
move_slime(BULLET *bp, int speed, BULLET *next)
{
	int i, j, dirmask, count;
	PLAYER *pp;
	BULLET *nbp;

	if (speed == 0) {
		if (bp->b_charge <= 0)
			free(bp);
		else
			save_bullet(bp);
		return;
	}

#ifdef VOLCANO
	showexpl(bp->b_y, bp->b_x, bp->b_type == LAVA ? LAVA : '*');
#else
	showexpl(bp->b_y, bp->b_x, '*');
#endif
	switch (Maze[bp->b_y][bp->b_x]) {
	  case LEFTS:
	  case RIGHT:
	  case ABOVE:
	  case BELOW:
#ifdef FLY
	  case FLYER:
#endif
		pp = play_at(bp->b_y, bp->b_x);
		message(pp, "You've been slimed.");
		checkdam(pp, bp->b_owner, bp->b_score, MINDAM, bp->b_type);
		break;
	  case SHOT:
	  case GRENADE:
	  case SATCHEL:
	  case BOMB:
#ifdef DRONE
	  case DSHOT:
#endif
		explshot(next, bp->b_y, bp->b_x);
		explshot(Bullets, bp->b_y, bp->b_x);
		break;
	}

	if (--bp->b_charge <= 0) {
		free(bp);
		return;
	}

	dirmask = 0;
	count = 0;
	switch (bp->b_face) {
	  case LEFTS:
		if (!iswall(bp->b_y, bp->b_x - 1))
			dirmask |= WEST, count++;
		if (!iswall(bp->b_y - 1, bp->b_x))
			dirmask |= NORTH, count++;
		if (!iswall(bp->b_y + 1, bp->b_x))
			dirmask |= SOUTH, count++;
		if (dirmask == 0)
			if (!iswall(bp->b_y, bp->b_x + 1))
				dirmask |= EAST, count++;
		break;
	  case RIGHT:
		if (!iswall(bp->b_y, bp->b_x + 1))
			dirmask |= EAST, count++;
		if (!iswall(bp->b_y - 1, bp->b_x))
			dirmask |= NORTH, count++;
		if (!iswall(bp->b_y + 1, bp->b_x))
			dirmask |= SOUTH, count++;
		if (dirmask == 0)
			if (!iswall(bp->b_y, bp->b_x - 1))
				dirmask |= WEST, count++;
		break;
	  case ABOVE:
		if (!iswall(bp->b_y - 1, bp->b_x))
			dirmask |= NORTH, count++;
		if (!iswall(bp->b_y, bp->b_x - 1))
			dirmask |= WEST, count++;
		if (!iswall(bp->b_y, bp->b_x + 1))
			dirmask |= EAST, count++;
		if (dirmask == 0)
			if (!iswall(bp->b_y + 1, bp->b_x))
				dirmask |= SOUTH, count++;
		break;
	  case BELOW:
		if (!iswall(bp->b_y + 1, bp->b_x))
			dirmask |= SOUTH, count++;
		if (!iswall(bp->b_y, bp->b_x - 1))
			dirmask |= WEST, count++;
		if (!iswall(bp->b_y, bp->b_x + 1))
			dirmask |= EAST, count++;
		if (dirmask == 0)
			if (!iswall(bp->b_y - 1, bp->b_x))
				dirmask |= NORTH, count++;
		break;
	}
	if (count == 0) {
		/*
		 * No place to go.  Just sit here for a while and wait
		 * for adjacent squares to clear out.
		 */
		save_bullet(bp);
		return;
	}
	if (bp->b_charge < count) {
		/* Only bp->b_charge paths may be taken */
		while (count > bp->b_charge) {
			if (dirmask & WEST)
				dirmask &= ~WEST;
			else if (dirmask & EAST)
				dirmask &= ~EAST;
			else if (dirmask & NORTH)
				dirmask &= ~NORTH;
			else if (dirmask & SOUTH)
				dirmask &= ~SOUTH;
			count--;
		}
	}

	i = bp->b_charge / count;
	j = bp->b_charge % count;
	if (dirmask & WEST) {
		count--;
		nbp = create_shot(bp->b_type, bp->b_y, bp->b_x - 1, LEFTS,
			i, bp->b_size, bp->b_owner, bp->b_score, true, SPACE);
		move_slime(nbp, speed - 1, next);
	}
	if (dirmask & EAST) {
		count--;
		nbp = create_shot(bp->b_type, bp->b_y, bp->b_x + 1, RIGHT,
			(count < j) ? i + 1 : i, bp->b_size, bp->b_owner,
			bp->b_score, true, SPACE);
		move_slime(nbp, speed - 1, next);
	}
	if (dirmask & NORTH) {
		count--;
		nbp = create_shot(bp->b_type, bp->b_y - 1, bp->b_x, ABOVE,
			(count < j) ? i + 1 : i, bp->b_size, bp->b_owner,
			bp->b_score, true, SPACE);
		move_slime(nbp, speed - 1, next);
	}
	if (dirmask & SOUTH) {
		count--;
		nbp = create_shot(bp->b_type, bp->b_y + 1, bp->b_x, BELOW,
			(count < j) ? i + 1 : i, bp->b_size, bp->b_owner,
			bp->b_score, true, SPACE);
		move_slime(nbp, speed - 1, next);
	}

	free(bp);
}

/*
 * iswall:
 *	returns whether the given location is a wall
 */
static bool
iswall(int y, int x)
{
	if (y < 0 || x < 0 || y >= HEIGHT || x >= WIDTH)
		return true;
	switch (Maze[y][x]) {
	  case WALL1:
	  case WALL2:
	  case WALL3:
#ifdef REFLECT
	  case WALL4:
	  case WALL5:
#endif
#ifdef RANDOM
	  case DOOR:
#endif
#ifdef OOZE
	  case SLIME:
#ifdef VOLCANO
	  case LAVA:
#endif
#endif
		return true;
	}
	return false;
}
#endif

/*
 * zapshot:
 *	Take a shot out of the air.
 */
static void
zapshot(BULLET *blist, BULLET *obp)
{
	BULLET *bp;
	bool explode;

	explode = false;
	for (bp = blist; bp != NULL; bp = bp->b_next) {
		if (bp->b_x != obp->b_x || bp->b_y != obp->b_y)
			continue;
		if (bp->b_face == obp->b_face)
			continue;
		explode = true;
		break;
	}
	if (!explode)
		return;
	explshot(blist, obp->b_y, obp->b_x);
}

/*
 * explshot -
 *	Make all shots at this location blow up
 */
static void
explshot(BULLET *blist, int y, int x)
{
	BULLET *bp;

	for (bp = blist; bp != NULL; bp = bp->b_next)
		if (bp->b_x == x && bp->b_y == y) {
			bp->b_expl = true;
			if (bp->b_owner != NULL)
				message(bp->b_owner, "Shot intercepted");
		}
}

/*
 * play_at:
 *	Return a pointer to the player at the given location
 */
PLAYER *
play_at(int y, int x)
{
	PLAYER *pp;

	for (pp = Player; pp < End_player; pp++)
		if (pp->p_x == x && pp->p_y == y)
			return pp;
	errx(1, "driver: couldn't find player at (%d,%d)", x, y);
	/* NOTREACHED */
}

/*
 * opposite:
 *	Return true if the bullet direction faces the opposite direction
 *	of the player in the maze
 */
bool
opposite(int face, char dir)
{
	switch (face) {
	  case LEFTS:
		return (dir == RIGHT);
	  case RIGHT:
		return (dir == LEFTS);
	  case ABOVE:
		return (dir == BELOW);
	  case BELOW:
		return (dir == ABOVE);
	  default:
		return false;
	}
}

/*
 * is_bullet:
 *	Is there a bullet at the given coordinates?  If so, return
 *	a pointer to the bullet, otherwise return NULL
 */
BULLET *
is_bullet(int y, int x)
{
	BULLET *bp;

	for (bp = Bullets; bp != NULL; bp = bp->b_next)
		if (bp->b_y == y && bp->b_x == x)
			return bp;
	return NULL;
}

/*
 * fixshots:
 *	change the underlying character of the shots at a location
 *	to the given character.
 */
void
fixshots(int y, int x, char over)
{
	BULLET *bp;

	for (bp = Bullets; bp != NULL; bp = bp->b_next)
		if (bp->b_y == y && bp->b_x == x)
			bp->b_over = over;
}

/*
 * find_under:
 *	find the underlying character for a bullet when it lands
 *	on another bullet.
 */
static void
find_under(BULLET *blist, BULLET *bp)
{
	BULLET *nbp;

	for (nbp = blist; nbp != NULL; nbp = nbp->b_next)
		if (bp->b_y == nbp->b_y && bp->b_x == nbp->b_x) {
			bp->b_over = nbp->b_over;
			break;
		}
}

/*
 * mark_player:
 *	mark a player as under a shot
 */
static void
mark_player(BULLET *bp)
{
	PLAYER *pp;

	for (pp = Player; pp < End_player; pp++)
		if (pp->p_y == bp->b_y && pp->p_x == bp->b_x) {
			pp->p_undershot = true;
			break;
		}
}

#ifdef BOOTS
/*
 * mark_boot:
 *	mark a boot as under a shot
 */
static void
mark_boot(BULLET *bp)
{
	PLAYER *pp;

	for (pp = Boot; pp < &Boot[NBOOTS]; pp++)
		if (pp->p_y == bp->b_y && pp->p_x == bp->b_x) {
			pp->p_undershot = true;
			break;
		}
}
#endif
