mud/lua/
mud/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.                              *
 *                                                                         *
 *  In order to use any part of this Merc Diku Mud, you must comply with   *
 *  both the original Diku license in 'license.doc' as well the Merc       *
 *  license in 'license.txt'.  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"



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



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

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

    return -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 < MAX_SKILL; sn++ ) {
        if ( slot == skill_table[sn].slot )
            return sn;
    }

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

    return -1;
}



/*
 * 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;

    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 = skill_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 utters the words, '%s'.", buf );
    sprintf( buf,  "$n utters the words, '%s'.", skill_table[sn].name );

    for ( rch = ch->in_room->people; rch; rch = rch->next_in_room ) {
        if ( rch != ch )
            act( ch->class==rch->class ? buf : 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 save;

    save = 50 + ( victim->level - level - victim->saving_throw ) * 5;
    save = URANGE( 5, save, 95 );
    return number_percent( ) < save;
}



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

void do_cast( CHAR_DATA *ch, char *argument )
{
    char arg1[MAX_INPUT_LENGTH];
    char arg2[MAX_INPUT_LENGTH];
    CHAR_DATA *victim;
    OBJ_DATA *obj;
    void *vo;
    int mana;
    int sn;

    /*
     * Switched NPC's can cast spells, but others can't.
     */
    if ( IS_NPC(ch) && ch->desc == NULL )
        return;

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

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

    if ( ( sn = skill_lookup( arg1 ) ) < 0
            || ( !IS_NPC(ch) && ch->level < skill_table[sn].skill_level[ch->class] ) ) {
        send_to_char( "You can't do that.\r\n", ch );
        return;
    }

    if ( ch->position < skill_table[sn].minimum_position ) {
        send_to_char( "You can't concentrate enough.\r\n", ch );
        return;
    }

    mana = IS_NPC(ch) ? 0 : UMAX(
               skill_table[sn].min_mana,
               100 / ( 2 + ch->level - skill_table[sn].skill_level[ch->class] ) );

    /*
     * Locate targets.
     */
    victim	= NULL;
    obj		= NULL;
    vo		= NULL;

    switch ( skill_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 ) == NULL ) {
                send_to_char( "Cast the spell on whom?\r\n", ch );
                return;
            }
        } else {
            if ( ( victim = get_char_room( ch, arg2 ) ) == NULL ) {
                send_to_char( "They aren't here.\r\n", ch );
                return;
            }
        }

        if ( ch == victim ) {
            send_to_char( "You can't do that to yourself.\r\n", ch );
            return;
        }

        if ( !IS_NPC(ch) ) {
            if ( !IS_NPC(victim) ) {
                send_to_char( "You can't do that on a player.\r\n", ch );
                return;
            }

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

        vo = (void *) victim;
        break;

    case TAR_CHAR_DEFENSIVE:
        if ( arg2[0] == '\0' ) {
            victim = ch;
        } else {
            if ( ( victim = get_char_room( ch, arg2 ) ) == NULL ) {
                send_to_char( "They aren't here.\r\n", 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.\r\n", ch );
            return;
        }

        vo = (void *) ch;
        break;

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

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

        vo = (void *) obj;
        break;
    }

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

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

    WAIT_STATE( ch, skill_table[sn].beats );

    if ( !IS_NPC(ch) && number_percent( ) > ch->pcdata->learned[sn] ) {
        send_to_char( "You lost your concentration.\r\n", ch );
        ch->mana -= mana / 2;
    } else {
        ch->mana -= mana;
        (*skill_table[sn].spell_fun) ( sn, ch->level, ch, vo );
    }

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

        for ( vch = ch->in_room->people; vch; vch = vch_next ) {
            vch_next = vch->next_in_room;
            if ( victim == vch && victim->fighting == NULL ) {
                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_SKILL || skill_table[sn].spell_fun == 0 ) {
        bug( "Obj_cast_spell: bad sn %d.", sn );
        return;
    }

    switch ( skill_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 == NULL )
            victim = ch->fighting;
        if ( victim == NULL || !IS_NPC(victim) ) {
            send_to_char( "You can't do that.\r\n", ch );
            return;
        }
        vo = (void *) victim;
        break;

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

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

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

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

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

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

    return;
}



/*
 * Spell functions.
 */
void spell_acid_blast( int sn, int level, CHAR_DATA *ch, void *vo )
{
    CHAR_DATA *victim = (CHAR_DATA *) vo;
    int dam;

    dam = dice( level, 6 );
    if ( saves_spell( level, victim ) )
        dam /= 2;
    damage( ch, victim, dam, sn );
    return;
}



void spell_armor( int sn, int level, CHAR_DATA *ch, void *vo )
{
    CHAR_DATA *victim = (CHAR_DATA *) vo;
    AFFECT_DATA af;

    if ( is_affected( victim, sn ) )
        return;
    af.type      = sn;
    af.duration  = 24;
    af.modifier  = -20;
    af.location  = APPLY_AC;
    af.bitvector = 0;
    affect_to_char( victim, &af );
    send_to_char( "You feel someone protecting you.\r\n", victim );
    if ( ch != victim )
        send_to_char( "Ok.\r\n", ch );
    return;
}



void spell_bless( int sn, int level, CHAR_DATA *ch, void *vo )
{
    CHAR_DATA *victim = (CHAR_DATA *) vo;
    AFFECT_DATA af;

    if ( victim->position == POS_FIGHTING || is_affected( victim, sn ) )
        return;
    af.type      = sn;
    af.duration  = 6+level;
    af.location  = APPLY_HITROLL;
    af.modifier  = level / 8;
    af.bitvector = 0;
    affect_to_char( victim, &af );

    af.location  = APPLY_SAVING_SPELL;
    af.modifier  = 0 - level / 8;
    affect_to_char( victim, &af );
    send_to_char( "You feel righteous.\r\n", victim );
    if ( ch != victim )
        send_to_char( "Ok.\r\n", ch );
    return;
}



void spell_blindness( int sn, int level, CHAR_DATA *ch, void *vo )
{
    CHAR_DATA *victim = (CHAR_DATA *) vo;
    AFFECT_DATA af;

    if ( IS_AFFECTED(victim, AFF_BLIND) || saves_spell( level, victim ) )
        return;

    af.type      = sn;
    af.location  = APPLY_HITROLL;
    af.modifier  = -4;
    af.duration  = 1+level;
    af.bitvector = AFF_BLIND;
    affect_to_char( victim, &af );
    send_to_char( "You are blinded!\r\n", victim );
    if ( ch != victim )
        send_to_char( "Ok.\r\n", ch );
    return;
}



void spell_burning_hands( int sn, int level, CHAR_DATA *ch, void *vo )
{
    CHAR_DATA *victim = (CHAR_DATA *) vo;
    static const short dam_each[] = {
        0,
        0,  0,  0,  0,	14,	17, 20, 23, 26, 29,
        29, 29, 30, 30,	31,	31, 32, 32, 33, 33,
        34, 34, 35, 35,	36,	36, 37, 37, 38, 38,
        39, 39, 40, 40,	41,	41, 42, 42, 43, 43,
        44, 44, 45, 45,	46,	46, 47, 47, 48, 48
    };
    int dam;

    level	= UMIN(level, sizeof(dam_each)/sizeof(dam_each[0]) - 1);
    level	= UMAX(0, level);
    dam		= number_range( dam_each[level] / 2, dam_each[level] * 2 );
    if ( saves_spell( level, victim ) )
        dam /= 2;
    damage( ch, victim, dam, sn );
    return;
}



void spell_call_lightning( int sn, int level, CHAR_DATA *ch, void *vo )
{
    CHAR_DATA *vch;
    CHAR_DATA *vch_next;
    int dam;

    if ( !IS_OUTSIDE(ch) ) {
        send_to_char( "You must be out of doors.\r\n", ch );
        return;
    }

    if ( weather_info.sky < SKY_RAINING ) {
        send_to_char( "You need bad weather.\r\n", ch );
        return;
    }

    dam = dice(level/2, 8);

    send_to_char( "God's lightning strikes your foes!\r\n", ch );
    act( "$n calls God's lightning to strike $s foes!",
         ch, NULL, NULL, TO_ROOM );

    for ( vch = char_list; vch != NULL; vch = vch_next ) {
        vch_next	= vch->next;
        if ( vch->in_room == NULL )
            continue;
        if ( vch->in_room == ch->in_room ) {
            if ( vch != ch && ( IS_NPC(ch) ? !IS_NPC(vch) : IS_NPC(vch) ) )
                damage( ch, vch, saves_spell( level, vch ) ? dam/2 : dam, sn );
            continue;
        }

        if ( vch->in_room->area == ch->in_room->area
                &&   IS_OUTSIDE(vch)
                &&   IS_AWAKE(vch) )
            send_to_char( "Lightning flashes in the sky.\r\n", vch );
    }

    return;
}




void spell_cause_light( int sn, int level, CHAR_DATA *ch, void *vo )
{
    damage( ch, (CHAR_DATA *) vo, dice(1, 8) + level / 3, sn );
    return;
}



void spell_cause_critical( int sn, int level, CHAR_DATA *ch, void *vo )
{
    damage( ch, (CHAR_DATA *) vo, dice(3, 8) + level - 6, sn );
    return;
}



void spell_cause_serious( int sn, int level, CHAR_DATA *ch, void *vo )
{
    damage( ch, (CHAR_DATA *) vo, dice(2, 8) + level / 2, sn );
    return;
}



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

    if ( is_affected( victim, sn ) )
        return;
    af.type      = sn;
    af.duration  = 10 * level;
    af.location  = APPLY_SEX;
    do {
        af.modifier  = number_range( 0, 2 ) - victim->sex;
    } while ( af.modifier == 0 );
    af.bitvector = 0;
    affect_to_char( victim, &af );
    send_to_char( "You feel different.\r\n", victim );
    if ( ch != victim )
        send_to_char( "Ok.\r\n", ch );
    return;
}



void spell_charm_person( int sn, int level, CHAR_DATA *ch, void *vo )
{
    CHAR_DATA *victim = (CHAR_DATA *) vo;
    AFFECT_DATA af;

    if ( victim == ch ) {
        send_to_char( "You like yourself even better!\r\n", ch );
        return;
    }

    if ( IS_AFFECTED(victim, AFF_CHARM)
            ||   IS_AFFECTED(ch, AFF_CHARM)
            ||   level < victim->level
            ||   saves_spell( level, victim ) )
        return;

    if ( victim->master )
        stop_follower( victim );
    add_follower( victim, ch );
    af.type      = sn;
    af.duration  = number_fuzzy( level / 4 );
    af.location  = 0;
    af.modifier  = 0;
    af.bitvector = AFF_CHARM;
    affect_to_char( victim, &af );
    act( "Isn't $n just so nice?", ch, NULL, victim, TO_VICT );
    if ( ch != victim )
        send_to_char( "Ok.\r\n", ch );
    return;
}



void spell_chill_touch( int sn, int level, CHAR_DATA *ch, void *vo )
{
    CHAR_DATA *victim = (CHAR_DATA *) vo;
    static const short dam_each[] = {
        0,
        0,  0,  6,  7,  8,	 9, 12, 13, 13, 13,
        14, 14, 14, 15, 15,	15, 16, 16, 16, 17,
        17, 17, 18, 18, 18,	19, 19, 19, 20, 20,
        20, 21, 21, 21, 22,	22, 22, 23, 23, 23,
        24, 24, 24, 25, 25,	25, 26, 26, 26, 27
    };
    AFFECT_DATA af;
    int dam;

    level	= UMIN(level, sizeof(dam_each)/sizeof(dam_each[0]) - 1);
    level	= UMAX(0, level);
    dam		= number_range( dam_each[level] / 2, dam_each[level] * 2 );
    if ( !saves_spell( level, victim ) ) {
        af.type      = sn;
        af.duration  = 6;
        af.location  = APPLY_STR;
        af.modifier  = -1;
        af.bitvector = 0;
        affect_join( victim, &af );
    } else {
        dam /= 2;
    }

    damage( ch, victim, dam, sn );
    return;
}



void spell_colour_spray( int sn, int level, CHAR_DATA *ch, void *vo )
{
    CHAR_DATA *victim = (CHAR_DATA *) vo;
    static const short dam_each[] = {
        0,
        0,  0,  0,  0,  0,	 0,  0,  0,  0,  0,
        30, 35, 40, 45, 50,	55, 55, 55, 56, 57,
        58, 58, 59, 60, 61,	61, 62, 63, 64, 64,
        65, 66, 67, 67, 68,	69, 70, 70, 71, 72,
        73, 73, 74, 75, 76,	76, 77, 78, 79, 79
    };
    int dam;

    level	= UMIN(level, sizeof(dam_each)/sizeof(dam_each[0]) - 1);
    level	= UMAX(0, level);
    dam		= number_range( dam_each[level] / 2,  dam_each[level] * 2 );
    if ( saves_spell( level, victim ) )
        dam /= 2;

    damage( ch, victim, dam, sn );
    return;
}



void spell_continual_light( int sn, int level, CHAR_DATA *ch, void *vo )
{
    OBJ_DATA *light;

    light = create_object( get_obj_index( OBJ_VNUM_LIGHT_BALL ), 0 );
    obj_to_room( light, ch->in_room );
    act( "$n twiddles $s thumbs and $p appears.",   ch, light, NULL, TO_ROOM );
    act( "You twiddle your thumbs and $p appears.", ch, light, NULL, TO_CHAR );
    return;
}



void spell_control_weather( int sn, int level, CHAR_DATA *ch, void *vo )
{
    if ( !str_cmp( target_name, "better" ) )
        weather_info.change += dice( level / 3, 4 );
    else if ( !str_cmp( target_name, "worse" ) )
        weather_info.change -= dice( level / 3, 4 );
    else
        send_to_char ("Do you want it to get better or worse?\r\n", ch );

    send_to_char( "Ok.\r\n", ch );
    return;
}



void spell_create_food( int sn, int level, CHAR_DATA *ch, void *vo )
{
    OBJ_DATA *mushroom;

    mushroom = create_object( get_obj_index( OBJ_VNUM_MUSHROOM ), 0 );
    mushroom->value[0] = 5 + level;
    obj_to_room( mushroom, ch->in_room );
    act( "$p suddenly appears.", ch, mushroom, NULL, TO_ROOM );
    act( "$p suddenly appears.", ch, mushroom, NULL, TO_CHAR );
    return;
}



void spell_create_spring( int sn, int level, CHAR_DATA *ch, void *vo )
{
    OBJ_DATA *spring;

    spring = create_object( get_obj_index( OBJ_VNUM_SPRING ), 0 );
    spring->timer = level;
    obj_to_room( spring, ch->in_room );
    act( "$p flows from the ground.", ch, spring, NULL, TO_ROOM );
    act( "$p flows from the ground.", ch, spring, NULL, TO_CHAR );
    return;
}



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

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

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

    water = UMIN(
                level * (weather_info.sky >= SKY_RAINING ? 4 : 2),
                obj->value[0] - obj->value[1]
            );

    if ( water > 0 ) {
        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 );
            free_string( obj->name );
            obj->name = str_dup( buf );
        }
        act( "$p is filled.", ch, obj, NULL, TO_CHAR );
    }

    return;
}



