cm3/
cm3/clans/
cm3/mudprogs/
cm3/player/a/
/****************************************************************************
 * [S]imulated [M]edieval [A]dventure multi[U]ser [G]ame      |   \\._.//   *
 * -----------------------------------------------------------|   (0...0)   *
 * SMAUG 1.4 (C) 1994, 1995, 1996, 1998  by Derek Snider      |    ).:.(    *
 * -----------------------------------------------------------|    {o o}    *
 * SMAUG code team: Thoric, Altrag, Blodkai, Narn, Haus,      |   / ' ' \   *
 * Scryn, Rennard, Swordbearer, Gorog, Grishnakh, Nivek,      |~'~.VxvxV.~'~*
 * Tricops and Fireblade                                      |             *
 * ------------------------------------------------------------------------ *
 * Merc 2.1 Diku Mud improvments copyright (C) 1992, 1993 by Michael        *
 * Chastain, Michael Quan, and Mitchell Tse.                                *
 * Original Diku Mud copyright (C) 1990, 1991 by Sebastian Hammer,          *
 * Michael Seifert, Hans Henrik St{rfeldt, Tom Madsen, and Katja Nyboe.     *
 * ------------------------------------------------------------------------ *
 *			     Spell handling module			    *
 ****************************************************************************/

#include <sys/types.h>
#include <ctype.h>
#include <stdio.h>
#include <string.h>
#ifdef sun
  #include <strings.h>
#endif
#include <time.h>
#include "mud.h"

/*
 * Local functions.
 */
void	say_spell	args( ( CHAR_DATA *ch, int sn ) );

ch_ret	spell_affect	args( ( int sn, int wil, CHAR_DATA *ch, void *vo ));
ch_ret	spell_affectchar args( ( int sn, int wil, CHAR_DATA *ch, void *vo) );
int dispel_casting(AFFECT_DATA *paf,CHAR_DATA *ch,CHAR_DATA*victim,int affect,bool dispel);

void check_bodyparts(CHAR_DATA *ch);
bool check_counterspell(CHAR_DATA *ch, CHAR_DATA *victim);

extern SPECIES_DATA *find_species( char *name );
extern NATION_DATA *find_nation( char *name);

extern void learn_talent( CHAR_DATA *ch, int i);

/*
 * Is immune to a damage type
 */
bool is_immune( CHAR_DATA *ch, sh_int damtype )
{
    switch( damtype )
    {
	case SD_FIRE:	     if (IS_SET(ch->immune, RIS_FIRE))	 return TRUE;
	case SD_COLD:	     if (IS_SET(ch->immune, RIS_COLD))	 return TRUE;
	case SD_ELECTRICITY: if (IS_SET(ch->immune, RIS_ELECTRICITY)) return TRUE;
	case SD_ENERGY:	     if (IS_SET(ch->immune, RIS_ENERGY)) return TRUE;
	case SD_ACID:	     if (IS_SET(ch->immune, RIS_ACID))	 return TRUE;
	case SD_POISON:	     if (IS_SET(ch->immune, RIS_POISON)) return TRUE;
	case SD_DRAIN:	     if (IS_SET(ch->immune, RIS_DRAIN))	 return TRUE;
    }
    return FALSE;
}

/*
 * Lookup a skill by name, only stopping at skills the player has.
 */
int ch_slookup( CHAR_DATA *ch, const char *name )
{
    int sn;

    if ( IS_NPC(ch) )
	return skill_lookup( name );
    for ( sn = 0; sn < top_sn; sn++ )
    {
	if ( !skill_table[sn]->name )
	    break;
	if ( LEARNED(ch, sn) > 0
	&&   get_best_talent(ch, sn) != -1
	&&   LOWER(name[0]) == LOWER(skill_table[sn]->name[0])
	&&  !str_prefix( name, skill_table[sn]->name ) )
	    return sn;
    }

    return -1;
}

/*
 * Lookup an herb by name.
 */
int herb_lookup( const char *name )
{
    int sn;

    for ( sn = 0; sn < top_herb; sn++ )
    {
	if ( !herb_table[sn] || !herb_table[sn]->name )
	    return -1;
	if ( LOWER(name[0]) == LOWER(herb_table[sn]->name[0])
	&&  !str_prefix( name, herb_table[sn]->name ) )
	    return sn;
    }
    return -1;
}

/*
 * Lookup a skill by name.
 */
int skill_lookup( const char *name )
{
    int sn;

    if ( (sn=bsearch_skill_exact(name, gsn_first_spell, gsn_first_skill-1)) == -1 )
    if ( (sn=bsearch_skill_exact(name, gsn_first_skill, gsn_top_sn-1)) == -1)
	if ( (sn=bsearch_skill_prefix(name, gsn_first_spell, gsn_first_skill-1)) == -1 )
	if ( (sn=bsearch_skill_prefix(name, gsn_first_skill, gsn_top_sn-1)) == -1 )
	{
	      for ( sn = gsn_top_sn; sn < top_sn; sn++ )
	      {
			  if ( !skill_table[sn] || !skill_table[sn]->name )
				return -1;
			  if ( LOWER(name[0]) == LOWER(skill_table[sn]->name[0])
			  &&  !str_prefix( name, skill_table[sn]->name ) )
			    return sn;
		  }
		  return -1;
	}
    return sn;
}

/*
 * Return a skilltype pointer based on sn			-Thoric
 * Returns NULL if bad or unused
 */
SKILLTYPE *get_skilltype( int sn )
{
    if ( sn >= TYPE_HERB )
	return IS_VALID_HERB(sn-TYPE_HERB) ? herb_table[sn-TYPE_HERB] : NULL;
    if ( sn >= TYPE_HIT )
	return NULL;
    return IS_VALID_SN(sn) ? skill_table[sn] : NULL;
}

/*
 * Perform a binary search on a section of the skill table	-Thoric
 * Each different section of the skill table is sorted alphabetically
 *
 * Check for prefix matches
 */
int bsearch_skill_prefix( const char *name, int first, int top )
{
    int sn;

    for (;;)
    {
	sn = (first + top) >> 1;

	if (sn < 0)
		return -1;

	if (LOWER(name[0]) == LOWER(skill_table[sn]->name[0])
	&&  !str_prefix(name, skill_table[sn]->name))
	    return sn;
	if (first >= top)
	    return -1;
    	if (strcmp(name, skill_table[sn]->name) < 1)
	    top = sn - 1;
    	else
	    first = sn + 1;
    }
    return -1;
}

/*
 * Perform a binary search on a section of the skill table	-Thoric
 * Each different section of the skill table is sorted alphabetically
 *
 * Check for exact matches only
 */
int bsearch_skill_exact( const char *name, int first, int top )
{
    int sn;

    for (;;)
    {
	sn = (first + top) >> 1;

	if (sn < 0)
		return -1;
	if (!str_cmp(name, skill_table[sn]->name))
	    return sn;
	if (first >= top)
	    return -1;
    	if (strcmp(name, skill_table[sn]->name) < 1)
	    top = sn - 1;
    	else
	    first = sn + 1;
    }
    return -1;
}

/*
 * Perform a binary search on a section of the skill table	-Thoric
 * Each different section of the skill table is sorted alphabetically
 *
 * Check exact match first, then a prefix match
 */
int bsearch_skill( const char *name, int first, int top )
{
    int sn = bsearch_skill_exact(name, first, top);

    return (sn == -1) ? bsearch_skill_prefix(name, first, top) : sn;
}

/*
 * Perform a binary search on a section of the skill table
 * Each different section of the skill table is sorted alphabetically
 * Only match skills player knows				-Thoric
 */
int ch_bsearch_skill_prefix( CHAR_DATA *ch, const char *name, int first, int top )
{
    int sn;

    for (;;)
    {
	sn = (first + top) >> 1;

	if ( LOWER(name[0]) == LOWER(skill_table[sn]->name[0])
	&&  !str_prefix(name, skill_table[sn]->name)
	&&   LEARNED(ch, sn) > 0
	&&   get_best_talent(ch, sn) != -1 )
		return sn;
	if (first >= top)
	    return -1;
    	if (strcmp( name, skill_table[sn]->name) < 1)
	    top = sn - 1;
    	else
	    first = sn + 1;
    }
    return -1;
}

int ch_bsearch_skill_exact( CHAR_DATA *ch, const char *name, int first, int top )
{
    int sn;

    for (;;)
    {
	sn = (first + top) >> 1;

	if (!str_cmp(name, skill_table[sn]->name)
	&&   LEARNED(ch, sn) > 0
	&&   get_best_talent(ch, sn) != -1 )
		return sn;
	if (first >= top)
	    return -1;
    	if (strcmp(name, skill_table[sn]->name) < 1)
	    top = sn - 1;
    	else
	    first = sn + 1;
    }
    return -1;
}

int ch_bsearch_skill( CHAR_DATA *ch, const char *name, int first, int top )
{
    int sn = ch_bsearch_skill_exact(ch, name, first, top);

    return (sn == -1) ? ch_bsearch_skill_prefix(ch, name, first, top) : sn;
}

int find_spell( CHAR_DATA *ch, const char *name, bool know )
{
    if ( IS_NPC(ch) || !know )
	return bsearch_skill( name, gsn_first_spell, gsn_first_skill-1 );
    else
	return ch_bsearch_skill( ch, name, gsn_first_spell, gsn_first_skill-1 );
}

int find_skill( CHAR_DATA *ch, const char *name, bool know )
{
    if ( IS_NPC(ch) || !know )
	return bsearch_skill( name, gsn_first_skill, gsn_top_sn-1 );
    else
	return ch_bsearch_skill( ch, name, gsn_first_skill, gsn_top_sn-1 );
}

/*
 * Lookup a skill by slot number.
 * Used for object loading.
 */
int slot_lookup( int slot )
{
    extern bool fBootDb;
    int sn;

    if ( slot <= 0 )
	return -1;

    for ( sn = 0; sn < top_sn; sn++ )
	if ( slot == skill_table[sn]->slot )
	    return sn;

    if ( fBootDb )
    {
	bug( "Slot_lookup: bad slot %d.", slot ); 
/*	abort( ); */
    }

    return -1;
}


/*
 * Fancy message handling for a successful casting		-Thoric
 */
void successful_casting( SKILLTYPE *skill, CHAR_DATA *ch,
			 CHAR_DATA *victim, OBJ_DATA *obj )
{
    sh_int chitroom = (skill->type == SKILL_SPELL ? AT_MAGIC : AT_ACTION);
    sh_int chit	    = (skill->type == SKILL_SPELL ? AT_MAGIC : AT_HIT);
    sh_int chitme   = (skill->type == SKILL_SPELL ? AT_MAGIC : AT_HITME);

    if ( skill->target != TAR_CHAR_OFFENSIVE )
    {
	chit = chitroom;
	chitme = chitroom;
    }

    if ( ch && ch != victim )
    {
	if ( skill->hit_char && skill->hit_char[0] != '\0' )
	    act( chit, skill->hit_char, ch, obj, victim, TO_CHAR );
	else
	if ( skill->type == SKILL_SPELL )
            act( chit, "Ok.", ch, NULL, NULL, TO_CHAR );
    }
    if ( ch && skill->hit_room && skill->hit_room[0] != '\0' )
	act( chitroom, skill->hit_room, ch, obj, victim, TO_NOTVICT );
    if ( ch && victim && skill->hit_vict && skill->hit_vict[0] != '\0' )
    {
	if ( ch != victim )
	    act( chitme, skill->hit_vict, ch, obj, victim, TO_VICT );
	else
	    act( chitme, skill->hit_vict, ch, obj, victim, TO_CHAR );
    }
    else
    if ( ch && ch == victim && skill->type == SKILL_SPELL )
	act( chitme, "Ok.", ch, NULL, NULL, TO_CHAR );
}

/*
 * Fancy message handling for a failed casting			-Thoric
 */
void failed_casting( SKILLTYPE *skill, CHAR_DATA *ch,
		     CHAR_DATA *victim, OBJ_DATA *obj )
{
    sh_int chitroom = (skill->type == SKILL_SPELL ? AT_MAGIC : AT_ACTION);
    sh_int chit	    = (skill->type == SKILL_SPELL ? AT_MAGIC : AT_HIT);
    sh_int chitme   = (skill->type == SKILL_SPELL ? AT_MAGIC : AT_HITME);

    if ( skill->target != TAR_CHAR_OFFENSIVE )
    {
	chit = chitroom;
	chitme = chitroom;
    }

    if ( ch && ch != victim )
    {
	if ( skill->miss_char && skill->miss_char[0] != '\0' )
	    act( chit, skill->miss_char, ch, obj, victim, TO_CHAR );
	else
	if ( skill->type == SKILL_SPELL )
            act( chit, "You failed.", ch, NULL, NULL, TO_CHAR );
    }
    if ( ch && skill->miss_room && skill->miss_room[0] != '\0'
    &&   str_cmp(skill->miss_room, "supress") )
	act( chitroom, skill->miss_room, ch, obj, victim, TO_NOTVICT );
    if ( ch && victim && skill->miss_vict && skill->miss_vict[0] != '\0' )
    {
	if ( ch != victim )
	    act( chitme, skill->miss_vict, ch, obj, victim, TO_VICT );
	else
	    act( chitme, skill->miss_vict, ch, obj, victim, TO_CHAR );
    }
    else
    if ( ch && ch == victim )
    {
	if ( skill->miss_char && skill->miss_char[0] != '\0' )
	    act( chitme, skill->miss_char, ch, obj, victim, TO_CHAR );
	else
	if ( skill->type == SKILL_SPELL )
	    act( chitme, "You failed.", ch, NULL, NULL, TO_CHAR );
    }
}

/*
 * Fancy message handling for being immune to something		-Thoric
 */
