bast/
bast/area/
bast/backup/
bast/clans/
bast/doc/MSP/
bast/doc/OLC11/
bast/doc/OLC11/doc/
bast/doc/OLC11/options/
bast/log/
bast/mobprogs/
bast/player/
/***************************************************************************
 *  Original Diku Mud copyright (C) 1990, 1991 by Sebastian Hammer,        *
 *  Michael Seifert, Hans Henrik St{rfeldt, Tom Madsen, and Katja Nyboe.   *
 *                                                                         *
 *  Merc Diku Mud improvments copyright (C) 1992, 1993 by Michael          *
 *  Chastain, Michael Quan, and Mitchell Tse.                              *
 *                                                                         *
 *  Envy Diku Mud improvements copyright (C) 1994 by Michael Quan, David   *
 *  Love, Guilherme 'Willie' Arnold, and Mitchell Tse.                     *
 *                                                                         *
 *  EnvyMud 2.0 improvements copyright (C) 1995 by Michael Quan and        *
 *  Mitchell Tse.                                                          *
 *                                                                         *
 *  EnvyMud 2.2 improvements copyright (C) 1996, 1997 by Michael Quan.     *
 *                                                                         *
 *  In order to use any part of this Envy Diku Mud, you must comply with   *
 *  the original Diku license in 'license.doc', the Merc license in        *
 *  'license.txt', as well as the Envy license in 'license.nvy'.           *
 *  In particular, you may not remove either of these copyright notices.   *
 *                                                                         *
 *  Much time and thought has gone into this software and you are          *
 *  benefitting.  We hope that you share your changes too.  What goes      *
 *  around, comes around.                                                  *
 ***************************************************************************/

#if defined( macintosh )
#include <types.h>
#else
#include <sys/types.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "merc.h"

MEM_DATA *mem_first = NULL;
MEM_DATA *memdata_free = NULL;

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

    for ( sn = 0; sn < MAX_SKILL; sn++ )
    {
	if ( !skills_table[sn].name )
	    break;
	if ( LOWER( name[0] ) == LOWER( skills_table[sn].name[0] )
	    && !str_prefix( name, skills_table[sn].name ) )
	    return sn;
    }

    return -1;
}

/*
 * Lookup a spell by name.
 */
int spell_lookup( const char *name )
{
    int sn;

    for ( sn = 0; sn < MAX_SPELL; sn++ )
    {
	if ( !spells_table[sn].name )
	    break;
	if ( LOWER( name[0] ) == LOWER( spells_table[sn].name[0] )
	    && !str_prefix( name, spells_table[sn].name ) )
	    return sn;
    }

    return -1;
}



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

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

    static const struct syl_type   syl_table [ ] =
    {
	{ " ",		" "		},
	{ "ar",		"abra"		},
	{ "au",		"kada"		},
	{ "bless",	"fido"		},
	{ "blind",	"nose"		},
	{ "bur",	"mosa"		},
	{ "cu",		"judi"		},
	{ "de",		"oculo"		},
	{ "en",		"unso"		},
	{ "light",	"dies"		},
	{ "lo",		"hi"		},
	{ "mor",	"zak"		},
	{ "move",	"sido"		},
	{ "ness",	"lacri"		},
	{ "ning",	"illa"		},
	{ "per",	"duda"		},
	{ "ra",		"gru"		},
	{ "re",		"candus"	},
	{ "son",	"sabru"		},
	{ "tect",	"infra"		},
	{ "tri",	"cula"		},
	{ "ven",	"nofo"		},
	{ "a", "a" }, { "b", "b" }, { "c", "q" }, { "d", "e" },
	{ "e", "z" }, { "f", "y" }, { "g", "o" }, { "h", "p" },
	{ "i", "u" }, { "j", "y" }, { "k", "t" }, { "l", "r" },
	{ "m", "w" }, { "n", "i" }, { "o", "a" }, { "p", "s" },
	{ "q", "d" }, { "r", "f" }, { "s", "g" }, { "t", "h" },
	{ "u", "j" }, { "v", "z" }, { "w", "x" }, { "x", "n" },
	{ "y", "l" }, { "z", "k" },
	{ "", "" }
    };

    buf[0]	= '\0';
    for ( pName = spells_table[sn].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&n utters the words, '%s'.", buf );
    sprintf( buf,  "$n&n utters the words, '%s'.", spells_table[sn].name );

    for ( rch = ch->in_room->people; rch; rch = rch->next_in_room )
    {
	if ( rch != ch && (( rch->class == ch->class) || IS_IMMORTAL( ch )))
        {
	    act( buf, ch, NULL, rch, TO_VICT );
        }
        else if( rch != ch )
        {
	    act( buf2, ch, NULL, rch, TO_VICT );
        }
    }

    return;
}



/*
 * Compute a saving throw.
 * Negative apply's make saving throw better.
 */
bool saves_spell( int level, CHAR_DATA *victim, int dam_type )
{
    int save;
    int base = 50;

    if ( IS_NPC( victim ) )
        base += 25;
    save = base + ( victim->level - level - victim->saving_throw[4] ) * 5;

    switch( check_ris( victim, dam_type ) )
    {
    case IS_RESISTANT:		save += 2;		break;
    case IS_IMMUNE:		return TRUE;
    case IS_SUSCEPTIBLE:	save -= 2;		break;
    }

    save = URANGE( 5, save, 95 );
    return number_percent( ) < save;
}