void spell_cure_blindness( int sn, int level, CHAR_DATA *ch, void *vo )
{
    CHAR_DATA *victim = (CHAR_DATA *) vo;
    if ( !is_affected( victim, gsn_blindness ) )
        return;
    affect_strip( victim, gsn_blindness );
    send_to_char( "Your vision returns!\r\n", victim );
    if ( ch != victim )
        send_to_char( "Ok.\r\n", ch );
    return;
}



void spell_cure_critical( int sn, int level, CHAR_DATA *ch, void *vo )
{
    CHAR_DATA *victim = (CHAR_DATA *) vo;
    int heal;

    heal = dice(3, 8) + level - 6;
    victim->hit = UMIN( victim->hit + heal, victim->max_hit );
    update_pos( victim );
    send_to_char( "You feel better!\r\n", victim );
    if ( ch != victim )
        send_to_char( "Ok.\r\n", ch );
    return;
}



void spell_cure_light( int sn, int level, CHAR_DATA *ch, void *vo )
{
    CHAR_DATA *victim = (CHAR_DATA *) vo;
    int heal;

    heal = dice(1, 8) + level / 3;
    victim->hit = UMIN( victim->hit + heal, victim->max_hit );
    update_pos( victim );
    send_to_char( "You feel better!\r\n", victim );
    if ( ch != victim )
        send_to_char( "Ok.\r\n", ch );
    return;
}



