diff -c -r sl_orig/include/extern.h sl_petbr/include/extern.h *** sl_orig/include/extern.h Fri Mar 1 02:26:26 2002 --- sl_petbr/include/extern.h Sat Mar 2 01:24:00 2002 *************** *** 432,437 **** --- 432,438 ---- 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 boolean FDECL(has_adjacent_enemy, (struct monst *, int, int, BOOLEAN_P)); #ifdef USE_TRAMPOLI E void FDECL(wantdoor, (int,int,genericptr_t)); #endif *************** *** 952,957 **** --- 953,959 ---- /* ### mcastu.c ### */ E int FDECL(castmu, (struct monst *,struct attack *)); + E int FDECL(castmm, (struct monst *,struct attack *,struct monst *)); E int FDECL(buzzmu, (struct monst *,struct attack *)); /* ### mhitm.c ### */ *************** *** 1270,1277 **** --- 1272,1282 ---- E int FDECL(thitu, (int,int,struct obj *,const char *)); E int FDECL(ohitmon, (struct monst *,struct monst *,struct obj *,int,BOOLEAN_P)); E void FDECL(thrwmu, (struct monst *)); + E int FDECL(thrwmm, (struct monst *, struct monst *)); E int FDECL(spitmu, (struct monst *,struct attack *)); + E int FDECL(spitmm, (struct monst *,struct attack *, struct monst *)); E int FDECL(breamu, (struct monst *,struct attack *)); + 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 *)); *************** *** 1867,1872 **** --- 1872,1879 ---- #ifdef USE_TRAMPOLI E int NDECL(stealarm); #endif + E boolean FDECL(keep_ammo, (struct monst *, struct obj *, struct obj *)); + E boolean FDECL(keep_launcher, (struct monst *, struct obj *, struct obj *)); E long NDECL(somegold); E void FDECL(stealgold, (struct monst *)); E void FDECL(remove_worn_item, (struct obj *)); *************** *** 2351,2356 **** --- 2358,2364 ---- E struct monst *FDECL(boomhit, (int,int)); E int FDECL(burn_floor_paper, (int,int,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 -c -r sl_orig/src/dogmove.c sl_petbr/src/dogmove.c *** sl_orig/src/dogmove.c Fri Mar 1 02:26:32 2002 --- sl_petbr/src/dogmove.c Sat Mar 2 01:24:00 2002 *************** *** 21,26 **** --- 21,33 ---- extern boolean notonhead; + /* true if the pet perceives its master in need of assistance */ + static boolean master_in_trouble = 0; + static struct monst *near_enemy = 0; + + #define u_in_trouble() (u.uhp < 5 || (u.uhp < 25 && u.uhp < u.uhpmax / 4)) + #define distum(mon) (distu((mon)->mx, (mon)->my)) + #ifdef OVL0 STATIC_DCL boolean FDECL(dog_hunger,(struct monst *,struct edog *)); *************** *** 34,44 **** register struct monst *mon; { register struct obj *obj; ! struct obj *wep = MON_WEP(mon); boolean item1 = FALSE, item2 = FALSE; ! if (is_animal(mon->data) || mindless(mon->data)) item1 = item2 = TRUE; if (!tunnels(mon->data) || !needspick(mon->data)) item1 = TRUE; for(obj = mon->minvent; obj; obj = obj->nobj) { --- 41,58 ---- register struct monst *mon; { register struct obj *obj; ! 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) { *************** *** 51,57 **** item2 = TRUE; continue; } ! if (!obj->owornmask && obj != wep) return obj; } return (struct obj *)0; } --- 65,86 ---- item2 = TRUE; continue; } ! 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; } *************** *** 65,70 **** --- 94,104 ---- 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 *)); + STATIC_PTR int FDECL(find_friends, (struct monst *, struct monst *, int)); + STATIC_PTR struct monst *FDECL(adjacent_enemy, (struct monst *, int, int, boolean)); #ifdef OVLB STATIC_OVL boolean *************** *** 357,363 **** && obj->otyp != SCR_MAIL #endif ){ ! if (dogfood(mtmp, obj) <= CADAVER) return dog_eat(mtmp, obj, omx, omy, FALSE); /* [Tom] demonic & undead pets don't mind cursed items */ --- 391,399 ---- && obj->otyp != SCR_MAIL #endif ){ ! /* D: Verify that master is not in trouble before eating */ ! if (dogfood(mtmp, obj) <= CADAVER && ! (!master_in_trouble || !near_enemy)) return dog_eat(mtmp, obj, omx, omy, FALSE); /* [Tom] demonic & undead pets don't mind cursed items */ *************** *** 495,501 **** } /* follow player if appropriate */ ! if (gtyp == UNDEF || (gtyp != DOGFOOD && gtyp != APPORT && monstermoves < edog->hungrytime)) { gx = u.ux; gy = u.uy; --- 531,537 ---- } /* follow player if appropriate */ ! if (gtyp == UNDEF || master_in_trouble || (gtyp != DOGFOOD && gtyp != APPORT && monstermoves < edog->hungrytime)) { gx = u.ux; gy = u.uy; *************** *** 505,511 **** if (udist > 1) { if (!IS_ROOM(levl[u.ux][u.uy].typ) || !rn2(4) || whappr || ! (mtmp->minvent && rn2(edog->apport))) appr = 1; } /* if you have dog food it'll follow you more closely */ --- 541,547 ---- if (udist > 1) { if (!IS_ROOM(levl[u.ux][u.uy].typ) || !rn2(4) || whappr || ! (mtmp->minvent && edog && rn2(edog->apport))) appr = 1; } /* if you have dog food it'll follow you more closely */ *************** *** 519,524 **** --- 555,577 ---- obj = obj->nobj; } } + /* D: And if you're in trouble, it'll be keen on + * coming to help. + */ + if (master_in_trouble) { + appr = 1; + if (near_enemy) { + gx = near_enemy->mx; + gy = near_enemy->my; + } + /* If pet isn't too hungry, it'll even ignore food as it + * rushes to your aid. This ignoring of food only applies + * if you have immediately adjacent hostiles, though */ + if (edog && monstermoves < edog->hungrytime + 300 + && near_enemy) + gtyp = UNDEF; + } + } else appr = 1; /* gtyp != UNDEF */ if(mtmp->mconf) *************** *** 664,669 **** --- 717,735 ---- cursemsg[0] = FALSE; /* lint suppression */ info[0] = 0; /* ditto */ + /* D: The pet won't know its master's in trouble if it can't see + * him or if the pet is confused. If the pet's tameness is low, + * it won't care if its master's in trouble. + */ + if ((master_in_trouble = (couldsee(mtmp->mx, mtmp->my) && !mtmp->mconf + && mtmp->mcansee && haseyes(mtmp->data) + && (mtmp->mtame > rn2(10)) + && u_in_trouble()))) { + near_enemy = adjacent_enemy(mtmp, mtmp->mux, mtmp->muy, TRUE); + } + else + near_enemy = 0; + if (has_edog && !is_spell) { j = dog_invent(mtmp, edog, udist); if (j == 2) return 2; /* died */ *************** *** 785,790 **** --- 851,860 ---- register struct monst *mtmp2 = m_at(nx,ny); aligntyp align1, align2; /* For priests, minions */ boolean faith1 = TRUE, faith2 = TRUE; + /* Might mtmp2 be the cause of the master's trouble? */ + boolean cause_of_trouble = + master_in_trouble && !mtmp2->mpeaceful && + (distmin(mtmp2->mx, mtmp2->my, mtmp->mux, mtmp->muy) == 1); if (mtmp->isminion) align1 = EMIN(mtmp)->min_align; else if (mtmp->ispriest) align1 = EPRI(mtmp)->shralign; *************** *** 793,803 **** else if (mtmp2->ispriest) align2 = EPRI(mtmp2)->shralign; /* MAR */ else faith2 = FALSE; /* Mindless monsters and spelled monsters have no fear of ! * attacking higher level monsters */ ! if (((int)mtmp2->m_lev >= (int)mtmp->m_lev+2 && !is_spell && ! !mindless(mtmp->data)) || (mtmp2->data == &mons[PM_FLOATING_EYE] && rn2(10) && mtmp->mcansee && haseyes(mtmp->data) && mtmp2->mcansee && (perceives(mtmp->data) || !mtmp2->minvis)) || --- 863,882 ---- else if (mtmp2->ispriest) align2 = EPRI(mtmp2)->shralign; /* MAR */ else faith2 = FALSE; + /* Mindless monsters and spelled monsters have no fear of ! * attacking higher level monsters. ! * D: If the master's in trouble, a pet may risk its life to save ! * him (tameness is factored in when calculating ! * master_in_trouble). */ ! if (((int)mtmp2->m_lev >= (int)mtmp->m_lev+(cause_of_trouble? 12:2) ! && !is_spell && !mindless(mtmp->data)) || ! /* D: If master's in trouble, this monster is not the cause of the trouble ! * and this monster is not between pet and master, ignore it - this ! * should stop the pet from fighting its own battles when it should be ! * running to help the player */ ! (master_in_trouble && !cause_of_trouble && distum(mtmp) < distum(mtmp2)) || (mtmp2->data == &mons[PM_FLOATING_EYE] && rn2(10) && mtmp->mcansee && haseyes(mtmp->data) && mtmp2->mcansee && (perceives(mtmp->data) || !mtmp2->minvis)) || *************** *** 851,858 **** } else /* 1/40 chance of stepping on it anyway, in case * it has to pass one to follow the player... */ ! if (trap->tseen && rn2(40)) continue; } } --- 930,939 ---- } else /* 1/40 chance of stepping on it anyway, in case * it has to pass one to follow the player... + * D: If master's in trouble, the pet may ignore + * its normal instincts of self-preservation. */ ! if (trap->tseen && rn2(master_in_trouble? 5 : 40)) continue; } } *************** *** 872,879 **** nix = nx; niy = ny; chi = i; ! do_eat = TRUE; ! cursemsg[i] = FALSE; /* not reluctant */ goto newdogpos; } } --- 953,969 ---- nix = nx; niy = ny; chi = i; ! ! /* D: Suppress eating if pet isn't too hungry, master's ! * in trouble and there are hostiles adjacent to the ! * master. ! */ ! if (!master_in_trouble ! || (monstermoves >= edog->hungrytime + 300) ! || (!near_enemy)) { ! do_eat = TRUE; ! cursemsg[i] = FALSE; /* not reluctant */ ! } goto newdogpos; } } *************** *** 881,887 **** /* didn't find something to eat; if we saw a cursed item and aren't being forced to walk on it, usually keep looking */ if (cursemsg[i] && !mtmp->mleashed && uncursedcnt > 0 && ! rn2(13 * uncursedcnt)) continue; /* lessen the chance of backtracking to previous position(s) */ k = (has_edog && !is_spell) ? uncursedcnt : cnt; --- 971,977 ---- /* didn't find something to eat; if we saw a cursed item and aren't being forced to walk on it, usually keep looking */ if (cursemsg[i] && !mtmp->mleashed && uncursedcnt > 0 && ! rn2(13 * uncursedcnt) && !master_in_trouble) continue; /* lessen the chance of backtracking to previous position(s) */ k = (has_edog && !is_spell) ? uncursedcnt : cnt; *************** *** 903,908 **** --- 993,1056 ---- } 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 && !mtmp->isspell) { + 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->mlstmv != monstermoves && + 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; *************** *** 926,931 **** --- 1074,1080 ---- if (mon_wield_item(mtmp)) return 0; } + /* insert a worm_move() if worms ever begin to eat things */ remove_monster(omx, omy); place_monster(mtmp, nix, niy); *************** *** 978,983 **** --- 1127,1387 ---- #endif /* OVL0 */ #ifdef OVLB + 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; + + /* Is master in trouble and adjacent to this monster? */ + if (master_in_trouble + && distmin(mtmp->mux, mtmp->muy, mtarg->mx, mtarg->my) == 1) { + /* Verify that the monster is hostile and boost the score + * if it is. The monster's level will still be added, so + * hopefully the pet will choose the nastiest monster + * it's lined up with to attack. Also, the tamer the + * pet, the more likely it is to act. + */ + if (!mtarg->mpeaceful && mtmp->mtame > rn2(20)) + score += 700; + } + + /* 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; + } + + STATIC_PTR 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; + } + /*ARGSUSED*/ /* do_clear_area client */ STATIC_PTR void wantdoor(x, y, distance) *************** *** 993,998 **** --- 1397,1449 ---- } } + /* Find the first hostile monster adjacent to the location */ + STATIC_PTR 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); + } + + #endif /* OVLB */ /*dogmove.c*/ diff -c -r sl_orig/src/mcastu.c sl_petbr/src/mcastu.c *** sl_orig/src/mcastu.c Fri Mar 1 02:26:36 2002 --- sl_petbr/src/mcastu.c Sat Mar 2 01:24:00 2002 *************** *** 468,473 **** --- 468,1008 ---- return(1); } + STATIC_OVL + boolean + resists_attk(mdef, magr, dbias, abias) + /* No null checks on these */ + struct monst *mdef, *magr; + /* Biases should not cause overflow on int multiply */ + int dbias, /* Bias towards defender */ + abias; /* Bias towards aggressor */ + { + 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 * abias - dlevel * dbias; + return mdef->data->mr > (chance > 0? rn2(chance) : 0); + } + + int + castmm(mtmp, mattk, mdef) /* monster casts spell at monster */ + register struct monst *mtmp, *mdef; + register struct attack *mattk; + { + int dmg, ml = mtmp->m_lev, ml2 = (ml > 0? rn2(ml) : 0); + int spellev, chance, difficulty, splcaster, learning; + boolean sees_aggr = canseemon(mtmp), + sees_def = canseemon(mdef); + + /* Casting level is limited by available energy */ + if (mattk->adtyp == AD_SPEL || mattk->adtyp == AD_CLRC) { + if (ml > 1) ml = rn2(ml); + /* D: Instead of doing another rn2() farther along, do + * it here - not too sure if this really serves a + * purpose (except to shift the probability downwards) */ + if (ml > 1) ml = rn2(ml); + } + + spellev = (ml / 7) + 1; + if (spellev > 10) spellev = 10; + + if ((spellev * 5) > mtmp->m_en) { + spellev = (mtmp->m_en / 5); + ml = (spellev - 1) * 7 + 1; + } + + if(mtmp->mcan || (mtmp->m_en < 5) || mtmp->mspec_used || !ml) { /* could not attack */ + /* D: Not much point in the curse text for monster-monster combat */ + /* cursetxt(mtmp); */ + return(0); + } else { + nomul(0); + + mtmp->m_en -= spellev * 5; /* Use up the energy now */ + + /* We should probably do similar checks to what is done for + * the player - armor, etc. + * Checks for armour and other intrinsic ability change splcaster + * Difficulty and experience affect chance + * Assume that monsters only cast spells that they know well + */ + splcaster = 15 - (mtmp->m_lev / 2); /* Base for a wizard is 5...*/ + + if (splcaster < 5) splcaster = 5; + if (splcaster > 20) splcaster = 20; + + chance = 11 * (mtmp->m_lev > 25 ? 18 : (12 + (mtmp->m_lev / 5))); + chance++ ; /* Minimum chance of 1 */ + + difficulty = (spellev - 1) * 4 - (mtmp->m_lev - 1); + /* law of diminishing returns sets in quickly for + * low-level spells. That is, higher levels quickly + * result in almost no gain + */ + learning = 15 * (-difficulty / spellev); + chance += learning > 20 ? 20 : learning; + + /* clamp the chance */ + if (chance < 0) chance = 0; + if (chance > 120) chance = 120; + + /* combine */ + chance = chance * (20-splcaster) / 15 - splcaster; + + /* Clamp to percentile */ + if (chance > 100) chance = 100; + if (chance < 0) chance = 0; + + #if 0 + if(rn2(ml*10) < (mtmp->mconf ? 100 : 20)) { /* fumbled attack */ + #endif + if (mtmp->mconf || rnd(100) > chance) { /* 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)((ml2/3) + mattk->damn), (int)mattk->damd); + else dmg = d((int)((ml2/3) + 1), 6); + + switch(mattk->adtyp) { + case AD_FIRE: + if (sees_def) + pline("%s is enveloped in flames.", Monnam(mdef)); + if(resists_fire(mdef)) { + if (sees_def) { + shieldeff(mdef->mx, mdef->my); + pline("But %s resists the effects.", + he[pronoun_gender(mdef)]); + } + dmg = 0; + } + break; + case AD_COLD: + if (sees_def) + pline("%s is covered in frost.", Monnam(mdef)); + if(resists_cold(mdef)) { + if (sees_def) { + shieldeff(mdef->mx, mdef->my); + pline("But %s resists the effects.", + he[pronoun_gender(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)) { + if (sees_def) { + shieldeff(mdef->mx, mdef->my); + pline_The("missiles bounce off!"); + } + dmg = 0; + } + break; + case AD_SPEL: /* random spell */ + /* Still using mspec_used, just so monsters don't go + * bonkers with their spells + */ + if (rn2(15) > mtmp->m_lev) mtmp->mspec_used = 2; + switch(ml) { + case 22: + case 21: + if (sees_aggr) + pline("%s is using the touch of death!", + Monnam(mtmp)); + dmg = 0; + if (mdef->data == &mons[PM_DEATH]) { + if (mdef->mhp < mdef->mhpmax) { + mdef->mhp = mdef->mhpmax; + if (sees_def) + pline("%s seems in marvellous health!", + Monnam(mdef)); + } else if (sees_def) + pline("%s seems unaffected.", Monnam(mdef)); + } else 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) || + mdef->data->mresists & MR_DEATH)) { + dmg = mdef->mhp + 1; + } else { + if (resists_magm(mdef) && sees_def) + shieldeff(mdef->mx, mdef->my); + if (sees_def) + pline("That didn't work..."); + } + break; + case 20: + if ((levl[mdef->mx][mdef->my].typ == ROOM || + levl[mdef->mx][mdef->my].typ == CORR)) { + if (sees_def) + pline("A pool appears beneath %s!", + mon_nam(mdef)); + dmg = 0; + levl[mdef->mx][mdef->my].typ = POOL; + del_engr_at(mdef->mx, mdef->my); + water_damage(level.objects[mdef->mx][mdef->my], FALSE, TRUE); + if (minwater(mdef)) + return (1); + break; + } /* Fall through */ + case 19: + case 18: + case 17: /* D: Magical fisticuffs */ + if (canseemon(mdef)) { + pline("%s is hammered by invisible forces!", + Monnam(mdef)); + } + dmg = d(8, 8); + if (resists_attk(mdef, mtmp, 1, 1)) { + /* D: Nonstandard, but this whole section is + * nonstandard anyways */ + dmg /= 4; + + if (sees_def && flags.verbose && dmg < mdef->mhp) { + shieldeff(mdef->mx, mdef->my); + pline("%s resists!", Monnam(mdef)); + } + } + break; + case 16: + case 15: + case 14: + /* ye old DnD "call lightning"... */ + dmg = rn2(8)+rn2(8)+rn2(8)+rn2(8)+4; + + /* WAC add lightning strike effect */ + if (sees_def) { + zap_strike_fx(mdef->mx,mdef->my,(AD_ELEC-1)); + pline("%s is struck by a thunderbolt!", + Monnam(mdef)); + } + if(resists_elec(mdef)) { + if (sees_def) { + shieldeff(mdef->mx, mdef->my); + pline("But %s is unharmed!", mon_nam(mdef)); + } + dmg = 0; + } + if (!resists_blnd(mdef) && dmg < mdef->mhp) { + register unsigned rnd_tmp = rnd(50); + + if (mdef->mcansee && sees_def) + pline("%s is blinded by the flash!", + Monnam(mdef)); + mdef->mcansee = 0; + if((mdef->mblinded + rnd_tmp) > 127) + mdef->mblinded = 127; + else mdef->mblinded += rnd_tmp; + } + break; + case 13: + case 12: + case 11: + /* ye old DnD flame strike... */ + dmg = rn2(8)+rn2(8)+rn2(8)+rn2(8)+4; + if (sees_def) + pline("A pillar of flame erupts beneath %s!", + mon_nam(mdef)); + if(resists_fire(mdef)) { + if (sees_def) { + shieldeff(mdef->mx, mdef->my); + pline("But %s is unharmed!", mon_nam(mdef)); + } + dmg = 0; + } + break; + case 10: /* Drain level */ + case 9: + case 8: + /* 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, 1, 1)) { + /* 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; + } /* Fall through */ + case 7: + case 6: /* Hurt */ + dmg = 0; + if (!resists_magm(mdef)) { + if (sees_def) + pline("%s is battered!", Monnam(mdef)); + if (mtmp->m_lev > 0) + dmg = d(2, mtmp->m_lev); + else + dmg = rnd(5); + if (resists_attk(mdef, mtmp, 1, 1)) { + dmg /= 2; + if (sees_def && flags.verbose && + dmg < mdef->mhp) { + shieldeff(mdef->mx, mdef->my); + pline("%s resists!", Monnam(mdef)); + } + } + } + break; + case 5: /* make invisible if not */ + case 4: + if (!mtmp->minvis && !mtmp->invis_blkd) { + if(sees_aggr && !See_invisible) + pline("%s suddenly disappears!", Monnam(mtmp)); + mon_set_minvis(mtmp); + dmg = 0; + break; + } /* else fall into the next case */ + case 3: /* stun */ + dmg = 0; + if (!resists_magm(mdef) && + !resists_attk(mdef, mtmp, 1, 1)) { + if (sees_def) + pline("%s reels...", Monnam(mdef)); + dmg = d(1, 10); + mdef->mstun = 1; + } + break; + case 2: /* haste self */ + mon_adjust_speed(mtmp, 1); + dmg = 0; + break; + case 1: /* cure self */ + if(mtmp->mhp < mtmp->mhpmax) { + if((mtmp->mhp += rnd(8)) > mtmp->mhpmax) + mtmp->mhp = mtmp->mhpmax; + dmg = 0; + break; + } /* else fall through to default case */ + default: /* psi bolt */ + if (resists_magm(mdef)) { + if (sees_def) { + shieldeff(mdef->mx, mdef->my); + pline("%s looks slightly uncomfortable.", + Monnam(mdef)); + } + dmg = 1; + } else { + if (dmg > 0) { + if (sees_def) + pline("%s staggers!", Monnam(mdef)); + if (resists_attk(mdef, mtmp, 1, 1)) + dmg /= 2; + } + } + break; + } + break; + + case AD_CLRC: /* clerical spell */ + + mtmp->mspec_used = rn2(15) - mtmp->m_lev; + if (mtmp->mspec_used < 1) mtmp->mspec_used = 0; + + switch(ml) { + /* Other ideas: lightning bolts, towers of flame, + gush of water -3. */ + default: /* confuse */ + if(!resists_magm(mdef) && + !resists_attk(mdef, mtmp, 1, 1)) { + if (sees_def) + pline("%s looks confused.", Monnam(mdef)); + dmg = (int)mtmp->m_lev; + mdef->mconf = 1; + } + break; + case 13: + if ((levl[mdef->mx][mdef->my].typ == ROOM || + levl[mdef->mx][mdef->my].typ == CORR)) { + if (sees_def) + pline("A pool appears beneath %s!", + mon_nam(mdef)); + dmg = 0; + levl[mdef->mx][mdef->my].typ = POOL; + del_engr_at(mdef->mx, mdef->my); + water_damage(level.objects[mdef->mx][mdef->my], FALSE, TRUE); + if (minwater(mdef)) + return (1); + break; + } /* Fall through */ + case 12: + /* ye old DnD "call lightning"... */ + dmg = rn2(8)+rn2(8)+rn2(8)+rn2(8)+4; + + /* WAC add lightning strike effect */ + if (sees_def) { + zap_strike_fx(mdef->mx,mdef->my,(AD_ELEC-1)); + pline("%s is struck by a thunderbolt!", + Monnam(mdef)); + } + if(resists_elec(mdef)) { + if (sees_def) { + shieldeff(mdef->mx, mdef->my); + pline("But %s is unharmed!", mon_nam(mdef)); + } + dmg = 0; + } + if (!resists_blnd(mdef) && dmg < mdef->mhp) { + register unsigned rnd_tmp = rnd(50); + + if (mdef->mcansee && sees_def) + pline("%s is blinded by the flash!", + Monnam(mdef)); + mdef->mcansee = 0; + if((mdef->mblinded + rnd_tmp) > 127) + mdef->mblinded = 127; + else mdef->mblinded += rnd_tmp; + } + break; + case 11: + /* ye old DnD flame strike... */ + dmg = rn2(8)+rn2(8)+rn2(8)+rn2(8)+4; + if (sees_def) + pline("A pillar of flame erupts beneath %s!", + mon_nam(mdef)); + if(resists_fire(mdef)) { + if (sees_def) { + shieldeff(mdef->mx, mdef->my); + pline("But %s is unharmed!", mon_nam(mdef)); + } + dmg = 0; + } + break; + case 10: + case 9: /* blindness */ + /* note: resists_blnd() doesn't apply here */ + if (haseyes(mdef->data)) { + register unsigned rnd_tmp = rnd(50); + + if (mdef->mcansee && sees_def) + pline("Scales cover %s %s!", + s_suffix(mon_nam(mdef)), + 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 8: + if (!resists_magm(mdef) && !amorphous(mdef->data) + && !noncorporeal(mdef->data) + && !unsolid(mdef->data)) { + if (sees_def) + pline("%s is battered!", Monnam(mdef)); + if (mtmp->m_lev > 0) + dmg = d(2,8) + rn2(mtmp->m_lev); + if (resists_attk(mdef, mtmp, 1, 1)) { + dmg /= 2; + if (sees_def && flags.verbose + && dmg < mdef->mhp) { + shieldeff(mdef->mx, mdef->my); + pline("%s resists!", Monnam(mdef)); + } + } + break; + } /* Fall through */ + case 7: + case 6: /* Slow monster */ + if (mdef->permspeed != MSLOW) { + if (!resists_attk(mdef, mtmp, 1, 1)) { + mon_adjust_speed(mdef, -1); + if (sees_def) { + /* D: If defender is still fast, it must + * be speed-booted */ + if (mdef->mspeed == MFAST) + pline("%s quickness seems less natural.", + s_suffix(Monnam(mdef))); + else + pline("%s slows down!", Monnam(mdef)); + } + } + dmg = 0; + break; + } /* Fall through */ + case 5: + case 4: + case 3: /* hold */ + if(!resists_magm(mdef) + && !resists_attk(mdef, mtmp, 1, 1)) { + if (sees_def && mdef->mcanmove) + pline("%s is frozen in place!", Monnam(mdef)); + dmg = 4 + (int)mtmp->m_lev; + mdef->mcanmove = 0; + if (mdef->mfrozen < 20) + mdef->mfrozen += dmg; + } + dmg = 0; + break; + case 2: + case 1: /* cure self */ + if(mtmp->mhp < mtmp->mhpmax) { + if((mtmp->mhp += rnd(8)) > mtmp->mhpmax) + mtmp->mhp = mtmp->mhpmax; + dmg = 0; + } /* else fall through to default case */ + break; + } + } + if(dmg) { + if((mdef->mhp -= dmg) < 1) { + if (m_at(mdef->mx, mdef->my) == mtmp) { /* see gulpmm() */ + remove_monster(mdef->mx, mdef->my); + mdef->mhp = 1; /* otherwise place_monster will complain */ + place_monster(mdef, mdef->mx, mdef->my); + mdef->mhp = 0; + } + /* get experience from spell creatures */ + if (mtmp->uexp) mon_xkilled(mdef, "", (int)mattk->adtyp); + else monkilled(mdef, "", (int)mattk->adtyp); + + if (mdef->mhp > 0) return 0; /* mdef lifesaved */ + return (MM_DEF_DIED | + ((mtmp->mhp > 0 && grow_up(mtmp,mdef)) ? 0 : MM_AGR_DIED)); + } + } + + return(1); + } + #endif /* OVLB */ #ifdef OVL0 diff -c -r sl_orig/src/mhitm.c sl_petbr/src/mhitm.c *** sl_orig/src/mhitm.c Fri Mar 1 02:26:36 2002 --- sl_petbr/src/mhitm.c Sat Mar 2 01:24:00 2002 *************** *** 269,274 **** --- 269,285 ---- attk = 1; switch (mattk->aatyp) { case AT_WEAP: /* "hand to hand" attacks */ + if (distmin(magr->mx,magr->my,mdef->mx,mdef->my) > 1) { + /* D: Do a ranged attack here! */ + strike = thrwmm(magr, mdef); + if (DEADMONSTER(mdef)) + res[i] = MM_DEF_DIED; + + if (DEADMONSTER(magr)) + res[i] |= MM_AGR_DIED; + + break; + } if (magr->weapon_check == NEED_WEAPON || !MON_WEP(magr)) { magr->weapon_check = NEED_HTH_WEAPON; if (mon_wield_item(magr) != 0) return 0; *************** *** 290,296 **** case AT_TENT: /* Nymph that teleported away on first attack? */ if (distmin(magr->mx,magr->my,mdef->mx,mdef->my) > 1) ! return MM_MISS; /* Monsters won't attack cockatrices physically if they * have a weapon instead. This instinct doesn't work for * players, or under conflict or confusion. --- 301,310 ---- case AT_TENT: /* Nymph that teleported away on first attack? */ if (distmin(magr->mx,magr->my,mdef->mx,mdef->my) > 1) ! /*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. *************** *** 320,329 **** case AT_GAZE: strike = 0; /* will not wake up a sleeper */ ! res[i] = gazemm(magr, mdef, mattk); break; case AT_EXPL: strike = 1; /* automatic hit */ res[i] = explmm(magr, mdef, mattk); break; --- 334,348 ---- case AT_GAZE: strike = 0; /* will not wake up a sleeper */ ! /* D: Blinded monsters can't use gaze attacks */ ! if (magr->mcansee) ! res[i] = gazemm(magr, mdef, mattk); break; case AT_EXPL: + /* D: Prevent explosions from a distance */ + if (distmin(magr->mx,magr->my,mdef->mx,mdef->my) > 1) + continue; strike = 1; /* automatic hit */ res[i] = explmm(magr, mdef, mattk); break; *************** *** 335,340 **** --- 354,364 ---- 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 */ *************** *** 348,360 **** } break; default: /* no attack */ strike = 0; attk = 0; break; } ! if (attk && !(res[i] & MM_AGR_DIED)) res[i] = passivemm(magr, mdef, strike, res[i] & MM_DEF_DIED); if (res[i] & MM_DEF_DIED) return res[i]; --- 372,439 ---- } break; + case AT_BREA: + if (!monnear(magr, mdef->mx, mdef->my)) { + strike = breamm(magr, mattk, mdef); + + /* D: Is there a more scientific way to do this? */ + + /* D: I can't think of any monster that can be killed by + * its own breath, but we might as well guard against + * future nasties with fatal shortcomings + */ + if (DEADMONSTER(mdef)) + res[i] = MM_DEF_DIED; + + /* D: This is the only way I know to figure out whether + * the target died or not + */ + if (DEADMONSTER(magr)) + res[i] |= MM_AGR_DIED; + } + else + strike = 0; + break; + + case AT_SPIT: + if (!monnear(magr, mdef->mx, mdef->my)) { + strike = spitmm(magr, mattk, mdef); + + /* D: Is there a more scientific way to do this? */ + + if (DEADMONSTER(mdef)) + res[i] = MM_DEF_DIED; + + /* D: This is the only way I know to figure out whether + * the target died or not + */ + if (DEADMONSTER(magr)) + res[i] |= MM_AGR_DIED; + } + break; + + case AT_MAGC: + /* D: Pet arch-liches get their day in the sun! */ + /* D: castmm is shuffled around a bit from castmu */ + if (distmin(magr->mx,magr->my,mdef->mx,mdef->my) <= 1) { + strike = castmm(magr, mattk, mdef); + + 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) && ! 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 -c -r sl_orig/src/mthrowu.c sl_petbr/src/mthrowu.c *** sl_orig/src/mthrowu.c Fri Mar 1 02:26:38 2002 --- sl_petbr/src/mthrowu.c Sat Mar 2 01:58:46 2002 *************** *** 3,8 **** --- 3,9 ---- /* 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)); *************** *** 32,37 **** --- 33,42 ---- "strange breath #9" }; + /* D: The monster that's being shot at when one monster shoots at another */ + STATIC_OVL struct monst *target = 0; + + boolean FDECL(m_lined_up, (struct monst *, struct monst *)); int thitu(tlev, dam, obj, name) /* u is hit by sth, but not a monster */ *************** *** 194,199 **** --- 199,205 ---- 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, */ *************** *** 203,217 **** int damage, tmp; boolean vis, ismimic; int objgone = 1; 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); if (tmp < rnd(20)) { if (!ismimic) { if (vis) miss(distant_name(otmp, xname), mtmp); ! else if (verbose) pline("It is missed."); } if (!range) { /* Last position; object drops */ (void) drop_throw(mon, otmp, 0, mtmp->mx, mtmp->my); --- 209,235 ---- int damage, tmp; boolean vis, ismimic; int objgone = 1; + struct obj *mon_launcher = mon? MON_WEP(mon) : 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 (mon && target == mtmp) { + if (mon->m_lev > 5) + tmp += mon->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, xname), mtmp); ! else if (verbose && !target) pline("It is missed."); } if (!range) { /* Last position; object drops */ (void) drop_throw(mon, otmp, 0, mtmp->mx, mtmp->my); *************** *** 225,230 **** --- 243,253 ---- return 1; } else { damage = dmgval(otmp, mtmp); + + /* D: Add on artifact launcher damage bonus, if any */ + if (mon_launcher && mon_launcher->oartifact) + damage += spec_dbon(mon_launcher, mtmp, damage); + # ifdef P_SPOON if (otmp->otyp == SPOON) { pline("The spoon flashes brightly as it hits %s.", *************** *** 237,243 **** if (ismimic) seemimic(mtmp); mtmp->msleeping = 0; if (vis) hit(distant_name(otmp,xname), mtmp, exclam(damage)); ! else if (verbose) pline("It is hit%s", exclam(damage)); if (otmp->opoisoned) { if (resists_poison(mtmp)) { --- 260,266 ---- if (ismimic) seemimic(mtmp); mtmp->msleeping = 0; if (vis) hit(distant_name(otmp,xname), mtmp, exclam(damage)); ! else if (verbose && !target) pline("It is hit%s", exclam(damage)); if (otmp->opoisoned) { if (resists_poison(mtmp)) { *************** *** 256,276 **** 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!"); } if (otmp->otyp == ACID_VENOM && cansee(mtmp->mx,mtmp->my)) { if (resists_acid(mtmp)) { ! if (vis || verbose) 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!"); } } mtmp->mhp -= damage; if (mtmp->mhp < 1) { ! if (vis || verbose) pline("%s is %s!", Monnam(mtmp), (nonliving(mtmp->data) || !vis) ? "destroyed" : "killed"); --- 279,299 ---- hates_silver(mtmp->data)) { if (vis) pline_The("silver sears %s flesh!", s_suffix(mon_nam(mtmp))); ! 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 && !target)) pline("%s is unaffected.", Monnam(mtmp)); damage = 0; } else { if (vis) pline_The("acid burns %s!", mon_nam(mtmp)); ! else if (verbose && !target) pline("It is burned!"); } } mtmp->mhp -= damage; if (mtmp->mhp < 1) { ! if (vis || (verbose && !target)) pline("%s is %s!", Monnam(mtmp), (nonliving(mtmp->data) || !vis) ? "destroyed" : "killed"); *************** *** 423,428 **** --- 446,455 ---- break; default: dam = dmgval(singleobj, &youmonst); + /* D: Monsters get artifact launcher damage + * bonus too */ + if (mwep && mwep->oartifact) + dam += spec_dbon(mwep, &youmonst, dam); hitv = 3 - distmin(u.ux,u.uy, mon->mx,mon->my); if (hitv < -4) hitv = -4; if (is_elf(mon->data) && *************** *** 687,692 **** --- 714,867 ---- } } + 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 == DARK_ELVEN_ARROW + || otmp->otyp == ELVEN_ARROW + || otmp->otyp == ORCISH_ARROW + || otmp->otyp == YA + || otmp->otyp == CROSSBOW_BOLT) verb = "shoots"; + #ifdef FIREARMS + else if (is_bullet(otmp)) verb = "fires"; + #endif + if(ammo_and_launcher(otmp, mwep) && + is_launcher(mwep) && + objects[(mwep->otyp)].oc_range) { + if (dist2(mtmp->mx, mtmp->my, mtarg->mx, mtarg->my) > + (objects[(mwep->otyp)].oc_range * + objects[(mwep->otyp)].oc_range)) + return 0; /* Out of range */ + } + + /* Check that pets don't fire explosives at friends */ + if (mtmp->mtame && + ( + #ifdef FIREARMS + otmp->otyp == ROCKET || + otmp->otyp == GAS_GRENADE || + otmp->otyp == FRAG_GRENADE || + otmp->otyp == STICK_OF_DYNAMITE || + #endif + (mwep && mwep->oartifact == ART_HELLFIRE) + ) && + has_adjacent_enemy(mtmp, mtarg->mx, mtarg->my, FALSE)) + return 0; + + 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); + + #ifdef FIREARMS + if (mwep && objects[(mwep->otyp)].oc_rof && + is_launcher(mwep)) + multishot += objects[(mwep->otyp)].oc_rof; + #endif + + 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; + while (multishot-- > 0) + 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); + return 1; + } + } + return 0; + } + #endif /* OVL1 */ #ifdef OVLB *************** *** 729,734 **** --- 904,965 ---- 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; + int spit_energy = 4 + rnd(4); + + 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) && mtmp->m_en >= spit_energy) { + 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 ((mtmp->m_en -= spit_energy) < 0) + mtmp->m_en = 0; + + /* If this is a pet, it'll get hungry. Minions and + * spell beings won't hunger */ + if (mtmp->mtame && !mtmp->isminion && !mtmp->isspell) { + struct edog *dog = EDOG(mtmp); + + /* Hunger effects will catch up next move */ + if (dog->hungrytime > 1) + dog->hungrytime--; + } + + return 1; + } + } + return 0; + } + #endif /* OVLB */ #ifdef OVL1 *************** *** 774,779 **** --- 1005,1066 ---- return(1); } + int + breamm(mtmp, mattk, mtarg) /* monster breathes at monster (ranged) */ + register struct monst *mtmp, *mtarg; + register struct attack *mattk; + { + /* if new breath types are added, change AD_ACID to max type */ + int typ = (mattk->adtyp == AD_RBRE) ? rnd(AD_ACID) : mattk->adtyp ; + int breath_energy = 5 + rnd(5); + + 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) && mtmp->m_en >= breath_energy) { + + 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. + */ + if(!rn2(3)) + mtmp->mspec_used = 10+rn2(20); + + /* This should never go below zero, but check anyway */ + if ((mtmp->m_en -= breath_energy) < 0) + mtmp->m_en = 0; + + /* If this is a pet, it'll get hungry. Minions and + * spell beings won't hunger */ + if (mtmp->mtame && !mtmp->isminion && !mtmp->isspell) { + struct edog *dog = EDOG(mtmp); + + /* Hunger effects will catch up next move */ + if (dog->hungrytime > 1) + dog->hungrytime--; + } + } else impossible("Breath weapon %d used", typ-1); + } else + return (0); + } + return(1); + } + /* WAC for doorbusting ONLY (at this point in time) No checks */ boolean *************** *** 823,828 **** --- 1110,1122 ---- } 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 -c -r sl_orig/src/steal.c sl_petbr/src/steal.c *** sl_orig/src/steal.c Fri Mar 1 02:26:44 2002 --- sl_petbr/src/steal.c Sat Mar 2 01:24:00 2002 *************** *** 384,406 **** #endif /* OVLB */ #ifdef OVL0 /* release the objects the creature is carrying */ void relobj(mtmp,show,is_pet) register struct monst *mtmp; register int show; boolean is_pet; /* If true, pet should keep wielded/worn items */ { register struct obj *otmp; register int omx = mtmp->mx, omy = mtmp->my; struct obj *keepobj = 0; ! struct obj *wep = MON_WEP(mtmp); boolean item1 = FALSE, item2 = FALSE; ! if (!is_pet || mindless(mtmp->data) || is_animal(mtmp->data)) item1 = item2 = TRUE; if (!tunnels(mtmp->data) || !needspick(mtmp->data)) item1 = TRUE; while ((otmp = mtmp->minvent) != 0) { obj_extract_self(otmp); /* special case: pick-axe and unicorn horn are non-worn */ --- 384,467 ---- #endif /* OVLB */ #ifdef OVL0 + STATIC_OVL boolean + mon_has_launcher(mon, ammo, keep) + struct monst *mon; + struct obj *ammo, *keep; + { + struct obj *obj; + + for(obj = mon->minvent; obj; obj = obj->nobj) + if (is_launcher(obj) && ammo_and_launcher(ammo, obj)) + return TRUE; + + for (obj = keep; obj; obj = obj->nobj) + if (is_launcher(obj) && ammo_and_launcher(ammo, obj)) + return TRUE; + return FALSE; + } + + boolean + keep_ammo(mon, ammo, keep) + struct monst *mon; + struct obj *ammo, *keep; + { + return rn2(3) || mon_has_launcher(mon, ammo, keep) + #ifdef FIREARMS + || objects[ammo->otyp].w_ammotyp == WP_GRENADE + #endif + ; + } + + STATIC_OVL boolean + mon_has_ammo(mon, launcher, keep) + struct monst *mon; + struct obj *launcher, *keep; + { + struct obj *obj; + + for (obj = mon->minvent; obj; obj = obj->nobj) + if (ammo_and_launcher(obj, launcher)) + return TRUE; + + for (obj = keep; obj; obj = obj->nobj) + if (ammo_and_launcher(obj, launcher)) + return TRUE; + return FALSE; + } + + boolean + keep_launcher(mon, launcher, keep) + struct monst *mon; + struct obj *launcher, *keep; + { + return rn2(3) || mon_has_ammo(mon, launcher, keep); + } + /* release the objects the creature is carrying */ void relobj(mtmp,show,is_pet) register struct monst *mtmp; register int show; boolean is_pet; /* If true, pet should keep wielded/worn items */ + /* D: Added code to let pet retain ranged weapons */ { register struct obj *otmp; register int omx = mtmp->mx, omy = mtmp->my; struct obj *keepobj = 0; ! struct obj *wep = MON_WEP(mtmp), ! *pref_wep = 0; boolean item1 = FALSE, item2 = FALSE; ! boolean uses_weap = is_armed(mtmp->data); ! if (!is_pet || mindless(mtmp->data) || is_animal(mtmp->data)) item1 = item2 = TRUE; + else if (uses_weap) + pref_wep = select_hwep(mtmp); + if (!tunnels(mtmp->data) || !needspick(mtmp->data)) item1 = TRUE; + while ((otmp = mtmp->minvent) != 0) { obj_extract_self(otmp); /* special case: pick-axe and unicorn horn are non-worn */ *************** *** 421,426 **** --- 482,505 ---- mtmp->misc_worn_check &= ~(otmp->owornmask); otmp->owornmask = 0L; } + + /* D: Smart pets don't drop ranged weapons */ + if (is_pet && uses_weap && + #ifndef GIVE_PATCH + !mtmp->mflee && + #endif + otmp->oclass == WEAPON_CLASS) { + int skill = objects[otmp->otyp].oc_skill; + if ((is_missile(otmp) || otmp == pref_wep || + (is_ammo(otmp) && keep_ammo(mtmp, otmp, keepobj)) || + (is_launcher(otmp) && keep_launcher(mtmp, otmp, keepobj)) || + (skill == P_DAGGER || skill == P_KNIFE))) { + otmp->nobj = keepobj; + keepobj = otmp; + continue; + } + } + if (is_pet && cansee(omx, omy) && flags.verbose) pline("%s drops %s.", Monnam(mtmp), distant_name(otmp, doname)); diff -c -r sl_orig/src/zap.c sl_petbr/src/zap.c *** sl_orig/src/zap.c Fri Mar 1 02:26:46 2002 --- sl_petbr/src/zap.c Sat Mar 2 01:24:00 2002 *************** *** 3318,3324 **** mon->mhp -= tmp; #ifdef SHOW_DMG ! if (mon->mhp > 0) showdmg(tmp); #endif return(tmp); --- 3318,3324 ---- mon->mhp -= tmp; #ifdef SHOW_DMG ! if (mon->mhp > 0 && canseemon(mon)) showdmg(tmp); #endif return(tmp); *************** *** 3535,3540 **** --- 3535,3549 ---- return (3 - chance) < ac+spell_bonus; } /* #endif */ + + void buzz(type,nd,sx,sy,dx,dy) + register int type, nd; + register xchar sx,sy; + register int dx,dy; + { + dobuzz(type, nd, sx, sy, dx, dy, TRUE); + } + /* type == 0 to 9 : you shooting a wand */ /* type == 10 to 19 : you casting a spell */ /* type == 20 to 29 : you breathing as a monster */ *************** *** 3545,3554 **** /* type == -30 to -39 : monster shooting a wand */ /* called with dx = dy = 0 with vertical bolts */ void ! buzz(type,nd,sx,sy,dx,dy) register int type, nd; register xchar sx,sy; register int dx,dy; { int range, abstype; struct rm *lev; --- 3554,3564 ---- /* type == -30 to -39 : monster shooting a wand */ /* called with dx = dy = 0 with vertical bolts */ void ! 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; *************** *** 3770,3776 **** } else { if (!otmp) { /* normal non-fatal hit */ ! hit(fltxt, mon, exclam(tmp)); } else { /* some armor was destroyed; no damage done */ if (canseemon(mon)) --- 3780,3787 ---- } else { if (!otmp) { /* normal non-fatal hit */ ! if (say || canseemon(mon)) ! hit(fltxt, mon, exclam(tmp)); } else { /* some armor was destroyed; no damage done */ if (canseemon(mon)) *************** *** 3785,3791 **** } range -= 2; } else { ! miss(fltxt,mon); } } else if (sx == u.ux && sy == u.uy && range >= 0) { nomul(0); --- 3796,3803 ---- } range -= 2; } else { ! if (say || canseemon(mon)) ! miss(fltxt,mon); } } else if (sx == u.ux && sy == u.uy && range >= 0) { nomul(0);