From f4dd9713a0bfe874be697bcf25e9ffefbb482b58 Mon Sep 17 00:00:00 2001 From: jwalz Date: Sat, 5 Jan 2002 21:05:53 +0000 Subject: [PATCH] *** empty log message *** --- src/spell.c | 1177 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1177 insertions(+) create mode 100644 src/spell.c diff --git a/src/spell.c b/src/spell.c new file mode 100644 index 000000000..6cd09d167 --- /dev/null +++ b/src/spell.c @@ -0,0 +1,1177 @@ +/* SCCS Id: @(#)spell.c 3.3 2001/12/03 */ +/* 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_DCL int FDECL(spell_let_to_idx, (CHAR_P)); +STATIC_DCL void FDECL(cursed_book, (int)); +STATIC_DCL void FDECL(deadbook, (struct obj *)); +STATIC_PTR int NDECL(learn); +STATIC_DCL boolean FDECL(getspell, (int *)); +STATIC_DCL boolean FDECL(dospellmenu, (const char *,int,int *)); +STATIC_DCL int FDECL(percent_success, (int)); +STATIC_DCL int NDECL(throwspell); +STATIC_DCL void NDECL(cast_protection); +STATIC_DCL const char *FDECL(spelltypemnemonic, (int)); +STATIC_DCL 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 encumbrance. + * 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_OVL 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_OVL 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) { + if (uarmg->greased) { + grease_protect(uarmg, "gloves", TRUE, &youmonst); + } else { + 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_OVL 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) { + /* the Book operates on nearby monsters even if you're swallowed */ + for(mtmp = fmon; mtmp; mtmp = mtmp2) { + mtmp2 = mtmp->nmon; /* tamedog() changes chain */ + if (DEADMONSTER(mtmp)) continue; + + if (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 monflee(mtmp, 0, FALSE, 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_OVL 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_OVL 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_OVL 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) { + char buf[BUFSZ]; + Sprintf(buf, "zapped %sself with a spell", uhim()); + losehp(damage, buf, NO_KILLER_PREFIX); + } + } else { + explode(u.dx, u.dy, + pseudo->otyp - SPE_MAGIC_MISSILE + 10, + u.ulevel/2 + 1 + spell_damage_bonus(), 0, + (pseudo->otyp == SPE_CONE_OF_COLD) ? + EXPL_FROSTY : EXPL_FIERY); + } + 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) { + char buf[BUFSZ]; + Sprintf(buf, "zapped %sself with a spell", uhim()); + losehp(damage, buf, NO_KILLER_PREFIX); + } + } else weffects(pseudo); + } else weffects(pseudo); + update_inventory(); /* spell may modify inventory */ + 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; + /* flags.botl = 1; -- healup() handles this */ + } + 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_OVL 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_OVL 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_OVL 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_OVL 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))); + skill = max(skill,P_UNSKILLED) - 1; /* unskilled => 0 */ + 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 + * encumbrances). No matter how intelligent/wise and advanced + * a player is, intrinsics and encumbrance 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*/