void spell_cure_poison( int sn, int level, CHAR_DATA *ch, void *vo )
{
    CHAR_DATA *victim = (CHAR_DATA *) vo;
    if ( is_affected( victim, gsn_poison ) ) {
        affect_strip( victim, gsn_poison );
        act( "$N looks better.", ch, NULL, victim, TO_NOTVICT );
        send_to_char( "A warm feeling runs through your body.\r\n", victim );
        send_to_char( "Ok.\r\n", ch );
    }
    return;
}



void spell_cure_serious( int sn, int level, CHAR_DATA *ch, void *vo )
{
    CHAR_DATA *victim = (CHAR_DATA *) vo;
    int heal;

    heal = dice(2, 8) + level /2 ;
    victim->hit = UMIN( victim->hit + heal, victim->max_hit );
    update_pos( victim );
    send_to_char( "You feel better!\r\n", victim );
    if ( ch != victim )
        send_to_char( "Ok.\r\n", ch );
    return;
}



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

    if ( IS_AFFECTED(victim, AFF_CURSE) || saves_spell( level, victim ) )
        return;
    af.type      = sn;
    af.duration  = 4*level;
    af.location  = APPLY_HITROLL;
    af.modifier  = -1;
    af.bitvector = AFF_CURSE;
    affect_to_char( victim, &af );

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

    send_to_char( "You feel unclean.\r\n", victim );
    if ( ch != victim )
        send_to_char( "Ok.\r\n", ch );
    return;
}



void spell_detect_evil( int sn, int level, CHAR_DATA *ch, void *vo )
{
    CHAR_DATA *victim = (CHAR_DATA *) vo;
    AFFECT_DATA af;

    if ( IS_AFFECTED(victim, AFF_DETECT_EVIL) )
        return;
    af.type      = sn;
    af.duration  = level;
    af.modifier  = 0;
    af.location  = APPLY_NONE;
    af.bitvector = AFF_DETECT_EVIL;
    affect_to_char( victim, &af );
    send_to_char( "Your eyes tingle.\r\n", victim );
    if ( ch != victim )
        send_to_char( "Ok.\r\n", ch );
    return;
}



void spell_detect_hidden( int sn, int level, CHAR_DATA *ch, void *vo )
{
    CHAR_DATA *victim = (CHAR_DATA *) vo;
    AFFECT_DATA af;

    if ( IS_AFFECTED(victim, AFF_DETECT_HIDDEN) )
        return;
    af.type      = sn;
    af.duration  = level;
    af.location  = APPLY_NONE;
    af.modifier  = 0;
    af.bitvector = AFF_DETECT_HIDDEN;
    affect_to_char( victim, &af );
    send_to_char( "Your awareness improves.\r\n", victim );
    if ( ch != victim )
        send_to_char( "Ok.\r\n", ch );
    return;
}



void spell_detect_invis( int sn, int level, CHAR_DATA *ch, void *vo )
{
    CHAR_DATA *victim = (CHAR_DATA *) vo;
    AFFECT_DATA af;

    if ( IS_AFFECTED(victim, AFF_DETECT_INVIS) )
        return;
    af.type      = sn;
    af.duration  = level;
    af.modifier  = 0;
    af.location  = APPLY_NONE;
    af.bitvector = AFF_DETECT_INVIS;
    affect_to_char( victim, &af );
    send_to_char( "Your eyes tingle.\r\n", victim );
    if ( ch != victim )
        send_to_char( "Ok.\r\n", ch );
    return;
}



void spell_detect_magic( int sn, int level, CHAR_DATA *ch, void *vo )
{
    CHAR_DATA *victim = (CHAR_DATA *) vo;
    AFFECT_DATA af;

    if ( IS_AFFECTED(victim, AFF_DETECT_MAGIC) )
        return;
    af.type      = sn;
    af.duration  = level;
    af.modifier  = 0;
    af.location  = APPLY_NONE;
    af.bitvector = AFF_DETECT_MAGIC;
    affect_to_char( victim, &af );
    send_to_char( "Your eyes tingle.\r\n", victim );
    if ( ch != victim )
        send_to_char( "Ok.\r\n", ch );
    return;
}



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

    if ( obj->item_type == ITEM_DRINK_CON || obj->item_type == ITEM_FOOD ) {
        if ( obj->value[3] != 0 )
            send_to_char( "You smell poisonous fumes.\r\n", ch );
        else
            send_to_char( "It looks very delicious.\r\n", ch );
    } else {
        send_to_char( "It doesn't look poisoned.\r\n", ch );
    }

    return;
}



void spell_dispel_evil( int sn, int level, CHAR_DATA *ch, void *vo )
{
    CHAR_DATA *victim = (CHAR_DATA *) vo;
    int dam;

    if ( !IS_NPC(ch) && IS_EVIL(ch) )
        victim = ch;

    if ( IS_GOOD(victim) ) {
        act( "God protects $N.", ch, NULL, victim, TO_ROOM );
        return;
    }

    if ( IS_NEUTRAL(victim) ) {
        act( "$N does not seem to be affected.", ch, NULL, victim, TO_CHAR );
        return;
    }

    dam = dice( level, 4 );
    if ( saves_spell( level, victim ) )
        dam /= 2;
    damage( ch, victim, dam, sn );
    return;
}