void immune_casting( SKILLTYPE *skill, CHAR_DATA *ch,
		     CHAR_DATA *victim, OBJ_DATA *obj )
{
    sh_int chitroom = (skill->type == SKILL_SPELL ? AT_MAGIC : AT_ACTION);
    sh_int chit	    = (skill->type == SKILL_SPELL ? AT_MAGIC : AT_HIT);
    sh_int chitme   = (skill->type == SKILL_SPELL ? AT_MAGIC : AT_HITME);

    if ( skill->target != TAR_CHAR_OFFENSIVE )
    {
	chit = chitroom;
	chitme = chitroom;
    }

    if ( ch && ch != victim )
    {
	if ( skill->imm_char && skill->imm_char[0] != '\0' )
	    act( chit, skill->imm_char, ch, obj, victim, TO_CHAR );
	else
	if ( skill->miss_char && skill->miss_char[0] != '\0' )
	    act( chit, skill->hit_char, ch, obj, victim, TO_CHAR );
	else
	if ( skill->type == SKILL_SPELL || skill->type == SKILL_SKILL )
            act( chit, "That appears to have no effect.", ch, NULL, NULL, TO_CHAR );
    }
    if ( ch && skill->imm_room && skill->imm_room[0] != '\0' )
	act( chitroom, skill->imm_room, ch, obj, victim, TO_NOTVICT );
    else
    if ( ch && skill->miss_room && skill->miss_room[0] != '\0' )
	act( chitroom, skill->miss_room, ch, obj, victim, TO_NOTVICT );
    if ( ch && victim && skill->imm_vict && skill->imm_vict[0] != '\0' )
    {
	if ( ch != victim )
	    act( chitme, skill->imm_vict, ch, obj, victim, TO_VICT );
	else
	    act( chitme, skill->imm_vict, ch, obj, victim, TO_CHAR );
    }
    else
    if ( ch && victim && skill->miss_vict && skill->miss_vict[0] != '\0' )
    {
	if ( ch != victim )
	    act( chitme, skill->miss_vict, ch, obj, victim, TO_VICT );
	else
	    act( chitme, skill->miss_vict, ch, obj, victim, TO_CHAR );
    }
    else
    if ( ch && ch == victim )
    {
	if ( skill->imm_char && skill->imm_char[0] != '\0' )
	    act( chit, skill->imm_char, ch, obj, victim, TO_CHAR );
	else
	if ( skill->miss_char && skill->miss_char[0] != '\0' )
	    act( chit, skill->hit_char, ch, obj, victim, TO_CHAR );
	else
	if ( skill->type == SKILL_SPELL || skill->type == SKILL_SKILL )
            act( chit, "That appears to have no affect.", ch, NULL, NULL, TO_CHAR );
    }
}


/*
 * Utter mystical words for an sn.
 */
void say_spell( CHAR_DATA *ch, int sn )
{
    char buf  [MAX_STRING_LENGTH];
    char buf2 [MAX_STRING_LENGTH];
    CHAR_DATA *rch;
    char *pName;
    int iSyl;
    int length;
    SKILLTYPE *skill = get_skilltype( sn );

    struct syl_type
    {
	char *	old;
	char *	new;
    };

    static const struct syl_type syl_table[] =
    {
	{ " ",		" "		},
	{ "ar",		"kata"		},
	{ "fi",		"kh"		},
	{ "ice",	"nyst"		},
	{ "ing",	"nay"		},
	{ "el",		"r"		},
	{ "ld",		"ck"		},
	{ "light",	"khar"		},
	{ "ne",		"lyx"		},
	{ "ow",		"er"		},
	{ "re",		"an"		},
	{ "ru",		"tanta"		},
	{ "tra",	"ill"		},
	{ "sha",	"se"		},
	{ "shi",	"er"		},
	{ "a", "o" }, { "b", "b" }, { "c", "q" }, { "d", "dh" },
	{ "e", "y" }, { "f", "z" }, { "g", "o" }, { "h", "p" },
	{ "i", "u" }, { "j", "n" }, { "k", "t" }, { "l", "r" },
	{ "m", "w" }, { "n", "i" }, { "o", "a" }, { "p", "s" },
	{ "q", "d" }, { "r", "f" }, { "s", "g" }, { "t", "h" },
	{ "u", "j" }, { "v", "y" }, { "w", "r" }, { "x", "n" },
	{ "y", "i" }, { "z", "k" },
	{ "", "" }
    };

    buf[0]	= '\0';
    for ( pName = skill->name; *pName != '\0'; pName += length )
    {
	for ( iSyl = 0; (length = strlen(syl_table[iSyl].old)) != 0; iSyl++ )
	{
	    if ( !str_prefix( syl_table[iSyl].old, pName ) )
	    {
		strcat( buf, syl_table[iSyl].new );
		break;
	    }
	}

	if ( length == 0 )
	    length = 1;
    }

    sprintf( buf2, "$n closes $s eyes and utters the words, '%s!'", buf);
    sprintf( buf,  "$n closes $s eyes and utters the words, '%s!'", skill->name );

	/* by saying the spell out loud, you give yourself away -keo */
        if (IS_AFFECTED(ch, AFF_HIDE))
            xREMOVE_BIT(ch->affected_by, AFF_HIDE);

    for ( rch = ch->in_room->first_person; rch; rch = rch->next_in_room )
    {
	if ( rch != ch )
	act( AT_MAGIC, buf2, ch, NULL, rch, TO_VICT );
    }

    return;
}


/*
 * Make adjustments to saving throw based in RIS		-Thoric
 */
int ris_save( CHAR_DATA *ch, int chance, int ris )
{
   sh_int modifier;

   modifier = 10;
   if ( IS_SET(ch->immune, ris ) )
	modifier -= 10;
   if ( IS_SET(ch->resistant, ris ) )
	modifier -= 2;
   if ( IS_SET(ch->susceptible, ris ) )
	modifier += 2;
   if ( modifier <= 0 )
	return 1000;
   if ( modifier == 10 )
	return chance;
   return (chance * modifier) / 10;
}


/*								    -Thoric
 * Fancy dice expression parsing complete with order of operations,
 * simple exponent support, dice support as well as a few extra
 * variables: H = hp, M = mana, V = move, S = str, X = dex
 *            I = int, W = wil, C = con, P = per, E = end, A = age
 *
 * Used for spell dice parsing, ie: 3d8+W-6
 *
 */
int rd_parse(CHAR_DATA *ch, char *exp)
{
    int x, lop = 0, gop = 0, eop = 0;
    char operation;
    char *sexp[2];
    int total = 0, len = 0;

    /* take care of nulls coming in */
    if (!exp || !strlen(exp))
	return 0;

    /* get rid of brackets if they surround the entire expresion */
    if ( (*exp == '(') && !index(exp+1,'(') && exp[strlen(exp)-1] == ')' )
    {
	exp[strlen(exp)-1] = '\0';
	exp++;
    }

    /* check if the expresion is just a number */
    len = strlen(exp);
    if ( len == 1 && isalpha(exp[0]) )
    {
	switch(exp[0])
	{
	    case 'H': case 'h':	return ch->hit;
	    case 'M': case 'm':	return ch->mana;
	    case 'V': case 'v':	return ch->move;
	    case 'S': case 's':	return get_curr_str(ch);
	    case 'I': case 'i':	return get_curr_int(ch);
	    case 'W': case 'w':	return get_curr_wil(ch);
	    case 'X': case 'x':	return get_curr_dex(ch);
	    case 'C': case 'c':	return get_curr_con(ch);
	    case 'P': case 'p':	return get_curr_per(ch);
	    case 'E': case 'e':	return get_curr_end(ch);
	}
    }

    for (x = 0; x < len; ++x)
	if (!isdigit(exp[x]) && !isspace(exp[x]))
	    break;
    if (x == len)
	return atoi(exp);
  
    /* break it into 2 parts */
    for (x = 0; x < strlen(exp); ++x)
	switch(exp[x])
	{
	    case '^':
	      if (!total)
		eop = x;
	      break;
	    case '-': case '+':
	      if (!total) 
		lop = x;
	      break;
	    case '*': case '/': case '%': case 'd': case 'D':
	    case '<': case '>': case '{': case '}': case '=':
	      if (!total) 
		gop =  x;
	      break;
	    case '(':
	      ++total;
	      break;
	    case ')':
	      --total;
	      break;
	}
    if (lop)
	x = lop;
    else
    if (gop)
	x = gop;
    else
	x = eop;
    operation = exp[x];
    exp[x] = '\0';
    sexp[0] = exp;
    sexp[1] = (char *)(exp+x+1);

    /* work it out */
    total = rd_parse(ch, sexp[0]);
    switch(operation)
    {
	case '-':		total -= rd_parse(ch, sexp[1]);	break;
	case '+':		total += rd_parse(ch, sexp[1]);	break;
	case '*':		total *= rd_parse(ch, sexp[1]);	break;
	case '/':		total /= rd_parse(ch, sexp[1]);	break;
	case '%':		total %= rd_parse(ch, sexp[1]);	break;
	case 'd': case 'D':	total = dice( total, rd_parse(ch, sexp[1]));	break;
	case '<':		total = (total < rd_parse(ch, sexp[1]));		break;
	case '>':		total = (total > rd_parse(ch, sexp[1]));		break;
	case '=':		total = (total == rd_parse(ch, sexp[1]));	break;
	case '{':		total = UMIN( total, rd_parse(ch, sexp[1]) );	break;
	case '}':		total = UMAX( total, rd_parse(ch, sexp[1]));	break;

	case '^':
	{
	    int y = rd_parse(ch, sexp[1]), z = total;

	    for (x = 1; x < y; ++x, z *= total);
	    total = z;
	    break;
	}
    }
    return total;
}

/* wrapper function so as not to destroy exp */
int dice_parse(CHAR_DATA *ch, char *exp)
{
    char buf[MAX_INPUT_LENGTH];

    strcpy( buf, exp );
    return rd_parse(ch, buf);
}



/*
 * Process the spell's required components, if any		-Thoric
 * -----------------------------------------------
 * T###		check for item of type ###
 * V#####	check for item of vnum #####
 * Kword	check for item with keyword 'word'
 * G#####	check if player has ##### amount of gold
 * H####	check if player has #### amount of hitpoints
 *
 * Special operators:
 * ! spell fails if player has this
 * + don't consume this component
 * @@ decrease component's value[0], and extract if it reaches 0
 * # decrease component's value[1], and extract if it reaches 0
 * $ decrease component's value[2], and extract if it reaches 0
 * % decrease component's value[3], and extract if it reaches 0
 * ^ decrease component's value[4], and extract if it reaches 0
 * & decrease component's value[5], and extract if it reaches 0
 */
bool process_spell_components( CHAR_DATA *ch, int sn )
{
     SKILLTYPE *skill	= get_skilltype(sn);
     char *comp		= skill->components;
     char *check;
     char arg[MAX_INPUT_LENGTH];
     bool consume, fail, found;
     int  val, value;
     OBJ_DATA *obj;

     /* if no components necessary, then everything is cool */
     if ( !comp || comp[0] == '\0' )
	return TRUE;

     while ( comp[0] != '\0' )
     {
	comp = one_argument( comp, arg );
	consume = TRUE;
	fail = found = FALSE;
	val = -1;
	switch( arg[1] )
	{
	    default:	check = arg+1;				break;
	    case '!':	check = arg+2;	fail = TRUE;		break;
	    case '+':	check = arg+2;	consume = FALSE;	break;
	    case '@':	check = arg+2;	val = 0;		break;
	    case '#':	check = arg+2;	val = 1;		break;
	    case '$':	check = arg+2;	val = 2;		break;
	    case '%':	check = arg+2;	val = 3;		break;
	    case '^':	check = arg+2;	val = 4;		break;
	    case '&':	check = arg+2;	val = 5;		break;
	    /*   reserve '*', '(' and ')' for v6, v7 and v8   */
	}
	value = atoi(check);
	obj = NULL;
	switch( UPPER(arg[0]) )
	{
	    case 'T':
		for ( obj = ch->first_carrying; obj; obj = obj->next_content )
		   if ( obj->item_type == value )
		   {
			if ( fail )
			{
			  send_to_char( "Something disrupts the casting of this spell...\n\r", ch );
			  return FALSE;
			}
			found = TRUE;
			break;
		   }
		break;
	    case 'V':
		for ( obj = ch->first_carrying; obj; obj = obj->next_content )
		   if ( obj->pIndexData->vnum == value )
		   {
			if ( fail )
			{
			  send_to_char( "Something disrupts the casting of this spell...\n\r", ch );
			  return FALSE;
			}
			found = TRUE;
			break;
		   }
		break;
	    case 'K':
		for ( obj = ch->first_carrying; obj; obj = obj->next_content )
		   if ( nifty_is_name( check, obj->name ) )
		   {
			if ( fail )
			{
			  send_to_char( "Something disrupts the casting of this spell...\n\r", ch );
			  return FALSE;
			}
			found = TRUE;
			break;
		   }
		break;
	    case 'G':
			if ( ch->gold >= value ) {
		  if ( fail )
		  {
		    send_to_char( "Something disrupts the casting of this spell...\n\r", ch );
		    return FALSE;
		  }
		  else
		  {
		    if ( consume )
		    {
			set_char_color( AT_GOLD, ch );
			send_to_char( "You feel a little lighter...\n\r", ch );
		 	ch->gold -= value;
		    }
		    continue;
		  }
	    	break;
			}
	    case 'H':
			if ( ch->hit >= value ) {
		  if ( fail )
		  {
		    send_to_char( "Something disrupts the casting of this spell...\n\r", ch );
		    return FALSE;
		  }
		  else
		  {
		    if ( consume )
		    {
			set_char_color( AT_BLOOD, ch );
			send_to_char( "You feel a little weaker...\n\r", ch );
			ch->hit -= value;
			update_pos( ch );
		    }
		    continue;
		  }
		break;
			}
	}
	/* having this component would make the spell fail... if we get
	   here, then the caster didn't have that component */
	if ( fail )
	    continue;
	if ( !found )
	{
	    send_to_char( "Something is missing...\n\r", ch );
	    return FALSE;
	}
	if ( obj )
	{
	    if ( val >=0 && val < 6 )
	    {
		separate_obj(obj);
		if ( obj->value[val] <= 0 )
		{
		    act( AT_MAGIC, "$p disappears in a puff of smoke!", ch, obj, NULL, TO_CHAR );
		    act( AT_MAGIC, "$p disappears in a puff of smoke!", ch, obj, NULL, TO_ROOM );
		    extract_obj( obj );
		    return FALSE;
		}
		else
		if ( --obj->value[val] == 0 )
		{
		    act( AT_MAGIC, "$p glows briefly, then disappears in a puff of smoke!", ch, obj, NULL, TO_CHAR );
		    act( AT_MAGIC, "$p glows briefly, then disappears in a puff of smoke!", ch, obj, NULL, TO_ROOM );
		    extract_obj( obj );
		}
		else
		    act( AT_MAGIC, "$p glows briefly and a whisp of smoke rises from it.", ch, obj, NULL, TO_CHAR );
	    }
	    else
	    if ( consume )
	    {
		separate_obj(obj);
		act( AT_MAGIC, "$p glows brightly, then disappears in a puff of smoke!", ch, obj, NULL, TO_CHAR );
		act( AT_MAGIC, "$p glows brightly, then disappears in a puff of smoke!", ch, obj, NULL, TO_ROOM );
		extract_obj( obj );
	    }
	    else
	    {
		int count = obj->count;

		obj->count = 1;
		act( AT_MAGIC, "$p glows briefly.", ch, obj, NULL, TO_CHAR );
		obj->count = count;
	    }
	}
     }
     return TRUE;
}

