view xrogue/chase.c @ 265:7fcb2f9f57e6

Mention UltraRogue in the top-level README.
author John "Elwin" Edwards
date Sun, 19 Feb 2017 19:54:17 -0500
parents f54901b9c39b
children e52a8a7ad4c5
line wrap: on
line source

/*
    chase.c  -  Code for one object to chase another

    XRogue: Expeditions into the Dungeons of Doom
    Copyright (C) 1991 Robert Pietkivitch
    All rights reserved.
    
    Based on "Advanced Rogue"
    Copyright (C) 1984, 1985 Michael Morgan, Ken Dalka and AT&T
    All rights reserved.

    Based on "Rogue: Exploring the Dungeons of Doom"
    Copyright (C) 1980, 1981 Michael Toy, Ken Arnold and Glenn Wichman
    All rights reserved.
    
    See the file LICENSE.TXT for full copyright and licensing information.
*/

#include <ctype.h>
#include <curses.h>
#include <limits.h>
#include <stdlib.h>
#include "rogue.h"

bool straight_shot(int ery, int erx, int eey, int eex, coord *shooting);

/*
 * Canblink checks if the monster can teleport (blink).  If so, it will
 * try to blink the monster next to the player.
 */

bool
can_blink(struct thing *tp)
{
    register int y, x, index=9;
    coord tryp; /* To hold the coordinates for use in diag_ok */
    bool spots[9], found_one=FALSE;

    /*
     * First, can the monster even blink?  And if so, there is only a 50%
     * chance that it will do so.  And it won't blink if it is running or
     * held.
     */
    if (off(*tp, CANBLINK) || (on(*tp, ISHELD)) ||
        on(*tp, ISFLEE) ||
        tp->t_action == A_FREEZE ||
        (rnd(12) < 6)) return(FALSE);


    /* Initialize the spots as illegal */
    do {
        spots[--index] = FALSE;
    } while (index > 0);

    /* Find a suitable spot next to the player */
    for (y=hero.y-1; y<hero.y+2; y++)
        for (x=hero.x-1; x<hero.x+2; x++, index++) {
            /* Make sure x coordinate is in range and that we are
             * not at the player's position
             */
            if (x<0 || x >= cols || index == 4) continue;

            /* Is it OK to move there? */
            if (step_ok(y, x, NOMONST, tp) &&
                (!isatrap(mvwinch(cw, y, x)) ||
                  rnd(10) >= tp->t_stats.s_intel ||
                  on(*tp, ISFLY))) {
                /* OK, we can go here.  But don't go there if
                 * monster can't get at player from there
                 */
                tryp.y = y;
                tryp.x = x;
                if (diag_ok(&tryp, &hero, tp)) {
                    spots[index] = TRUE;
                    found_one = TRUE;
                }
            }
        }

    /* If we found one, go to it */
    if (found_one) {
        unsigned char rch;       /* What's really where the creatures moves to */

        /* Find a legal spot */
        while (spots[index=rnd(9)] == FALSE) continue;

        /* Get the coordinates */
        y = hero.y + (index/3) - 1;
        x = hero.x + (index % 3) - 1;

        /* Move the monster from the old space */
        mvwaddch(cw, tp->t_pos.y, tp->t_pos.x, tp->t_oldch);

        /* Move it to the new space */
        tp->t_oldch = mvwinch(cw, y, x);

        /* Display the creature if our hero can see it */
        if (cansee(y, x) &&
            off(*tp, ISINWALL) &&
            !invisible(tp))
            mvwaddch(cw, y, x, tp->t_type);

        /* Fix the monster window */
        mvwaddch(mw, tp->t_pos.y, tp->t_pos.x, ' '); /* Clear old position */
        mvwaddch(mw, y, x, tp->t_type);

        /* Record the new position */
        tp->t_pos.y = y;
        tp->t_pos.x = x;

        /* If the monster is on a trap, trap it */
        rch = mvinch(y, x);
        if (isatrap(rch)) {
            if (cansee(y, x)) tp->t_oldch = rch;
            be_trapped(tp, &(tp->t_pos));
        }
    }

    return(found_one);
}