void spell_dispel_magic( int sn, int level, CHAR_DATA *ch, void *vo )
{
    CHAR_DATA *victim = (CHAR_DATA *) vo;
    int affected_by;

    if ( victim->affected_by == 0
            ||   level < victim->level
            ||   saves_spell( level, victim ) ) {
        send_to_char( "You failed.\r\n", ch );
        return;
    }

    for ( ;; ) {
        affected_by = 1 << number_bits( 5 );
        if ( IS_SET(victim->affected_by, affected_by) )
            break;
    }
    REMOVE_BIT(victim->affected_by, affected_by);
    send_to_char( "Ok.\r\n", ch );

    return;
}



void spell_earthquake( int sn, int level, CHAR_DATA *ch, void *vo )
{
    CHAR_DATA *vch;
    CHAR_DATA *vch_next;

    send_to_char( "The earth trembles beneath your feet!\r\n", ch );
    act( "$n makes the earth tremble and shiver.", ch, NULL, NULL, TO_ROOM );

    for ( vch = char_list; vch != NULL; vch = vch_next ) {
        vch_next	= vch->next;
        if ( vch->in_room == NULL )
            continue;
        if ( vch->in_room == ch->in_room ) {
            if ( vch != ch && ( IS_NPC(ch) ? !IS_NPC(vch) : IS_NPC(vch) ) )
                damage( ch, vch, level + dice(2, 8), sn );
            continue;
        }

        if ( vch->in_room->area == ch->in_room->area )
            send_to_char( "The earth trembles and shivers.\r\n", vch );
    }

    return;
}



void spell_enchant_weapon( int sn, int level, CHAR_DATA *ch, void *vo )
{
    OBJ_DATA *obj = (OBJ_DATA *) vo;
    AFFECT_DATA *paf;

    if ( obj->item_type != ITEM_WEAPON
            ||   IS_OBJ_STAT(obj, ITEM_MAGIC)
            ||   obj->affected != NULL )
        return;

    if ( affect_free == NULL ) {
        paf		= alloc_perm( sizeof(*paf) );
    } else {
        paf		= affect_free;
        affect_free	= affect_free->next;
    }

    paf->type		= -1;
    paf->duration	= -1;
    paf->location	= APPLY_HITROLL;
    paf->modifier	= level / 5;
    paf->bitvector	= 0;
    paf->next		= obj->affected;
    obj->affected	= paf;

    if ( affect_free == NULL ) {
        paf		= alloc_perm( sizeof(*paf) );
    } else {
        paf		= affect_free;
        affect_free	= affect_free->next;
    }

    paf->type		= -1;
    paf->duration	= -1;
    paf->location	= APPLY_DAMROLL;
    paf->modifier	= level / 10;
    paf->bitvector	= 0;
    paf->next		= obj->affected;
    obj->affected	= paf;

    if ( IS_GOOD(ch) ) {
        SET_BIT(obj->extra_flags, ITEM_ANTI_EVIL);
        act( "$p glows blue.", ch, obj, NULL, TO_CHAR );
    } else if ( IS_EVIL(ch) ) {
        SET_BIT(obj->extra_flags, ITEM_ANTI_GOOD);
        act( "$p glows red.", ch, obj, NULL, TO_CHAR );
    } else {
        SET_BIT(obj->extra_flags, ITEM_ANTI_EVIL);
        SET_BIT(obj->extra_flags, ITEM_ANTI_GOOD);
        act( "$p glows yellow.", ch, obj, NULL, TO_CHAR );
    }

    send_to_char( "Ok.\r\n", ch );
    return;
}



/*
 * Drain XP, MANA, HP.
 * Caster gains HP.
 */
void spell_energy_drain( int sn, int level, CHAR_DATA *ch, void *vo )
{
    CHAR_DATA *victim = (CHAR_DATA *) vo;
    int dam;

    if ( saves_spell( level, victim ) )
        return;

    ch->alignment = UMAX(-1000, ch->alignment - 200);
    if ( victim->level <= 2 ) {
        dam		 = ch->hit + 1;
    } else {
        gain_exp( victim, 0 - number_range( level / 2, 3 * level / 2 ) );
        victim->mana	/= 2;
        victim->move	/= 2;
        dam		 = dice(1, level);
        ch->hit		+= dam;
    }

    damage( ch, victim, dam, sn );

    return;
}



void spell_fireball( int sn, int level, CHAR_DATA *ch, void *vo )
{
    CHAR_DATA *victim = (CHAR_DATA *) vo;
    static const short dam_each[] = {
        0,
        0,   0,   0,   0,   0,	  0,   0,   0,   0,   0,
        0,   0,   0,   0,  30,	 35,  40,  45,  50,  55,
        60,  65,  70,  75,  80,	 82,  84,  86,  88,  90,
        92,  94,  96,  98, 100,	102, 104, 106, 108, 110,
        112, 114, 116, 118, 120,	122, 124, 126, 128, 130
    };
    int dam;

    level	= UMIN(level, sizeof(dam_each)/sizeof(dam_each[0]) - 1);
    level	= UMAX(0, level);
    dam		= number_range( dam_each[level] / 2, dam_each[level] * 2 );
    if ( saves_spell( level, victim ) )
        dam /= 2;
    damage( ch, victim, dam, sn );
    return;
}



void spell_flamestrike( int sn, int level, CHAR_DATA *ch, void *vo )
{
    CHAR_DATA *victim = (CHAR_DATA *) vo;
    int dam;

    dam = dice(6, 8);
    if ( saves_spell( level, victim ) )
        dam /= 2;
    damage( ch, victim, dam, sn );
    return;
}



void spell_faerie_fire( int sn, int level, CHAR_DATA *ch, void *vo )
{
    CHAR_DATA *victim = (CHAR_DATA *) vo;
    AFFECT_DATA af;

    if ( IS_AFFECTED(victim, AFF_FAERIE_FIRE) )
        return;
    af.type      = sn;
    af.duration  = level;
    af.location  = APPLY_AC;
    af.modifier  = 2 * level;
    af.bitvector = AFF_FAERIE_FIRE;
    affect_to_char( victim, &af );
    send_to_char( "You are surrounded by a pink outline.\r\n", victim );
    act( "$n is surrounded by a pink outline.", victim, NULL, NULL, TO_ROOM );
    return;
}



void spell_faerie_fog( int sn, int level, CHAR_DATA *ch, void *vo )
{
    CHAR_DATA *ich;

    act( "$n conjures a cloud of purple smoke.", ch, NULL, NULL, TO_ROOM );
    send_to_char( "You conjure a cloud of purple smoke.\r\n", ch );

    for ( ich = ch->in_room->people; ich != NULL; ich = ich->next_in_room ) {
        if ( !IS_NPC(ich) && IS_SET(ich->act, PLR_WIZINVIS) )
            continue;

        if ( ich == ch || saves_spell( level, ich ) )
            continue;

        affect_strip ( ich, gsn_invis			);
        affect_strip ( ich, gsn_mass_invis		);
        affect_strip ( ich, gsn_sneak			);
        REMOVE_BIT   ( ich->affected_by, AFF_HIDE	);
        REMOVE_BIT   ( ich->affected_by, AFF_INVISIBLE	);
        REMOVE_BIT   ( ich->affected_by, AFF_SNEAK	);
        act( "$n is revealed!", ich, NULL, NULL, TO_ROOM );
        send_to_char( "You are revealed!\r\n", ich );
    }

    return;
}