int find_char_mana( CHAR_DATA *ch, int bit )
{
    OBJ_DATA *obj_next;
    OBJ_DATA *obj;
    int       mana;

    for ( obj = ch->carrying, mana = 0; obj; obj = obj_next )
    {
        obj_next = obj->next_content;

        if ( obj->deleted )
            continue;
            
	if ( obj->item_type == TYPE_GEM && IS_SET( obj->value[0], bit ) )
	    mana += obj->value[1];
    }

    return mana;
}


void take_mana_char( CHAR_DATA *ch, int mana, int bit )
{
    OBJ_DATA *obj_next;
    OBJ_DATA *obj;

    for ( obj = ch->carrying; obj; obj = obj_next )
    {
        obj_next = obj->next_content;

        if ( obj->deleted )
            continue;
            
	if ( obj->item_type == TYPE_GEM && IS_SET( obj->value[0], bit ) )
        {
	    if ( obj->value[1] >= mana )
	    {
		obj->value[1] -= mana;
		if ( obj->value[1] <= 0 )
		{
		    act( "$p is drained of all its power, and shatters into thousands of shards!",
			ch, obj, NULL, TO_CHAR );
		    obj_from_char( obj );
		    extract_obj( obj );
		}
                return;
            }
            else
            {
		mana -= obj->value[1];
		act( "$p is drained of all its power, and shatters into thousands of shards!",
		    ch, obj, NULL, TO_CHAR );
		obj_from_char( obj );
		extract_obj( obj );
	    }
	}
    }

    return;
}



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

void do_cast( CHAR_DATA *ch, char *argument )
{
    void      *vo;
    OBJ_DATA  *obj;
    CHAR_DATA *victim;
    MEM_DATA  *mem;
    bool       found;
    char       arg1 [ MAX_INPUT_LENGTH ];
    char       arg2 [ MAX_INPUT_LENGTH ];
    int        sn;
    int        beats; // For quick chant

    target_name = one_argument( argument, arg1 );
    one_argument( target_name, arg2 );

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

    if ( IS_NPC( ch ) )
        return;

    if( ch->class == CLASS_PSIONICIST )
    {
        send_to_char( "Psionicists use the WILL command to invoke their powers.\n\r", ch );
        return;
    }

    if ( ( sn = spell_lookup( arg1 ) ) < 0
	|| ch->level < (spells_table[sn].spell_circle[ch->class] * 5 - 4)  )
    {
	send_to_char( "You can't do that.\n\r", ch );
	return;
    }

    if ( ch->position == POS_FIGHTING && !(spells_table[sn].cast_combat) )
    {
	send_to_char( "You can't concentrate enough.\n\r", ch );
	return;
    }
    else if( ch->position != POS_STANDING && ch->position != POS_FIGHTING )
    {
        send_to_char( "You must be standing to cast spells!\n\r", ch );
        return;
    }

    if (   IS_AFFECTED( ch, AFF_MUTE )
	|| IS_SET( race_table[ch->race].race_abilities, RACE_MUTE ) )
    {
	send_to_char( "Your lips move but no sound comes out.\n\r", ch );
	return;
    }

    if ( IS_SET( ch->in_room->room_flags, ROOM_SILENT ) )
    {
        send_to_char( "You can't...you are in a Cone of Silence!\n\r", ch );
        return;
    }

    if( IS_SET( ch->in_room->room_flags, ROOM_NO_MAGIC ) )
    {
        send_to_char( "You start casting...", ch );
        send_to_char( "After a brief gathering of energy, your spell fizzles.\n\r", ch );
        WAIT_STATE( ch, 6 );
        return;
    }

    found = FALSE;

    if( !IS_IMMORTAL( ch ))
    {
    for( mem = ch->pcdata->memorized; mem; mem = mem->next )
    {
       if( !mem->memmed )
          continue;
       if( mem->sn == sn )
       {
          found = TRUE;
          break;
       }
    }

      if( !found )
      {
         send_to_char( "You do not have that spell memorized!\n\r", ch );
         return;
      }
    }

    /*
     * Locate targets.
     */
    victim	= NULL;
    vo		= NULL;
      
    switch ( spells_table[sn].target )
    {
    default:
	bug( "Do_cast: bad target for sn %d.", sn );
	return;

    case TAR_IGNORE:
	break;

    case TAR_CHAR_OFFENSIVE:
	if ( arg2[0] == '\0' )
	{
	    if ( !( victim = ch->fighting ) )
	    {
		send_to_char( "Cast the spell on whom?\n\r", ch );
		return;
	    }
	}
	else
	{
	    if ( !( victim = get_char_room( ch, arg2 ) ) )
	    {
		send_to_char( "They aren't here.\n\r", ch );
		return;
	    }
	}

	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;
	}

	if ( is_safe( ch, victim ) )
	    return;

	check_killer( ch, victim );

	vo = (void *) victim;
	break;

    case TAR_CHAR_DEFENSIVE:
	if ( arg2[0] == '\0' )
	{
	    victim = ch;
	}
	else
	{
	    if ( !( victim = get_char_room( ch, arg2 ) ) )
	    {
		send_to_char( "They aren't here.\n\r", ch );
		return;
	    }
	}

	vo = (void *) victim;
	break;

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

	vo = (void *) ch;
	break;

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

	if ( !( obj = get_obj_carry( ch, arg2 ) ) )
	{
	    send_to_char( "You are not carrying that.\n\r", ch );
	    return;
	}

	vo = (void *) obj;
	break;
    }


    send_to_char( "You begin casting...\n\r", ch );

    if ( str_cmp( spells_table[sn].name, "ventriloquate" ) )
    {
	act( "$n&n begins casting...", ch, NULL, NULL, TO_ROOM );
    }
      
    beats = spells_table[sn].beats;

    if( ch->level >= skills_table[gsn_quick_chant].skill_level[ch->class] )
    {
      if( number_percent() < ch->pcdata->skl_lrn[gsn_quick_chant] )
      {
        // Want this one to go up slowly, so it only checks when successful
        skill_practice( ch, gsn_quick_chant );
        beats = beats * 3 / 4;
      }
    }

    WAIT_STATE( ch, beats );

    // Create an event to handle the spell
    create_event( EVENT_SPELL_CAST, beats, ch, arg2, sn );
    if( !IS_NPC( ch ))
       SET_BIT( ch->act, PLR_CASTING );
    else
       bug( "Trying to setbit an NPC as casting", 0 );

    return;
}