/* 
 * Can_shoot determines if the monster (er) has a direct line of shot 
 * at the prey (ee).  If so, it returns the direction in which to shoot.
 */

int
can_shoot(coord *er, coord *ee, coord *shoot_dir)
{
    /* 
     * They must be in the same room or very close (at door)
     */
    if (roomin(er) != roomin(ee) && DISTANCE(er->y,er->x,ee->y,ee->x) > 1)
    {
        shoot_dir->x = shoot_dir->y = 0;
        return(-1);
    }

    /* Do we have a straight shot? */
    if (!straight_shot(er->y, er->x, ee->y, ee->x, shoot_dir)) 
    {
        shoot_dir->x = shoot_dir->y = 0;
        return(-2);
    }
    else 
        return(0);
}

/*
 * chase:
 *      Find the spot for the chaser(er) to move closer to the
 *      chasee(ee).  Rer is the room of the chaser, and ree is the
 *      room of the creature being chased (chasee).
 * 	flee: True if destination (ee) is player and monster is running away
 *	or the player is in a wall and the monster can't get to it
 */

void
chase(struct thing *tp, coord *ee, struct room *rer, struct room *ree, 
      bool flee)
{
    int dist, thisdist, monst_dist = INT_MAX; 
    register coord *er = &tp->t_pos; 
    struct thing *prey;                 /* What we are chasing */
    coord ch_ret;                       /* Where chasing takes you */
    unsigned char ch, mch;
    bool next_player = FALSE;

    /* 
     * set the distance from the chas(er) to the chas(ee) here and then
     * we won't have to reset it unless the chas(er) moves (instead of shoots)
     */
    dist = DISTANCE(er->y, er->x, ee->y, ee->x);

    /*
     * See if our destination is a monster or player.  If so, make "prey" point
     * to it.
     */
    if (ce(hero, *ee)) prey = &player;  /* Is it the player? */
    else if (tp->t_dest && ce(*(tp->t_dest), *ee)) {    /* Is it a monster? */
        struct linked_list *item;

        /* What is the monster we're chasing? */
        item = find_mons(ee->y, ee->x);
        if (item != NULL) prey = THINGPTR(item);
        else prey = NULL;
    }
    else prey = NULL;

    /* We will use at least one movement period */
    tp->t_no_move = movement(tp);
    if (on(*tp, ISFLY)) /* If the creature is flying, speed it up */
        tp->t_no_move /= 2;

    /*
     * If the thing is confused or it can't see the player,
     * let it move randomly. 
     */
    if ((on(*tp, ISHUH) && rnd(10) < 8) ||
        (prey && on(*prey, ISINVIS) && off(*tp, CANSEE))) { /* invisible prey */
        /*
         * get a valid random move
         */
        tp->t_newpos = rndmove(tp);
        dist = DISTANCE(tp->t_newpos.y, tp->t_newpos.x, ee->y, ee->x);
    }

    /*
     * Otherwise, find the empty spot next to the chaser that is
     * closest to the chasee.
     */
    else {
        register int ey, ex, x, y;
        int dist_to_old = INT_MIN; /* Dist from goal to old position */

        /*
         * This will eventually hold where we move to get closer
         * If we can't find an empty spot, we stay where we are.
         */
        dist = flee ? 0 : INT_MAX;
        ch_ret = *er;

        /* Are we at our goal already? */
        if (!flee && ce(ch_ret, *ee)) {
            turn_off(*tp, ISRUN);       /* So stop running! */
            return;
        }

        ey = er->y + 1;
        ex = er->x + 1;

        /* Check all possible moves */
        for (x = er->x - 1; x <= ex; x++) {
            if (x < 0 || x >= cols) /* Don't try off the board */
                continue;
            for (y = er->y - 1; y <= ey; y++) {
                coord tryp;

                if ((y < 1) || (y >= lines - 2)) /* Don't try off the board */
                    continue;

                /* Don't try the player if not going after the player */
                if ((flee || !ce(hero, *ee) || on(*tp, ISFRIENDLY)) &&
                    x == hero.x && y == hero.y) {
                    next_player = TRUE;
                    continue;
                }

                tryp.x = x;
                tryp.y = y;

                /* Is there a monster on this spot closer to our goal?
                 * Don't look in our spot or where we were.
                 */
                if (!ce(tryp, *er) && !ce(tryp, tp->t_oldpos) &&
                    isalpha(mch = mvwinch(mw, y, x))) {
                    int test_dist;

                    test_dist = DISTANCE(y, x, ee->y, ee->x);
                    if (test_dist <= 25 &&   /* Let's be fairly close */
                        test_dist < monst_dist) {
                        /* Could we really move there? */
                        mvwaddch(mw, y, x, ' '); /* Temporarily blank monst */
                        if (diag_ok(er, &tryp, tp)) monst_dist = test_dist;
                        mvwaddch(mw, y, x, mch); /* Restore monster */
                    }
                }

                /* Can we move onto the spot? */        
                if (!diag_ok(er, &tryp, tp)) continue;

                ch = mvwinch(cw, y, x); /* Screen character */

                /*
                 * Stepping on player is NOT okay if we are fleeing.
                 * If we are friendly to the player and there is a monster
                 * in the way that is not of our race, it is okay to move
                 * there.
                 */
                if (step_ok(y, x, FIGHTOK, tp) &&
                    (off(*tp, ISFLEE) || ch != PLAYER))
                {
                    /*
                     * If it is a trap, an intelligent monster may not
                     * step on it (unless our hero is on top!)
                     */
                    if ((isatrap(ch))                   && 
                        (rnd(10) < tp->t_stats.s_intel) &&
                        (!on(*tp, ISFLY))               &&
                        (y != hero.y || x != hero.x)) 
                            continue;

                    /*
                     * OK -- this place counts
                     */
                    thisdist = DISTANCE(y, x, ee->y, ee->x);

                    /* Adjust distance if we are being shot at */
                    if (tp->t_wasshot && tp->t_stats.s_intel > 5 &&
                        prey != NULL) {
                        /* Move out of line of sight */
                        if (straight_shot(tryp.y, tryp.x, ee->y, ee->x, (coord *)NULL)) {
                            if (flee) thisdist -= SHOTPENALTY;
                            else thisdist += SHOTPENALTY;
                        }

                        /* But do we want to leave the room? */
                        else if (rer && rer == ree && ch == DOOR)
                            thisdist += DOORPENALTY;
                    }

                    /* Don't move to the last position if we can help it
                     * (unless out prey just moved there)
                     */
                    if (ce(tryp, tp->t_oldpos) && (flee || !ce(tryp, hero)))
                        dist_to_old = thisdist;

                    else if ((flee && (thisdist > dist)) ||
                        (!flee && (thisdist < dist)))
                    {
                        ch_ret = tryp;
                        dist = thisdist;
                    }
                }
            }
        }

        /* If we aren't trying to get the player, but he is in our way,
         * hit him (unless we have been turned or are friendly).  next_player
         * being TRUE -> we are next to the player but don't want to hit him.
         *
         * If we are friendly to the player, following him, and standing next
         * to him, we will try to help him out in battle.
         */
        if (next_player && off(*tp, WASTURNED)) {
            if (off(*tp, ISFRIENDLY) &&
                ((flee && ce(ch_ret, *er)) ||
                 (!flee && DISTANCE(er->y, er->x, ee->y, ee->x) < dist)) &&
                step_ok(tp->t_dest->y, tp->t_dest->x, NOMONST, tp)) {
                /* Okay to hit player */
                debug("Switching to hero.");
                tp->t_newpos = hero;
                tp->t_action = A_MOVE;
                return;
            }
            else if (on(*tp, ISFRIENDLY) && !flee && ce(*ee, hero)) {
                /*
                 * Look all around the player.  If there is a fightable
                 * creature next to both of us, hit it.  Otherwise, if
                 * there is a fightable creature next to the player, try
                 * to move next to it.
                 */
                dist = INT_MAX;
                for (x = hero.x - 1; x <= hero.x + 1; x++) {
                    if (x < 0 || x >= cols) /* Don't try off the board */
                        continue;
                    for (y = hero.y - 1; y <= hero.y + 1; y++) {
                        if ((y < 1) || (y >= lines - 2)) /* Stay on the board */
                            continue;

                        /* Is there a fightable monster here? */
                        if (isalpha(mvwinch(mw, y, x)) &&
                            step_ok(y, x, FIGHTOK, tp) &&
                            off(*tp, ISSTONE)) {
                            thisdist = DISTANCE(er->y, er->x, y, x);
                            if (thisdist < dist) {
                                dist = thisdist;
                                ch_ret.y = y;
                                ch_ret.x = x;
                            }
                        }
                    }
                }

                /* Are we next to a bad guy? */
                if (dist <= 2) {        /* Get him! */
                    tp->t_newpos = ch_ret;
                    tp->t_action = A_MOVE;
                }

                /* Try to move to the bad guy */
                else if (dist < INT_MAX)
                    chase(tp, &ch_ret,
                          roomin(&tp->t_pos), roomin(&ch_ret), FALSE);

                else tp->t_action = A_NIL;

                return;
            }
        }

        /*
         * If we have decided that we can move onto a monster (we are
         * friendly to the player, go to it.
         */
        if (!ce(ch_ret, *er) && isalpha(mvwinch(mw, ch_ret.y, ch_ret.x))) {
            debug("Attack monster");
            tp->t_newpos = ch_ret;
            tp->t_action = A_MOVE;
            return;
        }

        /* If we can't get closer to the player (if that's our goal)
         * because other monsters are in the way, just stay put
         */
        if (!flee && ce(hero, *ee) && monst_dist < INT_MAX &&
            DISTANCE(er->y, er->x, hero.y, hero.x) < dist) {
                tp->t_action = A_NIL; /* do nothing for awhile */
                return;
        }

        /* Do we want to go back to the last position? */
        else if (dist_to_old != INT_MIN &&      /* It is possible to move back */
            ((flee && dist == 0) ||     /* No other possible moves */
             (!flee && dist == INT_MAX))) {
            /* Do we move back or just stay put (default)? */
            dist = DISTANCE(er->y, er->x, ee->y, ee->x); /* Current distance */
            if (!flee || (flee && (dist_to_old > dist))) ch_ret = tp->t_oldpos;
        }

        /* Record the new destination */
        tp->t_newpos = ch_ret;
    }

    /*
     * Do we want to fight or move?  If our selected destination (ch_ret)
     * is our hero, then we want to fight.  Otherwise, we want to move.
     */
    if (ce(tp->t_newpos, hero)) {
        /* Fight! (or sell) */
        if (on(*tp, CANSELL)) {
            tp->t_action = A_SELL;
            tp->t_no_move += movement(tp); /* takes a little time to sell */
        }
        else {
            tp->t_action = A_ATTACK;

            /*
             * Try to find a weapon to wield.  Wield_weap will return a
             * projector if weapon is a projectile (eg. bow for arrow).
             * If weapon is NULL (the case here), it will try to find
             * a suitable weapon.
             *
             *          Add in rest of time. Fight is 
             *          movement() + weap_move() + FIGHTBASE
             */
            tp->t_using = wield_weap((struct object *)NULL, tp);
            if (tp->t_using == NULL)
                tp->t_no_move += weap_move(tp, (struct object *)NULL);
            else
                tp->t_no_move += weap_move(tp, OBJPTR(tp->t_using));

            if (on(*tp, ISHASTE))
                    tp->t_no_move += FIGHTBASE/2;
            else if (on(*tp, ISSLOW))
                    tp->t_no_move += FIGHTBASE*2;
            else
                    tp->t_no_move += FIGHTBASE;
        }
    }
    else {
        /* Move */
        tp->t_action = A_MOVE;

        /*
         * Check if the creature is not next to the player.  If it
         * is not and has held or suffocated the player, then stop it!
         * Note that this code should more appropriately appear in
         * the area that actually moves the monster, but for now it
         * is okay here because the player can't move while held or
         * suffocating.
         */
        if (dist > 2) {
            if (on(*tp, DIDHOLD)) {
                 turn_off(*tp, DIDHOLD);
                 turn_on(*tp, CANHOLD);
                 if (--hold_count == 0) 
                     turn_off(player, ISHELD);
            }

            /* If monster was suffocating, stop it */
            if (on(*tp, DIDSUFFOCATE)) {
                turn_off(*tp, DIDSUFFOCATE);
                turn_on(*tp, CANSUFFOCATE);
                extinguish(suffocate);
                msg("You can breathe again.....Whew!");
            }
        }
    }
}

