/*      SCCS Id: @(#)spell.c    3.2     96/08/04        */
/*      Copyright (c) M. Stephenson 1988                          */
/* NetHack may be freely redistributed.  See license for details. */

#include "hack.h"
#include "edog.h"

/* Are now ints */
static NEARDATA int delay;            /* moves left for this spell */
static NEARDATA int end_delay;        /* when to stop studying */
static NEARDATA struct obj *book;       /* last/current book being xscribed */
/* LSZ/WWA wizard patch 7/96 */
/* KMH/WAC -- Increased KEEN from 5000 to 10000 */
/* WAC -- Increased MAX_SPELL_STUDY to 30 */
#define KEEN 		10000	/* memory increase reading the book */
#define CAST_BOOST 	  500	/* memory increase for successful casting */
#define MAX_KNOW 	70000	/* Absolute Max timeout */
#define MAX_CAN_STUDY 	60000	/* Can study while timeout is less than */

#define MAX_STUDY_TIME 	  300	/* Max time for one study session */
#define MAX_SPELL_STUDY    30	/* Uses before spellbook crumbles */

#define spellknow(spell)      spl_book[spell].sp_know 


#define incrnknow(spell)        spl_book[spell].sp_know = ((spl_book[spell].sp_know < 1) ? KEEN \
				: ((spl_book[spell].sp_know + KEEN) > MAX_KNOW) ? MAX_KNOW \
				: spl_book[spell].sp_know + KEEN)
#define boostknow(spell,boost)  spl_book[spell].sp_know = ((spl_book[spell].sp_know + boost > MAX_KNOW) ? MAX_KNOW \
				: spl_book[spell].sp_know + boost)
/* Removed?!!
#define spelluses(spell)        spl_book[spell].sp_uses
#define decrnuses(spell)        spl_book[spell].sp_uses--
*/
#define spellev(spell)          spl_book[spell].sp_lev
#define spellid(spell)          spl_book[spell].sp_id
#define spellname(spell)        OBJ_NAME(objects[spellid(spell)])
#define spellet(spell)  \
	((char)((spell < 26) ? ('a' + spell) : ('A' + spell - 26)))

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, (int, int *));
static int FDECL(percent_success, (int));
/* [max] added more statics */
static void NDECL(cast_protection);
static int FDECL(isqrt, (int));
static const char *FDECL(spelltypemnemonic, (int));

/* The cl_sptmp table lists the class-specific values for tuning
 * percent_success().
 *
 * Reasoning:
 *   splcaster, special:
 *      A are aware of magic through historical research
 *      B abhor magic (Conan finds it "interferes with his animal instincts")
 *      C are ignorant to magic             
 *      E are from a magical realm
 *      H are very aware of healing magic through medical research
 *      K are moderately aware of healing from Paladin training
 *      P are very aware of healing magic through theological research
 *      R are moderately aware of magic through trickery
 *      S have limited magical awareness, prefering meditation to conjuring
 *      T are aware of magic from all the great films they have seen
 *      V have limited magical awareness, prefering fighting
 *      W are trained mages
 *
 *      The arms penalty is lessened for trained fighters B, K, S, V -
 *      the penalty is its metal interference, not encumberance.
 *      The `specspel' is a single spell which is fundamentally easier
 *       for that class to cast.
 *
 *  specspel, specbon:
 *      A map masters (SPE_MAGIC_MAPPING)
 *      B fugue/berserker (SPE_HASTE_SELF)
 *      C born to dig (SPE_DIG)
 *      E infra-like vision (SPE_DETECT_UNSEEN)
 *      H to heal (SPE_CURE_SICKNESS)
 *      K to turn back evil (SPE_TURN_UNDEAD)
 *      P to bless (SPE_REMOVE_CURSE)
 *      R to find loot (SPE_DETECT_TREASURE)
 *      S to be At One (SPE_CLAIRVOYANCE)
 *      T to smile (SPE_CHARM_MONSTER)
 **NEW* V control lightning (SPE_LIGHTNING)
 **NEW* Y guard doors (SPE_KNOCK)
 *      
 *      W 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 class-specific (but
 *      still have an effect, except helm of brilliance, which is designed
 *      to permit magic-use).
 */
