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

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


/*
 * Local functions.
 */
void say_spell args((CHAR_DATA * ch, int sn));
/*
CHAR_DATA *make_poly_mob args( (CHAR_DATA *ch, int vnum) );
*/
ch_ret spell_affect args((int sn, int level, CHAR_DATA * ch, void *vo));
ch_ret spell_affectchar
args((int sn, int level, CHAR_DATA * ch, void *vo));
int dispel_casting(AFFECT_DATA * paf, CHAR_DATA * ch, CHAR_DATA * victim,
                   int affect, bool dispel);
bool can_charm(CHAR_DATA * ch);

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

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

  if (IS_NPC(ch))
    return skill_lookup(name);
  for (sn = 0; sn < top_sn; sn++) {
    if (!skill_table[sn]->name)
      break;
    if (ch->pcdata->learned[sn] > 0
        && ch->exp >= skill_table[sn]->skill_level[ch->class]
        && LOWER(name[0]) == LOWER(skill_table[sn]->name[0])
        && !str_prefix(name, skill_table[sn]->name))
      return sn;
  }

  return -1;
}

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

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

/*
 * Lookup a personal skill
 * Unused for now.  In place to allow a player to have a custom spell/skill.
 * When this is put in make sure you put in cleanup code if you do any
 * sort of allocating memory in free_char --Shaddai
 */
int
personal_lookup(CHAR_DATA * ch, const char *name)
{
  int sn;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    if (!str_cmp(name, skill_table[sn]->name)
        && ch->pcdata->learned[sn] > 0
        && ch->exp >= skill_table[sn]->skill_level[ch->class])
      return sn;
    if (first >= top)
      return -1;
    if (strcmp(name, skill_table[sn]->name) < 1)
      top = sn - 1;
    else
      first = sn + 1;
  }
  return -1;
}

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

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

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

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

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

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


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

  if (slot <= 0)
    return -1;

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

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

  return -1;
}

/*
 * Handler to tell the victim which spell is being affected.
 * Shaddai
 */
int
dispel_casting(AFFECT_DATA * paf, CHAR_DATA * ch, CHAR_DATA * victim,
               int affect, bool dispel)
{
  char buf[MAX_STRING_LENGTH];
  char *spell;
  SKILLTYPE *sktmp;
  bool is_mage = FALSE, has_detect = FALSE;
  EXT_BV ext_bv = meb(affect);

  if (IS_NPC(ch))
    is_mage = TRUE;
  if (IS_AFFECTED(ch, AFF_DETECT_MAGIC))
    has_detect = TRUE;

  if (paf) {
    if ((sktmp = get_skilltype(paf->type)) == NULL)
      return 0;
    spell = sktmp->name;
  } else
    spell = affect_bit_name(&ext_bv);

  set_char_color(AT_MAGIC, ch);
  set_char_color(AT_HITME, victim);

  if (!can_see(ch, victim))
    strcpy(buf, "Someone");
  else {
    strcpy(buf, (IS_NPC(victim) ? victim->short_descr : victim->name));
    buf[0] = toupper(buf[0]);
  }

  if (dispel) {
    ch_printf(victim, "Your %s vanishes.\n\r", spell);
    if (is_mage && has_detect)
      ch_printf(ch, "%s's %s vanishes.\n\r", buf, spell);
    else
      return 0;                 /* So we give the default Ok. Message */
  } else {
    if (is_mage && has_detect)
      ch_printf(ch, "%s's %s wavers but holds.\n\r", buf, spell);
    else
      return 0;                 /* The wonderful Failed. Message */
  }
  return 1;
}

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

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

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

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

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

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

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

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

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


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

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

  static const struct syl_type syl_table[] = {
    {" ", " "},
    {"ar", "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"},
    {"polymorph", "iaddahs"},
    {"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->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->name);

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

  return;
}


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

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


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

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

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

  /*
   * check if the expresion is just a number 
   */
  len = strlen(exp);
  if (len == 1 && isalpha(exp[0])) {
    switch (exp[0]) {
    case 'L':
    case 'l':
      return level;
    case 'H':
    case 'h':
      return ch->hit;
    case 'M':
    case 'm':
      return ch->mana;
    case 'V':
    case 'v':
      return ch->max_move;
    case 'S':
    case 's':
      return get_curr_str(ch);
    case 'I':
    case 'i':
      return get_curr_int(ch);
    case 'X':
    case 'x':
      return get_curr_dex(ch);
    case 'C':
    case 'c':
      return get_curr_con(ch);
    case 'U':
    case 'u':
      return get_curr_lck(ch);
    case 'Y':
    case 'y':
      return get_age(ch);
    }
  }

  for (x = 0; x < len; ++x)
    if (!isdigit(exp[x]) && !isspace(exp[x]))
      break;
  if (x == len)
    return atoi(exp);

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

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

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

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

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

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

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

  save = 50 + (victim->level - level - victim->saving_poison_death) * 5;
  save = URANGE(5, save, 95);
  return chance(victim, save);
}

