root/games/larn/movem.c
/*      $NetBSD: movem.c,v 1.10 2019/02/03 03:19:25 mrg Exp $   */

/*
 * movem.c (move monster)               Larn is copyrighted 1986 by Noah Morgan.
 *
 * Here are the functions in this file:
 *
 * movemonst()          Routine to move the monsters toward the player
 * movemt(x,y)          Function to move a monster at (x,y) -- must determine where
 * mmove(x,y,xd,yd)     Function to actually perform the monster movement
 * movsphere()          Function to look for and move spheres of annihilation
 */
#include <sys/cdefs.h>
#ifndef lint
__RCSID("$NetBSD: movem.c,v 1.10 2019/02/03 03:19:25 mrg Exp $");
#endif                          /* not lint */

#include "header.h"
#include "extern.h"

static void movemt(int, int);
static void mmove(int, int, int, int);
static void movsphere(void);

/*
 * movemonst()          Routine to move the monsters toward the player
 *
 * This routine has the responsibility to determine which monsters are to
 * move, and call movemt() to do the move.
 * Returns no value.
 */
static short    w1[9], w1x[9], w1y[9];
static int      tmp1, tmp2, tmp3, tmp4, distance;
void
movemonst(void)
{
        int    i, j;
        if (c[TIMESTOP])
                return;         /* no action if time is stopped */
        if (c[HASTESELF])
                if ((c[HASTESELF] & 1) == 0)
                        return;
        if (spheres)
                movsphere();    /* move the spheres of annihilation if any */
        if (c[HOLDMONST])
                return;         /* no action if monsters are held */

        if (c[AGGRAVATE]) {     /* determine window of monsters to move */
                tmp1 = playery - 5;
                tmp2 = playery + 6;
                tmp3 = playerx - 10;
                tmp4 = playerx + 11;
                distance = 40;  /* depth of intelligent monster movement */
        } else {
                tmp1 = playery - 3;
                tmp2 = playery + 4;
                tmp3 = playerx - 5;
                tmp4 = playerx + 6;
                distance = 17;  /* depth of intelligent monster movement */
        }

        if (level == 0) {       /* if on outside level monsters can move in
                                 * perimeter */
                if (tmp1 < 0)
                        tmp1 = 0;
                if (tmp2 > MAXY)
                        tmp2 = MAXY;
                if (tmp3 < 0)
                        tmp3 = 0;
                if (tmp4 > MAXX)
                        tmp4 = MAXX;
        } else {                /* if in a dungeon monsters can't be on the
                                 * perimeter (wall there) */
                if (tmp1 < 1)
                        tmp1 = 1;
                if (tmp2 > MAXY - 1)
                        tmp2 = MAXY - 1;
                if (tmp3 < 1)
                        tmp3 = 1;
                if (tmp4 > MAXX - 1)
                        tmp4 = MAXX - 1;
        }

        for (j = tmp1; j < tmp2; j++)   /* now reset monster moved flags */
                for (i = tmp3; i < tmp4; i++)
                        moved[i][j] = 0;
        moved[lasthx][lasthy] = 0;

        if (c[AGGRAVATE] || !c[STEALTH]) {      /* who gets moved? split for
                                                 * efficiency */
                for (j = tmp1; j < tmp2; j++)   /* look thru all locations in
                                                 * window */
                        for (i = tmp3; i < tmp4; i++)
                                if (mitem[i][j])        /* if there is a monster
                                                         * to move */
                                        if (moved[i][j] == 0)   /* if it has not already
                                                                 * been moved */
                                                movemt(i, j);   /* go and move the
                                                                 * monster */
        } else {                /* not aggravated and not stealth */
                for (j = tmp1; j < tmp2; j++)   /* look thru all locations in
                                                 * window */
                        for (i = tmp3; i < tmp4; i++)
                                if (mitem[i][j])        /* if there is a monster
                                                         * to move */
                                        if (moved[i][j] == 0)   /* if it has not already
                                                                 * been moved */
                                                if (stealth[i][j])      /* if it is asleep due
                                                                         * to stealth */
                                                        movemt(i, j);   /* go and move the
                                                                         * monster */
        }

        if (mitem[lasthx][lasthy]) {    /* now move monster last hit by
                                         * player if not already moved */
                if (moved[lasthx][lasthy] == 0) {       /* if it has not already
                                                         * been moved */
                        movemt(lasthx, lasthy);
                        lasthx = w1x[0];
                        lasthy = w1y[0];
                }
        }
}

