From: arromdee Date: Fri, 11 Jan 2002 01:09:07 +0000 (+0000) Subject: Finally overhauled some spell stuff. --Ken A. X-Git-Tag: MOVE2GIT~3511 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=157840766d74354950771b637cbfd9664ac10b97;p=nethack Finally overhauled some spell stuff. --Ken A. Summary of spell changes: -- wimpiness of 'default' spell fixed by doing half damage for magic resistance instead of 1 damage, and using half monster level instead of 1/3. It may still need tweaking, but is much better than before. -- 'default' spell for cleric monsters is now the wounds spell, by analogy with wizard monsters. -- added clerical lightning strike, flame strike, gush of water -- all spells should now say the monster is casting a spell, and all spells should have messages. (Side effect: monsters speeding up by other means also give a message saying so). -- casting undirected spells is not affected by whether the monster knows where you are. Monsters that are attacking your displaced image, that are several squares away, or that are peaceful can use undirected spells. -- messages should correctly say whether the spell is undirected (a monster was always casting at thin air or pointing at you and cursing, without checking to see if the spell wouldn't require pointing) -- Monsters which are attacking your displaced image, etc. use up mspec_used. If they are casting an undirected spell, the spell still works. -- Monsters which are not attacking can cast spells that don't attack. -- If a monster didn't have ranged spellcasting ability (which most don't), it would print a curse message from buzzmu() every round it was at range, creating a useless stream of constant curse messages I still haven't made spellcasters "smarter" in the sense of noticing whether you have reflection, fire resistance, etc. That opens a big can of worms because it would mean giving monsters a memory. Known bug: the higher level a monster is, the more spells it has; since it chooses a noncombat spell by randomly picking a spell and casting if it happens to be noncombat, the higher level the monster is the greater the chance of getting nothing. --- diff --git a/doc/buglist b/doc/buglist index 7bef1c930..645ac5900 100644 --- a/doc/buglist +++ b/doc/buglist @@ -50,18 +50,6 @@ stone-to-flesh monsters' inventory? [psmith@spod-central.org] Releasing your pet from a bear trap by displacing is silly. [eino.keskitalo@purkki.mbnet.fi] -Monster spellcasting is in serious need of overhaul. -Clerical spell `case 1' has ``if (...) {} /* else fall into default */'' -but the default case has been moved away so the comment is a lie. -Monsters which cast spells automatically "cast at thin air" if they don't know -where you are. In fact, castmu() includes undirected spells (healing) which -should still be possible under these circumstances. -The higher level a monster is, the more likely it becomes to cast the wimpy -"default" spell. This is particularly silly for the Wizard who has a higher -level each time he's reincarnated. Beyond that, spell using monsters ought -to pick their spells intelligently rather than randomly, especially once -they learn that their target is magic resistant. - Recoil from throwing while levitating won't trigger every type of trap. Some were checked for and added in 3.3.2 (magic_portal, fire, pits et al on Sokoban), but there are others that you can bypass just by throwing diff --git a/doc/fixes33.2 b/doc/fixes33.2 index 9092e38bf..739d93cb6 100644 --- a/doc/fixes33.2 +++ b/doc/fixes33.2 @@ -375,6 +375,11 @@ Chromatic Dragon has silver scales too (she reflects) being killed when wishing for an artifact should retain that item in bones data the drain life spell should not wipe out engravings (especially not using a function that requires you to be able to reach the floor) +monsters who can cast undirected spells don't need to be in combat with you + to do so +messages consistent for all monster spells +monsters casting spells at your displaced image now set mspec_used +monsters without ranged spells don't print curse messages for ranged spells Platform- and/or Interface-Specific Fixes diff --git a/include/extern.h b/include/extern.h index e6879155b..6b74dc82c 100644 --- a/include/extern.h +++ b/include/extern.h @@ -904,7 +904,7 @@ E void FDECL(mapglyph, (int, int *, int *, unsigned *, int, int)); /* ### mcastu.c ### */ -E int FDECL(castmu, (struct monst *,struct attack *)); +E int FDECL(castmu, (struct monst *,struct attack *,BOOLEAN_P,BOOLEAN_P)); E int FDECL(buzzmu, (struct monst *,struct attack *)); /* ### mhitm.c ### */ diff --git a/src/apply.c b/src/apply.c index 684906154..55b707925 100644 --- a/src/apply.c +++ b/src/apply.c @@ -776,7 +776,9 @@ register struct obj *obj; } else switch (rn2(3)) { default: break; - case 1: mon_adjust_speed(mtmp, 2); + case 1: in_mklev = TRUE; /* don't print messages */ + mon_adjust_speed(mtmp, 2); + in_mklev = FALSE; break; case 2: /* no explanation; it just happens... */ nomovemsg = ""; diff --git a/src/mcastu.c b/src/mcastu.c index 778ec23cd..16ea2ebe1 100644 --- a/src/mcastu.c +++ b/src/mcastu.c @@ -4,7 +4,11 @@ #include "hack.h" -STATIC_DCL void FDECL(cursetxt,(struct monst *)); +STATIC_DCL void FDECL(cursetxt,(struct monst *,BOOLEAN_P)); +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,(int,int)); +STATIC_DCL boolean FDECL(spell_would_be_useless,(struct monst *,int,int)); #ifdef OVL0 @@ -13,13 +17,16 @@ extern const char *flash_types[]; /* from zap.c */ /* feedback when frustrated monster couldn't cast a spell */ STATIC_OVL void -cursetxt(mtmp) +cursetxt(mtmp, undirected) struct monst *mtmp; +boolean undirected; { if (canseemon(mtmp) && couldsee(mtmp->mx, mtmp->my)) { const char *point_msg; /* spellcasting monsters are impolite */ - if ((Invis && !perceives(mtmp->data) && + if (undirected) + point_msg = "all around, then curses"; + else if ((Invis && !perceives(mtmp->data) && (mtmp->mux != u.ux || mtmp->muy != u.uy)) || (youmonst.m_ap_type == M_AP_OBJECT && youmonst.mappearance == STRANGE_OBJECT) || @@ -39,33 +46,92 @@ struct monst *mtmp; #endif /* OVL0 */ #ifdef OVLB +/* return values: + * 1: successful spell + * 0: unsuccessful spell + */ int -castmu(mtmp, mattk) /* monster casts spell at you */ +castmu(mtmp, mattk, thinks_it_foundyou, foundyou) register struct monst *mtmp; register struct attack *mattk; + boolean thinks_it_foundyou; + boolean foundyou; { int dmg, ml = mtmp->m_lev; + int ret; - if(mtmp->mcan || mtmp->mspec_used || !ml) { /* could not attack */ - cursetxt(mtmp); - return(0); - } else { - 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); + int spellnum = 0; + + /* Three cases: + * -- monster is attacking you. Cast spell normally. + * -- monster thinks it's attacking you. Cast spell, but spells that + * are not undirected fail with cursetxt() and loss of mspec_used. + * -- monster isn't trying to attack. Cast spell; spells that are + * are not undirected don't go off, but they don't fail--they're + * just not cast. + * Since most spells are directed, this means that a monster that isn't + * attacking casts spells only a small portion of the time that an + * attacking monster does. + */ + if (mattk->adtyp == AD_SPEL || mattk->adtyp == AD_CLRC) { + spellnum = rn2(ml); + /* not trying to attack? don't allow directed spells */ + if (!thinks_it_foundyou && (!is_undirected_spell(mattk->adtyp, spellnum) || spell_would_be_useless(mtmp, mattk->adtyp, spellnum))) + { + if (foundyou) + impossible("spellcasting monster found you and doesn't know it?"); + return 0; } } + + /* monster unable to cast spells? */ + if(mtmp->mcan || mtmp->mspec_used || !ml) { + cursetxt(mtmp, is_undirected_spell(mattk->adtyp, spellnum)); + 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; + } + + /* monster can cast spells, but is casting a directed spell at the + wrong place? If so, give a message, and return. Do this *after* + penalizing mspec_used. */ + if (!foundyou && thinks_it_foundyou && !is_undirected_spell(mattk->adtyp, spellnum)) { + pline("%s casts a spell at %s!", + canseemon(mtmp) ? Monnam(mtmp) : "It", + levl[mtmp->mux][mtmp->muy].typ == WATER + ? "empty water" : "thin air"); + return(0); + } + + 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); + } + pline("%s casts a spell%s!", Monnam(mtmp), + is_undirected_spell(mattk->adtyp, spellnum) ? "" : " at you"); + /* * As these are spells, the damage is related to the level * of the monster casting the spell. */ - if (mattk->damd) - dmg = d((int)((ml/3) + mattk->damn), (int)mattk->damd); - else dmg = d((int)((ml/3) + 1), 6); + if (!foundyou) { + dmg = 0; + if (mattk->adtyp != AD_SPEL && mattk->adtyp != AD_CLRC) { + impossible("%s casting non-hand-to-hand version of hand-to-hand spell %d?", Monnam(mtmp), mattk->adtyp); + return(0); + } + } else if (mattk->damd) + dmg = d((int)((ml/2) + mattk->damn), (int)mattk->damd); + else dmg = d((int)((ml/2) + 1), 6); if (Half_spell_damage) dmg = (dmg+1) / 2; + ret = 1; + switch(mattk->adtyp) { case AD_FIRE: @@ -93,233 +159,390 @@ castmu(mtmp, mattk) /* monster casts spell at you */ dmg = 0; } else dmg = d((int)mtmp->m_lev/2 + 1,6); break; - case AD_SPEL: /* random spell */ - - mtmp->mspec_used = 10 - mtmp->m_lev; - if (mtmp->mspec_used < 2) mtmp->mspec_used = 2; - switch(rn2((int)mtmp->m_lev)) { - case 22: - case 21: - case 20: - pline("Oh no, %s's using the touch of death!", - humanoid(mtmp->data) - ? (mtmp->female ? "she" : "he") - : "it" - ); - if (nonliving(youmonst.data) || is_demon(youmonst.data)) - You("seem no deader than before."); - else if (!Antimagic && rn2(ml) > 12) { - - if(Hallucination) - You("have an out of body experience."); - else { - killer_format = KILLED_BY_AN; - killer = "touch of death"; - done(DIED); - } - } else { - if(Antimagic) shieldeff(u.ux, u.uy); - pline("Lucky for you, it didn't work!"); - } - dmg = 0; - break; - case 19: - case 18: - if(mtmp->iswiz && flags.no_of_wizards == 1) { - pline("Double Trouble..."); - clonewiz(); - dmg = 0; - break; - } /* else fall into the next case */ - case 17: - case 16: - case 15: - if(mtmp->iswiz) - verbalize("Destroy the thief, my pets!"); - nasty(mtmp); /* summon something nasty */ - /* fall into the next case */ - case 14: /* aggravate all monsters */ - case 13: - aggravate(); - dmg = 0; - break; - case 12: /* curse random items */ - case 11: - case 10: - rndcurse(); - dmg = 0; - break; - case 9: - case 8: /* destroy armor */ - if (Antimagic) { - shieldeff(u.ux, u.uy); - pline("A field of force surrounds you!"); - } else if(!destroy_arm(some_armor(&youmonst))) - Your("skin itches."); - dmg = 0; - break; - case 7: - case 6: /* drain strength */ - if(Antimagic) { - shieldeff(u.ux, u.uy); - You_feel("momentarily weakened."); - } else { - You("suddenly feel weaker!"); - dmg = ml - 6; - if(Half_spell_damage) dmg = (dmg+1) / 2; - losestr(rnd(dmg)); - if(u.uhp < 1) - done_in_by(mtmp); - } - dmg = 0; - break; - case 5: /* make invisible if not */ - case 4: - if (!mtmp->minvis && !mtmp->invis_blkd) { - if(canseemon(mtmp) && !See_invisible) - pline("%s suddenly disappears!", Monnam(mtmp)); - mon_set_minvis(mtmp); - dmg = 0; - break; - } /* else fall into the next case */ - case 3: /* stun */ - if (Antimagic || Free_action) { - shieldeff(u.ux, u.uy); - if(!Stunned) - You_feel("momentarily disoriented."); - make_stunned(1L, FALSE); - } else { - if (Stunned) - You("struggle to keep your balance."); - else - You("reel..."); - dmg = d(ACURR(A_DEX) < 12 ? 6 : 4, 4); - if(Half_spell_damage) dmg = (dmg+1) / 2; - make_stunned(HStun + dmg, FALSE); - } - dmg = 0; - 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(Antimagic) { - shieldeff(u.ux, u.uy); - You("get a slight %sache.",body_part(HEAD)); - dmg = 1; - } else { - if (dmg <= 10) - Your("brain is on fire!"); - else Your("%s suddenly aches!", body_part(HEAD)); - } - break; + case AD_SPEL: /* wizard spell */ + case AD_CLRC: /* clerical spell */ + { + if (mattk->adtyp == AD_SPEL) + cast_wizard_spell(mtmp, dmg, spellnum); + else + cast_cleric_spell(mtmp, dmg, spellnum); + dmg = 0; /* done by the spell casting functions */ + break; + } + } + if(dmg) mdamageu(mtmp, dmg); + return(ret); +} + +/* monster wizard and cleric spellcasting functions */ +/* If dmg is zero, then the monster is not casting at you */ +/* If the monster is intentionally not casting at you, we have previously + called spell_would_be_useless() and the fallthroughs shouldn't happen */ +/* If you modify either of these, be sure to change is_undirected_spell() + and spell_would_be_useless(). */ +STATIC_OVL +void +cast_wizard_spell(mtmp, dmg, spellnum) +struct monst *mtmp; +int dmg; +int spellnum; +{ + if (dmg == 0 && !is_undirected_spell(AD_SPEL, spellnum)) { + impossible("cast directed wizard spell with dmg=0?"); + return; + } + + switch(spellnum) { + case 22: + case 21: + case 20: + pline("Oh no, %s's using the touch of death!", mhe(mtmp)); + if (nonliving(youmonst.data) || is_demon(youmonst.data)) + You("seem no deader than before."); + else if (!Antimagic && rn2(mtmp->m_lev) > 12) { + + if(Hallucination) + You("have an out of body experience."); + else { + killer_format = KILLED_BY_AN; + killer = "touch of death"; + done(DIED); } + } else { + if(Antimagic) shieldeff(u.ux, u.uy); + pline("Lucky for you, it didn't work!"); + } + dmg = 0; + break; + case 19: + case 18: + if(mtmp->iswiz && flags.no_of_wizards == 1) { + pline("Double Trouble..."); + clonewiz(); + dmg = 0; + break; + } /* else fall into the next case */ + case 17: + case 16: + case 15: + if(mtmp->iswiz) + verbalize("Destroy the thief, my pets!"); + else + pline("A monster appears!"); + nasty(mtmp); /* summon something nasty */ + /* fall into the next case */ + case 14: /* aggravate all monsters */ + case 13: + You_feel("that monsters are aware of your presence."); + aggravate(); + dmg = 0; + break; + case 12: /* curse random items */ + case 11: + case 10: + You_feel("as if you need some help."); + rndcurse(); + dmg = 0; + break; + case 9: + case 8: /* destroy armor */ + if (Antimagic) { + shieldeff(u.ux, u.uy); + pline("A field of force surrounds you!"); + } else if(!destroy_arm(some_armor(&youmonst))) + Your("skin itches."); + dmg = 0; + break; + case 7: + case 6: /* drain strength */ + if(Antimagic) { + shieldeff(u.ux, u.uy); + You_feel("momentarily weakened."); + } else { + You("suddenly feel weaker!"); + dmg = mtmp->m_lev - 6; + if(Half_spell_damage) dmg = (dmg+1) / 2; + losestr(rnd(dmg)); + if(u.uhp < 1) + done_in_by(mtmp); + } + dmg = 0; + break; + case 5: /* make invisible if not */ + case 4: + if (!mtmp->minvis && !mtmp->invis_blkd) { + if(canseemon(mtmp)) { + if (!See_invisible) + pline("%s suddenly disappears!", Monnam(mtmp)); + else + pline("%s suddenly becomes transparent!", Monnam(mtmp)); + } + mon_set_minvis(mtmp); + dmg = 0; + break; + } + /* else fall through to next case */ + case 3: /* stun */ + if (Antimagic || Free_action) { + shieldeff(u.ux, u.uy); + if(!Stunned) + You_feel("momentarily disoriented."); + make_stunned(1L, FALSE); + } else { + if (Stunned) + You("struggle to keep your balance."); + else + You("reel..."); + dmg = d(ACURR(A_DEX) < 12 ? 6 : 4, 4); + if(Half_spell_damage) dmg = (dmg+1) / 2; + make_stunned(HStun + dmg, FALSE); + } + dmg = 0; + break; + case 2: /* haste self */ + mon_adjust_speed(mtmp, 1); + dmg = 0; + break; + case 1: /* cure self */ + if(mtmp->mhp < mtmp->mhpmax) { + if (canseemon(mtmp)) + pline("%s looks better.", Monnam(mtmp)); + if((mtmp->mhp += rnd(8)) > mtmp->mhpmax) + mtmp->mhp = mtmp->mhpmax; + dmg = 0; break; + } + /* else fall through to default */ + case 0: + default: /* psi bolt */ + /* prior to 3.3.2 Antimagic was setting the damage to 1--this + made the spell virtually harmless to players with magic res. */ + if(Antimagic) { + shieldeff(u.ux, u.uy); + dmg = (dmg + 1)/2; + } + if (dmg <= 5) + You("get a slight %sache.",body_part(HEAD)); + else if (dmg <= 10) + Your("brain is on fire!"); + else if (dmg <= 20) + Your("%s suddenly aches painfully!", body_part(HEAD)); + else + Your("%s suddenly aches very painfully!", body_part(HEAD)); + break; + } + if(dmg) mdamageu(mtmp, dmg); +} - case AD_CLRC: /* clerical spell */ - mtmp->mspec_used = 10 - mtmp->m_lev; - if (mtmp->mspec_used < 2) mtmp->mspec_used = 2; - switch(rn2((int)mtmp->m_lev)) { - /* Other ideas: lightning bolts, towers of flame, - gush of water -3. */ - - default: /* confuse */ - if(Antimagic) { - shieldeff(u.ux, u.uy); - You_feel("momentarily dizzy."); - } else { - dmg = (int)mtmp->m_lev; - if(Half_spell_damage) dmg = (dmg+1) / 2; - make_confused(HConfusion + dmg, TRUE); - } - dmg = 0; - break; - case 12: /* curse random items */ - case 11: - case 10: - rndcurse(); - dmg = 0; - break; - case 9: - case 8: /* insects */ - /* Try for insects, and if there are none - left, go for (sticks to) snakes. -3. */ - { - int i; - struct permonst *pm = mkclass(S_ANT,0); - struct monst *mtmp2; - char let = (pm ? S_ANT : S_SNAKE); - - for (i = 0; i <= (int) mtmp->m_lev; i++) - if ((pm = mkclass(let,0)) && +STATIC_OVL +void +cast_cleric_spell(mtmp, dmg, spellnum) +struct monst *mtmp; +int dmg; +int spellnum; +{ + if (dmg == 0 && !is_undirected_spell(AD_CLRC, spellnum)) { + impossible("cast directed cleric spell with dmg=0?"); + return; + } + + switch(spellnum) { + case 14: + /* this is physical damage, not magical damage */ + pline("A sudden geyser slams into you from nowhere!"); + dmg = d(8, 6); + if(Half_physical_damage) dmg = (dmg+1) / 2; + break; + case 13: + pline("A pillar of fire strikes all around you!"); + if (Fire_resistance) { + shieldeff(u.ux, u.uy); + dmg = 0; + } else + dmg = d(8, 6); + if(Half_spell_damage) dmg = (dmg+1) / 2; + burn_away_slime(); + (void) burnarmor(&youmonst); + destroy_item(SCROLL_CLASS, AD_FIRE); + destroy_item(POTION_CLASS, AD_FIRE); + destroy_item(SPBOOK_CLASS, AD_FIRE); + burn_floor_paper(u.ux, u.uy, TRUE, FALSE); + break; + case 12: + pline("A bolt of lightning strikes down at you from above!"); + if (ureflects("It bounces off your %s.", "") || Shock_resistance) { + shieldeff(u.ux, u.uy); + dmg = 0; + } else + dmg = d(8, 6); + if(Half_spell_damage) dmg = (dmg+1) / 2; + destroy_item(WAND_CLASS, AD_ELEC); + destroy_item(RING_CLASS, AD_ELEC); + break; + case 11: /* curse random items */ + case 10: + You_feel("as if you need some help."); + rndcurse(); + dmg = 0; + break; + case 9: + case 8: /* insects */ + { + /* Try for insects, and if there are none + left, go for (sticks to) snakes. -3. */ + int i; + struct permonst *pm = mkclass(S_ANT,0); + struct monst *mtmp2; + char let = (pm ? S_ANT : S_SNAKE); + boolean success; + + success = pm ? TRUE : FALSE; + for (i = 0; i <= (int) mtmp->m_lev; i++) { + if ((pm = mkclass(let,0)) && (mtmp2 = makemon(pm, u.ux, u.uy, NO_MM_FLAGS))) { - mtmp2->msleeping = mtmp2->mpeaceful = - mtmp2->mtame = 0; - set_malign(mtmp2); - } - } - dmg = 0; - break; - case 6: - case 7: /* blindness */ - /* note: resists_blnd() doesn't apply here */ - if (!Blinded) { - int num_eyes = eyecount(youmonst.data); - pline("Scales cover your %s!", - (num_eyes == 1) ? - body_part(EYE) : makeplural(body_part(EYE))); - make_blinded(Half_spell_damage ? 100L:200L, FALSE); - if (!Blind) Your(vision_clears); - dmg = 0; - break; - } - case 4: - case 5: /* wound */ - if(Antimagic) { - shieldeff(u.ux, u.uy); - Your("skin itches badly for a moment."); - dmg = 0; - } else { - pline("Wounds appear on your body!"); - dmg = d(2,8) + 1; - if (Half_spell_damage) dmg = (dmg+1) / 2; - } - break; - case 3: /* hold */ - if (Antimagic || Free_action) { - shieldeff(u.ux, u.uy); - if(multi >= 0) - You("stiffen briefly."); - nomul(-1); - } else { - if (multi >= 0) - You("are frozen in place!"); - dmg = 4 + (int)mtmp->m_lev; - if (Half_spell_damage) dmg = (dmg+1) / 2; - nomul(-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; - break; - } /* else fall through to default case */ + success = TRUE; + mtmp2->msleeping = mtmp2->mpeaceful = mtmp2->mtame = 0; + set_malign(mtmp2); } + } + if (canseemon(mtmp)) { + /* wrong--should check if you can see the insects/snakes */ + if (!success) + pline("%s casts at a clump of sticks, but nothing happens.", + Monnam(mtmp)); + else if (let == S_SNAKE) + pline("%s transforms clump of sticks into snakes!", + Monnam(mtmp)); + else + pline("%s summons insects!", Monnam(mtmp)); + } + dmg = 0; + break; } - if(dmg) mdamageu(mtmp, dmg); - return(1); + case 6: + case 7: /* blindness */ + /* note: resists_blnd() doesn't apply here */ + if (!Blinded) { + int num_eyes = eyecount(youmonst.data); + pline("Scales cover your %s!", + (num_eyes == 1) ? + body_part(EYE) : makeplural(body_part(EYE))); + make_blinded(Half_spell_damage ? 100L:200L, FALSE); + if (!Blind) Your(vision_clears); + dmg = 0; + break; + } + /* otherwise fall through */ + case 4: + case 5: /* hold */ + if (Antimagic || Free_action) { + shieldeff(u.ux, u.uy); + if(multi >= 0) + You("stiffen briefly."); + nomul(-1); + } else { + if (multi >= 0) + You("are frozen in place!"); + dmg = 4 + (int)mtmp->m_lev; + if (Half_spell_damage) dmg = (dmg+1) / 2; + nomul(-dmg); + } + dmg = 0; + break; + case 3: + if(Antimagic) { + shieldeff(u.ux, u.uy); + You_feel("momentarily dizzy."); + } else { + dmg = (int)mtmp->m_lev; + if(Half_spell_damage) dmg = (dmg+1) / 2; + make_confused(HConfusion + dmg, TRUE); + } + dmg = 0; + break; + case 2: + case 1: /* cure self */ + if(mtmp->mhp < mtmp->mhpmax) { + if (canseemon(mtmp)) + pline("%s looks better.", Monnam(mtmp)); + if((mtmp->mhp += rnd(8)) > mtmp->mhpmax) + mtmp->mhp = mtmp->mhpmax; + dmg = 0; + break; + } + /* else fall through to default */ + default: /* wounds */ + case 0: + if(Antimagic) { + shieldeff(u.ux, u.uy); + dmg = (dmg + 1)/2; + } + if (dmg <= 5) + Your("skin itches badly for a moment."); + else if (dmg <= 10) + pline("Wounds appear on your body!"); + else if (dmg <= 20) + pline("Severe wounds appear on your body!"); + else + pline("Your body is covered with painful wounds!"); + break; + } + if(dmg) mdamageu(mtmp, dmg); +} + +STATIC_DCL +boolean +is_undirected_spell(adtyp, spellnum) +int adtyp; +int spellnum; +{ + if (adtyp == AD_SPEL) { + if ((spellnum >= 13 && spellnum <= 19) || spellnum == 5 || + spellnum == 4 || spellnum == 2 || spellnum == 1) + return TRUE; + } else if (adtyp == AD_CLRC) { + if (spellnum == 9 || spellnum == 8 | spellnum == 2 || spellnum == 1) + return TRUE; + } + return FALSE; +} + +/* Some undirected spells are useless under some circumstances */ +STATIC_DCL +boolean +spell_would_be_useless(mtmp, adtyp, spellnum) +struct monst *mtmp; +int spellnum; +int adtyp; +{ + if (adtyp == AD_SPEL) { + /* aggravate monsters, etc. won't be cast by peaceful monsters */ + if (mtmp->mpeaceful && spellnum >= 13 && spellnum <= 19) + return TRUE; + /* haste self when already fast */ + if (mtmp->permspeed == MFAST && spellnum == 2) + return TRUE; + /* invisibility when already invisible */ + if ((mtmp->minvis || mtmp->invis_blkd) && (spellnum == 4 || spellnum == 5)) + return TRUE; + /* peaceful monster won't cast invisibility if you can't see invisible, + same as when monster drink potions of invisibility. This doesn't + really make a lot of sense, but lets the player avoid hitting + peaceful monsters by mistake */ + if (mtmp->mpeaceful && !See_invisible && (spellnum == 4 || spellnum == 5)) + return TRUE; + /* healing when already healed */ + if (mtmp->mhp == mtmp->mhpmax && spellnum == 1) + return TRUE; + } else if (adtyp == AD_CLRC) { + /* summon insects/sticks to snakes won't be cast by peaceful monsters */ + if (mtmp->mpeaceful && (spellnum == 9 || spellnum == 8)) + return TRUE; + /* healing when already healed */ + if (mtmp->mhp == mtmp->mhpmax && (spellnum == 1 || spellnum == 2)) + return TRUE; + } + return FALSE; } #endif /* OVLB */ @@ -333,8 +556,13 @@ buzzmu(mtmp, mattk) /* monster uses spell (ranged) */ register struct monst *mtmp; register struct attack *mattk; { - if(mtmp->mcan || mattk->adtyp > AD_SPC2) { - cursetxt(mtmp); + /* don't print constant stream of curse messages for 'normal' + spellcasting monsters at range */ + if (mattk->adtyp > AD_SPC2) + return(0); + + if (mtmp->mcan) { + cursetxt(mtmp, FALSE); return(0); } if(lined_up(mtmp) && rn2(3)) { diff --git a/src/mhitu.c b/src/mhitu.c index 396fc6709..98b90409c 100644 --- a/src/mhitu.c +++ b/src/mhitu.c @@ -616,20 +616,12 @@ mattacku(mtmp) case AT_MAGC: if (range2) sum[i] = buzzmu(mtmp, mattk); - else + else { if (foundyou) - sum[i] = castmu(mtmp, mattk); + sum[i] = castmu(mtmp, mattk, TRUE, TRUE); else - pline("%s casts a spell at %s!", - youseeit ? Monnam(mtmp) : "It", - levl[mtmp->mux][mtmp->muy].typ == WATER - ? "empty water" : "thin air"); - /* FIXME: castmu includes spells that are not - * cast at the player and thus should be - * possible whether the monster knows your - * location or not. - * --KAA - */ + sum[i] = castmu(mtmp, mattk, TRUE, FALSE); + } break; default: /* no attack */ diff --git a/src/mon.c b/src/mon.c index 3074a9c32..9d0cb72ac 100644 --- a/src/mon.c +++ b/src/mon.c @@ -2505,13 +2505,8 @@ int damtype, dam; return; } if (slow) { - if (mon->mspeed != MSLOW) { - unsigned int oldspeed = mon->mspeed; - + if (mon->mspeed != MSLOW) mon_adjust_speed(mon, -1); - if (mon->mspeed != oldspeed && cansee(mon->mx, mon->my)) - pline("%s seems to be moving slower.", Monnam(mon)); - } } if (heal) { if (mon->mhp < mon->mhpmax) { diff --git a/src/monmove.c b/src/monmove.c index 9aca9b07f..7912fd178 100644 --- a/src/monmove.c +++ b/src/monmove.c @@ -446,6 +446,7 @@ register struct monst *mtmp; } } toofar: + /* If monster is nearby you, and has to wield a weapon, do so. This * costs the monster a move, of course. */ @@ -484,6 +485,24 @@ toofar: #endif (is_wanderer(mdat) && !rn2(4)) || (Conflict && !mtmp->iswiz) || (!mtmp->mcansee && !rn2(4)) || mtmp->mpeaceful) { + /* Possibly cast an undirected spell if not attacking you */ + /* note that most of the time castmu() will pick a directed + spell and do nothing, so the monster moves normally */ + /* arbitrary distance restriction to keep monster far away + from you from having cast dozens of sticks-to-snakes + or similar spells by the time you reach it */ + if (dist2(mtmp->mx, mtmp->my, u.ux, u.uy) <= 64 && !mtmp->mspec_used) { + struct attack *a; + + 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)) { + tmp = 3; + break; + } + } + } + } tmp = m_move(mtmp, 0); distfleeck(mtmp,&inrange,&nearby,&scared); /* recalc */ diff --git a/src/muse.c b/src/muse.c index bdf3acb82..1ca7f0d27 100644 --- a/src/muse.c +++ b/src/muse.c @@ -1744,8 +1744,6 @@ skipmsg: different methods of maintaining speed ratings: player's character becomes "very fast" temporarily; monster becomes "one stage faster" permanently */ - if (vismon) - pline("%s is suddenly moving faster.", Monnam(mtmp)); if (oseen) makeknown(POT_SPEED); mon_adjust_speed(mtmp, 1); m_useup(mtmp, otmp); diff --git a/src/worn.c b/src/worn.c index ead443e62..96c611731 100644 --- a/src/worn.c +++ b/src/worn.c @@ -147,6 +147,8 @@ int adjust; /* positive => increase speed, negative => decrease */ { struct obj *otmp; + int oldspeed = mon->mspeed; + switch (adjust) { case 2: mon->permspeed = MFAST; @@ -173,6 +175,13 @@ int adjust; /* positive => increase speed, negative => decrease */ mon->mspeed = MFAST; else mon->mspeed = mon->permspeed; + + if (!in_mklev && mon->mspeed != oldspeed && canseemon(mon)) { + if (adjust > 0) + pline("%s is suddenly moving faster.", Monnam(mon)); + else + pline("%s seems to be moving slower.", Monnam(mon)); + } } /* armor put on or taken off; might be magical variety */