bool
saves_wands(int level, CHAR_DATA * victim)
{
  int save;

  if (IS_SET(victim->immune, RIS_MAGIC))
    return TRUE;

  save = 50 + (victim->level - level - victim->saving_wand) * 5;
  save = URANGE(5, save, 95);
  return chance(victim, save);
}

bool
saves_para_petri(int level, CHAR_DATA * victim)
{
  int save;

  save = 50 + (victim->level - level - victim->saving_para_petri) * 5;
  save = URANGE(5, save, 95);
  return chance(victim, save);
}

bool
saves_breath(int level, CHAR_DATA * victim)
{
  int save;

  save = 50 + (victim->level - level - victim->saving_breath) * 5;
  save = URANGE(5, save, 95);
  return chance(victim, save);
}

bool
saves_spell_staff(int level, CHAR_DATA * victim)
{
  int save;

  if (IS_SET(victim->immune, RIS_MAGIC))
    return TRUE;

  if (IS_NPC(victim) && level > 10)
    level -= 5;
  save = 50 + (victim->level - level - victim->saving_spell_staff) * 5;
  save = URANGE(5, save, 95);
  return chance(victim, save);
}


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

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

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

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




int pAbort;

/*
 * Locate targets.
 */
/* Turn off annoying message and just abort if needed */
bool silence_locate_targets;

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

  *victim = NULL;
  *obj = NULL;

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

  case TAR_IGNORE:
    break;

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

    /*
     * Offensive spells will choose the ch up to 92% of the time
     * * if the nuisance flag is set -- Shaddai 
     */
    if (!IS_NPC(ch) && ch->pcdata->nuisance &&
        ch->pcdata->nuisance->flags > 5
        && number_percent() <
        (((ch->pcdata->nuisance->flags - 5) * 8) +
         ch->pcdata->nuisance->power * 6))
      *victim = ch;

    if (is_safe(ch, *victim, TRUE))
      return &pAbort;

    if (ch == *victim) {
      if (SPELL_FLAG(get_skilltype(sn), SF_NOSELF)) {
        if (!silence_locate_targets)
          send_to_char("You can't cast this on yourself!\n\r", ch);
        return &pAbort;
      }
      if (!silence_locate_targets)
        send_to_char("Cast this on yourself?  Okay...\n\r", ch);
      /*
       * send_to_char( "You can't do that to yourself.\n\r", ch );
       * return &pAbort;
       */
    }

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

        if (get_timer(*victim, TIMER_PKILLED) > 0) {
          if (!silence_locate_targets)
            send_to_char
                ("This player has been killed in the last 5 minutes.\n\r",
                 ch);
          return &pAbort;
        }
        if (xIS_SET(ch->act, PLR_NICE) && ch != *victim) {
          if (!silence_locate_targets)
            send_to_char("You are too nice to attack another player.\n\r",
                         ch);
          return &pAbort;
        }
        if (*victim != ch) {
          if (!silence_locate_targets)
            send_to_char
                ("You really shouldn't do this to another player...\n\r",
                 ch);
          else if (who_fighting(*victim) != ch) {
            /*
             * Only auto-attack those that are hitting you. 
             */
            return &pAbort;
          }
        }
      }

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

    check_illegal_pk(ch, *victim);
    vo = (void *) *victim;
    break;

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

    /*
     * Nuisance flag will pick who you are fighting for defensive
     * * spells up to 36% of the time -- Shaddai
     */

    if (!IS_NPC(ch) && ch->fighting && ch->pcdata->nuisance &&
        ch->pcdata->nuisance->flags > 5
        && number_percent() <
        (((ch->pcdata->nuisance->flags - 5) * 8) +
         6 * ch->pcdata->nuisance->power))
      *victim = who_fighting(ch);

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

    vo = (void *) *victim;
    break;

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

    vo = (void *) ch;
    break;

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

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

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

  return vo;
}



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


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

  retcode = rNONE;

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

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

    if (xIS_SET(ch->in_room->room_flags, ROOM_NO_MAGIC)) {
      set_char_color(AT_MAGIC, ch);
      send_to_char("You failed.\n\r", ch);
      return;
    }

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

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

    /*
     * Regular mortal spell casting 
     */
    if (get_trust(ch) < LEVEL_GOD) {
      if ((sn = find_spell(ch, arg1, TRUE)) < 0
          || (!IS_NPC(ch)
              && ch->level < skill_table[sn]->skill_level[ch->class])) {
        send_to_char("You can't do that.\n\r", ch);
        return;
      }
      if ((skill = get_skilltype(sn)) == NULL) {
        send_to_char("You can't do that right now...\n\r", ch);
        return;
      }
    } else
      /*
       * Godly "spell builder" spell casting with debugging messages
       */
    {
      if ((sn = skill_lookup(arg1)) < 0) {
        send_to_char("We didn't create that yet...\n\r", ch);
        return;
      }
      if (sn >= MAX_SKILL) {
        send_to_char("Hmm... that might hurt.\n\r", ch);
        return;
      }
      if ((skill = get_skilltype(sn)) == NULL) {
        send_to_char("Something is severely wrong with that one...\n\r",
                     ch);
        return;
      }
      if (skill->type != SKILL_SPELL) {
        send_to_char("That isn't a spell.\n\r", ch);
        return;
      }
      if (!skill->spell_fun) {
        send_to_char("We didn't finish that one yet...\n\r", ch);
        return;
      }
    }

    /*
     * Something else removed by Merc     -Thoric
     */
    /*
     * Band-aid alert!  !IS_NPC check -- Blod 
     */
    if (ch->position < skill->minimum_position && !IS_NPC(ch)) {
      switch (ch->position) {
      default:
        send_to_char("You can't concentrate enough.\n\r", ch);
        break;
      case POS_SITTING:
        send_to_char("You can't summon enough energy sitting down.\n\r",
                     ch);
        break;
      case POS_RESTING:
        send_to_char("You're too relaxed to cast that spell.\n\r", ch);
        break;
      case POS_FIGHTING:
        if (skill->minimum_position <= POS_EVASIVE) {
          send_to_char
              ("This fighting style is too demanding for that!\n\r", ch);
        } else {
          send_to_char("No way!  You are still fighting!\n\r", ch);
        }
        break;
      case POS_DEFENSIVE:
        if (skill->minimum_position <= POS_EVASIVE) {
          send_to_char
              ("This fighting style is too demanding for that!\n\r", ch);
        } else {
          send_to_char("No way!  You are still fighting!\n\r", ch);
        }
        break;
      case POS_AGGRESSIVE:
        if (skill->minimum_position <= POS_EVASIVE) {
          send_to_char
              ("This fighting style is too demanding for that!\n\r", ch);
        } else {
          send_to_char("No way!  You are still fighting!\n\r", ch);
        }
        break;
      case POS_BERSERK:
        if (skill->minimum_position <= POS_EVASIVE) {
          send_to_char
              ("This fighting style is too demanding for that!\n\r", ch);
        } else {
          send_to_char("No way!  You are still fighting!\n\r", ch);
        }
        break;
      case POS_EVASIVE:
        send_to_char("No way!  You are still fighting!\n\r", ch);
        break;
      case POS_SLEEPING:
        send_to_char("You dream about great feats of magic.\n\r", ch);
        break;
      }
      return;
    }

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

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

    if (!IS_NPC(ch)             /* fixed by Thoric */
        &&!IS_IMMORTAL(ch)
        && skill->guild != CLASS_NONE && (!ch->pcdata->clan
                                          || skill->guild !=
                                          ch->pcdata->clan->class)) {
      send_to_char
          ("That is only available to members of a certain guild.\n\r",
           ch);
      return;
    }

    /*
     * Mystaric, 980908 - Added checks for spell sector type 
     */
    if (!ch->in_room
        || (skill->spell_sector
            && !IS_SET(skill->spell_sector,
                       (1 << ch->in_room->sector_type)))) {
      send_to_char("You can not cast that here.\n\r", ch);
      return;
    }

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

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

    if (!IS_NPC(ch) && victim && !IS_NPC(victim)
        && CAN_PKILL(victim) && !CAN_PKILL(ch) && !in_arena(ch)
        && !in_arena(victim)) {
      set_char_color(AT_MAGIC, ch);
      send_to_char
          ("The gods will not permit you to cast spells on that character.\n\r",
           ch);
      return;
    }


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

    if (skill->participants <= 1)
      break;

    /*
     * multi-participant spells     -Thoric 
     */
    add_timer(ch, TIMER_DO_FUN, UMIN(skill->beats / 10, 3), do_cast, 1);
    act(AT_MAGIC, "You begin to chant...", ch, NULL, NULL, TO_CHAR);
    act(AT_MAGIC, "$n begins to chant...", ch, NULL, NULL, TO_ROOM);
    sprintf(staticbuf, "%s %s", arg2, target_name);
    ch->alloc_ptr = str_dup(staticbuf);
    ch->tempnum = sn;
    return;
  case SUB_TIMER_DO_ABORT:
    DISPOSE(ch->alloc_ptr);
    if (IS_VALID_SN((sn = ch->tempnum))) {
      if ((skill = get_skilltype(sn)) == NULL) {
        send_to_char("Something went wrong...\n\r", ch);
        bug("do_cast: SUB_TIMER_DO_ABORT: bad sn %d", sn);
        return;
      }
      mana =
          IS_NPC(ch) ? 0 : UMAX(skill->min_mana,
                                100 / (2 + ch->level -
                                       skill->skill_level[ch->class]));
      if (ch->level < LEVEL_IMMORTAL)   /* so imms dont lose mana */
        ch->mana -= mana / 3;
    }
    set_char_color(AT_MAGIC, ch);
    send_to_char("You stop chanting...\n\r", ch);
    /*
     * should add chance of backfire here 
     */
    return;
  case 1:
    sn = ch->tempnum;
    if ((skill = get_skilltype(sn)) == NULL) {
      send_to_char("Something went wrong...\n\r", ch);
      bug("do_cast: substate 1: bad sn %d", sn);
      return;
    }
    if (!ch->alloc_ptr || !IS_VALID_SN(sn) || skill->type != SKILL_SPELL) {
      send_to_char("Something cancels out the spell!\n\r", ch);
      bug("do_cast: ch->alloc_ptr NULL or bad sn (%d)", sn);
      return;
    }
    mana =
        IS_NPC(ch) ? 0 : UMAX(skill->min_mana,
                              100 / (2 + ch->level -
                                     skill->skill_level[ch->class]));
    strcpy(staticbuf, ch->alloc_ptr);
    target_name = one_argument(staticbuf, arg2);
    DISPOSE(ch->alloc_ptr);
    ch->substate = SUB_NONE;
    if (skill->participants > 1) {
      int cnt = 1;
      CHAR_DATA *tmp;
      TIMER *t;

      for (tmp = ch->in_room->first_person; tmp; tmp = tmp->next_in_room)
        if (tmp != ch
            && (t = get_timerptr(tmp, TIMER_DO_FUN)) != NULL
            && t->count >= 1 && t->do_fun == do_cast
            && tmp->tempnum == sn && tmp->alloc_ptr
            && !str_cmp(tmp->alloc_ptr, staticbuf))
          ++cnt;
      if (cnt >= skill->participants) {
        for (tmp = ch->in_room->first_person; tmp; tmp = tmp->next_in_room)
          if (tmp != ch
              && (t = get_timerptr(tmp, TIMER_DO_FUN)) != NULL
              && t->count >= 1 && t->do_fun == do_cast
              && tmp->tempnum == sn && tmp->alloc_ptr
              && !str_cmp(tmp->alloc_ptr, staticbuf)) {
            extract_timer(tmp, t);
            act(AT_MAGIC,
                "Channeling your energy into $n, you help cast the spell!",
                ch, NULL, tmp, TO_VICT);
            act(AT_MAGIC, "$N channels $S energy into you!", ch, NULL, tmp,
                TO_CHAR);
            act(AT_MAGIC, "$N channels $S energy into $n!", ch, NULL, tmp,
                TO_NOTVICT);
            learn_from_success(tmp, sn);
            tmp->mana -= mana;
            tmp->substate = SUB_NONE;
            tmp->tempnum = -1;
            DISPOSE(tmp->alloc_ptr);
          }
        dont_wait = TRUE;
        send_to_char
            ("You concentrate all the energy into a burst of mystical words!\n\r",
             ch);
        vo = locate_targets(ch, arg2, sn, &victim, &obj);
        if (vo == &pAbort)
          return;
      } else {
        set_char_color(AT_MAGIC, ch);
        send_to_char
            ("There was not enough power for the spell to succeed...\n\r",
             ch);
        if (ch->level < LEVEL_IMMORTAL) /* so imms dont lose mana */
          ch->mana -= mana / 2;
        learn_from_failure(ch, sn);
        return;
      }
    }
  }

  /*
   * uttering those magic words unless casting "ventriloquate" 
   */
  if (str_cmp(skill->name, "ventriloquate"))
    say_spell(ch, sn);

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

  /*
   * Getting ready to cast... check for spell components  -Thoric
   */
  if (!process_spell_components(ch, sn)) {
    if (ch->level < LEVEL_IMMORTAL)     /* so imms dont lose mana */
      ch->mana -= mana / 2;
    learn_from_failure(ch, sn);
    return;
  }

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

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

  if (ch->in_room && IS_SET(ch->in_room->area->flags, AFLAG_SPELLLIMIT))
    ch->in_room->area->curr_spell_count++;

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

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


  /*
   * favor adjustments 
   */
  if (victim && victim != ch && !IS_NPC(victim)
      && skill->target == TAR_CHAR_DEFENSIVE)
    adjust_favor(ch, 7, 1);

  if (victim && victim != ch && !IS_NPC(ch)
      && skill->target == TAR_CHAR_DEFENSIVE)
    adjust_favor(victim, 13, 1);

  if (victim && victim != ch && !IS_NPC(ch)
      && skill->target == TAR_CHAR_OFFENSIVE)
    adjust_favor(ch, 4, 1);

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

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

      if (vch == victim) {
        if (vch->master != ch && !vch->fighting)
          retcode = multi_hit(vch, ch, TYPE_UNDEFINED);
        break;
      }
    }
  }

  return;
}


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

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

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

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

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

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

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

  case TAR_CHAR_OFFENSIVE:
    if (victim != ch) {
      if (!victim)
        victim = who_fighting(ch);
      if (!victim || (!IS_NPC(victim) && !in_arena(victim))) {
        send_to_char("You can't do that.\n\r", ch);
        return rNONE;
      }
    }
    if (ch != victim && is_safe(ch, victim, TRUE))
      return rNONE;
    vo = (void *) victim;
    break;

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

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

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

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

  if (retcode == rSPELL_FAILED)
    retcode = rNONE;

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

  if (char_died(ch))
    return rCHAR_DIED;

  if (skill->target == TAR_CHAR_OFFENSIVE && victim != ch
      && !char_died(victim)) {
    CHAR_DATA *vch;
    CHAR_DATA *vch_next;

    for (vch = ch->in_room->first_person; vch; vch = vch_next) {
      vch_next = vch->next_in_room;
      if (victim == vch && !vch->fighting && vch->master != ch) {
        retcode = multi_hit(vch, ch, TYPE_UNDEFINED);
        break;
      }
    }
  }

  return retcode;
}



