root/games/hunt/huntd/shots.c
/*      $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