diff -pruN slashem-0.0.7E7F1-official-release/include/extern.h slashem-0.0.7E7F1-pet-ranged/include/extern.h --- slashem-0.0.7E7F1-official-release/include/extern.h 2005-07-02 09:24:44.000000000 +0200 +++ slashem-0.0.7E7F1-pet-ranged/include/extern.h 2005-10-26 00:51:54.000000000 +0200 @@ -460,6 +460,10 @@ E void FDECL(wary_dog, (struct monst *, 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)); E boolean FDECL(betrayed, (struct monst *)); #ifdef USE_TRAMPOLI E void FDECL(wantdoor, (int,int,genericptr_t)); @@ -669,7 +673,7 @@ E const char *FDECL(fqname, (const char E FILE *FDECL(fopen_datafile, (const char *,const char *,int)); #endif 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 @@ -1036,6 +1040,9 @@ E void FDECL(mapglyph, (int, int *, int 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 ### */ @@ -1202,11 +1209,11 @@ E int FDECL(meatmetal, (struct monst *)) E void FDECL(meatcorpse, (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*)); @@ -1254,6 +1261,7 @@ E void FDECL(set_mon_data, (struct monst 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 *)); @@ -1372,6 +1380,9 @@ E int FDECL(ohitmon, (struct monst *,str 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(breamspot, (struct monst *, struct attack *, XCHAR_P, XCHAR_P)); E boolean FDECL(linedup, (XCHAR_P,XCHAR_P,XCHAR_P,XCHAR_P)); E boolean FDECL(lined_up, (struct monst *)); @@ -1385,6 +1396,9 @@ E boolean FDECL(hits_bars, (struct obj * 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 *)); @@ -1791,6 +1805,7 @@ E void FDECL(show_region, (NhRegion*, XC 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)); E NhRegion* FDECL(create_cthulhu_death_cloud, (XCHAR_P, XCHAR_P, int, int)); /* ### restore.c ### */ @@ -2003,6 +2018,7 @@ E boolean FDECL(load_special, (const cha #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 *)); @@ -2026,6 +2042,8 @@ E long FDECL(somegold, (long)); #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 *)); @@ -2113,6 +2131,9 @@ E void FDECL(hatch_egg, (genericptr_t, l 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 FDECL(burn_faster, (struct obj *, long)); #ifdef LIGHTSABERS @@ -2125,6 +2146,7 @@ E void NDECL(run_timers); 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)); @@ -2499,12 +2521,15 @@ E boolean FDECL(worm_known, (struct mons 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 *)); @@ -2550,6 +2575,7 @@ E struct monst *FDECL(bhit, (int,int,int 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 -pruN slashem-0.0.7E7F1-official-release/include/monattk.h slashem-0.0.7E7F1-pet-ranged/include/monattk.h --- slashem-0.0.7E7F1-official-release/include/monattk.h 2005-07-02 09:24:44.000000000 +0200 +++ slashem-0.0.7E7F1-pet-ranged/include/monattk.h 2005-10-26 00:51:54.000000000 +0200 @@ -101,4 +101,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 -pruN slashem-0.0.7E7F1-official-release/include/timeout.h slashem-0.0.7E7F1-pet-ranged/include/timeout.h --- slashem-0.0.7E7F1-official-release/include/timeout.h 2005-07-02 09:24:44.000000000 +0200 +++ slashem-0.0.7E7F1-pet-ranged/include/timeout.h 2005-10-26 00:51:54.000000000 +0200 @@ -30,20 +30,23 @@ typedef void FDECL((*timeout_proc), (gen #define HATCH_EGG 5 #define FIG_TRANSFORM 6 #define UNPOLY_MON 7 +#define TIMEOUT_LEV 8 +#define TIMEOUT_INVIS 9 +#define TIMEOUT_SPEED 10 # ifdef FIREARMS -#define BOMB_BLOW 8 +#define BOMB_BLOW 11 # ifdef UNPOLYPILE -#define UNPOLY_OBJ 9 -#define NUM_TIME_FUNCS 10 +#define UNPOLY_OBJ 12 +#define NUM_TIME_FUNCS 13 # else -#define NUM_TIME_FUNCS 9 +#define NUM_TIME_FUNCS 12 # endif # else /* FIREARMS */ # ifdef UNPOLYPILE -#define UNPOLY_OBJ 8 -#define NUM_TIME_FUNCS 9 +#define UNPOLY_OBJ 11 +#define NUM_TIME_FUNCS 12 # else -#define NUM_TIME_FUNCS 8 +#define NUM_TIME_FUNCS 11 # endif # endif /* FIREARMS */ diff -pruN slashem-0.0.7E7F1-official-release/question slashem-0.0.7E7F1-pet-ranged/question --- slashem-0.0.7E7F1-official-release/question 1970-01-01 01:00:00.000000000 +0100 +++ slashem-0.0.7E7F1-pet-ranged/question 2005-10-26 00:51:54.000000000 +0200 @@ -0,0 +1,7 @@ +include/timeout.h ++ #define TIMEOUT_LEV 6 ++ #define TIMEOUT_INVIS 7 ++ #define TIMEOUT_SPEED 8 + +=> que si #define MONSTERS_TIMERS ? + diff -pruN slashem-0.0.7E7F1-official-release/src/bones.c slashem-0.0.7E7F1-pet-ranged/src/bones.c --- slashem-0.0.7E7F1-official-release/src/bones.c 2005-07-02 09:24:44.000000000 +0200 +++ slashem-0.0.7E7F1-pet-ranged/src/bones.c 2005-10-26 00:51:54.000000000 +0200 @@ -329,7 +329,7 @@ struct obj *corpse; 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); @@ -405,11 +405,15 @@ struct obj *corpse; } #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 -pruN slashem-0.0.7E7F1-official-release/src/dbridge.c slashem-0.0.7E7F1-pet-ranged/src/dbridge.c --- slashem-0.0.7E7F1-official-release/src/dbridge.c 2005-07-02 09:24:44.000000000 +0200 +++ slashem-0.0.7E7F1-pet-ranged/src/dbridge.c 2005-10-26 00:51:54.000000000 +0200 @@ -363,7 +363,7 @@ int x, y; (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)) || @@ -470,7 +470,7 @@ boolean chunks; (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)) @@ -708,7 +708,7 @@ struct entity *etmp; 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 -pruN slashem-0.0.7E7F1-official-release/src/dig.c slashem-0.0.7E7F1-pet-ranged/src/dig.c --- slashem-0.0.7E7F1-official-release/src/dig.c 2005-07-02 09:24:44.000000000 +0200 +++ slashem-0.0.7E7F1-pet-ranged/src/dig.c 2005-10-26 00:51:54.000000000 +0200 @@ -601,7 +601,7 @@ int ttyp; 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)) ? @@ -667,7 +667,7 @@ int ttyp; 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 -pruN slashem-0.0.7E7F1-official-release/src/do.c slashem-0.0.7E7F1-pet-ranged/src/do.c --- slashem-0.0.7E7F1-official-release/src/do.c 2005-07-02 09:24:44.000000000 +0200 +++ slashem-0.0.7E7F1-pet-ranged/src/do.c 2005-10-26 00:51:54.000000000 +0200 @@ -1469,7 +1469,7 @@ final_level() 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 -pruN slashem-0.0.7E7F1-official-release/src/dogmove.c slashem-0.0.7E7F1-pet-ranged/src/dogmove.c --- slashem-0.0.7E7F1-official-release/src/dogmove.c 2005-07-02 09:24:44.000000000 +0200 +++ slashem-0.0.7E7F1-pet-ranged/src/dogmove.c 2005-10-26 00:51:54.000000000 +0200 @@ -37,11 +37,18 @@ DROPPABLES(mon) 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) { @@ -54,7 +61,22 @@ register struct monst *mon; 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; } @@ -68,6 +90,9 @@ STATIC_OVL boolean FDECL(cursed_object_a 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 @@ -371,6 +396,13 @@ int udist; (!obj->blessed || (!is_demon(mtmp->data) && !is_undead(mtmp->data)))) { if(rn2(20) < edog->apport+3) { if (rn2(udist) || !rn2(edog->apport)) { + /* 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 ((!nohands(mtmp->data)) || /* KMH, balance patch -- 10*level */ (obj->quan <= dogquan)) @@ -426,7 +458,8 @@ int udist; mtmp->weapon_check = NEED_HTH_WEAPON; (void) mon_wield_item(mtmp); } - m_dowear(mtmp, FALSE); + m_dowear(mtmp, FALSE, TRUE); + } } } } @@ -675,6 +708,7 @@ register int after; /* this is extra fas 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)) /* @@ -801,7 +835,18 @@ register int after; /* this is extra fas } 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; + } + } #ifdef DEBUG debugpline("%d positions found with allow: %s", cnt, allow_set(allowflags)); @@ -967,6 +1012,62 @@ register int after; /* this is extra fas } 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; @@ -1120,6 +1221,322 @@ genericptr_t distance; } } +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 */ /*dogmove.c*/ diff -pruN slashem-0.0.7E7F1-official-release/src/dokick.c slashem-0.0.7E7F1-pet-ranged/src/dokick.c --- slashem-0.0.7E7F1-official-release/src/dokick.c 2005-07-02 09:24:44.000000000 +0200 +++ slashem-0.0.7E7F1-pet-ranged/src/dokick.c 2005-10-26 00:51:54.000000000 +0200 @@ -243,7 +243,7 @@ doit: } 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 -pruN slashem-0.0.7E7F1-official-release/src/dothrow.c slashem-0.0.7E7F1-pet-ranged/src/dothrow.c --- slashem-0.0.7E7F1-official-release/src/dothrow.c 2005-07-02 09:24:44.000000000 +0200 +++ slashem-0.0.7E7F1-pet-ranged/src/dothrow.c 2005-10-26 00:51:54.000000000 +0200 @@ -783,8 +783,10 @@ mhurtle(mon, dx, dy, range) 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) @@ -793,17 +795,25 @@ mhurtle(mon, dx, dy, range) 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 -pruN slashem-0.0.7E7F1-official-release/src/makemon.c slashem-0.0.7E7F1-pet-ranged/src/makemon.c --- slashem-0.0.7E7F1-official-release/src/makemon.c 2005-07-02 09:24:44.000000000 +0200 +++ slashem-0.0.7E7F1-pet-ranged/src/makemon.c 2005-10-26 00:51:54.000000000 +0200 @@ -1698,7 +1698,7 @@ register int mmflags; 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 { if (mtmp->minvent) discard_minvent(mtmp); mtmp->minvent = (struct obj *)0; /* caller expects this */ diff -pruN slashem-0.0.7E7F1-official-release/src/mcastu.c slashem-0.0.7E7F1-pet-ranged/src/mcastu.c --- slashem-0.0.7E7F1-official-release/src/mcastu.c 2005-07-02 09:24:44.000000000 +0200 +++ slashem-0.0.7E7F1-pet-ranged/src/mcastu.c 2005-10-26 00:51:54.000000000 +0200 @@ -32,18 +32,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, FALSE); + 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 @@ -124,6 +1042,29 @@ int spellval; } } +/* 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) @@ -158,6 +1099,35 @@ int spellnum; } } +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 @@ -170,10 +1140,14 @@ castmu(mtmp, mattk, thinks_it_foundyou, boolean foundyou; { int dmg, ml = mtmp->m_lev; - int ret; + int ret = 0; int spellnum = 0; int spellev, chance, difficulty, splcaster, learning; + if (!mtmp->mpeaceful && + (ret = gcastm(mtmp, mattk, &youmonst))) + return ret; + /* Three cases: * -- monster is attacking you. Search for a useful spell. * -- monster thinks it's attacking you. Search for a useful spell, @@ -513,8 +1487,9 @@ int spellnum; 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; @@ -533,7 +1508,8 @@ int spellnum; 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: @@ -572,6 +1548,150 @@ int spellnum; } 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; @@ -763,6 +1883,115 @@ int spellnum; 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) @@ -817,7 +2046,7 @@ int spellnum; spellnum == MGC_CALL_UNDEAD)) 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) @@ -865,6 +2094,116 @@ int spellnum; 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 @@ -898,6 +2237,36 @@ buzzmu(mtmp, mattk) /* monster uses spe 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 */ /*mcastu.c*/ diff -pruN slashem-0.0.7E7F1-official-release/src/mhitm.c slashem-0.0.7E7F1-pet-ranged/src/mhitm.c --- slashem-0.0.7E7F1-official-release/src/mhitm.c 2005-07-02 09:24:44.000000000 +0200 +++ slashem-0.0.7E7F1-pet-ranged/src/mhitm.c 2005-10-26 00:51:54.000000000 +0200 @@ -296,43 +296,8 @@ mattackm(magr, mdef) otmp = (struct obj *)0; attk = 1; switch (mattk->aatyp) { - case AT_BREA: - case AT_SPIT: - if (range) { - if (mattk->aatyp == AT_BREA) - res[i] = breamm(magr, mdef, mattk); - else - res[i] = spitmm(magr, mdef, mattk); - /* We can't distinguish no action from failed attack - * so assume defender doesn't waken unless actually hit. - */ - strike = res[i] & MM_HIT; - } else - strike = 0; - attk = 0; - break; - - case AT_MAGC: - /* [ALI] Monster-on-monster spell casting always fails. This - * is partly for balance reasons and partly because the - * amount of code required to implement it is prohibitive. - */ - strike = 0; - attk = 0; - if (canseemon(magr) && couldsee(magr->mx, magr->my)) { - char buf[BUFSZ]; - Strcpy(buf, Monnam(magr)); - if (vis) - pline("%s points at %s, then curses.", buf, - mon_nam(mdef)); - else - pline("%s points and curses at something.", buf); - } else if (flags.soundok) - Norep("You hear a mumbled curse."); - break; - case AT_WEAP: - /* "ranged" attacks */ + /* "ranged" attacks */ #ifdef REINCARNATION if (!Is_rogue_level(&u.uz) && range) { #else @@ -344,6 +309,17 @@ mattackm(magr, mdef) break; } /* "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; @@ -365,7 +341,10 @@ mattackm(magr, mdef) 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. @@ -412,6 +391,10 @@ mattackm(magr, mdef) 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; @@ -427,6 +410,11 @@ mattackm(magr, mdef) 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 */ @@ -440,13 +428,48 @@ mattackm(magr, mdef) } break; + case AT_BREA: + if (!monnear(magr, mdef->mx, mdef->my)) { + strike = breamm(magr, mdef, mattk); + + /* 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, mdef, mattk); + + /* 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 -pruN slashem-0.0.7E7F1-official-release/src/mon.c slashem-0.0.7E7F1-pet-ranged/src/mon.c --- slashem-0.0.7E7F1-official-release/src/mon.c 2005-07-02 09:24:44.000000000 +0200 +++ slashem-0.0.7E7F1-pet-ranged/src/mon.c 2005-10-26 00:51:54.000000000 +0200 @@ -487,9 +487,9 @@ register struct monst *mtmp; 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 @@ -997,15 +997,20 @@ mpickgold(mtmp) #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. */ @@ -1018,10 +1023,23 @@ mpickstuff(mtmp, str) !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->mx, mtmp->my) <= 5) ? @@ -1031,12 +1049,13 @@ mpickstuff(mtmp, str) 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 */ @@ -1136,11 +1155,12 @@ struct obj *otmp; /* 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; @@ -1157,9 +1177,12 @@ mfndpos(mon, poss, info, flag) 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; @@ -1316,6 +1339,7 @@ impossible("A monster looked at a very s && ttmp->ttyp != HOLE) || (!is_flyer(mdat) && !is_floater(mdat) + && !is_levitating(mon) && !is_clinger(mdat)) || In_sokoban(&u.uz)) && (ttmp->ttyp != SLP_GAS_TRAP || @@ -1337,10 +1361,17 @@ impossible("A monster looked at a very s } } } + +#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; @@ -1643,6 +1674,9 @@ register struct monst *mtmp; 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) @@ -2909,7 +2943,7 @@ boolean msg; 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 -pruN slashem-0.0.7E7F1-official-release/src/mondata.c slashem-0.0.7E7F1-pet-ranged/src/mondata.c --- slashem-0.0.7E7F1-official-release/src/mondata.c 2005-07-02 09:24:44.000000000 +0200 +++ slashem-0.0.7E7F1-pet-ranged/src/mondata.c 2005-10-26 00:51:54.000000000 +0200 @@ -29,6 +29,15 @@ int flag; #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 -pruN slashem-0.0.7E7F1-official-release/src/monmove.c slashem-0.0.7E7F1-pet-ranged/src/monmove.c --- slashem-0.0.7E7F1-official-release/src/monmove.c 2005-07-02 09:24:44.000000000 +0200 +++ slashem-0.0.7E7F1-pet-ranged/src/monmove.c 2005-10-26 00:51:54.000000000 +0200 @@ -414,6 +414,17 @@ register struct monst *mtmp; /* 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; @@ -558,6 +569,9 @@ toofar: 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; } @@ -660,7 +674,7 @@ register int after; 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; @@ -698,7 +712,8 @@ register int after; 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);*/ /* WAC add dragon breath */ @@ -780,6 +795,13 @@ not_special: 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 { @@ -974,8 +996,20 @@ not_special: 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; @@ -1134,7 +1168,11 @@ postmov: 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 */ @@ -1222,6 +1260,8 @@ postmov: 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) { @@ -1245,7 +1285,10 @@ postmov: 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 */ /* KMH -- Taz likes organics, too! */ @@ -1256,14 +1299,17 @@ postmov: if (ptr == &mons[PM_GHOUL] || ptr == &mons[PM_GHAST]) meatcorpse(mtmp); 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 -pruN slashem-0.0.7E7F1-official-release/src/monst.c slashem-0.0.7E7F1-pet-ranged/src/monst.c --- slashem-0.0.7E7F1-official-release/src/monst.c 2005-07-02 09:24:44.000000000 +0200 +++ slashem-0.0.7E7F1-pet-ranged/src/monst.c 2005-10-26 00:51:54.000000000 +0200 @@ -1003,7 +1003,7 @@ NEARDATA struct permonst mons[] = { M2_HOSTILE|M2_LORD|M2_MALE|M2_COLLECT, M3_INFRAVISIBLE|M3_INFRAVISION|M3_TRAITOR, 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, @@ -1183,7 +1183,7 @@ NEARDATA struct permonst mons[] = { M2_ORC|M2_STRONG|M2_GREEDY|M2_JEWELS|M2_COLLECT, M3_INFRAVISIBLE|M3_INFRAVISION|M3_TRAITOR, CLR_BLACK), MON("orc shaman", S_ORC, - LVL(3, 9, 5, 10, -5), (G_NOHELL|G_GENO|1), + LVL(5, 9, 5, 10, -5), (G_NOHELL|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, @@ -2479,7 +2479,7 @@ NEARDATA struct permonst mons[] = { 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_NOHELL|G_GENO|2), + LVL(5, 10, 4, 10, 0), (G_NOHELL|G_GENO|2), 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 -pruN slashem-0.0.7E7F1-official-release/src/monstr.c slashem-0.0.7E7F1-pet-ranged/src/monstr.c --- slashem-0.0.7E7F1-official-release/src/monstr.c 2005-10-25 19:54:29.000000000 +0200 +++ slashem-0.0.7E7F1-pet-ranged/src/monstr.c 2005-10-26 00:51:54.000000000 +0200 @@ -9,8 +9,8 @@ const int monstr[] = { 10, 3, 5, 6, 7, 7, 7, 7, 7, 14, 8, 8, 9, 11, 14, 8, 8, 11, 5, 2, 4, 4, 5, 6, 8, 6, 9, 17, 20, 18, 33, 3, 3, 3, 4, 5, 7, 7, 7, 9, 11, 5, 6, 7, 8, 10, 10, 14, - 1, 2, 3, 4, 4, 6, 8, 16, 4, 11, 8, 9, 11, 5, 5, 5, - 5, 11, 8, 15, 1, 3, 3, 4, 5, 5, 5, 7, 8, 11, 24, 11, + 1, 2, 3, 6, 4, 6, 8, 16, 4, 11, 8, 9, 11, 5, 5, 5, + 5, 11, 8, 15, 1, 3, 3, 4, 5, 5, 7, 7, 8, 11, 24, 11, 14, 4, 6, 9, 3, 4, 4, 6, 5, 6, 7, 7, 8, 8, 10, 9, 11, 13, 15, 22, 34, 31, 10, 1, 1, 1, 2, 4, 5, 2, 4, 4, 4, 7, 16, 3, 4, 5, 7, 7, 8, 12, 10, 15, 28, 31, 10, 13, @@ -20,7 +20,7 @@ const int monstr[] = { 28, 38, 49, 2, 3, 5, 4, 6, 7, 9, 9, 11, 10, 15, 19, 6, 8, 9, 5, 5, 6, 6, 5, 5, 5, 5, 5, 5, 5, 25, 25, 25, 27, 25, 25, 25, 25, 25, 26, 25, 24, 32, 9, 10, 10, 10, 10, 1, - 2, 2, 2, 2, 2, 5, 7, 8, 3, 3, 4, 5, 6, 6, 19, 6, + 2, 2, 2, 2, 2, 5, 7, 8, 3, 3, 4, 7, 6, 6, 19, 6, 20, 26, 33, 22, 10, 11, 12, 13, 15, 15, 23, 21, 20, 31, 51, 52, 20, 27, 3, 4, 5, 6, 14, 18, 21, 29, 4, 5, 6, 6, 7, 7, 8, 10, 11, 4, 4, 4, 4, 8, 10, 13, 16, 7, 9, 10, 11, 13, diff -pruN slashem-0.0.7E7F1-official-release/src/mplayer.c slashem-0.0.7E7F1-pet-ranged/src/mplayer.c --- slashem-0.0.7E7F1-official-release/src/mplayer.c 2005-07-02 09:24:44.000000000 +0200 +++ slashem-0.0.7E7F1-pet-ranged/src/mplayer.c 2005-10-26 00:51:54.000000000 +0200 @@ -260,7 +260,7 @@ register boolean special; 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 -pruN slashem-0.0.7E7F1-official-release/src/mthrowu.c slashem-0.0.7E7F1-pet-ranged/src/mthrowu.c --- slashem-0.0.7E7F1-official-release/src/mthrowu.c 2005-07-02 09:24:44.000000000 +0200 +++ slashem-0.0.7E7F1-pet-ranged/src/mthrowu.c 2005-10-26 00:51:54.000000000 +0200 @@ -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 monst *, struct obj *,BOOLEAN_P,int,int)); @@ -10,6 +11,8 @@ STATIC_DCL int FDECL(drop_throw,(struct #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 const char *breathwep[]; @@ -32,6 +35,13 @@ NEARDATA const char *breathwep[] = { "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) @@ -191,6 +201,7 @@ int ohitmon(mon, mtmp, otmp, range, verbose) struct monst *mon; /* monster thrower (if applicable) */ 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, */ @@ -200,15 +211,27 @@ boolean verbose; /* give message(s) eve 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(mon, otmp, 0, mtmp->mx, mtmp->my); @@ -240,7 +263,8 @@ boolean verbose; /* give message(s) eve 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)) { @@ -259,21 +283,21 @@ boolean verbose; /* give message(s) eve 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"); @@ -537,6 +561,130 @@ m_throw(mon, x, y, dx, dy, range, obj) } } +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 @@ -763,6 +911,58 @@ register struct attack *mattk; 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 @@ -837,6 +1037,56 @@ xchar ax, ay; } +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; @@ -857,6 +1107,13 @@ register xchar ax, ay, bx, by; } boolean +m_lined_up(mtarg, mtmp) +register struct monst *mtarg, *mtmp; +{ + return (linedup(mtarg->mx, mtarg->my, mtmp->mx, mtmp->my)); +} + +boolean lined_up(mtmp) /* is mtmp in position to use ranged attack? */ register struct monst *mtmp; { diff -pruN slashem-0.0.7E7F1-official-release/src/muse.c slashem-0.0.7E7F1-pet-ranged/src/muse.c --- slashem-0.0.7E7F1-official-release/src/muse.c 2005-07-02 09:24:44.000000000 +0200 +++ slashem-0.0.7E7F1-pet-ranged/src/muse.c 2005-10-26 00:51:54.000000000 +0200 @@ -26,7 +26,6 @@ STATIC_DCL int FDECL(precheck, (struct m 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 *)); @@ -240,11 +239,12 @@ struct obj *otmp; #define MUSE_UNICORN_HORN 17 #define MUSE_POT_FULL_HEALING 18 #define MUSE_LIZARD_CORPSE 19 +#define MUSE_SPE_TELEPORT_AWAY 20 /* [Tom] my new items... */ -#define MUSE_WAN_HEALING 20 -#define MUSE_WAN_EXTRA_HEALING 21 -#define MUSE_WAN_CREATE_HORDE 22 -#define MUSE_POT_VAMPIRE_BLOOD 23 +#define MUSE_WAN_HEALING 21 +#define MUSE_WAN_EXTRA_HEALING 22 +#define MUSE_WAN_CREATE_HORDE 23 +#define MUSE_POT_VAMPIRE_BLOOD 24 /* #define MUSE_INNATE_TPT 9999 * We cannot use this. Since monsters get unlimited teleportation, if they @@ -367,6 +367,11 @@ struct monst *mtmp; } 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) @@ -410,6 +415,9 @@ struct monst *mtmp; } } + if (cancast(mtmp, SPE_TELEPORT_AWAY)) + m.has_defense = MUSE_SPE_TELEPORT_AWAY; + if (nohands(mtmp->data)) /* can't use objects */ goto botm; @@ -442,6 +450,7 @@ struct monst *mtmp; 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; @@ -464,6 +473,7 @@ struct monst *mtmp; } 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 @@ -552,6 +562,7 @@ struct monst *mtmp; m.has_defense = MUSE_SCR_CREATE_MONSTER; } } + botm: return((boolean)(!!m.has_defense)); #undef nomore } @@ -605,6 +616,9 @@ struct monst *mtmp; 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; @@ -1216,13 +1230,14 @@ struct monst *mtmp; #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) { @@ -1230,19 +1245,42 @@ register struct obj *otmp; 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", FALSE); + } 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)) { @@ -1250,19 +1288,20 @@ register struct obj *otmp; 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 */ @@ -1320,18 +1359,31 @@ register struct obj *otmp; 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; @@ -1340,8 +1392,14 @@ struct obj *obj; /* 2nd arg to fhitm/f bhitpos.x = mon->mx; bhitpos.y = mon->my; - u.dx = ddx = sgn(mon->mux - mon->mx); - u.dy = ddy = sgn(mon->muy - mon->my); + + if (dx == -10) { + u.dx = ddx = sgn(mon->mux - mon->mx); + u.dy = ddy = sgn(mon->muy - mon->my); + } else { + ddx = dx; + ddy = dy; + } while(range-- > 0) { int x,y; @@ -1358,6 +1416,7 @@ struct obj *obj; /* 2nd arg to fhitm/f 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) { @@ -1391,6 +1450,7 @@ struct obj *obj; /* 2nd arg to fhitm/f 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 @@ -1935,7 +1995,10 @@ skipmsg: 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.", @@ -1953,7 +2016,8 @@ skipmsg: 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); @@ -1961,7 +2025,9 @@ skipmsg: 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: @@ -2337,6 +2403,21 @@ boolean by_you; 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; + } + } return FALSE; } diff -pruN slashem-0.0.7E7F1-official-release/src/potion.c slashem-0.0.7E7F1-pet-ranged/src/potion.c --- slashem-0.0.7E7F1-official-release/src/potion.c 2005-07-02 09:24:44.000000000 +0200 +++ slashem-0.0.7E7F1-pet-ranged/src/potion.c 2005-10-26 00:51:54.000000000 +0200 @@ -1298,7 +1298,9 @@ boolean your_fault; 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 */ @@ -1318,7 +1320,9 @@ boolean your_fault; 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 -pruN slashem-0.0.7E7F1-official-release/src/priest.c slashem-0.0.7E7F1-pet-ranged/src/priest.c --- slashem-0.0.7E7F1-official-release/src/priest.c 2005-07-02 09:24:44.000000000 +0200 +++ slashem-0.0.7E7F1-pet-ranged/src/priest.c 2005-10-26 00:51:54.000000000 +0200 @@ -55,7 +55,7 @@ register xchar omx,omy,gx,gy; 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; iowornmask & W_ARM)) { obj_extract_self(obj); obfree(obj, (struct obj *)0); diff -pruN slashem-0.0.7E7F1-official-release/src/region.c slashem-0.0.7E7F1-pet-ranged/src/region.c --- slashem-0.0.7E7F1-official-release/src/region.c 2005-07-02 09:24:44.000000000 +0200 +++ slashem-0.0.7E7F1-pet-ranged/src/region.c 2005-10-26 00:51:54.000000000 +0200 @@ -18,6 +18,7 @@ static int max_regions = 0; #define NO_CALLBACK (-1) boolean FDECL(inside_gas_cloud, (genericptr,genericptr)); +boolean FDECL(inside_acid_cloud, (genericptr,genericptr)); boolean FDECL(expire_gas_cloud, (genericptr,genericptr)); boolean FDECL(revive_cthulhu, (genericptr, genericptr)); boolean FDECL(inside_rect, (NhRect *,int,int)); @@ -48,10 +49,12 @@ static void FDECL(reset_region_mids, (Nh static callback_proc callbacks[] = { #define INSIDE_GAS_CLOUD 0 - inside_gas_cloud, -#define EXPIRE_GAS_CLOUD 1 - expire_gas_cloud, -#define REVIVE_CTHULHU 2 /* Cthulhu comes back... */ + inside_gas_cloud, +#define INSIDE_ACID_CLOUD 1 + inside_acid_cloud, +#define EXPIRE_GAS_CLOUD 2 + expire_gas_cloud, +#define REVIVE_CTHULHU 3 /* Cthulhu comes back... */ revive_cthulhu }; @@ -997,6 +1000,61 @@ int damage; return cloud; } +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; @@ -1032,4 +1090,15 @@ int damage; 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; +} + /*region.c*/ diff -pruN slashem-0.0.7E7F1-official-release/src/save.c slashem-0.0.7E7F1-pet-ranged/src/save.c --- slashem-0.0.7E7F1-official-release/src/save.c 2005-07-02 09:24:44.000000000 +0200 +++ slashem-0.0.7E7F1-pet-ranged/src/save.c 2005-10-26 00:51:54.000000000 +0200 @@ -258,7 +258,7 @@ dosave0() } #endif /* MFLOPPY */ - store_version(fd); + store_version(fd, FALSE); #ifdef STORE_PLNAME_IN_FILE bwrite(fd, (genericptr_t) plname, PL_NSIZ); #endif @@ -446,7 +446,7 @@ savestateinlock() (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 -pruN slashem-0.0.7E7F1-official-release/src/shknam.c slashem-0.0.7E7F1-pet-ranged/src/shknam.c --- slashem-0.0.7E7F1-official-release/src/shknam.c 2005-07-02 09:24:44.000000000 +0200 +++ slashem-0.0.7E7F1-pet-ranged/src/shknam.c 2005-10-26 00:51:54.000000000 +0200 @@ -531,7 +531,7 @@ struct mkroom *sroom; otmp = mksobj(AMULET_OF_LIFE_SAVING, FALSE, FALSE); mpickobj(shk, otmp); /* wear armor and amulet */ - m_dowear(shk, TRUE); + m_dowear(shk, TRUE, FALSE); otmp = mksobj(SKELETON_KEY, FALSE, FALSE); mpickobj(shk, otmp); } diff -pruN slashem-0.0.7E7F1-official-release/src/spell.c slashem-0.0.7E7F1-pet-ranged/src/spell.c --- slashem-0.0.7E7F1-official-release/src/spell.c 2005-07-02 09:24:44.000000000 +0200 +++ slashem-0.0.7E7F1-pet-ranged/src/spell.c 2005-10-26 00:51:54.000000000 +0200 @@ -50,7 +50,6 @@ STATIC_DCL int FDECL(percent_success, (i 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(). @@ -1277,7 +1276,7 @@ int *spell_no; } /* Integer square root function without using floating point. */ -STATIC_OVL int +int isqrt(val) int val; { diff -pruN slashem-0.0.7E7F1-official-release/src/steal.c slashem-0.0.7E7F1-pet-ranged/src/steal.c --- slashem-0.0.7E7F1-official-release/src/steal.c 2005-07-02 09:24:44.000000000 +0200 +++ slashem-0.0.7E7F1-pet-ranged/src/steal.c 2005-10-26 00:51:54.000000000 +0200 @@ -589,6 +589,60 @@ struct monst *mon; } } +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) @@ -599,11 +653,16 @@ boolean is_pet; /* If true, pet should 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; @@ -625,6 +684,23 @@ boolean is_pet; /* If true, pet should continue; } } + /* 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; + } + } + mdrop_obj(mtmp, otmp, is_pet && flags.verbose); } diff -pruN slashem-0.0.7E7F1-official-release/src/timeout.c slashem-0.0.7E7F1-pet-ranged/src/timeout.c --- slashem-0.0.7E7F1-official-release/src/timeout.c 2005-07-02 09:24:44.000000000 +0200 +++ slashem-0.0.7E7F1-pet-ranged/src/timeout.c 2005-10-26 00:51:54.000000000 +0200 @@ -1733,6 +1733,114 @@ long adj; accelerate_timer(BURN_OBJECT, obj, adj); } +/* 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() { @@ -1868,7 +1976,10 @@ static const ttable timeout_funcs[NUM_TI 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"), TTAB(unpoly_mon, (timeout_proc)0, "unpoly_mon"), #ifdef FIREARMS TTAB(bomb_blow, (timeout_proc)0, "bomb_blow"), @@ -2091,6 +2202,33 @@ obj_split_timers(src, dest) } } +/* + * 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 diff -pruN slashem-0.0.7E7F1-official-release/src/trap.c slashem-0.0.7E7F1-pet-ranged/src/trap.c --- slashem-0.0.7E7F1-official-release/src/trap.c 2005-07-02 09:24:44.000000000 +0200 +++ slashem-0.0.7E7F1-pet-ranged/src/trap.c 2005-10-26 00:51:54.000000000 +0200 @@ -484,7 +484,7 @@ int *fail_reason; 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 */ @@ -1684,6 +1684,42 @@ register int n; } +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; @@ -1695,6 +1731,11 @@ register struct monst *mtmp; 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 -pruN slashem-0.0.7E7F1-official-release/src/version.c slashem-0.0.7E7F1-pet-ranged/src/version.c --- slashem-0.0.7E7F1-official-release/src/version.c 2005-07-02 09:24:44.000000000 +0200 +++ slashem-0.0.7E7F1-pet-ranged/src/version.c 2005-10-26 00:51:54.000000000 +0200 @@ -126,8 +126,9 @@ const char *name; } void -store_version(fd) +store_version(fd, bones) int fd; +boolean bones; { const static struct version_info version_data = { VERSION_NUMBER, VERSION_FEATURES, @@ -135,8 +136,17 @@ int fd; }; 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 -pruN slashem-0.0.7E7F1-official-release/src/worn.c slashem-0.0.7E7F1-pet-ranged/src/worn.c --- slashem-0.0.7E7F1-official-release/src/worn.c 2005-07-02 09:24:44.000000000 +0200 +++ slashem-0.0.7E7F1-pet-ranged/src/worn.c 2005-10-26 00:51:54.000000000 +0200 @@ -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 { @@ -146,10 +147,11 @@ register struct obj *obj; } 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 */ @@ -176,6 +178,7 @@ struct obj *obj; /* item to make known i 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: @@ -196,7 +199,7 @@ struct obj *obj; /* item to make known i 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; @@ -223,6 +226,20 @@ struct obj *obj; /* item to make known i } } +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) @@ -260,8 +277,16 @@ boolean on, silently; 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 */ @@ -291,6 +316,21 @@ boolean on, silently; 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: @@ -305,11 +345,7 @@ boolean on, silently; 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; @@ -363,7 +399,8 @@ register struct monst *mon; * 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 @@ -373,9 +410,9 @@ register struct monst *mon; * 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 @@ -390,41 +427,45 @@ boolean creation; (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)); @@ -484,6 +525,12 @@ boolean racialexception; 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 @@ -510,8 +557,11 @@ outer_break: 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) @@ -529,6 +579,86 @@ outer_break: } #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) struct monst *mon; diff -pruN slashem-0.0.7E7F1-official-release/src/zap.c slashem-0.0.7E7F1-pet-ranged/src/zap.c --- slashem-0.0.7E7F1-official-release/src/zap.c 2005-07-02 09:24:44.000000000 +0200 +++ slashem-0.0.7E7F1-pet-ranged/src/zap.c 2005-10-26 00:51:54.000000000 +0200 @@ -174,7 +174,8 @@ struct obj *otmp; 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)); @@ -186,7 +187,8 @@ struct obj *otmp; 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 SPE_TURN_UNDEAD: @@ -250,7 +252,7 @@ struct obj *otmp; /* 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); @@ -2591,6 +2593,10 @@ boolean ordinary; (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); @@ -3853,6 +3859,16 @@ int type; 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); +} + /* #endif */ /* type == 0 to 9 : you shooting a wand */ /* type == 10 to 19 : you casting a spell */ @@ -3864,10 +3880,11 @@ int type; /* 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; struct rm *lev; @@ -4097,7 +4114,8 @@ register int dx,dy; } else { if (!otmp) { /* normal non-fatal hit */ - hit(fltxt, mon, exclam(tmp)); + if (say || canseemon(mon)) + hit(fltxt, mon, exclam(tmp)); if (mblamed && mblamed != mon && !DEADMONSTER(mblamed) && mon->movement >= NORMAL_SPEED && rn2(4)) { @@ -4119,7 +4137,8 @@ register int dx,dy; } 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 -pruN slashem-0.0.7E7F1-official-release/util/makedefs.c slashem-0.0.7E7F1-pet-ranged/util/makedefs.c --- slashem-0.0.7E7F1-official-release/util/makedefs.c 2005-07-02 09:24:44.000000000 +0200 +++ slashem-0.0.7E7F1-pet-ranged/util/makedefs.c 2005-10-26 00:51:54.000000000 +0200 @@ -442,9 +442,23 @@ do_rumors() * 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 @@ -525,6 +539,9 @@ make_version() #ifdef SCORE_ON_BOTL | (1L << 21) #endif +#ifdef MONSTER_TIMERS + | (1L << 24) +#endif /* data format [COMPRESS excluded] (27..31) */ #ifdef ZEROCOMP | (1L << 27) @@ -635,6 +652,10 @@ do_date() 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", @@ -885,6 +906,9 @@ static const char *build_opts[] = { #ifdef WALLIFIED_MAZE "walled mazes", #endif +#ifdef MONSTER_TIMERS + "timers on monsters", +#endif #ifdef YEOMAN "yeomen", #endif