#ifndef ROLE_PATCH
static struct sptmp {
	    char        class;          /* key */
	    int         splcaster;      /* base spellcasting ability */
	    int         special;        /* healing spell bonus */
	    int         uarmsbon;       /* penalty for wearing a (small) shield */
	    int         uarmbon;        /* penalty for wearing metal armour */
	    int         statused;       /* which stat is used */
	    int         specspel;       /* spell the class excels at */
	    int         specbon;        /* bonus when casting specspel */
} cl_sptmp[] = {
	    { 'A',  9, 2, 2, 10, A_INT, SPE_MAGIC_MAPPING,   -4 },
	    { 'B', 17, 2, 0,  8, A_INT, SPE_HASTE_SELF,      -4 },
	    { 'C', 17, 2, 1,  8, A_INT, SPE_DIG,             -4 },
	    { 'D',  9, 2, 0, 10, A_INT, SPE_POLYMORPH,       -4 },
	    { 'E',  9, 2, 1, 10, A_INT, SPE_DETECT_UNSEEN,   -4 },
	    { 'F',  5, 2, 2, 10, A_INT, SPE_FIREBALL,        -4 },
	    { 'G', 11, 2, 2, 10, A_INT, SPE_CAUSE_FEAR,      -4 },
	    { 'H',  7,-2, 2, 10, A_WIS, SPE_CURE_SICKNESS,   -4 },
	    { 'I',  5, 2, 2, 10, A_INT, SPE_CONE_OF_COLD,    -4 },
	    { 'K', 13,-2, 0,  9, A_WIS, SPE_TURN_UNDEAD,     -4 },
	    { 'L', 17, 2, 0, 10, A_INT, SPE_CREATE_FAMILIAR, -4 },
	    { 'M',  9,-2, 2, 20, A_WIS, SPE_RESTORE_ABILITY, -4 },
	    { 'N',  5, 2, 2, 10, A_INT, SPE_SUMMON_UNDEAD,   -4 },
	    { 'P',  7,-2, 2, 10, A_WIS, SPE_REMOVE_CURSE,    -4 },
	    { 'R', 13, 2, 2,  9, A_INT, SPE_DETECT_TREASURE, -4 },
	    { 'S', 17, 2, 0,  8, A_INT, SPE_CLAIRVOYANCE,    -4 },
#ifdef TOURIST
	    { 'T',  9, 2, 2, 10, A_INT, SPE_CHARM_MONSTER,   -4 },
#endif
	    { 'U', 13,-2, 0,  9, A_WIS, SPE_TURN_UNDEAD,     -4 },
	    { 'V', 13,-2, 0,  9, A_WIS, SPE_LIGHTNING,       -4 },
	    { 'W',  5, 0, 3, 10, A_INT, SPE_MAGIC_MISSILE,   -4 },
#ifdef YEOMAN
	    { 'Y', 13,-2, 0,  9, A_WIS, SPE_KNOCK,           -4 },
#endif	    
	    {   0, 10, 0, 0,  4, A_INT, 0, -3 }
};
#endif

#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";

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:
		/* [Tom] lowered this (used to be 100,250) */
		make_blinded(Blinded + rn1(50,25),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) {
		    /* Note: at this writing, there are no corrodeable
		     * gloves in the game.  If no one plans on adding
		     * copper gauntlets, most of this could be removed. -3.
		     */
		    if (uarmg->oerodeproof || !is_corrodeable(uarmg)) {
			Your("gloves seem unaffected.");
		    } else if (uarmg->oeroded < MAX_ERODE) {
			Your("gloves corrode%s!",
			     uarmg->oeroded+1 == MAX_ERODE ? " completely" :
			     uarmg->oeroded ? " further" : "");
			uarmg->oeroded++;
		    } 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(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)
			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;
	boolean just_browsing = FALSE; /* for not charging for looking at book */


	if (delay < end_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);
	}

	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 anymore.");
			    book->otyp = booktype = SPE_BLANK_PAPER;
			} else if (spellknow(i) <= MAX_CAN_STUDY) {
			    Your("knowledge of that spell is keener.");
			    incrnknow(i);
			    book->spestudied++;
			    if (end_delay) {
			    	boostknow(i, end_delay * (book->spe > 0 ? 20 : 10));
#ifdef WEAPON_SKILLS
				use_skill(spell_skilltype(book->otyp), end_delay / (book->spe > 0 ? 10 : 20));
#endif
			    }
			    exercise(A_WIS, TRUE);      /* extra study */
			} else /* MAX_CAN_STUDY < spellknow(i) <= MAX_KNOW */
			    You("know that spell quite well already.");
			/* make book become known even when spell is already
			   known, in case amnesia made you forget the book */
			makeknown((int)booktype);
			/* WAC browsing only if no end_delay */
			if (!end_delay) just_browsing = TRUE;
			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("have keen knowledge of the spell.");
			You("add the spell to your repertoire.");
			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 (!just_browsing) 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 = 0;

	if (delay && spellbook == book)
		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(SPE_BLANK_PAPER);
			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);
		}