/*
 * do_chase:
 *      Make one thing chase another.
 */

void
do_chase(struct thing *th)
{
    register struct room *orig_rer,     /* Original room of chaser */
                         *new_room;     /* new room of monster */
    unsigned char floor, rch, sch;
    coord old_pos,                      /* Old position of monster */
          ch_ret;                       /* Where we want to go */

    if (on(*th, NOMOVE)) return;

    ch_ret = th->t_newpos;      /* Record our desired new position */

    /*
     * Make sure we have an open spot (no other monster's gotten in our way,
     * someone didn't just drop a scare monster there, our prey didn't just
     * get there, etc.)
     */
    if (!step_ok(th->t_newpos.y, th->t_newpos.x, FIGHTOK, th)) {
        /*
         * Most monsters get upset now.  Guardians are all friends,
         * and we don't want to see 50 messages in a row!
         */
        if (th->t_stats.s_intel > 4 &&
            off(*th, ISUNDEAD)      &&
            off(*th, ISGUARDIAN)    &&
            off(*th, AREMANY)       &&
            off(*th, ISHUH)         &&
            off(*th, ISCHARMED)     &&
            off(player, ISBLIND)    &&
            cansee(unc(th->t_pos))  &&
        !invisible(th) && (rnd(15) < 5)) {
            switch (rnd(10)) {
              case 0: case 1:
            msg("%s lashes out at you! ",prname(monster_name(th),TRUE));
          when 2: case 3:
            msg("%s scrambles around. ",prname(monster_name(th), TRUE));
                  otherwise:
            msg("%s motions angrily. ", prname(monster_name(th), TRUE));
            }
    }
        return;
    }
    else if (ce(th->t_newpos, hero) ||  /* Player just got in our way */
             isalpha(mvwinch(mw, th->t_newpos.y, th->t_newpos.x))) {
        bool fightplayer = ce(th->t_newpos, hero);

        /* If we were turned or are friendly, we just have to sit here! */
        if (fightplayer && (on(*th, WASTURNED) || on(*th, ISFRIENDLY))) return;

        /* Do we want to sell something? */
        if (fightplayer && on(*th, CANSELL)) {
            th->t_action = A_SELL;
            th->t_no_move += movement(th); /* takes a little time to sell */
            return;
        }

        /* Let's hit him */
        th->t_action = A_ATTACK;

        /*
         * Try to find a weapon to wield.  Wield_weap will return a
         * projector if weapon is a projectile (eg. bow for arrow).
         * If weapon is NULL (the case here), it will try to find
         * a suitable weapon.
         */
        th->t_using = wield_weap((struct object *)NULL, th);
        /*
         * add in rest of time
         */
        if (th->t_using == NULL)
            th->t_no_move += weap_move(th, (struct object *)NULL);
        else
            th->t_no_move += weap_move(th, OBJPTR(th->t_using));
        if (on(*th, ISHASTE))
                th->t_no_move += FIGHTBASE/2;
        else if (on(*th, ISSLOW))
                th->t_no_move += FIGHTBASE*2;
        else
                th->t_no_move += FIGHTBASE;
        return;
    }

    /*
     * Blank out the old position and record the new position --
     * the blanking must be done first in case the positions are the same.
     */
    mvwaddch(mw, th->t_pos.y, th->t_pos.x, ' ');
    mvwaddch(mw, ch_ret.y, ch_ret.x, th->t_type);

    /* Get new and old rooms of monster */
    new_room = roomin(&ch_ret);
    orig_rer = roomin(&th->t_pos);

    /* Store the critter's old position and update the current one */
    old_pos = th->t_pos;
    th->t_pos = ch_ret;
    floor = (roomin(&ch_ret) == NULL) ? PASSAGE : FLOOR;

    /* If we have a scavenger, it can pick something up */
    if (off(*th, ISGUARDIAN)) {
        register struct linked_list *n_item, *o_item;
        register int item_count = 0;
        bool want_something = FALSE;

        while ((n_item = find_obj(ch_ret.y, ch_ret.x)) != NULL) {
            register struct object *n_obj, *o_obj;
            bool wants_it;

            /* Does this monster want anything? */
            if (want_something == FALSE) {
                if (on(*th, ISSCAVENGE)  || on(*th, CARRYFOOD)   ||
                    on(*th, CARRYGOLD)   || on(*th, CARRYSCROLL) ||
                    on(*th, CARRYPOTION) || on(*th, CARRYRING)   ||
                    on(*th, CARRYSTICK)  || on(*th, CARRYMISC)   ||
                    on(*th, CARRYWEAPON) || on(*th, CARRYARMOR)  ||
                    on(*th, CARRYDAGGER))  {
                        want_something = TRUE;

                        /*
                         * Blank the area.  We have to do it only before the
                         * first item in case an item gets dropped in same
                         * place.  We don't want to blank it out after it get
                         * dropped.
                         */
                        mvaddch(ch_ret.y, ch_ret.x, floor);

                        /* Were we specifically after something here? */
                        if (ce(*th->t_dest, ch_ret)) {
                            /* If we're mean, we go after the hero */
                            if (on(*th, ISMEAN)) runto(th, &hero);

                            /* Otherwise just go back to sleep */
                            else {
                                turn_off(*th, ISRUN);
                                th->t_dest = NULL;
                            }
                        }
                }
                else break;
            }

            item_count++;       /* Count the number of items */

            /*
             * see if he's got one of this group already
             */
            o_item = NULL;
            n_obj = OBJPTR(n_item);
            detach(lvl_obj, n_item);

            /* See if he wants it */
            if (n_obj->o_type == SCROLL && n_obj->o_which == S_SCARE &&
                th->t_stats.s_intel < 16)
                wants_it = FALSE; /* Most monsters don't want a scare monster */
            else if (on(*th, ISSCAVENGE)) wants_it = TRUE;
            else {
                wants_it = FALSE;       /* Default case */
                switch (n_obj->o_type) {
                    case FOOD:  if(on(*th, CARRYFOOD))   wants_it = TRUE;
                    when GOLD:  if(on(*th, CARRYGOLD))   wants_it = TRUE;
                    when SCROLL:if(on(*th, CARRYSCROLL)) wants_it = TRUE;
                    when POTION:if(on(*th, CARRYPOTION)) wants_it = TRUE;
                    when RING:  if(on(*th, CARRYRING))   wants_it = TRUE;
                    when STICK: if(on(*th, CARRYSTICK))  wants_it = TRUE;
                    when MM:    if(on(*th, CARRYMISC))   wants_it = TRUE;
                    when ARMOR: if(on(*th, CARRYARMOR))  wants_it = TRUE;
                    when WEAPON:if(on(*th, CARRYWEAPON) ||
                                  (on(*th,CARRYDAGGER)&&n_obj->o_which==DAGGER))
                                        wants_it = TRUE;
                }
            }
            /*
             * The quartermaster doesn't sell cursed stuff so he won't
             * pick it up
             */
            if (on(*th, CANSELL) && (n_obj->o_flags & ISCURSED))
                wants_it = FALSE;

            /* If he doesn't want it, throw it away */
            if (wants_it == FALSE) {
                fall(n_item, FALSE);
                continue;
            }

            /* Otherwise, let's pick it up */
            if (n_obj->o_group) {
                for(o_item = th->t_pack; o_item != NULL; o_item = next(o_item)){
                    o_obj = OBJPTR(o_item);
                    if (o_obj->o_group == n_obj->o_group) {
                        o_obj->o_count += n_obj->o_count;
                        o_discard(n_item);
                        break;
                    }
                }
            }
            if (o_item == NULL) {       /* didn't find it */
                attach(th->t_pack, n_item);
            }
        }

        /* If there was anything here, we may have to update the screen */
        if (item_count) {
            if (cansee(ch_ret.y, ch_ret.x))
                mvwaddch(cw, ch_ret.y, ch_ret.x, mvinch(ch_ret.y, ch_ret.x));
            updpack(TRUE, th); /* Update the monster's encumberance, too */
        }
    }

    rch = mvwinch(stdscr, old_pos.y, old_pos.x); 
    if (th->t_oldch == floor && rch != floor && !isatrap(rch))
        mvwaddch(cw, old_pos.y, old_pos.x, rch);
    else
        mvwaddch(cw, old_pos.y, old_pos.x, th->t_oldch);
    sch = mvwinch(cw, ch_ret.y, ch_ret.x); /* What player sees */
    rch = mvwinch(stdscr, ch_ret.y, ch_ret.x); /* What's really there */

    /* If we have a tunneling monster, it may be making a tunnel */
    if (on(*th, CANTUNNEL)      &&
        (rch==SECRETDOOR || rch==WALL || rch==VERTWALL || rch==HORZWALL)) {
        unsigned char nch;       /* The new look to the tunnel */

        if (rch == WALL && levtype == OUTSIDE) nch = FLOOR;
        else if (rch == WALL) nch = PASSAGE;
        else if (levtype == MAZELEV || levtype == OUTSIDE) nch = FLOOR;
        else nch = DOOR;
        addch(nch);

        if (cansee(ch_ret.y, ch_ret.x)) sch = nch; /* Can player see this? */

        /* Does this make a new exit? */
        if (rch == VERTWALL || rch == HORZWALL) {
            struct linked_list *newroom;
            coord *exit;

            newroom = new_item(sizeof(coord));
            exit = DOORPTR(newroom);
            *exit = ch_ret;
            attach(new_room->r_exit, newroom);
        }
    }

    /* Mark if the monster is inside a wall */
    if (isrock(mvinch(ch_ret.y, ch_ret.x))) turn_on(*th, ISINWALL);
    else turn_off(*th, ISINWALL);

    /* If the monster can illuminate rooms, check for a change */
    if (on(*th, HASFIRE)) {
        register struct linked_list *fire_item;

        /* Is monster entering a room? */
        if (orig_rer != new_room && new_room != NULL) {
            fire_item = creat_item();   /* Get an item-only structure */
            ldata(fire_item) = (char *) th;

            attach(new_room->r_fires, fire_item);
            new_room->r_flags |= HASFIRE;

            if (cansee(ch_ret.y, ch_ret.x) && next(new_room->r_fires) == NULL)
                light(&hero);
        }

        /* Is monster leaving a room? */