/* NetHack 3.7 mcastu.c $NHDT-Date: 1705428596 2024/01/16 18:09:56 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.95 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Robert Patrick Rankin, 2011. */ /* NetHack may be freely redistributed. See license for details. */ #include "hack.h" /* monster mage spells */ enum mcast_mage_spells { MGC_PSI_BOLT = 0, MGC_CURE_SELF, MGC_HASTE_SELF, MGC_STUN_YOU, MGC_DISAPPEAR, MGC_WEAKEN_YOU, MGC_DESTRY_ARMR, MGC_CURSE_ITEMS, MGC_AGGRAVATION, MGC_SUMMON_MONS, MGC_CLONE_WIZ, MGC_DEATH_TOUCH }; /* monster cleric spells */ enum mcast_cleric_spells { CLC_OPEN_WOUNDS = 0, CLC_CURE_SELF, CLC_CONFUSE_YOU, CLC_PARALYZE, CLC_BLIND_YOU, CLC_INSECTS, CLC_CURSE_ITEMS, CLC_LIGHTNING, CLC_FIRE_PILLAR, CLC_GEYSER }; staticfn void cursetxt(struct monst *, boolean); staticfn int choose_magic_spell(int); staticfn int choose_clerical_spell(int); staticfn int m_cure_self(struct monst *, int); staticfn void cast_wizard_spell(struct monst *, int, int); staticfn void cast_cleric_spell(struct monst *, int, int); staticfn boolean is_undirected_spell(unsigned int, int); staticfn boolean spell_would_be_useless(struct monst *, unsigned int, int); /* feedback when frustrated monster couldn't cast a spell */ staticfn void cursetxt(struct monst *mtmp, boolean undirected) { if (canseemon(mtmp) && couldsee(mtmp->mx, mtmp->my)) { const char *point_msg; /* spellcasting monsters are impolite */ if (undirected) point_msg = "all around, then curses"; else if ((Invis && !perceives(mtmp->data) && (mtmp->mux != u.ux || mtmp->muy != u.uy)) || is_obj_mappear(&gy.youmonst, STRANGE_OBJECT) || u.uundetected) point_msg = "and curses in your general direction"; else if (Displaced && (mtmp->mux != u.ux || mtmp->muy != u.uy)) point_msg = "and curses at your displaced image"; else point_msg = "at you, then curses"; pline_xy(mtmp->mx, mtmp->my, "%s points %s.", Monnam(mtmp), point_msg); } else if ((!(gm.moves % 4) || !rn2(4))) { if (!Deaf) Norep("You hear a mumbled curse."); /* Deaf-aware */ } } /* convert a level-based random selection into a specific mage spell; inappropriate choices will be screened out by spell_would_be_useless() */ staticfn int choose_magic_spell(int spellval) { /* for 3.4.3 and earlier, val greater than 22 selected default spell */ while (spellval > 24 && rn2(25)) spellval = rn2(spellval); switch (spellval) { case 24: case 23: if (Antimagic || Hallucination) return MGC_PSI_BOLT; /*FALLTHRU*/ case 22: case 21: case 20: return MGC_DEATH_TOUCH; case 19: case 18: return MGC_CLONE_WIZ; case 17: case 16: case 15: return MGC_SUMMON_MONS; case 14: case 13: return MGC_AGGRAVATION; case 12: case 11: case 10: return MGC_CURSE_ITEMS; case 9: case 8: return MGC_DESTRY_ARMR; case 7: case 6: return MGC_WEAKEN_YOU; case 5: case 4: return MGC_DISAPPEAR; case 3: return MGC_STUN_YOU; case 2: return MGC_HASTE_SELF; case 1: return MGC_CURE_SELF; case 0: default: return MGC_PSI_BOLT; } } /* convert a level-based random selection into a specific cleric spell */ staticfn int choose_clerical_spell(int spellnum) { /* for 3.4.3 and earlier, num greater than 13 selected the default spell */ while (spellnum > 15 && rn2(16)) spellnum = rn2(spellnum); switch (spellnum) { case 15: case 14: if (rn2(3)) return CLC_OPEN_WOUNDS; /*FALLTHRU*/ case 13: return CLC_GEYSER; case 12: return CLC_FIRE_PILLAR; case 11: return CLC_LIGHTNING; case 10: case 9: return CLC_CURSE_ITEMS; case 8: return CLC_INSECTS; case 7: case 6: return CLC_BLIND_YOU; case 5: case 4: return CLC_PARALYZE; case 3: case 2: return CLC_CONFUSE_YOU; case 1: return CLC_CURE_SELF; case 0: default: return CLC_OPEN_WOUNDS; } } /* return values: * 1: successful spell * 0: unsuccessful spell */ int castmu( struct monst *mtmp, /* caster */ struct attack *mattk, /* caster's current attack */ boolean thinks_it_foundyou, /* might be mistaken if displaced */ boolean foundyou) /* knows hero's precise location */ { int dmg, ml = mtmp->m_lev; int ret; int spellnum = 0; /* Three cases: * -- monster is attacking you. Search for a useful spell. * -- monster thinks it's attacking you. Search for a useful spell, * without checking for undirected. If the spell found is directed, * it fails with cursetxt() and loss of mspec_used. * -- monster isn't trying to attack. Select a spell once. Don't keep * searching; if that spell is not useful (or if it's directed), * return and do something else. * 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) && ml) { int cnt = 40; do { spellnum = rn2(ml); if (mattk->adtyp == AD_SPEL) spellnum = choose_magic_spell(spellnum); else spellnum = choose_clerical_spell(spellnum); /* not trying to attack? don't allow directed spells */ if (!thinks_it_foundyou) { if (!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 M_ATTK_MISS; } break; } } while (--cnt > 0 && spell_would_be_useless(mtmp, mattk->adtyp, spellnum)); if (cnt == 0) return M_ATTK_MISS; } /* monster unable to cast spells? */ if (mtmp->mcan || mtmp->mspec_used || !ml || m_seenres(mtmp, cvt_adtyp_to_mseenres(mattk->adtyp))) { cursetxt(mtmp, is_undirected_spell(mattk->adtyp, spellnum)); return M_ATTK_MISS; } if (mattk->adtyp == AD_SPEL || mattk->adtyp == AD_CLRC) { /* monst->m_lev is unsigned (uchar), monst->mspec_used is int */ mtmp->mspec_used = (int) ((mtmp->m_lev < 8) ? (10 - mtmp->m_lev) : 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. * * FIXME? * Shouldn't wall of lava have a case similar to wall of water? * And should cold damage hit water or lava instead of missing * even when the caster has targeted the wrong spot? Likewise * for fire mis-aimed at ice. */ if (!foundyou && thinks_it_foundyou && !is_undirected_spell(mattk->adtyp, spellnum)) { pline_xy(mtmp->mx, mtmp->my, "%s casts a spell at %s!", canseemon(mtmp) ? Monnam(mtmp) : "Something", is_waterwall(mtmp->mux, mtmp->muy) ? "empty water" : "thin air"); return M_ATTK_MISS; } nomul(0); if (rn2(ml * 10) < (mtmp->mconf ? 100 : 20)) { /* fumbled attack */ Soundeffect(se_air_crackles, 60); if (canseemon(mtmp) && !Deaf) { set_msg_xy(mtmp->mx, mtmp->my); pline_The("air crackles around %s.", mon_nam(mtmp)); } return M_ATTK_MISS; } if (canspotmon(mtmp) || !is_undirected_spell(mattk->adtyp, spellnum)) { pline_xy(mtmp->mx, mtmp->my, "%s casts a spell%s!", canspotmon(mtmp) ? Monnam(mtmp) : "Something", is_undirected_spell(mattk->adtyp, spellnum) ? "" : (Invis && !perceives(mtmp->data) && !u_at(mtmp->mux, mtmp->muy)) ? " at a spot near you" : (Displaced && !u_at(mtmp->mux, mtmp->muy)) ? " at your displaced image" : " at you"); } /* * As these are spells, the damage is related to the level * of the monster casting the spell. */ 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 M_ATTK_MISS; } } 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 = M_ATTK_HIT; /* * FIXME: none of these hit the steed when hero is riding, nor do * they inflict damage on carried items. */ switch (mattk->adtyp) { case AD_FIRE: pline("You're enveloped in flames."); if (Fire_resistance) { shieldeff(u.ux, u.uy); pline("But you resist the effects."); monstseesu(M_SEEN_FIRE); dmg = 0; } else { monstunseesu(M_SEEN_FIRE); } burn_away_slime(); /* burn up flammable items on the floor, melt ice terrain */ mon_spell_hits_spot(mtmp, AD_FIRE, u.ux, u.uy); break; case AD_COLD: pline("You're covered in frost."); if (Cold_resistance) { shieldeff(u.ux, u.uy); pline("But you resist the effects."); monstseesu(M_SEEN_COLD); dmg = 0; } else { monstunseesu(M_SEEN_COLD); } /* freeze water or lava terrain */ /* FIXME: mon_spell_hits_spot() uses zap_over_floor(); unlike with * fire, it does not target susceptible floor items with cold */ mon_spell_hits_spot(mtmp, AD_COLD, u.ux, u.uy); break; case AD_MAGM: You("are hit by a shower of missiles!"); if (Antimagic) { shieldeff(u.ux, u.uy); pline_The("missiles bounce off!"); monstseesu(M_SEEN_MAGR); dmg = 0; } else { dmg = d((int) mtmp->m_lev / 2 + 1, 6); monstunseesu(M_SEEN_MAGR); } /* shower of magic missiles scuffs an engraving */ mon_spell_hits_spot(mtmp, AD_MAGM, u.ux, u.uy); 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; } } /* switch */ if (dmg) mdamageu(mtmp, dmg); return ret; } staticfn int m_cure_self(struct monst *mtmp, int dmg) { if (mtmp->mhp < mtmp->mhpmax) { if (canseemon(mtmp)) pline("%s looks better.", Monnam(mtmp)); /* note: player healing does 6d4; this used to do 1d8 */ if ((mtmp->mhp += d(3, 6)) > mtmp->mhpmax) mtmp->mhp = mtmp->mhpmax; dmg = 0; } return dmg; } /* unlike the finger of death spell which behaves like a wand of death, this monster spell only attacks the hero */ void touch_of_death(struct monst *mtmp) { char kbuf[BUFSZ]; int dmg = 50 + d(8, 6); int drain = dmg / 2; /* if we get here, we know that hero isn't magic resistant and isn't poly'd into an undead or demon */ You_feel("drained..."); (void) death_inflicted_by(kbuf, "the touch of death", mtmp); if (Upolyd) { u.mh = 0; rehumanize(); /* fatal iff Unchanging */ } else if (drain >= u.uhpmax) { gk.killer.format = KILLED_BY; Strcpy(gk.killer.name, kbuf); done(DIED); } else { u.uhpmax -= drain; losehp(dmg, kbuf, KILLED_BY); } gk.killer.name[0] = '\0'; /* not killed if we get here... */ } /* give a reason for death by some monster spells */ char * death_inflicted_by( char *outbuf, /* assumed big enough; pm_names are short */ const char *deathreason, /* cause of death */ struct monst *mtmp) /* monster who caused it */ { Strcpy(outbuf, deathreason); if (mtmp) { struct permonst *mptr = mtmp->data, *champtr = (ismnum(mtmp->cham)) ? &mons[mtmp->cham] : mptr; const char *realnm = pmname(champtr, Mgender(mtmp)), *fakenm = pmname(mptr, Mgender(mtmp)); /* greatly simplified extract from done_in_by(), primarily for reason for death due to 'touch of death' spell; if mtmp is shape changed, it won't be a vampshifter or mimic since they can't cast spells */ if (!type_is_pname(champtr) && !the_unique_pm(mptr)) realnm = an(realnm); Sprintf(eos(outbuf), " inflicted by %s%s", the_unique_pm(mptr) ? "the " : "", realnm); if (champtr != mptr) Sprintf(eos(outbuf), " imitating %s", an(fakenm)); } return outbuf; } /* * 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 spellnum should always be a valid undirected spell. If you modify either of these, be sure to change is_undirected_spell() and spell_would_be_useless(). */ staticfn void cast_wizard_spell(struct monst *mtmp, int dmg, int spellnum) { if (dmg == 0 && !is_undirected_spell(AD_SPEL, spellnum)) { impossible("cast directed wizard spell (%d) with dmg=0?", spellnum); return; } switch (spellnum) { case MGC_DEATH_TOUCH: pline("Oh no, %s's using the touch of death!", mhe(mtmp)); if (nonliving(gy.youmonst.data) || is_demon(gy.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 { touch_of_death(mtmp); } monstunseesu(M_SEEN_MAGR); } else { if (Antimagic) { shieldeff(u.ux, u.uy); monstseesu(M_SEEN_MAGR); } pline("Lucky for you, it didn't work!"); } dmg = 0; break; case MGC_CLONE_WIZ: if (mtmp->iswiz && gc.context.no_of_wizards == 1) { pline("Double Trouble..."); clonewiz(); dmg = 0; } else impossible("bad wizard cloning?"); break; case MGC_SUMMON_MONS: { int count = nasty(mtmp); if (!count) { ; /* nothing was created? */ } else if (mtmp->iswiz) { SetVoice(mtmp, 0, 80, 0); verbalize("Destroy the thief, my pet%s!", plur(count)); } else { boolean one = (count == 1); const char *mappear = one ? "A monster appears" : "Monsters appear"; /* messages not quite right if plural monsters created but only a single monster is seen */ if (Invis && !perceives(mtmp->data) && (mtmp->mux != u.ux || mtmp->muy != u.uy)) pline("%s %s a spot near you!", mappear, one ? "at" : "around"); else if (Displaced && (mtmp->mux != u.ux || mtmp->muy != u.uy)) pline("%s %s your displaced image!", mappear, one ? "by" : "around"); else pline("%s from nowhere!", mappear); } dmg = 0; break; } case MGC_AGGRAVATION: You_feel("that monsters are aware of your presence."); aggravate(); dmg = 0; break; case MGC_CURSE_ITEMS: You_feel("as if you need some help."); rndcurse(); dmg = 0; break; case MGC_DESTRY_ARMR: if (Antimagic) { shieldeff(u.ux, u.uy); monstseesu(M_SEEN_MAGR); pline("A field of force surrounds you!"); } else if (!destroy_arm(some_armor(&gy.youmonst))) { Your("skin itches."); } else { /* monsters only realize you aren't magic-protected if armor is actually destroyed */ monstunseesu(M_SEEN_MAGR); } dmg = 0; break; case MGC_WEAKEN_YOU: /* drain strength */ if (Antimagic) { shieldeff(u.ux, u.uy); monstseesu(M_SEEN_MAGR); You_feel("momentarily weakened."); } else { char kbuf[BUFSZ]; You("suddenly feel weaker!"); dmg = mtmp->m_lev - 6; if (dmg < 1) /* paranoia since only chosen when m_lev is high */ dmg = 1; if (Half_spell_damage) dmg = (dmg + 1) / 2; losestr(rnd(dmg), death_inflicted_by(kbuf, "strength loss", mtmp), KILLED_BY); gk.killer.name[0] = '\0'; /* not killed if we get here... */ monstunseesu(M_SEEN_MAGR); } dmg = 0; break; case MGC_DISAPPEAR: /* makes self invisible */ if (!mtmp->minvis && !mtmp->invis_blkd) { if (canseemon(mtmp)) pline("%s suddenly %s!", Monnam(mtmp), !See_invisible ? "disappears" : "becomes transparent"); mon_set_minvis(mtmp); if (cansee(mtmp->mx, mtmp->my) && !canspotmon(mtmp)) map_invisible(mtmp->mx, mtmp->my); dmg = 0; } else impossible("no reason for monster to cast disappear spell?"); break; case MGC_STUN_YOU: if (Antimagic || Free_action) { shieldeff(u.ux, u.uy); monstseesu(M_SEEN_MAGR); if (!Stunned) You_feel("momentarily disoriented."); make_stunned(1L, FALSE); } else { You(Stunned ? "struggle to keep your balance." : "reel..."); dmg = d(ACURR(A_DEX) < 12 ? 6 : 4, 4); if (Half_spell_damage) dmg = (dmg + 1) / 2; make_stunned((HStun & TIMEOUT) + (long) dmg, FALSE); monstunseesu(M_SEEN_MAGR); } dmg = 0; break; case MGC_HASTE_SELF: mon_adjust_speed(mtmp, 1, (struct obj *) 0); dmg = 0; break; case MGC_CURE_SELF: dmg = m_cure_self(mtmp, dmg); break; case MGC_PSI_BOLT: /* prior to 3.4.0 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); monstseesu(M_SEEN_MAGR); dmg = (dmg + 1) / 2; } else { monstunseesu(M_SEEN_MAGR); } 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; default: impossible("mcastu: invalid magic spell (%d)", spellnum); dmg = 0; break; } if (dmg) mdamageu(mtmp, dmg); } DISABLE_WARNING_FORMAT_NONLITERAL staticfn void cast_cleric_spell(struct monst *mtmp, int dmg, int spellnum) { int orig_dmg = 0; if (dmg == 0 && !is_undirected_spell(AD_CLRC, spellnum)) { impossible("cast directed cleric spell (%d) with dmg=0?", spellnum); return; } switch (spellnum) { case CLC_GEYSER: /* this is physical damage (force not heat), * not magical damage or fire damage */ pline("A sudden geyser slams into you from nowhere!"); dmg = d(8, 6); if (Half_physical_damage) dmg = (dmg + 1) / 2; #if 0 /* since inventory items aren't affected, don't include this */ /* make floor items wet */ water_damage_chain(level.objects[u.ux][u.uy], TRUE); #endif break; case CLC_FIRE_PILLAR: pline("A pillar of fire strikes all around you!"); orig_dmg = dmg = d(8, 6); if (Fire_resistance) { shieldeff(u.ux, u.uy); monstseesu(M_SEEN_FIRE); dmg = 0; } else { monstunseesu(M_SEEN_FIRE); } if (Half_spell_damage) dmg = (dmg + 1) / 2; burn_away_slime(); (void) burnarmor(&gy.youmonst); /* item destruction dmg */ (void) destroy_items(&gy.youmonst, AD_FIRE, orig_dmg); ignite_items(gi.invent); /* burn up flammable items on the floor, melt ice terrain */ mon_spell_hits_spot(mtmp, AD_FIRE, u.ux, u.uy); break; case CLC_LIGHTNING: { boolean reflects; Soundeffect(se_bolt_of_lightning, 80); pline("A bolt of lightning strikes down at you from above!"); reflects = ureflects("It bounces off your %s%s.", ""); orig_dmg = dmg = d(8, 6); if (reflects || Shock_resistance) { shieldeff(u.ux, u.uy); dmg = 0; if (reflects) { monstseesu(M_SEEN_REFL); break; } monstunseesu(M_SEEN_REFL); monstseesu(M_SEEN_ELEC); } else { monstunseesu(M_SEEN_ELEC | M_SEEN_REFL); } if (Half_spell_damage) dmg = (dmg + 1) / 2; (void) destroy_items(&gy.youmonst, AD_ELEC, orig_dmg); /* lightning might destroy iron bars if hero is on such a spot; reflection protects terrain here [execution won't get here due to 'if (reflects) break' above] but hero resistance doesn't; do this before maybe blinding the hero via flashburn() */ mon_spell_hits_spot(mtmp, AD_ELEC, u.ux, u.uy); /* blind hero; no effect if already blind */ (void) flashburn((long) rnd(100), TRUE); break; } case CLC_CURSE_ITEMS: You_feel("as if you need some help."); rndcurse(); dmg = 0; break; case CLC_INSECTS: { /* Try for insects, and if there are none left, go for (sticks to) snakes. -3. */ struct permonst *pm = mkclass(S_ANT, 0); struct monst *mtmp2 = (struct monst *) 0; char whatbuf[QBUFSZ], let = (pm ? S_ANT : S_SNAKE); boolean success = FALSE, seecaster; int i, quan, oldseen, newseen; coord bypos; const char *fmt, *what; oldseen = monster_census(TRUE); quan = (mtmp->m_lev < 2) ? 1 : rnd((int) mtmp->m_lev / 2); if (quan < 3) quan = 3; for (i = 0; i <= quan; i++) { if (!enexto(&bypos, mtmp->mux, mtmp->muy, mtmp->data)) break; if ((pm = mkclass(let, 0)) != 0 && (mtmp2 = makemon(pm, bypos.x, bypos.y, MM_ANGRY | MM_NOMSG)) != 0) { success = TRUE; mtmp2->msleeping = mtmp2->mpeaceful = mtmp2->mtame = 0; set_malign(mtmp2); } } newseen = monster_census(TRUE); /* not canspotmon() which includes unseen things sensed via warning */ seecaster = canseemon(mtmp) || tp_sensemon(mtmp) || Detect_monsters; what = (let == S_SNAKE) ? "snakes" : "insects"; if (Hallucination) what = makeplural(bogusmon(whatbuf, (char *) 0)); fmt = 0; if (!seecaster) { if (newseen <= oldseen || Unaware) { /* unseen caster fails or summons unseen critters, or unconscious hero ("You dream that you hear...") */ You_hear("someone summoning %s.", what); } else { char *arg; if (what != whatbuf) what = strcpy(whatbuf, what); /* unseen caster summoned seen critter(s) */ arg = (newseen == oldseen + 1) ? an(makesingular(what)) : whatbuf; if (!Deaf) { Soundeffect(se_someone_summoning, 100); You_hear("someone summoning something, and %s %s.", arg, vtense(arg, "appear")); } else { pline("%s %s.", upstart(arg), vtense(arg, "appear")); } } /* seen caster, possibly producing unseen--or just one--critters; hero is told what the caster is doing and doesn't necessarily observe complete accuracy of that caster's results (in other words, no need to fuss with visibility or singularization; player is told what's happening even if hero is unconscious) */ } else if (!success) { fmt = "%s casts at a clump of sticks, but nothing happens.%s"; what = ""; } else if (let == S_SNAKE) { fmt = "%s transforms a clump of sticks into %s!"; } else if (Invis && !perceives(mtmp->data) && (mtmp->mux != u.ux || mtmp->muy != u.uy)) { fmt = "%s summons %s around a spot near you!"; } else if (Displaced && (mtmp->mux != u.ux || mtmp->muy != u.uy)) { fmt = "%s summons %s around your displaced image!"; } else { fmt = "%s summons %s!"; } if (fmt) pline(fmt, Monnam(mtmp), what); dmg = 0; break; } case CLC_BLIND_YOU: /* note: resists_blnd() doesn't apply here */ if (!Blinded) { int num_eyes = eyecount(gy.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) Your1(vision_clears); dmg = 0; } else impossible("no reason for monster to cast blindness spell?"); break; case CLC_PARALYZE: if (Antimagic || Free_action) { shieldeff(u.ux, u.uy); monstseesu(M_SEEN_MAGR); if (gm.multi >= 0) You("stiffen briefly."); dmg = 1; /* to produce nomul(-1), not actual damage */ } else { if (gm.multi >= 0) You("are frozen in place!"); dmg = 4 + (int) mtmp->m_lev; if (Half_spell_damage) dmg = (dmg + 1) / 2; monstunseesu(M_SEEN_MAGR); } nomul(-dmg); gm.multi_reason = "paralyzed by a monster"; gn.nomovemsg = 0; dmg = 0; break; case CLC_CONFUSE_YOU: if (Antimagic) { shieldeff(u.ux, u.uy); monstseesu(M_SEEN_MAGR); You_feel("momentarily dizzy."); } else { boolean oldprop = !!Confusion; dmg = (int) mtmp->m_lev; if (Half_spell_damage) dmg = (dmg + 1) / 2; make_confused(HConfusion + dmg, TRUE); if (Hallucination) You_feel("%s!", oldprop ? "trippier" : "trippy"); else You_feel("%sconfused!", oldprop ? "more " : ""); monstunseesu(M_SEEN_MAGR); } dmg = 0; break; case CLC_CURE_SELF: dmg = m_cure_self(mtmp, dmg); break; case CLC_OPEN_WOUNDS: if (Antimagic) { shieldeff(u.ux, u.uy); monstseesu(M_SEEN_MAGR); dmg = (dmg + 1) / 2; } else { monstunseesu(M_SEEN_MAGR); } 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 Your("body is covered with painful wounds!"); break; default: impossible("mcastu: invalid clerical spell (%d)", spellnum); dmg = 0; break; } if (dmg) mdamageu(mtmp, dmg); } RESTORE_WARNING_FORMAT_NONLITERAL staticfn boolean is_undirected_spell(unsigned int adtyp, int spellnum) { if (adtyp == AD_SPEL) { switch (spellnum) { case MGC_CLONE_WIZ: case MGC_SUMMON_MONS: case MGC_AGGRAVATION: case MGC_DISAPPEAR: case MGC_HASTE_SELF: case MGC_CURE_SELF: return TRUE; default: break; } } else if (adtyp == AD_CLRC) { switch (spellnum) { case CLC_INSECTS: case CLC_CURE_SELF: return TRUE; default: break; } } return FALSE; } /* Some spells are useless under some circumstances. */ staticfn boolean spell_would_be_useless(struct monst *mtmp, unsigned int adtyp, int spellnum) { /* Some spells don't require the player to really be there and can be cast * by the monster when you're invisible, yet still shouldn't be cast when * the monster doesn't even think you're there. * This check isn't quite right because it always uses your real position. * We really want something like "if the monster could see mux, muy". */ boolean mcouldseeu = couldsee(mtmp->mx, mtmp->my); if (adtyp == AD_SPEL) { /* aggravate monsters, etc. won't be cast by peaceful monsters */ if (mtmp->mpeaceful && (spellnum == MGC_AGGRAVATION || spellnum == MGC_SUMMON_MONS || spellnum == MGC_CLONE_WIZ)) return TRUE; /* haste self when already fast */ if (mtmp->permspeed == MFAST && spellnum == MGC_HASTE_SELF) return TRUE; /* invisibility when already invisible */ if ((mtmp->minvis || mtmp->invis_blkd) && spellnum == MGC_DISAPPEAR) return TRUE; /* peaceful monster won't cast invisibility if you can't see invisible, same as when monsters 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 == MGC_DISAPPEAR) return TRUE; /* healing when already healed */ if (mtmp->mhp == mtmp->mhpmax && spellnum == MGC_CURE_SELF) return TRUE; /* don't summon monsters if it doesn't think you're around */ if (!mcouldseeu && (spellnum == MGC_SUMMON_MONS || (!mtmp->iswiz && spellnum == MGC_CLONE_WIZ))) return TRUE; if ((!mtmp->iswiz || gc.context.no_of_wizards > 1) && spellnum == MGC_CLONE_WIZ) return TRUE; /* aggravation (global wakeup) when everyone is already active */ if (spellnum == MGC_AGGRAVATION) { /* if nothing needs to be awakened then this spell is useless but caster might not realize that [chance to pick it then must be very small otherwise caller's many retry attempts will eventually end up picking it too often] */ if (!has_aggravatables(mtmp)) return rn2(100) ? TRUE : FALSE; } } else if (adtyp == AD_CLRC) { /* summon insects/sticks to snakes won't be cast by peaceful monsters */ if (mtmp->mpeaceful && spellnum == CLC_INSECTS) return TRUE; /* healing when already healed */ if (mtmp->mhp == mtmp->mhpmax && spellnum == CLC_CURE_SELF) return TRUE; /* don't summon insects if it doesn't think you're around */ if (!mcouldseeu && spellnum == CLC_INSECTS) return TRUE; /* blindness spell on blinded player */ if (Blinded && spellnum == CLC_BLIND_YOU) return TRUE; } return FALSE; } /* monster uses spell (ranged) */ int buzzmu(struct monst *mtmp, struct attack *mattk) { /* don't print constant stream of curse messages for 'normal' spellcasting monsters at range */ if (!BZ_VALID_ADTYP(mattk->adtyp)) return M_ATTK_MISS; if (mtmp->mcan || m_seenres(mtmp, cvt_adtyp_to_mseenres(mattk->adtyp))) { cursetxt(mtmp, FALSE); return M_ATTK_MISS; } if (lined_up(mtmp) && rn2(3)) { nomul(0); if (canseemon(mtmp)) pline("%s zaps you with a %s!", Monnam(mtmp), flash_str(BZ_OFS_AD(mattk->adtyp), FALSE)); gb.buzzer = mtmp; buzz(BZ_M_SPELL(BZ_OFS_AD(mattk->adtyp)), (int) mattk->damn, mtmp->mx, mtmp->my, sgn(gt.tbx), sgn(gt.tby)); gb.buzzer = 0; return M_ATTK_HIT; } return M_ATTK_MISS; } /*mcastu.c*/