diff urogue/chase.c @ 256:c495a4f288c6

Import UltraRogue from the Roguelike Restoration Project (r1490)
author John "Elwin" Edwards
date Tue, 31 Jan 2017 19:56:04 -0500
parents
children 0250220d8cdd
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/urogue/chase.c	Tue Jan 31 19:56:04 2017 -0500
@@ -0,0 +1,1326 @@
+/*
+    chase.c  -  Code for one creature to chase another
+ 
+    UltraRogue: The Ultimate Adventure in the Dungeons of Doom
+    Copyright (C) 1985, 1986, 1992, 1993, 1995 Herb Chong
+    All rights reserved.
+
+    Based on "Advanced Rogue"
+    Copyright (C) 1984, 1985 Michael Morgan, Ken Dalka
+    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 <stdlib.h>
+#include <ctype.h>
+#include <limits.h>
+#include "rogue.h"
+
+/*
+    do_chase()
+        Make one thing chase another.
+*/
+
+void
+do_chase(struct thing *th, int flee)
+{
+    struct room    *rer;        /* room of chaser */
+    struct room    *ree;        /* room of chasee */
+    struct room    *old_room;   /* old room of monster */
+    struct room    *new_room;   /* new room of monster */
+
+    int i, mindist = INT_MAX, maxdist = INT_MIN, dist = INT_MIN;
+
+    int last_door = -1;     /* Door we just came from */
+    int stoprun = FALSE;    /* TRUE means we are there */
+    int rundoor;            /* TRUE means run to a door */
+    int hit_bad = FALSE;    /* TRUE means hit bad monster */
+    int mon_attack;         /* TRUE means find a monster to hit */
+
+    char    sch;
+    struct linked_list *item;
+    coord   this;           /* Temporary destination for chaser */
+
+    if (!th->t_ischasing)
+        return;
+
+    /* Make sure the monster can move */
+
+    if (th->t_no_move != 0)
+    {
+        th->t_no_move--;
+        return;
+    }
+
+    /*
+     * Bad monsters check for a good monster to hit, friendly monsters
+     * check for a bad monster to hit.
+     */
+
+    mon_attack = FALSE;
+
+    if (good_monster(*th))
+    {
+        hit_bad = TRUE;
+        mon_attack = TRUE;
+    }
+    else if (on(*th, ISMEAN))
+    {
+        hit_bad = FALSE;
+        mon_attack = TRUE;
+    }
+
+    if (mon_attack)
+    {
+        struct linked_list  *mon_to_hit;
+
+	mon_to_hit = f_mons_a(th->t_pos.y, th->t_pos.x, hit_bad);
+
+        if (mon_to_hit)
+        {
+            mon_mon_attack(th, mon_to_hit, pick_weap(th), NOTHROWN);
+            return;
+        }
+    }
+	
+    /* no nearby monster to hit */
+	
+    rer = roomin(th->t_pos);            /* Find room of chaser */
+    ree = roomin(th->t_chasee->t_pos);  /* Find room of chasee */
+
+    /*
+     * We don't count doors as inside rooms for this routine
+     */
+
+    if (mvwinch(stdscr, th->t_pos.y, th->t_pos.x) == DOOR)
+        rer = NULL;
+
+    this = th->t_chasee->t_pos;
+
+    /*
+     * If we are not in a corridor and not a phasing monster, then if we
+     * are running after the player, we run to a door if he is not in the
+     * same room. If we are fleeing, we run to a door if he IS in the
+     * same room.  Note:  We don't bother with doors in mazes. Phasing 
+     * monsters don't need to look for doors. There are no doors in mazes
+     * and throne rooms. 
+     */
+
+    if (levtype != MAZELEV && levtype != THRONE && rer != NULL && off(*th, CANINWALL))
+    {
+        if (flee)
+            rundoor = (rer == ree);
+        else
+            rundoor = (rer != ree);
+    }
+    else
+        rundoor = FALSE;
+
+    if (rundoor)
+    {
+        coord   d_exit;   /* A particular door */
+        int exity, exitx;   /* Door's coordinates */
+
+        if (th->t_doorgoal != -1)
+        { /* Do we already have the goal? */
+            this = rer->r_exit[th->t_doorgoal];
+            dist = 0;   /* Indicate that we have our door */
+        }
+        else
+            for (i = 0; i < rer->r_nexits; i++)
+            {   /* Loop through doors */
+                d_exit = rer->r_exit[i];
+                exity = d_exit.y;
+                exitx = d_exit.x;
+
+                /* Avoid secret doors */
+                if (mvwinch(stdscr, exity, exitx) == DOOR)
+                {
+                    /* Were we just on this door? */
+                    if (ce(d_exit, th->t_oldpos))
+                        last_door = i;
+                    else
+                    {
+                        dist = DISTANCE(th->t_chasee->t_pos, d_exit);
+
+                        /*
+                         * If fleeing, we want to
+                         * maximize distance from
+                         * door to what we flee, and
+                         * minimize distance from
+                         * door to us.
+                         */
+
+                        if (flee)
+                            dist-=DISTANCE(th->t_pos,d_exit);
+
+                        /*
+                         * Maximize distance if
+                         * fleeing, otherwise
+                         * minimize it
+                         */
+
+                        if ((flee && (dist > maxdist)) ||
+                            (!flee && (dist < mindist)))
+                        {
+                            th->t_doorgoal = i; /* Use this door */
+                            this = d_exit;
+                            mindist = maxdist = dist;
+                        }
+                    }
+                }
+            }
+
+        /* Could we not find a door? */
+        if (dist == INT_MIN)
+        {
+            /* If we were on a door, go ahead and use it */
+            if (last_door != -1)
+            {
+                th->t_doorgoal = last_door;
+                this = th->t_oldpos;
+                dist = 0;   /* Indicate that we found a door */
+            }
+        }
+
+        /* Indicate that we do not want to flee from the door */
+        if (dist != INT_MIN)
+            flee = FALSE;
+    }
+    else
+        th->t_doorgoal = -1;    /* Not going to any door */
+
+    /*
+     * this now contains what we want to run to this time so we run to
+     * it.  If we hit it we either want to fight it or stop running
+     */
+
+    if (!chase(th, &this, flee))
+    {
+        if (ce(th->t_nxtpos, hero))
+        {
+            /* merchants try to sell something */
+
+            if (on(*th, CANSELL))
+            {
+                sell(th);
+                return;
+            }
+            else if (off(*th, ISFRIENDLY) && off(*th, ISCHARMED)
+                    && (off(*th, CANFLY) || (on(*th, CANFLY) && rnd(2))))
+                    attack(th, pick_weap(th), FALSE);
+                return;
+        }
+        else if (on(*th, NOMOVE))
+            stoprun = TRUE;
+    }
+
+    if (!curr_mons)
+        return;     /* Did monster get itself killed? */
+
+    if (on(*th, NOMOVE))
+        return;
+
+    /* If we have a scavenger, it can pick something up */
+
+    if ((item = find_obj(th->t_nxtpos.y, th->t_nxtpos.x)) != NULL)
+    {
+		struct linked_list *node, *top = item;
+        struct object *obt;
+		
+		while(top)
+		{
+			/* grab all objects that qualify */
+			
+			struct object *obj = OBJPTR(item);
+			
+			obt = OBJPTR(top);
+			node = obt->next_obj;
+			
+			if (on(*th, ISSCAVENGE) ||
+                ((on(*th, CANWIELD) || on(*th, CANSHOOT)) &&
+                (obj->o_type == WEAPON || obj->o_type == ARMOR)) ||
+                (on(*th, CANCAST) && is_magic(obj))) 
+			{
+                rem_obj(top, FALSE);
+                attach(th->t_pack, top);
+            }
+			
+			top = node;
+		}
+		
+		light(&hero);
+    }
+
+    mvwaddch(cw, th->t_pos.y, th->t_pos.x, th->t_oldch);
+    sch = CCHAR( mvwinch(cw, th->t_nxtpos.y, th->t_nxtpos.x) );
+
+    /* Get old and new room of monster */
+    old_room = roomin(th->t_pos);
+    new_room = roomin(th->t_nxtpos);
+
+    /* If the monster can illuminate rooms, check for a change */
+    if (on(*th, HASFIRE))
+    {
+        /* Is monster entering a room? */
+        if (old_room != new_room && new_room != NULL)
+        {
+            new_room->r_flags |= HASFIRE;
+            new_room->r_fires++;
+            if (cansee(th->t_nxtpos.y, th->t_nxtpos.x) && new_room->r_fires==1)
+                light(&hero);
+        }
+
+        /* Is monster leaving a room? */
+        if (old_room != new_room && old_room != NULL)
+        {
+            if (--(old_room->r_fires) <= 0)
+            {
+                old_room->r_flags &= ~HASFIRE;
+                if (cansee(th->t_pos.y, th->t_pos.x))
+                    light(&th->t_pos);
+            }
+        }
+    }
+
+    /*
+     * If monster is entering player's room and player can see it, stop
+     * the player's running.
+     */
+
+    if (new_room != old_room && new_room != NULL &&
+        new_room == ree && cansee(th->t_nxtpos.y, th->t_nxtpos.x) &&
+        (off(*th, ISINVIS) || (off(*th, ISSHADOW) || rnd(10) == 0) ||
+         on(player, CANSEE)) && off(*th, CANSURPRISE))
+        running = FALSE;
+
+    if (rer != NULL && (rer->r_flags & ISDARK) &&
+        !(rer->r_flags & HASFIRE) && sch == FLOOR &&
+         DISTANCE(th->t_nxtpos, th->t_pos) < see_dist &&
+        off(player, ISBLIND))
+        th->t_oldch = ' ';
+    else
+        th->t_oldch = sch;
+
+    if (cansee(th->t_nxtpos.y, th->t_nxtpos.x) &&
+      off(*th, ISINWALL) &&
+      ((off(*th, ISINVIS) && (off(*th, ISSHADOW) || rnd(100) < 10)) ||
+      on(player, CANSEE)) &&
+      off(*th, CANSURPRISE))
+        mvwaddch(cw, th->t_nxtpos.y, th->t_nxtpos.x, th->t_type);
+
+    mvwaddch(mw, th->t_pos.y, th->t_pos.x, ' ');
+    mvwaddch(mw, th->t_nxtpos.y, th->t_nxtpos.x, th->t_type);
+
+    /* Record monster's last position (if new one is different) */
+
+    if (!ce(th->t_nxtpos, th->t_pos))
+        th->t_oldpos = th->t_pos;
+
+    th->t_pos = th->t_nxtpos; /* Mark the monster's new position */
+
+    /* If the monster is on a trap, trap it */
+
+    sch = CCHAR(mvinch(th->t_nxtpos.y, th->t_nxtpos.x));
+
+    if (isatrap(sch))
+    {
+        debug("Monster trapped by %c.", sch);
+
+        if (cansee(th->t_nxtpos.y, th->t_nxtpos.x))
+            th->t_oldch = sch;
+
+        be_trapped(th, th->t_nxtpos);
+    }
+
+    /* And stop running if need be */
+
+    if (stoprun && ce(th->t_pos, th->t_chasee->t_pos))
+    {
+        th->t_ischasing = FALSE;
+        turn_off(*th, ISRUN);
+    }
+}
+
+/*
+    chase_it()
+        Set a monster running after something or stop it from running (for
+        when it dies)
+*/
+
+void
+chase_it(coord *runner, struct thing *th)
+{
+    struct linked_list  *item;
+    struct thing    *tp;
+
+    /* If we couldn't find him, something is funny */
+
+    if ((item = find_mons(runner->y, runner->x)) == NULL)
+    {
+        debug("CHASER '%s'", unctrl(winat(runner->y, runner->x)));
+        return;
+    }
+
+    tp = THINGPTR(item);
+
+    /* Start the beastie running */
+
+    tp->t_ischasing = TRUE;
+    tp->t_chasee    = th;
+
+    turn_on(*tp, ISRUN);
+    turn_off(*tp, ISDISGUISE);
+
+    return;
+}
+
+/*
+    chase()
+        Find the spot for the chaser(er) to move closer to the chasee(ee).
+        Returns TRUE if we want to keep on chasing later, FALSE if we reach the
+        goal.
+*/
+
+int
+chase(struct thing *tp, coord *ee, int flee)
+{
+    int x, y;
+    int dist, thisdist, monst_dist = INT_MAX;
+    struct linked_list  *weapon;
+    coord   *er = &tp->t_pos;
+    coord shoot;
+    coord *shootit_dir = NULL;
+    int ch;
+    char   mch;
+    int    next_player = FALSE;
+
+    /* Take care of shooting directions */
+
+    if (on(*tp, CANBREATHE) || on(*tp, CANSHOOT) || on(*tp, CANCAST))
+    {
+        if (good_monster(*tp))
+        {
+            shootit_dir = find_shoot(tp, &shoot); /* find a mean monster */
+
+            if (wizard && shootit_dir)
+                msg("Found monster to attack towards (%d,%d).",
+                    shootit_dir->x, shootit_dir->y);
+        }
+        else
+            shootit_dir = can_shoot(er, ee, &shoot);  /* shoot hero */
+    }
+
+    /*
+     * If the thing is confused, let it move randomly. Some monsters are
+     * slightly confused all of the time.
+     */
+
+    if ((on(*tp, ISHUH) && rnd(10) < 8) ||
+        ((on(*tp, ISINVIS) || on(*tp, ISSHADOW)) && rnd(100) < 20) ||
+        (on(player, ISINVIS) && off(*tp, CANSEE)))
+    {   /* Player is invisible */
+
+        /* get a valid random move */
+
+        tp->t_nxtpos = rndmove(tp);
+
+        dist = DISTANCE(tp->t_nxtpos, *ee);
+
+        if (on(*tp, ISHUH) && rnd(20) == 0) /* monster might lose confusion */
+            turn_off(*tp, ISHUH);
+
+        /*
+         * check to see if random move takes creature away from
+         * player if it does then turn off ISHELD
+         */
+
+        if (dist > 1 && on(*tp, DIDHOLD))
+        {
+            turn_off(*tp, DIDHOLD);
+            turn_on(*tp, CANHOLD);
+
+            if (--hold_count == 0)
+                turn_off(player, ISHELD);
+        }
+    } /* If we can breathe, we may do so */
+    else if (on(*tp, CANBREATHE) && (shootit_dir) && (rnd(100) < 67) &&
+         (off(player, ISDISGUISE) || (rnd(tp->t_stats.s_lvl) > 6)) &&
+         (DISTANCE(*er, *ee) < BOLT_LENGTH * BOLT_LENGTH))
+    {
+        int   chance;
+        char    *breath;
+
+        /* Will it breathe at random */
+
+        if (on(*tp, CANBRANDOM))
+        {
+            if (rnd(level / 20) == 0 && tp->t_index != nummonst + 1
+                && !(good_monster(*tp)))
+                turn_off(*tp, CANBRANDOM);
+
+            /* Select type of breath */
+
+            chance = rnd(100);
+
+            if (chance < 11)
+                breath = "acid";
+            else if (chance < 22)
+                breath = "flame";
+            else if (chance < 33)
+                breath = "lightning bolt";
+            else if (chance < 44)
+                breath = "chlorine gas";
+            else if (chance < 55)
+                breath = "ice";
+            else if (chance < 66)
+                breath = "nerve gas";
+            else if (chance < 77)
+                breath = "sleeping gas";
+            else if (chance < 88)
+                breath = "slow gas";
+            else
+                breath = "fear gas";
+        } /* Or can it breathe acid? */
+        else if (on(*tp, CANBACID))
+        {
+            if (!good_monster(*tp) && rnd(level / 15) == 0)
+                turn_off(*tp, CANBACID);
+
+            breath = "acid";
+        } /* Or can it breathe fire */
+        else if (on(*tp, CANBFIRE))
+        {
+            if (!good_monster(*tp) && rnd(level / 15) == 0)
+                turn_off(*tp, CANBFIRE);
+
+            breath = "flame";
+        } /* Or can it breathe electricity? */
+        else if (on(*tp, CANBBOLT))
+        {
+            if (!good_monster(*tp) && rnd(level / 15) == 0)
+                turn_off(*tp, CANBBOLT);
+
+            breath = "lightning bolt";
+        } /* Or can it breathe gas? */
+        else if (on(*tp, CANBGAS))
+        {
+            if (!good_monster(*tp) && rnd(level / 15) == 0)
+                turn_off(*tp, CANBGAS);
+
+            breath = "chlorine gas";
+        } /* Or can it breathe ice? */
+        else if (on(*tp, CANBICE))
+        {
+            if (!good_monster(*tp) && rnd(level / 15) == 0)
+                turn_off(*tp, CANBICE);
+
+            breath = "ice";
+        }
+        else if (on(*tp, CANBPGAS))
+        {
+            if (!good_monster(*tp) && rnd(level / 15) == 0)
+                turn_off(*tp, CANBPGAS);
+
+            breath = "nerve gas";
+        }
+        else if (on(*tp, CANBSGAS))
+        {
+            if (!good_monster(*tp) && rnd(level / 15) == 0)
+                turn_off(*tp, CANBSGAS);
+
+            breath = "sleeping gas";
+        }
+        else if (on(*tp, CANBSLGAS))
+        {
+            if (!good_monster(*tp) && rnd(level / 15) == 0)
+                turn_off(*tp, CANBSLGAS);
+
+            breath = "slow gas";
+        }
+        else
+        {
+            if (!good_monster(*tp) && rnd(level / 15) == 0)
+                turn_off(*tp, CANBFGAS);
+
+            breath = "fear gas";
+        }
+
+        shoot_bolt(tp, *er, *shootit_dir, (tp == THINGPTR(fam_ptr)),
+               tp->t_index, breath, roll(tp->t_stats.s_lvl, 6));
+
+        tp->t_nxtpos = *er;
+
+        dist = DISTANCE(tp->t_nxtpos, *ee);
+
+        if (!curr_mons)
+            return (TRUE);
+    }
+    else if (shootit_dir && on(*tp, CANCAST) &&
+         (off(player, ISDISGUISE) || (rnd(tp->t_stats.s_lvl) > 6)))
+    {
+        /*
+            If we can cast spells we might do so - even if adjacent fleeing
+            monsters are restricted to certain spells
+        */
+
+        incant(tp, *shootit_dir);
+        tp->t_nxtpos = *er;
+        dist = DISTANCE(tp->t_nxtpos, *ee);
+    }
+    else if (shootit_dir && on(*tp, CANSHOOT)) 
+    {
+	weapon = get_hurl(tp);
+	
+	if (weapon &&
+         (off(*tp, ISFLEE) || rnd(DISTANCE(*er, *ee)) > 2) &&
+         (off(player, ISDISGUISE) || (rnd(tp->t_stats.s_lvl) > 6)))
+	{
+	    /*
+	        Should we shoot or throw something? fleeing monsters 
+		may to shoot anyway if far enough away
+	    */
+
+	    missile(shootit_dir->y, shootit_dir->x, weapon, tp);
+	    tp->t_nxtpos = *er;
+	    dist = DISTANCE(tp->t_nxtpos, *ee);
+	}
+    }
+    else
+    {
+        /*
+            Otherwise, find the empty spot next to the chaser that is closest
+            to the chasee.
+        */
+        int ey, ex;
+        struct room *rer, *ree;
+        int dist_to_old = INT_MIN;   /* Dist from goal to old position */
+
+        /* Get rooms */
+        rer = roomin(*er);
+        ree = roomin(*ee);
+
+        /*
+         * 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;
+        tp->t_nxtpos = *er;
+
+        /* Are we at our goal already? */
+
+        if (!flee && ce(tp->t_nxtpos, *ee))
+            return (FALSE);
+
+        ey = er->y + 1;
+        ex = er->x + 1;
+
+        for (x = er->x - 1; x <= ex; x++)
+            for (y = er->y - 1; y <= ey; y++)
+            {
+                coord   tryp; /* test position */
+
+                /* Don't try off the screen */
+
+                if ((x < 0) || (x >= COLS) || (y < 1) || (y >= LINES - 2))
+                    continue;
+
+                /*
+                 * Don't try the player if not going after
+                 * the player or he's disguised and monster is dumb
+                 */
+
+                if (((off(*tp, ISFLEE) && !ce(hero, *ee)) ||
+                     (on(player, ISDISGUISE) && (rnd(tp->t_stats.s_lvl) < 6))
+                     || good_monster(*tp))
+                    && x == hero.x && y == hero.y)
+                    continue;
+
+                tryp.x = x;
+                tryp.y = y;