/*
 * movemt(x,y)          Function to move a monster at (x,y) -- must determine where
 *      int x,y;
 *
 * This routine is responsible for determining where one monster at (x,y) will
 * move to.  Enter with the monsters coordinates in (x,y).
 * Returns no value.
 */
static int      tmpitem, xl, xh, yl, yh;
static void
movemt(int i, int j)
{
        int    k, m, z, tmp, xtmp, ytmp, monst;
        switch (monst = mitem[i][j]) {  /* for half speed monsters */
        case TROGLODYTE:
        case HOBGOBLIN:
        case METAMORPH:
        case XVART:
        case INVISIBLESTALKER:
        case ICELIZARD:
                if ((gltime & 1) == 1)
                        return;
        };

        if (c[SCAREMONST]) {    /* choose destination randomly if scared */
                if ((xl = i + rnd(3) - 2) < 0)
                        xl = 0;
                if (xl >= MAXX)
                        xl = MAXX - 1;
                if ((yl = j + rnd(3) - 2) < 0)
                        yl = 0;
                if (yl >= MAXY)
                        yl = MAXY - 1;
                if ((tmp = item[xl][yl]) != OWALL)
                        if (mitem[xl][yl] == 0)
                                if ((mitem[i][j] != VAMPIRE) || (tmpitem != OMIRROR))
                                        if (tmp != OCLOSEDDOOR)
                                                mmove(i, j, xl, yl);
                return;
        }
        if (monster[monst].intelligence > 10 - c[HARDGAME]) {   /* if smart monster */
                /* intelligent movement here -- first setup screen array */
                xl = tmp3 - 2;
                yl = tmp1 - 2;
                xh = tmp4 + 2;
                yh = tmp2 + 2;
                vxy(&xl, &yl);
                vxy(&xh, &yh);
                for (k = yl; k < yh; k++)
                        for (m = xl; m < xh; m++) {
                                switch (item[m][k]) {
                                case OWALL:
                                case OPIT:
                                case OTRAPARROW:
                                case ODARTRAP:
                                case OCLOSEDDOOR:
                                case OTRAPDOOR:
                                case OTELEPORTER:
                        smm:            screen[m][k] = 127;
                                        break;
                                case OMIRROR:
                                        if (mitem[m][k] == VAMPIRE)
                                                goto smm;
                                        /* FALLTHROUGH */
                                default:
                                        screen[m][k] = 0;
                                        break;
                                };
                        }
                screen[playerx][playery] = 1;

                /*
                 * now perform proximity ripple from playerx,playery to
                 * monster
                 */
                xl = tmp3 - 1;
                yl = tmp1 - 1;
                xh = tmp4 + 1;
                yh = tmp2 + 1;
                vxy(&xl, &yl);
                vxy(&xh, &yh);
                for (tmp = 1; tmp < distance; tmp++)    /* only up to 20 squares
                                                         * away */
                        for (k = yl; k < yh; k++)
                                for (m = xl; m < xh; m++)
                                        if (screen[m][k] == tmp)        /* if find proximity n
                                                                         * advance it */
                                                for (z = 1; z < 9; z++) {       /* go around in a circle */
                                                        if (screen[xtmp = m + diroffx[z]][ytmp = k + diroffy[z]] == 0)
                                                                screen[xtmp][ytmp] = tmp + 1;
                                                        if (xtmp == i && ytmp == j)
                                                                goto out;
                                                }

out:            if (tmp < distance)     /* did find connectivity */
                        /* now select lowest value around playerx,playery */
                        for (z = 1; z < 9; z++) /* go around in a circle */
                                if (screen[xl = i + diroffx[z]][yl = j + diroffy[z]] == tmp)
                                        if (!mitem[xl][yl]) {
                                                mmove(i, j, w1x[0] = xl, w1y[0] = yl);
                                                return;
                                        }
        }
        /* dumb monsters move here */
        xl = i - 1;
        yl = j - 1;
        xh = i + 2;
        yh = j + 2;
        if (i < playerx)
                xl++;
        else if (i > playerx)
                --xh;
        if (j < playery)
                yl++;
        else if (j > playery)
                --yh;
        for (k = 0; k < 9; k++)
                w1[k] = 10000;

        for (k = xl; k < xh; k++)
                for (m = yl; m < yh; m++) {     /* for each square compute
                                                 * distance to player */
                        tmp = k - i + 4 + 3 * (m - j);
                        tmpitem = item[k][m];
                        if (tmpitem != OWALL || (k == playerx && m == playery))
                                if (mitem[k][m] == 0)
                                        if ((mitem[i][j] != VAMPIRE) || (tmpitem != OMIRROR))
                                                if (tmpitem != OCLOSEDDOOR) {
                                                        w1[tmp] = (playerx - k) * (playerx - k) + (playery - m) * (playery - m);
                                                        w1x[tmp] = k;
                                                        w1y[tmp] = m;
                                                }
                }

        tmp = 0;
        for (k = 1; k < 9; k++)
                if (w1[tmp] > w1[k])
                        tmp = k;

        if (w1[tmp] < 10000)
                if ((i != w1x[tmp]) || (j != w1y[tmp]))
                        mmove(i, j, w1x[tmp], w1y[tmp]);
}