/*
 * Spell functions.
 */


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

/*
 * saving throw check						-Thoric
 */
bool
check_save(int sn, int level, CHAR_DATA * ch, CHAR_DATA * victim)
{
  SKILLTYPE *skill = get_skilltype(sn);
  bool saved = FALSE;

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

  if (skill->saves)
    switch (skill->saves) {
    case SS_POISON_DEATH:
      saved = saves_poison_death(level, victim);
      break;
    case SS_ROD_WANDS:
      saved = saves_wands(level, victim);
      break;
    case SS_PARA_PETRI:
      saved = saves_para_petri(level, victim);
      break;
    case SS_BREATH:
      saved = saves_breath(level, victim);
      break;
    case SS_SPELL_STAFF:
      saved = saves_spell_staff(level, victim);
      break;
    }
  return saved;
}

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

  if (saved && SPELL_SAVE(skill) == SE_NEGATE) {
    failed_casting(skill, ch, victim, NULL);
    return rSPELL_FAILED;
  }
  if (skill->dice)
    dam = UMAX(0, dice_parse(ch, level, skill->dice));
  else
    dam = dice(1, level / 2);
  if (saved) {
    switch (SPELL_SAVE(skill)) {
    case SE_3QTRDAM:
      dam = (dam * 3) / 4;
      break;
    case SE_HALFDAM:
      dam >>= 1;
      break;
    case SE_QUARTERDAM:
      dam >>= 2;
      break;
    case SE_EIGHTHDAM:
      dam >>= 3;
      break;

    case SE_ABSORB:            /* victim absorbs spell for hp's */
      act(AT_MAGIC, "$N absorbs your $t!", ch, skill->noun_damage, victim,
          TO_CHAR);
      act(AT_MAGIC, "You absorb $N's $t!", victim, skill->noun_damage, ch,
          TO_CHAR);
      act(AT_MAGIC, "$N absorbs $n's $t!", ch, skill->noun_damage, victim,
          TO_NOTVICT);
      victim->hit = URANGE(0, victim->hit + dam, victim->max_hit);
      update_pos(victim);
      if ((dam > 0 && ch->fighting && ch->fighting->who == victim)
          || (dam > 0 && victim->fighting && victim->fighting->who == ch)) {
        int xp = ch->fighting ? ch->fighting->xp : victim->fighting->xp;
        int xp_gain = (int) (xp * dam * 2) / victim->max_hit;

        gain_exp(ch, 0 - xp_gain);
      }
      if (skill->affects)
        retcode = spell_affectchar(sn, level, ch, victim);
      return retcode;

    case SE_REFLECT:           /* reflect the spell to the caster */
      return spell_attack(sn, level, victim, ch);
    }
  }
  dam = get_attmod(ch, victim) * dam;
  retcode = damage(ch, victim, dam, sn);
  if (retcode == rNONE && skill->affects
      && !char_died(ch) && !char_died(victim)
      && (!is_affected(victim, sn) || SPELL_FLAG(skill, SF_ACCUMULATIVE)
          || SPELL_FLAG(skill, SF_RECASTABLE)))
    retcode = spell_affectchar(sn, level, ch, victim);
  return retcode;
}

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

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

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

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

    if (!IS_NPC(vch) && xIS_SET(vch->act, PLR_WIZINVIS)
        && vch->pcdata->wizinvis >= LEVEL_IMMORTAL)
      continue;

    if (vch == ch)
      continue;

    if (is_safe(ch, vch, FALSE))
      continue;

    if (!IS_NPC(ch) && !IS_NPC(vch) && !in_arena(ch)
        && (!IS_PKILL(ch) || !IS_PKILL(vch)))
      continue;

    saved = check_save(sn, level, ch, vch);
    if (saved && SPELL_SAVE(skill) == SE_NEGATE) {
      failed_casting(skill, ch, vch, NULL);
      continue;
    } else if (skill->dice)
      dam = dice_parse(ch, level, skill->dice);
    else
      dam = dice(1, level / 2);
    if (saved) {
      switch (SPELL_SAVE(skill)) {
      case SE_3QTRDAM:
        dam = (dam * 3) / 4;
        break;
      case SE_HALFDAM:
        dam >>= 1;
        break;
      case SE_QUARTERDAM:
        dam >>= 2;
        break;
      case SE_EIGHTHDAM:
        dam >>= 3;
        break;

      case SE_ABSORB:          /* victim absorbs spell for hp's */
        act(AT_MAGIC, "$N absorbs your $t!", ch, skill->noun_damage, vch,
            TO_CHAR);
        act(AT_MAGIC, "You absorb $N's $t!", vch, skill->noun_damage, ch,
            TO_CHAR);
        act(AT_MAGIC, "$N absorbs $n's $t!", ch, skill->noun_damage, vch,
            TO_NOTVICT);
        vch->hit = URANGE(0, vch->hit + dam, vch->max_hit);
        update_pos(vch);
        if ((dam > 0 && ch->fighting && ch->fighting->who == vch)
            || (dam > 0 && vch->fighting && vch->fighting->who == ch)) {
          int xp = ch->fighting ? ch->fighting->xp : vch->fighting->xp;
          int xp_gain = (int) (xp * dam * 2) / vch->max_hit;

          gain_exp(ch, 0 - xp_gain);
        }
        continue;

      case SE_REFLECT:         /* reflect the spell to the caster */
        retcode = spell_attack(sn, level, vch, ch);
        if (char_died(ch)) {
          ch_died = TRUE;
          break;
        }
        continue;
      }
    }
    dam = get_attmod(ch, vch) * dam;
    retcode = damage(ch, vch, dam, sn);
    if (retcode == rNONE && affects && !char_died(ch) && !char_died(vch)
        && (!is_affected(vch, sn) || SPELL_FLAG(skill, SF_ACCUMULATIVE)
            || SPELL_FLAG(skill, SF_RECASTABLE)))
      retcode = spell_affectchar(sn, level, ch, vch);
    if (retcode == rCHAR_DIED || char_died(ch)) {
      ch_died = TRUE;
      break;
    }
  }
  return retcode;
}


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

  if (SPELL_FLAG(skill, SF_RECASTABLE))
    affect_strip(victim, sn);
  for (saf = skill->affects; saf; saf = saf->next) {
    if (saf->location >= REVERSE_APPLY)
      victim = ch;
    else
      victim = (CHAR_DATA *) vo;
    /*
     * Check if char has this bitvector already 
     */
    af.bitvector = meb(saf->bitvector);
    if (saf->bitvector >= 0 && xIS_SET(victim->affected_by, saf->bitvector)
        && !SPELL_FLAG(skill, SF_ACCUMULATIVE))
      continue;
    /*
     * necessary for affect_strip to work properly...
     */
    switch (saf->bitvector) {
    default:
      af.type = sn;
      break;
    case AFF_POISON:
      af.type = gsn_poison;
      chance = ris_save(victim, level, RIS_POISON);
      if (chance == 1000) {
        retcode = rVICT_IMMUNE;
        if (SPELL_FLAG(skill, SF_STOPONFAIL))
          return retcode;
        continue;
      }
      if (saves_poison_death(chance, victim)) {
        if (SPELL_FLAG(skill, SF_STOPONFAIL))
          return retcode;
        continue;
      }
      victim->mental_state = URANGE(30, victim->mental_state + 2, 100);
      break;
    case AFF_BLIND:
      af.type = gsn_blindness;
      break;
    case AFF_CURSE:
      af.type = gsn_curse;
      break;
    case AFF_INVISIBLE:
      af.type = gsn_invis;
      break;
    case AFF_SLEEP:
      af.type = gsn_sleep;
      chance = ris_save(victim, level, RIS_SLEEP);
      if (chance == 1000) {
        retcode = rVICT_IMMUNE;
        if (SPELL_FLAG(skill, SF_STOPONFAIL))
          return retcode;
        continue;
      }
      break;
    case AFF_CHARM:
      af.type = gsn_charm_person;
      chance = ris_save(victim, level, RIS_CHARM);
      if (chance == 1000) {
        retcode = rVICT_IMMUNE;
        if (SPELL_FLAG(skill, SF_STOPONFAIL))
          return retcode;
        continue;
      }
      break;
    case AFF_POSSESS:
      af.type = gsn_possess;
      break;
    }
    af.duration = dice_parse(ch, level, saf->duration);
    af.modifier = dice_parse(ch, level, saf->modifier);
    af.location = saf->location % REVERSE_APPLY;


    if (af.duration == 0) {
      int xp_gain;

      switch (af.location) {
      case APPLY_HIT:
        victim->hit =
            URANGE(0, victim->hit + af.modifier, victim->max_hit);
        update_pos(victim);
        if ((af.modifier > 0 && ch->fighting
             && ch->fighting->who == victim)
            || (af.modifier > 0 && victim->fighting
                && victim->fighting->who == ch)) {
          int xp = ch->fighting ? ch->fighting->xp : victim->fighting->xp;

          xp_gain = (int) (xp * af.modifier * 2) / victim->max_hit;
          gain_exp(ch, 0 - xp_gain);
        }
        if (IS_NPC(victim) && victim->hit <= 0)
          damage(ch, victim, 5, TYPE_UNDEFINED);
        break;
      case APPLY_MANA:
        victim->mana =
            URANGE(0, victim->mana + af.modifier, victim->max_mana);
        update_pos(victim);
        break;
      case APPLY_MOVE:
        victim->max_move = af.modifier;
        update_pos(victim);
        break;
      default:
        affect_modify(victim, &af, TRUE);
        break;
      }
    } else if (SPELL_FLAG(skill, SF_ACCUMULATIVE))
      affect_join(victim, &af);
    else
      affect_to_char(victim, &af);
  }
  update_pos(victim);
  return retcode;
}


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    if (SPELL_CLASS(skill) != SC_NONE) {
      failed_casting(skill, ch, NULL, obj);
      return rNONE;
    }
    switch (SPELL_POWER(skill)) {       /* clone object */
      OBJ_DATA *clone;

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

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

  case SA_CHANGE:
    return rNONE;
  }
  return rNONE;
}

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

  switch (SPELL_POWER(skill)) {
  default:
  case SP_NONE:
    lvl = 10;
    break;
  case SP_MINOR:
    lvl = 0;
    break;
  case SP_GREATER:
    lvl = level / 2;
    break;
  case SP_MAJOR:
    lvl = level;
    break;
  }

  /*
   * Add predetermined objects here
   */
  if (vnum == 0) {
    if (!str_cmp(target_name, "sword"))
      vnum = OBJ_VNUM_SCHOOL_SWORD;
    if (!str_cmp(target_name, "shield"))
      vnum = OBJ_VNUM_SCHOOL_SHIELD;
  }

  if ((oi = get_obj_index(vnum)) == NULL
      || (obj = create_object(oi, lvl)) == NULL) {
    failed_casting(skill, ch, NULL, NULL);
    return rNONE;
  }
  obj->timer = skill->dice ? dice_parse(ch, level, skill->dice) : 0;
  successful_casting(skill, ch, NULL, obj);
  if (CAN_WEAR(obj, ITEM_TAKE))
    obj_to_char(obj, ch);
  else
    obj_to_room(obj, ch->in_room);
  return rNONE;
}

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

  /*
   * set maximum mob level 
   */
  switch (SPELL_POWER(skill)) {
  default:
  case SP_NONE:
    lvl = 20;
    break;
  case SP_MINOR:
    lvl = 5;
    break;
  case SP_GREATER:
    lvl = level / 2;
    break;
  case SP_MAJOR:
    lvl = level;
    break;
  }

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

  if ((mi = get_mob_index(vnum)) == NULL
      || (mob = create_mobile(mi)) == NULL) {
    failed_casting(skill, ch, NULL, NULL);
    return rNONE;
  }
  mob->level =
      UMIN(lvl,
           skill->dice ? dice_parse(ch, level, skill->dice) : mob->level);
  mob->armor = interpolate(mob->level, 100, -100);

  mob->max_hit =
      mob->level * 8 + number_range(mob->level * mob->level / 4,
                                    mob->level * mob->level);
  mob->hit = mob->max_hit;
  mob->gold = 0;
  successful_casting(skill, ch, mob, NULL);
  char_to_room(mob, ch->in_room);
  add_follower(mob, ch);
  af.type = sn;
  af.duration = (number_fuzzy((level + 1) / 3) + 1) * DUR_CONV;
  af.location = 0;
  af.modifier = 0;
  af.bitvector = meb(AFF_CHARM);
  affect_to_char(mob, &af);
  return rNONE;
}