int pAbort;

/*
 * Locate targets.
 */
void *locate_targets( CHAR_DATA *ch, char *arg, int sn,
		      CHAR_DATA **victim, OBJ_DATA **obj )
{
    SKILLTYPE *skill = get_skilltype( sn );
    void *vo	= NULL;

    *victim	= NULL;
    *obj	= NULL;
      
    switch ( skill->target )
    {
	default:
	  bug( "Do_cast: bad target for sn %d.", sn );
	  return &pAbort;

	case TAR_IGNORE:
	  break;

	case TAR_CHAR_OFFENSIVE:
	  if ( arg[0] == '\0' )
	  {
		if ( ( *victim = ch->last_hit ) == NULL )
		{
		    send_to_char( "Cast the spell on whom?\n\r", ch );
		    return &pAbort;
		}
	  } else {
		if ( ( *victim = get_char_room( ch, arg ) ) == NULL )
		{
		    if (skill->range > 0)
			break;
		    send_to_char( "They aren't here.\n\r", ch );
		    return &pAbort;
		}
	  }

	  if ( ch == *victim )
	  {
	        if ( SPELL_FLAG(get_skilltype(sn), SF_NOSELF))
	        {
		  send_to_char( "You can't cast this on yourself!\n\r", ch);
		  return &pAbort;
		} else if (skill->range < 1)
		send_to_char( "Cast this on yourself?  Okay...\n\r", ch );
	  }

	  if ( !IS_NPC(ch) )
	  {
		if ( !IS_NPC(*victim) )
		{
		    if ( get_timer( ch, TIMER_PKILLED ) > 0 )
		    {
			send_to_char( "You have been killed in the last 5 minutes.\n\r", ch);
			return &pAbort;
		    }

		    if ( get_timer( *victim, TIMER_PKILLED ) > 0 )
		    {
			send_to_char( "This player has been killed in the last 5 minutes.\n\r", ch );
			return &pAbort;
		    }	
		}

		if ( IS_AFFECTED(ch, AFF_CHARM) && ch->master == *victim )
		{
		    send_to_char( "You can't do that on your own follower.\n\r", ch );
		    return &pAbort;
		}
	  }

	  vo = (void *) *victim;
	  break;

	case TAR_CHAR_DEFENSIVE:
	  if ( arg[0] == '\0' )
		*victim = ch;
	  else
	  {
		if (ch->curr_talent[TAL_MOTION] >= 100
		&&  ch->curr_talent[TAL_SEEKING] >= 100) {
                if ( ( *victim = get_char_world( ch, arg ) ) == NULL )
                {
                    send_to_char( "They aren't here.\n\r", ch );
                    return &pAbort;
                }
		} else {
		if ( ( *victim = get_char_room( ch, arg ) ) == NULL )
		{
		    send_to_char( "They aren't here.\n\r", ch );
		    return &pAbort;
		}
		}
	  }

	  if ( ch == *victim && 
			SPELL_FLAG(get_skilltype(sn), SF_NOSELF))
	  {
		  send_to_char( "You can't cast this on yourself!\n\r", ch);
		  return &pAbort;
	  }

	  vo = (void *) *victim;
	  break;

	case TAR_CHAR_SELF:
	  if ( arg[0] != '\0' && !nifty_is_name( arg, ch->name ) )
	  {
		send_to_char( "You cannot cast this spell on another.\n\r", ch );
		return &pAbort;
	  }

	  vo = (void *) ch;
	  break;

	case TAR_OBJ_INV:
	  if ( arg[0] == '\0' )
	  {
		send_to_char( "What should the spell be cast upon?\n\r", ch );
		return &pAbort;
	  }

	  if ( ( *obj = get_obj_carry( ch, arg ) ) == NULL )
	  {
		send_to_char( "You are not carrying that.\n\r", ch );
		return &pAbort;
	  }

	  vo = (void *) *obj;
	  break;
    }

    return vo;
}


/*
 * The kludgy global is for spells who want more stuff from command line.
 */
char *target_name;
char *ranged_target_name = NULL;


/*
 * Cast a spell.  Multi-caster and component support by Thoric
 * Ranged spell support by Scion
 */
void do_cast( CHAR_DATA *ch, char *argument )
{
    char arg1[MAX_INPUT_LENGTH];
    char arg2[MAX_INPUT_LENGTH];
    static char staticbuf[MAX_INPUT_LENGTH];
    CHAR_DATA *victim;
    OBJ_DATA *obj;
    void *vo = NULL;
    int mana;
    int sn;
    ch_ret retcode;
    bool dont_wait = FALSE;
    SKILLTYPE *skill = NULL;
    struct timeval time_used;

    retcode = rNONE;

    switch( ch->substate )
    {
      default:
	/* no ordering charmed mobs to cast spells */

	if ( IS_NPC(ch) 
	&& ( IS_AFFECTED( ch, AFF_CHARM ) || IS_AFFECTED( ch, AFF_POSSESS ) ) )
	{
	    send_to_char( "You can't seem to do that right now...\n\r", ch );
	    return;
	}

	if ( IS_SET( ch->in_room->room_flags, ROOM_NO_MAGIC ) )
	{
	    set_char_color( AT_MAGIC, ch );
	    send_to_char( "Your magic flickers and dies.\n\r", ch );
	    return;
	}

	/* arg1 = spell name
	   arg2 = dir or target
	   target_name = dir+target || dir || target
	*/

	target_name = one_argument( argument, arg1 );
	one_argument( target_name, arg2 );
	if ( ranged_target_name )
		DISPOSE( ranged_target_name );
	ranged_target_name = str_dup( target_name );

	if ( arg1[0] == '\0' )
	{
	    send_to_char( "Cast which what where?\n\r", ch );
	    return;
	}

	/* Regular mortal spell casting */
	if (!ch->pcdata || !IS_SET(ch->pcdata->permissions, PERMIT_MISC) )
	{
	    if ( ( sn = find_spell( ch, arg1, TRUE ) ) < 0
	    || ( !IS_NPC(ch) && get_best_talent(ch, sn) == -1 ) )
	    {
		send_to_char( "You don't know how to do that.\n\r", ch );
		return;
	    }
	    if ( (skill=get_skilltype(sn)) == NULL )
	    {
		send_to_char( "You can't do that.\n\r", ch );
		return;
	    }
	}
	else
	/*
	 * Godly "spell builder" spell casting with debugging messages
	 */
	{
	    if ( (sn=skill_lookup(arg1)) < 0 )
	    {
		send_to_char( "That's not a spell.\n\r", ch );
		return;
	    }
	    if ( sn >= MAX_SKILL )
	    {
		send_to_char( "Someone used too many skill numbers, that spell is unusable.\n\r", ch );
		return;
	    }
	    if ( (skill=get_skilltype(sn)) == NULL )
	    {
		send_to_char( "Something is severely wrong with that spell.\n\r", ch );
		return;
	    }
	    if ( skill->type != SKILL_SPELL )
	    {
		send_to_char( "That isn't a spell.\n\r", ch );
		return;
	    }
	    if ( !skill->spell_fun )
	    {
		send_to_char( "We didn't finish that one yet.\n\r", ch );
		return;
	    }
	}

	/*
	 * Something else removed by Merc			-Thoric
	 */
	/* Band-aid alert!  !IS_NPC check -- Blod */
	if ( ch->position < skill->minimum_position && !IS_NPC(ch) )
	{
	    switch( ch->position )
	    {
	      default:
		send_to_char( "You can't concentrate enough.\n\r", ch );
		break;
	      case POS_SITTING:
		send_to_char( "You can't summon enough energy sitting down.\n\r", ch );
		break;
	      case POS_RESTING:
		send_to_char( "You're too relaxed to cast that spell.\n\r", ch );
		break;
	      case POS_KNEELING:
		send_to_char( "You should get to your feet first.\n\r",ch);
                break;
	      case POS_SQUATTING:
		send_to_char("You should stand up first.\n\r", ch);
		break;
	      case POS_SLEEPING:
		send_to_char( "You dream about great feats of magic.\n\r", ch );
		break;
	    }
	    return;
	}

	if ( skill->spell_fun == spell_null )
	{
	    send_to_char( "That's not a spell!\n\r", ch );
	    return;
	}

	if ( !skill->spell_fun )
	{
	    send_to_char( "You cannot cast that... yet.\n\r", ch );
	    return;
	}

	mana = IS_NPC(ch) ? 0 : skill->min_mana;

	if (!IS_NPC(ch) && GET_ADEPT(ch, skill_lookup(skill->name)))
		mana=mana-(mana/4);

	/*
	 * Locate targets.
	 */
	vo = locate_targets( ch, arg2, sn, &victim, &obj );
	if ( vo == &pAbort )
	    return;

	if ( victim
	&&   !IS_SAME_PLANE(ch, victim) )
	{
	    set_char_color( AT_MAGIC, ch );
	    send_to_char( "You don't seem to be able to cast spells on them.\n\r", ch );
	    return;
	}

	if ( !IS_NPC(ch) && ch->mana < mana )
	{
	    send_to_char( "You don't have enough mana.\n\r", ch );
	    return;
	}

	if ((SPELL_FLAG(skill, SF_INSTANT))
	|| (IS_NPC(ch)))
		break;
	if (number_percent() < ch->curr_talent[TAL_TIME]) {
		learn_talent(ch, TAL_TIME);
		act( AT_MAGIC, "You sidestep time.", ch, NULL, NULL, TO_CHAR);
		dont_wait = TRUE;
		break;
	}

	/* multi-participant spells			-Thoric */
	/* we don't use multi-participent spells, lets make this */
	/* for all spells instead -keo */
	add_timer( ch, TIMER_DO_FUN, UMIN(skill->beats / 10, 3),
		do_cast, 1 );
	act( AT_MAGIC, "You begin to channel...", ch, NULL, NULL, TO_CHAR);
	act( AT_MAGIC, "$n begins to channel...", ch, NULL, NULL, TO_ROOM);
	sprintf( staticbuf, "%s %s", arg2, target_name );
	ch->alloc_ptr = str_dup( staticbuf );
	ch->tempnum = sn;
	mana_from_char(ch, mana);
	return;	
      case SUB_TIMER_DO_ABORT:
        DISPOSE( ch->alloc_ptr );
	if ( IS_VALID_SN((sn = ch->tempnum)) )
	{
	    if ( (skill=get_skilltype(sn)) == NULL )
	    {
		send_to_char( "Something went wrong...\n\r", ch );
		bug( "do_cast: SUB_TIMER_DO_ABORT: bad sn %d", sn );
		return;
	    }
	}
	set_char_color( AT_MAGIC, ch );
	send_to_char( "You stop channeling...\n\r", ch );
        return;
      case 1:
	sn = ch->tempnum;
	if ( (skill=get_skilltype(sn)) == NULL )
	{
	    send_to_char( "Something went wrong...\n\r", ch );
	    bug( "do_cast: substate 1: bad sn %d", sn );
	    return;
	}
	if ( !ch->alloc_ptr || !IS_VALID_SN(sn) || skill->type != SKILL_SPELL )
	{
	    send_to_char( "Something cancels out the spell!\n\r", ch );
	    bug( "do_cast: ch->alloc_ptr NULL or bad sn (%d)", sn );
	    return;
	}

	strcpy( staticbuf, ch->alloc_ptr );
	target_name = one_argument(staticbuf, arg2);
	DISPOSE( ch->alloc_ptr );
	ch->substate = SUB_NONE;
	dont_wait = TRUE;
	send_to_char( "You concentrate all the energy into a burst of mystical words!\n\r", ch );
	vo = locate_targets( ch, arg2, sn, &victim, &obj );
	if ( vo == &pAbort )
	  return;
    } 

	say_spell( ch, sn );

    if ( !dont_wait )
	WAIT_STATE( ch, skill->beats );

    /*
     * Getting ready to cast... check for spell components	-Thoric
     */
    if ( !process_spell_components( ch, sn ) )
    {
	
	learn_from_failure( ch, sn );
	return;
    }

    if ( !IS_NPC(ch)
    &&   (number_percent( ) + skill->difficulty * 5) > LEARNED(ch, sn) )
    {
	/* Some more interesting loss of concentration messages  -Thoric */
	switch( number_bits(2) )
	{
	    case 0:	/* too busy */
		if ( IS_FIGHTING(ch) )
		  send_to_char( "This round of battle is too hectic to concentrate properly.\n\r", ch );
		else
		  send_to_char( "You lost your concentration.\n\r", ch );
		break;
	    case 1:	/* irritation */
		if ( number_bits(2) == 0 )
		{
		  switch( number_bits(2) )
		  {
		     case 0: send_to_char( "A tickle in your nose prevents you from keeping your concentration.\n\r", ch ); break;
		     case 1: send_to_char( "An itch on your leg keeps you from properly casting your spell.\n\r", ch ); break;
		     case 2: send_to_char( "Something in your throat prevents you from uttering the proper phrase.\n\r", ch ); break;
		     case 3: send_to_char( "A twitch in your eye disrupts your concentration for a moment.\n\r", ch ); break;
		  }
		}
		else
		  send_to_char( "Something distracts you, and you lose your concentration.\n\r", ch );
		break;
	    case 2:	/* not enough time */
		if ( IS_FIGHTING(ch) )
		  send_to_char( "There wasn't enough time this round to complete the casting.\n\r", ch );
		else
		  send_to_char( "You lost your concentration.\n\r", ch );
		break;
	    case 3:
		send_to_char( "You get a mental block mid-way through the casting.\n\r", ch );
		break;
	}
	learn_from_failure( ch, sn );
	return;
    }
    else
    {
	if ( ch != victim && (skill->target == TAR_CHAR_OFFENSIVE)) {
	if (check_counterspell(ch, victim) ||
	check_displacement(ch,victim) ||
	check_antimagic(ch,victim)) {
		learn_from_success(ch, sn);
		return;
	}
	}

	/*
	 * check for immunity to magic if victim is known...
	 * and it is a TAR_CHAR_DEFENSIVE/SELF spell
	 * otherwise spells will have to check themselves
	 */
	if ( ((skill->target == TAR_CHAR_DEFENSIVE
	||    skill->target == TAR_CHAR_SELF)
	&&    victim && IS_SET(victim->immune, RIS_MAGIC)) )
	{
	   immune_casting( skill, ch, victim, NULL );
	   retcode = rSPELL_FAILED;
	}
	else
	{
	   start_timer(&time_used);

       if (!char_died(ch))
	  retcode = (*skill->spell_fun) ( sn, get_curr_wil(ch), ch, vo );

	 end_timer(&time_used);
	 update_userec(&time_used, &skill->userec);
	}
    }

    if ( retcode == rCHAR_DIED || retcode == rERROR || char_died(ch) )
	return;

    /* learning */
    if ( retcode != rSPELL_FAILED )
		learn_from_success( ch, sn );
	else
		learn_from_failure( ch, sn );

    /*
     * Fixed up a weird mess here, and added double safeguards	-Thoric
     */
    if ( skill->target == TAR_CHAR_OFFENSIVE && victim && !char_died(victim) && victim != ch )
    {
		CHAR_DATA *vch, *vch_next;
	
		for ( vch = ch->in_room->first_person; vch; vch = vch_next )
		{
    	   vch_next = vch->next_in_room;
		}
    }
    
    return;
}


