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

/*
 MurkMUD++ - A Windows compatible, C++ compatible Merc 2.2 Mud.

 \author Jon A. Lambert
 \date 01/02/2007
 \version 1.5
 \remarks
  This source code copyright (C) 2005, 2006, 2007 by Jon A. Lambert
  All rights reserved.

  Use governed by the MurkMUD++ public license found in license.murk++
*/

#include "os.hpp"
#include "config.hpp"
#include "globals.hpp"
#include "utils.hpp"

#include "object.hpp"
#include "room.hpp"
#include "affect.hpp"
#include "objproto.hpp"
#include "mobproto.hpp"
#include "world.hpp"

// temp externs
extern void damage(Character *ch, Character *victim, int dam, int dt);
extern std::string extra_bit_name (int extra_flags);
extern std::string affect_loc_name (int location);
extern bool is_same_group (Character * ach, Character * bch);

/*
 * Spell functions.
 */
void Character::spell_acid_blast (int sn, int lvl, void *vo)
{
  Character *victim = (Character *) vo;
  int dam;

  dam = dice (lvl, 6);
  if (victim->saves_spell (lvl))
    dam /= 2;
  damage (this, victim, dam, sn);
  return;
}

void Character::spell_armor (int sn, int lvl, void *vo)
{
  Character *victim = (Character *) vo;
  Affect af;

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

void Character::spell_bless (int sn, int lvl, void *vo)
{
  Character *victim = (Character *) vo;
  Affect af;

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

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

void Character::spell_blindness (int sn, int lvl, void *vo)
{
  Character *victim = (Character *) vo;
  Affect af;

  if (victim->is_affected (AFF_BLIND) || victim->saves_spell (lvl))
    return;

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

void Character::spell_burning_hands (int sn, int lvl, void *vo)
{
  Character *victim = (Character *) vo;
  static const sh_int 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
  };

  lvl = std::min (lvl, (int) (sizeof (dam_each) / sizeof (dam_each[0]) - 1));
  lvl = std::max (0, lvl);
  int dam = number_range (dam_each[lvl] / 2, dam_each[lvl] * 2);
  if (victim->saves_spell (lvl))
    dam /= 2;
  damage (this, victim, dam, sn);
  return;
}

void Character::spell_call_lightning (int sn, int lvl, void *vo)
{
  Character *vch;

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

  if (!g_world->is_raining()) {
    send_to_char ("You need bad weather.\r\n");
    return;
  }

  int dam = dice (lvl / 2, 8);

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

  CharIter c, next;
  for (c = char_list.begin(); c != char_list.end(); c = next) {
    vch = *c;
    next = ++c;
    if (vch->in_room == NULL)
      continue;
    if (vch->in_room == in_room) {
      if (vch != this && (is_npc () ? !vch->is_npc () : vch->is_npc ()))
        damage (this, vch, vch->saves_spell (lvl) ? dam / 2 : dam, sn);
      continue;
    }

    if (vch->in_room->area == in_room->area && vch->is_outside()
      && vch->is_awake ())
      vch->send_to_char ("Lightning flashes in the sky.\r\n");
  }

  return;
}

void Character::spell_cause_light (int sn, int lvl, void *vo)
{
  damage (this, (Character *) vo, dice (1, 8) + lvl / 3, sn);
  return;
}

void Character::spell_cause_critical (int sn, int lvl, void *vo)
{
  damage (this, (Character *) vo, dice (3, 8) + lvl - 6, sn);
  return;
}

void Character::spell_cause_serious (int sn, int lvl, void *vo)
{
  damage (this, (Character *) vo, dice (2, 8) + lvl / 2, sn);
  return;
}

void Character::spell_change_sex (int sn, int lvl, void *vo)
{
  Character *victim = (Character *) vo;
  Affect af;

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

void Character::spell_charm_person (int sn, int lvl, void *vo)
{
  Character *victim = (Character *) vo;
  Affect af;

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

  if (victim->is_affected (AFF_CHARM)
    || is_affected (AFF_CHARM)
    || lvl < victim->level || victim->saves_spell (lvl))
    return;

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

void Character::spell_chill_touch (int sn, int lvl, void *vo)
{
  Character *victim = (Character *) vo;
  static const sh_int 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 af;

  lvl = std::min (lvl, (int) (sizeof (dam_each) / sizeof (dam_each[0]) - 1));
  lvl = std::max (0, lvl);
  int dam = number_range (dam_each[lvl] / 2, dam_each[lvl] * 2);
  if (!victim->saves_spell (lvl)) {
    af.type = sn;
    af.duration = 6;
    af.location = APPLY_STR;
    af.modifier = -1;
    af.bitvector = 0;
    victim->affect_join (&af);
  } else {
    dam /= 2;
  }

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

void Character::spell_colour_spray (int sn, int lvl, void *vo)
{
  Character *victim = (Character *) vo;
  static const sh_int 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
  };

  lvl = std::min (lvl, (int) (sizeof (dam_each) / sizeof (dam_each[0]) - 1));
  lvl = std::max (0, lvl);
  int dam = number_range (dam_each[lvl] / 2, dam_each[lvl] * 2);
  if (victim->saves_spell (lvl))
    dam /= 2;

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

void Character::spell_continual_light (int sn, int lvl, void *vo)
{
  Object *light;

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

void Character::spell_control_weather (int sn, int lvl, void *vo)
{
  int change = dice (lvl / 3, 4);
  if (!str_cmp (target_name, "better"))
    change *= 1;
  else if (!str_cmp (target_name, "worse"))
    change *= -1;
  else
    send_to_char ("Do you want it to get better or worse?\r\n");

  g_world->change_weather(change);
  send_to_char ("Ok.\r\n");
  return;
}

void Character::spell_create_food (int sn, int lvl, void *vo)
{
  Object* mushroom = get_obj_index(OBJ_VNUM_MUSHROOM)->create_object(0);
  mushroom->value[0] = 5 + lvl;
  mushroom->obj_to_room (in_room);
  act ("$p suddenly appears.", mushroom, NULL, TO_ROOM);
  act ("$p suddenly appears.", mushroom, NULL, TO_CHAR);
  return;
}

void Character::spell_create_spring (int sn, int lvl, void *vo)
{
  Object* spring = get_obj_index(OBJ_VNUM_SPRING)->create_object(0);
  spring->timer = lvl;
  spring->obj_to_room (in_room);
  act ("$p flows from the ground.", spring, NULL, TO_ROOM);
  act ("$p flows from the ground.", spring, NULL, TO_CHAR);
  return;
}

void Character::spell_create_water (int sn, int lvl, void *vo)
{
  Object *obj = (Object *) vo;

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

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

  int water = std::min (lvl * (g_world->is_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)) {
      obj->name = obj->name + " water";
    }
    act ("$p is filled.", obj, NULL, TO_CHAR);
  }

  return;
}

void Character::spell_cure_blindness (int sn, int lvl, void *vo)
{
  Character *victim = (Character *) vo;
  if (!victim->has_affect(skill_lookup("blindness")))
    return;
  victim->affect_strip (skill_lookup("blindness"));
  victim->send_to_char ("Your vision returns!\r\n");
  if (this != victim)
    send_to_char ("Ok.\r\n");
  return;
}

void Character::spell_cure_critical (int sn, int lvl, void *vo)
{
  Character *victim = (Character *) vo;

  int heal = dice (3, 8) + lvl - 6;
  victim->hit = std::min (victim->hit + heal, victim->max_hit);
  victim->update_pos();
  victim->send_to_char ("You feel better!\r\n");
  if (this != victim)
    send_to_char ("Ok.\r\n");
  return;
}

void Character::spell_cure_light (int sn, int lvl, void *vo)
{
  Character *victim = (Character *) vo;

  int heal = dice (1, 8) + lvl / 3;
  victim->hit = std::min (victim->hit + heal, victim->max_hit);
  victim->update_pos();
  victim->send_to_char ("You feel better!\r\n");
  if (this != victim)
    send_to_char ("Ok.\r\n");
  return;
}

void Character::spell_cure_poison (int sn, int lvl, void *vo)
{
  Character *victim = (Character *) vo;
  int pois = skill_lookup("poison");

  if (victim->has_affect(pois)) {
    victim->affect_strip (pois);
    act ("$N looks better.", NULL, victim, TO_NOTVICT);
    victim->send_to_char ("A warm feeling runs through your body.\r\n");
    send_to_char ("Ok.\r\n");
  }
  return;
}

void Character::spell_cure_serious (int sn, int lvl, void *vo)
{
  Character *victim = (Character *) vo;

  int heal = dice (2, 8) + lvl / 2;
  victim->hit = std::min (victim->hit + heal, victim->max_hit);
  victim->update_pos();
  victim->send_to_char ("You feel better!\r\n");
  if (this != victim)
    send_to_char ("Ok.\r\n");
  return;
}

void Character::spell_curse (int sn, int lvl, void *vo)
{
  Character *victim = (Character *) vo;
  Affect af;

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

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

  victim->send_to_char ("You feel unclean.\r\n");
  if (this != victim)
    send_to_char ("Ok.\r\n");
  return;
}

void Character::spell_detect_evil (int sn, int lvl, void *vo)
{
  Character *victim = (Character *) vo;
  Affect af;

  if (victim->is_affected (AFF_DETECT_EVIL))
    return;
  af.type = sn;
  af.duration = lvl;
  af.modifier = 0;
  af.location = APPLY_NONE;
  af.bitvector = AFF_DETECT_EVIL;
  victim->affect_to_char(&af);
  victim->send_to_char ("Your eyes tingle.\r\n");
  if (this != victim)
    send_to_char ("Ok.\r\n");
  return;
}

void Character::spell_detect_hidden (int sn, int lvl, void *vo)
{
  Character *victim = (Character *) vo;
  Affect af;

  if (victim->is_affected (AFF_DETECT_HIDDEN))
    return;
  af.type = sn;
  af.duration = lvl;
  af.location = APPLY_NONE;
  af.modifier = 0;
  af.bitvector = AFF_DETECT_HIDDEN;
  victim->affect_to_char(&af);
  victim->send_to_char ("Your awareness improves.\r\n");
  if (this != victim)
    send_to_char ("Ok.\r\n");
  return;
}

void Character::spell_detect_invis (int sn, int lvl, void *vo)
{
  Character *victim = (Character *) vo;
  Affect af;

  if (victim->is_affected (AFF_DETECT_INVIS))
    return;
  af.type = sn;
  af.duration = lvl;
  af.modifier = 0;
  af.location = APPLY_NONE;
  af.bitvector = AFF_DETECT_INVIS;
  victim->affect_to_char(&af);
  victim->send_to_char ("Your eyes tingle.\r\n");
  if (this != victim)
    send_to_char ("Ok.\r\n");
  return;
}

void Character::spell_detect_magic (int sn, int lvl, void *vo)
{
  Character *victim = (Character *) vo;
  Affect af;

  if (victim->is_affected (AFF_DETECT_MAGIC))
    return;
  af.type = sn;
  af.duration = lvl;
  af.modifier = 0;
  af.location = APPLY_NONE;
  af.bitvector = AFF_DETECT_MAGIC;
  victim->affect_to_char(&af);
  victim->send_to_char ("Your eyes tingle.\r\n");
  if (this != victim)
    send_to_char ("Ok.\r\n");
  return;
}

void Character::spell_detect_poison (int sn, int lvl, void *vo)
{
  Object *obj = (Object *) 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");
    else
      send_to_char ("It looks very delicious.\r\n");
  } else {
    send_to_char ("It doesn't look poisoned.\r\n");
  }

  return;
}

void Character::spell_dispel_magic (int sn, int lvl, void *vo)
{
  send_to_char ("Sorry but this spell has been disabled.\r\n");
  return;
}

void Character::spell_dispel_evil (int sn, int lvl, void *vo)
{
  Character *victim = (Character *) vo;

  if (!is_npc () && is_evil ())
    victim = this;

  if (victim->is_good ()) {
    act ("God protects $N.", NULL, victim, TO_ROOM);
    return;
  }

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

  int dam = dice (lvl, 4);
  if (victim->saves_spell (lvl))
    dam /= 2;
  damage (this, victim, dam, sn);
  return;
}

void Character::spell_earthquake (int sn, int lvl, void *vo)
{
  send_to_char ("The earth trembles beneath your feet!\r\n");
  act ("$n makes the earth tremble and shiver.", NULL, NULL, TO_ROOM);

  CharIter c, next;
  for (c = char_list.begin(); c != char_list.end(); c = next) {
    Character* vch = *c;
    next = ++c;
    if (vch->in_room == NULL)
      continue;
    if (vch->in_room == in_room) {
      if (vch != this && (is_npc () ? !vch->is_npc () : vch->is_npc ()))
        damage (this, vch, lvl + dice (2, 8), sn);
      continue;
    }

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

  return;
}

void Character::spell_enchant_weapon (int sn, int lvl, void *vo)
{
  Object *obj = (Object *) vo;
  Affect *paf;

  if (obj->item_type != ITEM_WEAPON || obj->is_obj_stat(ITEM_MAGIC)
    || !obj->affected.empty())
    return;

  paf = new Affect();

  paf->type = sn;
  paf->duration = -1;
  paf->location = APPLY_HITROLL;
  paf->modifier = lvl / 5;
  paf->bitvector = 0;
  obj->affected.push_back(paf);

  paf = new Affect();

  paf->type = -1;
  paf->duration = -1;
  paf->location = APPLY_DAMROLL;
  paf->modifier = lvl / 10;
  paf->bitvector = 0;
  obj->affected.push_back(paf);
  obj->level = number_fuzzy (level - 5);

  if (is_good ()) {
    SET_BIT (obj->extra_flags, ITEM_ANTI_EVIL);
    act ("$p glows blue.", obj, NULL, TO_CHAR);
  } else if (is_evil ()) {
    SET_BIT (obj->extra_flags, ITEM_ANTI_GOOD);
    act ("$p glows red.", 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.", obj, NULL, TO_CHAR);
  }

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

/*
 * Drain XP, MANA, HP.
 * Caster gains HP.
 */
void Character::spell_energy_drain (int sn, int lvl, void *vo)
{
  Character *victim = (Character *) vo;
  int dam;

  if (victim->saves_spell (lvl))
    return;

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

  damage (this, victim, dam, sn);

  return;
}

void Character::spell_fireball (int sn, int lvl, void *vo)
{
  Character *victim = (Character *) vo;
  static const sh_int 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
  };

  lvl = std::min (lvl, (int) (sizeof (dam_each) / sizeof (dam_each[0]) - 1));
  lvl = std::max (0, lvl);
  int dam = number_range (dam_each[lvl] / 2, dam_each[lvl] * 2);
  if (victim->saves_spell (lvl))
    dam /= 2;
  damage (this, victim, dam, sn);
  return;
}

void Character::spell_flamestrike (int sn, int lvl, void *vo)
{
  Character *victim = (Character *) vo;

  int dam = dice (6, lvl);
  if (victim->saves_spell (lvl))
    dam /= 2;
  damage (this, victim, dam, sn);
  return;
}

void Character::spell_faerie_fire (int sn, int lvl, void *vo)
{
  Character *victim = (Character *) vo;
  Affect af;

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

void Character::spell_faerie_fog (int sn, int lvl, void *vo)
{
  act ("$n conjures a cloud of purple smoke.", NULL, NULL, TO_ROOM);
  send_to_char ("You conjure a cloud of purple smoke.\r\n");

  CharIter ich;
  for (ich = in_room->people.begin(); ich != in_room->people.end(); ich++) {
    if (*ich == this || (*ich)->saves_spell (lvl))
      continue;

    (*ich)->affect_strip (skill_lookup("invis"));
    (*ich)->affect_strip (skill_lookup("mass invis"));
    (*ich)->affect_strip (skill_lookup("sneak"));
    REMOVE_BIT ((*ich)->affected_by, AFF_HIDE);
    REMOVE_BIT ((*ich)->affected_by, AFF_INVISIBLE);
    REMOVE_BIT ((*ich)->affected_by, AFF_SNEAK);
    (*ich)->act ("$n is revealed!", NULL, NULL, TO_ROOM);
    (*ich)->send_to_char ("You are revealed!\r\n");
  }

  return;
}

void Character::spell_fly (int sn, int lvl, void *vo)
{
  Character *victim = (Character *) vo;
  Affect af;

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

void Character::spell_gate (int sn, int lvl, void *vo)
{
  get_mob_index(MOB_VNUM_VAMPIRE)->create_mobile()->char_to_room(in_room);
  return;
}

/*
 * Spell for mega1.are from Glop/Erkenbrand.
 */
void Character::spell_general_purpose (int sn, int lvl, void *vo)
{
  Character *victim = (Character *) vo;

  int dam = number_range (25, 100);
  if (victim->saves_spell (lvl))
    dam /= 2;
  damage (this, victim, dam, sn);
  return;
}

void Character::spell_giant_strength (int sn, int lvl, void *vo)
{
  Character *victim = (Character *) vo;
  Affect af;

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

void Character::spell_harm (int sn, int lvl, void *vo)
{
  Character *victim = (Character *) vo;
  int dam;

  dam = std::max (20, victim->hit - dice (1, 4));
  if (victim->saves_spell (lvl))
    dam = std::min (50, dam / 4);
  dam = std::min (100, dam);
  damage (this, victim, dam, sn);
  return;
}

void Character::spell_heal (int sn, int lvl, void *vo)
{
  Character *victim = (Character *) vo;
  victim->hit = std::min (victim->hit + 100, victim->max_hit);
  victim->update_pos();
  victim->send_to_char ("A warm feeling fills your body.\r\n");
  if (this != victim)
    send_to_char ("Ok.\r\n");
  return;
}

/*
 * Spell for mega1.are from Glop/Erkenbrand.
 */
void Character::spell_high_explosive (int sn, int lvl, void *vo)
{
  Character *victim = (Character *) vo;

  int dam = number_range (30, 120);
  if (victim->saves_spell (lvl))
    dam /= 2;
  damage (this, victim, dam, sn);
  return;
}

void Character::spell_identify (int sn, int lvl, void *vo)
{
  Object *obj = (Object *) vo;
  char buf[MAX_STRING_LENGTH];

  snprintf (buf, sizeof buf,
    "Object '%s' is type %s, extra flags %s.\r\nWeight is %d, value is %d, lvl is %d.\r\n",
    obj->name.c_str(),
    obj->item_type_name().c_str(),
    extra_bit_name (obj->extra_flags).c_str(), obj->weight, obj->cost, obj->level);
  send_to_char (buf);

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

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

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

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

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

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

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

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

  case ITEM_WEAPON:
    snprintf (buf, sizeof 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);
    break;

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

  AffIter af;
  for (af = obj->pIndexData->affected.begin(); af != obj->pIndexData->affected.end(); af++) {
    if ((*af)->location != APPLY_NONE && (*af)->modifier != 0) {
      snprintf (buf, sizeof buf, "Affects %s by %d.\r\n",
        affect_loc_name ((*af)->location).c_str(), (*af)->modifier);
      send_to_char (buf);
    }
  }

  for (af = obj->affected.begin(); af != obj->affected.end(); af++) {
    if ((*af)->location != APPLY_NONE && (*af)->modifier != 0) {
      snprintf (buf, sizeof buf, "Affects %s by %d.\r\n",
        affect_loc_name ((*af)->location).c_str(), (*af)->modifier);
      send_to_char (buf);
    }
  }

  return;
}

void Character::spell_infravision (int sn, int lvl, void *vo)
{
  Character *victim = (Character *) vo;
  Affect af;

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

void Character::spell_invis (int sn, int lvl, void *vo)
{
  Character *victim = (Character *) vo;
  Affect af;

  if (victim->is_affected (AFF_INVISIBLE))
    return;

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

void Character::spell_know_alignment (int sn, int lvl, void *vo)
{
  Character *victim = (Character *) vo;
  const char *msg;

  int 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, NULL, victim, TO_CHAR);
  return;
}

void Character::spell_lightning_bolt (int sn, int lvl, void *vo)
{
  Character *victim = (Character *) vo;
  static const sh_int 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
  };

  lvl = std::min (lvl, (int) (sizeof (dam_each) / sizeof (dam_each[0]) - 1));
  lvl = std::max (0, lvl);
  int dam = number_range (dam_each[lvl] / 2, dam_each[lvl] * 2);
  if (victim->saves_spell (lvl))
    dam /= 2;
  damage (this, victim, dam, sn);
  return;
}

void Character::spell_locate_object (int sn, int lvl, void *vo)
{
  std::string buf;
  bool found = false;
  ObjIter o;
  for (o = object_list.begin(); o != object_list.end(); o++) {
    Object *in_obj;

    if (!can_see_obj(*o) || !is_name (target_name, (*o)->name))
      continue;

    found = true;

    for (in_obj = *o; in_obj->in_obj != NULL; in_obj = in_obj->in_obj) ;

    if (in_obj->carried_by != NULL) {
      buf = (*o)->short_descr + " carried by " + in_obj->carried_by->describe_to(this) + "\r\n";
    } else {
      buf = (*o)->short_descr + " in " +
        (in_obj->in_room == NULL ? "somewhere" : in_obj->in_room->name.c_str()) +
        ".\r\n";
    }

    buf[0] = toupper (buf[0]);
    send_to_char (buf);
  }

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

  return;
}

void Character::spell_magic_missile (int sn, int lvl, void *vo)
{
  Character *victim = (Character *) vo;
  static const sh_int 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
  };

  lvl = std::min (lvl, (int) (sizeof (dam_each) / sizeof (dam_each[0]) - 1));
  lvl = std::max (0, lvl);
  int dam = number_range (dam_each[lvl] / 2, dam_each[lvl] * 2);
  if (victim->saves_spell (lvl))
    dam /= 2;
  damage (this, victim, dam, sn);
  return;
}

void Character::spell_mass_invis (int sn, int lvl, void *vo)
{
  Affect af;

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

  return;
}

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

void Character::spell_pass_door (int sn, int lvl, void *vo)
{
  Character *victim = (Character *) vo;
  Affect af;

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

void Character::spell_poison (int sn, int lvl, void *vo)
{
  Character *victim = (Character *) vo;
  Affect af;

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

void Character::spell_protection (int sn, int lvl, void *vo)
{
  Character *victim = (Character *) vo;
  Affect af;

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

void Character::spell_refresh (int sn, int lvl, void *vo)
{
  Character *victim = (Character *) vo;
  victim->move = std::min (victim->move + lvl, victim->max_move);
  victim->send_to_char ("You feel less tired.\r\n");
  if (this != victim)
    send_to_char ("Ok.\r\n");
  return;
}

void Character::spell_remove_curse (int sn, int lvl, void *vo)
{
  Character *victim = (Character *) vo;
  if (victim->has_affect(skill_lookup("curse"))) {
    victim->affect_strip (skill_lookup("curse"));
    victim->send_to_char ("You feel better.\r\n");
    if (this != victim)
      send_to_char ("Ok.\r\n");
  }

  return;
}

void Character::spell_sanctuary (int sn, int lvl, void *vo)
{
  Character *victim = (Character *) vo;
  Affect af;

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

void Character::spell_shield (int sn, int lvl, void *vo)
{
  Character *victim = (Character *) vo;
  Affect af;

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

void Character::spell_shocking_grasp (int sn, int lvl, void *vo)
{
  Character *victim = (Character *) 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
  };

  lvl = std::min (lvl, (int) (sizeof (dam_each) / sizeof (dam_each[0]) - 1));
  lvl = std::max (0, lvl);
  int dam = number_range (dam_each[lvl] / 2, dam_each[lvl] * 2);
  if (victim->saves_spell (lvl))
    dam /= 2;
  damage (this, victim, dam, sn);
  return;
}

void Character::spell_sleep (int sn, int lvl, void *vo)
{
  Character *victim = (Character *) vo;
  Affect af;

  if (victim->is_affected (AFF_SLEEP)
    || lvl < victim->level || victim->saves_spell (lvl))
    return;

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

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

  return;
}

void Character::spell_stone_skin (int sn, int lvl, void *vo)
{
  Character *victim = (Character *) vo;
  Affect af;

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

void Character::spell_summon (int sn, int lvl, void *vo)
{
  Character *victim;

  if ((victim = get_char_world (target_name)) == NULL
    || victim == this
    || 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 >= lvl + 3
    || victim->fighting != NULL
    || victim->in_room->area != in_room->area
    || (victim->is_npc () && victim->saves_spell (lvl))) {
    send_to_char ("You failed.\r\n");
    return;
  }

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

void Character::spell_teleport (int sn, int lvl, void *vo)
{
  Character *victim = (Character *) vo;

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

  Room *pRoomIndex;
  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;
  }

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

void Character::spell_ventriloquate (int sn, int lvl, void *vo)
{
  std::string speaker;

  target_name = one_argument (target_name, speaker);

  char buf1[MAX_STRING_LENGTH];
  char buf2[MAX_STRING_LENGTH];
  snprintf (buf1, sizeof buf1, "%s says '%s'.\r\n", speaker.c_str(), target_name.c_str());
  snprintf (buf2, sizeof buf2, "Someone makes %s say '%s'.\r\n", speaker.c_str(), target_name.c_str());
  buf1[0] = toupper (buf1[0]);

  CharIter vch;
  for (vch = in_room->people.begin(); vch != in_room->people.end(); vch++) {
    if (!is_name (speaker, (*vch)->name))
      (*vch)->send_to_char ((*vch)->saves_spell (lvl) ? buf2 : buf1);
  }

  return;
}

void Character::spell_weaken (int sn, int lvl, void *vo)
{
  Character *victim = (Character *) vo;
  Affect af;

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

/*
 * This is for muds that _want_ scrolls of recall.
 * Ick.
 */
void Character::spell_word_of_recall (int sn, int lvl, void *vo)
{
  ((Character *) vo)->do_recall ("");
  return;
}

/*
 * NPC spells.
 */
void Character::spell_acid_breath (int sn, int lvl, void *vo)
{
  Character *victim = (Character *) vo;

  if (number_percent () < 2 * lvl && !victim->saves_spell (lvl)) {
    Object *obj_lose;
    ObjIter o, onext;
    for (o = victim->carrying.begin(); o != victim->carrying.end(); o = onext) {
      obj_lose = *o;
      onext = ++o;
      int iWear;

      if (number_percent() <= 75)
        continue;

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

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

  int hpch = std::max (10, hit);
  int dam = number_range (hpch / 16 + 1, hpch / 8);
  if (victim->saves_spell (lvl))
    dam /= 2;
  damage (this, victim, dam, sn);
  return;
}

void Character::spell_fire_breath (int sn, int lvl, void *vo)
{
  Character *victim = (Character *) vo;

  if (number_percent () < 2 * lvl && !victim->saves_spell (lvl)) {
    Object *obj_lose;
    ObjIter o, onext;
    for (o = victim->carrying.begin(); o != victim->carrying.end(); o = onext) {
      obj_lose = *o;
      onext = ++o;
      const char *msg;

      if (number_percent() <= 75)
        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;
      }

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

  int hpch = std::max (10, hit);
  int dam = number_range (hpch / 16 + 1, hpch / 8);
  if (victim->saves_spell (lvl))
    dam /= 2;
  damage (this, victim, dam, sn);
  return;
}

void Character::spell_frost_breath (int sn, int lvl, void *vo)
{
  Character *victim = (Character *) vo;

  if (number_percent () < 2 * lvl && !victim->saves_spell (lvl)) {
    Object *obj_lose;
    ObjIter o, onext;
    for (o = victim->carrying.begin(); o != victim->carrying.end(); o = onext) {
      obj_lose = *o;
      onext = ++o;
      const char *msg;

      if (number_percent() <= 75)
        continue;

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

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

  int hpch = std::max (10, hit);
  int dam = number_range (hpch / 16 + 1, hpch / 8);
  if (victim->saves_spell (lvl))
    dam /= 2;
  damage (this, victim, dam, sn);
  return;
}

void Character::spell_gas_breath (int sn, int lvl, void *vo)
{
  Character *vch;

  CharIter rch, next;
  for (rch = in_room->people.begin(); rch != in_room->people.end(); rch = next) {
    vch = *rch;
    next = ++rch;
    if (is_npc () ? !vch->is_npc () : vch->is_npc ()) {
      int hpch = std::max (10, hit);
      int dam = number_range (hpch / 16 + 1, hpch / 8);
      if (vch->saves_spell (lvl))
        dam /= 2;
      damage (this, vch, dam, sn);
    }
  }
  return;
}

void Character::spell_lightning_breath (int sn, int lvl, void *vo)
{
  Character *victim = (Character *) vo;

  int hpch = std::max (10, hit);
  int dam = number_range (hpch / 16 + 1, hpch / 8);
  if (victim->saves_spell (lvl))
    dam /= 2;
  damage (this, victim, dam, sn);
  return;
}