diff -u -r1.1.1.1 config.h --- include/config.h 24 Feb 2003 16:06:14 -0000 1.1.1.1 +++ include/config.h 4 Mar 2003 16:52:44 -0000 @@ -342,6 +342,12 @@ #define EXP_ON_BOTL /* Show experience on bottom line */ /* #define SCORE_ON_BOTL */ /* added by Gary Erickson (erickson@ucivax) */ +#define MONSTER_TIMERS /* Some monster intrinsics can time out */ + +#define PET_RANGED_ATTACKS /* Pets can use ranged attacks */ + +#define NEW_MONSTER_SPELLS /* Monsters get to use more spells */ + /* * Section 5: EXPERIMENTAL STUFF * diff -u -r1.1.1.1 extern.h --- include/extern.h 24 Feb 2003 16:06:14 -0000 1.1.1.1 +++ include/extern.h 16 Mar 2003 15:53:15 -0000 @@ -411,6 +411,10 @@ E int FDECL(dog_nutrition, (struct monst *,struct obj *)); E int FDECL(dog_eat, (struct monst *,struct obj *,int,int,BOOLEAN_P)); E int FDECL(dog_move, (struct monst *,int)); +E struct monst *FDECL(adjacent_enemy, (struct monst *, int, int, BOOLEAN_P)); +E boolean FDECL(has_adjacent_enemy, (struct monst *, int, int, BOOLEAN_P)); +E boolean FDECL(region_has_friendly, (struct monst *, int, int, int)); +E int FDECL(find_friends, (struct monst *, struct monst *, int)); #ifdef USE_TRAMPOLI E void FDECL(wantdoor, (int,int,genericptr_t)); #endif @@ -603,7 +607,7 @@ E const char *FDECL(fqname, (const char *, int, int)); E FILE *FDECL(fopen_datafile, (const char *,const char *,int)); E boolean FDECL(uptodate, (int,const char *)); -E void FDECL(store_version, (int)); +E void FDECL(store_version, (int, BOOLEAN_P)); #ifdef MFLOPPY E void NDECL(set_lock_and_bones); #endif @@ -936,6 +940,9 @@ E int FDECL(castmu, (struct monst *,struct attack *,BOOLEAN_P,BOOLEAN_P)); E int FDECL(buzzmu, (struct monst *,struct attack *)); +E int FDECL(castmm, (struct monst *,struct attack *,struct monst *)); +E int FDECL(cancast, (struct monst *, int)); +E int FDECL(mcast_escape_spell, (struct monst *, int)); /* ### mhitm.c ### */ @@ -1092,11 +1099,11 @@ E int FDECL(meatmetal, (struct monst *)); E int FDECL(meatobj, (struct monst *)); E void FDECL(mpickgold, (struct monst *)); -E boolean FDECL(mpickstuff, (struct monst *,const char *)); +E boolean FDECL(mpickstuff, (struct monst *,const char *, boolean *)); E int FDECL(curr_mon_load, (struct monst *)); E int FDECL(max_mon_load, (struct monst *)); E boolean FDECL(can_carry, (struct monst *,struct obj *)); -E int FDECL(mfndpos, (struct monst *,coord *,long *,long)); +E int FDECL(mfndpos, (struct monst *,coord *,long *,long, int *)); E boolean FDECL(monnear, (struct monst *,int,int)); E void NDECL(dmonsfree); E int FDECL(mcalcmove, (struct monst*)); @@ -1143,6 +1150,7 @@ E struct attack *FDECL(attacktype_fordmg, (struct permonst *,int,int)); E boolean FDECL(attacktype, (struct permonst *,int)); E boolean FDECL(poly_when_stoned, (struct permonst *)); +E boolean FDECL(is_levitating, (struct monst *)); E boolean FDECL(resists_drli, (struct monst *)); E boolean FDECL(resists_magm, (struct monst *)); E boolean FDECL(resists_blnd, (struct monst *)); @@ -1259,6 +1267,9 @@ E void FDECL(thrwmu, (struct monst *)); E int FDECL(spitmu, (struct monst *,struct attack *)); E int FDECL(breamu, (struct monst *,struct attack *)); +E int FDECL(thrwmm, (struct monst *, struct monst *)); +E int FDECL(spitmm, (struct monst *,struct attack *, struct monst *)); +E int FDECL(breamm, (struct monst *,struct attack *, struct monst *)); E boolean FDECL(linedup, (XCHAR_P,XCHAR_P,XCHAR_P,XCHAR_P)); E boolean FDECL(lined_up, (struct monst *)); E struct obj *FDECL(m_carrying, (struct monst *,int)); @@ -1271,6 +1282,9 @@ E boolean FDECL(find_defensive, (struct monst *)); E int FDECL(use_defensive, (struct monst *)); E int FDECL(rnd_defensive_item, (struct monst *)); +E int FDECL(mbhitm, (struct monst *, struct obj *)); +E void FDECL(dombhit, (struct monst *, int, int (*) (MONST_P, OBJ_P), + int (*) (OBJ_P, OBJ_P), struct obj *, int, int)); E boolean FDECL(find_offensive, (struct monst *)); #ifdef USE_TRAMPOLI E int FDECL(mbhitm, (struct monst *,struct obj *)); @@ -1649,6 +1663,7 @@ E void FDECL(save_regions, (int,int)); E void FDECL(rest_regions, (int,BOOLEAN_P)); E NhRegion* FDECL(create_gas_cloud, (XCHAR_P, XCHAR_P, int, int)); +E NhRegion* FDECL(create_acid_cloud, (XCHAR_P, XCHAR_P, int, int)); /* ### restore.c ### */ @@ -1851,6 +1866,7 @@ #ifdef USE_TRAMPOLI E int NDECL(learn); #endif +E int FDECL(isqrt, (int)); E int FDECL(study_book, (struct obj *)); E void FDECL(book_disappears, (struct obj *)); E void FDECL(book_substitution, (struct obj *,struct obj *)); @@ -1872,6 +1888,8 @@ #else E long NDECL(somegold); #endif +E boolean FDECL(keep_ammo, (struct monst *, struct obj *, struct obj *)); +E boolean FDECL(keep_launcher, (struct monst *, struct obj *, struct obj *)); E void FDECL(stealgold, (struct monst *)); E void FDECL(remove_worn_item, (struct obj *,BOOLEAN_P)); E int FDECL(steal, (struct monst *, char *)); @@ -1937,6 +1955,9 @@ E void FDECL(learn_egg_type, (int)); E void FDECL(burn_object, (genericptr_t, long)); E void FDECL(begin_burn, (struct obj *, BOOLEAN_P)); +E void FDECL(begin_levitation, (struct monst *, int)); +E void FDECL(begin_invis, (struct monst *, int)); +E void FDECL(begin_speed, (struct monst *, int)); E void FDECL(end_burn, (struct obj *, BOOLEAN_P)); E void NDECL(do_storms); E boolean FDECL(start_timer, (long, SHORT_P, SHORT_P, genericptr_t)); @@ -1945,6 +1966,7 @@ E void FDECL(obj_move_timers, (struct obj *, struct obj *)); E void FDECL(obj_split_timers, (struct obj *, struct obj *)); E void FDECL(obj_stop_timers, (struct obj *)); +E void FDECL(mon_stop_timers, (struct monst *)); E boolean FDECL(obj_is_local, (struct obj *)); E void FDECL(save_timers, (int,int,int)); E void FDECL(restore_timers, (int,int,BOOLEAN_P,long)); @@ -2287,12 +2309,15 @@ E void FDECL(setworn, (struct obj *,long)); E void FDECL(setnotworn, (struct obj *)); -E void FDECL(mon_set_minvis, (struct monst *)); +E struct obj *FDECL(get_equiv_armor, (struct monst *, int)); +E void FDECL(m_remove_armor, (struct monst *, struct obj *, BOOLEAN_P)); +E int FDECL(m_stop_levitating, (struct monst *)); +E void FDECL(mon_set_minvis, (struct monst *, BOOLEAN_P)); E void FDECL(mon_adjust_speed, (struct monst *,int,struct obj *)); E void FDECL(update_mon_intrinsics, (struct monst *,struct obj *,BOOLEAN_P,BOOLEAN_P)); E int FDECL(find_mac, (struct monst *)); -E void FDECL(m_dowear, (struct monst *,BOOLEAN_P)); +E void FDECL(m_dowear, (struct monst *,BOOLEAN_P, BOOLEAN_P)); E struct obj *FDECL(which_armor, (struct monst *,long)); E void FDECL(mon_break_armor, (struct monst *,BOOLEAN_P)); E void FDECL(bypass_obj, (struct obj *)); @@ -2337,6 +2362,7 @@ E struct monst *FDECL(boomhit, (int,int)); E int FDECL(burn_floor_paper, (int,int,BOOLEAN_P,BOOLEAN_P)); E void FDECL(buzz, (int,int,XCHAR_P,XCHAR_P,int,int)); +E void FDECL(dobuzz, (int,int,XCHAR_P,XCHAR_P,int,int,boolean)); E void FDECL(melt_ice, (XCHAR_P,XCHAR_P)); E int FDECL(zap_over_floor, (XCHAR_P,XCHAR_P,int,boolean *)); E void FDECL(fracture_rock, (struct obj *)); diff -u -r1.1.1.1 monattk.h --- include/monattk.h 24 Feb 2003 16:06:14 -0000 1.1.1.1 +++ include/monattk.h 24 Feb 2003 16:29:32 -0000 @@ -97,4 +97,15 @@ #define MM_DEF_DIED 0x2 /* defender died */ #define MM_AGR_DIED 0x4 /* aggressor died */ +struct monspell { + int spell; /* Spell ID SPE_foo */ + int cost; /* Cost the caster incurs by using the spell, */ + /* in terms of mspec_used. */ + int pref; /* How much monster likes the spell: all preferences */ + /* summed up should be less than 100 for attack spells. */ + /* For escape spells, this is how likely the spell is */ + /* work on any given turn. */ + char escape;/* Escape spell if true */ +}; + #endif /* MONATTK_H */ diff -u -r1.1.1.1 timeout.h --- include/timeout.h 24 Feb 2003 16:06:15 -0000 1.1.1.1 +++ include/timeout.h 24 Feb 2003 16:29:32 -0000 @@ -28,7 +28,10 @@ #define BURN_OBJECT 3 #define HATCH_EGG 4 #define FIG_TRANSFORM 5 -#define NUM_TIME_FUNCS 6 +#define TIMEOUT_LEV 6 +#define TIMEOUT_INVIS 7 +#define TIMEOUT_SPEED 8 +#define NUM_TIME_FUNCS 9 /* used in timeout.c */ typedef struct fe { diff -u -r1.1.1.1 bones.c --- src/bones.c 24 Feb 2003 16:06:15 -0000 1.1.1.1 +++ src/bones.c 16 Mar 2003 15:54:04 -0000 @@ -282,7 +282,7 @@ an(mons[u.ugrave_arise].mname)); display_nhwindow(WIN_MESSAGE, FALSE); drop_upon_death(mtmp, (struct obj *)0); - m_dowear(mtmp, TRUE); + m_dowear(mtmp, TRUE, FALSE); } if (mtmp) { mtmp->m_lev = (u.ulevel ? u.ulevel : 1); @@ -358,11 +358,15 @@ } #endif /* MFLOPPY */ - store_version(fd); + /* Kill monster timers to stay compatible with unpatched NetHack */ + mon_stop_timers((struct monst *) 0); + + store_version(fd, TRUE); bwrite(fd, (genericptr_t) &c, sizeof c); bwrite(fd, (genericptr_t) bonesid, (unsigned) c); /* DD.nnn */ savefruitchn(fd, WRITE_SAVE | FREE_SAVE); update_mlstmv(); /* update monsters for eventual restoration */ + savelev(fd, ledger_no(&u.uz), WRITE_SAVE | FREE_SAVE); bclose(fd); commit_bonesfile(&u.uz); diff -u -r1.1.1.1 dbridge.c --- src/dbridge.c 24 Feb 2003 16:06:15 -0000 1.1.1.1 +++ src/dbridge.c 24 Feb 2003 16:29:32 -0000 @@ -363,7 +363,7 @@ (Wwalking || Amphibious || Swimming || Flying || Levitation)) || is_swimmer(etmp->edata) || is_flyer(etmp->edata) || - is_floater(etmp->edata)); + is_levitating(etmp->emon)); /* must force call to lava_effects in e_died if is_u */ if (is_lava(x, y)) return (boolean)((is_u(etmp) && (Levitation || Flying)) || @@ -461,7 +461,7 @@ (etmp->emon->mcanmove && !etmp->emon->msleeping))) /* flying requires mobility */ misses = 5; /* out of 8 */ - else if (is_floater(etmp->edata) || + else if (is_levitating(etmp->emon) || (is_u(etmp) && Levitation)) /* doesn't require mobility */ misses = 3; else if (chunks && is_pool(etmp->ex, etmp->ey)) @@ -699,7 +699,7 @@ You_hear("a splash."); if (e_survives_at(etmp, etmp->ex, etmp->ey)) { if (e_inview && !is_flyer(etmp->edata) && - !is_floater(etmp->edata)) + !is_levitating(etmp->emon)) pline("%s from the bridge.", E_phrase(etmp, "fall")); return; diff -u -r1.1.1.1 dig.c --- src/dig.c 24 Feb 2003 16:06:15 -0000 1.1.1.1 +++ src/dig.c 24 Feb 2003 16:29:32 -0000 @@ -554,7 +554,7 @@ if (oldobjs != newobjs) /* something unearthed */ (void) pickup(1); /* detects pit */ } else if(mtmp) { - if(is_flyer(mtmp->data) || is_floater(mtmp->data)) { + if(is_flyer(mtmp->data) || is_levitating(mtmp)) { if(canseemon(mtmp)) pline("%s %s over the pit.", Monnam(mtmp), (is_flyer(mtmp->data)) ? @@ -613,7 +613,7 @@ impact_drop((struct obj *)0, x, y, 0); if (mtmp) { /*[don't we need special sokoban handling here?]*/ - if (is_flyer(mtmp->data) || is_floater(mtmp->data) || + if (is_flyer(mtmp->data) || is_levitating(mtmp) || mtmp->data == &mons[PM_WUMPUS] || (mtmp->wormno && count_wsegs(mtmp) > 5) || mtmp->data->msize >= MZ_HUGE) return; diff -u -r1.1.1.1 do.c --- src/do.c 24 Feb 2003 16:06:15 -0000 1.1.1.1 +++ src/do.c 24 Feb 2003 16:29:32 -0000 @@ -1389,7 +1389,7 @@ if ((otmp = which_armor(mtmp, W_ARMS)) == 0 || otmp->otyp != SHIELD_OF_REFLECTION) { (void) mongets(mtmp, AMULET_OF_REFLECTION); - m_dowear(mtmp, TRUE); + m_dowear(mtmp, TRUE, FALSE); } } } diff -u -r1.1.1.1 dogmove.c --- src/dogmove.c 24 Feb 2003 16:06:15 -0000 1.1.1.1 +++ src/dogmove.c 24 Feb 2003 16:33:16 -0000 @@ -6,6 +6,8 @@ #include "mfndpos.h" #include "edog.h" +#include "emin.h" +#include "epri.h" extern boolean notonhead; @@ -24,11 +26,18 @@ register struct monst *mon; { register struct obj *obj; - struct obj *wep = MON_WEP(mon); + struct obj *wep = MON_WEP(mon), + /* The melee weapon the pet favors - don't drop + * unless scared */ + *pref_wep = 0; boolean item1 = FALSE, item2 = FALSE; - + boolean uses_weap = is_armed(mon->data); + if (is_animal(mon->data) || mindless(mon->data)) item1 = item2 = TRUE; + else if (uses_weap) + pref_wep = select_hwep(mon); + if (!tunnels(mon->data) || !needspick(mon->data)) item1 = TRUE; for(obj = mon->minvent; obj; obj = obj->nobj) { @@ -41,7 +50,22 @@ item2 = TRUE; continue; } - if (!obj->owornmask && obj != wep) return obj; + if (!obj->owornmask && obj != wep) { + if (uses_weap && +#ifndef GIVE_PATCH + !mon->mflee && +#endif + obj->oclass == WEAPON_CLASS) { + int skill = objects[obj->otyp].oc_skill; + if ((is_missile(obj) || obj == pref_wep || + (is_ammo(obj) && keep_ammo(mon, obj, 0)) || + (is_launcher(obj) && keep_launcher(mon, obj, 0)) || + skill == P_DAGGER || skill == P_KNIFE)) { + continue; + } + } + return obj; + } } return (struct obj *)0; } @@ -55,6 +79,9 @@ STATIC_VAR xchar gtyp, gx, gy; /* type and position of dog's current goal */ STATIC_PTR void FDECL(wantdoor, (int, int, genericptr_t)); +STATIC_PTR struct monst* FDECL(find_targ, (struct monst *, int, int, int)); +STATIC_PTR struct monst* FDECL(best_target, (struct monst *)); +STATIC_PTR long FDECL(score_targ, (struct monst *, struct monst *)); #ifdef OVLB STATIC_OVL boolean @@ -300,18 +327,27 @@ !is_pool(mtmp->mx,mtmp->my)) { if(rn2(20) < edog->apport+3) { if (rn2(udist) || !rn2(edog->apport)) { - if (cansee(omx, omy) && flags.verbose) - pline("%s picks up %s.", Monnam(mtmp), - distant_name(obj, doname)); - obj_extract_self(obj); - newsym(omx,omy); - (void) mpickobj(mtmp,obj); - if (attacktype(mtmp->data, AT_WEAP) && + /* Stop levitating */ + if (!(mtmp->mintrinsics & MR2_LEVITATE) || + m_stop_levitating(mtmp) >= 0) { + + /* If ending levitation left monster frozen */ + mtmp->mfrozen = 0; + mtmp->mcanmove = 1; + + if (cansee(omx, omy) && flags.verbose) + pline("%s picks up %s.", Monnam(mtmp), + distant_name(obj, doname)); + obj_extract_self(obj); + newsym(omx,omy); + (void) mpickobj(mtmp,obj); + if (attacktype(mtmp->data, AT_WEAP) && mtmp->weapon_check == NEED_WEAPON) { - mtmp->weapon_check = NEED_HTH_WEAPON; - (void) mon_wield_item(mtmp); + mtmp->weapon_check = NEED_HTH_WEAPON; + (void) mon_wield_item(mtmp); + } + m_dowear(mtmp, FALSE, TRUE); } - m_dowear(mtmp, FALSE); } } } @@ -486,6 +522,7 @@ int chi = -1, nidist, ndist; coord poss[9]; long info[9], allowflags; + int wants_lev = 0; #define GDIST(x,y) (dist2(x,y,gx,gy)) /* @@ -572,7 +609,19 @@ } if (is_giant(mtmp->data)) allowflags |= BUSTDOOR; if (tunnels(mtmp->data)) allowflags |= ALLOW_DIG; - cnt = mfndpos(mtmp, poss, info, allowflags); + + wants_lev = 0; + cnt = mfndpos(mtmp, poss, info, allowflags, &wants_lev); + + if (wants_lev > 1) { + /* mtmp would like to levitate. Can we arrange for it? */ + if (cancast(mtmp, SPE_LEVITATION)) { + /* Yes! */ + mcast_escape_spell(mtmp, SPE_LEVITATION); + /* This turn spent getting airborne */ + return 0; + } + } /* Normally dogs don't step on cursed items, but if they have no * other choice they will. This requires checking ahead of time @@ -702,6 +751,62 @@ } nxti: ; } + + /* Pet hasn't attacked anything but is considering moving - + * now's the time for ranged attacks. Note that the pet can + * move after it performs its ranged attack. Should this be + * changed? + */ + { + struct monst *mtarg; + int hungry = 0; + + /* How hungry is the pet? */ + if (!mtmp->isminion) { + struct edog *dog = EDOG(mtmp); + hungry = (monstermoves > (dog->hungrytime + 300)); + } + + /* Identify the best target in a straight line from the pet; + * if there is such a target, we'll let the pet attempt an + * attack. + */ + mtarg = best_target(mtmp); + + /* Hungry pets are unlikely to use breath/spit attacks */ + if (mtarg && (!hungry || !rn2(5))) { + int mstatus; + + if (mtarg == &youmonst) { + if (mattacku(mtmp)) + return 2; + } else { + mstatus = mattackm(mtmp, mtarg); + + /* Shouldn't happen, really */ + if (mstatus & MM_AGR_DIED) return 2; + + /* Allow the targeted nasty to strike back - if + * the targeted beast doesn't have a ranged attack, + * nothing will happen. + */ + if ((mstatus & MM_HIT) && !(mstatus & MM_DEF_DIED) && + rn2(4) && mtarg != &youmonst) { + + /* Can monster see? If it can, it can retaliate + * even if the pet is invisible, since it'll see + * the direction from which the ranged attack came; + * if it's blind or unseeing, it can't retaliate + */ + if (mtarg->mcansee && haseyes(mtarg->data)) { + mstatus = mattackm(mtarg, mtmp); + if (mstatus & MM_DEF_DIED) return 2; + } + } + } + } + } + newdogpos: if (nix != omx || niy != omy) { struct obj *mw_tmp; @@ -844,6 +949,322 @@ gy = y; *(int*)distance = ndist; } +} + +STATIC_PTR struct monst* +best_target(mtmp) +struct monst *mtmp; /* Pet */ +{ + int dx, dy; + long bestscore = -40000L, currscore; + struct monst *best_targ = 0, *temp_targ = 0; + + /* Help! */ + if (!mtmp) + return 0; + + /* If the pet is blind, it's not going to see any target */ + if (!mtmp->mcansee) + return 0; + + /* Search for any monsters lined up with the pet, within an arbitrary + * distance from the pet (7 squares, even along diagonals). Monsters + * are assigned scores and the best score is chosen. + */ + for (dy = -1; dy < 2; ++dy) { + for (dx = -1; dx < 2; ++dx) { + if (!dx && !dy) + continue; + /* Traverse the line to find the first monster within 7 + * squares. Invisible monsters are skipped (if the + * pet doesn't have see invisible). + */ + temp_targ = find_targ(mtmp, dx, dy, 7); + + /* Nothing in this line? */ + if (!temp_targ) + continue; + + /* Decide how attractive the target is */ + currscore = score_targ(mtmp, temp_targ); + + if (currscore > bestscore) { + bestscore = currscore; + best_targ = temp_targ; + } + } + } + + /* Filter out targets the pet doesn't like */ + if (bestscore < 0L) + best_targ = 0; + + return best_targ; +} + +STATIC_PTR struct monst * +find_targ(mtmp, dx, dy, maxdist) +register struct monst *mtmp; +int dx, dy; +int maxdist; +{ + struct monst *targ = 0; + + int curx = mtmp->mx, cury = mtmp->my; + int dist = 0; + /* Walk outwards */ + for ( ; dist < maxdist; ++dist) { + curx += dx; + cury += dy; + if (!isok(curx, cury)) + break; + + /* FIXME: Check if we hit a wall/door/boulder to + * short-circuit unnecessary subsequent checks + */ + + /* If we can't see up to here, forget it - will this + * mean pets in corridors don't breathe at monsters + * in rooms? If so, is that necessarily bad? + */ + if (!m_cansee(mtmp, curx, cury)) + break; + + targ = m_at(curx, cury); + + if (curx == mtmp->mux && cury == mtmp->muy) + return &youmonst; + + if (targ) { + /* Is the monster visible to the pet? */ + if ((!targ->minvis || perceives(mtmp->data)) && + !targ->mundetected) + break; + + /* If the pet can't see it, it assumes it aint there */ + targ = 0; + } + } + + return targ; +} + +STATIC_PTR long +score_targ(mtmp, mtarg) +struct monst *mtmp, *mtarg; +{ + long score = 0L; + + /* If the monster is confused, normal scoring is disrupted - + * anything may happen + */ + + /* Give 1 in 3 chance of safe breathing even if pet is confused or + * if you're on the quest start level */ + if (!mtmp->mconf || !rn2(3) || Is_qstart(&u.uz)) { + aligntyp align1, align2; /* For priests, minions */ + boolean faith1 = TRUE, faith2 = TRUE; + + if (mtmp->isminion) align1 = EMIN(mtmp)->min_align; + else if (mtmp->ispriest) align1 = EPRI(mtmp)->shralign; + else faith1 = FALSE; + if (mtarg->isminion) align2 = EMIN(mtarg)->min_align; /* MAR */ + else if (mtarg->ispriest) align2 = EPRI(mtarg)->shralign; /* MAR */ + else faith2 = FALSE; + + /* Never target quest friendlies */ + if (mtarg->data->msound == MS_LEADER + || mtarg->data->msound == MS_GUARDIAN) + return -5000L; + + /* D: Fixed angelic beings using gaze attacks on coaligned priests */ + if (faith1 && faith2 && align1 == align2 && mtarg->mpeaceful) { + score -= 5000L; + return score; + } + + /* Is monster adjacent? */ + if (distmin(mtmp->mx, mtmp->my, mtarg->mx, mtarg->my) <= 1) { + score -= 3000L; + return score; + } + + /* Is the monster peaceful or tame? */ + if (/*mtarg->mpeaceful ||*/ mtarg->mtame || mtarg == &youmonst) { + /* Pets will never be targeted */ + score -= 3000L; + return score; + } + + /* Is master/pet behind monster? Check up to 15 squares beyond + * pet. + */ + if (find_friends(mtmp, mtarg, 15)) { + score -= 3000L; + return score; + } + + /* Target hostile monsters in preference to peaceful ones */ + if (!mtarg->mpeaceful) + score += 10; + + /* Is the monster passive? Don't waste energy on it, if so */ + if (mtarg->data->mattk[0].aatyp == AT_NONE) + score -= 1000; + + /* Even weak pets with breath attacks shouldn't take on very + * low-level monsters. Wasting breath on lichens is ridiculous. + */ + if ((mtarg->m_lev < 2 && mtmp->m_lev > 5) || + (mtmp->m_lev > 12 && mtarg->m_lev < mtmp->m_lev - 9 + && u.ulevel > 8 && mtarg->m_lev < u.ulevel - 7)) + score -= 25; + + /* And pets will hesitate to attack vastly stronger foes. + * This penalty will be discarded if master's in trouble. + */ + if (mtarg->m_lev > mtmp->m_lev + 4L) + score -= (mtarg->m_lev - mtmp->m_lev) * 20L; + + /* All things being the same, go for the beefiest monster. This + * bonus should not be large enough to override the pet's aversion + * to attacking much stronger monsters. + */ + score += mtarg->m_lev * 2 + mtarg->mhp / 3; + } + + /* Fuzz factor to make things less predictable when very + * similar targets are abundant + */ + score += rnd(5); + + /* Pet may decide not to use ranged attack when confused */ + if (mtmp->mconf && !rn2(3)) + score -= 1000; + + return score; +} + +int +find_friends(mtmp, mtarg, maxdist) +struct monst *mtmp, *mtarg; +int maxdist; +{ + struct monst *pal; + + int dx = sgn(mtarg->mx - mtmp->mx), + dy = sgn(mtarg->my - mtmp->my); + int curx = mtarg->mx, cury = mtarg->my; + int dist = distmin(mtarg->mx, mtarg->my, mtmp->mx, mtmp->my); + + for ( ; dist <= maxdist; ++dist) { + curx += dx; + cury += dy; + + if (!isok(curx, cury)) + return 0; + + /* If the pet can't see beyond this point, don't + * check any farther + */ + if (!m_cansee(mtmp, curx, cury)) + return 0; + + /* Does pet think you're here? */ + if (mtmp->mux == curx && mtmp->muy == cury) + return 1; + + pal = m_at(curx, cury); + + if (pal) { + if (pal->mtame) { + /* Pet won't notice invisible pets */ + if (!pal->minvis || perceives(mtmp->data)) + return 1; + } else { + /* Quest leaders and guardians are always seen */ + if (pal->data->msound == MS_LEADER || + pal->data->msound == MS_GUARDIAN) + return 1; + } + } + } + + return 0; +} + +/* Find the first hostile monster adjacent to the location */ +struct monst * +adjacent_enemy(mtmp, x, y, enemy) +struct monst *mtmp; +int x, y; +boolean enemy; /* true for enemies, false for friends */ +{ + int nx, ny, curx, cury; + for (ny = -1; ny < 2; ++ny) { + for (nx = -1; nx < 2; ++nx) { + if (!nx && !ny) + continue; + curx = nx + x; + cury = ny + y; + if (isok(curx, cury)) { + struct monst *mx; + + if (!enemy && curx == mtmp->mux && cury == mtmp->muy) + return &youmonst; + + mx = m_at(curx, cury); + + /* D: D'oh missed this null check before */ + if (!mx) continue ; + + if (((!enemy && mx->mtame) || + (enemy && !mx->mtame && !mx->mpeaceful)) + && (!mx->minvis || perceives(mtmp->data))) + return mx; + } + } + } + return 0; +} + +/* Returns true if there's a hostile monster adjacent to the location */ +boolean +has_adjacent_enemy(mtmp, x, y, enemy) +struct monst *mtmp; /* Pet */ +int x, y; +boolean enemy; /* true for enemies, false for friends */ +{ + return !!adjacent_enemy(mtmp, x, y, enemy); +} + +/* Returns true if there's a monster friendly to mtmp in the specified region */ +boolean +region_has_friendly(mtmp, x, y, radius) +struct monst *mtmp; /* Monster, probably pet */ +int x, y, radius; +{ + int lx = x, rx = x, wy, iy, rad; + + /* Hostile monsters have no friends */ + if (!mtmp || !mtmp->mtame) return FALSE; + + /* Check for player, other pets or quest friendlies inside region, and + * abort if necessary */ + for (rad = radius - 1; rad >= 0; --rad, --lx, ++rx) { + for (iy = -rad, wy = y + iy; iy <= rad; ++iy, wy = y + iy) { + struct monst *m1 = isok(lx, wy)? m_at(lx, wy) : 0, + *m2 = rx != lx && isok(rx, wy)? m_at(lx, wy) : 0; +#define is_friendly(m1) (m1 && (m1->mtame || m1->data->msound == MS_LEADER || \ + m1->data->msound == MS_GUARDIAN)) + + if (is_friendly(m1) || is_friendly(m2) || + ((u.ux == lx || u.ux == rx) && u.uy == wy)) + return TRUE; + } + } + /* Ok to proceed, nobody worthwhile is in here :-) */ + return FALSE; } #endif /* OVLB */ diff -u -r1.1.1.1 dokick.c --- src/dokick.c 24 Feb 2003 16:06:15 -0000 1.1.1.1 +++ src/dokick.c 24 Feb 2003 16:29:32 -0000 @@ -214,7 +214,7 @@ } pline("%s %s, %s evading your %skick.", Monnam(mon), (can_teleport(mon->data) ? "teleports" : - is_floater(mon->data) ? "floats" : + is_levitating(mon) ? "floats" : is_flyer(mon->data) ? "swoops" : (nolimbs(mon->data) || slithy(mon->data)) ? "slides" : "jumps"), diff -u -r1.1.1.1 dothrow.c --- src/dothrow.c 24 Feb 2003 16:06:15 -0000 1.1.1.1 +++ src/dothrow.c 24 Feb 2003 16:29:32 -0000 @@ -639,8 +639,10 @@ coord mc, cc; /* At the very least, debilitate the monster */ + if (range > -1) { mon->movement = 0; mon->mstun = 1; + } /* Is the monster stuck or too heavy to push? * (very large monsters have too much inertia, even floaters and flyers) @@ -649,17 +651,25 @@ return; /* Make sure dx and dy are [-1,0,1] */ - dx = sgn(dx); - dy = sgn(dy); + if (range != -1) { + dx = sgn(dx); + dy = sgn(dy); + } if(!range || (!dx && !dy)) return; /* paranoia */ - /* Send the monster along the path */ - mc.x = mon->mx; - mc.y = mon->my; - cc.x = mon->mx + (dx * range); - cc.y = mon->my + (dy * range); - (void) walk_path(&mc, &cc, mhurtle_step, (genericptr_t)mon); - return; + /* Send the monster along the path */ + mc.x = mon->mx; + mc.y = mon->my; + + if (range == -1) { + cc.x = dx; + cc.y = dy; + } else { + cc.x = mon->mx + (dx * range); + cc.y = mon->my + (dy * range); + } + (void) walk_path(&mc, &cc, mhurtle_step, (genericptr_t)mon); + return; } STATIC_OVL void diff -u -r1.1.1.1 makemon.c --- src/makemon.c 24 Feb 2003 16:06:16 -0000 1.1.1.1 +++ src/makemon.c 24 Feb 2003 16:29:32 -0000 @@ -1029,7 +1029,7 @@ if(is_armed(ptr)) m_initweap(mtmp); /* equip with weapons / armor */ m_initinv(mtmp); /* add on a few special items incl. more armor */ - m_dowear(mtmp, TRUE); + m_dowear(mtmp, TRUE, FALSE); } else { /* no initial inventory is allowed */ if (mtmp->minvent) discard_minvent(mtmp); diff -u -r1.1.1.1 mcastu.c --- src/mcastu.c 24 Feb 2003 16:06:16 -0000 1.1.1.1 +++ src/mcastu.c 24 Feb 2003 19:25:59 -0000 @@ -30,18 +30,936 @@ #define CLC_FIRE_PILLAR 8 #define CLC_GEYSER 9 +/* monster miscellaneous spells, assigned to specific monster types, rather + * than given to all monsters. Since these have to coexist with the SPE_* + * spell names, make them negative (we also don't need to bother with pseudo + * objects for these). + */ +#define MSP_POISON_BLAST -1 /* From Slash'EM */ +#define MSP_POISON_FOG -2 /* Fancy name for stinking cloud :-) */ +#define MSP_ACID_FOG -3 /* Corrosive fog */ +#define MSP_BLADES -4 /* Whirling Blades of Doom :-) */ +#define MSP_FORCE_ANIM -5 /* Animate object to hit target */ +#define MSP_FORCE_SLAM -6 /* Bash target into wall or ground */ +#define MSP_FORCE_REPEL -7 /* Only escape, if too close to the player, + * pushes the player away */ + STATIC_DCL void FDECL(cursetxt,(struct monst *,BOOLEAN_P)); STATIC_DCL int FDECL(choose_magic_spell, (int)); +STATIC_DCL int FDECL(get_cloud_radius, (struct monst *, int, int, int)); +STATIC_DCL int FDECL(m_choose_magic_spell, (int)); STATIC_DCL int FDECL(choose_clerical_spell, (int)); +STATIC_DCL int FDECL(m_choose_clerical_spell, (int)); STATIC_DCL void FDECL(cast_wizard_spell,(struct monst *, int,int)); STATIC_DCL void FDECL(cast_cleric_spell,(struct monst *, int,int)); STATIC_DCL boolean FDECL(is_undirected_spell,(unsigned int,int)); STATIC_DCL boolean FDECL(spell_would_be_useless,(struct monst *,unsigned int,int)); +STATIC_DCL boolean FDECL(getjumptarget, (struct monst *, struct monst *, + int *, int *, int)); +STATIC_DCL boolean FDECL(resists_attk, (struct monst *, struct monst *)); +STATIC_DCL int FDECL(damagem, (struct monst *, struct monst *, int)); #ifdef OVL0 extern const char * const flash_types[]; /* from zap.c */ +extern boolean m_using; + +/* Non-standard monster spells must be defined in arrays here; this is clumsy, + * so we should probably put this in a text file somewhere and let makedefs.c + * parse the file and spit out these arrays automatically. + */ + +/* If a spell can be used for both attack and escape, define its monspell + * twice */ +#define ATTACK 0 +#define ESCAPE 1 + +/* Monster specific spells section */ + +/* The idea here is to make magic users significantly more dangerous in the + * early/middle game. Making the endgame difficult is difficult :-) */ +static struct monspell gnomewiz[] = { + /* Gnomish wizards get a little muscle */ + { SPE_FORCE_BOLT, 1, 25, ATTACK }, + { SPE_JUMPING , 2, 45, ATTACK }, /* Close in on hero */ + { SPE_JUMPING , 2, 90, ESCAPE }, + { SPE_KNOCK , 1, 60, ESCAPE }, + { SPE_CURE_BLINDNESS, 4, 60, ESCAPE }, + { STRANGE_OBJECT, 0, 0, 0 } +}; + +static struct monspell orcshaman[] = { + { SPE_FORCE_BOLT, 1, 20, ATTACK }, + /* Dangerous! */ + { MSP_BLADES , 1, 50, ATTACK }, + { STRANGE_OBJECT, 0, 0, 0 } +}; + +static struct monspell koboldsh[] = { + /* Magic missile is apt to kill the shaman himself */ +/* { SPE_MAGIC_MISSILE , 1, 10, ATTACK }, */ + /* Kobold shamans can safely specialize in poison attacks */ +/* { MSP_POISON_BLAST , 1, 5, ATTACK }, */ + /* This seems quite dangerous for low-level characters :-) */ + { MSP_POISON_FOG , 2, 10, ATTACK }, + + /* Unlikely to be used, ever */ + { SPE_RESTORE_ABILITY, 3, 5, ESCAPE }, + /* Might be used, but who cares? */ + { SPE_LEVITATION , 2, 50, ESCAPE }, + { STRANGE_OBJECT, 0, 0, 0 } +}; + +/* Archons should concentrate more on other spells, since the hero's probably + * immune to everything by the time he meets archons */ +static struct monspell archon[] = { + { SPE_CHARM_MONSTER, 5, 15, ATTACK }, + { SPE_FIREBALL , 3, 10, ATTACK }, + { SPE_CONE_OF_COLD, 3, 5, ATTACK }, + { STRANGE_OBJECT, 0, 0, 0 } +}; + +/* My favourite nasty now */ +static struct monspell goldennaga[] = { + /* Creates an interesting mix of monsters, unlike summon nasties */ + { SPE_CREATE_MONSTER, 5, 15, ATTACK }, + /* And the naga is usually tricky to pin down */ + { SPE_TELEPORT_AWAY, 3, 30, ESCAPE }, + { SPE_LEVITATION , 2, 80, ESCAPE }, + { STRANGE_OBJECT, 0, 0, 0 } +}; + +static struct monspell darkone[] = { + { SPE_DRAIN_LIFE, 2, 30, ATTACK }, + { STRANGE_OBJECT, 0, 0, 0 } +}; + +static struct monspell nalfeshnee[] = { + { SPE_FINGER_OF_DEATH, 2, 5, ATTACK }, + { SPE_CHARM_MONSTER, 2, 30, ATTACK }, + { STRANGE_OBJECT, 0, 0, 0 } +}; + +static struct monspell kirin[] = { + { SPE_STONE_TO_FLESH, 3, 50, ATTACK }, /* Animate statues */ + { SPE_STONE_TO_FLESH, 0, 75, ESCAPE }, /* Ward against stoning */ + { SPE_RESTORE_ABILITY, 2, 45, ESCAPE }, + { STRANGE_OBJECT, 0, 0, 0 } +}; + +/* Liches are nasty enough as they are */ +static struct monspell archlich[] = { + { SPE_CAUSE_FEAR, 2, 5, ATTACK }, + { STRANGE_OBJECT, 0, 0, 0 } +}; + +static struct monspell rodney[] = { + { SPE_STONE_TO_FLESH, 0, 80, ESCAPE }, + { STRANGE_OBJECT, 0, 0, 0 } +}; + +static struct monspell thothamon[] = { + { SPE_MAGIC_MISSILE, 2, 5, ATTACK }, + { STRANGE_OBJECT, 0, 0, 0 } +}; + +static struct monspell titan[] = { + { SPE_SLEEP , 2, 10, ATTACK }, + { SPE_KNOCK , 1, 80, ESCAPE }, + { STRANGE_OBJECT, 0, 0, 0 } +}; + +static struct monspell priest[] = { + /* The standard priestly spells are nicely varied and effective, and we + * don't want to reduce those spells by defining general attack spells + * here. Happily, acid fog won't be used if the target is nearby. */ + { MSP_ACID_FOG , 4, 10, ATTACK }, + { STRANGE_OBJECT, 0, 0, 0 } +}; + +STATIC_OVL +struct monspell * +findmspell(spells, spellnum, escape) +struct monspell *spells; +int spellnum; +boolean escape; +{ + int i; + + for (i = 0; spells[i].spell != STRANGE_OBJECT; ++i) { + if (spells[i].spell == spellnum && spells[i].escape == escape) + return &spells[i]; + } + return NULL; +} + +STATIC_OVL +struct monspell * +getmspells(mon) +struct monst *mon; +{ + int ndx = monsndx(mon->data); + + switch (ndx) { + case PM_ALIGNED_PRIEST: + case PM_HIGH_PRIEST: + return priest; + case PM_ARCH_LICH: + return archlich; + case PM_ARCHON: + return archon; + case PM_DARK_ONE: + return darkone; + case PM_GNOMISH_WIZARD: + return gnomewiz; + case PM_GOLDEN_NAGA: + return goldennaga; + case PM_KI_RIN: + return kirin; + case PM_KOBOLD_SHAMAN: + return koboldsh; + case PM_NALFESHNEE: + return nalfeshnee; + case PM_ORC_SHAMAN: + return orcshaman; + case PM_THOTH_AMON: + case PM_NEFERET_THE_GREEN: + return thothamon; + case PM_TITAN: + return titan; + case PM_WIZARD_OF_YENDOR: + return rodney; + } + + return NULL; +} + +/* Returns monspell if the monster can cast the spell - makes no judgement on + * how castable the spell is */ +struct monspell * +getmspell(mon, spell, escape) +struct monst *mon; +int spell; +boolean escape; +{ + struct monspell *msp = getmspells(mon); + + return msp? findmspell(msp, spell, escape) : NULL; +} + +int +cancast(mon, spell) +struct monst *mon; +int spell; +{ + struct monspell *msp = NULL; + + /* A spellcaster versed in restore ability can use it even when confused */ + if (mon->mcan || (mon->mconf && spell != SPE_RESTORE_ABILITY)) + return 0; + + msp = getmspell(mon, spell, ESCAPE); + return (msp && msp->pref > rn2(100)); +} + +/* Returns the radius of a moderately sized region (presumably to be occupied by + * a damaging fog), which does not include the position of the spellcaster, or + * the spellcaster's friends. + */ +STATIC_OVL +int +get_cloud_radius(mon, x, y, dist) +struct monst *mon; +int x, y, dist; +{ + int radius; + + if (dist < 0) dist = dist2(x, y, mon->mx, mon->my); + if (dist < 9) return 0; + + /* A high level spellcaster might create a cloud as big as a b ?oSC would */ + if ((radius = 2 + (mon->m_lev / 5)) > 4) radius = 4; + + /* Reduce cloud radius until the caster is safe. Crude. */ + while (dist < radius * radius) radius--; + /* If there are friends inside, do nothing */ + if (region_has_friendly(mon, x, y, radius)) return 0; + + return radius; +} + +/* Find a statue adjacent to (x,y) - does a light search, looking only at the + * top two objects on each square, to prevent a major performance hit. Statues + * at (x,y) are not considered */ +STATIC_OVL +struct obj * +find_adjacent_statue(x, y) +int x, y; +{ + int dx, dy, px, py, bailout; + struct obj *otmp; + + /* Randomize direction of search to prevent the top-left-object-first + * syndrome; this still leaves us with the corner-object-first problem, but + * that can't be helped. */ + int xi = rn2(100) >= 50? 1 : -1, xend = x + xi + xi, + yi = rn2(100) >= 50? 1 : -1, yend = y + yi + yi; + + for (dx = x - xi; dx != xend; dx += xi) { + for (dy = y - yi; dy != yend; dy += yi) { + if ((dx == x && dy == y) || !isok(dx, dy)) continue; + + for (otmp = level.objects[dx][dy], bailout = 0; + otmp && bailout < 2; + otmp = otmp->nexthere, bailout++) + if (otmp->otyp == STATUE) + return otmp; + } + } + return NULL; +} + + + +/* Cast an attack spell at a target, possibly the player. Returns nonzero if + * a spell was actually cast, zero if the spell was aborted. */ +int +mcast_attk_spell(mtmp, mspel, mtarg) +struct monst *mtmp, *mtarg; +struct monspell *mspel; +{ + int tx, ty; + boolean lined = FALSE; + struct obj *pseudo = (struct obj *) 0; + char caster[BUFSZ]; + boolean youtarg = (mtarg == &youmonst); + boolean seecast = canseemon(mtmp), + seevict = (youtarg || canseemon(mtarg)), + mess = seecast, + wiz = (mtmp->m_lev > 15); /* Skilled spellcaster */ + int ret = 0, dist; + struct attack mattk; + + mattk.aatyp = AT_MAGC; + mattk.adtyp = AD_SPEL; + + /* Simplistic damage (lev/2)d6 */ + mattk.damn = mtmp->m_lev / 2 + 1; + /* Cap the damage at a reasonable number? */ + if (mattk.damn > 10) mattk.damn = 10; + mattk.damd = 6; + + if (mtarg == &youmonst) { + tx = mtmp->mux; + ty = mtmp->muy; + } else { + tx = mtarg->mx; + ty = mtarg->my; + } + + dist = dist2(tx, ty, mtmp->mx, mtmp->my); + lined = linedup(tx, ty, mtmp->mx, mtmp->my); + + /* If the target is adjacent, force a friendly check before using ray + * attacks, since adjacent targets are a result of mhitm attacks, rather + * than pets attacking at range. */ + if (lined && dist < 4 && !youtarg && mtmp->mtame && + find_friends(mtmp, mtarg, 15)) + lined = FALSE; + + tbx = sgn(tbx); + tby = sgn(tby); + + /* Build pseudo spell object, if this is a player spell */ + if (mspel->spell >= 0) { + pseudo = mksobj(mspel->spell, FALSE, FALSE); + pseudo->blessed = pseudo->cursed = 0; + pseudo->quan = 20L; + } + + strcpy(caster, Monnam(mtmp)); + switch (mspel->spell) { + case SPE_FORCE_BOLT: + if (!lined) break; + + ret = 1; + if (mess) + pline("%s zaps a force bolt at %s!", caster, youtarg? "you" : + mon_nam(mtarg)); + m_using = TRUE; + dombhit(mtmp, rn1(8, 6), mbhitm, bhito, pseudo, tbx, tby); + m_using = FALSE; + break; + case SPE_MAGIC_MISSILE: + if (!lined) break; + mattk.adtyp = AD_MAGM; + m_using = TRUE; + ret = buzzmm(mtmp, &mattk, mtarg); + m_using = FALSE; + break; + case MSP_POISON_BLAST: + if (!lined) break; + mattk.adtyp = AD_DRST; + m_using = TRUE; + ret = buzzmm(mtmp, &mattk, mtarg); + m_using = FALSE; + break; + case MSP_POISON_FOG: + { + int radius = get_cloud_radius(mtmp, tx, ty, dist); + if (radius <= 1) break; + /* Don't allow overlapping regions */ + if (visible_region_at(tx, ty)) break; + + if (mess) pline("%s utters an incantation!", caster); + ret = 1; + if (!create_gas_cloud(tx, ty, radius, 5 + mattk.damn) + && mess) + pline("%s seems annoyed.", caster); + } + break; + case MSP_ACID_FOG: + /* In contrast to poison fog, this is a high-damage fog, and acid + * resistance is hard to acquire. Monsters that use this are likely to + * be uncommonly nasty. + */ + { + int radius = get_cloud_radius(mtmp, tx, ty, dist); + if (radius <= 1) break; + /* Don't allow overlapping regions */ + if (visible_region_at(tx, ty)) break; + + if (mess) pline("%s utters an incantation!", caster); + ret = 1; + /* What happens if there are many, many acid clouds around? */ + if (!create_acid_cloud(tx, ty, radius, 8 + mattk.damn) + && mess) + pline("%s seems annoyed.", caster); + } + break; + case SPE_CONE_OF_COLD: + if (!lined && !wiz) break; + ret = 1; + if (wiz) { + if (mess) + pline("%s casts a cone of cold!", caster); + + if (dist < 4 && !resists_cold(mtmp) || + (mtmp->mtame && + has_adjacent_enemy(mtmp, tx, ty, FALSE))) { + ret = 0; + break; + } + m_using = TRUE; + explode(tx, ty, -(pseudo->otyp - SPE_MAGIC_MISSILE), + 20 + rnd(30), + SPBOOK_CLASS, + EXPL_FROSTY); + m_using = FALSE; + } else { + mattk.adtyp = AD_COLD; + m_using = TRUE; + buzzmm(mtmp, &mattk, mtarg); + m_using = FALSE; + } + break; + case SPE_FIREBALL: + if (!lined && !wiz) break; + ret = 1; + if (wiz) { + if (mess) + pline("%s zaps a fireball!", caster); + if (dist < 4 && !resists_fire(mtmp) || + (mtmp->mtame && has_adjacent_enemy(mtmp, tx, ty, FALSE))) { + ret = 0; + break; + } + m_using = TRUE; + explode(tx, ty, -(pseudo->otyp - SPE_MAGIC_MISSILE), + 20 + rnd(30), + SPBOOK_CLASS, + EXPL_FIERY); + m_using = FALSE; + } else { + mattk.adtyp = AD_FIRE; + m_using = TRUE; + buzzmm(mtmp, &mattk, mtarg); + m_using = FALSE; + } + break; + case SPE_SLEEP: + if (!lined) break; + ret = 1; + mattk.adtyp = AD_SLEE; + m_using = TRUE; + buzzmm(mtmp, &mattk, mtarg); + m_using = FALSE; + break; + case SPE_FINGER_OF_DEATH: + if (!lined) break; + ret = 1; + mattk.adtyp = AD_DISN; + m_using = TRUE; + buzzmm(mtmp, &mattk, mtarg); + m_using = FALSE; + break; + case SPE_CREATE_MONSTER: + /* We don't care whether it's lined up or not */ + { + struct monst *mon; + struct permonst *pm = 0, *fish = 0; + coord cc; + int cnt = 1 + (!rn2(3)? rnd(4) : 0); + + if (mtmp->mtame || mtmp->mpeaceful) break; + + ret = 1; + + /* Not really satisfactory, since there might be only the one + * monster created. */ + if (mess) pline("%s creates monsters!", Monnam(mtmp)); + + while(cnt--) { + /* `fish' potentially gives bias towards water locations; + `pm' is what to actually create (0 => random) */ + if (!enexto(&cc, mtmp->mx, mtmp->my, fish)) break; + mon = makemon(pm, cc.x, cc.y, NO_MM_FLAGS); + } + } + break; + case SPE_DRAIN_LIFE: + if (!lined) break; + ret = 1; + + if (mess || youtarg) + pline("%s zaps a spell at %s!", caster, youtarg? "you" : + mon_nam(mtarg)); + m_using = TRUE; + dombhit(mtmp, rn1(8, 6), mbhitm, bhito, pseudo, tbx, tby); + m_using = FALSE; + break; + case SPE_CHARM_MONSTER: + if (mtarg != &youmonst && mtarg->mtame && !mtmp->mpeaceful) { + boolean notice; + + /* Will pet resist? */ + if (mess || seevict) + pline("%s makes a hypnotic gesture at %s!", caster, + mon_nam(mtarg)); + + notice = (haseyes(mtarg->data) && mtarg->mcansee + && !mtarg->msleeping && !mtarg->mconf + && !mtarg->mstun); + + if (!resists_attk(mtarg, mtmp) && !mindless(mtarg->data) + && notice) { + if (seevict) pline("%s turns on you!", Monnam(mtarg)); + mtarg->mtame = 0; + mtarg->mpeaceful = 0; + } else { + if (seevict) { + if (notice) + pline("%s seems unimpressed.", Monnam(mtarg)); + else + pline("%s doesn't notice.", Monnam(mtarg)); + } + } + ret = 1; + } + break; + case SPE_CAUSE_FEAR: + if (mtarg != &youmonst && !mtarg->mflee && mtmp->mcansee) { + static char *fear[] = { + "utters a fearsome curse.", + "mutters a dark incantation.", + "snarls a curse.", + "sings 'Gunga Din'!", + "dials 911!", + "demands an audit!" + }; + if (mess) { + int index = rn2(3) + (Hallucination? 3 : 0); + pline("%s stares at %s and %s", caster, mon_nam(mtarg), + fear[index]); + } + + if (!mindless(mtarg->data) && !mtarg->mconf + && !resists_attk(mtarg, mtmp)) { + monflee(mtarg, rn1(5, 5), TRUE, TRUE); + } else { + if (seevict) + pline("%s seems unimpressed.", Monnam(mtarg)); + } + ret = 1; + } + break; + case SPE_STONE_TO_FLESH: + /* Exotic spell - try to stf any nearby statue */ + if (mtmp->mpeaceful) break; + { + struct obj *adj_statue = find_adjacent_statue(mtmp->mx, mtmp->my); + if (adj_statue) { + ret = 1; + if (mess) + pline("%s casts a spell!", caster); + lined = linedup(adj_statue->ox, adj_statue->oy, + mtmp->mx, mtmp->my); + tbx = sgn(tbx); + tby = sgn(tby); + + m_using = TRUE; + dombhit(mtmp, rn1(8, 5), mbhitm, bhito, pseudo, tbx, tby); + m_using = FALSE; + } + } + break; + case SPE_JUMPING: + { + int ax, ay; /* Target to jump to */ + + /* Levitating monster can't jump, even magically. Keeps consistent + * with player movement code and makes life easier :) */ + if (is_levitating(mtmp) || !getjumptarget(mtmp, mtarg, &ax, &ay, 1) + || mtmp->data->msize >= MZ_HUGE + || mtmp->mtrapped) + break; + + ret = 3; + if (seecast) + pline("%s jumps towards %s!", caster, youtarg? "you" : + mon_nam(mtarg)); + mhurtle(mtmp, ax, ay, -1); + } + break; + case MSP_BLADES: + { + int dmg = d(mattk.damn, mattk.damd); + + ret = 1; + if (mess) + pline("%s points at %s and chants a cryptic spell.", caster, + (youtarg? "you" : mon_nam(mtarg))); + if (youtarg) { + if (Blind) + You_feel("blades slashing at you!"); + else + You("are slashed by whirling blades!"); + } else if (seevict) + pline("%s is slashed by whirling blades!", Monnam(mtarg)); + + { + /* Work out damage */ + struct permonst *mp = youtarg? youmonst.data : mtarg->data; + + if (is_whirly(mp) || unsolid(mp)) { + if (youtarg || seevict) + pline("The blades don't seem to harm %s.", + (youtarg? "you" : mon_nam(mtarg))); + break; + } + /* There's no to-hit calculation here - assume that those blades + * never miss :-) But since this is weapon-based damage, armor + * does protect. */ + if (youtarg) { + if (u.uac < 0) dmg -= rnd(-u.uac); + } else { + int mac = find_mac(mtarg); + if (mac < 0) dmg -= rnd(-mac); + } + if (dmg < 0) break; + } + + if (youtarg) losehp(dmg, "whirling blade", KILLED_BY_AN); + else damagem(mtmp, mtarg, dmg); + } + break; + } /* switch (mspel->spell) */ + +done: + if (pseudo) obfree(pseudo, (struct obj *) 0); + if (ret && !DEADMONSTER(mtmp)) + mtmp->mspec_used = mspel->cost; + return ret; +} + +#define JUMP_DIST 4 +STATIC_DCL +boolean +getjumptarget(mon, mtarg, x, y, appr) +struct monst *mon, *mtarg; +int *x, *y; +int appr; /* -1 to go away from hero, 1 to jump to hero. */ +{ + int dx, dy, tx, ty; + int dist, sqdist; + + if (mtarg == &youmonst) { + tx = mon->mux; + ty = mon->muy; + } else { + tx = mtarg->mx; + ty = mtarg->my; + } + + dist = dist2(tx, ty, mon->mx, mon->my); + + if (dist <= 16 || dist > (BOLT_LIM + 4) * (BOLT_LIM + 4)) + return FALSE; + + dx = tx - mon->mx; + dy = ty - mon->my; + + if (appr == -1) { + dx = -dx; + dy = -dy; + } + + if (dist > JUMP_DIST * JUMP_DIST) { + sqdist = isqrt(dist); + + /* Scale back */ + dx = (dx * JUMP_DIST) / sqdist; + dy = (dy * JUMP_DIST) / sqdist; + } + + if (dx * dx + dy * dy < 9) /* Too close, no point in jumping */ + return FALSE; + + /* Convert relative to abs coords */ + dx += mon->mx; + dy += mon->my; + + if (goodpos(dx, dy, mon, 0)) { + *x = dx; + *y = dy; + return TRUE; + } + return FALSE; +} + +int +mcast_escape_spell(mtmp, spell) +struct monst *mtmp; +int spell; +{ + struct monspell *msp = getmspell(mtmp, spell, ESCAPE); + boolean vismon = canseemon(mtmp); + int ret = 0; + char caster[BUFSZ]; + + strcpy(caster, Monnam(mtmp)); + + if (!msp) { + impossible("Monster doesn't know spell?"); + return 0; + } + + switch (msp->spell) { + case SPE_TELEPORT_AWAY: + /* Duplicated from muse.c */ + if (vismon) + pline("%s casts a teleportation spell!", caster); + if (tele_restrict(mtmp)) { + ret = 2; + break; + } + if ((On_W_tower_level(&u.uz)) && !rn2(3)) { + if (vismon) + pline("%s seems disoriented for a moment.", caster); + ret = 2; + break; + } + rloc(mtmp); + ret = 1; + break; + case SPE_LEVITATION: +#ifdef SINKS + /* If the monster's on a sink, don't even bother */ + if (IS_SINK(levl[mtmp->mx][mtmp->my].typ)) break; +#endif + ret = 3; + if (vismon) { + if (is_levitating(mtmp)) + pline("%s casts a levitation spell!", caster); + else + pline("%s begins to levitate!", caster); + } + begin_levitation(mtmp, 10 + rnd(20)); + mtmp->mintrinsics |= MR2_LEVITATE; + break; + case SPE_JUMPING: + { + int ax, ay; /* Target to jump to */ + + /* Levitating monster can't jump, even magically. Keeps consistent + * with player movement code and makes life easier :) */ + if (is_levitating(mtmp) || !getjumptarget(mtmp, &youmonst, + &ax, &ay, -1) + || mtmp->data->msize >= MZ_HUGE + || mtmp->mtrapped) + break; + + ret = 3; + if (vismon) + pline("%s jumps away!", caster); + mhurtle(mtmp, ax, ay, -1); + } + break; + case SPE_STONE_TO_FLESH: + /* This should be called only if the monster needs unstoning */ + ret = 1; + + /* Nothing much to do to the caster, but... */ + if (vismon) + pline("%s casts a spell at %sself!", caster, mhim(mtmp)); + + /* ... caster's inventory is nuked by the spell */ + { + struct obj *pseudo, *otemp, *onext; + boolean didmerge; + + pseudo = mksobj(msp->spell, FALSE, FALSE); + pseudo->blessed = pseudo->cursed = 0; + pseudo->quan = 20L; + + /* This code borrowed from zap.c: */ + for (otemp = mtmp->minvent; otemp; otemp = onext) { + onext = otemp->nobj; + (void) bhito(otemp, pseudo); + } + + obfree(pseudo, (struct obj *) 0); + + /* + * Also from zap.c - it's questionable whether a monster's + * inventory really needs merging, but the speed hit is arguably + * excusable, since it's the rare turn that a monster casts stone + * to flesh at itself + */ + /* + * It is possible that we can now merge some inventory. + * Do a higly paranoid merge. Restart from the beginning + * until no merges. + */ + do { + didmerge = FALSE; + for (otemp = mtmp->minvent; !didmerge && otemp; + otemp = otemp->nobj) + for (onext = otemp->nobj; onext; onext = onext->nobj) + if (merged(&otemp, &onext)) { + didmerge = TRUE; + break; + } + } while (didmerge); + } + + break; + case SPE_KNOCK: + /* Nothing to do here, caller does all the real work */ + if (vismon) + pline("%s casts a spell.", caster); + ret = 1; + break; + case SPE_CURE_BLINDNESS: + if (mtmp->mblinded) { + if (vismon) + pline("%s casts a spell at %sself.", caster, mhim(mtmp)); + + mtmp->mblinded = 0; + mtmp->mcansee = 1; + if (vismon) + pline("%s can see again!", caster); + ret = 1; + } + break; + case SPE_RESTORE_ABILITY: + if (mtmp->mblinded || mtmp->mconf || mtmp->mstun) { + ret = 1; + if (vismon) + pline("%s casts a spell at %sself.", caster, mhim(mtmp)); + + if (mtmp->mblinded) { + mtmp->mblinded = 0; + mtmp->mcansee = 1; + if (vismon) + pline("%s can see again!", caster); + } + + if (mtmp->mconf || mtmp->mstun) { + mtmp->mconf = mtmp->mstun = 0; + if (vismon) + pline("%s seems steadier now.", caster); + } + } + break; + } + + if (ret) + mtmp->mspec_used = msp->cost; + + return ret; +} + +#define spellname(spell) OBJ_NAME(objects[spell]) +/* return values: + * 1: successful spell + * 0: unsuccessful spell + */ +int +gcastm(mtmp, mattk, mtarg) +register struct monst *mtmp, *mtarg; +register struct attack *mattk; +{ + struct monspell *msp, *bestspell = NULL; + int bestscore = -1, count = 0, chance = rn2(100); + int tx, ty; + + /* First, see whether we should cast the monster's favourite + * attack spells */ + if (mtmp->mcan || mtmp->mspec_used || mtmp->mconf + || !mtmp->mcansee) + return 0; + + if ((mtarg == &youmonst && !m_canseeu(mtmp)) || + (mtarg != &youmonst && !m_cansee(mtmp, mtarg->mx, mtarg->my))) + return 0; + + if (mtarg == &youmonst) { + tx = mtmp->mux; + ty = mtmp->muy; + } else { + tx = mtarg->mx; + ty = mtarg->my; + } + + if (dist2(tx, ty, mtmp->mx, mtmp->my) > BOLT_LIM * BOLT_LIM) + return 0; + + if (!(msp = getmspells(mtmp))) + return 0; + + for ( ; msp->spell != STRANGE_OBJECT; msp++) { + int score; + + if (msp->escape) + continue; + + count++; + if ((chance -= msp->pref) < 0) { + bestscore = 1; + bestspell = msp; + break; + } + } + + /* Have we chosen a spell? */ + if (bestscore <= 0) + return 0; + + return mcast_attk_spell(mtmp, bestspell, mtarg); +} + /* feedback when frustrated monster couldn't cast a spell */ STATIC_OVL void @@ -120,6 +1038,29 @@ } } +/* convert a level based random selection into a specific mage spell for + * a monster at monster spell */ +STATIC_OVL int +m_choose_magic_spell(spellval) +int spellval; +{ + switch (spellval) { + case 22: + case 21: + case 20: + return MGC_DEATH_TOUCH; + case 8: + case 7: + case 6: + return MGC_WEAKEN_YOU; + case 4: + case 3: + return MGC_STUN_YOU; + default: + return MGC_PSI_BOLT; + } +} + /* convert a level based random selection into a specific cleric spell */ STATIC_OVL int choose_clerical_spell(spellnum) @@ -154,6 +1095,35 @@ } } +STATIC_OVL int +m_choose_clerical_spell(spellnum) +int spellnum; +{ + switch (spellnum) { + case 13: + return CLC_GEYSER; + case 12: + return CLC_FIRE_PILLAR; + case 11: + return CLC_LIGHTNING; + case 7: + case 6: + return CLC_BLIND_YOU; + case 5: + case 4: + return CLC_PARALYZE; + case 3: + case 2: + return CLC_CONFUSE_YOU; + case 1: + return -1; + case 0: + default: + return CLC_OPEN_WOUNDS; + } +} + + /* return values: * 1: successful spell * 0: unsuccessful spell @@ -166,8 +1136,12 @@ boolean foundyou; { int dmg, ml = mtmp->m_lev; - int ret; - int spellnum = 0; + int ret = 0; + int spellnum = 0; + + if (!mtmp->mpeaceful && + (ret = gcastm(mtmp, mattk, &youmonst))) + return ret; /* Three cases: * -- monster is attacking you. Search for a useful spell. @@ -417,8 +1391,9 @@ if (canseemon(mtmp)) pline("%s suddenly %s!", Monnam(mtmp), !See_invisible ? "disappears" : "becomes transparent"); - mon_set_minvis(mtmp); + mon_set_minvis(mtmp, FALSE); dmg = 0; + begin_invis(mtmp, 20 + rnd(20)); } else impossible("no reason for monster to cast disappear spell?"); break; @@ -437,7 +1412,8 @@ dmg = 0; break; case MGC_HASTE_SELF: - mon_adjust_speed(mtmp, 1, (struct obj *)0); + mon_adjust_speed(mtmp, 4, (struct obj *)0); + begin_speed(mtmp, 20 + rnd(20)); dmg = 0; break; case MGC_CURE_SELF: @@ -476,6 +1452,150 @@ } STATIC_OVL +int +damagem(mtmp, mdef, dmg) +struct monst *mtmp, *mdef; +int dmg; +{ + if(DEADMONSTER(mdef) || (mdef->mhp -= dmg) < 1) { + if (m_at(mdef->mx, mdef->my) == mtmp) { /* see gulpmm() */ + remove_monster(mdef->mx, mdef->my); + /* otherwise place_monster will complain */ + mdef->mhp = 1; + place_monster(mdef, mdef->mx, mdef->my); + mdef->mhp = 0; + } + /* AD_PHYS is okay here, since digest, disintegration attacks won't + * use damagem() anyway */ + monkilled(mdef, "", AD_PHYS); + + if (mdef->mhp > 0) return 0; /* mdef lifesaved */ + return (MM_DEF_DIED | + ((mtmp->mhp > 0 && grow_up(mtmp,mdef)) ? 0 : MM_AGR_DIED)); + } +} + +STATIC_OVL +boolean +resists_attk(mdef, magr) +/* No null checks on these */ +struct monst *mdef, *magr; +{ + int alevel = magr->m_lev, + dlevel = mdef->m_lev; + int chance; + if (alevel < 1) + alevel = 1; + if (alevel > 50) + alevel = 50; + if (dlevel < 1) + dlevel = 1; + if (dlevel > 50) + dlevel = 50; + chance = 100 + alevel - dlevel; + return mdef->data->mr > (chance > 0? rn2(chance) : 0); +} + +STATIC_OVL +void +m_cast_wizard_spell(mtmp, mdef, dmg, spellnum) +struct monst *mtmp, *mdef; +int dmg; +int spellnum; +{ + int sees_agr = canseemon(mtmp), sees_def = canseemon(mdef); + + if (dmg == 0 && !is_undirected_spell(AD_SPEL, spellnum)) { + impossible("cast directed wizard spell (%d) with dmg=0?", spellnum); + return; + } + + switch (spellnum) { + case MGC_DEATH_TOUCH: + if (sees_agr) + pline("%s is using the touch of death!", Monnam(mtmp)); + dmg = 0; + if (nonliving(mdef->data) || is_demon(mdef->data)) { + if (sees_def) + pline("%s seems no deader than before.", Monnam(mdef)); + } else if (!resists_magm(mdef)) { + dmg = mdef->mhp; + } else { + shieldeff(mdef->mx, mdef->my); + if (sees_def && sees_agr) + pline("That didn't work..."); + } + break; + case MGC_WEAKEN_YOU: /* drain strength */ + /* For monster-monster combat, drain levels instead */ + /* D: Angelic beings won't drain levels */ + if (mtmp->data->mlet != S_ANGEL) { + /* D: In case level-drain fails */ + dmg = 0; + if (!resists_drli(mdef) && !resists_attk(mdef, mtmp)) { + /* D: Might drain up to 3 levels */ + int nlev = rnd(3); + + dmg = d(2 * nlev, 6); + if (sees_def) + pline("%s suddenly seems %sweaker!", + Monnam(mdef), + ((nlev > 1)? "a lot " : "")); + if ((mdef->mhpmax -= dmg) < 1) + mdef->mhpmax = 1; + /* D: hp itself is drained at the end */ + while (nlev--) + if (mdef->m_lev == 0) { + dmg = mdef->mhp; + mdef->mhpmax = 1; + break; + } + else mdef->m_lev--; + /* Automatic kill if drained past level 0 */ + } + } + + break; + case MGC_STUN_YOU: + if (!resists_magm(mdef) && !resists_attk(mdef, mtmp)) { + if (sees_def && !unsolid(mdef->data) + && !is_whirly(mdef->data) && !amorphous(mdef->data)) + pline("%s reels...", Monnam(mdef)); + dmg = d(4, 4); + mdef->mstun = 1; + } + break; + case MGC_PSI_BOLT: + /* D: This is way too common - make it less so */ + if (rn2(3)) { + dmg = 0; + break; + } + + if (resists_magm(mdef)) + dmg = (dmg + 1) / 2; + + if (mindless(mdef->data)) + dmg = 0; + + if (sees_agr && sees_def) { + char buf[BUFSZ]; + strcpy(buf, mon_nam(mdef)); + pline("%s casts a psi-bolt at %s!", Monnam(mtmp), buf); + if (dmg == 0) + pline("%s seems unharmed.", Monnam(mdef)); + } + + break; + default: + dmg = 0; + break; + } + + if (dmg) damagem(mtmp, mdef, dmg); +} + +STATIC_OVL void cast_cleric_spell(mtmp, dmg, spellnum) struct monst *mtmp; @@ -660,6 +1780,115 @@ if (dmg) mdamageu(mtmp, dmg); } +STATIC_OVL +void +m_cast_cleric_spell(mtmp, mdef, dmg, spellnum) +struct monst *mtmp, *mdef; +int dmg; +int spellnum; +{ + int sees_def = canseemon(mdef); + + if (dmg == 0 && !is_undirected_spell(AD_CLRC, spellnum)) { + impossible("cast directed cleric spell (%d) with dmg=0?", spellnum); + return; + } + + switch (spellnum) { + case CLC_GEYSER: + /* this is physical damage, not magical damage */ + if (sees_def) + pline("A sudden geyser slams into %s from nowhere!", + mon_nam(mdef)); + dmg = d(8, 6); + break; + case CLC_FIRE_PILLAR: + if (sees_def) + pline("A pillar of fire strikes all around %s!", mon_nam(mdef)); + if (resists_fire(mdef)) { + shieldeff(mdef->mx, mdef->my); + dmg = 0; + } else + dmg = d(8, 6); + (void) burnarmor(mdef); + destroy_mitem(mdef, SCROLL_CLASS, AD_FIRE); + destroy_mitem(mdef, POTION_CLASS, AD_FIRE); + destroy_mitem(mdef, SPBOOK_CLASS, AD_FIRE); + (void) burn_floor_paper(mdef->mx, mdef->my, TRUE, FALSE); + break; + case CLC_LIGHTNING: + { + boolean reflects; + + if (sees_def) + pline("A bolt of lightning strikes down at %s from above!", + mon_nam(mdef)); + reflects = mon_reflects(mdef, "It bounces off %s %s."); + if (reflects || resists_elec(mdef)) { + shieldeff(mdef->mx, mdef->my); + dmg = 0; + if (reflects) + break; + } else + dmg = d(8, 6); + destroy_mitem(mdef, WAND_CLASS, AD_ELEC); + destroy_mitem(mdef, RING_CLASS, AD_ELEC); + break; + } + case CLC_BLIND_YOU: + /* note: resists_blnd() doesn't apply here */ + if (mdef->mcansee && haseyes(mdef->data)) { + register unsigned rnd_tmp = rnd(50) + 5; + int num_eyes = eyecount(mdef->data); + if (sees_def) + pline("Scales cover %s %s!", + s_suffix(mon_nam(mdef)), + (num_eyes == 1) ? + mbodypart(mdef, EYE) : makeplural(mbodypart(mdef, EYE))); + mdef->mcansee = 0; + if((mdef->mblinded + rnd_tmp) > 127) + mdef->mblinded = 127; + else mdef->mblinded += rnd_tmp; + } + dmg = 0; + break; + case CLC_PARALYZE: + if (!resists_magm(mdef)) { + if (sees_def && mdef->mcanmove) + pline("%s is frozen in place!", Monnam(mdef)); + + mdef->mcanmove = 0; + if (mdef->mfrozen < 20) + mdef->mfrozen += dmg; + } + dmg = 0; + break; + case CLC_CONFUSE_YOU: + if (!resists_magm(mdef) && !mdef->mconf) { + if (sees_def) + pline("%s looks confused.", Monnam(mdef)); + dmg = (int)mtmp->m_lev; + mdef->mconf = 1; + } + dmg = 0; + break; + case CLC_OPEN_WOUNDS: + if (resists_magm(mdef)) + dmg = (dmg + 1) / 2; + + if (unsolid(mdef->data) || is_whirly(mdef->data)) + dmg = 0; + if (dmg > 0 && sees_def) + pline("%s is %s!", (is_golem(mdef->data)? "damaged" : "wounded")); + break; + default: + dmg = 0; + break; + } + + if (dmg) damagem(mtmp, mdef, dmg); +} + STATIC_DCL boolean is_undirected_spell(adtyp, spellnum) @@ -712,7 +1941,7 @@ spellnum == MGC_SUMMON_MONS || spellnum == MGC_CLONE_WIZ)) return TRUE; /* haste self when already fast */ - if (mtmp->permspeed == MFAST && spellnum == MGC_HASTE_SELF) + if (mtmp->mspeed == MFAST && spellnum == MGC_HASTE_SELF) return TRUE; /* invisibility when already invisible */ if ((mtmp->minvis || mtmp->invis_blkd) && spellnum == MGC_DISAPPEAR) @@ -750,6 +1979,116 @@ return FALSE; } + +/* return values: + * 1: successful spell + * 0: unsuccessful spell + */ +int +castmm(mtmp, mattk, mdef) + register struct monst *mtmp, *mdef; + register struct attack *mattk; +{ + int dmg, ml = mtmp->m_lev; + int ret; + int spellnum = 0; + int sees_def = canseemon(mdef), sees_agr = canseemon(mtmp), + dist = dist2(mdef->mx, mdef->my, mtmp->mx, mtmp->my); + + if (ret = gcastm(mtmp, mattk, mdef)) return ret; + + /* Too far away for a conventional spell? */ + if (dist >= 4) return 0; + + /* The chief difference between this and castmu() is that we expect + * to cast only close-range spells and that we _always_ know our + * target exactly. This allows us to simplify the function + * considerably. Since monster healing spells are handled by castmu(), + * we can concentrate solely on attack spells here + */ + if (mattk->adtyp == AD_SPEL || mattk->adtyp == AD_CLRC) { + spellnum = rn2(ml); + if (mattk->adtyp == AD_SPEL) + spellnum = m_choose_magic_spell(spellnum); + else + spellnum = m_choose_clerical_spell(spellnum); + } + + /* monster unable to cast spells? */ + if(mtmp->mcan || mtmp->mspec_used || !ml || spellnum == -1) { + return(0); + } + + if (mattk->adtyp == AD_SPEL || mattk->adtyp == AD_CLRC) { + mtmp->mspec_used = 10 - mtmp->m_lev; + if (mtmp->mspec_used < 2) mtmp->mspec_used = 2; + } + + nomul(0); + if(rn2(ml*10) < (mtmp->mconf ? 100 : 20)) { /* fumbled attack */ + if (canseemon(mtmp) && flags.soundok) + pline_The("air crackles around %s.", mon_nam(mtmp)); + return(0); + } + +/* + * As these are spells, the damage is related to the level + * of the monster casting the spell. + */ + if (mattk->damd) + dmg = d((int)((ml/2) + mattk->damn), (int)mattk->damd); + else dmg = d((int)((ml/2) + 1), 6); + + ret = 1; + + switch (mattk->adtyp) { + + case AD_FIRE: + if (sees_def) + pline("%s is enveloped in flames.", Monnam(mdef)); + if(resists_fire(mdef)) { + shieldeff(mdef->mx, mdef->my); + if (sees_def) + pline("But %s resists the effects.", + mhe(mdef)); + dmg = 0; + } + break; + case AD_COLD: + if (sees_def) + pline("%s is covered in frost.", Monnam(mdef)); + if(resists_cold(mdef)) { + shieldeff(mdef->mx, mdef->my); + pline("But %s resists the effects.", + mhe(mdef)); + dmg = 0; + } + break; + case AD_MAGM: + if (sees_def) + pline("%s is hit by a shower of missiles!", Monnam(mdef)); + if(resists_magm(mdef)) { + shieldeff(mdef->mx, mdef->my); + pline_The("missiles bounce off!"); + dmg = 0; + } else dmg = d((int)mtmp->m_lev/2 + 1,6); + break; + case AD_SPEL: /* wizard spell */ + case AD_CLRC: /* clerical spell */ + { + if (mattk->adtyp == AD_SPEL) + m_cast_wizard_spell(mtmp, mdef, dmg, spellnum); + else + m_cast_cleric_spell(mtmp, mdef, dmg, spellnum); + dmg = 0; /* done by the spell casting functions */ + break; + } + } + if(dmg) damagem(mtmp, mdef, dmg); + + return(ret); +} + #endif /* OVLB */ #ifdef OVL0 @@ -781,6 +2120,36 @@ } else impossible("Monster spell %d cast", mattk->adtyp-1); } return(1); +} + +int +buzzmm(mtmp, mattk, mtarg) /* monster zaps spell at another monster */ +register struct monst *mtmp, *mtarg; +register struct attack *mattk; +{ + if (mtarg == &youmonst) + return buzzmu(mtmp, mattk); + + if(mtmp->mcan || mattk->adtyp > AD_SPC2) { + return(0); + } + if(m_lined_up(mtarg, mtmp) && rn2(5)) { + /* Before zapping fireballs, verify that our friends aren't + * adjacent */ + if (mattk->adtyp == AD_FIRE && mtmp->mtame) { + if (has_adjacent_enemy(mtmp, mtarg->mx, mtarg->my, FALSE)) + return (0); + } + if(mattk->adtyp && (mattk->adtyp < 11)) { /* no cf unsigned >0 */ + if(canseemon(mtmp)) { + pline("%s zaps a %s!", Monnam(mtmp), + flash_types[ad_to_typ(mattk->adtyp)]); + } + dobuzz(-ad_to_typ(mattk->adtyp), (int)mattk->damn, + mtmp->mx, mtmp->my, sgn(tbx), sgn(tby), FALSE); + } else impossible("Monster spell %d cast", mattk->adtyp-1); + } + return(1); } #endif /* OVL0 */ diff -u -r1.1.1.1 mhitm.c --- src/mhitm.c 24 Feb 2003 16:06:16 -0000 1.1.1.1 +++ src/mhitm.c 24 Feb 2003 16:36:58 -0000 @@ -238,6 +238,17 @@ attk = 1; switch (mattk->aatyp) { case AT_WEAP: /* "hand to hand" attacks */ + if (distmin(magr->mx,magr->my,mdef->mx,mdef->my) > 1) { + /* D: Do a ranged attack here! */ + strike = thrwmm(magr, mdef); + if (DEADMONSTER(mdef)) + res[i] = MM_DEF_DIED; + + if (DEADMONSTER(magr)) + res[i] |= MM_AGR_DIED; + + break; + } if (magr->weapon_check == NEED_WEAPON || !MON_WEP(magr)) { magr->weapon_check = NEED_HTH_WEAPON; if (mon_wield_item(magr) != 0) return 0; @@ -259,7 +270,10 @@ case AT_TENT: /* Nymph that teleported away on first attack? */ if (distmin(magr->mx,magr->my,mdef->mx,mdef->my) > 1) - return MM_MISS; + /*return MM_MISS;*/ + /* Continue because the monster may have a ranged + * attack */ + continue; /* Monsters won't attack cockatrices physically if they * have a weapon instead. This instinct doesn't work for * players, or under conflict or confusion. @@ -306,6 +320,10 @@ break; case AT_EXPL: + /* D: Prevent explosions from a distance */ + if (distmin(magr->mx,magr->my,mdef->mx,mdef->my) > 1) + continue; + res[i] = explmm(magr, mdef, mattk); if (res[i] == MM_MISS) { /* cancelled--no attack */ strike = 0; @@ -321,6 +339,11 @@ break; } #endif + + /* D: Prevent engulf from a distance */ + if (distmin(magr->mx,magr->my,mdef->mx,mdef->my) > 1) + continue; + /* Engulfing attacks are directed at the hero if * possible. -dlc */ @@ -334,13 +357,48 @@ } break; + case AT_BREA: + if (!monnear(magr, mdef->mx, mdef->my)) { + strike = breamm(magr, mattk, mdef); + + /* We don't really know if we hit or not, but pretend + * we did */ + if (strike) res[i] |= MM_HIT; + if (DEADMONSTER(mdef)) res[i] = MM_DEF_DIED; + if (DEADMONSTER(magr)) res[i] |= MM_AGR_DIED; + } + else + strike = 0; + break; + + case AT_SPIT: + if (!monnear(magr, mdef->mx, mdef->my)) { + strike = spitmm(magr, mattk, mdef); + + /* We don't really know if we hit or not, but pretend + * we did */ + if (strike) res[i] |= MM_HIT; + if (DEADMONSTER(mdef)) res[i] = MM_DEF_DIED; + if (DEADMONSTER(magr)) res[i] |= MM_AGR_DIED; + } + break; + + case AT_MAGC: + /* Tame spellcasters get their day in the sun! */ + strike = castmm(magr, mattk, mdef); + if (strike) res[i] |= MM_HIT; + if (DEADMONSTER(mdef)) res[i] = MM_DEF_DIED; + if (DEADMONSTER(magr)) res[i] |= MM_AGR_DIED; + break; + default: /* no attack */ strike = 0; attk = 0; break; } - if (attk && !(res[i] & MM_AGR_DIED)) + if (attk && !(res[i] & MM_AGR_DIED) && + distmin(magr->mx,magr->my,mdef->mx,mdef->my) <= 1) res[i] = passivemm(magr, mdef, strike, res[i] & MM_DEF_DIED); if (res[i] & MM_DEF_DIED) return res[i]; diff -u -r1.1.1.1 mon.c --- src/mon.c 24 Feb 2003 16:06:16 -0000 1.1.1.1 +++ src/mon.c 8 Apr 2003 15:05:20 -0000 @@ -336,9 +336,9 @@ boolean inpool, inlava, infountain; inpool = is_pool(mtmp->mx,mtmp->my) && - !is_flyer(mtmp->data) && !is_floater(mtmp->data); + !is_flyer(mtmp->data) && !is_levitating(mtmp); inlava = is_lava(mtmp->mx,mtmp->my) && - !is_flyer(mtmp->data) && !is_floater(mtmp->data); + !is_flyer(mtmp->data) && !is_levitating(mtmp); infountain = IS_FOUNTAIN(levl[mtmp->mx][mtmp->my].typ); #ifdef STEED @@ -816,15 +816,20 @@ #ifdef OVL2 boolean -mpickstuff(mtmp, str) +mpickstuff(mtmp, str, moreleft) register struct monst *mtmp; register const char *str; + boolean *moreleft; { register struct obj *otmp, *otmp2; + boolean pickedup = FALSE; /* prevent shopkeepers from leaving the door of their shop */ if(mtmp->isshk && inhishop(mtmp)) return FALSE; + /* D: If we did something special this turn, we can't pick up stuff */ + if (mtmp->mfrozen) return FALSE; + for(otmp = level.objects[mtmp->mx][mtmp->my]; otmp; otmp = otmp2) { otmp2 = otmp->nexthere; /* Nymphs take everything. Most monsters don't pick up corpses. */ @@ -837,10 +842,23 @@ !acidic(&mons[otmp->corpsenm])) continue; if (!touch_artifact(otmp,mtmp)) continue; if (!can_carry(mtmp,otmp)) continue; - if (is_pool(mtmp->mx,mtmp->my)) continue; + /* D: Any point in staying in the loop over a pool? */ + if (is_pool(mtmp->mx,mtmp->my)) return FALSE; #ifdef INVISIBLE_OBJECTS if (otmp->oinvis && !perceives(mtmp->data)) continue; #endif + + /* If we already picked up some stuff and we're here, we'll + * want to grab some stuff next turn as well, so return here + * to prevent a monster with enchanted levitation boots putting + * it on right now */ + if (pickedup) return (*moreleft = TRUE); + + /* D: Check if we're levitating and need to stop */ + if (is_levitating(mtmp) && !is_floater(mtmp->data) + && m_stop_levitating(mtmp)) + return FALSE; + if (cansee(mtmp->mx,mtmp->my) && flags.verbose) pline("%s picks up %s.", Monnam(mtmp), (distu(mtmp->my, mtmp->my) <= 5) ? @@ -850,12 +868,13 @@ if (otmp->otyp == BOULDER) unblock_point(otmp->ox,otmp->oy); /* vision */ (void) mpickobj(mtmp, otmp); /* may merge and free otmp */ - m_dowear(mtmp, FALSE); newsym(mtmp->mx, mtmp->my); - return TRUE; /* pick only one object */ + + pickedup = TRUE; /* pick only one object */ } } - return FALSE; + + return pickedup; } #endif /* OVL2 */ @@ -949,11 +968,12 @@ /* return number of acceptable neighbour positions */ int -mfndpos(mon, poss, info, flag) +mfndpos(mon, poss, info, flag, lev) register struct monst *mon; coord *poss; /* coord poss[9] */ long *info; /* long info[9] */ long flag; + int *lev; /* If levitation would be useful, set this to true */ { struct permonst *mdat = mon->data; register xchar x,y,nx,ny; @@ -970,9 +990,12 @@ nodiag = (mdat == &mons[PM_GRID_BUG]); wantpool = mdat->mlet == S_EEL; - poolok = is_flyer(mdat) || is_clinger(mdat) || + poolok = is_flyer(mdat) || is_levitating(mon) || is_clinger(mdat) || (is_swimmer(mdat) && !wantpool); - lavaok = is_flyer(mdat) || is_clinger(mdat) || likes_lava(mdat); + lavaok = is_flyer(mdat) || is_clinger(mdat) || likes_lava(mdat) || + is_levitating(mon); + if (lev) *lev = 0; + thrudoor = ((flag & (ALLOW_WALL|BUSTDOOR)) != 0L); if (flag & ALLOW_DIG) { struct obj *mw_tmp; @@ -1123,6 +1146,7 @@ && ttmp->ttyp != HOLE) || (!is_flyer(mdat) && !is_floater(mdat) + && !is_levitating(mon) && !is_clinger(mdat)) || In_sokoban(&u.uz)) && (ttmp->ttyp != SLP_GAS_TRAP || @@ -1144,10 +1168,17 @@ } } } + +#ifdef SINKS + /* D: Levitating monsters will avoid sinks if possible */ + if ((mon->mintrinsics & MR2_LEVITATE) && + IS_SINK(levl[nx][ny].typ) && + rn2(mon->mhp > 25? 6 : 15)) continue; +#endif poss[cnt].x = nx; poss[cnt].y = ny; cnt++; - } + } else if (!wantpool && lev) ++*lev; } if(!cnt && wantpool && !is_pool(x,y)) { wantpool = FALSE; @@ -1382,6 +1413,9 @@ dismount_steed(DISMOUNT_GENERIC); #endif + /* D: Kill monster timers, if any */ + mon_stop_timers(mtmp); + mptr = mtmp->data; /* save this for m_detach() */ /* restore chameleon, lycanthropes to true form at death */ if (mtmp->cham) @@ -2478,7 +2512,7 @@ if (!(mtmp->misc_worn_check & W_ARMG)) mselftouch(mtmp, "No longer petrify-resistant, ", !flags.mon_moving); - m_dowear(mtmp, FALSE); + m_dowear(mtmp, FALSE, FALSE); /* This ought to re-test can_carry() on each item in the inventory * rather than just checking ex-giants & boulders, but that'd be diff -u -r1.1.1.1 mondata.c --- src/mondata.c 24 Feb 2003 16:06:16 -0000 1.1.1.1 +++ src/mondata.c 24 Feb 2003 16:29:32 -0000 @@ -29,6 +29,15 @@ #endif /* OVLB */ #ifdef OVL0 +boolean +is_levitating(mon) +struct monst *mon; +{ + if (!mon) + return FALSE; + return is_floater(mon->data) || (mon->mintrinsics & MR2_LEVITATE); +} + struct attack * attacktype_fordmg(ptr, atyp, dtyp) struct permonst *ptr; diff -u -r1.1.1.1 monmove.c --- src/monmove.c 24 Feb 2003 16:06:16 -0000 1.1.1.1 +++ src/monmove.c 24 Feb 2003 16:29:32 -0000 @@ -369,6 +369,17 @@ /* check distance and scariness of attacks */ distfleeck(mtmp,&inrange,&nearby,&scared); + /* D: Ideally, this should be someplace else, such as find_defensive + * itself */ + if (mtmp->mblinded || mtmp->mconf || mtmp->mstun) { + if (cancast(mtmp, SPE_RESTORE_ABILITY) + && mcast_escape_spell(mtmp, SPE_RESTORE_ABILITY)) + return 1; + if (cancast(mtmp, SPE_CURE_BLINDNESS) + && mcast_escape_spell(mtmp, SPE_CURE_BLINDNESS)) + return 1; + } + if(find_defensive(mtmp)) { if (use_defensive(mtmp) != 0) return 1; @@ -497,6 +508,9 @@ for (a = &mdat->mattk[0]; a < &mdat->mattk[NATTK]; a++) { if (a->aatyp == AT_MAGC && (a->adtyp == AD_SPEL || a->adtyp == AD_CLRC)) { if (castmu(mtmp, a, FALSE, FALSE)) { + /* Monster managed to kill itself, tsk tsk */ + if (DEADMONSTER(mtmp)) + return (1); tmp = 3; break; } @@ -595,7 +609,7 @@ 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 can_open=0, can_unlock=0, doorbuster=0, spell_unlock = FALSE; boolean uses_items=0, setlikes=0; boolean avoid=FALSE; struct permonst *ptr; @@ -631,7 +645,8 @@ can_tunnel = tunnels(ptr); can_open = !(nohands(ptr) || verysmall(ptr)); can_unlock = ((can_open && m_carrying(mtmp, SKELETON_KEY)) || - mtmp->iswiz || is_rider(ptr)); + mtmp->iswiz || is_rider(ptr) || + (spell_unlock = cancast(mtmp, SPE_KNOCK))); doorbuster = is_giant(ptr); if(mtmp->wormno) goto not_special; /* my dog gets special treatment */ @@ -709,6 +724,13 @@ gx = mtmp->mux; gy = mtmp->muy; appr = mtmp->mflee ? -1 : 1; + + if (appr == -1 && cancast(mtmp, SPE_JUMPING)) { + int ret = mcast_escape_spell(mtmp, SPE_JUMPING); + /* Check whether the monster found a good place to jump to */ + if (ret) return 3; + } + if (mtmp->mconf || (u.uswallow && mtmp == u.ustuck)) appr = 0; else { @@ -903,8 +925,20 @@ int ndist, nidist; register coord *mtrk; coord poss[9]; + int wantslev = 0; + + cnt = mfndpos(mtmp, poss, info, flag, &wantslev); + + if (wantslev > 1) { + /* mtmp would like to levitate. Can we arrange for it? */ + if (cancast(mtmp, SPE_LEVITATION)) { + /* Yes! */ + mcast_escape_spell(mtmp, SPE_LEVITATION); + /* This turn spent getting airborne */ + return 3; + } + } - cnt = mfndpos(mtmp, poss, info, flag); chcnt = 0; jcnt = min(MTSZ, cnt-1); chi = -1; @@ -1062,7 +1096,11 @@ ptr == &mons[PM_YELLOW_LIGHT]) ? "flows" : "oozes"); } else if(here->doormask & D_LOCKED && can_unlock) { - if(btrapped) { + if (spell_unlock) + mcast_escape_spell(mtmp, SPE_KNOCK); + + /* Using knock automatically untraps door */ + if(btrapped && !spell_unlock) { here->doormask = D_NODOOR; newsym(mtmp->mx, mtmp->my); unblock_point(mtmp->mx,mtmp->my); /* vision */ @@ -1144,6 +1182,8 @@ newsym(mtmp->mx,mtmp->my); } if(OBJ_AT(mtmp->mx, mtmp->my) && mtmp->mcanmove) { + boolean picked = FALSE; + /* recompute the likes tests, in case we polymorphed * or if the "likegold" case got taken above */ if (setlikes) { @@ -1167,7 +1207,10 @@ if (meatmetal(mtmp) == 2) return 2; /* it died */ } - if(g_at(mtmp->mx,mtmp->my) && likegold) mpickgold(mtmp); + if(g_at(mtmp->mx,mtmp->my) && likegold) { + mpickgold(mtmp); + picked = TRUE; + } /* Maybe a cube ate just about anything */ if (ptr == &mons[PM_GELATINOUS_CUBE]) { @@ -1175,14 +1218,17 @@ } if(!*in_rooms(mtmp->mx, mtmp->my, SHOPBASE) || !rn2(25)) { - boolean picked = FALSE; + boolean more = 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(likeobjs) picked |= mpickstuff(mtmp, practical, &more); + if(likemagic) picked |= mpickstuff(mtmp, magical, &more); + if(likerock) picked |= mpickstuff(mtmp, boulder_class, + &more); + if(likegems) picked |= mpickstuff(mtmp, gem_class, &more); + if(uses_items) picked |= mpickstuff(mtmp, (char *)0, + &more); + if(picked || mtmp->mfrozen) mmoved = 3; + if (picked && !more) m_dowear(mtmp, FALSE, TRUE); } if(mtmp->minvis) { diff -u -r1.1.1.1 monst.c --- src/monst.c 24 Feb 2003 16:06:16 -0000 1.1.1.1 +++ src/monst.c 24 Feb 2003 16:29:32 -0000 @@ -589,7 +589,7 @@ M2_HOSTILE|M2_LORD|M2_MALE|M2_COLLECT, M3_INFRAVISIBLE|M3_INFRAVISION, HI_LORD), MON("kobold shaman", S_KOBOLD, - LVL(2, 6, 6, 10, -4), (G_GENO|1), + LVL(4, 6, 6, 10, -4), (G_GENO|1), A(ATTK(AT_MAGC, AD_SPEL, 0, 0), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK), SIZ(450, 150, 0, MS_ORC, MZ_SMALL), MR_POISON, 0, @@ -703,7 +703,7 @@ M2_ORC|M2_STRONG|M2_GREEDY|M2_JEWELS|M2_COLLECT, M3_INFRAVISIBLE|M3_INFRAVISION, CLR_BLACK), MON("orc shaman", S_ORC, - LVL(3, 9, 5, 10, -5), (G_GENO|1), + LVL(5, 9, 5, 10, -5), (G_GENO|1), A(ATTK(AT_MAGC, AD_SPEL, 0, 0), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK), SIZ(1000, 300, 0, MS_ORC, MZ_HUMAN), 0, 0, M1_HUMANOID|M1_OMNIVORE, @@ -1462,7 +1462,7 @@ M1_HUMANOID|M1_OMNIVORE, M2_GNOME|M2_LORD|M2_MALE|M2_COLLECT, M3_INFRAVISIBLE|M3_INFRAVISION, CLR_BLUE), MON("gnomish wizard", S_GNOME, - LVL(3, 10, 4, 10, 0), (G_GENO|1), + LVL(5, 10, 4, 10, 0), (G_GENO|1), A(ATTK(AT_MAGC, AD_SPEL, 0, 0), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK), SIZ(700, 120, 0, MS_ORC, MZ_SMALL), 0, 0, diff -u -r1.1.1.1 mplayer.c --- src/mplayer.c 24 Feb 2003 16:06:16 -0000 1.1.1.1 +++ src/mplayer.c 24 Feb 2003 16:29:32 -0000 @@ -249,7 +249,7 @@ GAUNTLETS_OF_DEXTERITY)); if (rn2(8)) mk_mplayer_armor(mtmp, rnd_class(LOW_BOOTS, LEVITATION_BOOTS)); - m_dowear(mtmp, TRUE); + m_dowear(mtmp, TRUE, FALSE); quan = rn2(3) ? rn2(3) : rn2(16); while(quan--) diff -u -r1.1.1.1 mthrowu.c --- src/mthrowu.c 24 Feb 2003 16:06:16 -0000 1.1.1.1 +++ src/mthrowu.c 24 Feb 2003 18:09:14 -0000 @@ -3,6 +3,7 @@ /* NetHack may be freely redistributed. See license for details. */ #include "hack.h" +#include "edog.h" STATIC_DCL int FDECL(drop_throw,(struct obj *,BOOLEAN_P,int,int)); @@ -10,6 +11,8 @@ #define POLE_LIM 5 /* How far monsters can use pole-weapons */ +#define PET_MISSILE_RANGE2 36 /* Square of distance within which pets shoot */ + #ifndef OVLB STATIC_DCL const char *breathwep[]; @@ -32,6 +35,13 @@ "strange breath #9" }; +/* The monster that's being shot at when one monster shoots at another */ +STATIC_OVL struct monst *target = 0, +/* The monster that's doing the shooting/throwing */ + *archer = 0; + +boolean FDECL(m_lined_up, (struct monst *, struct monst *)); + /* hero is hit by something other than a monster */ int thitu(tlev, dam, obj, name) @@ -150,6 +160,7 @@ int ohitmon(mtmp, otmp, range, verbose) struct monst *mtmp; /* accidental target */ + /* D: May be deliberate target, so added a check */ struct obj *otmp; /* missile; might be destroyed by drop_throw */ int range; /* how much farther will object travel if it misses */ /* Use -1 to signify to keep going even after hit, */ @@ -159,15 +170,27 @@ int damage, tmp; boolean vis, ismimic; int objgone = 1; + struct obj *mon_launcher = archer? MON_WEP(archer) : NULL; ismimic = mtmp->m_ap_type && mtmp->m_ap_type != M_AP_MONSTER; vis = cansee(bhitpos.x, bhitpos.y); tmp = 5 + find_mac(mtmp) + omon_adj(mtmp, otmp, FALSE); + + /* D: High level monsters will be more likely to hit */ + /* This check applies only if this monster is the target + * the archer was aiming at. */ + if (archer && target == mtmp) { + if (archer->m_lev > 5) + tmp += archer->m_lev - 5; + if (mon_launcher && mon_launcher->oartifact) + tmp += spec_abon(mon_launcher, mtmp); + } + if (tmp < rnd(20)) { if (!ismimic) { if (vis) miss(distant_name(otmp, mshot_xname), mtmp); - else if (verbose) pline("It is missed."); + else if (verbose && !target) pline("It is missed."); } if (!range) { /* Last position; object drops */ (void) drop_throw(otmp, 0, mtmp->mx, mtmp->my); @@ -186,7 +209,8 @@ if (ismimic) seemimic(mtmp); mtmp->msleeping = 0; if (vis) hit(distant_name(otmp,mshot_xname), mtmp, exclam(damage)); - else if (verbose) pline("%s is hit%s", Monnam(mtmp), exclam(damage)); + else if (verbose && !target) pline("%s is hit%s", Monnam(mtmp), + exclam(damage)); if (otmp->opoisoned && is_poisonable(otmp)) { if (resists_poison(mtmp)) { @@ -205,21 +229,21 @@ hates_silver(mtmp->data)) { if (vis) pline_The("silver sears %s flesh!", s_suffix(mon_nam(mtmp))); - else if (verbose) pline("Its flesh is seared!"); + else if (verbose && !target) pline("Its flesh is seared!"); } if (otmp->otyp == ACID_VENOM && cansee(mtmp->mx,mtmp->my)) { if (resists_acid(mtmp)) { - if (vis || verbose) + if (vis || (verbose && !target)) pline("%s is unaffected.", Monnam(mtmp)); damage = 0; } else { if (vis) pline_The("acid burns %s!", mon_nam(mtmp)); - else if (verbose) pline("It is burned!"); + else if (verbose && !target) pline("It is burned!"); } } mtmp->mhp -= damage; if (mtmp->mhp < 1) { - if (vis || verbose) + if (vis || (verbose && !target)) pline("%s is %s!", Monnam(mtmp), (nonliving(mtmp->data) || !canspotmon(mtmp)) ? "destroyed" : "killed"); @@ -475,6 +499,130 @@ } } +int +thrwmm(mtmp, mtarg) /* Monster throws item at monster */ +struct monst *mtmp, *mtarg; +{ + struct obj *otmp, *mwep; + register xchar x, y; + boolean ispole; + schar skill; + int multishot = 1; + + /* D: Polearms won't be applied by monsters against other monsters */ + /* Rearranged beginning so monsters can use polearms not in a line */ + if (mtmp->weapon_check == NEED_WEAPON || !MON_WEP(mtmp)) { + mtmp->weapon_check = NEED_RANGED_WEAPON; + /* mon_wield_item resets weapon_check as appropriate */ + if(mon_wield_item(mtmp) != 0) return 0; + } + + /* Pick a weapon */ + otmp = select_rwep(mtmp); + if (!otmp) return 0; + ispole = is_pole(otmp); + skill = objects[otmp->otyp].oc_skill; + + x = mtmp->mx; + y = mtmp->my; + + mwep = MON_WEP(mtmp); /* wielded weapon */ + + if(!ispole && m_lined_up(mtarg, mtmp)) { + /* WAC Catch this since rn2(0) is illegal */ + int chance = (BOLT_LIM-distmin(x,y,mtarg->mx,mtarg->my) > 0) ? + BOLT_LIM-distmin(x,y,mtarg->mx,mtarg->my) : 1; + + if(!mtarg->mflee || !rn2(chance)) { + const char *verb = "throws"; + + if (otmp->otyp == ARROW + || otmp->otyp == ELVEN_ARROW + || otmp->otyp == ORCISH_ARROW + || otmp->otyp == YA + || otmp->otyp == CROSSBOW_BOLT) verb = "shoots"; + + if(ammo_and_launcher(otmp, mwep) && + is_launcher(mwep)) { + if (dist2(mtmp->mx, mtmp->my, mtarg->mx, mtarg->my) > + PET_MISSILE_RANGE2) + return 0; /* Out of range */ + } + + if (canseemon(mtmp)) { + pline("%s %s %s!", Monnam(mtmp), verb, + obj_is_pname(otmp) ? + the(singular(otmp, xname)) : + an(singular(otmp, xname))); + } + + /* Multishot calculations */ + if ((ammo_and_launcher(otmp, mwep) || skill == P_DAGGER || + skill == -P_DART || skill == -P_SHURIKEN) && + !mtmp->mconf) { + /* Assumes lords are skilled, princes are expert */ + if (is_lord(mtmp->data)) multishot++; + if (is_prince(mtmp->data)) multishot += 2; + + /* Elven Craftsmanship makes for light, quick bows */ + if (otmp->otyp == ELVEN_ARROW && !otmp->cursed) + multishot++; + if (mwep && mwep->otyp == ELVEN_BOW && + !mwep->cursed) multishot++; + /* 1/3 of object enchantment */ + if (mwep && mwep->spe > 1) + multishot += (long) rounddiv(mwep->spe,3); + /* Some randomness */ + if (multishot > 1L) + multishot = (long) rnd((int) multishot); + + switch (monsndx(mtmp->data)) { + case PM_RANGER: + multishot++; + break; + case PM_ROGUE: + if (skill == P_DAGGER) multishot++; + break; + case PM_SAMURAI: + if (otmp->otyp == YA && mwep && + mwep->otyp == YUMI) multishot++; + break; + default: + break; + } + { /* racial bonus */ + if (is_elf(mtmp->data) && + otmp->otyp == ELVEN_ARROW && + mwep && mwep->otyp == ELVEN_BOW) + multishot++; + else if (is_orc(mtmp->data) && + otmp->otyp == ORCISH_ARROW && + mwep && mwep->otyp == ORCISH_BOW) + multishot++; + } + + } + if (otmp->quan < multishot) multishot = (int)otmp->quan; + if (multishot < 1) multishot = 1; + + /* Set target monster */ + target = mtarg; + archer = mtmp; + while (multishot-- > 0) + m_throw(mtmp, mtmp->mx, mtmp->my, + sgn(tbx), sgn(tby), + distmin(mtmp->mx, mtmp->my, + mtarg->mx, mtarg->my), + otmp); + archer = (struct monst *)0; + target = (struct monst *)0; + nomul(0); + return 1; + } + } + return 0; +} + #endif /* OVL1 */ #ifdef OVLB @@ -674,6 +822,58 @@ return 0; } +int +spitmm(mtmp, mattk, mtarg) /* monster spits substance at monster */ +register struct monst *mtmp, *mtarg; +register struct attack *mattk; +{ + register struct obj *otmp; + + if(mtmp->mcan) { + + if(flags.soundok) + pline("A dry rattle comes from %s throat.", + s_suffix(mon_nam(mtmp))); + return 0; + } + if(m_lined_up(mtarg, mtmp)) { + switch (mattk->adtyp) { + case AD_BLND: + case AD_DRST: + otmp = mksobj(BLINDING_VENOM, TRUE, FALSE); + break; + default: + impossible("bad attack type in spitmu"); + /* fall through */ + case AD_ACID: + otmp = mksobj(ACID_VENOM, TRUE, FALSE); + break; + } + if(!rn2(BOLT_LIM-distmin(mtmp->mx,mtmp->my,mtarg->mx,mtarg->my))) { + if (canseemon(mtmp)) + pline("%s spits venom!", Monnam(mtmp)); + target = mtarg; + m_throw(mtmp, mtmp->mx, mtmp->my, sgn(tbx), sgn(tby), + distmin(mtmp->mx,mtmp->my,mtarg->mx,mtarg->my), otmp); + target = (struct monst *)0; + nomul(0); + + /* If this is a pet, it'll get hungry. Minions and + * spell beings won't hunger */ + if (mtmp->mtame && !mtmp->isminion) { + struct edog *dog = EDOG(mtmp); + + /* Hunger effects will catch up next move */ + if (dog->hungrytime > 1) + dog->hungrytime -= 5; + } + + return 1; + } + } + return 0; +} + #endif /* OVLB */ #ifdef OVL1 @@ -719,6 +919,56 @@ return(1); } +int +breamm(mtmp, mattk, mtarg) /* monster breathes at monster (ranged) */ +register struct monst *mtmp, *mtarg; +register struct attack *mattk; +{ + /* if new breath types are added, change AD_ACID to max type */ + int typ = (mattk->adtyp == AD_RBRE) ? rnd(AD_ACID) : mattk->adtyp ; + + if(m_lined_up(mtarg, mtmp)) { + + if(mtmp->mcan) { + if(flags.soundok) { + if(canseemon(mtmp)) + pline("%s coughs.", Monnam(mtmp)); + else + You_hear("a cough."); + } + return(0); + } + if(!mtmp->mspec_used && rn2(3)) { + + if((typ >= AD_MAGM) && (typ <= AD_ACID)) { + + if(canseemon(mtmp)) + pline("%s breathes %s!", Monnam(mtmp), + breathwep[typ-1]); + dobuzz((int) (-20 - (typ-1)), (int)mattk->damn, + mtmp->mx, mtmp->my, sgn(tbx), sgn(tby), FALSE); + nomul(0); + /* breath runs out sometimes. Also, give monster some + * cunning; don't breath if the target fell asleep. + */ + mtmp->mspec_used = 6+rn2(18); + + /* If this is a pet, it'll get hungry. Minions and + * spell beings won't hunger */ + if (mtmp->mtame && !mtmp->isminion) { + struct edog *dog = EDOG(mtmp); + + /* Hunger effects will catch up next move */ + if (dog->hungrytime >= 10) + dog->hungrytime -= 10; + } + } else impossible("Breath weapon %d used", typ-1); + } else + return (0); + } + return(1); +} + boolean linedup(ax, ay, bx, by) register xchar ax, ay, bx, by; @@ -736,6 +986,13 @@ else if(clear_path(ax,ay,bx,by)) return TRUE; } return FALSE; +} + +boolean +m_lined_up(mtarg, mtmp) +register struct monst *mtarg, *mtmp; +{ + return (linedup(mtarg->mx, mtarg->my, mtmp->mx, mtmp->my)); } boolean diff -u -r1.1.1.1 muse.c --- src/muse.c 24 Feb 2003 16:06:16 -0000 1.1.1.1 +++ src/muse.c 24 Feb 2003 16:29:33 -0000 @@ -24,7 +24,6 @@ STATIC_DCL void FDECL(mzapmsg, (struct monst *,struct obj *,BOOLEAN_P)); STATIC_DCL void FDECL(mreadmsg, (struct monst *,struct obj *)); STATIC_DCL void FDECL(mquaffmsg, (struct monst *,struct obj *)); -STATIC_PTR int FDECL(mbhitm, (struct monst *,struct obj *)); STATIC_DCL void FDECL(mbhit, (struct monst *,int,int FDECL((*),(MONST_P,OBJ_P)), int FDECL((*),(OBJ_P,OBJ_P)),struct obj *)); @@ -238,6 +237,9 @@ #define MUSE_UNICORN_HORN 17 #define MUSE_POT_FULL_HEALING 18 #define MUSE_LIZARD_CORPSE 19 + +#define MUSE_SPE_TELEPORT_AWAY 20 + /* #define MUSE_INNATE_TPT 9999 * We cannot use this. Since monsters get unlimited teleportation, if they @@ -349,6 +351,11 @@ } if (levl[x][y].typ == STAIRS && !stuck && !immobile) { + /* Use is_floater instead of is_levitating, since a levitating + * monster can be assumed to be in control of its levitation + * FIXME: Handle cases where levitating monster is not in control, + * such as from potion quaffing? + */ if (x == xdnstair && y == ydnstair && !is_floater(mtmp->data)) m.has_defense = MUSE_DOWNSTAIRS; if (x == xupstair && y == yupstair && ledger_no(&u.uz) != 1) @@ -392,6 +399,9 @@ } } + if (cancast(mtmp, SPE_TELEPORT_AWAY)) + m.has_defense = MUSE_SPE_TELEPORT_AWAY; + if (nohands(mtmp->data)) /* can't use objects */ goto botm; @@ -424,6 +434,7 @@ t = 0; /* ok for monster to dig here */ #define nomore(x) if(m.has_defense==x) continue; + for (obj = mtmp->minvent; obj; obj = obj->nobj) { /* don't always use the same selection pattern */ if (m.has_defense && !rn2(3)) break; @@ -444,6 +455,7 @@ } nomore(MUSE_WAN_TELEPORTATION_SELF); nomore(MUSE_WAN_TELEPORTATION); + nomore(MUSE_SPE_TELEPORT_AWAY); if(obj->otyp == WAN_TELEPORTATION && obj->spe > 0) { /* use the TELEP_TRAP bit to determine if they know * about noteleport on this level or not. Avoids @@ -512,6 +524,7 @@ m.has_defense = MUSE_SCR_CREATE_MONSTER; } } + botm: return((boolean)(!!m.has_defense)); #undef nomore } @@ -565,6 +578,9 @@ You_hear("a bugle playing reveille!"); awaken_soldiers(); return 2; + case MUSE_SPE_TELEPORT_AWAY: + mcast_escape_spell(mtmp, SPE_TELEPORT_AWAY); + return 2; case MUSE_WAN_TELEPORTATION_SELF: if ((mtmp->isshk && inhishop(mtmp)) || mtmp->isgd || mtmp->ispriest) return 2; @@ -1091,13 +1107,14 @@ #undef nomore } -STATIC_PTR int mbhitm(mtmp, otmp) register struct monst *mtmp; register struct obj *otmp; { int tmp; + boolean wand = otmp->oclass == WAND_CLASS; + char *effector = wand? "wand" : "spell"; boolean reveal_invis = FALSE; if (mtmp != &youmonst) { @@ -1105,19 +1122,42 @@ if (mtmp->m_ap_type) seemimic(mtmp); } switch(otmp->otyp) { + case SPE_STONE_TO_FLESH: + if (mtmp == &youmonst) + zapyourself(otmp, FALSE); + else + bhitm(mtmp, otmp); + break; + case SPE_DRAIN_LIFE: + if (mtmp == &youmonst) { + /* Unlike AD_DRLI, you don't get the chance to resist */ + losexp("life drainage"); + } else if (!resists_drli(mtmp)) { + tmp = d(2,6); + if (canseemon(mtmp)) + pline("%s suddenly seems weaker!", Monnam(mtmp)); + mtmp->mhpmax -= tmp; + if (mtmp->m_lev < 1) + tmp = mtmp->mhp; + else mtmp->m_lev--; + mtmp->mhp -= tmp; + monkilled(mtmp, "", AD_RBRE); + } + break; case WAN_STRIKING: + case SPE_FORCE_BOLT: reveal_invis = TRUE; if (mtmp == &youmonst) { - if (zap_oseen) makeknown(WAN_STRIKING); + if (zap_oseen && wand) makeknown(WAN_STRIKING); if (Antimagic) { shieldeff(u.ux, u.uy); pline("Boing!"); } else if (rnd(20) < 10 + u.uac) { - pline_The("wand hits you!"); + pline_The("%s hits you!", effector); tmp = d(2,12); if(Half_spell_damage) tmp = (tmp+1) / 2; - losehp(tmp, "wand", KILLED_BY_AN); - } else pline_The("wand misses you."); + losehp(tmp, effector, KILLED_BY_AN); + } else pline_The("%s misses you.", effector); stop_occupation(); nomul(0); } else if (resists_magm(mtmp)) { @@ -1125,19 +1165,20 @@ pline("Boing!"); } else if (rnd(20) < 10+find_mac(mtmp)) { tmp = d(2,12); - hit("wand", mtmp, exclam(tmp)); + hit(effector, mtmp, exclam(tmp)); (void) resist(mtmp, otmp->oclass, tmp, TELL); - if (cansee(mtmp->mx, mtmp->my) && zap_oseen) + if (cansee(mtmp->mx, mtmp->my) && zap_oseen && wand) makeknown(WAN_STRIKING); } else { - miss("wand", mtmp); - if (cansee(mtmp->mx, mtmp->my) && zap_oseen) + miss(effector, mtmp); + if (cansee(mtmp->mx, mtmp->my) && zap_oseen && wand) makeknown(WAN_STRIKING); } break; case WAN_TELEPORTATION: + case SPE_TELEPORT_AWAY: if (mtmp == &youmonst) { - if (zap_oseen) makeknown(WAN_TELEPORTATION); + if (zap_oseen && wand) makeknown(WAN_TELEPORTATION); tele(); } else { /* for consistency with zap.c, don't identify */ @@ -1164,18 +1205,31 @@ return 0; } +STATIC_OVL +void +mbhit(mon,range,fhitm,fhito,obj) +struct monst *mon; /* monster shooting the wand */ +register int range; /* direction and range */ +int FDECL((*fhitm),(MONST_P,OBJ_P)); +int FDECL((*fhito),(OBJ_P,OBJ_P)); /* fns called when mon/obj hit */ +struct obj *obj; /* 2nd arg to fhitm/fhito */ +{ + dombhit(mon, range, fhitm, fhito, obj, -10, -10); +} + /* A modified bhit() for monsters. Based on bhit() in zap.c. Unlike * buzz(), bhit() doesn't take into account the possibility of a monster * zapping you, so we need a special function for it. (Unless someone wants * to merge the two functions...) */ -STATIC_OVL void -mbhit(mon,range,fhitm,fhito,obj) +void +dombhit(mon,range,fhitm,fhito,obj,dx,dy) struct monst *mon; /* monster shooting the wand */ register int range; /* direction and range */ int FDECL((*fhitm),(MONST_P,OBJ_P)); int FDECL((*fhito),(OBJ_P,OBJ_P)); /* fns called when mon/obj hit */ struct obj *obj; /* 2nd arg to fhitm/fhito */ +int dx, dy; { register struct monst *mtmp; register struct obj *otmp; @@ -1184,8 +1238,14 @@ bhitpos.x = mon->mx; bhitpos.y = mon->my; - ddx = sgn(mon->mux - mon->mx); - ddy = sgn(mon->muy - mon->my); + + if (dx == -10) { + ddx = sgn(mon->mux - mon->mx); + ddy = sgn(mon->muy - mon->my); + } else { + ddx = dx; + ddy = dy; + } while(range-- > 0) { int x,y; @@ -1202,6 +1262,7 @@ if (find_drawbridge(&x,&y)) switch (obj->otyp) { case WAN_STRIKING: + case SPE_FORCE_BOLT: destroy_drawbridge(x,y); } if(bhitpos.x==u.ux && bhitpos.y==u.uy) { @@ -1235,6 +1296,7 @@ case WAN_OPENING: case WAN_LOCKING: case WAN_STRIKING: + case SPE_FORCE_BOLT: if (doorlock(obj, bhitpos.x, bhitpos.y)) { makeknown(obj->otyp); /* if a shop door gets broken, add it to @@ -1751,7 +1813,10 @@ mquaffmsg(mtmp, otmp); /* format monster's name before altering its visibility */ Strcpy(nambuf, See_invisible ? Monnam(mtmp) : mon_nam(mtmp)); - mon_set_minvis(mtmp); + mon_set_minvis(mtmp, otmp->blessed + || otmp->otyp == WAN_MAKE_INVISIBLE); + if (!otmp->blessed && otmp->otyp == POT_INVISIBILITY) + begin_invis(mtmp, 30 + rnd(otmp->cursed? 10 : 30)); if (vismon && mtmp->minvis) { /* was seen, now invisible */ if (See_invisible) pline("%s body takes on a %s transparency.", @@ -1769,7 +1834,8 @@ case MUSE_WAN_SPEED_MONSTER: mzapmsg(mtmp, otmp, TRUE); otmp->spe--; - mon_adjust_speed(mtmp, 1, otmp); + mon_adjust_speed(mtmp, 4, otmp); + begin_speed(mtmp, 30 + rnd(15)); return 2; case MUSE_POT_SPEED: mquaffmsg(mtmp, otmp); @@ -1777,7 +1843,9 @@ different methods of maintaining speed ratings: player's character becomes "very fast" temporarily; monster becomes "one stage faster" permanently */ - mon_adjust_speed(mtmp, 1, otmp); + mon_adjust_speed(mtmp, 4, otmp); + begin_speed(mtmp, + 25 + rnd(otmp->blessed? 40 : otmp->cursed? 10 : 20)); m_useup(mtmp, otmp); return 2; case MUSE_WAN_POLYMORPH: @@ -2112,6 +2180,21 @@ if ((obj->otyp == POT_ACID) || (obj->otyp == CORPSE && (obj->corpsenm == PM_LIZARD || (acidic(&mons[obj->corpsenm]) && obj->corpsenm != PM_GREEN_SLIME)))) { mon_consume_unstone(mon, obj, by_you, TRUE); + return TRUE; + } + } + + /* Last ditch attempt - can we cast stf? */ + if (cancast(mon, SPE_STONE_TO_FLESH)) { + if (mcast_escape_spell(mon, SPE_STONE_TO_FLESH)) { + if (canseemon(mon)) { + if (Hallucination) + pline("What a pity - %s just ruined a future " + "piece of art!", + mon_nam(mon)); + else + pline("%s seems limber!", Monnam(mon)); + } return TRUE; } } diff -u -r1.1.1.1 potion.c --- src/potion.c 24 Feb 2003 16:06:16 -0000 1.1.1.1 +++ src/potion.c 24 Feb 2003 16:29:33 -0000 @@ -1060,7 +1060,9 @@ break; case POT_INVISIBILITY: angermon = FALSE; - mon_set_minvis(mon); + mon_set_minvis(mon, obj->blessed); + if (!obj->blessed) + begin_invis(mon, 30 + rnd(obj->cursed? 10 : 30)); break; case POT_SLEEPING: /* wakeup() doesn't rouse victims of temporary sleep */ @@ -1080,7 +1082,9 @@ break; case POT_SPEED: angermon = FALSE; - mon_adjust_speed(mon, 1, obj); + mon_adjust_speed(mon, 4, obj); + begin_speed(mon, + (obj->blessed? 12 : obj->cursed? 2 : 6) + rnd(5)); break; case POT_BLINDNESS: if(haseyes(mon->data)) { diff -u -r1.1.1.1 priest.c --- src/priest.c 24 Feb 2003 16:06:17 -0000 1.1.1.1 +++ src/priest.c 24 Feb 2003 16:29:33 -0000 @@ -55,7 +55,7 @@ if (m_carrying(mtmp, SKELETON_KEY)) allowflags |= BUSTDOOR; } if (is_giant(mtmp->data)) allowflags |= BUSTDOOR; - cnt = mfndpos(mtmp, poss, info, allowflags); + cnt = mfndpos(mtmp, poss, info, allowflags, NULL); if(mtmp->isshk && avoid && uondoor) { /* perhaps we cannot avoid him */ for(i=0; idata) && !breathless(mtmp->data)) { if (cansee(mtmp->mx, mtmp->my)) pline("%s coughs!", Monnam(mtmp)); - setmangry(mtmp); + if (heros_fault(reg)) setmangry(mtmp); if (haseyes(mtmp->data) && mtmp->mcansee) { mtmp->mblinded = 1; mtmp->mcansee = 0; @@ -947,6 +950,61 @@ return FALSE; /* Monster is still alive */ } +boolean +inside_acid_cloud(p1, p2) +genericptr_t p1; +genericptr_t p2; +{ + NhRegion *reg; + struct monst *mtmp; + int dam; + + reg = (NhRegion *) p1; + dam = (int) reg->arg; + if (p2 == NULL) { /* This means *YOU* Bozo! */ + /* Blinding is necessary, if only to help the player feel his way + * around (the fog hides the player's glyph) */ + if (!Blind) make_blinded(1L, FALSE); + + if (!Acid_resistance) { + /* Acid would probably burn your lungs, as well. */ + You("are caught in an acidic fog!"); + losehp(rnd(dam) + 5, "acid cloud", KILLED_BY_AN); + return FALSE; + } else { + pline("Acidic vapors swirl around you."); + return FALSE; + } + } else { /* A monster is inside the cloud */ + mtmp = (struct monst *) p2; + + if (haseyes(mtmp->data) && mtmp->mcansee) { + mtmp->mblinded = 1; + mtmp->mcansee = 0; + } + + if (heros_fault(reg)) setmangry(mtmp); + + /* Acid resistance protects */ + if (!resists_acid(mtmp)) { + if (cansee(mtmp->mx, mtmp->my)) + pline("%s is burned by the acid!", Monnam(mtmp)); + mtmp->mhp -= rnd(dam) + 5; + if (mtmp->mhp <= 0) { + if (heros_fault(reg)) + killed(mtmp); + else + monkilled(mtmp, "", AD_DRST); + if (mtmp->mhp <= 0) { /* not lifesaved */ + return TRUE; + } + } + } + } + return FALSE; /* Monster is still alive */ +} + + NhRegion * create_gas_cloud(x, y, radius, damage) xchar x, y; @@ -979,6 +1037,17 @@ cloud->visible = TRUE; cloud->glyph = cmap_to_glyph(S_cloud); add_region(cloud); + return cloud; +} + +NhRegion * +create_acid_cloud(x, y, radius, damage) +xchar x, y; +int radius; +int damage; +{ + NhRegion *cloud = create_gas_cloud(x, y, radius, damage); + if (cloud) cloud->inside_f = INSIDE_ACID_CLOUD; return cloud; } diff -u -r1.1.1.1 save.c --- src/save.c 24 Feb 2003 16:06:17 -0000 1.1.1.1 +++ src/save.c 26 Mar 2003 16:10:45 -0000 @@ -202,7 +202,7 @@ } #endif /* MFLOPPY */ - store_version(fd); + store_version(fd, FALSE); #ifdef STORE_PLNAME_IN_FILE bwrite(fd, (genericptr_t) plname, PL_NSIZ); #endif @@ -381,7 +381,7 @@ (void) write(fd, (genericptr_t) &currlev, sizeof(currlev)); save_savefile_name(fd); - store_version(fd); + store_version(fd, FALSE); #ifdef STORE_PLNAME_IN_FILE bwrite(fd, (genericptr_t) plname, PL_NSIZ); #endif diff -u -r1.1.1.1 spell.c --- src/spell.c 24 Feb 2003 16:06:18 -0000 1.1.1.1 +++ src/spell.c 24 Feb 2003 16:29:33 -0000 @@ -32,7 +32,6 @@ STATIC_DCL void NDECL(cast_protection); STATIC_DCL void FDECL(spell_backfire, (int)); STATIC_DCL const char *FDECL(spelltypemnemonic, (int)); -STATIC_DCL int FDECL(isqrt, (int)); /* The roles[] table lists the role-specific values for tuning * percent_success(). @@ -1115,7 +1114,7 @@ } /* Integer square root function without using floating point. */ -STATIC_OVL int +int isqrt(val) int val; { diff -u -r1.1.1.1 steal.c --- src/steal.c 24 Feb 2003 16:06:18 -0000 1.1.1.1 +++ src/steal.c 24 Feb 2003 16:29:33 -0000 @@ -513,6 +513,60 @@ #endif /* OVLB */ #ifdef OVL0 +STATIC_OVL boolean +mon_has_launcher(mon, ammo, keep) +struct monst *mon; +struct obj *ammo, *keep; +{ + struct obj *obj; + + for(obj = mon->minvent; obj; obj = obj->nobj) + if (is_launcher(obj) && ammo_and_launcher(ammo, obj)) + return TRUE; + + for (obj = keep; obj; obj = obj->nobj) + if (is_launcher(obj) && ammo_and_launcher(ammo, obj)) + return TRUE; + return FALSE; +} + +boolean +keep_ammo(mon, ammo, keep) +struct monst *mon; +struct obj *ammo, *keep; +{ + return rn2(3) || mon_has_launcher(mon, ammo, keep) +#ifdef FIREARMS + || objects[ammo->otyp].w_ammotyp == WP_GRENADE +#endif + ; +} + +STATIC_OVL boolean +mon_has_ammo(mon, launcher, keep) +struct monst *mon; +struct obj *launcher, *keep; +{ + struct obj *obj; + + for (obj = mon->minvent; obj; obj = obj->nobj) + if (ammo_and_launcher(obj, launcher)) + return TRUE; + + for (obj = keep; obj; obj = obj->nobj) + if (ammo_and_launcher(obj, launcher)) + return TRUE; + return FALSE; +} + +boolean +keep_launcher(mon, launcher, keep) +struct monst *mon; +struct obj *launcher, *keep; +{ + return rn2(3) || mon_has_ammo(mon, launcher, keep); +} + /* release the objects the creature is carrying */ void relobj(mtmp,show,is_pet) @@ -523,11 +577,16 @@ register struct obj *otmp; register int omx = mtmp->mx, omy = mtmp->my; struct obj *keepobj = 0; - struct obj *wep = MON_WEP(mtmp); + struct obj *wep = MON_WEP(mtmp), + *pref_wep = 0; boolean item1 = FALSE, item2 = FALSE; + boolean uses_weap = is_armed(mtmp->data); if (!is_pet || mindless(mtmp->data) || is_animal(mtmp->data)) item1 = item2 = TRUE; + else if (uses_weap) + pref_wep = select_hwep(mtmp); + if (!tunnels(mtmp->data) || !needspick(mtmp->data)) item1 = TRUE; while ((otmp = mtmp->minvent) != 0) { @@ -562,6 +621,24 @@ setmnotwielded(mtmp, otmp); otmp->owornmask = 0L; } + + /* D: Smart pets don't drop ranged weapons */ + if (is_pet && uses_weap && +#ifndef GIVE_PATCH + !mtmp->mflee && +#endif + otmp->oclass == WEAPON_CLASS) { + int skill = objects[otmp->otyp].oc_skill; + if ((is_missile(otmp) || otmp == pref_wep || + (is_ammo(otmp) && keep_ammo(mtmp, otmp, keepobj)) || + (is_launcher(otmp) && keep_launcher(mtmp, otmp, keepobj)) || + (skill == P_DAGGER || skill == P_KNIFE))) { + otmp->nobj = keepobj; + keepobj = otmp; + continue; + } + } + if (is_pet && cansee(omx, omy) && flags.verbose) pline("%s drops %s.", Monnam(mtmp), distant_name(otmp, doname)); diff -u -r1.1.1.1 timeout.c --- src/timeout.c 24 Feb 2003 16:06:18 -0000 1.1.1.1 +++ src/timeout.c 16 Mar 2003 15:40:43 -0000 @@ -1191,6 +1191,114 @@ #endif /* OVL0 */ #ifdef OVL1 +/* Start a levitation timer for a monster */ +void +begin_levitation(mon, duration) +struct monst *mon; +int duration; +{ + stop_timer(TIMEOUT_LEV, (genericptr_t) mon); + start_timer(duration, TIMER_MONSTER, TIMEOUT_LEV, (genericptr_t) mon); +} + +static +void +end_levitation(arg, timeout) +genericptr_t arg; +long timeout; +{ + struct monst *mon = (struct monst *) arg; + boolean grace = FALSE; + boolean has_levitation = mon && !!get_equiv_armor(mon, LEVITATION); + boolean needs_recast = FALSE; + + if (!mon || DEADMONSTER(mon)) return ; + + needs_recast = (is_pool(mon->mx,mon->my) || is_lava(mon->mx, mon->my)) && + !is_flyer(mon->data) && !is_floater(mon->data) && + !is_swimmer(mon->data); + + if (!has_levitation) { + if (needs_recast) { + if (timeout != monstermoves) { + /* Timeout ended while away - restart it */ + begin_levitation(mon, 10 + rnd(10)); + return ; + } + + /* Monster tries to recast levitation, if necessary */ + if (cancast(mon, SPE_LEVITATION)) { + mcast_escape_spell(mon, SPE_LEVITATION); + return ; + } + + if (mon->mspec_used || rn2(100) >= 20) { + /* Give monster another chance */ + begin_levitation(mon, 1); + return ; + } + } + /* Big trouble! */ + if (canseemon(mon)) + pline("%s levitation spell wears off.", s_suffix(Monnam(mon))); + + mon->mintrinsics &= ~MR2_LEVITATE; + minliquid(mon); + } +} + +void +begin_invis(mon, duration) +struct monst *mon; +int duration; +{ + stop_timer(TIMEOUT_INVIS, (genericptr_t) mon); + start_timer(duration, TIMER_MONSTER, TIMEOUT_INVIS, (genericptr_t) mon); +} + +static +void +end_invis(arg, timeout) +genericptr_t arg; +long timeout; +{ + struct monst *mon = (struct monst *) arg; + + if (!get_equiv_armor(mon, INVIS)) { + if (mon->minvis && !(mon->minvis = mon->perminvis)) { + /* 'suddenly reappears' sounds strange if you never saw it + * disappearing, but what else can we do? */ + if (canseemon(mon)) { + if (See_invisible) + pline("%s seems to unfade.", Monnam(mon)); + else + pline("%s suddenly reappears.", Monnam(mon)); + newsym(mon->mx, mon->my); /* make it appear */ + if (mon->wormno) see_wsegs(mon); /* and any tail too */ + } + } + } +} + +void +begin_speed(mon, duration) +struct monst *mon; +int duration; +{ + stop_timer(TIMEOUT_SPEED, (genericptr_t) mon); + start_timer(duration, TIMER_MONSTER, TIMEOUT_SPEED, (genericptr_t) mon); +} + +static +void +end_speed(arg, timeout) +genericptr_t arg; +long timeout; +{ + struct monst *mon = (struct monst *) arg; + mon_adjust_speed(mon, 0, NULL); +} + void do_storms() { @@ -1323,7 +1431,10 @@ TTAB(revive_mon, (timeout_proc)0, "revive_mon"), TTAB(burn_object, cleanup_burn, "burn_object"), TTAB(hatch_egg, (timeout_proc)0, "hatch_egg"), - TTAB(fig_transform, (timeout_proc)0, "fig_transform") + TTAB(fig_transform, (timeout_proc)0, "fig_transform"), + TTAB(end_levitation,(timeout_proc)0, "end_levitation"), + TTAB(end_invis, (timeout_proc)0, "end_invis"), + TTAB(end_speed, (timeout_proc)0, "end_speed") }; #undef TTAB @@ -1539,6 +1650,33 @@ } } +/* + * Stop all monster timers attached to this monster. If mon is NULL, stop *all* + * monster timers. + */ +void +mon_stop_timers(mon) +struct monst *mon; +{ + timer_element *curr, *prev, *next_timer=0; + + for (prev = 0, curr = timer_base; curr; curr = next_timer) { + next_timer = curr->next; + if (curr->kind == TIMER_MONSTER && + (!mon || curr->arg == (genericptr_t)mon)) { + if (prev) + prev->next = curr->next; + else + timer_base = curr->next; + if (timeout_funcs[curr->func_index].cleanup) + (*timeout_funcs[curr->func_index].cleanup)(curr->arg, + curr->timeout); + free((genericptr_t) curr); + } else { + prev = curr; + } + } +} /* * Stop all timers attached to this object. We can get away with this because @@ -1849,7 +1987,20 @@ if (!curr->arg) panic("cant find o_id %d", nid); curr->needs_fixup = 0; } else if (curr->kind == TIMER_MONSTER) { - panic("relink_timers: no monster timer implemented"); + /* panic("relink_timers: no monster timer implemented"); */ + /* WAC attempt to relink monster timers based on above + * and light source code + */ + if (ghostly) { + if (!lookup_id_mapping((unsigned)curr->arg, &nid)) + panic("relink_timers 1b"); + } else + nid = (unsigned) curr->arg; + curr->arg = (genericptr_t) find_mid(nid, FM_EVERYWHERE); + /* D: Don't panic :) */ + if (!curr->arg) impossible("cant find m_id %d for timer func," + " index %d", nid, curr->func_index); + curr->needs_fixup = 0; } else panic("relink_timers 2"); } diff -u -r1.1.1.1 trap.c --- src/trap.c 24 Feb 2003 16:06:18 -0000 1.1.1.1 +++ src/trap.c 24 Feb 2003 16:29:33 -0000 @@ -444,7 +444,7 @@ obj_extract_self(item); (void) add_to_minv(mon, item); } - m_dowear(mon, TRUE); + m_dowear(mon, TRUE, FALSE); delobj(statue); /* mimic statue becomes seen mimic; other hiders won't be hidden */ if (mon->m_ap_type) seemimic(mon); @@ -1514,6 +1514,42 @@ #endif /* OVL3 */ #ifdef OVL1 +STATIC_OVL +void +m_dosinkfall(mon) +struct monst *mon; +{ + boolean vis = canseemon(mon); + struct obj *worn; + + if (is_floater(mon->data)) { + if (vis) pline("%s wobbles unsteadily for a moment.", Monnam(mon)); + return ; + } + + if (vis) pline("%s crashes to the floor!", Monnam(mon)); + /* Be kinder to monsters than to the player */ + mon->mhp -= rn1(24, 2); + + if (mon->mhp < 0) { + monkilled(mon, "", AD_PHYS); + if (mon->mhp <= 0) { + newsym(mon->mx, mon->my); + return ; + } + } + + /* Force levitation off before call m_remove_armor, to prevent redundant + * floats down to floor messages */ + mon->mintrinsics &= ~MR2_LEVITATE; + /* Here's the fun bit: we need to force off all levitation items */ + while (worn = get_equiv_armor(mon, LEVITATION)) + m_remove_armor(mon, worn, TRUE); + + /* Stop levitation, assuming it's still on */ + stop_timer(TIMEOUT_LEV, (genericptr_t) mon); +} + int mintrap(mtmp) register struct monst *mtmp; @@ -1525,6 +1561,11 @@ if (!trap) { mtmp->mtrapped = 0; /* perhaps teleported? */ +#ifdef SINKS + /* Check whether we have a levitating monster over a sink */ + if(IS_SINK(levl[mtmp->mx][mtmp->my].typ) && is_levitating(mtmp)) + m_dosinkfall(mtmp); +#endif } else if (mtmp->mtrapped) { /* is currently in the trap */ if (!trap->tseen && cansee(mtmp->mx, mtmp->my) && canseemon(mtmp) && diff -u -r1.1.1.1 version.c --- src/version.c 24 Feb 2003 16:06:18 -0000 1.1.1.1 +++ src/version.c 31 Mar 2003 08:03:15 -0000 @@ -118,8 +118,9 @@ } void -store_version(fd) +store_version(fd, bones) int fd; +boolean bones; { const static struct version_info version_data = { VERSION_NUMBER, VERSION_FEATURES, @@ -127,8 +128,17 @@ }; bufoff(fd); - /* bwrite() before bufon() uses plain write() */ - bwrite(fd,(genericptr_t)&version_data,(unsigned)(sizeof version_data)); +#ifdef BONES_MASKED_FEATURES + if (bones) { + struct version_info port_version = version_data; + port_version.feature_set &= ~BONES_MASKED_FEATURES; + bwrite(fd,(genericptr_t)&port_version, + (unsigned)(sizeof port_version)); + } else +#endif + /* bwrite() before bufon() uses plain write() */ + bwrite(fd,(genericptr_t)&version_data, + (unsigned)(sizeof version_data)); bufon(fd); return; } diff -u -r1.1.1.1 worn.c --- src/worn.c 24 Feb 2003 16:06:18 -0000 1.1.1.1 +++ src/worn.c 24 Feb 2003 17:49:34 -0000 @@ -5,7 +5,8 @@ #include "hack.h" STATIC_DCL void FDECL(m_lose_armor, (struct monst *,struct obj *)); -STATIC_DCL void FDECL(m_dowear_type, (struct monst *,long, BOOLEAN_P, BOOLEAN_P)); +STATIC_DCL void FDECL(m_dowear_type, (struct monst *,long, BOOLEAN_P, + BOOLEAN_P, BOOLEAN_P)); STATIC_DCL int FDECL(extra_pref, (struct monst *, struct obj *)); const struct worn { @@ -129,10 +130,11 @@ } void -mon_set_minvis(mon) +mon_set_minvis(mon, perminvis) struct monst *mon; +boolean perminvis; { - mon->perminvis = 1; + if (perminvis) mon->perminvis = 1; if (!mon->invis_blkd) { mon->minvis = 1; newsym(mon->mx, mon->my); /* make it disappear */ @@ -159,6 +161,7 @@ if (mon->permspeed == MSLOW) mon->permspeed = 0; else mon->permspeed = MFAST; break; + case 4: /* Set speed to fast, but not permspeed */ case 0: /* just check for worn speed boots */ break; case -1: @@ -179,7 +182,7 @@ for (otmp = mon->minvent; otmp; otmp = otmp->nobj) if (otmp->owornmask && objects[otmp->otyp].oc_oprop == FAST) break; - if (otmp) /* speed boots */ + if (otmp || adjust == 4) /* speed boots or spell */ mon->mspeed = MFAST; else mon->mspeed = mon->permspeed; @@ -206,6 +209,20 @@ } } +struct obj * +get_equiv_armor(mon, which) +struct monst *mon; +int which; +{ + struct obj *otmp; + + for (otmp = mon->minvent; otmp; otmp = otmp->nobj) + if (otmp->owornmask && + (int) objects[otmp->otyp].oc_oprop == which) + return otmp; + return (struct obj *) NULL; +} + /* armor put on or taken off; might be magical variety */ void update_mon_intrinsics(mon, obj, on, silently) @@ -243,8 +260,16 @@ case STEALTH: case TELEPAT: break; - /* properties which should have an effect but aren't implemented */ case LEVITATION: + if (!is_levitating(mon) && !is_flyer(mon->data)) { + mon->mintrinsics |= MR2_LEVITATE; + if (canseemon(mon) && !silently) { + pline("%s begins to float in the air!", Monnam(mon)); + makeknown(obj->otyp); + } + } + break; + /* properties which should have an effect but aren't implemented */ case WWALKING: break; /* properties which maybe should have an effect but don't */ @@ -274,6 +299,21 @@ in_mklev = save_in_mklev; break; } + case LEVITATION: + if (!get_equiv_armor(mon, which)) { + if (mon->mintrinsics & MR2_LEVITATE) { + mon->mintrinsics &= ~MR2_LEVITATE; + if (!is_flyer(mon->data) && !is_floater(mon->data) + && canseemon(mon)) { + if (!silently) { + pline("%s floats down to the %s.", Monnam(mon), + surface(mon->mx, mon->my)); + makeknown(obj->otyp); + } + } + } + } + break; case FIRE_RES: case COLD_RES: case SLEEP_RES: @@ -288,11 +328,7 @@ we don't currently check for anything conferred via simply carrying an object. */ if (!(mon->data->mresists & mask)) { - for (otmp = mon->minvent; otmp; otmp = otmp->nobj) - if (otmp->owornmask && - (int) objects[otmp->otyp].oc_oprop == which) - break; - if (!otmp) + if (!get_equiv_armor(mon, which)) mon->mintrinsics &= ~((unsigned short) mask); } break; @@ -346,7 +382,8 @@ * the monster can put everything on at once; otherwise, wearing takes time. * This doesn't affect monster searching for objects--a monster may very well * search for objects it would not want to wear, because we don't want to - * check which_armor() each round. + * check which_armor() each round. If forceall is true, the monster won't let + * being frozen prevent it from wearing armor. * * We'll let monsters put on shirts and/or suits under worn cloaks, but * not shirts under worn suits. This is somewhat arbitrary, but it's @@ -356,9 +393,9 @@ * already worn body armor is too obviously buggy... */ void -m_dowear(mon, creation) +m_dowear(mon, creation, forceall) register struct monst *mon; -boolean creation; +boolean creation, forceall; { #define RACE_EXCEPTION TRUE /* Note the restrictions here are the same as in dowear in do_wear.c @@ -373,41 +410,45 @@ (mon->data->mlet != S_MUMMY && mon->data != &mons[PM_SKELETON]))) return; - m_dowear_type(mon, W_AMUL, creation, FALSE); + m_dowear_type(mon, W_AMUL, creation, FALSE, forceall); #ifdef TOURIST /* can't put on shirt if already wearing suit */ if (!cantweararm(mon->data) || (mon->misc_worn_check & W_ARM)) - m_dowear_type(mon, W_ARMU, creation, FALSE); + m_dowear_type(mon, W_ARMU, creation, FALSE, forceall); #endif /* treating small as a special case allows hobbits, gnomes, and kobolds to wear cloaks */ if (!cantweararm(mon->data) || mon->data->msize == MZ_SMALL) - m_dowear_type(mon, W_ARMC, creation, FALSE); - m_dowear_type(mon, W_ARMH, creation, FALSE); + m_dowear_type(mon, W_ARMC, creation, FALSE, forceall); + m_dowear_type(mon, W_ARMH, creation, FALSE, forceall); if (!MON_WEP(mon) || !bimanual(MON_WEP(mon))) - m_dowear_type(mon, W_ARMS, creation, FALSE); - m_dowear_type(mon, W_ARMG, creation, FALSE); + m_dowear_type(mon, W_ARMS, creation, FALSE, forceall); + m_dowear_type(mon, W_ARMG, creation, FALSE, forceall); if (!slithy(mon->data) && mon->data->mlet != S_CENTAUR) - m_dowear_type(mon, W_ARMF, creation, FALSE); + m_dowear_type(mon, W_ARMF, creation, FALSE, forceall); if (!cantweararm(mon->data)) - m_dowear_type(mon, W_ARM, creation, FALSE); + m_dowear_type(mon, W_ARM, creation, FALSE, forceall); else - m_dowear_type(mon, W_ARM, creation, RACE_EXCEPTION); + m_dowear_type(mon, W_ARM, creation, RACE_EXCEPTION, forceall); } STATIC_OVL void -m_dowear_type(mon, flag, creation, racialexception) +m_dowear_type(mon, flag, creation, racialexception, forceall) struct monst *mon; long flag; boolean creation; boolean racialexception; +boolean forceall; { struct obj *old, *best, *obj; int m_delay = 0; int unseen = !canseemon(mon); char nambuf[BUFSZ]; - if (mon->mfrozen) return; /* probably putting previous item on */ + /* D: If monster took off lev. boots to pick up armour, we want it to + * wear both armor and boots, so ignore mfrozen if the caller thinks + * this is a special case. */ + if (mon->mfrozen && !forceall) return; /* Get a copy of monster's name before altering its visibility */ Strcpy(nambuf, See_invisible ? Monnam(mon) : mon_nam(mon)); @@ -467,6 +508,12 @@ outer_break: if (!best || best == old) return; +#ifdef SINKS + /* Don't wear levitating stuff when standing on a sink */ + if (objects[best->otyp].oc_oprop == LEVITATION && + IS_SINK(levl[mon->mx][mon->my].typ)) return ; +#endif + /* if wearing a cloak, account for the time spent removing and re-wearing it when putting on a suit or shirt */ if ((flag == W_ARM @@ -493,8 +540,11 @@ pline("%s%s puts on %s.", Monnam(mon), buf, distant_name(best,doname)); } /* can see it */ + + /* D: Delays are cumulative, assuming the monster's trying to + * wear all its armour in one go */ m_delay += objects[best->otyp].oc_delay; - mon->mfrozen = m_delay; + mon->mfrozen += m_delay; if (mon->mfrozen) mon->mcanmove = 0; } if (old) @@ -511,6 +561,86 @@ } } #undef RACE_EXCEPTION + +/* D: Removes a worn item from a monster, usually a piece of armor */ +void +m_remove_armor(mon, armor, force) +struct monst *mon; +struct obj *armor; +boolean force; /* Is the armor removal forcible? */ +{ + long wornmask = armor->owornmask; + + if ((armor->cursed && !force) || !wornmask) return ; + + /* Spend some turns getting this off */ + if (mon->mcanmove) { + mon->mfrozen = 1; + mon->mcanmove = 0; + } + + /* This code borrowed from the nymph/foocubus steal section of mdamagem() */ + + /* Flag armor as no longer in use */ + mon->misc_worn_check &= ~wornmask; + /* Is the monster wielding this? Unlikely to happen for armor, but this + * needn't be armor */ + if (wornmask & W_WEP) setmnotwielded(mon, armor); + armor->owornmask = 0L; + + /* If we actually took off some armor, say so */ + if ((wornmask & W_ARMOR) && !force && canseemon(mon)) + pline("%s removes %s.", Monnam(mon), distant_name(armor, doname)); + + /* Update monster intrinsics; this may produce messages, which should only + * appear after the removes . message */ + update_mon_intrinsics(mon, armor, FALSE, FALSE); +} + +/* Performs an action appropriate for the monster to stop levitating: returns + * -1 if the monster couldn't stop levitating or the number of turns the action + * of stopping levitation will take. + * The caller is responsible for checking whether this monster is actually + * levitating or not before calling this and to call minliquid() if that's + * needed. + */ +int +m_stop_levitating(mon) +struct monst *mon; +{ + struct obj *levarm = NULL; + + /* If we can't move, we can't move */ + if (mon->mfrozen) { + impossible("Paralyzed monster wants to stop levitating?"); + return -1; + } + + /* Check first for worn levitating items (only boots for monsters atm) */ + levarm = get_equiv_armor(mon, LEVITATION); + if (levarm) { + /* Can we take this off? */ + if (levarm->cursed) return -1; + m_remove_armor(mon, levarm, FALSE); + return mon->mfrozen; + } + + /* No levitating gear equipped, so this is either a potion or spell effect; + * potion effects aren't implemented yet, so this must be a spell effect, + * but take no chances */ + if (getmspell(mon, SPE_LEVITATION, 1)) { + /* Assume that a spellcaster that knows how to levitate knows how to + * stop levitating */ + if (stop_timer(TIMEOUT_LEV, (genericptr_t) mon)) { + mon->mintrinsics &= ~MR2_LEVITATE; + if (canseemon(mon)) pline("%s stops levitating.", Monnam(mon)); + return 0; + } + } + + /* Some power beyond our control has us levitating */ + return -1; +} struct obj * which_armor(mon, flag) diff -u -r1.1.1.1 zap.c --- src/zap.c 24 Feb 2003 16:06:18 -0000 1.1.1.1 +++ src/zap.c 24 Feb 2003 16:29:33 -0000 @@ -80,7 +80,7 @@ "sleep ray", "finger of death", "bolt of lightning", /* There is no spell, used for retribution */ - "", + "blast of poison gas", "", "", "", @@ -139,7 +139,8 @@ case SPE_SLOW_MONSTER: if (!resist(mtmp, otmp->oclass, 0, NOTELL)) { mon_adjust_speed(mtmp, -1, otmp); - m_dowear(mtmp, FALSE); /* might want speed boots */ + /* might want speed boots */ + m_dowear(mtmp, FALSE, FALSE); if (u.uswallow && (mtmp == u.ustuck) && is_whirly(mtmp->data)) { You("disrupt %s!", mon_nam(mtmp)); @@ -151,7 +152,8 @@ case WAN_SPEED_MONSTER: if (!resist(mtmp, otmp->oclass, 0, NOTELL)) { mon_adjust_speed(mtmp, 1, otmp); - m_dowear(mtmp, FALSE); /* might want speed boots */ + /* might want speed boots */ + m_dowear(mtmp, FALSE, FALSE); } break; case WAN_UNDEAD_TURNING: @@ -215,7 +217,7 @@ /* format monster's name before altering its visibility */ Strcpy(nambuf, Monnam(mtmp)); - mon_set_minvis(mtmp); + mon_set_minvis(mtmp, TRUE); if (!oldinvis && knowninvisible(mtmp)) { pline("%s turns transparent!", nambuf); makeknown(otyp); @@ -2106,6 +2108,10 @@ (void) polymon(PM_FLESH_GOLEM); if (Stoned) fix_petrification(); /* saved! */ /* but at a cost.. */ + + /* If !ordinary, this is a stone-to-flesh cast by a monster, + * don't touch hero's inventory */ + if (!ordinary) break; for (otemp = invent; otemp; otemp = onext) { onext = otemp->nobj; (void) bhito(otemp, obj); @@ -3216,6 +3222,17 @@ return (3 - chance) < ac+spell_bonus; } + +void +buzz(type,nd,sx,sy,dx,dy) +register int type, nd; +register xchar sx,sy; +register int dx,dy; +{ + dobuzz(type, nd, sx, sy, dx, dy, TRUE); +} + + /* type == 0 to 9 : you shooting a wand */ /* type == 10 to 19 : you casting a spell */ /* type == 20 to 29 : you breathing as a monster */ @@ -3224,10 +3241,11 @@ /* type == -30 to -39 : monster shooting a wand */ /* called with dx = dy = 0 with vertical bolts */ void -buzz(type,nd,sx,sy,dx,dy) +dobuzz(type,nd,sx,sy,dx,dy,say) register int type, nd; register xchar sx,sy; register int dx,dy; +boolean say; /* D: Announce out of sight hit/miss events if true */ { int range, abstype = abs(type) % 10; struct rm *lev; @@ -3372,7 +3390,8 @@ } else { if (!otmp) { /* normal non-fatal hit */ - hit(fltxt, mon, exclam(tmp)); + if (say || canseemon(mon)) + hit(fltxt, mon, exclam(tmp)); } else { /* some armor was destroyed; no damage done */ if (canseemon(mon)) @@ -3387,7 +3406,8 @@ } range -= 2; } else { - miss(fltxt,mon); + if (say || canseemon(mon)) + miss(fltxt,mon); } } else if (sx == u.ux && sy == u.uy && range >= 0) { nomul(0); diff -u -r1.1.1.1 makedefs.c --- util/makedefs.c 24 Feb 2003 16:06:26 -0000 1.1.1.1 +++ util/makedefs.c 16 Mar 2003 15:57:37 -0000 @@ -413,9 +413,23 @@ * break save file compatibility with 3.4.0 files, so we will * explicitly mask it out during version checks. * This should go away in the next version update. + * + * MONSTER_TIMERS: Active monster timers make savefiles incompatible with + * vanilla NetHack (which will panic if it finds a monster timer). So we add + * monster timers as VERSION_FEATURES to prevent unpatched vanilla from dying + * horribly; at the same time, we don't mind seeing savefiles from a version + * without monster timers, so we put that in IGNORED_FEATURES. */ #define IGNORED_FEATURES ( 0L \ | (1L << 23) /* TIMED_DELAY */ \ + | (1L << 24) /* MONSTER_TIMERS */ \ + ) + +/* + * Features that should be suppressed when saving bones files. + */ +#define BONES_MASKED_FEATURES ( 0L \ + | (1L << 24) /* MONSTER_TIMERS */ \ ) static void @@ -478,6 +492,9 @@ #ifdef SCORE_ON_BOTL | (1L << 21) #endif +#ifdef MONSTER_TIMERS + | (1L << 24) +#endif /* data format [COMPRESS excluded] (27..31) */ #ifdef ZEROCOMP | (1L << 27) @@ -582,6 +599,10 @@ Fprintf(ofp,"#define IGNORED_FEATURES 0x%08lx%s\n", (unsigned long) IGNORED_FEATURES, ul_sfx); #endif +#ifdef BONES_MASKED_FEATURES + Fprintf(ofp,"#define BONES_MASKED_FEATURES 0x%08lx%s\n", + (unsigned long) BONES_MASKED_FEATURES, ul_sfx); +#endif Fprintf(ofp,"#define VERSION_SANITY1 0x%08lx%s\n", version.entity_count, ul_sfx); Fprintf(ofp,"#define VERSION_SANITY2 0x%08lx%s\n", @@ -744,6 +765,9 @@ #endif #ifdef WALLIFIED_MAZE "walled mazes", +#endif +#ifdef MONSTER_TIMERS + "timers on monsters", #endif #ifdef ZEROCOMP "zero-compressed save files",