void spell_fly( int sn, int level, CHAR_DATA *ch, void *vo )
{
    CHAR_DATA *victim = (CHAR_DATA *) vo;
    AFFECT_DATA af;

    if ( IS_AFFECTED(victim, AFF_FLYING) )
        return;
    af.type      = sn;
    af.duration  = level + 3;
    af.location  = 0;
    af.modifier  = 0;
    af.bitvector = AFF_FLYING;
    affect_to_char( victim, &af );
    send_to_char( "Your feet rise off the ground.\r\n", victim );
    act( "$n's feet rise off the ground.", victim, NULL, NULL, TO_ROOM );
    return;
}



void spell_gate( int sn, int level, CHAR_DATA *ch, void *vo )
{
    char_to_room( create_mobile( get_mob_index(MOB_VNUM_VAMPIRE) ),
                  ch->in_room );
    return;
}



/*
 * Spell for mega1.are from Glop/Erkenbrand.
 */
void spell_general_purpose( int sn, int level, CHAR_DATA *ch, void *vo )
{
    CHAR_DATA *victim = (CHAR_DATA *) vo;
    int dam;

    dam = number_range( 25, 100 );
    if ( saves_spell( level, victim ) )
        dam /= 2;
    damage( ch, victim, dam, sn );
    return;
}



void spell_giant_strength( int sn, int level, CHAR_DATA *ch, void *vo )
{
    CHAR_DATA *victim = (CHAR_DATA *) vo;
    AFFECT_DATA af;

    if ( is_affected( victim, sn ) )
        return;
    af.type      = sn;
    af.duration  = level;
    af.location  = APPLY_STR;
    af.modifier  = 1 + (level >= 18) + (level >= 25);
    af.bitvector = 0;
    affect_to_char( victim, &af );
    send_to_char( "You feel stronger.\r\n", victim );
    if ( ch != victim )
        send_to_char( "Ok.\r\n", ch );
    return;
}



void spell_harm( int sn, int level, CHAR_DATA *ch, void *vo )
{
    CHAR_DATA *victim = (CHAR_DATA *) vo;
    int dam;

    dam = UMAX(  20, victim->hit - dice(1,4) );
    if ( saves_spell( level, victim ) )
        dam = UMIN( 50, dam / 4 );
    dam = UMIN( 100, dam );
    damage( ch, victim, dam, sn );
    return;
}



void spell_heal( int sn, int level, CHAR_DATA *ch, void *vo )
{
    CHAR_DATA *victim = (CHAR_DATA *) vo;
    victim->hit = UMIN( victim->hit + 100, victim->max_hit );
    update_pos( victim );
    send_to_char( "A warm feeling fills your body.\r\n", victim );
    if ( ch != victim )
        send_to_char( "Ok.\r\n", ch );
    return;
}



/*
 * Spell for mega1.are from Glop/Erkenbrand.
 */
void spell_high_explosive( int sn, int level, CHAR_DATA *ch, void *vo )
{
    CHAR_DATA *victim = (CHAR_DATA *) vo;
    int dam;

    dam = number_range( 30, 120 );
    if ( saves_spell( level, victim ) )
        dam /= 2;
    damage( ch, victim, dam, sn );
    return;
}



void spell_identify( int sn, int level, CHAR_DATA *ch, void *vo )
{
    OBJ_DATA *obj = (OBJ_DATA *) vo;
    char buf[MAX_STRING_LENGTH];
    AFFECT_DATA *paf;

    sprintf( buf,
             "Object '%s' is type %s, extra flags %s.\r\nWeight is %d, value is %d, level is %d.\r\n",

             obj->name,
             item_type_name( obj ),
             extra_bit_name( obj->extra_flags ),
             obj->weight,
             obj->cost,
             obj->level
           );
    send_to_char( buf, ch );

    switch ( obj->item_type ) {
    case ITEM_SCROLL:
    case ITEM_POTION:
        sprintf( buf, "Level %d spells of:", obj->value[0] );
        send_to_char( buf, ch );

        if ( obj->value[1] >= 0 && obj->value[1] < MAX_SKILL ) {
            send_to_char( " '", ch );
            send_to_char( skill_table[obj->value[1]].name, ch );
            send_to_char( "'", ch );
        }

        if ( obj->value[2] >= 0 && obj->value[2] < MAX_SKILL ) {
            send_to_char( " '", ch );
            send_to_char( skill_table[obj->value[2]].name, ch );
            send_to_char( "'", ch );
        }

        if ( obj->value[3] >= 0 && obj->value[3] < MAX_SKILL ) {
            send_to_char( " '", ch );
            send_to_char( skill_table[obj->value[3]].name, ch );
            send_to_char( "'", ch );
        }

        send_to_char( ".\r\n", ch );
        break;

    case ITEM_WAND:
    case ITEM_STAFF:
        sprintf( buf, "Has %d(%d) charges of level %d",
                 obj->value[1], obj->value[2], obj->value[0] );
        send_to_char( buf, ch );

        if ( obj->value[3] >= 0 && obj->value[3] < MAX_SKILL ) {
            send_to_char( " '", ch );
            send_to_char( skill_table[obj->value[3]].name, ch );
            send_to_char( "'", ch );
        }

        send_to_char( ".\r\n", ch );
        break;

    case ITEM_WEAPON:
        sprintf( buf, "Damage is %d to %d (average %d).\r\n",
                 obj->value[1], obj->value[2],
                 ( obj->value[1] + obj->value[2] ) / 2 );
        send_to_char( buf, ch );
        break;

    case ITEM_ARMOR:
        sprintf( buf, "Armor class is %d.\r\n", obj->value[0] );
        send_to_char( buf, ch );
        break;
    }

    for ( paf = obj->pIndexData->affected; paf != NULL; paf = paf->next ) {
        if ( paf->location != APPLY_NONE && paf->modifier != 0 ) {
            sprintf( buf, "Affects %s by %d.\r\n",
                     affect_loc_name( paf->location ), paf->modifier );
            send_to_char( buf, ch );
        }
    }

    for ( paf = obj->affected; paf != NULL; paf = paf->next ) {
        if ( paf->location != APPLY_NONE && paf->modifier != 0 ) {
            sprintf( buf, "Affects %s by %d.\r\n",
                     affect_loc_name( paf->location ), paf->modifier );
            send_to_char( buf, ch );
        }
    }

    return;
}



void spell_infravision( int sn, int level, CHAR_DATA *ch, void *vo )
{
    CHAR_DATA *victim = (CHAR_DATA *) vo;
    AFFECT_DATA af;

    if ( IS_AFFECTED(victim, AFF_INFRARED) )
        return;
    act( "$n's eyes glow red.\r\n", ch, NULL, NULL, TO_ROOM );
    af.type      = sn;
    af.duration  = 2 * level;
    af.location  = APPLY_NONE;
    af.modifier  = 0;
    af.bitvector = AFF_INFRARED;
    affect_to_char( victim, &af );
    send_to_char( "Your eyes glow red.\r\n", victim );
    if ( ch != victim )
        send_to_char( "Ok.\r\n", ch );
    return;
}