#if 0	/* KMH -- obsolete -- see above */
		switch(booktype)  {

	/* blank spellbook */
		case SPE_BLANK_PAPER:
			pline("This spellbook is all blank.");
			makeknown(SPE_BLANK_PAPER);
			return(1);
	/* level 1 spells */
		case SPE_HEALING:
		case SPE_DETECT_MONSTERS:
		case SPE_FORCE_BOLT:
		case SPE_FLAME_SPHERE:
		case SPE_FREEZE_SPHERE:
		case SPE_LIGHT:
		case SPE_SLEEP:
		case SPE_KNOCK:
		case SPE_WIZARD_LOCK:        
		case SPE_DETECT_FOOD:
		case SPE_RESIST_POISON:
		case SPE_RESIST_SLEEP:
		case SPE_PROTECTION:
		case SPE_JUMPING:

	/* level 2 spells */
		case SPE_MAGIC_MISSILE:
		case SPE_CONFUSE_MONSTER:
		case SPE_SLOW_MONSTER:
		case SPE_CURE_BLINDNESS:
		case SPE_CREATE_MONSTER:
		case SPE_ENDURE_COLD:                
		case SPE_ENDURE_HEAT:
		case SPE_INSULATE:
			delay = -objects[booktype].oc_delay;
			break;
	/* level 3 spells */
		case SPE_HASTE_SELF:
		case SPE_CAUSE_FEAR:
		case SPE_CURE_SICKNESS:
		case SPE_DETECT_UNSEEN:
		case SPE_EXTRA_HEALING:
		case SPE_CLAIRVOYANCE:
		case SPE_REMOVE_CURSE:
		case SPE_IDENTIFY:

	/* level 4 spells */
		case SPE_LEVITATION:
		case SPE_RESTORE_ABILITY:
		case SPE_INVISIBILITY:
		case SPE_FIREBALL:
		case SPE_ENLIGHTEN:                
		case SPE_DETECT_TREASURE:
/*Added Lightning acid + poison*/
		case SPE_LIGHTNING:
		case SPE_POISON_BLAST:
		case SPE_ACID_STREAM:
		case SPE_CONE_OF_COLD:

			delay = -(objects[booktype].oc_level - 1) * objects[booktype].oc_delay;
			break;
	/* level 5 spells */
		case SPE_SUMMON_UNDEAD:                
		case SPE_COMMAND_UNDEAD:
		case SPE_MAGIC_MAPPING:
		case SPE_DIG:
                case SPE_CHARM_MONSTER:
	/* level 6 spells */
		case SPE_TURN_UNDEAD:
		case SPE_POLYMORPH:
		case SPE_CREATE_FAMILIAR:
		case SPE_TELEPORT_AWAY:
			delay = -objects[booktype].oc_level * objects[booktype].oc_delay;
			break;
	/* level 7 spells */
		case SPE_CANCELLATION:
		case SPE_FINGER_OF_DEATH:
		case SPE_BOOK_OF_THE_DEAD:
		case SPE_ENCHANT_WEAPON:                        
		case SPE_ENCHANT_ARMOR:
			delay = -8 * objects[booktype].oc_delay;
			break;
	/* impossible */
		default:
			impossible("Unknown spellbook, %d;", booktype);
		return(0);
		}
#endif	/* Obsolete */

		/* Books are often wiser than their readers (Rus.) */
#ifndef NO_SIGNAL
		spellbook->in_use = TRUE;
#endif
		if(!spellbook->blessed &&
                   spellbook->otyp != SPE_BOOK_OF_THE_DEAD)
		{
		    if (spellbook->cursed) {
		        too_hard=1;
		    } 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 to 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 (ynq(qbuf) != 'y') {
#ifndef NO_SIGNAL
						spellbook->in_use = FALSE;
#endif
						return(1);
					}
		        }
		        /* Its up to random luck know */
		        if (rn2(20) > read_ability) {
		            too_hard=1;
		        }
		    }
		}
    
		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);
				if (carried(spellbook)) useup(spellbook);
				else useupf(spellbook);
		    }
#ifndef NO_SIGNAL
			else
			    spellbook->in_use = FALSE;
#endif
			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);
				if (carried(spellbook)) useup(spellbook);
				else useupf(spellbook);
			} else {
			    You(
		  "find yourself reading the first line over and over again.");
#ifndef NO_SIGNAL
			    spellbook->in_use = FALSE;
#endif
			}
			nomul(delay);
			delay = 0;
			return(1);
		}