/*
 * Cast spells at targets using a magical object.
 */
ch_ret obj_cast_spell( int sn, int level, CHAR_DATA *ch, CHAR_DATA *victim, OBJ_DATA *obj )
{
    void *vo;
    ch_ret retcode = rNONE;
    SKILLTYPE *skill = get_skilltype( sn );
    struct timeval time_used;

    if ( sn == -1 )
	return retcode;
    if ( !skill || !skill->spell_fun )
    {
	bug( "Obj_cast_spell: bad sn %d.", sn );
	return rERROR;
    }

    if ( IS_SET( ch->in_room->room_flags, ROOM_NO_MAGIC ) )
    {
	set_char_color( AT_MAGIC, ch );
	send_to_char( "Nothing seems to happen...\n\r", ch );
	return rNONE;
    }

    if ( IS_SET( ch->in_room->room_flags, ROOM_SAFE ) && 
	 skill->target == TAR_CHAR_OFFENSIVE)
    {
	set_char_color( AT_MAGIC, ch );
	send_to_char( "Nothing seems to happen...\n\r", ch );
	return rNONE;
    }

    /*
     * Basically this was added to cut down on level 5 players using level
     * 40 scrolls in battle too often ;)		-Thoric
     */
    if ( (skill->target == TAR_CHAR_OFFENSIVE
    ||    number_bits(7) == 1)	/* 1/128 chance if non-offensive */
    &&    skill->type != SKILL_HERB
    &&   !chance( ch, 100 ) )
    {
	switch( number_bits(2) )
	{
	   case 0: failed_casting( skill, ch, victim, NULL );	break;
	   case 1:
		act( AT_MAGIC, "The $t spell backfires!", ch, skill->name, victim, TO_CHAR );
		if ( victim )
		  act( AT_MAGIC, "$n's $t spell backfires!", ch, skill->name, victim, TO_VICT );
		act( AT_MAGIC, "$n's $t spell backfires!", ch, skill->name, victim, TO_NOTVICT );
	   case 2: failed_casting( skill, ch, victim, NULL );	break;
	   case 3:
		act( AT_MAGIC, "The $t spell backfires!", ch, skill->name, victim, TO_CHAR );
		if ( victim )
		  act( AT_MAGIC, "$n's $t spell backfires!", ch, skill->name, victim, TO_VICT );
		act( AT_MAGIC, "$n's $t spell backfires!", ch, skill->name, victim, TO_NOTVICT );
	}
	return rNONE;
    }

    target_name = "";
    switch ( skill->target )
    {
    default:
	bug( "Obj_cast_spell: bad target for sn %d.", sn );
	return rERROR;

    case TAR_IGNORE:
	vo = NULL;
	if ( victim )
	    target_name = victim->name;
	else
	if ( obj )
	    target_name = obj->name;
	break;

    case TAR_CHAR_OFFENSIVE:
	if ( victim != ch )
	{
	  if ( !victim )
	      victim = ch->last_hit;
	  if ( !victim )
	  {
	      send_to_char( "You can't do that.\n\r", ch );
	      return rNONE;
	  }
	}
	vo = (void *) victim;
	break;

    case TAR_CHAR_DEFENSIVE:
	if ( victim == NULL )
	    victim = ch;
	vo = (void *) victim;
	if ( skill->type != SKILL_HERB
	&&   IS_SET(victim->immune, RIS_MAGIC ) )
	{
	    immune_casting( skill, ch, victim, NULL );
	    return rNONE;
	}
	break;

    case TAR_CHAR_SELF:
	vo = (void *) ch;
	if ( skill->type != SKILL_HERB
	&&   IS_SET(ch->immune, RIS_MAGIC ) )
	{
	    immune_casting( skill, ch, victim, NULL );
	    return rNONE;
	}
	break;

    case TAR_OBJ_INV:
	if ( obj == NULL )
	{
	    send_to_char( "You can't do that.\n\r", ch );
	    return rNONE;
	}
	vo = (void *) obj;
	break;
    }

    start_timer(&time_used);
    retcode = (*skill->spell_fun) ( sn, get_curr_wil(ch), ch, vo );
    end_timer(&time_used);
    update_userec(&time_used, &skill->userec);

    if ( retcode == rSPELL_FAILED )
	retcode = rNONE;

    if ( retcode == rCHAR_DIED || retcode == rERROR )
	return retcode;

	if ( char_died(ch) )
	return rCHAR_DIED;

    return retcode;
}



/*
 * Spell functions.
 */


ch_ret spell_blindness( int sn, int level, CHAR_DATA *ch, void *vo )
{
    CHAR_DATA *victim = (CHAR_DATA *) vo;
    AFFECT_DATA af;
    int tmp;
    SKILLTYPE *skill = get_skilltype(sn);

    if ( SPELL_FLAG(skill, SF_PKSENSITIVE)
    &&  !IS_NPC(ch) && !IS_NPC(victim) )
	tmp = level/2;
    else
	tmp = level;

    if ( IS_SET( victim->immune, RIS_MAGIC ) )
    {
	immune_casting( skill, ch, victim, NULL );
	return rSPELL_FAILED;
    }
    if ( IS_AFFECTED(victim, AFF_BLIND))
    {
	failed_casting( skill, ch, victim, NULL );
	return rSPELL_FAILED;
    }

    af.type      = sn;
    af.location  = APPLY_HITROLL;
    af.modifier  = -4;
    af.duration  = (1 + (level / 3)) * DUR_CONV;
    af.bitvector = meb(AFF_BLIND);
    affect_to_char( victim, &af );
    set_char_color( AT_MAGIC, victim );
    send_to_char( "You are blinded!\n\r", victim );
    if ( ch != victim )
    {
	act( AT_MAGIC, "You weave a spell of blindness around $N.", ch, NULL, victim, TO_CHAR );
	act( AT_MAGIC, "$n weaves a spell of blindness about $N.", ch, NULL, victim, TO_NOTVICT );
    }
    return rNONE;
}


ch_ret spell_balance_mind(int sn, int level, CHAR_DATA *ch, void *vo)
{
	CHAR_DATA *victim = (CHAR_DATA *) vo;
	victim->mental_state = 0;
	act( AT_MAGIC, "You balance $N's mind.", ch, NULL, victim, TO_CHAR);
	act( AT_MAGIC, "Your mind is balanced.", ch, NULL, victim, TO_VICT);
	return rNONE;
}


ch_ret spell_bloat(int sn, int level, CHAR_DATA *ch, void *vo)
{
	CHAR_DATA *victim = (CHAR_DATA *) vo;

	if (IS_NPC(victim) || victim->pcdata->condition[COND_THIRST] < 0)
	{
		act( AT_MAGIC, "$N seems unaffected.", ch, NULL, victim, TO_CHAR);
		return rSPELL_FAILED;
	}

	victim->pcdata->condition[COND_THIRST] += 10;
	act( AT_MAGIC, "You feel bloated.", ch, NULL, victim, TO_VICT);
	act( AT_MAGIC, "You fill $N up with water.", ch, NULL, victim, TO_CHAR);
	return rNONE;
}


ch_ret spell_change_sex( int sn, int level, CHAR_DATA *ch, void *vo )
{
    CHAR_DATA *victim = (CHAR_DATA *) vo;
    AFFECT_DATA af;
    SKILLTYPE *skill = get_skilltype(sn);

    if ( IS_SET( victim->immune, RIS_MAGIC ) )
    {
	immune_casting( skill, ch, victim, NULL );
	return rSPELL_FAILED;
    }
    if ( is_affected( victim, sn ) )
    {
        failed_casting( skill, ch, victim, NULL );
	return rSPELL_FAILED;
    }
    af.type      = sn;
    af.duration  = 10 * level * DUR_CONV;
    af.location  = APPLY_SEX;
    do
    {
	af.modifier  = number_range( 0, 2 ) - victim->sex;
    }
    while ( af.modifier == 0 );
    xCLEAR_BITS(af.bitvector);
    affect_to_char( victim, &af );
/*    set_char_color( AT_MAGIC, victim );
    send_to_char( "You feel different.\n\r", victim );
    if ( ch != victim )
	send_to_char( "Ok.\n\r", ch );*/
    successful_casting( skill, ch, victim, NULL );
    return rNONE;
}


ch_ret spell_cure_blindness( int sn, int level, CHAR_DATA *ch, void *vo )
{
    CHAR_DATA *victim = (CHAR_DATA *) vo;
    SKILLTYPE *skill = get_skilltype(sn);

    set_char_color( AT_MAGIC, ch );
    if ( IS_SET( victim->immune, RIS_MAGIC ) )
    {
	immune_casting( skill, ch, victim, NULL );
	return rSPELL_FAILED;
    }

    if ( !is_affected( victim, gsn_blindness ) )
    {
	if ( ch != victim )
	  send_to_char( "You work your cure, but it has no apparent effect.\n\r", ch );
	else
	  send_to_char( "You don't seem to be blind.\n\r", ch );
	return rSPELL_FAILED;
    }
    affect_strip( victim, gsn_blindness );
    set_char_color( AT_MAGIC, victim);
    send_to_char( "Your vision returns!\n\r", victim );
    if ( ch != victim )
	send_to_char( "You work your cure, restoring vision.\n\r", ch );
    return rNONE;
}

ch_ret spell_cure_poison( int sn, int level, CHAR_DATA *ch, void *vo )
{
    CHAR_DATA *victim = (CHAR_DATA *) vo;
    SKILLTYPE *skill = get_skilltype(sn);

    if ( IS_SET( victim->immune, RIS_MAGIC ) )
    {
	immune_casting( skill, ch, victim, NULL );
	return rSPELL_FAILED;
    }

    if ( is_affected( victim, gsn_poison ) )
    {
	affect_strip( victim, gsn_poison );
	set_char_color( AT_MAGIC, victim);
	send_to_char( "A warm feeling runs through your body.\n\r", victim );
	victim->mental_state = URANGE( -100, victim->mental_state, -10 );
	if ( ch != victim )
	{
	  act( AT_MAGIC, "A flush of health washes over $N.", ch, NULL, victim, TO_NOTVICT );
	  act( AT_MAGIC, "You lift the poison from $N's body.", ch, NULL, victim, TO_CHAR );
	}
	return rNONE;
    }
    else
    {
	set_char_color( AT_MAGIC, ch );
	if ( ch != victim )
	  send_to_char( "You work your cure, but it has no apparent effect.\n\r", ch );
	else
	  send_to_char( "You don't seem to be poisoned.\n\r", ch );
	return rSPELL_FAILED;
    }
}

