/* SCCS Id: @(#)spell.c 3.3 2000/01/10 */ /* Copyright (c) M. Stephenson 1988 */ /* NetHack may be freely redistributed. See license for details. */ #include "hack.h" static NEARDATA schar delay; /* moves left for this spell */ static NEARDATA struct obj *book; /* last/current book being xscribed */ /* spellmenu arguments; 0 thru n-1 used as spl_book[] index when swapping */ #define SPELLMENU_CAST (-2) #define SPELLMENU_VIEW (-1) #define KEEN 20000 #define MAX_SPELL_STUDY 3 #define incrnknow(spell) spl_book[spell].sp_know = KEEN #define spellev(spell) spl_book[spell].sp_lev #define spellname(spell) OBJ_NAME(objects[spellid(spell)]) #define spellet(spell) \ ((char)((spell < 26) ? ('a' + spell) : ('A' + spell - 26))) static int FDECL(spell_let_to_idx, (CHAR_P)); static void FDECL(cursed_book, (int)); static void FDECL(deadbook, (struct obj *)); STATIC_PTR int NDECL(learn); static boolean FDECL(getspell, (int *)); static boolean FDECL(dospellmenu, (const char *,int,int *)); static int FDECL(percent_success, (int)); static int NDECL(throwspell); static void NDECL(cast_protection); static const char *FDECL(spelltypemnemonic, (int)); static int FDECL(isqrt, (int)); /* The roles[] table lists the role-specific values for tuning * percent_success(). * * Reasoning: * spelbase, spelheal: * Arc are aware of magic through historical research * Bar abhor magic (Conan finds it "interferes with his animal instincts") * Cav are ignorant to magic * Hea are very aware of healing magic through medical research * Kni are moderately aware of healing from Paladin training * Mon use magic to attack and defend in lieu of weapons and armor * Pri are very aware of healing magic through theological research * Ran avoid magic, preferring to fight unseen and unheard * Rog are moderately aware of magic through trickery * Sam have limited magical awareness, prefering meditation to conjuring * Tou are aware of magic from all the great films they have seen * Val have limited magical awareness, prefering fighting * Wiz are trained mages * * The arms penalty is lessened for trained fighters Bar, Kni, Ran, * Sam, Val - * the penalty is its metal interference, not encumberance. * The `spelspec' is a single spell which is fundamentally easier * for that role to cast. * * spelspec, spelsbon: * Arc map masters (SPE_MAGIC_MAPPING) * Bar fugue/berserker (SPE_HASTE_SELF) * Cav born to dig (SPE_DIG) * Hea to heal (SPE_CURE_SICKNESS) * Kni to turn back evil (SPE_TURN_UNDEAD) * Mon to preserve their abilities (SPE_RESTORE_ABILITY) * Pri to bless (SPE_REMOVE_CURSE) * Ran to hide (SPE_INVISIBILITY) * Rog to find loot (SPE_DETECT_TREASURE) * Sam to be At One (SPE_CLAIRVOYANCE) * Tou to smile (SPE_CHARM_MONSTER) * Val control the cold (SPE_CONE_OF_COLD) * Wiz all really, but SPE_MAGIC_MISSILE is their party trick * * See percent_success() below for more comments. * * uarmbon, uarmsbon, uarmhbon, uarmgbon, uarmfbon: * Fighters find body armour & shield a little less limiting. * Headgear, Gauntlets and Footwear are not role-specific (but * still have an effect, except helm of brilliance, which is designed * to permit magic-use). */ #define uarmhbon 4 /* Metal helmets interfere with the mind */ #define uarmgbon 6 /* Casting channels through the hands */ #define uarmfbon 2 /* All metal interferes to some degree */ /* since the spellbook itself doesn't blow up, don't say just "explodes" */ static const char explodes[] = "radiates explosive energy"; /* convert a letter into a number in the range 0..51, or -1 if not a letter */ static int spell_let_to_idx(ilet) char ilet; { int indx; indx = ilet - 'a'; if (indx >= 0 && indx < 26) return indx; indx = ilet - 'A'; if (indx >= 0 && indx < 26) return indx + 26; return -1; } static void cursed_book(lev) register int lev; { switch(rn2(lev)) { case 0: You_feel("a wrenching sensation."); tele(); /* teleport him */ break; case 1: You_feel("threatened."); aggravate(); break; case 2: make_blinded(Blinded + rn1(100,250),TRUE); break; case 3: take_gold(); break; case 4: pline("These runes were just too much to comprehend."); make_confused(HConfusion + rn1(7,16),FALSE); break; case 5: pline_The("book was coated with contact poison!"); if (uarmg) { if (uarmg->oerodeproof || !is_corrodeable(uarmg)) { Your("gloves seem unaffected."); } else if (uarmg->oeroded2 < MAX_ERODE) { Your("gloves corrode%s!", uarmg->oeroded2+1 == MAX_ERODE ? " completely" : uarmg->oeroded2 ? " further" : ""); uarmg->oeroded2++; } else Your("gloves %s completely corroded.", Blind ? "feel" : "look"); break; } losestr(Poison_resistance ? rn1(2,1) : rn1(4,3)); losehp(rnd(Poison_resistance ? 6 : 10), "contact-poisoned spellbook", KILLED_BY_AN); break; case 6: if(Antimagic) { shieldeff(u.ux, u.uy); pline_The("book %s, but you are unharmed!", explodes); } else { pline("As you read the book, it %s in your %s!", explodes, body_part(FACE)); losehp (2*rnd(10)+5, "exploding rune", KILLED_BY_AN); } break; default: rndcurse(); break; } return; } /* special effects for The Book of the Dead */ static void deadbook(book2) struct obj *book2; { struct monst *mtmp, *mtmp2; coord mm; You("turn the pages of the Book of the Dead..."); makeknown(SPE_BOOK_OF_THE_DEAD); /* KMH -- Need ->known to avoid "_a_ Book of the Dead" */ book2->known = 1; if(invocation_pos(u.ux, u.uy) && !On_stairs(u.ux, u.uy)) { register struct obj *otmp; register boolean arti1_primed = FALSE, arti2_primed = FALSE, arti_cursed = FALSE; if(book2->cursed) { pline_The("runes appear scrambled. You can't read them!"); return; } if(!u.uhave.bell || !u.uhave.menorah) { pline("A chill runs down your %s.", body_part(SPINE)); if(!u.uhave.bell) You_hear("a faint chime..."); if(!u.uhave.menorah) pline("Vlad's doppelganger is amused."); return; } for(otmp = invent; otmp; otmp = otmp->nobj) { if(otmp->otyp == CANDELABRUM_OF_INVOCATION && otmp->spe == 7 && otmp->lamplit) { if(!otmp->cursed) arti1_primed = TRUE; else arti_cursed = TRUE; } if(otmp->otyp == BELL_OF_OPENING && (moves - otmp->age) < 5L) { /* you rang it recently */ if(!otmp->cursed) arti2_primed = TRUE; else arti_cursed = TRUE; } } if(arti_cursed) { pline_The("invocation fails!"); pline("At least one of your artifacts is cursed..."); } else if(arti1_primed && arti2_primed) { mkinvokearea(); u.uevent.invoked = 1; } else { /* at least one artifact not prepared properly */ You("have a feeling that %s is amiss...", something); goto raise_dead; } return; } /* when not an invocation situation */ if (book2->cursed) { raise_dead: You("raised the dead!"); /* first maybe place a dangerous adversary */ if (!rn2(3) && ((mtmp = makemon(&mons[PM_MASTER_LICH], u.ux, u.uy, NO_MINVENT)) != 0 || (mtmp = makemon(&mons[PM_NALFESHNEE], u.ux, u.uy, NO_MINVENT)) != 0)) { mtmp->mpeaceful = 0; set_malign(mtmp); } /* next handle the affect on things you're carrying */ (void) unturn_dead(&youmonst); /* last place some monsters around you */ mm.x = u.ux; mm.y = u.uy; mkundead(&mm, TRUE, NO_MINVENT); } else if(book2->blessed) { for(mtmp = fmon; mtmp; mtmp = mtmp2) { mtmp2 = mtmp->nmon; /* tamedog() changes chain */ if(!DEADMONSTER(mtmp) && is_undead(mtmp->data) && cansee(mtmp->mx, mtmp->my)) { mtmp->mpeaceful = TRUE; if(sgn(mtmp->data->maligntyp) == sgn(u.ualign.type) && distu(mtmp->mx, mtmp->my) < 4) if (mtmp->mtame) { if (mtmp->mtame < 20) mtmp->mtame++; } else (void) tamedog(mtmp, (struct obj *)0); else mtmp->mflee = TRUE; } } } else { switch(rn2(3)) { case 0: Your("ancestors are annoyed with you!"); break; case 1: pline_The("headstones in the cemetery begin to move!"); break; default: pline("Oh my! Your name appears in the book!"); } } return; } STATIC_PTR int learn() { int i; short booktype; char splname[BUFSZ]; boolean costly = TRUE; if (delay) { /* not if (delay++), so at end delay == 0 */ delay++; return(1); /* still busy */ } exercise(A_WIS, TRUE); /* you're studying. */ booktype = book->otyp; if(booktype == SPE_BOOK_OF_THE_DEAD) { deadbook(book); return(0); } Sprintf(splname, objects[booktype].oc_name_known ? "\"%s\"" : "the \"%s\" spell", OBJ_NAME(objects[booktype])); for (i = 0; i < MAXSPELL; i++) { if (spellid(i) == booktype) { if (book->spestudied > MAX_SPELL_STUDY) { pline("This spellbook is too faint to be read any more."); book->otyp = booktype = SPE_BLANK_PAPER; } else if (spellknow(i) <= 1000) { Your("knowledge of %s is keener.", splname); incrnknow(i); book->spestudied++; exercise(A_WIS,TRUE); /* extra study */ } else { /* 1000 < spellknow(i) <= MAX_SPELL_STUDY */ You("know %s quite well already.", splname); costly = FALSE; } /* make book become known even when spell is already known, in case amnesia made you forget the book */ makeknown((int)booktype); break; } else if (spellid(i) == NO_SPELL) { spl_book[i].sp_id = booktype; spl_book[i].sp_lev = objects[booktype].oc_level; incrnknow(i); book->spestudied++; You(i > 0 ? "add %s to your repertoire." : "learn %s.", splname); makeknown((int)booktype); break; } } if (i == MAXSPELL) impossible("Too many spells memorized!"); if (book->cursed) { /* maybe a demon cursed it */ cursed_book(objects[booktype].oc_level); } if (costly) check_unpaid(book); book = 0; return(0); } int study_book(spellbook) register struct obj *spellbook; { register int booktype = spellbook->otyp; register boolean confused = (Confusion != 0); boolean too_hard = FALSE; if (delay && spellbook == book && /* handle the sequence: start reading, get interrupted, have book become erased somehow, resume reading it */ booktype != SPE_BLANK_PAPER) { You("continue your efforts to memorize the spell."); } else { /* KMH -- Simplified this code */ if (booktype == SPE_BLANK_PAPER) { pline("This spellbook is all blank."); makeknown(booktype); return(1); } switch (objects[booktype].oc_level) { case 1: case 2: delay = -objects[booktype].oc_delay; break; case 3: case 4: delay = -(objects[booktype].oc_level - 1) * objects[booktype].oc_delay; break; case 5: case 6: delay = -objects[booktype].oc_level * objects[booktype].oc_delay; break; case 7: delay = -8 * objects[booktype].oc_delay; break; default: impossible("Unknown spellbook level %d, book %d;", objects[booktype].oc_level, booktype); return 0; } /* Books are often wiser than their readers (Rus.) */ spellbook->in_use = TRUE; if (!spellbook->blessed && spellbook->otyp != SPE_BOOK_OF_THE_DEAD) { if (spellbook->cursed) { too_hard = TRUE; } else { /* uncursed - chance to fail */ int read_ability = ACURR(A_INT) + 4 + u.ulevel/2 - 2*objects[booktype].oc_level; /* only wizards know if a spell is too difficult */ if (Role_if(PM_WIZARD) && read_ability < 20) { char qbuf[QBUFSZ]; Sprintf(qbuf, "This spellbook is %sdifficult to comprehend. Continue?", (read_ability < 12 ? "very " : "")); if (yn(qbuf) != 'y') { spellbook->in_use = FALSE; return(1); } } /* its up to random luck now */ if (rnd(20) > read_ability) { too_hard = TRUE; } } } if (too_hard) { cursed_book(objects[booktype].oc_level); nomul(delay); /* study time */ delay = 0; if(!rn2(3)) { pline_The("spellbook crumbles to dust!"); if (!objects[spellbook->otyp].oc_name_known && !objects[spellbook->otyp].oc_uname) docall(spellbook); useup(spellbook); } else spellbook->in_use = FALSE; return(1); } else if (confused) { if (!rn2(3) && spellbook->otyp != SPE_BOOK_OF_THE_DEAD) { pline( "Being confused you have difficulties in controlling your actions."); display_nhwindow(WIN_MESSAGE, FALSE); You("accidentally tear the spellbook to pieces."); if (!objects[spellbook->otyp].oc_name_known && !objects[spellbook->otyp].oc_uname) docall(spellbook); useup(spellbook); } else { You( "find yourself reading the first line over and over again."); spellbook->in_use = FALSE; } nomul(delay); delay = 0; return(1); } spellbook->in_use = FALSE; You("begin to %s the runes.", spellbook->otyp == SPE_BOOK_OF_THE_DEAD ? "recite" : "memorize"); } book = spellbook; set_occupation(learn, "studying", 0); return(1); } /* renaming an object usually results in it having a different address; so the sequence start reading, get interrupted, name the book, resume reading would read the "new" book from scratch */ void book_substitution(old_obj, new_obj) struct obj *old_obj, *new_obj; { if (old_obj == book) book = new_obj; } /* called from moveloop() */ void age_spells() { int i; /* * The time relative to the hero (a pass through move * loop) causes all spell knowledge to be decremented. * The hero's speed, rest status, conscious status etc. * does not alter the loss of memory. */ for (i = 0; i < MAXSPELL && spellid(i) != NO_SPELL; i++) if (spellknow(i)) decrnknow(i); return; } /* * Return TRUE if a spell was picked, with the spell index in the return * parameter. Otherwise return FALSE. */ static boolean getspell(spell_no) int *spell_no; { int nspells, idx; char ilet, lets[BUFSZ], qbuf[QBUFSZ]; if (spellid(0) == NO_SPELL) { You("don't know any spells right now."); return FALSE; } if (flags.menu_style == MENU_TRADITIONAL) { /* we know there is at least 1 known spell */ for (nspells = 1; nspells < MAXSPELL && spellid(nspells) != NO_SPELL; nspells++) continue; if (nspells == 1) Strcpy(lets, "a"); else if (nspells < 27) Sprintf(lets, "a-%c", 'a' + nspells - 1); else if (nspells == 27) Sprintf(lets, "a-zA"); else Sprintf(lets, "a-zA-%c", 'A' + nspells - 27); for(;;) { Sprintf(qbuf, "Cast which spell? [%s ?]", lets); if ((ilet = yn_function(qbuf, (char *)0, '\0')) == '?') break; if (index(quitchars, ilet)) return FALSE; idx = spell_let_to_idx(ilet); if (idx >= 0 && idx < nspells) { *spell_no = idx; return TRUE; } else You("don't know that spell."); } } return dospellmenu("Choose which spell to cast", SPELLMENU_CAST, spell_no); } /* the 'Z' command -- cast a spell */ int docast() { int spell_no; if (getspell(&spell_no)) return spelleffects(spell_no, FALSE); return 0; } static const char * spelltypemnemonic(skill) int skill; { switch (skill) { case P_ATTACK_SPELL: return "attack"; case P_HEALING_SPELL: return "healing"; case P_DIVINATION_SPELL: return "divination"; case P_ENCHANTMENT_SPELL: return "enchantment"; case P_CLERIC_SPELL: return "clerical"; case P_ESCAPE_SPELL: return "escape"; case P_MATTER_SPELL: return "matter"; default: impossible("Unknown spell skill, %d;", skill); return ""; } } int spell_skilltype(booktype) int booktype; { return (objects[booktype].oc_skill); } static void cast_protection() { int loglev = 0; int l = u.ulevel; int natac = u.uac - u.uspellprot; int gain; /* loglev=log2(u.ulevel)+1 (1..5) */ while (l) { loglev++; l /= 2; } /* The more u.uspellprot you already have, the less you get, * and the better your natural ac, the less you get. * * LEVEL AC SPELLPROT from sucessive SPE_PROTECTION casts * 1 10 0, 1, 2, 3, 4 * 1 0 0, 1, 2, 3 * 1 -10 0, 1, 2 * 2-3 10 0, 2, 4, 5, 6, 7, 8 * 2-3 0 0, 2, 4, 5, 6 * 2-3 -10 0, 2, 3, 4 * 4-7 10 0, 3, 6, 8, 9, 10, 11, 12 * 4-7 0 0, 3, 5, 7, 8, 9 * 4-7 -10 0, 3, 5, 6 * 7-15 -10 0, 3, 5, 6 * 8-15 10 0, 4, 7, 10, 12, 13, 14, 15, 16 * 8-15 0 0, 4, 7, 9, 10, 11, 12 * 8-15 -10 0, 4, 6, 7, 8 * 16-30 10 0, 5, 9, 12, 14, 16, 17, 18, 19, 20 * 16-30 0 0, 5, 9, 11, 13, 14, 15 * 16-30 -10 0, 5, 8, 9, 10 */ gain = loglev - (int)u.uspellprot / (4 - min(3,(10 - natac)/10)); if (gain > 0) { if (!Blind) { const char *hgolden = hcolor(golden); if (u.uspellprot) pline_The("%s haze around you becomes more dense.", hgolden); else pline_The("%s around you begins to shimmer with %s haze.", /*[ what about being inside solid rock while polyd? ]*/ (Underwater || Is_waterlevel(&u.uz)) ? "water" : "air", an(hgolden)); } u.uspellprot += gain; u.uspmtime = P_SKILL(spell_skilltype(SPE_PROTECTION)) == P_EXPERT ? 20 : 10; if (!u.usptime) u.usptime = u.uspmtime; find_ac(); } else { Your("skin feels warm for a moment."); } } int spelleffects(spell, atme) int spell; boolean atme; { int energy, damage, chance, n, intell; int skill, role_skill; boolean confused = (Confusion != 0); struct obj *pseudo; coord cc; /* * Spell casting no longer affects knowledge of the spell. A * decrement of spell knowledge is done every turn. */ if (spellknow(spell) <= 0) { Your("knowledge of this spell is twisted."); pline("It invokes nightmarish images in your mind..."); make_confused((long)spellev(spell) * 3, FALSE); return(0); } else if (spellknow(spell) <= 100) { You("strain to recall the spell."); } else if (spellknow(spell) <= 1000) { Your("knowledge of this spell is growing faint."); } energy = (spellev(spell) * 5); /* 5 <= energy <= 35 */ if (u.uhunger <= 10 && spellid(spell) != SPE_DETECT_FOOD) { You("are too hungry to cast that spell."); return(0); } else if (ACURR(A_STR) < 4) { You("lack the strength to cast spells."); return(0); } else if(check_capacity( "Your concentration falters while carrying so much stuff.")) { return (1); } else if (!freehand()) { Your("arms are not free to cast!"); return (0); } if (u.uhave.amulet) { You_feel("the amulet draining your energy away."); energy += rnd(2*energy); } if(energy > u.uen) { You("don't have enough energy to cast that spell."); return(0); } else { if (spellid(spell) != SPE_DETECT_FOOD) { int hungr = energy * 2; /* If hero is a wizard, their current intelligence * (bonuses + temporary + current) * affects hunger reduction in casting a spell. * 1. int = 17-18 no reduction * 2. int = 16 1/4 hungr * 3. int = 15 1/2 hungr * 4. int = 1-14 normal reduction * The reason for this is: * a) Intelligence affects the amount of exertion * in thinking. * b) Wizards have spent their life at magic and * understand quite well how to cast spells. */ intell = acurr(A_INT); if (!Role_if(PM_WIZARD)) intell = 10; switch (intell) { case 25: case 24: case 23: case 22: case 21: case 20: case 19: case 18: case 17: hungr = 0; break; case 16: hungr /= 4; break; case 15: hungr /= 2; break; } /* don't put player (quite) into fainting from * casting a spell, particularly since they might * not even be hungry at the beginning; however, * this is low enough that they must eat before * casting anything else except detect food */ if (hungr > u.uhunger-3) hungr = u.uhunger-3; morehungry(hungr); } } chance = percent_success(spell); if (confused || (rnd(100) > chance)) { You("fail to cast the spell correctly."); u.uen -= energy / 2; flags.botl = 1; return(1); } u.uen -= energy; flags.botl = 1; exercise(A_WIS, TRUE); /* pseudo is a temporary "false" object containing the spell stats */ pseudo = mksobj(spellid(spell), FALSE, FALSE); pseudo->blessed = pseudo->cursed = 0; pseudo->quan = 20L; /* do not let useup get it */ /* * Find the skill the hero has in a spell type category. * See spell_skilltype for categories. */ skill = spell_skilltype(pseudo->otyp); role_skill = P_SKILL(skill); switch(pseudo->otyp) { /* * At first spells act as expected. As the hero increases in skill * with the appropriate spell type, some spells increase in their * effects, e.g. more damage, further distance, and so on, without * additional cost to the spellcaster. */ case SPE_CONE_OF_COLD: case SPE_FIREBALL: if (role_skill >= P_SKILLED) { if (throwspell()) { cc.x=u.dx;cc.y=u.dy; n=rnd(8)+1; while(n--) { if(!u.dx && !u.dy && !u.dz) { if ((damage = zapyourself(pseudo, TRUE)) != 0) losehp(damage, self_pronoun("zapped %sself with a spell", "him"), NO_KILLER_PREFIX); } else { explode(u.dx, u.dy, pseudo->otyp - SPE_MAGIC_MISSILE + 10, u.ulevel/2 + 1 + spell_damage_bonus(), 0); } u.dx = cc.x+rnd(3)-2; u.dy = cc.y+rnd(3)-2; if (!isok(u.dx,u.dy) || !cansee(u.dx,u.dy) || IS_STWALL(levl[u.dx][u.dy].typ) || u.uswallow) { /* Spell is reflected back to center */ u.dx = cc.x; u.dy = cc.y; } } } break; } /* else fall through... */ /* these spells are all duplicates of wand effects */ case SPE_FORCE_BOLT: case SPE_SLEEP: case SPE_MAGIC_MISSILE: case SPE_KNOCK: case SPE_SLOW_MONSTER: case SPE_WIZARD_LOCK: case SPE_DIG: case SPE_TURN_UNDEAD: case SPE_POLYMORPH: case SPE_TELEPORT_AWAY: case SPE_CANCELLATION: case SPE_FINGER_OF_DEATH: case SPE_LIGHT: case SPE_DETECT_UNSEEN: case SPE_HEALING: case SPE_EXTRA_HEALING: case SPE_DRAIN_LIFE: case SPE_STONE_TO_FLESH: if (!(objects[pseudo->otyp].oc_dir == NODIR)) { if (atme) u.dx = u.dy = u.dz = 0; else (void) getdir((char *)0); if(!u.dx && !u.dy && !u.dz) { if ((damage = zapyourself(pseudo, TRUE)) != 0) losehp(damage, self_pronoun("zapped %sself with a spell", "him"), NO_KILLER_PREFIX); } else weffects(pseudo); } else weffects(pseudo); break; /* these are all duplicates of scroll effects */ case SPE_REMOVE_CURSE: case SPE_CONFUSE_MONSTER: case SPE_DETECT_FOOD: case SPE_CAUSE_FEAR: /* high skill yields effect equivalent to blessed scroll */ if (role_skill >= P_SKILLED) pseudo->blessed = 1; /* fall through */ case SPE_CHARM_MONSTER: case SPE_MAGIC_MAPPING: case SPE_CREATE_MONSTER: case SPE_IDENTIFY: (void) seffects(pseudo); break; /* these are all duplicates of potion effects */ case SPE_HASTE_SELF: case SPE_DETECT_TREASURE: case SPE_DETECT_MONSTERS: case SPE_LEVITATION: case SPE_RESTORE_ABILITY: /* high skill yields effect equivalent to blessed potion */ if (role_skill >= P_SKILLED) pseudo->blessed = 1; /* fall through */ case SPE_INVISIBILITY: (void) peffects(pseudo); break; case SPE_CURE_BLINDNESS: healup(0, 0, FALSE, TRUE); break; case SPE_CURE_SICKNESS: if (Sick) You("are no longer ill."); if (Slimed) { pline_The("slime disappears!"); Slimed = 0; } healup(0, 0, TRUE, FALSE); break; case SPE_CREATE_FAMILIAR: (void) make_familiar((struct obj *)0, u.ux, u.uy, FALSE); break; case SPE_CLAIRVOYANCE: if (!BClairvoyant) do_vicinity_map(); /* at present, only one thing blocks clairvoyance */ else if (uarmh && uarmh->otyp == CORNUTHAUM) You("sense a pointy hat on top of your %s.", body_part(HEAD)); break; case SPE_PROTECTION: cast_protection(); break; case SPE_JUMPING: if (!jump(max(role_skill,1))) pline(nothing_happens); break; default: impossible("Unknown spell %d attempted.", spell); obfree(pseudo, (struct obj *)0); return(0); } /* gain skill for successful cast */ if (role_skill != P_ISRESTRICTED && role_skill < P_EXPERT) use_skill(skill, spellev(spell)); obfree(pseudo, (struct obj *)0); /* now, get rid of it */ return(1); } /* Choose location where spell takes effect. */ static int throwspell() { coord cc; if (u.uinwater) { pline("You're joking! In this weather?"); return 0; } else if (Is_waterlevel(&u.uz)) { You("had better wait for the sun to come out."); return 0; } pline("Where do you want to cast the spell?"); cc.x = u.ux; cc.y = u.uy; if (getpos(&cc, TRUE, "the desired position") < 0) return 0; /* user pressed ESC */ /* The number of moves from hero to where the spell drops.*/ if (distmin(u.ux, u.uy, cc.x, cc.y) > 10) { pline_The("spell dissipates over the distance!"); return 0; } else if (u.uswallow) { pline_The("spell is cut short!"); exercise(A_WIS, FALSE); /* What were you THINKING! */ u.dx = 0; u.dy = 0; return 1; } else if (!cansee(cc.x, cc.y) || IS_STWALL(levl[cc.x][cc.y].typ)) { Your("mind fails to lock onto that location!"); return 0; } else { u.dx=cc.x; u.dy=cc.y; return 1; } } void losespells() { boolean confused = (Confusion != 0); int n, nzap, i; book = 0; for (n = 0; n < MAXSPELL && spellid(n) != NO_SPELL; n++) continue; if (n) { nzap = rnd(n) + confused ? 1 : 0; if (nzap > n) nzap = n; for (i = n - nzap; i < n; i++) { spellid(i) = NO_SPELL; exercise(A_WIS, FALSE); /* ouch! */ } } } /* the '+' command -- view known spells */ int dovspell() { char qbuf[QBUFSZ]; int splnum, othnum; struct spell spl_tmp; if (spellid(0) == NO_SPELL) You("don't know any spells right now."); else { while (dospellmenu("Currently known spells", SPELLMENU_VIEW, &splnum)) { Sprintf(qbuf, "Reordering spells; swap '%c' with", spellet(splnum)); if (!dospellmenu(qbuf, splnum, &othnum)) break; spl_tmp = spl_book[splnum]; spl_book[splnum] = spl_book[othnum]; spl_book[othnum] = spl_tmp; } } return 0; } static boolean dospellmenu(prompt, splaction, spell_no) const char *prompt; int splaction; /* SPELLMENU_CAST, SPELLMENU_VIEW, or spl_book[] index */ int *spell_no; { winid tmpwin; int i, n, how; char buf[BUFSZ]; menu_item *selected; anything any; tmpwin = create_nhwindow(NHW_MENU); start_menu(tmpwin); any.a_void = 0; /* zero out all bits */ /* * The correct spacing of the columns depends on the * following that (1) the font is monospaced and (2) * that selection letters are pre-pended to the given * string and are of the form "a - ". * * To do it right would require that we implement columns * in the window-ports (say via a tab character). */ Sprintf(buf, "%-20s Level %-12s Fail", " Name", "Category"); add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, buf, MENU_UNSELECTED); for (i = 0; i < MAXSPELL && spellid(i) != NO_SPELL; i++) { Sprintf(buf, "%-20s %2d%s %-12s %3d%%", spellname(i), spellev(i), spellknow(i) ? " " : "*", spelltypemnemonic(spell_skilltype(spellid(i))), 100 - percent_success(i)); any.a_int = i+1; /* must be non-zero */ add_menu(tmpwin, NO_GLYPH, &any, spellet(i), 0, ATR_NONE, buf, (i == splaction) ? MENU_SELECTED : MENU_UNSELECTED); } end_menu(tmpwin, prompt); how = PICK_ONE; if (splaction == SPELLMENU_VIEW && spellid(1) == NO_SPELL) how = PICK_NONE; /* only one spell => nothing to swap with */ n = select_menu(tmpwin, how, &selected); destroy_nhwindow(tmpwin); if (n > 0) { *spell_no = selected[0].item.a_int - 1; /* menu selection for `PICK_ONE' does not de-select any preselected entry */ if (n > 1 && *spell_no == splaction) *spell_no = selected[1].item.a_int - 1; free((genericptr_t)selected); /* default selection of preselected spell means that user chose not to swap it with anything */ if (*spell_no == splaction) return FALSE; return TRUE; } else if (splaction >= 0) { /* explicit de-selection of preselected spell means that user is still swapping but not for the current spell */ *spell_no = splaction; return TRUE; } return FALSE; } /* Integer square root function without using floating point. */ static int isqrt(val) int val; { int rt = 0; int odd = 1; while(val >= odd) { val = val-odd; odd = odd+2; rt = rt + 1; } return rt; } static int percent_success(spell) int spell; { /* Intrinsic and learned ability are combined to calculate * the probability of player's success at cast a given spell. */ int chance, splcaster, special, statused; int difficulty; int skill; /* Calculate intrinsic ability (splcaster) */ splcaster = urole.spelbase; special = urole.spelheal; statused = ACURR(urole.spelstat); if (uarm && is_metallic(uarm)) splcaster += (uarmc && uarmc->otyp == ROBE) ? urole.spelarmr/2 : urole.spelarmr; else if (uarmc && uarmc->otyp == ROBE) splcaster -= urole.spelarmr; if (uarms) splcaster += urole.spelshld; if (uarmh && is_metallic(uarmh) && uarmh->otyp != HELM_OF_BRILLIANCE) splcaster += uarmhbon; if (uarmg && is_metallic(uarmg)) splcaster += uarmgbon; if (uarmf && is_metallic(uarmf)) splcaster += uarmfbon; if (spellid(spell) == urole.spelspec) splcaster += urole.spelsbon; /* `healing spell' bonus */ if (spellid(spell) == SPE_HEALING || spellid(spell) == SPE_EXTRA_HEALING || spellid(spell) == SPE_CURE_BLINDNESS || spellid(spell) == SPE_CURE_SICKNESS || spellid(spell) == SPE_RESTORE_ABILITY || spellid(spell) == SPE_REMOVE_CURSE) splcaster += special; if (splcaster > 20) splcaster = 20; /* Calculate learned ability */ /* Players basic likelihood of being able to cast any spell * is based of their `magic' statistic. (Int or Wis) */ chance = 11 * statused / 2; /* * High level spells are harder. Easier for higher level casters. * The difficulty is based on the hero's level and their skill level * in that spell type. */ skill = P_SKILL(spell_skilltype(spellid(spell)))-1; difficulty= (spellev(spell)-1) * 4 - ((skill * 6) + (u.ulevel/3) + 1); if (difficulty > 0) { /* Player is too low level or unskilled. */ chance -= isqrt(900 * difficulty + 2000); } else { /* Player is above level. Learning continues, but the * law of diminishing returns sets in quickly for * low-level spells. That is, a player quickly gains * no advantage for raising level. */ int learning = 15 * -difficulty / spellev(spell); chance += learning > 20 ? 20 : learning; } /* Clamp the chance: >18 stat and advanced learning only help * to a limit, while chances below "hopeless" only raise the * specter of overflowing 16-bit ints (and permit wearing a * shield to raise the chances :-). */ if (chance < 0) chance = 0; if (chance > 120) chance = 120; /* Wearing anything but a light shield makes it very awkward * to cast a spell. The penalty is not quite so bad for the * player's role-specific spell. */ if (uarms && weight(uarms) > (int) objects[SMALL_SHIELD].oc_weight) { if (spellid(spell) == urole.spelspec) { chance /= 2; } else { chance /= 4; } } /* Finally, chance (based on player intell/wisdom and level) is * combined with ability (based on player intrinsics and * encumberances). No matter how intelligent/wise and advanced * a player is, intrinsics and encumberance can prevent casting; * and no matter how able, learning is always required. */ chance = chance * (20-splcaster) / 15 - splcaster; /* Clamp to percentile */ if (chance > 100) chance = 100; if (chance < 0) chance = 0; return chance; } /* Learn a spell during creation of the initial inventory */ void initialspell(obj) struct obj *obj; { int i; for (i = 0; i < MAXSPELL; i++) { if (spellid(i) == obj->otyp) { pline("Error: Spell %s already known.", OBJ_NAME(objects[obj->otyp])); return; } if (spellid(i) == NO_SPELL) { spl_book[i].sp_id = obj->otyp; spl_book[i].sp_lev = objects[obj->otyp].oc_level; incrnknow(i); return; } } impossible("Too many spells memorized!"); return; } /*spell.c*/