/*
 * mmove(x,y,xd,yd)     Function to actually perform the monster movement
 *      int x,y,xd,yd;
 *
 * Enter with the from coordinates in (x,y) and the destination coordinates
 * in (xd,yd).
 */
static void
mmove(int aa, int bb, int cc, int dd)
{
        int    tmp, i, flag;
        const char *who = NULL;

        flag = 0;               /* set to 1 if monster hit by arrow trap */
        if ((cc == playerx) && (dd == playery)) {
                hitplayer(aa, bb);
                moved[aa][bb] = 1;
                return;
        }
        i = item[cc][dd];
        if ((i == OPIT) || (i == OTRAPDOOR))
                switch (mitem[aa][bb]) {
                case SPIRITNAGA:
                case PLATINUMDRAGON:
                case WRAITH:
                case VAMPIRE:
                case SILVERDRAGON:
                case POLTERGEIST:
                case DEMONLORD:
                case DEMONLORD + 1:
                case DEMONLORD + 2:
                case DEMONLORD + 3:
                case DEMONLORD + 4:
                case DEMONLORD + 5:
                case DEMONLORD + 6:
                case DEMONPRINCE:
                        break;

                default:
                        mitem[aa][bb] = 0;      /* fell in a pit or trapdoor */
                };
        tmp = mitem[cc][dd] = mitem[aa][bb];
        if (i == OANNIHILATION) {
                if (tmp >= DEMONLORD + 3) {     /* demons dispel spheres */
                        cursors();
                        lprintf("\nThe %s dispels the sphere!", monster[tmp].name);
                        rmsphere(cc, dd);       /* delete the sphere */
                } else
                        i = tmp = mitem[cc][dd] = 0;
        }
        stealth[cc][dd] = 1;
        if ((hitp[cc][dd] = hitp[aa][bb]) < 0)
                hitp[cc][dd] = 1;
        mitem[aa][bb] = 0;
        moved[cc][dd] = 1;
        if (tmp == LEPRECHAUN)
                switch (i) {
                case OGOLDPILE:
                case OMAXGOLD:
                case OKGOLD:
                case ODGOLD:
                case ODIAMOND:
                case ORUBY:
                case OEMERALD:
                case OSAPPHIRE:
                        item[cc][dd] = 0;       /* leprechaun takes gold */
                };

        if (tmp == TROLL)       /* if a troll regenerate him */
                if ((gltime & 1) == 0)
                        if (monster[tmp].hitpoints > hitp[cc][dd])
                                hitp[cc][dd]++;

        if (i == OTRAPARROW) {  /* arrow hits monster */
                who = "An arrow";
                if ((hitp[cc][dd] -= rnd(10) + level) <= 0) {
                        mitem[cc][dd] = 0;
                        flag = 2;
                } else
                        flag = 1;
        }
        if (i == ODARTRAP) {    /* dart hits monster */
                who = "A dart";
                if ((hitp[cc][dd] -= rnd(6)) <= 0) {
                        mitem[cc][dd] = 0;
                        flag = 2;
                } else
                        flag = 1;
        }
        if (i == OTELEPORTER) { /* monster hits teleport trap */
                flag = 3;
                fillmonst(mitem[cc][dd]);
                mitem[cc][dd] = 0;
        }
        if (c[BLINDCOUNT])
                return;         /* if blind don't show where monsters are        */
        if (know[cc][dd] & 1) {
                if (flag)
                        cursors();
                switch (flag) {
                case 1:
                        lprintf("\n%s hits the %s", who, monster[tmp].name);
                        beep();
                        break;
                case 2:
                        lprintf("\n%s hits and kills the %s",
                            who, monster[tmp].name);
                        beep();
                        break;
                case 3:
                        lprintf("\nThe %s gets teleported", monster[tmp].name);
                        beep();
                        break;
                }
        }
        /*
         * if (yrepcount>1) { know[aa][bb] &= 2;  know[cc][dd] &= 2; return;
         * }
         */
        if (know[aa][bb] & 1)
                show1cell(aa, bb);
        if (know[cc][dd] & 1)
                show1cell(cc, dd);
}