ch_ret spell_curse( int sn, int level, CHAR_DATA *ch, void *vo )
{
    CHAR_DATA *victim = (CHAR_DATA *) vo;
    AFFECT_DATA af;
    SKILLTYPE *skill = get_skilltype(sn);

    if ( IS_SET( victim->immune, RIS_MAGIC ) )
    {
	immune_casting( skill, ch, victim, NULL );
	return rSPELL_FAILED;
    }

    if ( IS_AFFECTED(victim, AFF_CURSE))
    {
	failed_casting( skill, ch, victim, NULL );
	return rSPELL_FAILED;
    }
    af.type      = sn;
    af.duration  = (4*level) * DUR_CONV;
    af.location  = APPLY_HITROLL;
    af.modifier  = -1;
    af.bitvector = meb(AFF_CURSE);
    affect_to_char( victim, &af );

    af.location  = APPLY_SAVING_SPELL;
    af.modifier  = 1;
    affect_to_char( victim, &af );

    set_char_color( AT_MAGIC, victim);
    send_to_char( "You feel unclean.\n\r", victim );
    if ( ch != victim )
    {
      act( AT_MAGIC, "You utter a curse upon $N.", ch, NULL, victim, TO_CHAR );
      act( AT_MAGIC, "$n utters a curse upon $N.", ch, NULL, victim, TO_NOTVICT );
    }
    return rNONE;
}


/* modified version of spell_transport -keo */
ch_ret spell_pigeon( int sn, int level, CHAR_DATA *ch, void *vo )
{
    CHAR_DATA *victim;
    char arg3[MAX_STRING_LENGTH];
    OBJ_DATA *obj;

    target_name = one_argument(target_name, arg3 );

    if ( ( victim = get_char_world( ch, target_name ) ) == NULL
    ||   victim == ch
    ||   IS_SET(ch->in_room->room_flags, ROOM_INDOORS)
    ||   IS_SET(victim->in_room->room_flags, ROOM_INDOORS)
    ||   IS_SET(victim->in_room->room_flags, ROOM_PROTOTYPE))
    {
	send_to_char("The pigeon fails to find the recipient.\n\r", ch);
        return rSPELL_FAILED;
    }
    if ( (obj = get_obj_carry( ch, arg3 ) ) == NULL
    || ( victim->carry_weight + get_obj_weight ( obj ) ) > can_carry_w(victim))
    {
	send_to_char("The pigeon flies off then returns with the item undelivered.\n\r", ch);
        return rSPELL_FAILED;
    }
    if (obj->weight > 10) {
        send_to_char("The pigeon cannot carry that much weight.\n\r", ch);
        return rSPELL_FAILED;
    }
    if (IS_OBJ_STAT(obj, ITEM_ARTIFACT)) {
	send_to_char("The item squirms out of the pigeon's grasp!\n\r", ch);
	return rSPELL_FAILED;
    }

    separate_obj(obj);

    if ( IS_OBJ_STAT( obj, ITEM_NODROP ) )
    {
        send_to_char( "You can't seem to let go of it.\n\r", ch );
        return rSPELL_FAILED;
    }

    act( AT_MAGIC, "You attach $p to the pigeon's legs and it flies away.", ch, obj, NULL, TO_CHAR);
    act( AT_MAGIC, "$n attaches $p to a pigeons's legs and it flies away.", ch, obj, NULL, TO_ROOM);

    obj_from_char( obj );
    obj_to_char( obj, victim );

    act( AT_MAGIC, "A pigeon flies in and drops $p into your hands.", victim, obj, NULL, TO_CHAR);
    act( AT_MAGIC, "A pigeon flies in and drops $p into $n's hands.", victim, obj, NULL, TO_ROOM);

        save_char_obj(ch);
    save_char_obj(victim);
	return rNONE;
}

ch_ret spell_poison( int sn, int wil, CHAR_DATA *ch, void *vo )
{
    CHAR_DATA *victim = (CHAR_DATA *) vo;
    AFFECT_DATA af;
    bool first = TRUE;

    if ( IS_AFFECTED( victim, AFF_POISON ) )
	first = FALSE;
    af.type      = sn;
    af.duration  = wil * DUR_CONV;
    af.location  = APPLY_STR;
    af.modifier  = -2;
    af.bitvector = meb(AFF_POISON);
    affect_join( victim, &af );
    set_char_color( AT_GREEN, victim );
    send_to_char( "You feel very sick.\n\r", victim );
      victim->mental_state = URANGE( 10, victim->mental_state
  			   + (first ? 5 : 0), 100 );
    if ( ch != victim )
    {
      act( AT_GREEN, "$N shivers as your poison spreads through $S body.", ch, NULL, victim, TO_CHAR );
      act( AT_GREEN, "$N shivers as $n's poison spreads through $S body.", ch, NULL, victim, TO_NOTVICT );
    }
    return rNONE;
}

ch_ret spell_remove_curse( int sn, int level, CHAR_DATA *ch, void *vo )
{
    OBJ_DATA *obj;
    CHAR_DATA *victim = (CHAR_DATA *) vo;
    SKILLTYPE *skill = get_skilltype(sn);

    if ( IS_SET( victim->immune, RIS_MAGIC ) )
    {
	immune_casting( skill, ch, victim, NULL );
	return rSPELL_FAILED;
    }

    if ( is_affected( victim, gsn_curse ) )
    {
	affect_strip( victim, gsn_curse );
	set_char_color( AT_MAGIC, victim );
	send_to_char( "The weight of your curse is lifted.\n\r", victim );
	if ( ch != victim )
	{
	    act( AT_MAGIC, "You dispel the curses afflicting $N.", ch, NULL, victim, TO_CHAR );
	    act( AT_MAGIC, "$n's dispels the curses afflicting $N.", ch, NULL, victim, TO_NOTVICT );
	}
    }
    else
    if ( victim->first_carrying )
    {
	for ( obj = victim->first_carrying; obj; obj = obj->next_content )
	   if ( !obj->in_obj
	   && (IS_OBJ_STAT( obj, ITEM_NOREMOVE )
	    || IS_OBJ_STAT( obj, ITEM_NODROP ) ) )
	   {
	      if ( IS_OBJ_STAT( obj, ITEM_NOREMOVE ) )
		xREMOVE_BIT( obj->extra_flags, ITEM_NOREMOVE );
	      if ( IS_OBJ_STAT( obj, ITEM_NODROP ) )
		xREMOVE_BIT( obj->extra_flags, ITEM_NODROP );
	      set_char_color( AT_MAGIC, victim );
	      send_to_char( "You feel a burden released.\n\r", victim );
	      if ( ch != victim )
	      {
		act( AT_MAGIC, "You dispel the curses afflicting $N.", ch, NULL, victim, TO_CHAR );
		act( AT_MAGIC, "$n's dispels the curses afflicting $N.", ch, NULL, victim, TO_NOTVICT );
	      }
	      return rNONE;
	   }
    }
    return rNONE;
}

ch_ret spell_remove_trap( int sn, int level, CHAR_DATA *ch, void *vo )
{
    OBJ_DATA *obj;
    OBJ_DATA *trap;
    bool found;
    int retcode;
    SKILLTYPE *skill = get_skilltype(sn);

    if ( !target_name || target_name[0] == '\0' )
    {
       send_to_char( "Remove trap on what?\n\r", ch );
       return rSPELL_FAILED;
    }

    found = FALSE;

    if ( !ch->in_room->first_content )
    {
       send_to_char( "You can't find that here.\n\r", ch );
       return rNONE;
    }

    for ( obj = ch->in_room->first_content; obj; obj = obj->next_content )
       if ( can_see_obj( ch, obj ) && nifty_is_name( target_name, obj->name ) )
       {
	  found = TRUE;
	  break;
       }

    if ( !found )
    {
       send_to_char( "You can't find that here.\n\r", ch );
       return rSPELL_FAILED;
    }

    if ( (trap = get_trap( obj )) == NULL )
    {
	failed_casting( skill, ch, NULL, NULL );
	return rSPELL_FAILED;
    }


    if ( chance(ch, get_curr_wis(ch)) )
    {
       send_to_char( "Oops!\n\r", ch );
       retcode = spring_trap(ch, trap);
       if ( retcode == rNONE )
         retcode = rSPELL_FAILED;
       return retcode;
    }

    extract_obj( trap );

    successful_casting( skill, ch, NULL, NULL );
    return rNONE;
}


ch_ret spell_satiate(int sn, int level, CHAR_DATA *ch, void *vo)
{
        CHAR_DATA *victim = (CHAR_DATA *) vo;

        if (IS_NPC(victim) || victim->pcdata->condition[COND_FULL] < 0) {
                act( AT_MAGIC, "$N seems unaffected.", ch, NULL, victim, TO_CHAR);
                return rSPELL_FAILED;
        }

        victim->pcdata->condition[COND_FULL] += 20;
        act( AT_MAGIC, "You are satiated.", ch, NULL, victim, TO_VICT);
        act( AT_MAGIC, "You fill $N's stomach.", ch, NULL, victim, TO_CHAR);
        return rNONE;
}


/* room identify - keo */
ch_ret spell_sense_location( int sn, int level, CHAR_DATA *ch, void *vo )
{
	char *sect;
	int temp;
	ch_printf(ch, "You are in %s in %s, on the world of %s.\n\r",
ch->in_room->name, ch->in_room->area->name, ch->in_room->area->resetmsg);
switch( ch->in_room->sector_type ) {
        default:		sect = "an indeterminate location";
break;
        case SECT_INSIDE:	sect = "inside";
break;
        case SECT_CITY:		sect = "a city street";
break;
        case SECT_FIELD:	sect = "a field";
break;
        case SECT_FOREST:	sect = "a forest";
break;
        case SECT_HILLS:	sect = "in the hills";
break;
        case SECT_MOUNTAIN:	sect = "in the mountains";
break;
        case SECT_WATER_SWIM:	sect = "swallow water";
break;
        case SECT_WATER_NOSWIM:	sect = "deep water";
break;
        case SECT_UNDERWATER:	sect = "underwater";
break;
        case SECT_AIR:		sect = "in the air";
break;
        case SECT_DESERT:	sect = "in the desert";
break;
        case SECT_OCEANFLOOR:	sect = "on the oceanfloor";
break;
        case SECT_UNDERGROUND:	sect = "underground";
break;
	case SECT_LAVA:		sect = "molten lava";
break;
	case SECT_SWAMP:	sect = "in the swamps";
break;
	case SECT_BEACH:	sect = "on a beach";
break;
	case SECT_ICE:		sect = "on ice";
break;
	}
	ch_printf(ch, "It appears to be %s.\n\r", sect);
	if (ch->in_room->room_flags)
	ch_printf(ch, "It seems to be %s.\n\r",
		flag_string(ch->in_room->room_flags, r_flags) );
	temp = ch->in_room->curr_water;
	ch_printf(ch, "%s, and ", temp >= 100 ? 
	"The room is completely underwater" : temp > 50 ?
	"The water is deep" : temp > 0 ? "The water is muddy and shallow"
	: "The room is dry");
	temp = ch->in_room->curr_vegetation;
	ch_printf(ch, "%s vegetation grows.\n\r", temp >= 66 ?
	"thick" : temp > 33 ? "some" : "no"); 
	temp = ch->in_room->curr_resources;
        ch_printf(ch, "There is a %s concentration of resources here.\n\r", temp >= 66 ?
        "high" : temp > 33 ? "moderate" : "low");
	if (ch->in_room->runes)
	ch_printf( ch, "Runes of %s line the walls.\n\r",
	        flag_string(ch->in_room->runes, rune_flags) );
	return rNONE;
}


ch_ret spell_shadowshroud( int sn, int level, CHAR_DATA *ch, void *vo )
{
	OBJ_DATA *obj = (OBJ_DATA *) vo;

	if (IS_OBJ_STAT(obj, ITEM_DARK)) {
		ch_printf(ch, "That item is already shrouded in shadows.\n\r");
		return rSPELL_FAILED;
	}
	if (IS_OBJ_STAT(obj, ITEM_GLOW)) {
	    if (obj->value[4] == BRAND_RADIANT) {
		send_to_char("You are unable to quench this radiant light.\n\r", ch);
		return rSPELL_FAILED;
	    }
		act( AT_MAGIC, "The $t grows dim and ceases to shine.", ch, myobj(obj), NULL, TO_CHAR);
		act( AT_MAGIC, "$n's $t grows dim and ceases to shine.", ch, myobj(obj), NULL, TO_ROOM);
		xREMOVE_BIT(obj->extra_flags, ITEM_GLOW);
		return rNONE;
	}
	act( AT_MAGIC, "Swirling shadows shroud the $t.", ch, myobj(obj), NULL, TO_CHAR);
	act( AT_MAGIC, "Swirling shadows shroud $n's $t.", ch, myobj(obj), NULL, TO_ROOM);
	xSET_BIT(obj->extra_flags, ITEM_DARK);
	return rNONE;
}