#ifndef NO_SIGNAL
		spellbook->in_use = FALSE;
#endif

		/* WAC uncharged books take longer to read */
		if (spellbook->otyp != SPE_BOOK_OF_THE_DEAD) {
			if (spellbook->spe > 0) {
				pline_The("words on the page seem to glow faintly.");
				spellbook->spe--;
			} else {
				delay *= 2;
			}
		}
		end_delay = 0;  /* Changed if multi != 0 */

#ifdef DEBUG
		pline("Delay: %i", delay);
#endif
		if (multi) {
			/* Count == practice reading :) */
	        	char qbuf[QBUFSZ];
	        	
	        	if (multi + 1 > MAX_STUDY_TIME) multi = MAX_STUDY_TIME - 1;
	        	Sprintf(qbuf, "Study for at least %i turns?", (multi+1));
			if (ynq(qbuf) != 'y') {
				multi = 0;
				return(1);
			}
			if ((--multi) > (-delay)) end_delay = multi + delay;
			multi = 0;
#ifdef DEBUG
			pline("end_delay: %i", end_delay);
#endif
		}

		You("begin to %s the runes.",
		    spellbook->otyp == SPE_BOOK_OF_THE_DEAD ? "recite" :
		    "memorize");
	}

	book = spellbook;
	set_occupation(learn, "studying", 0);
	return(1);
}

/*
 * 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-z A");
	    else Sprintf(lets, "a-z A-%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;

		if (letter(ilet) && ilet != '@') {
		    /* in a-zA-Z, convert back to an index */
		    if (lowc(ilet) == ilet)     /* lower case */
			idx = ilet - 'a';
		    else
			idx = ilet - 'A' + 26;

		    if (idx < nspells) {
			*spell_no = idx;
			return TRUE;
		    }
		}
		You("don't know that spell.");
	    }
	}
	return dospellmenu(PICK_ONE, spell_no);
}

int
docast()
{
	int spell_no;

	if (getspell(&spell_no))
	    return spelleffects(spell_no, FALSE);
	return 0;
}

#ifdef WEAPON_SKILLS
static const char*
spelltypemnemonic(int skill)
{
	switch (skill) {
	    case P_ATTACK_SPELL:
	        return " attack";
	    case P_HEALING_SPELL:
	        return "healing";
	    case P_DIVINATION_SPELL:
	        return " divine";
	    case P_ENCHANTMENT_SPELL:
	        return "enchant";
		case P_PROTECTION_SPELL:
	        return "protect";
	    case P_BODY_SPELL:
	        return "   body";
	    case P_MATTER_SPELL:
	        return " matter";
	    default:
	        impossible("Unknown spell skill, %d;", skill);
	        return "";
	}
}
#endif /* WEAPON_SKILLS */
int
spell_skilltype(booktype)
int booktype;
{
	return (objects[booktype].oc_skill);

#if 0	/* KMH, balance patch -- now use object table */
        switch (booktype) {
            case SPE_FORCE_BOLT:
            case SPE_MAGIC_MISSILE:
            case SPE_CREATE_MONSTER:
            case SPE_SUMMON_UNDEAD:
            case SPE_COMMAND_UNDEAD:        
            case SPE_FINGER_OF_DEATH:
/*Added poison blast*/
            case SPE_POISON_BLAST:
                return P_ATTACK_SPELL;

            case SPE_HEALING:
            case SPE_CURE_BLINDNESS:
            case SPE_CURE_SICKNESS:
            case SPE_EXTRA_HEALING:
            case SPE_RESTORE_ABILITY:
            case SPE_CREATE_FAMILIAR:
                return P_HEALING_SPELL;

            case SPE_LIGHT:
            case SPE_DETECT_MONSTERS:
            case SPE_IDENTIFY:
            case SPE_DETECT_FOOD:
            case SPE_DETECT_UNSEEN:
            case SPE_CLAIRVOYANCE:
            case SPE_DETECT_TREASURE:
            case SPE_MAGIC_MAPPING:
                return P_DIVINATION_SPELL;

            case SPE_SLEEP:
            case SPE_CONFUSE_MONSTER:
            case SPE_SLOW_MONSTER:
            case SPE_CAUSE_FEAR:
            case SPE_CHARM_MONSTER:
            case SPE_ENCHANT_WEAPON:
            case SPE_ENCHANT_ARMOR:
                return P_ENCHANTMENT_SPELL;

            case SPE_PROTECTION:
            case SPE_RESIST_POISON:
            case SPE_RESIST_SLEEP:
            case SPE_ENDURE_HEAT:
            case SPE_ENDURE_COLD:
            case SPE_INSULATE:
            case SPE_REMOVE_CURSE:
            case SPE_TURN_UNDEAD:
                return P_PROTECTION_SPELL;

            case SPE_JUMPING:
            case SPE_HASTE_SELF:
            case SPE_INVISIBILITY:
            case SPE_LEVITATION:
            case SPE_TELEPORT_AWAY:
            case SPE_ENLIGHTEN:
            case SPE_POLYMORPH:
                return P_BODY_SPELL;

            case SPE_FIREBALL:
            case SPE_CONE_OF_COLD:
/*Added Lightning + Acid stream*/
            case SPE_LIGHTNING:
            case SPE_ACID_STREAM:
            case SPE_KNOCK:
            case SPE_WIZARD_LOCK:
            case SPE_DIG:
            case SPE_CANCELLATION:
                return P_MATTER_SPELL;

            default:
                impossible("Unknown spellbook, %d;", booktype);
            case SPE_BLANK_PAPER:
            case SPE_BOOK_OF_THE_DEAD:
                return P_NONE;
        }
#endif
}


