]> granicus.if.org Git - nethack/commitdiff
Finally overhauled some spell stuff. --Ken A.
authorarromdee <arromdee>
Fri, 11 Jan 2002 01:09:07 +0000 (01:09 +0000)
committerarromdee <arromdee>
Fri, 11 Jan 2002 01:09:07 +0000 (01:09 +0000)
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.

doc/buglist
doc/fixes33.2
include/extern.h
src/apply.c
src/mcastu.c
src/mhitu.c
src/mon.c
src/monmove.c
src/muse.c
src/worn.c

index 7bef1c930a2573ba820ae799bef271d09c4ce043..645ac59007b6365cf81a5f21f8cd6eb901daa386 100644 (file)
@@ -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
index 9092e38bf376d04006100754aaf9dfe59ab341f0..739d93cb6b3d60618f1c223d1c93acbc0c6d5d53 100644 (file)
@@ -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
index e6879155b513009035947dce0d5755099cc50411..6b74dc82c02bb4a9c55746c0fe619580efb268bb 100644 (file)
@@ -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 ### */
index 684906154e330597f5460a5879ae0bae364a0276..55b7079252c91125aa82f5ad28ef7f495f09ab35 100644 (file)
@@ -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 = "";
index 778ec23cda660fc7269daedd5a352f10719b3f91..16ea2ebe1d20f6502fd4eb40d7976b786fe55845 100644 (file)
@@ -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)) {
index 396fc6709c2d6f46560ff52e25527d8ba176bc19..98b90409c42be295123e181a6cb6223a9e3eab74 100644 (file)
@@ -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 */
index 3074a9c3228f2220afd754def23d3dfb7fe857d4..9d0cb72acdf03c89f68c990363b15891186a45d8 100644 (file)
--- 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) {
index 9aca9b07f46aeef0da5fb3a21bfeffb802541671..7912fd17895c5be9608f8aab8bad6d05ec2e8208 100644 (file)
@@ -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 */
index bdf3acb825ea81f830f649600a55777b782aaa57..1ca7f0d277edd1801103d3407db39ea67e01f3b3 100644 (file)
@@ -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);
index ead443e629cd094a752bef6a03371d4ee3490ce5..96c611731377f6292670e65e0d0bcd25060f411d 100644 (file)
@@ -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 */