ch_ret spell_sleep( int sn, int level, CHAR_DATA *ch, void *vo )
{
    AFFECT_DATA af;
    int tmp;
    CHAR_DATA *victim;
    SKILLTYPE *skill = get_skilltype(sn);

    if ( ( victim = get_char_room( ch, target_name ) ) == NULL )
    {
	send_to_char( "They aren't here.\n\r", ch );
	return rSPELL_FAILED;
    }

    if ( !IS_NPC(victim) && IS_FIGHTING(victim) )
    {
	send_to_char( "You cannot sleep a fighting player.\n\r", ch );
	return rSPELL_FAILED;
    }

    if ( IS_SET( victim->immune, RIS_SLEEP ) )
    {
	immune_casting( skill, ch, victim, NULL );
	return rSPELL_FAILED;
    }

    if ( SPELL_FLAG(skill, SF_PKSENSITIVE)
    &&  !IS_NPC(ch) && !IS_NPC(victim) )
	tmp = get_curr_wil(ch)/2;
    else
	tmp = get_curr_wil(ch);

    if ( number_range(1, get_curr_wil(ch))
    <    number_range(1, get_curr_wil(victim) + TALENT(victim, TAL_SECURITY)))
    {
	failed_casting( skill, ch, victim, NULL );
	return rSPELL_FAILED;
    }
	if (ch == victim)
	  return rSPELL_FAILED;
	if ( !IS_FIGHTING(victim) )
	{
	  victim->last_hit = ch;
	  ch->last_hit = victim;
	} else
	  return rSPELL_FAILED;

    af.type      = sn;
    af.duration  = get_curr_wil(ch)/5;
    af.location  = APPLY_NONE;
    af.modifier  = 0;
    af.bitvector = meb(AFF_SLEEP);
    affect_join( victim, &af );

    /* Added by Narn at the request of Dominus. */
    if ( !IS_NPC( victim ) )
    {
	sprintf( log_buf, "%s has cast sleep on %s.", ch->name, victim->name );
	to_channel( log_buf, "Spell", PERMIT_SECURITY );
    }

    if ( IS_AWAKE(victim) )
    {
	act( AT_MAGIC, "You feel very sleepy ..... zzzzzz.", victim, NULL, NULL, TO_CHAR );
	act( AT_MAGIC, "$n goes to sleep.", victim, NULL, NULL, TO_ROOM );
	victim->position = POS_SLEEPING;
    }
    if ( IS_NPC( victim ) )
      start_hating( victim, ch );

    return rNONE;
}


ch_ret spell_null( int sn, int level, CHAR_DATA *ch, void *vo )
{
    send_to_char( "That's not a spell!\n\r", ch );
    return rNONE;
}

/* don't remove, may look redundant, but is important */
ch_ret spell_notfound( int sn, int level, CHAR_DATA *ch, void *vo )
{
    send_to_char( "That's not a spell!\n\r", ch );
    return rNONE;
}


/*
 *   Haus' Spell Additions
 *
 */

/*
 * Syntax portal (mob/char) 
 * opens a 2-way EX_PORTAL from caster's room to room inhabited by  
 *  mob or character won't mess with existing exits
 *
 * do_mp_open_passage, combined with spell_astral
 */
ch_ret spell_portal( int sn, int level, CHAR_DATA *ch, void *vo )
{
    CHAR_DATA *victim;
    ROOM_INDEX_DATA *targetRoom, *fromRoom;
    int targetRoomVnum;
    OBJ_DATA *portalObj;
    EXIT_DATA *pexit;
    char buf[MAX_STRING_LENGTH];
    SKILLTYPE *skill = get_skilltype(sn);

    /* No go if all kinds of things aren't just right, including the caster
       and victim are not both pkill or both peaceful. -- Narn
    */
    if ( ( victim = get_char_world( ch, target_name ) ) == NULL
    ||   victim == ch
    ||   !victim->in_room
    ||   IS_SET(victim->in_room->room_flags, ROOM_PRIVATE)
    ||   IS_SET(victim->in_room->room_flags, ROOM_SOLITARY)
    ||   IS_SET(victim->in_room->room_flags, ROOM_NO_ASTRAL)
    ||   IS_SET(victim->in_room->room_flags, ROOM_NO_RECALL)
    ||   IS_SET(victim->in_room->room_flags, ROOM_PROTOTYPE)
    ||   IS_SET(ch->in_room->room_flags, ROOM_NO_RECALL)
    ||   IS_SET(ch->in_room->room_flags, ROOM_NO_ASTRAL)
    ||	(IS_NPC(victim) && xIS_SET(victim->act, ACT_PROTOTYPE)))
    {
	failed_casting( skill, ch, victim, NULL );
	return rSPELL_FAILED;
    }

    if (victim->in_room == ch->in_room)
    {
	send_to_char("They are right beside you!", ch);
	return rSPELL_FAILED;
    }

    
    targetRoomVnum = victim->in_room->vnum;
    fromRoom = ch->in_room;
    targetRoom = victim->in_room;

    /* Check if there already is a portal in either room. */
    for ( pexit = fromRoom->first_exit; pexit; pexit = pexit->next )
    {
	if ( IS_SET( pexit->exit_info, EX_PORTAL ) ) 
	{
	    send_to_char("There is already a portal in this room.\n\r",ch);
	    return rSPELL_FAILED;
	}
 
	if ( pexit->vdir == DIR_PORTAL )
	{
	    send_to_char("You may not create a portal in this room.\n\r",ch);
	    return rSPELL_FAILED;
	}
    }

    for ( pexit = targetRoom->first_exit; pexit; pexit = pexit->next )
	if ( pexit->vdir == DIR_PORTAL )
	{
	    failed_casting( skill, ch, victim, NULL );
            return rSPELL_FAILED;
	}

    pexit = make_exit( fromRoom, targetRoom, DIR_PORTAL ); 
    pexit->keyword 	= STRALLOC( "portal" );
    pexit->description	= STRALLOC( "You gaze into the shimmering portal...\n\r" );
    pexit->key     	= -1;
    pexit->exit_info	= EX_PORTAL | EX_xENTER | EX_HIDDEN | EX_xLOOK;
    pexit->vnum    	= targetRoomVnum;

    portalObj = create_object( get_obj_index( OBJ_VNUM_PORTAL ), 0 );
    portalObj->timer = 5;
    sprintf( buf, "a portal created by %s", ch->name );
    STRFREE( portalObj->short_descr );
    portalObj->short_descr = STRALLOC( buf ); 

    /* support for new casting messages */
    if ( !skill->hit_char || skill->hit_char[0] == '\0' )
    {
	set_char_color( AT_MAGIC, ch );
	send_to_char("You utter an incantation, and a portal forms in front of you!\n\r", ch);
    }
    else
	act( AT_MAGIC, skill->hit_char, ch, NULL, victim, TO_CHAR );
    if ( !skill->hit_room || skill->hit_room[0] == '\0' )
	act( AT_MAGIC, "$n utters an incantation, and a portal forms in front of you!", ch, NULL, NULL, TO_ROOM );
    else
	act( AT_MAGIC, skill->hit_room, ch, NULL, victim, TO_ROOM );
    if ( !skill->hit_vict || skill->hit_vict[0] == '\0' ) {
	act( AT_MAGIC, "A shimmering portal forms in front of you!", victim, NULL, NULL, TO_ROOM );
	act( AT_MAGIC, "A shimmering portal forms in front of you!", victim, NULL, NULL, TO_CHAR );
    } else
	act( AT_MAGIC, skill->hit_vict, victim, NULL, victim, TO_ROOM );
    portalObj = obj_to_room( portalObj, ch->in_room );

    pexit = make_exit( targetRoom, fromRoom, DIR_PORTAL );
    pexit->keyword 	= STRALLOC( "portal" );
    pexit->description	= STRALLOC( "You gaze into the shimmering portal...\n\r" );
    pexit->key          = -1;
    pexit->exit_info    = EX_PORTAL | EX_xENTER | EX_HIDDEN;
    pexit->vnum         = targetRoomVnum;

    portalObj = create_object( get_obj_index( OBJ_VNUM_PORTAL ), 0 );
    portalObj->timer = 5;
    STRFREE( portalObj->short_descr );
    portalObj->short_descr = STRALLOC( buf ); 
    portalObj = obj_to_room( portalObj, targetRoom );
    return rNONE;
}


ch_ret spell_recharge( int sn, int level, CHAR_DATA *ch, void *vo )
{
    OBJ_DATA *obj = (OBJ_DATA *) vo;

    if ( obj->item_type == ITEM_STAFF 
    ||   obj->item_type == ITEM_WAND) 
    {
	separate_obj(obj);
	if ( obj->value[2] == obj->value[1]
	||   obj->value[1] > (obj->pIndexData->value[1] * 4) )
	{
	    act( AT_FIRE, "$p bursts into flames, injuring you!", ch, obj, NULL, TO_CHAR ); 
	    act( AT_FIRE, "$p bursts into flames, charring $n!", ch, obj, NULL, TO_ROOM);
	    extract_obj(obj);
		return rSPELL_FAILED;
	}

	if ( chance(ch, 2) )
	{
	    act( AT_YELLOW, "$p glows with a blinding magical luminescence.", 
		ch, obj, NULL, TO_CHAR);
	    obj->value[1] *= 2;
	    obj->value[2] = obj->value[1];
	    return rNONE;
	}
	else
	if ( chance(ch, 5) )
	{
	    act( AT_YELLOW, "$p glows brightly for a few seconds...", 
		ch, obj, NULL, TO_CHAR);
	    obj->value[2] = obj->value[1];
	    return rNONE;
	}
	else
	if ( chance(ch, 10) )
	{
	    act( AT_WHITE, "$p disintegrates into a void.", ch, obj, NULL, TO_CHAR);
	    act( AT_WHITE, "$n's attempt at recharging fails, and $p disintegrates.", 
		ch, obj, NULL, TO_ROOM);
	    extract_obj(obj);
	    return rSPELL_FAILED;
	}
	else
	if ( chance(ch, 20 ) )
	{
	    send_to_char("Nothing happens.\n\r", ch);
	    return rSPELL_FAILED;
	}
	else
	{
	    act( AT_MAGIC, "$p feels warm to the touch.", ch, obj, NULL, TO_CHAR);
	    --obj->value[1];
	    obj->value[2] = obj->value[1];
	    return rNONE;
	}
    }
    else
    {
	send_to_char( "You can't recharge that!\n\r", ch);
	return rSPELL_FAILED;
    }    
}

/* Ignores pickproofs, but can't unlock containers. -- Altrag 17/2/96 */
ch_ret spell_knock( int sn, int level, CHAR_DATA *ch, void *vo )
{
  EXIT_DATA *pexit;
    SKILLTYPE *skill = get_skilltype(sn);

  set_char_color(AT_MAGIC, ch);
  /*
   * shouldn't know why it didn't work, and shouldn't work on pickproof
   * exits.  -Thoric
   */
  if ( !(pexit=find_door(ch, target_name, FALSE))
  ||   !IS_SET(pexit->exit_info, EX_CLOSED)
  ||   !IS_SET(pexit->exit_info, EX_LOCKED)
  ||    IS_SET(pexit->exit_info, EX_PICKPROOF) )
  {
	failed_casting( skill, ch, NULL, NULL );
	return rSPELL_FAILED;
  }
  REMOVE_BIT(pexit->exit_info, EX_LOCKED);
  send_to_char( "*Click*\n\r", ch );
  if ( pexit->rexit && pexit->rexit->to_room == ch->in_room )
    REMOVE_BIT( pexit->rexit->exit_info, EX_LOCKED );
  check_room_for_traps( ch, TRAP_UNLOCK | trap_door[pexit->vdir] );
  return rNONE;
}

/* Tells to sleepers in are. -- Altrag 17/2/96 */
ch_ret spell_dream( int sn, int level, CHAR_DATA *ch, void *vo )
{
  CHAR_DATA *victim;
  char arg[MAX_INPUT_LENGTH];
  
  target_name = one_argument(target_name, arg);
  set_char_color(AT_MAGIC, ch);
  if ( !(victim = get_char_world(ch, arg)) )
  {
    send_to_char("They aren't here.\n\r", ch);
    return rSPELL_FAILED;
  }
  if ( victim->position != POS_SLEEPING )
  {
    send_to_char("They aren't asleep.\n\r", ch);
    return rSPELL_FAILED;
  }
  if ( !target_name )
  {
    send_to_char("What do you want them to dream about?\n\r", ch );
    return rSPELL_FAILED;
  }

  set_char_color(AT_TELL, victim);
  ch_printf(victim, "You have dreams about %s telling you '%s'.\n\r",
	 PERS(ch, victim), target_name);
  successful_casting( get_skilltype(sn), ch, victim, NULL );
/*  send_to_char("Ok.\n\r", ch);*/
  return rNONE;
}


	/*******************************************************
	 * Everything after this point is part of SMAUG SPELLS *
	 *******************************************************/

/*
 * Generic offensive spell damage attack			-Thoric
 */
ch_ret spell_attack( int sn, int level, CHAR_DATA *ch, void *vo )
{
    CHAR_DATA *victim = (CHAR_DATA *) vo;
    SKILLTYPE *skill = get_skilltype(sn);
    int dam;
    ch_ret retcode = rNONE;

	if (victim == NULL) {
		failed_casting( skill, ch, victim, NULL );
		return rSPELL_FAILED;
	}
    
    if ( skill->dice )
	dam = UMAX( 0, dice_parse( ch, skill->dice ) );
    else
	dam = dice( 1, level/2 );
    if ( retcode == rNONE && skill->affects
    &&  !char_died(ch) && !char_died(victim)
    && (!is_affected(victim, sn)
    ||  SPELL_FLAG(skill, SF_ACCUMULATIVE)
    ||  SPELL_FLAG(skill, SF_RECASTABLE)) )
	retcode = spell_affectchar( sn, level, ch, victim );
    return retcode;
}

/*
 * Generic area attack						-Thoric
 */