// When the spell event terminates, we need something to happen...
//
// By this point we should have terminated the spell event data
// and should only need the info about the character and the sn
// and maybe the argument...
//
// Passing of the correct function parameters should be handled by the
// event system.
void finish_spell( CHAR_DATA *ch, int sn, char *argument )
{
    void      *vo;
    OBJ_DATA  *obj;
    CHAR_DATA *victim;
    MEM_DATA  *mem;
    bool       found;
    char       arg2 [ MAX_INPUT_LENGTH ];
    char       buf[MAX_STRING_LENGTH];

    // Keep in mind this needs changed because we already have the SN that they are casting.
    // all we need is the target data from the command line.
    one_argument( argument, arg2 );

    if( !IS_NPC( ch ))
       REMOVE_BIT( ch->act, PLR_CASTING );
    else
       bug( "Trying to removebit an NPC casting", 0 );

    // Bail if they're casting a spell that requires concentration
    if ( ch->position == POS_FIGHTING && !(spells_table[sn].cast_combat) )
    {
	send_to_char( "You can't concentrate enough.\n\r", ch );
	return;
    }

    // Bail if they got bashed
    else if( ch->position != POS_STANDING && ch->position != POS_FIGHTING )
    {
        send_to_char( "You must be standing to cast spells!\n\r", ch );
        return;
    }

    // We checked for all this stuff before, but the state of things may
    // have changed.
    if (   IS_AFFECTED( ch, AFF_MUTE )
	|| IS_SET( race_table[ch->race].race_abilities, RACE_MUTE ) )
    {
	send_to_char( "Your lips move but no sound comes out.\n\r", ch );
	return;
    }

    if ( IS_SET( ch->in_room->room_flags, ROOM_SILENT ) )
    {
        send_to_char( "You can't...you are in a Cone of Silence!\n\r", ch );
        return;
    }

    if( IS_SET( ch->in_room->room_flags, ROOM_NO_MAGIC ) )
    {
        send_to_char( "After a brief gathering of energy, your spell fizzles.\n\r", ch );
        return;
    }

    found = FALSE;

    if( !IS_IMMORTAL( ch ))
    {
    for( mem = ch->pcdata->memorized; mem; mem = mem->next )
    {
       if( !mem->memmed )
          continue;
       if( mem->sn == sn )
       {
          found = TRUE;
          break;
       }
    }

      if( !found )
      {
         send_to_char( "You do not have that spell memorized!\n\r", ch );
         return;
      }
    }

    /*
     * Locate targets.
     */
    victim	= NULL;
    vo		= NULL;
      
    switch ( spells_table[sn].target )
    {
    default:
	bug( "Do_cast: bad target for sn %d.", sn );
	return;

    case TAR_IGNORE:
	break;

    case TAR_CHAR_OFFENSIVE:
	if ( arg2[0] == '\0' )
	{
	    if ( !( victim = ch->fighting ) )
	    {
		send_to_char( "Cast the spell on whom?\n\r", ch );
		return;
	    }
	}
	else
	{
	    if ( !( victim = get_char_room( ch, arg2 ) ) )
	    {
		send_to_char( "They aren't here.\n\r", ch );
		return;
	    }
	}

	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;
	}

	if ( is_safe( ch, victim ) )
	    return;

	check_killer( ch, victim );

	vo = (void *) victim;
	break;

    case TAR_CHAR_DEFENSIVE:
	if ( arg2[0] == '\0' )
	{
	    victim = ch;
	}
	else
	{
	    if ( !( victim = get_char_room( ch, arg2 ) ) )
	    {
		send_to_char( "They aren't here.\n\r", ch );
		return;
	    }
	}

	vo = (void *) victim;
	break;

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

	vo = (void *) ch;
	break;

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

	if ( !( obj = get_obj_carry( ch, arg2 ) ) )
	{
	    send_to_char( "You are not carrying that.\n\r", ch );
	    return;
	}

	vo = (void *) obj;
	break;
    }

    // No wait state - we already made them wait.

    if ( str_cmp( spells_table[sn].name, "ventriloquate" ) )
	say_spell( ch, sn );
      
    spell_practice( ch, sn );

    if ( !IS_IMMORTAL( ch ) && number_percent( ) > ch->pcdata->spl_lrn[sn] )
    {
	send_to_char( "You lost your concentration.\n\r", ch );
    }
    else
    {
        send_to_char( "You complete your spell...\n\r", ch );
        sprintf( buf, "Spell %d (%s) being cast by %s", sn,
             spells_table[sn].name, ch->name );
        log_string( buf );
	(*spells_table[sn].spell_fun) ( sn,
				      URANGE( 1, ch->level, LEVEL_HERO ),
				      ch, vo );
        if( !IS_IMMORTAL( ch ))
        {
          mem->memmed = FALSE;
          mem->memtime = mem->full_memtime;
        }
    }

    if ( spells_table[sn].target == TAR_CHAR_OFFENSIVE
	&& victim->master != ch && victim != ch && IS_AWAKE( victim ) )
    {
	CHAR_DATA *vch;

	for ( vch = ch->in_room->people; vch; vch = vch->next_in_room )
	{
	    if ( vch->deleted )
	        continue;
	    if ( victim == vch && !victim->fighting )
	    {
		multi_hit( victim, ch, TYPE_UNDEFINED );
		break;
	    }
	}
    }

    return;
}

