From c335e1326ec65faa162ab07e29fdafa1ec743d2d Mon Sep 17 00:00:00 2001 From: jwalz Date: Sat, 5 Jan 2002 21:05:49 +0000 Subject: [PATCH] *** empty log message *** --- src/monmove.c | 1327 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1327 insertions(+) create mode 100644 src/monmove.c diff --git a/src/monmove.c b/src/monmove.c new file mode 100644 index 000000000..513a31ddd --- /dev/null +++ b/src/monmove.c @@ -0,0 +1,1327 @@ +/* SCCS Id: @(#)monmove.c 3.3 2000/08/16 */ +/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ +/* NetHack may be freely redistributed. See license for details. */ + +#include "hack.h" +#include "mfndpos.h" +#include "artifact.h" + +extern boolean notonhead; + +#ifdef OVL0 + +STATIC_DCL int FDECL(disturb,(struct monst *)); +STATIC_DCL void FDECL(distfleeck,(struct monst *,int *,int *,int *)); +STATIC_DCL int FDECL(m_arrival, (struct monst *)); +STATIC_DCL void FDECL(watch_on_duty,(struct monst *)); + +#endif /* OVL0 */ +#ifdef OVLB + +boolean /* TRUE : mtmp died */ +mb_trapped(mtmp) +register struct monst *mtmp; +{ + if (flags.verbose) { + if (cansee(mtmp->mx, mtmp->my)) + pline("KABOOM!! You see a door explode."); + else if (flags.soundok) + You_hear("a distant explosion."); + } + wake_nearto(mtmp->mx, mtmp->my, 7*7); + mtmp->mstun = 1; + mtmp->mhp -= rnd(15); + if(mtmp->mhp <= 0) { + mondied(mtmp); + if (mtmp->mhp > 0) /* lifesaved */ + return(FALSE); + else + return(TRUE); + } + return(FALSE); +} + +#endif /* OVLB */ +#ifdef OVL0 + +STATIC_OVL void +watch_on_duty(mtmp) +register struct monst *mtmp; +{ + register s_level *slev = Is_special(&u.uz); + int x, y; + + if(slev && slev->flags.town && mtmp->mpeaceful && + mtmp->mcansee && m_canseeu(mtmp) && !rn2(3)) { + + if(picking_lock(&x, &y) && IS_DOOR(levl[x][y].typ) && + (levl[x][y].doormask & D_LOCKED)) { + + if(couldsee(mtmp->mx, mtmp->my)) { + + pline("%s yells:", Amonnam(mtmp)); + if(levl[x][y].looted & D_WARNED) { + verbalize("Halt, thief! You're under arrest!"); + (void) angry_guards(!(flags.soundok)); + } else { + verbalize("Hey, stop picking that lock!"); + levl[x][y].looted |= D_WARNED; + } + stop_occupation(); + } + } else if (is_digging()) { + /* chewing, wand/spell of digging are checked elsewhere */ + watch_dig(mtmp, digging.pos.x, digging.pos.y, FALSE); + } + } +} + +#endif /* OVL0 */ +#ifdef OVL1 + +int +dochugw(mtmp) + register struct monst *mtmp; +{ + register int x = mtmp->mx, y = mtmp->my; + boolean already_saw_mon = !occupation ? 0 : canspotmon(mtmp); + int rd = dochug(mtmp); +#if 0 + /* part of the original warning code which was replaced in 3.3.1 */ + int dd; + + if(Warning && !rd && !mtmp->mpeaceful && + (dd = distu(mtmp->mx,mtmp->my)) < distu(x,y) && + dd < 100 && !canseemon(mtmp)) { + /* Note: this assumes we only want to warn against the monster to + * which the weapon does extra damage, as there is no "monster + * which the weapon warns against" field. + */ + if (spec_ability(uwep, SPFX_WARN) && spec_dbon(uwep, mtmp, 1)) + warnlevel = 100; + else if ((int) (mtmp->m_lev / 4) > warnlevel) + warnlevel = (mtmp->m_lev / 4); + } +#endif /* 0 */ + + /* a similar check is in monster_nearby() in hack.c */ + /* check whether hero notices monster and stops current activity */ + if (occupation && !rd && !Confusion && + (!mtmp->mpeaceful || Hallucination) && + /* it's close enough to be a threat */ + distu(mtmp->mx,mtmp->my) <= (BOLT_LIM+1)*(BOLT_LIM+1) && + /* and either couldn't see it before, or it was too far away */ + (!already_saw_mon || !couldsee(x,y) || + distu(x,y) > (BOLT_LIM+1)*(BOLT_LIM+1)) && + /* can see it now, or sense it and would normally see it */ + (canseemon(mtmp) || + (sensemon(mtmp) && couldsee(mtmp->mx,mtmp->my))) && + !noattacks(mtmp->data) && !onscary(u.ux, u.uy, mtmp)) + stop_occupation(); + + return(rd); +} + +#endif /* OVL1 */ +#ifdef OVL2 + +boolean +onscary(x, y, mtmp) +int x, y; +struct monst *mtmp; +{ + if (mtmp->isshk || mtmp->isgd || mtmp->iswiz || !mtmp->mcansee || + mtmp->mpeaceful || mtmp->data->mlet == S_HUMAN || + is_lminion(mtmp->data) || is_rider(mtmp->data) || + mtmp->data == &mons[PM_MINOTAUR]) + return(FALSE); + + return (boolean)(sobj_at(SCR_SCARE_MONSTER, x, y) +#ifdef ELBERETH + || sengr_at("Elbereth", x, y) +#endif + || (mtmp->data->mlet == S_VAMPIRE + && IS_ALTAR(levl[x][y].typ))); +} + +#endif /* OVL2 */ +#ifdef OVL0 + +/* regenerate lost hit points */ +void +mon_regen(mon, digest_meal) +struct monst *mon; +boolean digest_meal; +{ + if (mon->mhp < mon->mhpmax && + (moves % 20 == 0 || regenerates(mon->data))) mon->mhp++; + if (mon->mspec_used) mon->mspec_used--; + if (digest_meal) { + if (mon->meating) mon->meating--; + } +} + +/* + * Possibly awaken the given monster. Return a 1 if the monster has been + * jolted awake. + */ +STATIC_OVL int +disturb(mtmp) + register struct monst *mtmp; +{ + /* + * + Ettins are hard to surprise. + * + Nymphs, jabberwocks, and leprechauns do not easily wake up. + * + * Wake up if: + * in direct LOS AND + * within 10 squares AND + * not stealthy or (mon is an ettin and 9/10) AND + * (mon is not a nymph, jabberwock, or leprechaun) or 1/50 AND + * Aggravate or mon is (dog or human) or + * (1/7 and mon is not mimicing furniture or object) + */ + if(couldsee(mtmp->mx,mtmp->my) && + distu(mtmp->mx,mtmp->my) <= 100 && + (!Stealth || (mtmp->data == &mons[PM_ETTIN] && rn2(10))) && + (!(mtmp->data->mlet == S_NYMPH + || mtmp->data == &mons[PM_JABBERWOCK] +#if 0 /* DEFERRED */ + || mtmp->data == &mons[PM_VORPAL_JABBERWOCK] +#endif + || mtmp->data->mlet == S_LEPRECHAUN) || !rn2(50)) && + (Aggravate_monster + || (mtmp->data->mlet == S_DOG || + mtmp->data->mlet == S_HUMAN) + || (!rn2(7) && mtmp->m_ap_type != M_AP_FURNITURE && + mtmp->m_ap_type != M_AP_OBJECT) )) { + mtmp->msleeping = 0; + return(1); + } + return(0); +} + +/* monster begins fleeing for the specified time, 0 means untimed flee + * if first, only adds fleetime if monster isn't already fleeing + * if fleemsg, prints a message about new flight, otherwise, caller should */ +void +monflee(mtmp, fleetime, first, fleemsg) + struct monst *mtmp; + unsigned int fleetime; + boolean first; + boolean fleemsg; +{ + if (u.ustuck == mtmp) { + if (u.uswallow) + expels(mtmp, mtmp->data, TRUE); + else if (!sticks(youmonst.data)) { + unstuck(mtmp); /* monster lets go when fleeing */ + You("get released!"); + } + } + + if (!first || !mtmp->mflee) { + /* don't lose untimed scare */ + if (!fleetime) + mtmp->mfleetim = 0; + else if (!mtmp->mflee || mtmp->mfleetim) { + fleetime += mtmp->mfleetim; + /* ensure monster flees long enough to visibly stop fighting */ + if (fleetime == 1) fleetime++; + mtmp->mfleetim = min(fleetime, 127); + } + if (!mtmp->mflee && fleemsg && canseemon(mtmp)) + pline("%s turns to flee!", (Monnam(mtmp))); + mtmp->mflee = 1; + } +} + +STATIC_OVL void +distfleeck(mtmp,inrange,nearby,scared) +register struct monst *mtmp; +int *inrange, *nearby, *scared; +{ + int seescaryx, seescaryy; + + *inrange = (dist2(mtmp->mx, mtmp->my, mtmp->mux, mtmp->muy) <= + (BOLT_LIM * BOLT_LIM)); + *nearby = *inrange && monnear(mtmp, mtmp->mux, mtmp->muy); + + /* Note: if your image is displaced, the monster sees the Elbereth + * at your displaced position, thus never attacking your displaced + * position, but possibly attacking you by accident. If you are + * invisible, it sees the Elbereth at your real position, thus never + * running into you by accident but possibly attacking the spot + * where it guesses you are. + */ + if (!mtmp->mcansee || (Invis && !perceives(mtmp->data))) { + seescaryx = mtmp->mux; + seescaryy = mtmp->muy; + } else { + seescaryx = u.ux; + seescaryy = u.uy; + } + *scared = (*nearby && (onscary(seescaryx, seescaryy, mtmp) || + (!mtmp->mpeaceful && + in_your_sanctuary(mtmp, 0, 0)))); + + if(*scared) { + if (rn2(7)) + monflee(mtmp, rnd(10), TRUE, TRUE); + else + monflee(mtmp, rnd(100), TRUE, TRUE); + } + +} + +/* perform a special one-time action for a monster; returns -1 if nothing + special happened, 0 if monster uses up its turn, 1 if monster is killed */ +STATIC_OVL int +m_arrival(mon) +struct monst *mon; +{ + mon->mstrategy &= ~STRAT_ARRIVE; /* always reset */ + + return -1; +} + +/* returns 1 if monster died moving, 0 otherwise */ +/* The whole dochugw/m_move/distfleeck/mfndpos section is serious spaghetti + * code. --KAA + */ +int +dochug(mtmp) +register struct monst *mtmp; +{ + register struct permonst *mdat; + register int tmp=0; + int inrange, nearby, scared; +#ifdef GOLDOBJ + struct obj *ygold, *lepgold; +#endif + +/* Pre-movement adjustments */ + + mdat = mtmp->data; + + if (mtmp->mstrategy & STRAT_ARRIVE) { + int res = m_arrival(mtmp); + if (res >= 0) return res; + } + + /* check for waitmask status change */ + if ((mtmp->mstrategy & STRAT_WAITFORU) && + (m_canseeu(mtmp) || mtmp->mhp < mtmp->mhpmax)) + mtmp->mstrategy &= ~STRAT_WAITFORU; + + /* update quest status flags */ + quest_stat_check(mtmp); + + if (!mtmp->mcanmove || (mtmp->mstrategy & STRAT_WAITMASK)) { + if (Hallucination) newsym(mtmp->mx,mtmp->my); + if (mtmp->mcanmove && (mtmp->mstrategy & STRAT_CLOSE) && + !mtmp->msleeping && monnear(mtmp, u.ux, u.uy)) + quest_talk(mtmp); /* give the leaders a chance to speak */ + return(0); /* other frozen monsters can't do anything */ + } + + /* there is a chance we will wake it */ + if (mtmp->msleeping && !disturb(mtmp)) { + if (Hallucination) newsym(mtmp->mx,mtmp->my); + return(0); + } + + /* not frozen or sleeping: wipe out texts written in the dust */ + wipe_engr_at(mtmp->mx, mtmp->my, 1); + + /* confused monsters get unconfused with small probability */ + if (mtmp->mconf && !rn2(50)) mtmp->mconf = 0; + + /* stunned monsters get un-stunned with larger probability */ + if (mtmp->mstun && !rn2(10)) mtmp->mstun = 0; + + /* some monsters teleport */ + if (mtmp->mflee && !rn2(40) && can_teleport(mdat) && !mtmp->iswiz && + !level.flags.noteleport) { + rloc(mtmp); + return(0); + } + if (mdat->msound == MS_SHRIEK && !um_dist(mtmp->mx, mtmp->my, 1)) + m_respond(mtmp); + if (mdat == &mons[PM_MEDUSA] && couldsee(mtmp->mx, mtmp->my)) + m_respond(mtmp); + if (mtmp->mhp <= 0) return(1); /* m_respond gaze can kill medusa */ + + /* fleeing monsters might regain courage */ + if (mtmp->mflee && !mtmp->mfleetim + && mtmp->mhp == mtmp->mhpmax && !rn2(25)) mtmp->mflee = 0; + + set_apparxy(mtmp); + /* Must be done after you move and before the monster does. The + * set_apparxy() call in m_move() doesn't suffice since the variables + * inrange, etc. all depend on stuff set by set_apparxy(). + */ + + /* Monsters that want to acquire things */ + /* may teleport, so do it before inrange is set */ + if(is_covetous(mdat)) (void) tactics(mtmp); + + /* check distance and scariness of attacks */ + distfleeck(mtmp,&inrange,&nearby,&scared); + + if(find_defensive(mtmp)) { + if (use_defensive(mtmp) != 0) + return 1; + } else if(find_misc(mtmp)) { + if (use_misc(mtmp) != 0) + return 1; + } + + /* Demonic Blackmail! */ + if(nearby && mdat->msound == MS_BRIBE && + mtmp->mpeaceful && !mtmp->mtame && !u.uswallow) { + if (mtmp->mux != u.ux || mtmp->muy != u.uy) { + pline("%s whispers at thin air.", + cansee(mtmp->mux, mtmp->muy) ? Monnam(mtmp) : "It"); + + if (is_demon(youmonst.data)) { + /* "Good hunting, brother" */ + if (!tele_restrict(mtmp)) rloc(mtmp); + } else { + mtmp->minvis = mtmp->perminvis = 0; + /* Why? For the same reason in real demon talk */ + pline("%s gets angry!", Amonnam(mtmp)); + mtmp->mpeaceful = 0; + /* since no way is an image going to pay it off */ + } + } else if(demon_talk(mtmp)) return(1); /* you paid it off */ + } + + /* the watch will look around and see if you are up to no good :-) */ + if (mdat == &mons[PM_WATCHMAN] || mdat == &mons[PM_WATCH_CAPTAIN]) + watch_on_duty(mtmp); + + else if (is_mind_flayer(mdat) && !rn2(20)) { + struct monst *m2, *nmon = (struct monst *)0; + + if (canseemon(mtmp)) + pline("%s concentrates.", Monnam(mtmp)); + if (distu(mtmp->mx, mtmp->my) > BOLT_LIM * BOLT_LIM) { + You("sense a faint wave of psychic energy."); + goto toofar; + } + pline("A wave of psychic energy pours over you!"); + if (mtmp->mpeaceful && + (!Conflict || resist(mtmp, RING_CLASS, 0, 0))) + pline("It feels quite soothing."); + else { + register boolean m_sen = sensemon(mtmp); + + if (m_sen || (Blind_telepat && rn2(2)) || !rn2(10)) { + int dmg; + pline("It locks on to your %s!", + m_sen ? "telepathy" : + Blind_telepat ? "latent telepathy" : "mind"); + dmg = rnd(15); + if (Half_spell_damage) dmg = (dmg+1) / 2; + losehp(dmg, "psychic blast", KILLED_BY_AN); + } + } + for(m2=fmon; m2; m2 = nmon) { + nmon = m2->nmon; + if (DEADMONSTER(m2)) continue; + if (m2->mpeaceful == mtmp->mpeaceful) continue; + if (mindless(m2->data)) continue; + if (m2 == mtmp) continue; + if ((telepathic(m2->data) && + (rn2(2) || m2->mblinded)) || !rn2(10)) { + if (cansee(m2->mx, m2->my)) + pline("It locks on to %s.", mon_nam(m2)); + m2->mhp -= rnd(15); + if (m2->mhp <= 0) + monkilled(m2, "", AD_DRIN); + else + m2->msleeping = 0; + } + } + } +toofar: + /* If monster is nearby you, and has to wield a weapon, do so. This + * costs the monster a move, of course. + */ + if((!mtmp->mpeaceful || Conflict) && inrange && + dist2(mtmp->mx, mtmp->my, mtmp->mux, mtmp->muy) <= 8 + && attacktype(mdat, AT_WEAP)) { + struct obj *mw_tmp; + + /* The scared check is necessary. Otherwise a monster that is + * one square near the player but fleeing into a wall would keep + * switching between pick-axe and weapon. + */ + mw_tmp = MON_WEP(mtmp); + if (!(scared && mw_tmp && is_pick(mw_tmp)) && + mtmp->weapon_check == NEED_WEAPON) { + mtmp->weapon_check = NEED_HTH_WEAPON; + if (mon_wield_item(mtmp) != 0) return(0); + } + } + +/* Now the actual movement phase */ + +#ifndef GOLDOBJ + if(!nearby || mtmp->mflee || scared || + mtmp->mconf || mtmp->mstun || (mtmp->minvis && !rn2(3)) || + (mdat->mlet == S_LEPRECHAUN && !u.ugold && (mtmp->mgold || rn2(2))) || +#else + if (mdat->mlet == S_LEPRECHAUN) { + ygold = findgold(invent); + lepgold = findgold(mtmp->minvent); + } + + if(!nearby || mtmp->mflee || scared || + mtmp->mconf || mtmp->mstun || (mtmp->minvis && !rn2(3)) || + (mdat->mlet == S_LEPRECHAUN && !ygold && (lepgold || rn2(2))) || +#endif + (is_wanderer(mdat) && !rn2(4)) || (Conflict && !mtmp->iswiz) || + (!mtmp->mcansee && !rn2(4)) || mtmp->mpeaceful) { + + tmp = m_move(mtmp, 0); + distfleeck(mtmp,&inrange,&nearby,&scared); /* recalc */ + + switch (tmp) { + case 0: /* no movement, but it can still attack you */ + case 3: /* absolutely no movement */ + /* for pets, case 0 and 3 are equivalent */ + /* vault guard might have vanished */ + if (mtmp->isgd && (mtmp->mhp < 1 || + (mtmp->mx == 0 && mtmp->my == 0))) + return 1; /* behave as if it died */ + /* During hallucination, monster appearance should + * still change - even if it doesn't move. + */ + if(Hallucination) newsym(mtmp->mx,mtmp->my); + break; + case 1: /* monster moved */ + /* Maybe it stepped on a trap and fell asleep... */ + if (mtmp->msleeping || !mtmp->mcanmove) return(0); + if(!nearby && + (ranged_attk(mdat) || find_offensive(mtmp))) + break; + else if(u.uswallow && mtmp == u.ustuck) { + /* a monster that's digesting you can move at the + * same time -dlc + */ + return(mattacku(mtmp)); + } else + return(0); + /*NOTREACHED*/ + break; + case 2: /* monster died */ + return(1); + } + } + +/* Now, attack the player if possible - one attack set per monst */ + + if (!mtmp->mpeaceful || + (Conflict && !resist(mtmp, RING_CLASS, 0, 0))) { + if(inrange && !noattacks(mdat) && u.uhp > 0 && !scared && tmp != 3) + if(mattacku(mtmp)) return(1); /* monster died (e.g. exploded) */ + + if(mtmp->wormno) wormhitu(mtmp); + } + /* special speeches for quest monsters */ + if (!mtmp->msleeping && mtmp->mcanmove && nearby) + quest_talk(mtmp); + /* extra emotional attack for vile monsters */ + if (inrange && mtmp->data->msound == MS_CUSS && !mtmp->mpeaceful && + couldsee(mtmp->mx, mtmp->my) && !mtmp->minvis && !rn2(5)) + cuss(mtmp); + + return(tmp == 2); +} + +static NEARDATA const char practical[] = { WEAPON_CLASS, ARMOR_CLASS, GEM_CLASS, FOOD_CLASS, 0 }; +static NEARDATA const char magical[] = { + AMULET_CLASS, POTION_CLASS, SCROLL_CLASS, WAND_CLASS, RING_CLASS, + SPBOOK_CLASS, 0 }; +static NEARDATA const char indigestion[] = { BALL_CLASS, ROCK_CLASS, 0 }; +static NEARDATA const char boulder_class[] = { ROCK_CLASS, 0 }; +static NEARDATA const char gem_class[] = { GEM_CLASS, 0 }; + +boolean +itsstuck(mtmp) +register struct monst *mtmp; +{ + if (sticks(youmonst.data) && mtmp==u.ustuck && !u.uswallow) { + pline("%s cannot escape from you!", Monnam(mtmp)); + return(TRUE); + } + return(FALSE); +} + +/* Return values: + * 0: did not move, but can still attack and do other stuff. + * 1: moved, possibly can attack. + * 2: monster died. + * 3: did not move, and can't do anything else either. + */ +int +m_move(mtmp, after) +register struct monst *mtmp; +register int after; +{ + register int appr; + xchar gx,gy,nix,niy,chcnt; + int chi; /* could be schar except for stupid Sun-2 compiler */ + boolean likegold=0, likegems=0, likeobjs=0, likemagic=0, conceals=0; + boolean likerock=0, can_tunnel=0; + boolean can_open=0, can_unlock=0, doorbuster=0; + boolean uses_items=0, setlikes=0; + boolean avoid=FALSE; + struct permonst *ptr; + struct monst *mtoo; + schar mmoved = 0; /* not strictly nec.: chi >= 0 will do */ + long info[9]; + long flag; + int omx = mtmp->mx, omy = mtmp->my; + struct obj *mw_tmp; + + if(mtmp->mtrapped) { + int i = mintrap(mtmp); + if(i >= 2) { newsym(mtmp->mx,mtmp->my); return(2); }/* it died */ + if(i == 1) return(0); /* still in trap, so didn't move */ + } + ptr = mtmp->data; /* mintrap() can change mtmp->data -dlc */ + + if (mtmp->meating) { + mtmp->meating--; + return 3; /* still eating */ + } + if (hides_under(ptr) && OBJ_AT(mtmp->mx, mtmp->my) && rn2(10)) + return 0; /* do not leave hiding place */ + + set_apparxy(mtmp); + /* where does mtmp think you are? */ + /* Not necessary if m_move called from this file, but necessary in + * other calls of m_move (ex. leprechauns dodging) + */ + can_tunnel = tunnels(ptr) && +#ifdef REINCARNATION + !Is_rogue_level(&u.uz) && +#endif + (!needspick(ptr) || m_carrying(mtmp, PICK_AXE) || + (m_carrying(mtmp, DWARVISH_MATTOCK) && !which_armor(mtmp, W_ARMS))); + can_open = !(nohands(ptr) || verysmall(ptr)); + can_unlock = ((can_open && m_carrying(mtmp, SKELETON_KEY)) || + mtmp->iswiz || is_rider(ptr)); + doorbuster = is_giant(ptr); + if(mtmp->wormno) goto not_special; + /* my dog gets special treatment */ + if(mtmp->mtame) { + mmoved = dog_move(mtmp, after); + goto postmov; + } + + /* likewise for shopkeeper */ + if(mtmp->isshk) { + mmoved = shk_move(mtmp); + if(mmoved == -2) return(2); + if(mmoved >= 0) goto postmov; + mmoved = 0; /* follow player outside shop */ + } + + /* and for the guard */ + if(mtmp->isgd) { + mmoved = gd_move(mtmp); + if(mmoved == -2) return(2); + if(mmoved >= 0) goto postmov; + mmoved = 0; + } + + /* and the acquisitive monsters get special treatment */ + if(is_covetous(ptr)) { + xchar tx = STRAT_GOALX(mtmp->mstrategy), + ty = STRAT_GOALY(mtmp->mstrategy); + struct monst *intruder = m_at(tx, ty); + /* + * if there's a monster on the object or in possesion of it, + * attack it. + */ + if((dist2(mtmp->mx, mtmp->my, tx, ty) < 2) && + intruder && (intruder != mtmp)) { + + notonhead = (intruder->mx != tx || intruder->my != ty); + if(mattackm(mtmp, intruder) == 2) return(2); + mmoved = 1; + } else mmoved = 0; + goto postmov; + } + + /* and for the priest */ + if(mtmp->ispriest) { + mmoved = pri_move(mtmp); + if(mmoved == -2) return(2); + if(mmoved >= 0) goto postmov; + mmoved = 0; + } + +#ifdef MAIL + if(ptr == &mons[PM_MAIL_DAEMON]) { + if(flags.soundok && canseemon(mtmp)) + verbalize("I'm late!"); + mongone(mtmp); + return(2); + } +#endif + + /* teleport if that lies in our nature */ + if(ptr == &mons[PM_TENGU] && !rn2(5) && !mtmp->mcan && + !tele_restrict(mtmp)) { + if(mtmp->mhp < 7 || mtmp->mpeaceful || rn2(2)) + rloc(mtmp); + else + mnexto(mtmp); + mmoved = 1; + goto postmov; + } +not_special: + if(u.uswallow && !mtmp->mflee && u.ustuck != mtmp) return(1); + omx = mtmp->mx; + omy = mtmp->my; + gx = mtmp->mux; + gy = mtmp->muy; + appr = mtmp->mflee ? -1 : 1; + if (mtmp->mconf || (u.uswallow && mtmp == u.ustuck)) + appr = 0; + else { +#ifdef GOLDOBJ + struct obj *lepgold, *ygold; +#endif + boolean should_see = (couldsee(omx, omy) && + (levl[gx][gy].lit || + !levl[omx][omy].lit) && + (dist2(omx, omy, gx, gy) <= 36)); + + if (!mtmp->mcansee || + (should_see && Invis && !perceives(ptr) && rn2(11)) || + (youmonst.m_ap_type == M_AP_OBJECT && youmonst.mappearance == STRANGE_OBJECT) || u.uundetected || + (youmonst.m_ap_type == M_AP_OBJECT && youmonst.mappearance == GOLD_PIECE && !likes_gold(ptr)) || + (mtmp->mpeaceful && !mtmp->isshk) || /* allow shks to follow */ + ((monsndx(ptr) == PM_STALKER || ptr->mlet == S_BAT || + ptr->mlet == S_LIGHT) && !rn2(3))) + appr = 0; + + if(monsndx(ptr) == PM_LEPRECHAUN && (appr == 1) && +#ifndef GOLDOBJ + (mtmp->mgold > u.ugold)) +#else + ( (lepgold = findgold(mtmp->minvent)) && + (lepgold->quan > ((ygold = findgold(invent)) ? ygold->quan : 0L)) )) +#endif + appr = -1; + + if (!should_see && can_track(ptr)) { + register coord *cp; + + cp = gettrack(omx,omy); + if (cp) { + gx = cp->x; + gy = cp->y; + } + } + } + + if ((!mtmp->mpeaceful || !rn2(10)) +#ifdef REINCARNATION + && (!Is_rogue_level(&u.uz)) +#endif + ) { + boolean in_line = lined_up(mtmp) && + (distmin(mtmp->mx, mtmp->my, mtmp->mux, mtmp->muy) <= + (throws_rocks(youmonst.data) ? 20 : ACURRSTR/2+1) + ); + + if (appr != 1 || !in_line) { + /* Monsters in combat won't pick stuff up, avoiding the + * situation where you toss arrows at it and it has nothing + * better to do than pick the arrows up. + */ + register int pctload = (curr_mon_load(mtmp) * 100) / + max_mon_load(mtmp); + + /* look for gold or jewels nearby */ + likegold = (likes_gold(ptr) && pctload < 95); + likegems = (likes_gems(ptr) && pctload < 85); + uses_items = (!mindless(ptr) && !is_animal(ptr) + && pctload < 75); + likeobjs = (likes_objs(ptr) && pctload < 75); + likemagic = (likes_magic(ptr) && pctload < 85); + likerock = (throws_rocks(ptr) && pctload < 50 && !In_sokoban(&u.uz)); + conceals = hides_under(ptr); + setlikes = TRUE; + } + } + +#define SQSRCHRADIUS 5 + + { register int minr = SQSRCHRADIUS; /* not too far away */ + register struct obj *otmp; + register int xx, yy; + int oomx, oomy, lmx, lmy; + + /* cut down the search radius if it thinks character is closer. */ + if(distmin(mtmp->mux, mtmp->muy, omx, omy) < SQSRCHRADIUS && + !mtmp->mpeaceful) minr--; + /* guards shouldn't get too distracted */ + if(!mtmp->mpeaceful && is_mercenary(ptr)) minr = 1; + + if((likegold || likegems || likeobjs || likemagic || likerock || conceals) + && (!*in_rooms(omx, omy, SHOPBASE) || (!rn2(25) && !mtmp->isshk))) { + look_for_obj: + oomx = min(COLNO-1, omx+minr); + oomy = min(ROWNO-1, omy+minr); + lmx = max(1, omx-minr); + lmy = max(0, omy-minr); + for(otmp = fobj; otmp; otmp = otmp->nobj) { + /* monsters may pick rocks up, but won't go out of their way + to grab them; this might hamper sling wielders, but it cuts + down on move overhead by filtering out most common item */ + if (otmp->otyp == ROCK) continue; + xx = otmp->ox; + yy = otmp->oy; + /* Nymphs take everything. Most other creatures should not + * pick up corpses except as a special case like in + * searches_for_item(). We need to do this check in + * mpickstuff() as well. + */ + if(xx >= lmx && xx <= oomx && yy >= lmy && yy <= oomy) { + /* don't get stuck circling around an object that's underneath + an immobile or hidden monster; paralysis victims excluded */ + if ((mtoo = m_at(xx,yy)) != 0 && + (mtoo->msleeping || mtoo->mundetected || + (mtoo->mappearance && !mtoo->iswiz) || + !mtoo->data->mmove)) continue; + + if(((likegold && otmp->oclass == GOLD_CLASS) || + (likeobjs && index(practical, otmp->oclass) && + (otmp->otyp != CORPSE || (ptr->mlet == S_NYMPH + && !is_rider(&mons[otmp->corpsenm])))) || + (likemagic && index(magical, otmp->oclass)) || + (uses_items && searches_for_item(mtmp, otmp)) || + (likerock && otmp->otyp == BOULDER) || + (likegems && otmp->oclass == GEM_CLASS && + objects[otmp->otyp].oc_material != MINERAL) || + (conceals && !cansee(otmp->ox,otmp->oy)) || + (ptr == &mons[PM_GELATINOUS_CUBE] && + !index(indigestion, otmp->oclass) && + !(otmp->otyp == CORPSE && + touch_petrifies(&mons[otmp->corpsenm]))) + ) && touch_artifact(otmp,mtmp)) { + if(can_carry(mtmp,otmp) && + (throws_rocks(ptr) || + !sobj_at(BOULDER,xx,yy)) && + (!is_unicorn(ptr) || + objects[otmp->otyp].oc_material == GEMSTONE) && + /* Don't get stuck circling an Elbereth */ + !(onscary(xx, yy, mtmp))) { + minr = distmin(omx,omy,xx,yy); + oomx = min(COLNO-1, omx+minr); + oomy = min(ROWNO-1, omy+minr); + lmx = max(1, omx-minr); + lmy = max(0, omy-minr); + gx = otmp->ox; + gy = otmp->oy; + if (gx == omx && gy == omy) { + mmoved = 3; /* actually unnecessary */ + goto postmov; + } + } + } + } + } + } else if(likegold) { + /* don't try to pick up anything else, but use the same loop */ + uses_items = 0; + likegems = likeobjs = likemagic = likerock = conceals = 0; + goto look_for_obj; + } + + if(minr < SQSRCHRADIUS && appr == -1) { + if(distmin(omx,omy,mtmp->mux,mtmp->muy) <= 3) { + gx = mtmp->mux; + gy = mtmp->muy; + } else + appr = 1; + } + } + + if (can_tunnel && needspick(ptr) && + (mw_tmp = MON_WEP(mtmp)) != 0 && !is_pick(mw_tmp) && + mw_tmp->cursed && mtmp->weapon_check == NO_WEAPON_WANTED) + can_tunnel = FALSE; + + nix = omx; + niy = omy; + flag = 0L; + if (mtmp->mpeaceful && (!Conflict || resist(mtmp, RING_CLASS, 0, 0))) + flag |= (ALLOW_SANCT | ALLOW_SSM); + else flag |= ALLOW_U; + if (is_minion(ptr) || is_rider(ptr)) flag |= ALLOW_SANCT; + /* unicorn may not be able to avoid hero on a noteleport level */ + if (is_unicorn(ptr) && !level.flags.noteleport) flag |= NOTONL; + if (passes_walls(ptr)) flag |= (ALLOW_WALL | ALLOW_ROCK); + if (can_tunnel) flag |= ALLOW_DIG; + if (is_human(ptr) || ptr == &mons[PM_MINOTAUR]) flag |= ALLOW_SSM; + if (is_undead(ptr) && ptr->mlet != S_GHOST) flag |= NOGARLIC; + if (throws_rocks(ptr)) flag |= ALLOW_ROCK; + if (can_open) flag |= OPENDOOR; + if (can_unlock) flag |= UNLOCKDOOR; + if (doorbuster) flag |= BUSTDOOR; + { + register int i, j, nx, ny, nearer; + int jcnt, cnt; + int ndist, nidist; + register coord *mtrk; + coord poss[9]; + + cnt = mfndpos(mtmp, poss, info, flag); + chcnt = 0; + jcnt = min(MTSZ, cnt-1); + chi = -1; + nidist = dist2(nix,niy,gx,gy); + /* allow monsters be shortsighted on some levels for balance */ + if(!mtmp->mpeaceful && level.flags.shortsighted && + nidist > (couldsee(nix,niy) ? 144 : 36) && appr == 1) appr = 0; + if (is_unicorn(ptr) && level.flags.noteleport) { + /* on noteleport levels, perhaps we cannot avoid hero */ + for(i = 0; i < cnt; i++) + if(!(info[i] & NOTONL)) avoid=TRUE; + } + + for(i=0; i < cnt; i++) { + if (avoid && (info[i] & NOTONL)) continue; + nx = poss[i].x; + ny = poss[i].y; + + if (appr != 0) { + mtrk = &mtmp->mtrack[0]; + for(j=0; j < jcnt; mtrk++, j++) + if(nx == mtrk->x && ny == mtrk->y) + if(rn2(4*(cnt-j))) + goto nxti; + } + + nearer = ((ndist = dist2(nx,ny,gx,gy)) < nidist); + + if((appr == 1 && nearer) || (appr == -1 && !nearer) || + (!appr && !rn2(++chcnt)) || !mmoved) { + nix = nx; + niy = ny; + nidist = ndist; + chi = i; + mmoved = 1; + } + nxti: ; + } + } + + if(mmoved) { + register int j; + + if (mmoved==1 && (u.ux != nix || u.uy != niy) && itsstuck(mtmp)) + return(3); + + if(IS_ROCK(levl[nix][niy].typ) && may_dig(nix,niy) && + mmoved==1 && can_tunnel && needspick(ptr) && + (!(mw_tmp = MON_WEP(mtmp)) || !is_pick(mw_tmp))) { + mtmp->weapon_check = NEED_PICK_AXE; + if (mon_wield_item(mtmp)) + return(3); + } + /* If ALLOW_U is set, either it's trying to attack you, or it + * thinks it is. In either case, attack this spot in preference to + * all others. + */ + /* Actually, this whole section of code doesn't work as you'd expect. + * Most attacks are handled in dochug(). It calls distfleeck(), which + * among other things sets nearby if the monster is near you--and if + * nearby is set, we never call m_move unless it is a special case + * (confused, stun, etc.) The effect is that this ALLOW_U (and + * mfndpos) has no effect for normal attacks, though it lets a confused + * monster attack you by accident. + */ + if(info[chi] & ALLOW_U) { + nix = mtmp->mux; + niy = mtmp->muy; + } + if (nix == u.ux && niy == u.uy) { + mtmp->mux = u.ux; + mtmp->muy = u.uy; + return(0); + } + /* The monster may attack another based on 1 of 2 conditions: + * 1 - It may be confused. + * 2 - It may mistake the monster for your (displaced) image. + * Pets get taken care of above and shouldn't reach this code. + * Conflict gets handled even farther away (movemon()). + */ + if((info[chi] & ALLOW_M) || + (nix == mtmp->mux && niy == mtmp->muy)) { + struct monst *mtmp2; + int mstatus; + mtmp2 = m_at(nix,niy); + + notonhead = mtmp2 && (nix != mtmp2->mx || niy != mtmp2->my); + /* note: mstatus returns 0 if mtmp2 is nonexistent */ + mstatus = mattackm(mtmp, mtmp2); + + if (mstatus & MM_AGR_DIED) /* aggressor died */ + return 2; + + if ((mstatus & MM_HIT) && !(mstatus & MM_DEF_DIED) && + rn2(4) && mtmp2->movement >= NORMAL_SPEED) { + mtmp2->movement -= NORMAL_SPEED; + notonhead = 0; + mstatus = mattackm(mtmp2, mtmp); /* return attack */ + if (mstatus & MM_DEF_DIED) + return 2; + } + return 3; + } + + if (!m_in_out_region(mtmp,nix,niy)) + return 3; + remove_monster(omx, omy); + place_monster(mtmp, nix, niy); + for(j = MTSZ-1; j > 0; j--) + mtmp->mtrack[j] = mtmp->mtrack[j-1]; + mtmp->mtrack[0].x = omx; + mtmp->mtrack[0].y = omy; + /* Place a segment at the old position. */ + if (mtmp->wormno) worm_move(mtmp); + } else { + if(is_unicorn(ptr) && rn2(2) && !tele_restrict(mtmp)) { + rloc(mtmp); + return(1); + } + if(mtmp->wormno) worm_nomove(mtmp); + } +postmov: + if(mmoved == 1 || mmoved == 3) { + boolean canseeit = cansee(mtmp->mx, mtmp->my); + + if(mmoved == 1) { + newsym(omx,omy); /* update the old position */ + if (mintrap(mtmp) >= 2) { + if(mtmp->mx) newsym(mtmp->mx,mtmp->my); + return(2); /* it died */ + } + ptr = mtmp->data; + + /* open a door, or crash through it, if you can */ + if(IS_DOOR(levl[mtmp->mx][mtmp->my].typ) + && !passes_walls(ptr) /* doesn't need to open doors */ + && !can_tunnel /* taken care of below */ + ) { + struct rm *here = &levl[mtmp->mx][mtmp->my]; + boolean btrapped = (here->doormask & D_TRAPPED); + + if(here->doormask & (D_LOCKED|D_CLOSED) && amorphous(ptr)) { + if (flags.verbose && canseemon(mtmp)) + pline("%s %ss under the door.", Monnam(mtmp), + (ptr == &mons[PM_FOG_CLOUD] || + ptr == &mons[PM_YELLOW_LIGHT]) + ? "flow" : "ooze"); + } else if(here->doormask & D_LOCKED && can_unlock) { + if(btrapped) { + here->doormask = D_NODOOR; + newsym(mtmp->mx, mtmp->my); + unblock_point(mtmp->mx,mtmp->my); /* vision */ + if(mb_trapped(mtmp)) return(2); + } else { + if (flags.verbose) { + if (canseeit) + You("see a door unlock and open."); + else if (flags.soundok) + You_hear("a door unlock and open."); + } + here->doormask = D_ISOPEN; + /* newsym(mtmp->mx, mtmp->my); */ + unblock_point(mtmp->mx,mtmp->my); /* vision */ + } + } else if (here->doormask == D_CLOSED && can_open) { + if(btrapped) { + here->doormask = D_NODOOR; + newsym(mtmp->mx, mtmp->my); + unblock_point(mtmp->mx,mtmp->my); /* vision */ + if(mb_trapped(mtmp)) return(2); + } else { + if (flags.verbose) { + if (canseeit) + You("see a door open."); + else if (flags.soundok) + You_hear("a door open."); + } + here->doormask = D_ISOPEN; + /* newsym(mtmp->mx, mtmp->my); */ /* done below */ + unblock_point(mtmp->mx,mtmp->my); /* vision */ + } + } else if (here->doormask & (D_LOCKED|D_CLOSED)) { + /* mfndpos guarantees this must be a doorbuster */ + if(btrapped) { + here->doormask = D_NODOOR; + newsym(mtmp->mx, mtmp->my); + unblock_point(mtmp->mx,mtmp->my); /* vision */ + if(mb_trapped(mtmp)) return(2); + } else { + if (flags.verbose) { + if (canseeit) + You("see a door crash open."); + else if (flags.soundok) + You_hear("a door crash open."); + } + if (here->doormask & D_LOCKED && !rn2(2)) + here->doormask = D_NODOOR; + else here->doormask = D_BROKEN; + /* newsym(mtmp->mx, mtmp->my); */ /* done below */ + unblock_point(mtmp->mx,mtmp->my); /* vision */ + } + /* if it's a shop door, schedule repair */ + if (*in_rooms(mtmp->mx, mtmp->my, SHOPBASE)) + add_damage(mtmp->mx, mtmp->my, 0L); + } + } + + /* possibly dig */ + if (can_tunnel && mdig_tunnel(mtmp)) + return(2); /* mon died (position already updated) */ + + /* set also in domove(), hack.c */ + if (u.uswallow && mtmp == u.ustuck && + (mtmp->mx != omx || mtmp->my != omy)) { + /* If the monster moved, then update */ + u.ux0 = u.ux; + u.uy0 = u.uy; + u.ux = mtmp->mx; + u.uy = mtmp->my; + swallowed(0); + } else + newsym(mtmp->mx,mtmp->my); + } + if(OBJ_AT(mtmp->mx, mtmp->my) && mtmp->mcanmove) { + /* recompute the likes tests, in case we polymorphed + * or if the "likegold" case got taken above */ + if (setlikes) { + register int pctload = (curr_mon_load(mtmp) * 100) / + max_mon_load(mtmp); + + /* look for gold or jewels nearby */ + likegold = (likes_gold(ptr) && pctload < 95); + likegems = (likes_gems(ptr) && pctload < 85); + uses_items = (!mindless(ptr) && !is_animal(ptr) + && pctload < 75); + likeobjs = (likes_objs(ptr) && pctload < 75); + likemagic = (likes_magic(ptr) && pctload < 85); + likerock = (throws_rocks(ptr) && pctload < 50 && + !In_sokoban(&u.uz)); + conceals = hides_under(ptr); + } + + /* Maybe a rock mole just ate some metal object */ + if (metallivorous(ptr)) { + if (meatmetal(mtmp) == 2) return 2; /* it died */ + } + + if(g_at(mtmp->mx,mtmp->my) && likegold) mpickgold(mtmp); + + /* Maybe a cube ate just about anything */ + if (ptr == &mons[PM_GELATINOUS_CUBE]) { + if (meatobj(mtmp) == 2) return 2; /* it died */ + } + + if(!*in_rooms(mtmp->mx, mtmp->my, SHOPBASE) || !rn2(25)) { + boolean picked = FALSE; + + if(likeobjs) picked |= mpickstuff(mtmp, practical); + if(likemagic) picked |= mpickstuff(mtmp, magical); + if(likerock) picked |= mpickstuff(mtmp, boulder_class); + if(likegems) picked |= mpickstuff(mtmp, gem_class); + if(uses_items) picked |= mpickstuff(mtmp, (char *)0); + if(picked) mmoved = 3; + } + + if(mtmp->minvis) { + newsym(mtmp->mx, mtmp->my); + if (mtmp->wormno) see_wsegs(mtmp); + } + } + + if(hides_under(ptr) || ptr->mlet == S_EEL) { + /* Always set--or reset--mundetected if it's already hidden + (just in case the object it was hiding under went away); + usually set mundetected unless monster can't move. */ + if (mtmp->mundetected || + (mtmp->mcanmove && !mtmp->msleeping && rn2(5))) + mtmp->mundetected = (ptr->mlet != S_EEL) ? + OBJ_AT(mtmp->mx, mtmp->my) : + (is_pool(mtmp->mx, mtmp->my) && !Is_waterlevel(&u.uz)); + newsym(mtmp->mx, mtmp->my); + } + } + return(mmoved); +} + +#endif /* OVL0 */ +#ifdef OVL2 + +boolean +closed_door(x, y) +register int x, y; +{ + return((boolean)(IS_DOOR(levl[x][y].typ) && + (levl[x][y].doormask & (D_LOCKED | D_CLOSED)))); +} + +boolean +accessible(x, y) +register int x, y; +{ + return((boolean)(ACCESSIBLE(levl[x][y].typ) && !closed_door(x, y))); +} + +#endif /* OVL2 */ +#ifdef OVL0 + +/* decide where the monster thinks you are standing */ +void +set_apparxy(mtmp) +register struct monst *mtmp; +{ + boolean notseen, gotu; + register int disp, mx = mtmp->mux, my = mtmp->muy; +#ifdef GOLDOBJ + long umoney = money_cnt(invent); +#endif + + /* + * do cheapest and/or most likely tests first + */ + + /* pet knows your smell; grabber still has hold of you */ + if (mtmp->mtame || mtmp == u.ustuck) goto found_you; + + /* monsters which know where you are don't suddenly forget, + if you haven't moved away */ + if (mx == u.ux && my == u.uy) goto found_you; + + notseen = (!mtmp->mcansee || (Invis && !perceives(mtmp->data))); + /* add cases as required. eg. Displacement ... */ + if (notseen || Underwater) { + /* Xorns can smell valuable metal like gold, treat as seen */ + if ((mtmp->data == &mons[PM_XORN]) && +#ifndef GOLDOBJ + u.ugold +#else + umoney +#endif + && !Underwater) + disp = 0; + else + disp = 1; + } else if (Displaced) { + disp = couldsee(mx, my) ? 2 : 1; + } else disp = 0; + if (!disp) goto found_you; + + /* without something like the following, invis. and displ. + are too powerful */ + gotu = notseen ? !rn2(3) : Displaced ? !rn2(4) : FALSE; + +#if 0 /* this never worked as intended & isn't needed anyway */ + /* If invis but not displaced, staying around gets you 'discovered' */ + gotu |= (!Displaced && u.dx == 0 && u.dy == 0); +#endif + + if (!gotu) { + register int try_cnt = 0; + do { + if (++try_cnt > 200) goto found_you; /* punt */ + mx = u.ux - disp + rn2(2*disp+1); + my = u.uy - disp + rn2(2*disp+1); + } while (!isok(mx,my) + || (disp != 2 && mx == mtmp->mx && my == mtmp->my) + || ((mx != u.ux || my != u.uy) && + !passes_walls(mtmp->data) && + (!ACCESSIBLE(levl[mx][my].typ) || + (closed_door(mx, my) && !can_ooze(mtmp)))) + || !couldsee(mx, my)); + } else { +found_you: + mx = u.ux; + my = u.uy; + } + + mtmp->mux = mx; + mtmp->muy = my; +} + +boolean +can_ooze(mtmp) +struct monst *mtmp; +{ + struct obj *chain, *obj; + + if (!amorphous(mtmp->data)) return FALSE; + if (mtmp == &youmonst) { +#ifndef GOLDOBJ + if (u.ugold > 100L) return FALSE; +#endif + chain = invent; + } else { +#ifndef GOLDOBJ + if (mtmp->mgold > 100L) return FALSE; +#endif + chain = mtmp->minvent; + } + for (obj = chain; obj; obj = obj->nobj) { + int typ = obj->otyp; + +#ifdef GOLDOBJ + if (typ == GOLD_CLASS && obj->quan > 100L) return FALSE; +#endif + if (obj->oclass != GEM_CLASS && + !(typ >= ARROW && typ <= BOOMERANG) && + !(typ >= DAGGER && typ <= CRYSKNIFE) && + typ != SLING && + !is_cloak(obj) && typ != FEDORA && + !is_gloves(obj) && typ != LEATHER_JACKET && +#ifdef TOURIST + typ != CREDIT_CARD && !is_shirt(obj) && +#endif + !(typ == CORPSE && verysmall(&mons[obj->corpsenm])) && + typ != FORTUNE_COOKIE && typ != CANDY_BAR && + typ != PANCAKE && typ != LEMBAS_WAFER && + typ != LUMP_OF_ROYAL_JELLY && + obj->oclass != AMULET_CLASS && + obj->oclass != RING_CLASS && +#ifdef WIZARD + obj->oclass != VENOM_CLASS && +#endif + typ != SACK && typ != BAG_OF_HOLDING && + typ != BAG_OF_TRICKS && !Is_candle(obj) && + typ != OILSKIN_SACK && typ != LEASH && + typ != STETHOSCOPE && typ != BLINDFOLD && typ != TOWEL && + typ != TIN_WHISTLE && typ != MAGIC_WHISTLE && + typ != MAGIC_MARKER && typ != TIN_OPENER && + typ != SKELETON_KEY && typ != LOCK_PICK + ) return FALSE; + if (Is_container(obj) && obj->cobj) return FALSE; + + } + return TRUE; +} + +#endif /* OVL0 */ + +/*monmove.c*/