ch_ret spell_area_attack( int sn, int level, CHAR_DATA *ch, void *vo )
{
    CHAR_DATA *vch, *vch_next;
    SKILLTYPE *skill = get_skilltype(sn);
    bool affects;
    int dam;
    bool ch_died = FALSE;
    ch_ret retcode = rNONE;

    if ( IS_SET( ch->in_room->room_flags, ROOM_SAFE ) )
    {
	failed_casting( skill, ch, NULL, NULL );
	return rSPELL_FAILED;
    }

    affects = (skill->affects ? TRUE : FALSE);
    if ( skill->hit_char && skill->hit_char[0] != '\0' )
	act( AT_MAGIC, skill->hit_char, ch, NULL, NULL, TO_CHAR );
    if ( skill->hit_room && skill->hit_room[0] != '\0' )
	act( AT_MAGIC, skill->hit_room, ch, NULL, NULL, TO_ROOM );

    for ( vch = ch->in_room->first_person; vch; vch = vch_next )
    {
	vch_next = vch->next_in_room;

        if ( vch == ch )
	   continue;

	if (!IS_SAME_PLANE(ch, vch))
	   continue;

		if ( skill->dice )
		    dam = dice_parse(ch, skill->dice);
		else
		    dam = dice( 1, level/2 );
	if ( retcode == rNONE && affects && !char_died(ch) && !char_died(vch)
	&& (!is_affected(vch, sn)
	||  SPELL_FLAG(skill, SF_ACCUMULATIVE)
	||  SPELL_FLAG(skill, SF_RECASTABLE)) )
	    retcode = spell_affectchar( sn, level, ch, vch );
	if ( retcode == rCHAR_DIED || char_died(ch) )
	{
	    ch_died = TRUE;
	    break;
	}
    }
    return retcode;
}


ch_ret spell_affectchar( int sn, int level, CHAR_DATA *ch, void *vo )
{
    AFFECT_DATA af;
    SMAUG_AFF *saf;
    SKILLTYPE *skill = get_skilltype(sn);
    CHAR_DATA *victim = (CHAR_DATA *) vo;
    int chance;
    ch_ret retcode = rNONE;

    if ( SPELL_FLAG( skill, SF_RECASTABLE ) )
	affect_strip( victim, sn );
    for ( saf = skill->affects; saf; saf = saf->next ) {
		if ( saf->location >= REVERSE_APPLY )
		    victim = ch;
		else
		    victim = (CHAR_DATA *) vo;

		/* Check if char has this bitvector already */
		af.bitvector = meb(saf->bitvector);
		
		if ( saf->bitvector >= 0
		&&   xIS_SET(victim->affected_by, saf->bitvector)
		&&  !SPELL_FLAG(skill, SF_ACCUMULATIVE) )
		   continue;

		/*
	     * necessary for affect_strip to work properly...
		 */
        
		switch ( saf->bitvector )
        {
	    default:		af.type = sn;			break;
	    case AFF_POISON:
			af.type = gsn_poison;
			chance = ris_save( victim, level, RIS_POISON );
			if ( chance == 1000 ) {
			    retcode = rVICT_IMMUNE;
			    if ( SPELL_FLAG(skill, SF_STOPONFAIL) )
					return retcode;
			    continue;
			}

			victim->mental_state = URANGE( 30, victim->mental_state + 2, 100 );
			break;

		case AFF_BLIND:	af.type = gsn_blindness;	break;
	    case AFF_CURSE:	af.type = gsn_curse;		break;
	    case AFF_SLEEP:	af.type = gsn_sleep;
			chance = ris_save( victim, level, RIS_SLEEP );
			if ( chance == 1000 )
			{
			    retcode = rVICT_IMMUNE;
			    if ( SPELL_FLAG(skill, SF_STOPONFAIL) )
					return retcode;
			    continue;
			}
			break;
	    case AFF_CHARM:		af.type = gsn_charm_person;
			chance = ris_save( victim, level, RIS_CHARM );
			if ( chance == 1000 )
			{
			    retcode = rVICT_IMMUNE;
			    if ( SPELL_FLAG(skill, SF_STOPONFAIL) )
					return retcode;
			    continue;
			}
			break;
	}
	af.duration  = dice_parse(ch, saf->duration);
	af.modifier  = dice_parse(ch, saf->modifier);
	af.location  = saf->location % REVERSE_APPLY;
	if ( af.duration == 0 )
	{
	    switch( af.location )
	    {
		case APPLY_HIT:
		    victim->hit = URANGE( 0, victim->hit + af.modifier, victim->max_hit );
		    update_pos( victim );
		    break;
		case APPLY_MANA:
		    victim->mana = URANGE( 0, victim->mana + af.modifier, victim->max_mana );
		    update_pos( victim );
		    break;
		case APPLY_MOVE:
		    victim->move = URANGE( 0, victim->move + af.modifier, victim->max_move );
		    update_pos( victim );
		    break;
		default:
		    affect_modify( victim, &af, TRUE );
		    break;
	    }
	}
	else
	if ( SPELL_FLAG( skill, SF_ACCUMULATIVE ) )
	    affect_join( victim, &af );
	else
	    affect_to_char( victim, &af );
    }
    update_pos( victim );
    return retcode;
}


/*
 * Generic spell affect						-Thoric
 */
ch_ret spell_affect( int sn, int level, CHAR_DATA *ch, void *vo )
{
    SMAUG_AFF *saf;
    SKILLTYPE *skill = get_skilltype(sn);
    CHAR_DATA *victim = (CHAR_DATA *) vo;
    bool groupsp;
    bool areasp;
    bool hitchar = FALSE, hitroom = FALSE, hitvict = FALSE;
    ch_ret retcode;

    if ( !skill->affects )
    {
	bug( "spell_affect has no affects sn %d", sn );
	return rNONE;
    }
    if ( SPELL_FLAG(skill, SF_GROUPSPELL) )
	groupsp = TRUE;
    else
	groupsp = FALSE;

    if ( SPELL_FLAG(skill, SF_AREA ) )
	areasp = TRUE;
    else
	areasp = FALSE;
    if ( !groupsp && !areasp )
    {
	/* Can't find a victim */
	if ( !victim )
	{
	    failed_casting( skill, ch, victim, NULL );
	    return rSPELL_FAILED;
	}

	if ( (skill->type != SKILL_HERB
	&&    IS_SET( victim->immune, RIS_MAGIC ))
	||    is_immune( victim, SPELL_DAMAGE(skill) ) )
	{
	    immune_casting( skill, ch, victim, NULL );
	    return rSPELL_FAILED;
	}

	/* Spell is already on this guy */
	if ( is_affected( victim, sn )
	&&  !SPELL_FLAG( skill, SF_ACCUMULATIVE )
	&&  !SPELL_FLAG( skill, SF_RECASTABLE ) )
	{
	    failed_casting( skill, ch, victim, NULL );
	    return rSPELL_FAILED;
	}

	if ( (saf = skill->affects) && !saf->next
	&&    saf->location == APPLY_STRIPSN
	&&   !is_affected( victim, dice_parse(ch, saf->modifier) ) )
	{
	    failed_casting( skill, ch, victim, NULL );
	    return rSPELL_FAILED;
	}

    }
    else
    {
	if ( skill->hit_char && skill->hit_char[0] != '\0' )
	{
	    if ( strstr(skill->hit_char, "$N") )
		hitchar = TRUE;
	    else
		act( AT_MAGIC, skill->hit_char, ch, NULL, NULL, TO_CHAR );
	}
	if ( skill->hit_room && skill->hit_room[0] != '\0' )
	{
	    if ( strstr(skill->hit_room, "$N") )
		hitroom = TRUE;
	    else
		act( AT_MAGIC, skill->hit_room, ch, NULL, NULL, TO_ROOM );
	}
	if ( skill->hit_vict && skill->hit_vict[0] != '\0' )
	    hitvict = TRUE;
	if ( victim )
	  victim = victim->in_room->first_person;
	else
	  victim = ch->in_room->first_person;
    }
    if ( !victim )
    {
	bug( "spell_affect: could not find victim: sn %d", sn );
	failed_casting( skill, ch, victim, NULL );
	return rSPELL_FAILED;
    }

    for ( ; victim; victim = victim->next_in_room )
    {
	if ( groupsp || areasp )
	{
	    if ((groupsp && !is_same_group( victim, ch ))
	    ||	 IS_SET( victim->immune, RIS_MAGIC )
	    ||   is_immune( victim, SPELL_DAMAGE(skill) )
	    || (!SPELL_FLAG(skill, SF_RECASTABLE) && is_affected(victim, sn)))
		continue;

	    if ( hitvict && ch != victim )
	    {
		act( AT_MAGIC, skill->hit_vict, ch, NULL, victim, TO_VICT );
		if ( hitroom )
		{
		   act( AT_MAGIC, skill->hit_room, ch, NULL, victim, TO_NOTVICT );
		   act( AT_MAGIC, skill->hit_room, ch, NULL, victim, TO_CHAR );
		}
	    }
	    else
	    if ( hitroom )
		act( AT_MAGIC, skill->hit_room, ch, NULL, victim, TO_ROOM );
	    if ( ch == victim )
	    {
		if ( hitvict )
		  act( AT_MAGIC, skill->hit_vict, ch, NULL, ch, TO_CHAR );
		else
		if ( hitchar )
		  act( AT_MAGIC, skill->hit_char, ch, NULL, ch, TO_CHAR );
	    }
	    else
	    if ( hitchar )
		act( AT_MAGIC, skill->hit_char, ch, NULL, victim, TO_CHAR );
	}
	retcode = spell_affectchar( sn, level, ch, victim );
	if ( !groupsp && !areasp )
	{
	    if ( retcode == rVICT_IMMUNE )
		immune_casting( skill, ch, victim, NULL );
	    else
		successful_casting( skill, ch, victim, NULL );
	    break;
	}
    }
    return rNONE;
}

/*
 * Generic inventory object spell				-Thoric
 */
ch_ret spell_obj_inv( int sn, int level, CHAR_DATA *ch, void *vo )
{
    OBJ_DATA *obj = (OBJ_DATA *) vo;
    SKILLTYPE *skill = get_skilltype(sn);

    if ( !obj )
    {
	failed_casting( skill, ch, NULL, NULL );
	return rNONE;
    }

    switch( SPELL_ACTION(skill) )
    {
	default:
	case SA_NONE:
	  return rNONE;

	case SA_CREATE:
	  if ( SPELL_FLAG(skill, SF_WATER) )	/* create water */
	  {
	    int water;
	    WEATHER_DATA *weath = ch->in_room->area->weather;

	    if ( obj->item_type != ITEM_DRINK_CON )
	    {
		send_to_char( "It is unable to hold water.\n\r", ch );
		return rSPELL_FAILED;
	    }

	    if ( obj->value[2] != LIQ_WATER && obj->value[1] != 0 )
	    {
		send_to_char( "It contains some other liquid.\n\r", ch );
		return rSPELL_FAILED;
	    }

	    water = UMIN( (skill->dice ? dice_parse(ch, skill->dice) : level)
		       * (weath->precip >= 0 ? 2 : 1),
			obj->value[0] - obj->value[1] );

	    if ( water > 0 )
	    {
		separate_obj(obj);
		obj->value[2] = LIQ_WATER;
		obj->value[1] += water;
		if ( !is_name( "water", obj->name ) )
		{
		    char buf[MAX_STRING_LENGTH];

		    sprintf( buf, "%s water", obj->name );
		    STRFREE( obj->name );
		    obj->name = STRALLOC( buf );
		}
	     }
	     successful_casting( skill, ch, NULL, obj );
	     return rNONE;
	  }
	  if ( SPELL_DAMAGE(skill) == SD_FIRE )	/* burn object */
	  {
	     /* return rNONE; */
	  }
	  if ( SPELL_DAMAGE(skill) == SD_POISON	/* poison object */
	  ||   SPELL_CLASS(skill)  == SC_DEATH )
	  {
	     switch( obj->item_type )
	     {
		default:
		  failed_casting( skill, ch, NULL, obj );
		  break;
	        case ITEM_COOK:
		case ITEM_FOOD:
		case ITEM_DRINK_CON:
		  separate_obj(obj);
		  obj->value[3] = 1;
		  successful_casting( skill, ch, NULL, obj );
		  break;
	     }
	     return rNONE;
	  }
	  if ( SPELL_CLASS(skill) == SC_LIFE	/* purify food/water */
	  &&  (obj->item_type == ITEM_FOOD || obj->item_type == ITEM_DRINK_CON
	       || obj->item_type == ITEM_COOK) )
	  {
	     switch( obj->item_type )
	     {
		default:
		  failed_casting( skill, ch, NULL, obj );
		  break;
		case ITEM_COOK:
		case ITEM_FOOD:
		case ITEM_DRINK_CON:
		  separate_obj(obj);
		  obj->value[3] = 0;
		  successful_casting( skill, ch, NULL, obj );
		  break;
	     }
	     return rNONE;
	  }
	  
	  if ( SPELL_CLASS(skill) != SC_NONE )
	  {
	     failed_casting( skill, ch, NULL, obj );
	     return rNONE;
	  }
	  switch( SPELL_POWER(skill) )		/* clone object */
	  {
	     OBJ_DATA *clone;

	     default:
	     case SP_NONE:
		if ( obj->cost > (get_curr_int(ch) * get_curr_wis(ch))/5 )
		{
		   failed_casting( skill, ch, NULL, obj );
		   return rNONE;
		}
		break;
	     case SP_MINOR:
		if ( obj->cost > (get_curr_int(ch) * get_curr_wis(ch))/3 )
		{
		   failed_casting( skill, ch, NULL, obj );
		   return rNONE;
		}
		break;
	     case SP_GREATER:
		if ( obj->cost > (get_curr_int(ch) * get_curr_wis(ch))/2 )
		{
		   failed_casting( skill, ch, NULL, obj );
		   return rNONE;
		}
		break;
	     case SP_MAJOR:
		if ( obj->cost > (get_curr_int(ch) * get_curr_wis(ch)) )
		{
		   failed_casting( skill, ch, NULL, obj );
		   return rNONE;
		}
		break;
	     clone = clone_object(obj);
	     clone->timer = skill->dice ? dice_parse(ch, skill->dice) : 0;
	     obj_to_char( clone, ch );
	     successful_casting( skill, ch, NULL, obj );
	  }
	  return rNONE;

	case SA_DESTROY:
	case SA_RESIST:
	case SA_SUSCEPT:
	case SA_DIVINATE:
	  if ( SPELL_DAMAGE(skill) == SD_POISON ) /* detect poison */
	  {
	     if ( obj->item_type == ITEM_DRINK_CON
	     ||   obj->item_type == ITEM_FOOD 
	     ||   obj->item_type == ITEM_COOK )
	     {
		if ( obj->item_type == ITEM_COOK && obj->value[2] == 0)
		    send_to_char("It looks undercooked.\n\r", ch );
		else if ( obj->value[3] != 0 )
		    send_to_char( "You smell poisonous fumes.\n\r", ch );
		else
		    send_to_char( "It looks very delicious.\n\r", ch );
	     }
	     else
		send_to_char( "It doesn't look poisoned.\n\r", ch );
	     return rNONE;
	  }
	  return rNONE;
	case SA_OBSCURE:			/* make obj invis */
	  if ( IS_OBJ_STAT(obj, ITEM_INVIS) 
	  ||   chance(ch, skill->dice ? dice_parse(ch, skill->dice) : 20))
          {
	     failed_casting( skill, ch, NULL, NULL );
     	     return rSPELL_FAILED;
	  }
	  successful_casting( skill, ch, NULL, obj );
	  xSET_BIT(obj->extra_flags, ITEM_INVIS);
	  return rNONE;

	case SA_CHANGE:
	  return rNONE;
    }
    return rNONE;
}