ch_ret ranged_attack(CHAR_DATA *, char *, OBJ_DATA *, OBJ_DATA *, sh_int,
                     sh_int);

/*
 * Generic handler for new "SMAUG" spells			-Thoric
 */
ch_ret
spell_smaug(int sn, int level, CHAR_DATA * ch, void *vo)
{
  CHAR_DATA *victim;
  struct skill_type *skill = get_skilltype(sn);

  /*
   * Put this check in to prevent crashes from this getting a bad skill 
   */

  if (!skill) {
//      bug ( "spell_smaug: Called with a null skill for sn %d", sn );
    return rERROR;
  }

  switch (skill->target) {
  case TAR_IGNORE:

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

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

    /*
     * affect a distant player 
     */
    if (SPELL_FLAG(skill, SF_DISTANT)
        && (victim = get_char_world(ch, target_name))
        && !xIS_SET(victim->in_room->room_flags, ROOM_NO_ASTRAL)
        && SPELL_FLAG(skill, SF_CHARACTER))
      return spell_affect(sn, level, ch, get_char_world(ch, target_name));

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

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

  case TAR_CHAR_OFFENSIVE:

    /*
     * a regular damage inflicting spell attack 
     */
    if ((SPELL_ACTION(skill) == SA_DESTROY
         && SPELL_CLASS(skill) == SC_LIFE)
        || (SPELL_ACTION(skill) == SA_CREATE
            && SPELL_CLASS(skill) == SC_DEATH))
      return spell_attack(sn, level, ch, vo);

    /*
     * a nasty spell affect 
     */
    return spell_affect(sn, level, ch, vo);

  case TAR_CHAR_DEFENSIVE:
  case TAR_CHAR_SELF:
    if (SPELL_FLAG(skill, SF_NOFIGHT) &&
        (ch->position == POS_FIGHTING
         || ch->position == POS_EVASIVE
         || ch->position == POS_DEFENSIVE || ch->position == POS_AGGRESSIVE
         || ch->position == POS_BERSERK)) {
      send_to_char("You can't concentrate enough for that!\n\r", ch);
      return rNONE;
    }

    if (vo && SPELL_ACTION(skill) == SA_DESTROY) {
      CHAR_DATA *victim = (CHAR_DATA *) vo;

      /*
       * cure poison 
       */
      if (SPELL_DAMAGE(skill) == SD_POISON) {
        if (is_affected(victim, gsn_poison)) {
          affect_strip(victim, gsn_poison);
          victim->mental_state = URANGE(-100, victim->mental_state, -10);
          successful_casting(skill, ch, victim, NULL);
          return rNONE;
        }
        failed_casting(skill, ch, victim, NULL);
        return rSPELL_FAILED;
      }
      /*
       * cure blindness 
       */
      if (SPELL_CLASS(skill) == SC_ILLUSION) {
        if (is_affected(victim, gsn_blindness)) {
          affect_strip(victim, gsn_blindness);
          successful_casting(skill, ch, victim, NULL);
          return rNONE;
        }
        failed_casting(skill, ch, victim, NULL);
        return rSPELL_FAILED;
      }
    }
    return spell_affect(sn, level, ch, vo);

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

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

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