// By all rights psionics should be instantaneous and exempt from
// the casting stuff, and suffer lag afterward - Veygoth
void do_will( CHAR_DATA *ch, char *argument )
{
    void      *vo;
    OBJ_DATA  *obj;
    CHAR_DATA *victim;
    char       arg1 [ MAX_INPUT_LENGTH ];
    char       arg2 [ MAX_INPUT_LENGTH ];
    char       buf[MAX_STRING_LENGTH];
    int        total_mana;
    int        gem_mana;
    int        mana;
    int        sn;

    target_name = one_argument( argument, arg1 );
    one_argument( target_name, arg2 );

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

    if ( IS_NPC( ch ) )
        return;

    if( ch->class != CLASS_PSIONICIST )
    {
        send_to_char( "Your mind is much too puny for you to focus your will.\n\r", ch );
        return;
    }

    if ( ( sn = spell_lookup( arg1 ) ) < 0
	|| ch->level < (spells_table[sn].spell_circle[ch->class] * 5 - 4))
    {
	send_to_char( "You can't do that.\n\r", ch );
	return;
    }

    if ( ch->position == POS_FIGHTING && !(spells_table[sn].cast_combat) )
    {
	send_to_char( "You can't concentrate enough.\n\r", ch );
	return;
    }
    else if( ch->position != POS_STANDING && ch->position != POS_FIGHTING )
    {
        send_to_char( "You must be standing to cast spells!\n\r", ch );
        return;
    }

    if (   IS_AFFECTED( ch, AFF_MUTE )
	|| IS_SET( race_table[ch->race].race_abilities, RACE_MUTE ) )
    {
	send_to_char( "Your lips move but no sound comes out.\n\r", ch );
	return;
    }

    if ( IS_SET( ch->in_room->room_flags, ROOM_SILENT ) )
    {
        send_to_char( "You can't...you are in a Cone of Silence!\n\r", ch );
        return;
    }

    if( IS_SET( ch->in_room->room_flags, ROOM_NO_PSIONICS ) )
    {
        send_to_char( "Something here prevents you from focusing your will.\n\r", ch );
        WAIT_STATE( ch, 6 );
        return;
    }

    mana = MANA_COST( ch, sn );

    /*
     * Locate targets.
     */
    victim	= NULL;
    vo		= NULL;
      
    switch ( spells_table[sn].target )
    {
    default:
	bug( "Do_cast: bad target for sn %d.", sn );
	return;

    case TAR_IGNORE:
	break;

    case TAR_CHAR_OFFENSIVE:
	if ( arg2[0] == '\0' )
	{
	    if ( !( victim = ch->fighting ) )
	    {
		send_to_char( "Cast the spell on whom?\n\r", ch );
		return;
	    }
	}
	else
	{
	    if ( !( victim = get_char_room( ch, arg2 ) ) )
	    {
		send_to_char( "They aren't here.\n\r", ch );
		return;
	    }
	}

	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;
	}

	if ( is_safe( ch, victim ) )
	    return;

	check_killer( ch, victim );

	vo = (void *) victim;
	break;

    case TAR_CHAR_DEFENSIVE:
	if ( arg2[0] == '\0' )
	{
	    victim = ch;
	}
	else
	{
	    if ( !( victim = get_char_room( ch, arg2 ) ) )
	    {
		send_to_char( "They aren't here.\n\r", ch );
		return;
	    }
	}

	vo = (void *) victim;
	break;

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

	vo = (void *) ch;
	break;

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

	if ( !( obj = get_obj_carry( ch, arg2 ) ) )
	{
	    send_to_char( "You are not carrying that.\n\r", ch );
	    return;
	}

	vo = (void *) obj;
	break;
    }

    if ( ch->mana < mana
      && ( gem_mana = find_char_mana( ch, spells_table[sn].mana_type ) ) > 0 )
    {
	total_mana = gem_mana + ch->mana;
    }
    else
    {
	gem_mana  = 0;
	total_mana = ch->mana;
    }

    if ( total_mana < mana )
    {
	char      *msg;

        switch ( spells_table[sn].mana_type )
	{
	default:         msg = "You don't have enough mana.\n\r";
									  break;
	case MANA_FIRE:  msg = "You don't have enough fire mana.\n\r";
									  break;
	case MANA_AIR:   msg = "You don't have enough air mana.\n\r";
									  break;
	case MANA_WATER: msg = "You don't have enough water mana.\n\r";
									  break;
	case MANA_EARTH: msg = "You don't have enough earth mana.\n\r";
									  break;
	}
	send_to_char( msg, ch );
        return;
    }

    if ( str_cmp( spells_table[sn].name, "ventriloquate" ) )
	say_spell( ch, sn );
      
    WAIT_STATE( ch, spells_table[sn].beats );

    spell_practice( ch, sn );

    if ( !IS_IMMORTAL( ch ) && number_percent( ) > ch->pcdata->spl_lrn[sn] )
    {
	send_to_char( "You lost your concentration.\n\r", ch );
	  if ( gem_mana > 0 )
	  {
	    take_mana_char( ch, mana/2, spells_table[sn].mana_type );

	    if ( gem_mana < mana / 2 )
		ch->mana -= mana/2 - gem_mana;
	  }
	  else
	    ch->mana -= mana/2;
    }
    else
    {
	if ( gem_mana > 0 )
	{
	    take_mana_char( ch, mana, spells_table[sn].mana_type );

	    if ( gem_mana < mana )
		ch->mana -= mana - gem_mana;
	}
	else
	    ch->mana -= mana;
        sprintf( buf, "Spell %d (%s) being willed by %s", sn,
             spells_table[sn].name, ch->name );
        log_string( buf );
	(*spells_table[sn].spell_fun) ( sn,
				      URANGE( 1, ch->level, LEVEL_HERO ),
				      ch, vo );
    }

    if ( spells_table[sn].target == TAR_CHAR_OFFENSIVE
	&& victim->master != ch && victim != ch && IS_AWAKE( victim ) )
    {
	CHAR_DATA *vch;

	for ( vch = ch->in_room->people; vch; vch = vch->next_in_room )
	{
	    if ( vch->deleted )
	        continue;
	    if ( victim == vch && !victim->fighting )
	    {
		multi_hit( victim, ch, TYPE_UNDEFINED );
		break;
	    }
	}
    }

    return;
}