void spell_invis( int sn, int level, CHAR_DATA *ch, void *vo )
{
    CHAR_DATA *victim = (CHAR_DATA *) vo;
    AFFECT_DATA af;

    if ( IS_AFFECTED(victim, AFF_INVISIBLE) )
        return;

    act( "$n fades out of existence.", victim, NULL, NULL, TO_ROOM );
    af.type      = sn;
    af.duration  = 24;
    af.location  = APPLY_NONE;
    af.modifier  = 0;
    af.bitvector = AFF_INVISIBLE;
    affect_to_char( victim, &af );
    send_to_char( "You fade out of existence.\r\n", victim );
    if ( ch != victim )
        send_to_char( "Ok.\r\n", ch );
    return;
}



void spell_know_alignment( int sn, int level, CHAR_DATA *ch, void *vo )
{
    CHAR_DATA *victim = (CHAR_DATA *) vo;
    char *msg;
    int ap;

    ap = victim->alignment;

    if ( ap >  700 ) msg = "$N has an aura as white as the driven snow.";
    else if ( ap >  350 ) msg = "$N is of excellent moral character.";
    else if ( ap >  100 ) msg = "$N is often kind and thoughtful.";
    else if ( ap > -100 ) msg = "$N doesn't have a firm moral commitment.";
    else if ( ap > -350 ) msg = "$N lies to $S friends.";
    else if ( ap > -700 ) msg = "$N's slash DISEMBOWELS you!";
    else msg = "I'd rather just not say anything at all about $N.";

    act( msg, ch, NULL, victim, TO_CHAR );
    return;
}



void spell_lightning_bolt( int sn, int level, CHAR_DATA *ch, void *vo )
{
    CHAR_DATA *victim = (CHAR_DATA *) vo;
    static const short dam_each[] = {
        0,
        0,  0,  0,  0,  0,	 0,  0,  0, 25, 28,
        31, 34, 37, 40, 40,	41, 42, 42, 43, 44,
        44, 45, 46, 46, 47,	48, 48, 49, 50, 50,
        51, 52, 52, 53, 54,	54, 55, 56, 56, 57,
        58, 58, 59, 60, 60,	61, 62, 62, 63, 64
    };
    int dam;

    level	= UMIN(level, sizeof(dam_each)/sizeof(dam_each[0]) - 1);
    level	= UMAX(0, level);
    dam		= number_range( dam_each[level] / 2, dam_each[level] * 2 );
    if ( saves_spell( level, victim ) )
        dam /= 2;
    damage( ch, victim, dam, sn );
    return;
}



void spell_locate_object( int sn, int level, CHAR_DATA *ch, void *vo )
{
    char buf[MAX_INPUT_LENGTH];
    OBJ_DATA *obj;
    OBJ_DATA *in_obj;
    bool found;

    found = FALSE;
    for ( obj = object_list; obj != NULL; obj = obj->next ) {
        if ( !can_see_obj( ch, obj ) || !is_name( target_name, obj->name ) )
            continue;

        found = TRUE;

        for ( in_obj = obj; in_obj->in_obj != NULL; in_obj = in_obj->in_obj )
            ;

        if ( in_obj->carried_by != NULL ) {
            sprintf( buf, "%s carried by %s.\r\n",
                     obj->short_descr, PERS(in_obj->carried_by, ch) );
        } else {
            sprintf( buf, "%s in %s.\r\n",
                     obj->short_descr, in_obj->in_room == NULL
                     ? "somewhere" : in_obj->in_room->name );
        }

        buf[0] = UPPER(buf[0]);
        send_to_char( buf, ch );
    }

    if ( !found )
        send_to_char( "Nothing like that in hell, earth, or heaven.\r\n", ch );

    return;
}



void spell_magic_missile( int sn, int level, CHAR_DATA *ch, void *vo )
{
    CHAR_DATA *victim = (CHAR_DATA *) vo;
    static const short dam_each[] = {
        0,
        3,  3,  4,  4,  5,	 6,  6,  6,  6,  6,
        7,  7,  7,  7,  7,	 8,  8,  8,  8,  8,
        9,  9,  9,  9,  9,	10, 10, 10, 10, 10,
        11, 11, 11, 11, 11,	12, 12, 12, 12, 12,
        13, 13, 13, 13, 13,	14, 14, 14, 14, 14
    };
    int dam;

    level	= UMIN(level, sizeof(dam_each)/sizeof(dam_each[0]) - 1);
    level	= UMAX(0, level);
    dam		= number_range( dam_each[level] / 2, dam_each[level] * 2 );
    if ( saves_spell( level, victim ) )
        dam /= 2;
    damage( ch, victim, dam, sn );
    return;
}



void spell_mass_invis( int sn, int level, CHAR_DATA *ch, void *vo )
{
    AFFECT_DATA af;
    CHAR_DATA *gch;

    for ( gch = ch->in_room->people; gch != NULL; gch = gch->next_in_room ) {
        if ( !is_same_group( gch, ch ) || IS_AFFECTED(gch, AFF_INVISIBLE) )
            continue;
        act( "$n slowly fades out of existence.", gch, NULL, NULL, TO_ROOM );
        send_to_char( "You slowly fade out of existence.\r\n", gch );
        af.type      = sn;
        af.duration  = 24;
        af.location  = APPLY_NONE;
        af.modifier  = 0;
        af.bitvector = AFF_INVISIBLE;
        affect_to_char( gch, &af );
    }
    send_to_char( "Ok.\r\n", ch );

    return;
}



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



void spell_pass_door( int sn, int level, CHAR_DATA *ch, void *vo )
{
    CHAR_DATA *victim = (CHAR_DATA *) vo;
    AFFECT_DATA af;

    if ( IS_AFFECTED(victim, AFF_PASS_DOOR) )
        return;
    af.type      = sn;
    af.duration  = number_fuzzy( level / 4 );
    af.location  = APPLY_NONE;
    af.modifier  = 0;
    af.bitvector = AFF_PASS_DOOR;
    affect_to_char( victim, &af );
    act( "$n turns translucent.", victim, NULL, NULL, TO_ROOM );
    send_to_char( "You turn translucent.\r\n", victim );
    return;
}



void spell_poison( int sn, int level, CHAR_DATA *ch, void *vo )
{
    CHAR_DATA *victim = (CHAR_DATA *) vo;
    AFFECT_DATA af;

    if ( saves_spell( level, victim ) )
        return;
    af.type      = sn;
    af.duration  = level;
    af.location  = APPLY_STR;
    af.modifier  = -2;
    af.bitvector = AFF_POISON;
    affect_join( victim, &af );
    send_to_char( "You feel very sick.\r\n", victim );
    if ( ch != victim )
        send_to_char( "Ok.\r\n", ch );
    return;
}



