Mercurial > hg > early-roguelike
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;