diff arogue7/chase.c @ 125:adfa37e67084

Import Advanced Rogue 7.7 from the Roguelike Restoration Project (r1490)
author John "Elwin" Edwards
date Fri, 08 May 2015 15:24:40 -0400
parents
children f9ef86cf22b2
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/arogue7/chase.c	Fri May 08 15:24:40 2015 -0400
@@ -0,0 +1,928 @@
+/*
+ * chase.c  -  Code for one object to chase another
+ *
+ * Advanced Rogue
+ * Copyright (C) 1984, 1985, 1986 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.
+ */
+
+/*
+ * Code for one object to chase another
+ *
+ */
+
+#include <ctype.h>
+#include <limits.h>
+#include "curses.h"
+#include "rogue.h"
+#define	MAXINT	INT_MAX
+#define	MININT	INT_MIN
+
+
+/*
+ * Canblink checks if the monster can teleport (blink).  If so, it will
+ * try to blink the monster next to the player.
+ */
+
+bool
+can_blink(tp)
+register 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) {
+	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 = CCHAR( 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 = CCHAR( 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.
+ */
+
+coord *
+can_shoot(er, ee)
+register coord *er, *ee;
+{
+    static 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)
+	return(NULL);
+
+    /* Do we have a straight shot? */
+    if (!straight_shot(er->y, er->x, ee->y, ee->x, &shoot_dir)) return(NULL);
+    else return(&shoot_dir);
+}
+
+/*
+ * 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).
+ */
+
+chase(tp, ee, rer, ree, flee)
+register struct thing *tp;
+register coord *ee;
+register struct room *rer, *ree;
+bool 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
+	    */
+{
+    int dist, thisdist, monst_dist = MAXINT; 
+    register coord *er = &tp->t_pos; 
+    struct thing *prey;			/* What we are chasing */
+    coord ch_ret;			/* Where chasing takes you */
+    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 = MININT; /* 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 : MAXINT;
+	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 = CCHAR( 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 = CCHAR( 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, 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 = MAXINT;
+		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 < MAXINT)
+		    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 < MAXINT &&
+	    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 != MININT &&      /* It is possible to move back */
+	    ((flee && dist == 0) ||	/* No other possible moves */
+	     (!flee && dist == MAXINT))) {
+	    /* 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(NULL, tp);
+	    if (tp->t_using == NULL)
+		tp->t_no_move += weap_move(tp, 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.
+ */
+
+do_chase(th)
+register struct thing *th;
+{
+    register struct room *orig_rer,	/* Original room of chaser */
+			 *new_room;	/* new room of monster */
+    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(player, ISBLIND)	&&
+	    cansee(unc(th->t_pos))	&&
+	    !invisible(th))
+	    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(NULL, th);
+	/*
+	 * add in rest of time
+	 */
+	if (th->t_using == NULL)
+	    th->t_no_move += weap_move(th, 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;