/* LSZ/WWA - The Wizard Patch - July, 1996 */
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=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 = max(0,loglev-u.uspellprot/(4-min(3,(10-natac)/10)));

	if (gain > 0) {
	    if (!Blind) {
	        if (u.uspellprot) {
	            pline("The golden haze around you becomes more dense.");
	        } else {
	            pline("The %s around you begins to shimmer with a golden haze.",
	                (Underwater || Is_waterlevel(&u.uz)) ? "water" : "air");
	        }
	    }
	    u.uspellprot += gain;
#ifdef WEAPON_SKILLS
	    u.uspmtime =
	       P_SKILL(spell_skilltype(SPE_PROTECTION)) == P_EXPERT ? 20 : 10;
#else
	       u.uspmtime = 10;
#endif /* WEAPON_SKILLS */
	    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 skill, class_skill, energy, damage;
	boolean confused = (Confusion != 0);
	struct obj *pseudo;


	/* LSZ/WWA THE Wizard Patch July '96.
	 * Find the skill the hero has in a spell type category. 
	 * See spell_skilltype for categories.
	 */
#ifdef WEAPON_SKILLS
	skill = spell_skilltype(spellid(spell));
	class_skill = P_SKILL(skill);
#endif /* WEAPON_SKILLS */
	
	/* LSZ/WWA Wizard Patch 21 Aug '96 Spell casting no longer effects knowledge
	 * of the spell. A decrement of spell knowledge is done in the allmain
	 */
	if (spellknow(spell) <= 0) {
	    Your("knowledge of this spell is twisted...");
	    pline ("it invokes nightmarish images in your mind!");
	    make_confused(HConfusion + (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.");
                /* WAC Experts can override with HP loss */
#ifdef WEAPON_SKILLS
                if ((class_skill >= P_SKILLED) && (yn("Continue?") == 'y')) {                   
                        energy -= u.uen;
                        losehp(energy,"spellcasting exhaustion", KILLED_BY);                        
                        if (class_skill < P_EXPERT) exercise(A_WIS, FALSE);
                        energy = u.uen;
                } else
#endif /*WEAPON_SKILLS */
                return(0);
/* [Tom] removed this stupid food thing
WAC not putting in hunger code from wiz patch
	} else {

 *              if (spellid(spell) != SPE_DETECT_FOOD) {
 *                      int hungr = energy * 2;
 *
 *			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);
 *		}
 */	}

	/* KMH -- directly call percent_success(), instead of copying it here */
	if (confused || (rnd(100) > percent_success(spell))) {
		pline("You fail to cast the spell correctly.");
		u.uen -= (energy * 0.50);
		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 */ 

	/* WAC -- If skilled enough,  will act like a blessed version */
#ifdef WEAPON_SKILLS
	if (class_skill >= P_SKILLED)
		pseudo->blessed = 1;
#endif /* WEAPON_SKILLS */

	switch(pseudo->otyp)  {

	/* LSZ/WWA The Wizard Patch '96 At first these act as expected, as the
	 * character increases in experience the spell increases in its ability.
	 * Initially start the spells have their expected levels of damage. When 
	 * the hero level reaches three times the level of the spell the spell 
	 * does special damage. This special damage is indicated before each 
	 * spell. Note even when the hero reaches three times the level of the 
	 * spell she still has the choice of of casting either spell. Also the new 
	 * level of spell has an increase cost in casting it. 
	 */
	case SPE_MAGIC_MISSILE:
	case SPE_FIREBALL:
	case SPE_CONE_OF_COLD:
	/*WAC - Add lightning, acid , poison*/
	case SPE_LIGHTNING:
	case SPE_ACID_STREAM:
	case SPE_POISON_BLAST:
		if (tech_inuse(T_SIGIL_TEMPEST) > 1) {
			weffects(pseudo);
			break;
		}
/* These spells are all duplicates of wand effects */
	case SPE_FORCE_BOLT:
	case SPE_SLEEP:
	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:
		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_CONFUSE_MONSTER:
	case SPE_DETECT_FOOD:
	case SPE_CAUSE_FEAR:
	case SPE_CHARM_MONSTER:
	case SPE_MAGIC_MAPPING:
	case SPE_CREATE_MONSTER:
	case SPE_IDENTIFY:
	case SPE_COMMAND_UNDEAD:                
	case SPE_SUMMON_UNDEAD:
	case SPE_REMOVE_CURSE:
		/* WAC -- Equivalent to a blessed scroll if skilled or better;
		 * otherwise an uncursed scroll.  See above.
		 */
		(void) seffects(pseudo);
		break;

	case SPE_ENCHANT_WEAPON:                
	case SPE_ENCHANT_ARMOR:
		if (!rn2(10)) 
			(void) seffects(pseudo);
		else Your("enchantment failed!");                                                               
		break;
	case SPE_HASTE_SELF:
	case SPE_DETECT_TREASURE:
	case SPE_DETECT_MONSTERS:
	case SPE_LEVITATION:
	case SPE_RESTORE_ABILITY:
	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.");
		healup(0, 0, TRUE, FALSE);
        if (Slimed) {                    
			pline("The slime disappears!");
			Slimed = 0;
		}
		break;
	case SPE_CREATE_FAMILIAR:
		make_familiar((struct obj *)0, u.ux, u.uy);
		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:
		/* LSZ/WWA - The Wizard Patch - July 1996 */
		cast_protection();
		break;
	case SPE_JUMPING:
		/* LSZ/WWA - The Wizard Patch - July 1996 */
		if (!jump(class_skill)) {
			pline("Nothing happens.");
		}
		break;

	case SPE_RESIST_POISON:
		if(!(HPoison_resistance & INTRINSIC)) {
			You("feel healthy ..... for the moment at least.");
			incr_itimeout(&HPoison_resistance,
					rn1(1000, 500) + spell_damage_bonus(skill)*100);
		} else pline(nothing_happens);	/* Already have as intrinsic */
		break;
	case SPE_RESIST_SLEEP:
		if(!(HSleep_resistance & INTRINSIC)) {
			if (Hallucination)
				pline("Too much coffee!");
			else
				You("no longer feel tired.");
			incr_itimeout(&HSleep_resistance,
					rn1(1000, 500) + spell_damage_bonus(skill)*100);
		} else pline(nothing_happens);	/* Already have as intrinsic */
		break;
	case SPE_ENDURE_COLD:
		if(!(HCold_resistance & INTRINSIC)) {
			You("feel warmer.");
			incr_itimeout(&HCold_resistance,
					rn1(1000, 500) + spell_damage_bonus(skill)*100);
		} else pline(nothing_happens);	/* Already have as intrinsic */
		break;
	case SPE_ENDURE_HEAT:
		if(!(HFire_resistance & INTRINSIC)) {
			if (Hallucination)
				pline("Excellent! You feel, like, totally cool!");
			else
				You("feel colder.");
			incr_itimeout(&HFire_resistance,
					rn1(1000, 500) + spell_damage_bonus(skill)*100);
		} else pline(nothing_happens);	/* Already have as intrinsic */
		break;
	case SPE_INSULATE:
		if(!(HShock_resistance & INTRINSIC)) {
			if (Hallucination)
				pline("Bummer! You've been grounded!");
			else
				You("are not at all shocked by this feeling.");
			incr_itimeout(&HShock_resistance,
					rn1(1000, 500) + spell_damage_bonus(skill)*100);
		} else pline(nothing_happens);	/* Already have as intrinsic */
		break;
	case SPE_ENLIGHTEN: 
		You("feel self-knowledgeable...");
		display_nhwindow(WIN_MESSAGE, FALSE);
		enlightenment(FALSE);
		pline("The feeling subsides.");
		exercise(A_WIS, TRUE);
		break;

	/* WAC -- new spells */
	case SPE_FLAME_SPHERE:
	case SPE_FREEZE_SPHERE:
	{	register int cnt = 1;
		struct monst *mtmp;


#ifdef WEAPON_SKILLS
		if (class_skill >= P_SKILLED) cnt += (class_skill - P_BASIC);
#endif /* WEAPON_SKILLS */
		while(cnt--) {
			mtmp = make_helper((pseudo->otyp == SPE_FLAME_SPHERE) ?
					PM_FLAMING_SPHERE : PM_FREEZING_SPHERE, u.ux, u.uy);
			if (!mtmp) continue;
			mtmp->mtame = 10;
			mtmp->mhpmax = mtmp->mhp = 1;
			mtmp->isspell = mtmp->uexp = TRUE;
		} /* end while... */
		break;
	}

	/* KMH -- new spells */
	case SPE_PASSWALL:
		if (!Passes_walls)
			You_feel("etheral.");
		incr_itimeout(&HPasses_walls, rn1(100, 50));
		break;

	default:
		impossible("Unknown spell %d attempted.", spell);
		obfree(pseudo, (struct obj *)0);
		return(0);
	}
  
	/* LSZ/WWA Wizard Patch June '96 Gain skill for successful cast */
#ifdef WEAPON_SKILLS
	if (class_skill != P_ISRESTRICTED && class_skill < P_EXPERT) {
	    int sl = spellev(spell);

	    int skill_gain = sl;
	    use_skill(skill,skill_gain);
	}
#endif /* WEAPON_SKILLS */

	/* WAC successful casting increases solidity of knowledge */
	boostknow(spell,CAST_BOOST);

	obfree(pseudo, (struct obj *)0);        /* now, get rid of it */
	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! */
		}
	}
}


int
dovspell()
{
	int dummy;

	if (spellid(0) == NO_SPELL)
	    You("don't know any spells right now.");
	else
	    (void) dospellmenu(PICK_NONE, &dummy);
	return 0;
}


static boolean
dospellmenu(how, spell_no)
	int how;
	int *spell_no;
{
	winid tmpwin;
	int i, n;
	char buf[BUFSZ];
	menu_item *selected;
	anything any;


	tmpwin = create_nhwindow(NHW_MENU);
	start_menu(tmpwin);
	any.a_void = 0;         /* zero out all bits */

	/* LSZ/WWA Wizard Patch July '96 Modified to display spell 
	 * spell class.
	 * 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).
	 */
#ifdef WIZARD
	if (wizard) Sprintf(buf, "%-20s Level    Class  Fail  Know", "Name");
	else
#endif /* WIZARD*/
	Sprintf(buf, "%-20s Level    Class  Fail", "Name");
	add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, buf, MENU_UNSELECTED);

	for (i = 0; i < MAXSPELL && spellid(i) != NO_SPELL; i++) {
#ifdef WIZARD
		if (wizard)
		Sprintf(buf, "%-16s %5d%s %s  %3d%%  %i",
				spellname(i), spellev(i),
				spellknow(i) ? " " : "*", 
#ifdef WEAPON_SKILLS
				spelltypemnemonic(spell_skilltype(spellid(i))),
#else
				"",
#endif
				100 - percent_success(i),spellknow(i));
		else
#endif /*WIZARD*/
		Sprintf(buf, "%-16s %5d%s %s  %3d%%",
				spellname(i), spellev(i),
				spellknow(i) ? " " : "*", 
#ifdef WEAPON_SKILLS
				spelltypemnemonic(spell_skilltype(spellid(i))),
#else
				"",
#endif
				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, MENU_UNSELECTED);
	}
	end_menu(tmpwin, how == PICK_ONE ? "Choose a spell" :
					   "Currently known spells");

	n = select_menu(tmpwin, how, &selected);
	destroy_nhwindow(tmpwin);
	if (n > 0) {
		*spell_no = selected[0].item.a_int - 1;
		free((genericptr_t)selected);
		return TRUE;
	}
	return FALSE;
}