/*
 * Generic object creating spell				-Thoric
 */
ch_ret spell_create_obj( int sn, int level, CHAR_DATA *ch, void *vo )
{
    SKILLTYPE *skill = get_skilltype(sn);
    int vnum = skill->value;
    OBJ_DATA *obj;
    OBJ_INDEX_DATA *oi;

    /*
     * Add predetermined objects here
     */
    if ( (oi=get_obj_index(vnum)) == NULL
    ||   (obj=create_object(oi, 0)) == NULL )
    {
	failed_casting( skill, ch, NULL, NULL );
	return rNONE;
    }
    obj->timer = skill->dice ? dice_parse( ch, skill->dice ) : 0;
    successful_casting( skill, ch, NULL, obj );
    if ( !IS_OBJ_STAT(obj, ITEM_NO_TAKE) )
      obj_to_char( obj, ch );
    else
      obj_to_room( obj, ch->in_room );
    return rNONE;
}

/*
 * Generic mob creating spell					-Thoric
 */
ch_ret spell_create_mob( int sn, int level, CHAR_DATA *ch, void *vo )
{
    SKILLTYPE *skill = get_skilltype(sn);
    int vnum = skill->value;
    CHAR_DATA *mob;
    MOB_INDEX_DATA *mi;
    AFFECT_DATA af;

    /*
     * Add predetermined mobiles here
     */
    if ( vnum == 0 )
    {
	if ( !str_cmp( target_name, "cityguard" ) )
	  vnum = MOB_VNUM_CITYGUARD;
	if ( !str_cmp( target_name, "vampire" ) )
	  vnum = MOB_VNUM_VAMPIRE;
    }

    if ( (mi=get_mob_index(vnum)) == NULL
    ||   (mob=create_mobile(mi)) == NULL )
    {
	failed_casting( skill, ch, NULL, NULL );
	return rNONE;
    }
    mob->max_hit = get_curr_con(mob)*25;
    mob->hit	 = mob->max_hit;
    mob->gold	 = 0;
    successful_casting( skill, ch, mob, NULL );
    char_to_room( mob, ch->in_room );
    add_follower( mob, ch );
    af.type      = sn;
    af.duration  = (number_fuzzy( (level + 1) / 3 ) + 1) * DUR_CONV;
    af.location  = 0;
    af.modifier  = 0;
    af.bitvector = meb(AFF_CHARM);
    affect_to_char( mob, &af );
    return rNONE;
}

/*
 * Generic handler for new "SMAUG" spells			-Thoric
 */
ch_ret spell_smaug( int sn, int level, CHAR_DATA *ch, void *vo )
{
	char arg2[MAX_STRING_LENGTH];
    struct skill_type *skill = get_skilltype(sn);
/* added bad skill check - shogar */
    if(!skill || sn == -1)
    {
	if (ch && ch->name && ch->in_room && ch->in_room->vnum)
	bug("bad skill %d passed to spell_smaug: ch->name %s, room %d",sn, ch->name, ch->in_room->vnum);
	return rNONE;
    }

	if (ranged_target_name)
		one_argument(ranged_target_name, arg2);

	if (SPELL_FLAG(skill, SF_EARTH)
	&& (ch->in_room->curr_vegetation < 33)) {
		act( AT_GREEN, "There isn't enough vegetation here to cast this spell.", ch, NULL, NULL, TO_CHAR);
		return rNONE;
	}

	if (SPELL_FLAG(skill, SF_AIR)
	&& (ch->in_room->curr_water >= 100)) {
		act( AT_CYAN, "There isn't enough air here to cast this spell.", ch, NULL, NULL, TO_CHAR);
		act( AT_CYAN, "Some water bubbles float up toward the surface.", ch, NULL, NULL, TO_ROOM);
		return rNONE;
	}

	if (SPELL_FLAG(skill, SF_WATER)
	&& (ch->in_room->curr_water < 33)) {
		act( AT_BLUE, "There isn't enough water here to cast this spell.", ch, NULL, NULL, TO_CHAR);
		return rNONE;
	}

	if (SPELL_FLAG(skill, SF_ASTRAL)
	&& (!IS_AFFECTED(ch, AFF_DREAMWORLD))) {
		send_to_char("You must be in the dreamworld to cast this spell.\n\r", ch);
		return rNONE;
	}

	if (SPELL_FLAG(skill, SF_GROUND)
	&& (IS_SET(ch->in_room->room_flags, ROOM_NOFLOOR))) {
		send_to_char("There needs to be ground nearby to cast this spell.\n\r", ch);
		return rNONE;
	}


    switch( skill->target )
    {
	case TAR_IGNORE:

	  /* offensive area spell */
	  if ( SPELL_FLAG(skill, SF_AREA)
	  && ((SPELL_ACTION(skill) == SA_DESTROY
	  &&   SPELL_CLASS(skill) == SC_LIFE)
	  ||  (SPELL_ACTION(skill) == SA_CREATE
	  &&   SPELL_CLASS(skill) == SC_DEATH)) )
		return spell_area_attack( sn, level, ch, vo );

	  if ( SPELL_ACTION(skill) == SA_CREATE )
	  {
		if ( SPELL_FLAG(skill, SF_OBJECT) )	/* create object */
		  return spell_create_obj( sn, level, ch,  vo );
		if ( SPELL_CLASS(skill) == SC_LIFE )	/* create mob */
		  return spell_create_mob( sn, level, ch,  vo );
	  }

	  /* affect a distant player */
	  if ( SPELL_FLAG(skill, SF_DISTANT)
	  &&   SPELL_FLAG(skill, SF_CHARACTER))
		return spell_affect(sn, level, ch, get_char_world( ch, target_name ));

	  /* affect a player in this room (should have been TAR_CHAR_XXX) */
	  if ( SPELL_FLAG(skill, SF_CHARACTER) )
		return spell_affect(sn, level, ch, get_char_room( ch, target_name ));

	  if ( skill->range > 0 && find_door(ch, arg2, TRUE) != NULL && (
	   		(SPELL_ACTION(skill) == SA_DESTROY
			&& SPELL_CLASS(skill) == SC_LIFE)
			|| (SPELL_ACTION(skill) == SA_CREATE
			&& SPELL_CLASS(skill) == SC_DEATH) ) )
	  /* will fail, or be an area/group affect */
	  return spell_affect( sn, level, ch, vo );

	case TAR_CHAR_OFFENSIVE:

	  if ((skill->range > 0) 
		  && ranged_target_name 
		  && find_door(ch, arg2, TRUE) != NULL 
		  && ((SPELL_ACTION(skill) == SA_DESTROY 
				&& SPELL_CLASS(skill) == SC_LIFE)
		  || (SPELL_ACTION(skill) == SA_CREATE  
				&& SPELL_CLASS(skill) == SC_DEATH))
		  && get_char_room(ch, ranged_target_name)==NULL)
	  
	  /* a regular damage inflicting spell attack */
	  if ((SPELL_ACTION(skill) == SA_DESTROY && SPELL_CLASS(skill) == SC_LIFE)
	  ||  (SPELL_ACTION(skill) == SA_CREATE  && SPELL_CLASS(skill) == SC_DEATH)) 
			return spell_attack( sn, level, ch, vo );
	  
	  /* a nasty spell affect */
	  return spell_affect( sn, level, ch, vo );

	case TAR_CHAR_DEFENSIVE:
	case TAR_CHAR_SELF:

	  return spell_affect( sn, level, ch, vo );

	case TAR_OBJ_INV:
	  return spell_obj_inv( sn, level, ch, vo );
    }
    return rNONE;
}

/* tap <amount> - draws <amount> mana from a zone into a char, plus or minus a random element */
void do_tap(CHAR_DATA *ch, char *argument) {
	int amount;
	char arg[MAX_INPUT_LENGTH];

	argument = one_argument(argument, arg);

	amount = atoi(arg);

	if (amount < 1) {
		send_to_char("You can't do that.\r\n", ch);
		return;
	}

	if (amount > ch->max_mana) {
		send_to_char("That might be harmful to your health.\r\n", ch);
		return;
	}

	STRFREE(ch->last_taken);
	ch->last_taken = STRALLOC("tapping mana");
	ch->wait=UMAX(ch->wait, (amount/(100+TALENT(ch, TAL_CATALYSM))));

	amount += number_range(-10,
		(ch->in_room->area->weather->mana * 10) - amount);

	if (amount < 0) {
		act(AT_MAGIC, "There seems to be a powerful mana vaccuum here!", ch, NULL, NULL, TO_CHAR);
		act(AT_MAGIC, "$n concentrates, then looks startled as $s body glows white for an instant.", ch, NULL, NULL, TO_ROOM);
	} else {
		act(AT_MAGIC, "You open yourself to the surroundings, letting extra mana flow into you.", ch, NULL, NULL, TO_CHAR);
		act(AT_MAGIC, "$n concentrates, then gets a content look on $s face.", ch, NULL, NULL, TO_ROOM);
	}

	ch->mana += amount;
	ch->in_room->area->weather->mana -= (int)amount / 100;
}

void do_link(CHAR_DATA *ch, char *argument) {
	CHAR_DATA *victim;

	if (ch->curr_talent[TAL_CATALYSM] < 100) {
		huh(ch);
		return;
	}

	victim = find_target(ch, argument, FALSE);
	if (!victim) return;

	if (IS_NPC(victim)) {
		send_to_char("You can't link with a mensch!\n\r", ch);
		return;
	}

	if (victim == ch) {
		if (ch->pcdata->magiclink) {
			act(AT_MAGIC, "$n breaks off the magic link.", ch, NULL, victim, TO_VICT);
			act(AT_MAGIC, "You break off the magic link with $N.", ch, NULL, ch->pcdata->magiclink, TO_CHAR);
			ch->pcdata->magiclink->pcdata->magiclink = NULL;
			ch->pcdata->magiclink = NULL;
		} else {
			send_to_char("You are not linked to anyone.\n\r", ch);
		}
		return;
	}

	if (ch->pcdata->magiclink) {
		send_to_char("You are already linked with somebody.\n\r", ch);
		return;
	}

	if (victim->pcdata->magiclink) {
		send_to_char("They are already linked with somebody.\n\r", ch);
		return;
	}

	if (IS_NPC(victim)) {
		send_to_char("They have no magic to link with.\n\r", ch);
		return;
	}

	if (!IS_CONSENTING(victim, ch)) {
		send_to_char("You cannot link your magic with them without their consent.\n\r", ch);
		return;
	}

	act(AT_MAGIC, "You link your magic together with $N.", ch, NULL, victim, TO_CHAR);
	act(AT_MAGIC, "$n links $s magic together with you.", ch, NULL, victim, TO_VICT);
	ch->pcdata->magiclink = victim;
	victim->pcdata->magiclink = ch;
}

/* Return TRUE if it stops you from casting the spell */
bool check_manastorm(CHAR_DATA *ch) {
	WEATHER_DATA *weath;
	int storm;

	weath = ch->in_room->area->weather;
	if (weath->mana < 200) return FALSE;
	if (weath->wind < 200) return FALSE;
	storm = weath->wind + weath->precip;
	storm *= weath->mana;

	if (storm < 4000) return FALSE;

	switch(number_range(1,5)) {
	    case 1:
		act(AT_CHAOS, "The errant mana surges through your being!",
		ch, NULL, NULL, TO_CHAR);
		ch->mana += 100 + (int)storm/4000;
		return TRUE;
	    case 2:
		act(AT_CHAOS, "White-violet lightning sears your being!",
		ch, NULL, NULL, TO_CHAR);
		act(AT_CHAOS, "White-violet lightning sears $n's being!",
		ch, NULL, NULL, TO_ROOM);
		lose_hp(ch, 100 + (int)storm/4000);
		return TRUE;
	    default:
		if (number_range(1, get_curr_wil(ch)) < 100) {
			act(AT_CHAOS, "You are unable to control the mana flows.",
			ch, NULL, NULL, TO_CHAR);
			return TRUE;
		} else {
			act(AT_CHAOS, "With great concentration you control the mana through the storm.",
			ch, NULL, NULL, TO_CHAR);
			return FALSE;
		}
	}

	return TRUE;
}