/*
 * Cast spells at targets using a magical object.
 */
void obj_cast_spell( int sn, int level, CHAR_DATA *ch, CHAR_DATA *victim,
		    OBJ_DATA *obj )
{
    void *vo;

    if ( sn <= 0 )
	return;

    if ( sn >= MAX_SPELL || spells_table[sn].spell_fun == 0 )
    {
	bug( "Obj_cast_spell: bad sn %d.", sn );
	return;
    }

    switch ( spells_table[sn].target )
    {
    default:
	bug( "Obj_cast_spell: bad target for sn %d.", sn );
	return;

    case TAR_IGNORE:
	vo = NULL;
	break;

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

	if ( is_safe( ch, victim ) )
	    return;

	check_killer( ch, victim );

	vo = (void *) victim;
	break;

    case TAR_CHAR_DEFENSIVE:
	if ( !victim )
	    victim = ch;
	vo = (void *) victim;
	break;

    case TAR_CHAR_SELF:
	vo = (void *) ch;
	break;

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

    target_name = "";
    (*spells_table[sn].spell_fun) ( sn, level, ch, vo );

    if ( spells_table[sn].target == TAR_CHAR_OFFENSIVE
	&& victim->master != ch && ch != victim )
    {
	CHAR_DATA *vch;

	for ( vch = ch->in_room->people; vch; vch = vch->next_in_room )
	{
	    if ( vch->deleted )
	        continue;
	    if ( victim == vch && !victim->fighting )
	    {
		multi_hit( victim, ch, TYPE_UNDEFINED );
		break;
	    }
	}
    }

    return;
}

void do_memorize( CHAR_DATA *ch, char *argument )
{
        MEM_DATA *mem = NULL;
        int sn;
        char buf[MAX_STRING_LENGTH];
        char buf2[MAX_STRING_LENGTH];
        int memmed[MAX_SPELL];
        int count;
        int total_mem;
        bool found;
        bool lesser; // Do they use the lesser mem chart? (For future classes)
        bool left;
        int circle[MAX_CIRCLE];
        int circfree[MAX_CIRCLE];
        int lvltotal;

        // Mobiles cannot mem spells
        if( IS_NPC( ch ))
           return;

        // Check to see if they are allowed to mem anything
        switch( ch->class )
        {
           default:
             send_to_char( "You are not a spellcaster!\n\r", ch );
             return;
             break;
           case CLASS_CLERIC:
           case CLASS_SORCERER:
             lesser = FALSE;
             break;
        }

	  // with an argument they want to start memorizing a new spell.
        if( argument[0] != '\0' )
        {
           // Must be in the proper position
           if( ch->position != POS_RESTING )

           {
               send_to_char( "You can only memorize spells while resting.\n\r", ch );
               return;
           }

           // Find the spell they want
           sn = spell_lookup( argument );
           if( sn <= 0 )
           {
             send_to_char( "Never heard of that spell...\n\r", ch );
             return;
           }

           // Check to see that they can memorize another spell
           // Immortals have no limits
           if( !IS_IMMORTAL( ch ))
           {
             if( ch->level < (spells_table[sn].spell_circle[ch->class] * 5 - 4))
             {
               send_to_char( "That spell is beyond you.\n\r", ch );
               return;
             }
             if( ch->pcdata->spl_lrn[sn] < 1 )
             {
               send_to_char( "You have not yet learned that spell.  Find a place to scribe it.\n\r", ch );
               return;
             }
             lvltotal = 0;
             for( mem = ch->pcdata->memorized; mem; mem = mem->next )
             {
               if( mem->circle == spells_table[sn].spell_circle[ch->class])
                  lvltotal += 1;
             }
             if( lvltotal >= memchart[(ch->level - 1)][(spells_table[sn].spell_circle[ch->class] - 1)])
             {
               send_to_char( "You can memorize no more spells of that level.\n\r", ch );
               return;
             }
           } // if( !IS_IMMORTAL( ch ))

           // If we know what they want and they can have it, let's create it.
           if(!(mem = (create_memdata( ch, sn ))))
           {
             bug( "Unable to create memorization (sn %d)", sn );
             return;
           }

           // If they're not already memorizing, they are now
           SET_BIT( ch->act, PLR_MEMORIZING );
           sprintf( buf, "You start memorizing %s which will take about %d seconds.\n\r",
             spells_table[mem->sn].name, (mem->memtime / PULSE_PER_SECOND) );
           send_to_char( buf, ch );

           return;           
        }
        else
        {
           found = FALSE;
           // If they didn't give us an argument, that means that they must
           // want to either see their spell list or continue memorizing
           // Either way we show their spell list.
           // make sure they have some mem data first...
           if( ch->pcdata->memorized )
           {
           for( count = 0; count < MAX_SPELL; count ++ )
             memmed[count] = 0;
           // Figure out what spells they have memorized
           for( mem = ch->pcdata->memorized; mem; mem = mem->next )
           {
             if( mem->memmed )
                 memmed[mem->sn] += 1;
           }

           // Show memorized spells
           send_to_char( "You have memorized the following spells:\n\r", ch );
           for( count = 0; count < MAX_SPELL; count++ )
           {
              if( memmed[count] > 0 && spells_table[count].name != NULL)
              {
                sprintf( buf, "(%2d%s circle)  %d - %s\n\r",
                   spells_table[count].spell_circle[ch->class],
                   text_number( spells_table[count].spell_circle[ch->class] ),
                   memmed[count],
                   spells_table[count].name );
                send_to_char( buf, ch );
              }
           }

           // Figure out what spells they are working on
           send_to_char( "You are memorizing the following spells:\n\r", ch );
           total_mem = 0;
            for( mem = ch->pcdata->memorized_last; mem; mem = mem->prev )
            {
              if( !spells_table[mem->sn].name )
                  break;
              if( mem->memmed )
                  continue;
              else
                  found = TRUE;
              sprintf( buf, "    %d seconds:  (%d%s) %s\n\r",  
                  ((total_mem + mem->memtime) / PULSE_PER_SECOND),
                  spells_table[mem->sn].spell_circle[ch->class],
                  text_number(spells_table[mem->sn].spell_circle[ch->class]),
                  spells_table[mem->sn].name );
              send_to_char( buf, ch );
              total_mem += mem->memtime;
            }
           } // if( ch->pcdata->memorized )

//
           // Tell them what they still have slots open for...
           for( count = 0; count < MAX_CIRCLE; count++ )
                circle[count] = 0;
             for( mem = ch->pcdata->memorized; mem; mem = mem->next )
             {
                  circle[(mem->circle - 1)] += 1;
             }
             left = FALSE;
             for( count = 0; count < MAX_CIRCLE; count++ )
             {
                  circfree[count] = memchart[(ch->level - 1)][count]
                                  - circle[count];
                  if( circfree[count] > 0 )
                      left = TRUE;
             }
             if( !left )
             {
                 send_to_char( "\n\rYou can memorize no more spells.\n\r", ch );
             }
             else
             {
                 sprintf( buf, "\n\rYou can memorize" );
                 for( count = 0; count < MAX_CIRCLE; count++ )
                 {
                   if( circfree[count] > 0 )
                   {
                     sprintf( buf2, " %d-%d%s", circfree[count], (count + 1), text_number( count ) );
                     strcat( buf, buf2 );
                   }
                 }
                 strcat( buf, " level spells.\n\r" );
                 send_to_char( buf, ch );
             }
//
           // If they aren't memming and they should be, start 'em up.
           if( found && !IS_SET( ch->act, PLR_MEMORIZING ) 
               && ch->position == POS_RESTING )
           {
             SET_BIT( ch->act, PLR_MEMORIZING );
             send_to_char( "You continue your studies.\n\r", ch );
           }


        }
        return;
}

void do_forget( CHAR_DATA *ch, char *argument )
{
        MEM_DATA *mem;
        MEM_DATA *prev;
        int sn;
        bool found;
        char buf[MAX_STRING_LENGTH];

        if( IS_NPC( ch ))
            return;

        if( argument[0] == '\0' )
        {
            send_to_char( "Looks like your forgot what you were going to forget.\n\r", ch );
            return;
        }

        if( !ch->pcdata->memorized )
        {
            send_to_char( "You don't have anything to forget.\n\r", ch );
            return;
        }

        if( !str_cmp( argument, "all" ))
        {
            forget_all( ch );
            send_to_char( "You forget everything.\n\r", ch );
            return;
        }

        sn = spell_lookup( argument );

        if( sn <= 0 )
        {
            send_to_char( "Forget what?\n\r", ch );
            return;
        }

        found = FALSE;
        prev = NULL;
        for( mem = ch->pcdata->memorized; mem; mem = mem->next )
        {
            if( mem->sn == sn )
            {
               found = TRUE;
               break;
            }
            prev = mem;
        }

        if( !found )
        {
            send_to_char( "You do not have that spell memorized.\n\r", ch );
            return;
        }

        // This is where we remove the spell from their double-linked
        // list and tack it onto the free list.  Too bad I don't know how
        // yet -- Veygoth
        sprintf( buf, "You forget %s.\n\r", spells_table[sn].name );
        send_to_char( buf, ch );

        // Put the data on the free stack.
        // Reset memorized_last since that doesen't affect much of anything
        if( mem == ch->pcdata->memorized_last )
        {
            if( mem->prev )
                ch->pcdata->memorized_last = mem->prev;
            else
                ch->pcdata->memorized_last = NULL;
        }

        // repoint the previous data to the next data if any
        if( prev )
        {
            prev->next = mem->next;
        }
        else if( ch->pcdata->memorized == mem )
        {
            ch->pcdata->memorized = mem->next;
        }
        else
        {
            bug( "Previous data not found and mem data not at top of list", 0 );
        }

        // repoint the next memorization data to the right place
        if( mem->next )
        {
          if( prev )
             mem->next->prev = prev;
          else
             mem->next->prev = NULL;
        }

        // put it on the free stack
        mem->next = memdata_free;
        memdata_free = mem;

        // reset the previous pointer
        mem->prev = NULL;
        return;
}

// Should hard code level checks to see if it is okay to mem a certain
// spell - Veygoth
MEM_DATA *create_memdata( CHAR_DATA *ch, int sn )
{
	MEM_DATA *mem;

        if( IS_NPC( ch ))
          return FALSE;

        if( !memdata_free )
        {
	   mem = alloc_perm( sizeof( *mem ));
           top_memdata++;
        }
        else
        {
           mem = memdata_free;
           memdata_free = memdata_free->next;
        }

        memset( mem, 0, sizeof( MEM_DATA ));

        mem->sn = sn;
        mem->memtime =		calc_memtime( ch, sn );
        mem->full_memtime =     calc_memtime( ch, sn );
        mem->circle = spells_table[sn].spell_circle[ch->class];
        mem->memmed = FALSE;

        // Point the new data to all the rest of the list.
        // The prev pointer is reset when the data is actually
        // used so we don't have to worry about it in the forget
        // command.
        if( ch->pcdata->memorized )
            ch->pcdata->memorized->prev = mem;
        mem->next = ch->pcdata->memorized;
        mem->prev = NULL;
        if( !ch->pcdata->memorized_last )
             ch->pcdata->memorized_last = mem;

        // Put the new mem data at the top of the list.
        ch->pcdata->memorized = mem;

	return mem;
}

int calc_memtime( CHAR_DATA *ch, int sn )
{
        int memtime;
        int attribute;

        if( ch->class == CLASS_CLERIC )
            attribute = get_curr_wis( ch );
        else
            attribute = get_curr_int( ch );

        memtime = 220 - attribute - (ch->level * 3)
                + (spells_table[sn].spell_circle[ch->class] * 8);
        if( memtime < 4 )
            memtime = 4;
        return memtime;
}

void memorize_update( void )
{
	 MEM_DATA *mem;
        MEM_DATA *chkspl;
        CHAR_DATA *ch;
        bool done;

        for( ch = char_list; ch; ch = ch->next )
        {
           done = FALSE;

           if( IS_NPC( ch ))
             continue;

           if( !(IS_SET( ch->act, PLR_MEMORIZING )))
             continue;

           if( !ch->pcdata->memorized )
           {
             bug( "Memorizing character with no mem_data", 0 );
             REMOVE_BIT( ch->act, PLR_MEMORIZING );
           }

           // Find the oldest unmemmed piece of spell data
           for( mem = ch->pcdata->memorized_last; mem; mem = mem->prev )
           {
              if( !mem->memmed )
              {
                  chkspl = mem;
                  break;
              }
           }


         if( !chkspl )
         {
             REMOVE_BIT( ch->act, PLR_MEMORIZING );
             send_to_char( "Your studies are complete...", ch );
             continue;
         }
         else
         {
           if( chkspl->memtime < 1 )
           {
               char buf[MAX_STRING_LENGTH];
               chkspl->memmed = TRUE;
               sprintf( buf, "You have finished memorizing %s.\n\r",
                        spells_table[chkspl->sn].name );
               send_to_char( buf, ch );
           }
           else
           {
               if( IS_SET( ch->act, PLR_MEDITATING ))
                  if( ( chkspl->memtime
                      >= (chkspl->full_memtime / 2)) 
                      && (( chkspl->memtime - PULSE_MEMORIZE)
                       < ( chkspl->full_memtime / 2 )))
               {
                  if( number_percent() < ch->pcdata->skl_lrn[gsn_meditate] )
                  {
                     char buf[MAX_STRING_LENGTH];
                     chkspl->memmed = TRUE;
                     sprintf( buf, "You have finished memorizing %s.\n\r",
                              spells_table[chkspl->sn].name );
                     send_to_char( buf, ch );
                  }
               }
               chkspl->memtime -= PULSE_MEMORIZE;
           } // if( chkspl->memtime < 1 )
         } // if( chkspl )

         done = TRUE;
         for( mem = ch->pcdata->memorized; mem; mem = mem->next )
         {
              if( !mem->memmed )
              {
                  done = FALSE;
                  break;
              }
         }

         if( (done) && (IS_SET( ch->act, PLR_MEMORIZING )))
         {
            REMOVE_BIT( ch->act, PLR_MEMORIZING );
            send_to_char( "Your studies are complete.\n\r", ch );
            act( "$n&n is finished memorizing.", ch, NULL, NULL, TO_ROOM
);
         }
        }

        return;
}

void do_meditate( CHAR_DATA *ch, char *argument )
{
        if( IS_NPC( ch ))
           return;

        if( IS_SET( ch->act, PLR_MEDITATING ))
        {
           send_to_char( "You are already metitating!\n\r", ch );
           return;
        }

        if( ch->position != POS_RESTING )
        {
           send_to_char( "You must be resting on order to meditate.\n\r", ch );
           return;
        }

        if( ch->fighting )
        {
           send_to_char( "Meditation during battle leads to permenant inner peace.\n\r", ch );
           return;
        }

        SET_BIT( ch->act, PLR_MEDITATING );
        WAIT_STATE( ch, skills_table[gsn_meditate].beats );
        send_to_char( "You start meditating...\n\r", ch );
        return;

}

// Used for both death and for 'forget all'
void forget_all( CHAR_DATA *ch )
{
       MEM_DATA *mem;

       if( IS_NPC( ch ))
         return;

       if( !ch->pcdata->memorized )
         return;

       // No clue whether this works, but we yank the whole
       // data chain from the character and place it on the
       // free stack - Veygoth
       for( mem = ch->pcdata->memorized; mem; mem = mem->next )
       {
          mem->prev = NULL;
          if( mem->next == NULL )
              break;
       }
       mem->next = memdata_free;
       memdata_free = ch->pcdata->memorized;
       ch->pcdata->memorized = NULL;
       ch->pcdata->memorized_last = NULL;

       return;
}

void do_scribe( CHAR_DATA *ch, char *argument )
{
       CHAR_DATA *teacher;
       OBJ_DATA *quill;
       char buf[MAX_STRING_LENGTH];
       int sn;

       if( IS_NPC( ch ))
           return;

       for( teacher = ch->in_room->people; teacher; teacher = teacher->next_in_room )
       {
           if( !IS_NPC( teacher ))
               continue;
           if( IS_SET( teacher->act, ACT_TEACHER ))
               break;
       }

       if( !teacher )
       {
           send_to_char( "Nobody here can teach you anything.\n\r", ch );
           return;
       }

       for( quill = ch->carrying; quill; quill = quill->next_content )
       {
           if( quill->item_type == TYPE_PEN )
               break;
       }

       if( !quill )
       {
         send_to_char( "You have nothing to write with!\n\r", ch );
         return;
       }

       if( argument[0] == '\0' )
       {
          send_to_char( "Scribe what?\n\r", ch );
          return;
       }

       sn = spell_lookup( argument );

       if( sn <= 0 )
       {
           send_to_char( "No such spell.\n\r", ch );
           return;
       }

       if( (spells_table[sn].spell_circle[teacher->class] * 5) >
            (teacher->level + 4) ) 
       {
           send_to_char( "The teacher does not know that spell.\n\r", ch );
           return;
       }

       if( (spells_table[sn].spell_circle[ch->class] * 5) > (ch->level + 4) ) 
       {
           send_to_char( "That spell is beyond you.\n\r", ch );
           return;
       }

       if( ch->pcdata->spl_lrn[sn] < 1 )
       {
           // Scribe is used so rarely give them 5 chances to learn it...
           skill_practice( ch, gsn_scribe );
           skill_practice( ch, gsn_scribe );
           skill_practice( ch, gsn_scribe );
           skill_practice( ch, gsn_scribe );
           skill_practice( ch, gsn_scribe );
           sprintf( buf, "You scribe %s.\n\r", spells_table[sn].name );
           send_to_char( buf, ch );
           ch->pcdata->spl_lrn[sn] = BASE_SPELL_ADEPT;
           if( number_percent() < ch->pcdata->skl_lrn[gsn_scribe] )
           {
              ch->pcdata->spl_lrn[sn] += 10;
           }
           if( number_percent() < ch->pcdata->skl_lrn[gsn_scribe] )
           {
              ch->pcdata->spl_lrn[sn] += 5;
           }
           WAIT_STATE( ch, skills_table[gsn_scribe].beats );
       }
       else
       {
           send_to_char( "You already know that spell.\n\r", ch );
       }

       return;
}