/*
 * movsphere()  Function to look for and move spheres of annihilation
 *
 * This function works on the sphere linked list, first duplicating the list
 * (the act of moving changes the list), then processing each sphere in order
 * to move it.  They eat anything in their way, including stairs, volcanic
 * shafts, potions, etc, except for upper level demons, who can dispel
 * spheres.
 * No value is returned.
 */
#define SPHMAX 20               /* maximum number of spheres movsphere can
                                 * handle */
static void
movsphere(void)
{
        int    x, y, dir, len;
        struct sphere *sp, *sp2;
        struct sphere   sph[SPHMAX];

        /* first duplicate sphere list */
        for (sp = 0, x = 0, sp2 = spheres; sp2; sp2 = sp2->p)   /* look through sphere
                                                                 * list */
                if (sp2->lev == level) {        /* only if this level */
                        sph[x] = *sp2;
                        sph[x++].p = 0; /* copy the struct */
                        if (x > 1)
                                sph[x - 2].p = &sph[x - 1];     /* link pointers */
                }
        if (x)
                sp = sph;       /* if any spheres, point to them */
        else
                return;         /* no spheres */

        for (sp = sph; sp; sp = sp->p) {        /* look through sphere list */
                x = sp->x;
                y = sp->y;
                if (item[x][y] != OANNIHILATION)
                        continue;       /* not really there */
                if (--(sp->lifetime) < 0) {     /* has sphere run out of gas? */
                        rmsphere(x, y); /* delete sphere */
                        continue;
                }
                switch (rnd((int) max(7, c[INTELLIGENCE] >> 1))) {      /* time to move the
                                                                         * sphere */
                case 1:
                case 2: /* change direction to a random one */
                        sp->dir = rnd(8);
                        /* FALLTHROUGH */
                default:        /* move in normal direction */
                        dir = sp->dir;
                        len = sp->lifetime;
                        rmsphere(x, y);
                        newsphere(x + diroffx[dir], y + diroffy[dir], dir, len);
                };
        }
}