/* LSZ/WWA Wizard Patch July '96. Provides square root function*/
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 i, chance, splcaster, special, statused;
	int difficulty, fake_spell_lev;
	int skill, level;


#ifdef ROLE_PATCH
  	splcaster = urole.spelbase;
  	special = urole.spelheal;
 	statused = ACURR(urole.spelstat);

	/* Calculate armor penalties */
	if(Role_if(PM_HEALER) || Role_if(PM_MONK) ||
			Role_if(PM_NECROMANCER) || Role_if(PM_WIZARD)) {
		if (uarm && !(uarm->otyp == ROBE ||
			uarm->otyp == ROBE_OF_POWER ||
			uarm->otyp == ROBE_OF_PROTECTION)) splcaster += 5;
	}
	if (uarm && is_metallic(uarm)) 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;

	/* Add bonuses (negative penalties) for specific spells */
	if (spellid(spell) == urole.spelspec)
		splcaster += urole.spelsbon;
	else if (Role_if(PM_NECROMANCER) && (spellid(spell) == SPE_COMMAND_UNDEAD ||
			spellid(spell) == SPE_TURN_UNDEAD))
		splcaster += urole.spelsbon;
#else
	/* Calculate intrinsic ability (splcaster) */
 	for (i = 0; cl_sptmp[i].class; i++)
  		if (cl_sptmp[i].class == pl_character[0]) break;
  	splcaster = cl_sptmp[i].splcaster;
  	special = cl_sptmp[i].special;
 	statused = ACURR(cl_sptmp[i].statused);

	if(Role_if(PM_HEALER) || Role_if(PM_MONK) ||
			Role_if(PM_NECROMANCER) || Role_if(PM_WIZARD)) {
		if (uarm && !(uarm->otyp == ROBE ||
			uarm->otyp == ROBE_OF_POWER ||
			uarm->otyp == ROBE_OF_PROTECTION)) splcaster += 5;
	}
	if (uarm && is_metallic(uarm)) splcaster += cl_sptmp[i].uarmbon;
	if (uarms) splcaster += cl_sptmp[i].uarmsbon;

	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) == cl_sptmp[i].specspel)
		splcaster += cl_sptmp[i].specbon;
	if (Role_if(PM_NECROMANCER) && (spellid(spell) == SPE_COMMAND_UNDEAD ||
			spellid(spell) == SPE_TURN_UNDEAD))
		splcaster += cl_sptmp[i].specbon;