void spell_protection( int sn, int level, CHAR_DATA *ch, void *vo )
{
    CHAR_DATA *victim = (CHAR_DATA *) vo;
    AFFECT_DATA af;

    if ( IS_AFFECTED(victim, AFF_PROTECT) )
        return;
    af.type      = sn;
    af.duration  = 24;
    af.location  = APPLY_NONE;
    af.modifier  = 0;
    af.bitvector = AFF_PROTECT;
    affect_to_char( victim, &af );
    send_to_char( "You feel protected.\r\n", victim );
    if ( ch != victim )
        send_to_char( "Ok.\r\n", ch );
    return;
}



void spell_refresh( int sn, int level, CHAR_DATA *ch, void *vo )
{
    CHAR_DATA *victim = (CHAR_DATA *) vo;
    victim->move = UMIN( victim->move + level, victim->max_move );
    send_to_char( "You feel less tired.\r\n", victim );
    if ( ch != victim )
        send_to_char( "Ok.\r\n", ch );
    return;
}



void spell_remove_curse( int sn, int level, CHAR_DATA *ch, void *vo )
{
    CHAR_DATA *victim = (CHAR_DATA *) vo;
    if ( is_affected( victim, gsn_curse ) ) {
        affect_strip( victim, gsn_curse );
        send_to_char( "You feel better.\r\n", victim );
        if ( ch != victim )
            send_to_char( "Ok.\r\n", ch );
    }

    return;
}



void spell_sanctuary( int sn, int level, CHAR_DATA *ch, void *vo )
{
    CHAR_DATA *victim = (CHAR_DATA *) vo;
    AFFECT_DATA af;

    if ( IS_AFFECTED(victim, AFF_SANCTUARY) )
        return;
    af.type      = sn;
    af.duration  = number_fuzzy( level / 8 );
    af.location  = APPLY_NONE;
    af.modifier  = 0;
    af.bitvector = AFF_SANCTUARY;
    affect_to_char( victim, &af );
    act( "$n is surrounded by a white aura.", victim, NULL, NULL, TO_ROOM );
    send_to_char( "You are surrounded by a white aura.\r\n", victim );
    return;
}



void spell_shield( int sn, int level, CHAR_DATA *ch, void *vo )
{
    CHAR_DATA *victim = (CHAR_DATA *) vo;
    AFFECT_DATA af;

    if ( is_affected( victim, sn ) )
        return;
    af.type      = sn;
    af.duration  = 8 + level;
    af.location  = APPLY_AC;
    af.modifier  = -20;
    af.bitvector = 0;
    affect_to_char( victim, &af );
    act( "$n is surrounded by a force shield.", victim, NULL, NULL, TO_ROOM );
    send_to_char( "You are surrounded by a force shield.\r\n", victim );
    return;
}



void spell_shocking_grasp( int sn, int level, CHAR_DATA *ch, void *vo )
{
    CHAR_DATA *victim = (CHAR_DATA *) vo;
    static const int dam_each[] = {
        0,
        0,  0,  0,  0,  0,	 0, 20, 25, 29, 33,
        36, 39, 39, 39, 40,	40, 41, 41, 42, 42,
        43, 43, 44, 44, 45,	45, 46, 46, 47, 47,
        48, 48, 49, 49, 50,	50, 51, 51, 52, 52,
        53, 53, 54, 54, 55,	55, 56, 56, 57, 57
    };
    int dam;

    level	= UMIN(level, sizeof(dam_each)/sizeof(dam_each[0]) - 1);
    level	= UMAX(0, level);
    dam		= number_range( dam_each[level] / 2, dam_each[level] * 2 );
    if ( saves_spell( level, victim ) )
        dam /= 2;
    damage( ch, victim, dam, sn );
    return;
}



void spell_sleep( int sn, int level, CHAR_DATA *ch, void *vo )
{
    CHAR_DATA *victim = (CHAR_DATA *) vo;
    AFFECT_DATA af;

    if ( IS_AFFECTED(victim, AFF_SLEEP)
            ||   level < victim->level
            ||   saves_spell( level, victim ) )
        return;

    af.type      = sn;
    af.duration  = 4 + level;
    af.location  = APPLY_NONE;
    af.modifier  = 0;
    af.bitvector = AFF_SLEEP;
    affect_join( victim, &af );

    if ( IS_AWAKE(victim) ) {
        send_to_char( "You feel very sleepy ..... zzzzzz.\r\n", victim );
        act( "$n goes to sleep.", victim, NULL, NULL, TO_ROOM );
        victim->position = POS_SLEEPING;
    }

    return;
}



void spell_stone_skin( int sn, int level, CHAR_DATA *ch, void *vo )
{
    CHAR_DATA *victim = (CHAR_DATA *) vo;
    AFFECT_DATA af;

    if ( is_affected( ch, sn ) )
        return;
    af.type      = sn;
    af.duration  = level;
    af.location  = APPLY_AC;
    af.modifier  = -40;
    af.bitvector = 0;
    affect_to_char( victim, &af );
    act( "$n's skin turns to stone.", victim, NULL, NULL, TO_ROOM );
    send_to_char( "Your skin turns to stone.\r\n", victim );
    return;
}



void spell_summon( int sn, int level, CHAR_DATA *ch, void *vo )
{
    CHAR_DATA *victim;

    if ( ( victim = get_char_world( ch, target_name ) ) == NULL
            ||   victim == ch
            ||   victim->in_room == NULL
            ||   IS_SET(victim->in_room->room_flags, ROOM_SAFE)
            ||   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_RECALL)
            ||   victim->level >= level + 3
            ||   victim->fighting != NULL
            ||   victim->in_room->area != ch->in_room->area
            ||   (IS_NPC(victim) && saves_spell( level, victim ) ) ) {
        send_to_char( "You failed.\r\n", ch );
        return;
    }

    act( "$n disappears suddenly.", victim, NULL, NULL, TO_ROOM );
    char_from_room( victim );
    char_to_room( victim, ch->in_room );
    act( "$n arrives suddenly.", victim, NULL, NULL, TO_ROOM );
    act( "$N has summoned you!", victim, NULL, ch,   TO_VICT );
    do_look( victim, "auto" );
    return;
}



void spell_teleport( int sn, int level, CHAR_DATA *ch, void *vo )
{
    CHAR_DATA *victim = (CHAR_DATA *) vo;
    ROOM_INDEX_DATA *pRoomIndex;

    if ( victim->in_room == NULL
            ||   IS_SET(victim->in_room->room_flags, ROOM_NO_RECALL)
            || ( !IS_NPC(ch) && victim->fighting != NULL )
            || ( victim != ch
                 && ( saves_spell( level, victim ) || saves_spell( level, victim ) ) ) ) {
        send_to_char( "You failed.\r\n", ch );
        return;
    }

    for ( ; ; ) {
        pRoomIndex = get_room_index( number_range( 0, 65535 ) );
        if ( pRoomIndex != NULL )
            if ( !IS_SET(pRoomIndex->room_flags, ROOM_PRIVATE)
                    &&   !IS_SET(pRoomIndex->room_flags, ROOM_SOLITARY) )
                break;
    }

    act( "$n slowly fades out of existence.", victim, NULL, NULL, TO_ROOM );
    char_from_room( victim );
    char_to_room( victim, pRoomIndex );
    act( "$n slowly fades into existence.", victim, NULL, NULL, TO_ROOM );
    do_look( victim, "auto" );
    return;
}