#endif

	/* `healing spell' bonus */
/* WAC - replaced with call to spelltype, since healing spells are a type
	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 ||
		spellid(spell) == SPE_ENLIGHTEN)
			splcaster += special;
  */
	if (spell_skilltype(spellid(spell)) == P_HEALING_SPELL)
		splcaster += special;

	if (uarm && uarm->otyp == ROBE_OF_POWER) splcaster -= 3;
	if (splcaster < 5) splcaster = 5;
	if (splcaster > 20) splcaster = 20;

	/* Calculate learned ability */
 	fake_spell_lev = spellev(spell);
	/* 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 */
	/* difficult is based on a spell casters skill level in that spell type */
	/* LSZ/WWA July '96. Modified to use skilltype and hero's level */
#ifdef WEAPON_SKILLS
	skill = P_SKILL(spell_skilltype(spellid(spell))) - 1;
	level = (u.ulevel/3) + 1;
	difficulty = (spellev(spell)-1) * 4 - ((skill * 6) + level);
#else
	/*Matches Nethack 3.2.2.  [JCK]*/
	difficulty = (spellev(spell) - 1) * 4 - (u.ulevel - 1);
#endif /* WEAPON_SKILLS */

	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 / fake_spell_lev);
		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 class-specific spell.
	 */
	if (uarms && weight(uarms) > (int) objects[SMALL_SHIELD].oc_weight) {
		if ((fake_spell_lev == 
#ifdef ROLE_PATCH
			urole.spelspec
#else
			cl_sptmp[i].specspel
#endif
			) ||
		    (Role_if(PM_NECROMANCER) && (fake_spell_lev == SPE_COMMAND_UNDEAD ||
				fake_spell_lev == SPE_TURN_UNDEAD))) {
			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;
}


boolean
studyspell()
{
	/*Vars are for studying spells 'W', 'F', 'I', 'N'*/
	int spell_no;

	if (getspell(&spell_no)) {
		if (spellknow(spell_no) <= 0) {
			You("are unable to focus your memory of the spell.");
			return (FALSE);
		} else if (spellknow(spell_no) <= 1000) {
			Your("focus and reinforce your memory of the spell.");
			incrnknow(spell_no);
			exercise(A_WIS, TRUE);      /* extra study */
			return (TRUE);
		} else /* 1000 < spellknow(spell_no) <= 5000 */
			You("know that spell quite well already.");
	}
	return (FALSE);
}


/*WAC for u_init.c*/
void
learnspell(obj)
register struct obj *obj;
{
	int i;


	for (i = 0; i < MAXSPELL; i++) {
		if (spellid(i) == obj->otyp) {
		     pline ("Error:  Spell already known.");
		     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);
		    break;
		}
	}
	if (i == MAXSPELL) impossible("Too many spells memorized!");
	return;
}
/*spell.c*/