void spell_ventriloquate( int sn, int level, CHAR_DATA *ch, void *vo )
{
    char buf1[MAX_STRING_LENGTH];
    char buf2[MAX_STRING_LENGTH];
    char speaker[MAX_INPUT_LENGTH];
    CHAR_DATA *vch;

    target_name = one_argument( target_name, speaker );

    sprintf( buf1, "%s says '%s'.\r\n",              speaker, target_name );
    sprintf( buf2, "Someone makes %s say '%s'.\r\n", speaker, target_name );
    buf1[0] = UPPER(buf1[0]);

    for ( vch = ch->in_room->people; vch != NULL; vch = vch->next_in_room ) {
        if ( !is_name( speaker, vch->name ) )
            send_to_char( saves_spell( level, vch ) ? buf2 : buf1, vch );
    }

    return;
}



void spell_weaken( int sn, int level, CHAR_DATA *ch, void *vo )
{
    CHAR_DATA *victim = (CHAR_DATA *) vo;
    AFFECT_DATA af;

    if ( is_affected( victim, sn ) || saves_spell( level, victim ) )
        return;
    af.type      = sn;
    af.duration  = level / 2;
    af.location  = APPLY_STR;
    af.modifier  = -2;
    af.bitvector = 0;
    affect_to_char( victim, &af );
    send_to_char( "You feel weaker.\r\n", victim );
    if ( ch != victim )
        send_to_char( "Ok.\r\n", ch );
    return;
}



/*
 * This is for muds that _want_ scrolls of recall.
 * Ick.
 */
void spell_word_of_recall( int sn, int level, CHAR_DATA *ch, void *vo )
{
    do_recall( (CHAR_DATA *) vo, "" );
    return;
}



/*
 * NPC spells.
 */
void spell_acid_breath( int sn, int level, CHAR_DATA *ch, void *vo )
{
    CHAR_DATA *victim = (CHAR_DATA *) vo;
    OBJ_DATA *obj_lose;
    OBJ_DATA *obj_next;
    int dam;
    int hpch;

    if ( number_percent( ) < 2 * level && !saves_spell( level, victim ) ) {
        for ( obj_lose = ch->carrying; obj_lose != NULL; obj_lose = obj_next ) {
            int iWear;

            obj_next = obj_lose->next_content;

            if ( number_bits( 2 ) != 0 )
                continue;

            switch ( obj_lose->item_type ) {
            case ITEM_ARMOR:
                if ( obj_lose->value[0] > 0 ) {
                    act( "$p is pitted and etched!",
                         victim, obj_lose, NULL, TO_CHAR );
                    if ( ( iWear = obj_lose->wear_loc ) != WEAR_NONE )
                        victim->armor -= apply_ac( obj_lose, iWear );
                    obj_lose->value[0] -= 1;
                    obj_lose->cost      = 0;
                    if ( iWear != WEAR_NONE )
                        victim->armor += apply_ac( obj_lose, iWear );
                }
                break;

            case ITEM_CONTAINER:
                act( "$p fumes and dissolves!",
                     victim, obj_lose, NULL, TO_CHAR );
                extract_obj( obj_lose );
                break;
            }
        }
    }

    hpch = UMAX( 10, ch->hit );
    dam  = number_range( hpch/16+1, hpch/8 );
    if ( saves_spell( level, victim ) )
        dam /= 2;
    damage( ch, victim, dam, sn );
    return;
}



void spell_fire_breath( int sn, int level, CHAR_DATA *ch, void *vo )
{
    CHAR_DATA *victim = (CHAR_DATA *) vo;
    OBJ_DATA *obj_lose;
    OBJ_DATA *obj_next;
    int dam;
    int hpch;

    if ( number_percent( ) < 2 * level && !saves_spell( level, victim ) ) {
        for ( obj_lose = victim->carrying; obj_lose != NULL;
                obj_lose = obj_next ) {
            char *msg;

            obj_next = obj_lose->next_content;
            if ( number_bits( 2 ) != 0 )
                continue;

            switch ( obj_lose->item_type ) {
            default:
                continue;
            case ITEM_CONTAINER:
                msg = "$p ignites and burns!";
                break;
            case ITEM_POTION:
                msg = "$p bubbles and boils!";
                break;
            case ITEM_SCROLL:
                msg = "$p crackles and burns!";
                break;
            case ITEM_STAFF:
                msg = "$p smokes and chars!";
                break;
            case ITEM_WAND:
                msg = "$p sparks and sputters!";
                break;
            case ITEM_FOOD:
                msg = "$p blackens and crisps!";
                break;
            case ITEM_PILL:
                msg = "$p melts and drips!";
                break;
            }

            act( msg, victim, obj_lose, NULL, TO_CHAR );
            extract_obj( obj_lose );
        }
    }

    hpch = UMAX( 10, ch->hit );
    dam  = number_range( hpch/16+1, hpch/8 );
    if ( saves_spell( level, victim ) )
        dam /= 2;
    damage( ch, victim, dam, sn );
    return;
}



void spell_frost_breath( int sn, int level, CHAR_DATA *ch, void *vo )
{
    CHAR_DATA *victim = (CHAR_DATA *) vo;
    OBJ_DATA *obj_lose;
    OBJ_DATA *obj_next;
    int dam;
    int hpch;

    if ( number_percent( ) < 2 * level && !saves_spell( level, victim ) ) {
        for ( obj_lose = victim->carrying; obj_lose != NULL;
                obj_lose = obj_next ) {
            char *msg;

            obj_next = obj_lose->next_content;
            if ( number_bits( 2 ) != 0 )
                continue;

            switch ( obj_lose->item_type ) {
            default:
                continue;
            case ITEM_CONTAINER:
            case ITEM_DRINK_CON:
            case ITEM_POTION:
                msg = "$p freezes and shatters!";
                break;
            }

            act( msg, victim, obj_lose, NULL, TO_CHAR );
            extract_obj( obj_lose );
        }
    }

    hpch = UMAX( 10, ch->hit );
    dam  = number_range( hpch/16+1, hpch/8 );
    if ( saves_spell( level, victim ) )
        dam /= 2;
    damage( ch, victim, dam, sn );
    return;
}



void spell_gas_breath( int sn, int level, CHAR_DATA *ch, void *vo )
{
    CHAR_DATA *vch;
    CHAR_DATA *vch_next;
    int dam;
    int hpch;

    for ( vch = ch->in_room->people; vch != NULL; vch = vch_next ) {
        vch_next = vch->next_in_room;
        if ( IS_NPC(ch) ? !IS_NPC(vch) : IS_NPC(vch) ) {
            hpch = UMAX( 10, ch->hit );
            dam  = number_range( hpch/16+1, hpch/8 );
            if ( saves_spell( level, vch ) )
                dam /= 2;
            damage( ch, vch, dam, sn );
        }
    }
    return;
}



void spell_lightning_breath( int sn, int level, CHAR_DATA *ch, void *vo )
{
    CHAR_DATA *victim = (CHAR_DATA *) vo;
    int dam;
    int hpch;

    hpch = UMAX( 10, ch->hit );
    dam = number_range( hpch/16+1, hpch/8 );
    if ( saves_spell( level, victim ) )
        dam /= 2;
    damage( ch, victim, dam, sn );
    return;
}