alanthia/area/
alanthia/gods/
alanthia/player/
/***************************************************************************
 *  Original Diku Mud copyright (C) 1990, 1991 by Sebastian Hammer,        *
 *  Michael Seifert, Hans Henrik St{rfeldt, Tom Madsen, and Katja Nyboe.   *
 *                                                                         *
 *  Merc Diku Mud improvments copyright (C) 1992, 1993 by Michael          *
 *  Chastain, Michael Quan, and Mitchell Tse.                              *
 *                                                                         *
 *  In order to use any part of this Merc Diku Mud, you must comply with   *
 *  both the original Diku license in 'license.doc' as well the Merc       *
 *  license in 'license.txt'.  In particular, you may not remove either of *
 *  these copyright notices.                                               *
 *                                                                         *
 *  Much time and thought has gone into this software and you are          *
 *  benefitting.  We hope that you share your changes too.  What goes      *
 *  around, comes around.                                                  *
 ***************************************************************************/

/***************************************************************************
*	ROM 2.4 is copyright 1993-1996 Russ Taylor			   *
*	ROM has been brought to you by the ROM consortium		   *
*	    Russ Taylor (rtaylor@efn.org)				   *
*	    Gabrielle Taylor						   *
*	    Brian Moore (zump@rom.org)					   *
*	By using this code, you have agreed to follow the terms of the	   *
*	ROM license, in the file Rom24/doc/rom.license			   *
***************************************************************************/

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

/* command procedures needed */
DECLARE_DO_FUN(do_look);
DECLARE_DO_FUN(do_emote);
DECLARE_DO_FUN(do_say);
DECLARE_DO_FUN(do_at);
DECLARE_DO_FUN(do_flee);


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

/* imported functions */
bool remove_obj args((CHAR_DATA * ch, int iWear, bool fReplace));
void wear_obj args((CHAR_DATA * ch, OBJ_DATA * obj, bool fReplace));

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

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

    return -1;
}

int find_spell(CHAR_DATA * ch, const char *name)
{
  /* finds a spell the character can cast if possible */
  int sn, found = -1;

  if (IS_NPC(ch))
		return skill_lookup(name);

  for(sn = 0; sn < MAX_SKILL; sn++)
	{
		if (skill_table[sn].name == NULL)
	    break;
		if (LOWER(name[0]) == LOWER(skill_table[sn].name[0])
				&& !str_prefix(name, skill_table[sn].name))
		{
	    if (found == -1)
				found = sn;
	    if (ch->level >= skill_table[sn].skill_level[ch->class]
					&& ch->pcdata->learned[sn] > 0)
				return sn;
		}
  }
  return found;
}



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

  if (slot <= 0)
		return -1;

  for (sn = 0; sn < MAX_SKILL; sn++)
	{
		if (slot == skill_table[sn].slot)
	    return sn;
  }

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



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

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

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

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

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

  sprintf(buf2, "$n utters the words, '%s'.", buf);
  sprintf(buf, "$n utters the words, '%s'.", skill_table[sn].name);

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

  return;
}



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

    save =
	((((50 + (victim->level - level)) - (victim->saving_throw)) * 3) /
	 4);
    if (IS_AFFECTED(victim, AFF_BERSERK))
	save += victim->level / 2;

    switch (check_immune(victim, dam_type)) {
    case IS_IMMUNE:
	return TRUE;
    case IS_RESISTANT:
	save += 15;
	break;
    case IS_VULNERABLE:
	save -= 25;
	break;
    }

    if (!IS_NPC(victim) && class_table[victim->class].fMana) {
	save = 9 * save / 10;
    }
    save = URANGE(5, save, 95);
    return number_percent() < save;
}

/* RT save for dispels */

bool saves_dispel(int dis_level, int spell_level, int duration)
{
    int save;

    if (duration == -1)
	spell_level += 5;
    /* very hard to dispel permanent effects */

    save = 50 + (spell_level - dis_level) * 5;
    save = URANGE(5, save, 95);
    return number_percent() < save;
}

/* co-routine for dispel magic and cancellation */

bool check_dispel(int dis_level, CHAR_DATA * victim, int sn)
{
  AFFECT_DATA *af;

  if (is_affected(victim, sn))
	{
		for (af = victim->affected; af != NULL; af = af->next)
		{
	    if (af->type == sn)
			{
				if (!saves_dispel(dis_level, af->level, af->duration))
				{
					affect_strip(victim, sn);
					if (skill_table[sn].msg_off)
					{
						send_to_char(skill_table[sn].msg_off, victim);
						send_to_char("\n\r", victim);
					}
		    return TRUE;
				}
				else
					af->level--;
			}
		}
  }
  return FALSE;
}

/* for finding mana costs -- temporary version */
int mana_cost(CHAR_DATA * ch, int min_mana, int level)
{
    if (ch->level + 2 == level)
	return 1000;
    return UMAX(min_mana, (100 / (2 + ch->level - level)));
}



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

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

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

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

  if (arg1[0] == '\0')
	{
		send_to_char("Cast which what where?\n\r", ch);
		return;
  }
  if ((sn = find_spell(ch, arg1)) < 1	|| skill_table[sn].spell_fun == spell_null
			|| (!IS_NPC(ch) && (ch->level < skill_table[sn].skill_level[ch->class]
			|| ch->pcdata->learned[sn] == 0)))
	{
		send_to_char("You don't know any spells of that name.\n\r", ch);
		return;
  }

  if (ch->position < skill_table[sn].minimum_position)
	{
		send_to_char("You can't concentrate enough.\n\r", ch);
		return;
  }
  if (ch->level + 2 == skill_table[sn].skill_level[ch->class])
		mana = 50;
  else
		mana = UMAX(skill_table[sn].min_mana, 100 / (2 + ch->level -
			   skill_table[sn].skill_level[ch->class]));
  level = ch->level;

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

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

    case TAR_IGNORE:
			break;

    case TAR_CHAR_OFFENSIVE:
			if (arg2[0] == '\0')
			{
				if ((victim = ch->fighting) == NULL)
				{
					send_to_char("Cast the spell on whom?\n\r", ch);
					return;
				}
			}
			else
			{
				if ((victim = get_char_room(ch, target_name)) == NULL)
				{
					send_to_char("They aren't here.\n\r", ch);
					return;
				}
			}
			if (!IS_NPC(ch))
			{
				if (is_safe(ch, victim) && victim != ch)
				{
					send_to_char("Not on that target.\n\r", ch);
					return;
				}
				check_killer(ch, victim);
			}
			if (victim->fighting != NULL && !is_same_group(ch, victim->fighting)) {
	send_to_char("Kill stealing is not permitted.\n\r", ch);
	return;
    }
			if (IS_AFFECTED(ch, AFF_CHARM) && ch->master == victim)
			{
				send_to_char("You can't do that on your own follower.\n\r", ch);
				return;
			}
			vo = (void *) victim;
			target = TARGET_CHAR;
			break;

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

			if (victim->level < (ch->level - 10))
				level = victim->level + (ch->level / 15);

			vo = (void *) victim;
			target = TARGET_CHAR;
			break;

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

    case TAR_OBJ_INV:
			if (arg2[0] == '\0')
			{
				send_to_char("What should the spell be cast upon?\n\r", ch);
				return;
			}
			if ((obj = get_obj_carry(ch, target_name, ch)) == NULL)
			{
				send_to_char("You are not carrying that.\n\r", ch);
				return;
			}
			vo = (void *) obj;
			target = TARGET_OBJ;
			break;

    case TAR_OBJ_CHAR_OFF:
			if (arg2[0] == '\0')
			{
				if ((victim = ch->fighting) == NULL)
				{
					send_to_char("Cast the spell on whom or what?\n\r", ch);
					return;
				}
				target = TARGET_CHAR;
			}
			else if ((victim = get_char_room(ch, target_name)) != NULL)
			{
				target = TARGET_CHAR;
			}
			if (target == TARGET_CHAR)
			{	/* check the sanity of the attack */
				if (victim->fighting != NULL && !is_same_group(ch, victim->fighting)) {
	send_to_char("Kill stealing is not permitted.\n\r", ch);
	return;
    }
				if (is_safe_spell(ch, victim, FALSE) && victim != ch)
				{
					send_to_char("Not on that target.\n\r", ch);
					return;
				}
				if (IS_AFFECTED(ch, AFF_CHARM) && ch->master == victim)
				{
					send_to_char("You can't do that on your own follower.\n\r", ch);
					return;
				}
				if (!IS_NPC(ch))
					check_killer(ch, victim);

			vo = (void *) victim;
			}
			else if ((obj = get_obj_here(ch, target_name)) != NULL)
			{
				vo = (void *) obj;
				target = TARGET_OBJ;
			}
			else
			{
				send_to_char("You don't see that here.\n\r", ch);
	    return;
			}
			break;

    case TAR_OBJ_CHAR_DEF:
			if (arg2[0] == '\0')
			{
				vo = (void *) ch;
				target = TARGET_CHAR;
			}
			else if ((victim = get_char_room(ch, target_name)) != NULL)
			{
				vo = (void *) victim;
				target = TARGET_CHAR;
				if (victim->level < (ch->level - 10))
					level = victim->level + (ch->level / 10);
			}
			else if ((obj = get_obj_carry(ch, target_name, ch)) != NULL)
			{
				vo = (void *) obj;
				target = TARGET_OBJ;
			}
			else
			{
				send_to_char("You don't see that here.\n\r", ch);
				return;
			}
			break;
	}//end switch

  if (ch->mana < mana)
	{
		send_to_char("You don't have enough mana.\n\r", ch);
		return;
  }
  if (str_cmp(skill_table[sn].name, "ventriloquate"))
		say_spell(ch, sn);

  if (ch->level < 301)
		WAIT_STATE(ch, skill_table[sn].beats);

  if (number_percent() > get_skill(ch, sn))
	{
		send_to_char("You lost your concentration.\n\r", ch);
		check_improve(ch, sn, FALSE, 1);
		ch->mana -= mana / 2;
  }
	else
	{
		ch->mana -= mana;

		if (level < 0)
				level = 1;
		if ((level > ch->level) || IS_IMMORTAL(ch) || IS_NPC(ch))
				level = ch->level;

		if (class_table[ch->class].fMana)
				(*skill_table[sn].spell_fun) (sn, level, ch, vo, target);
		else
				(*skill_table[sn].spell_fun) (sn, 3 * level / 4, ch, vo, target);
		check_improve(ch, sn, TRUE, 1);
  }

  if (level < ch->level)
		act("Your spell's energy leaks from the body of $N.", ch, NULL, victim, TO_CHAR);

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


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



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

    if (sn <= 0)
	return;

    if (sn >= MAX_SKILL || skill_table[sn].spell_fun == 0) {
	bug("Obj_cast_spell: bad sn %d.", sn);
	return;
    }
    switch (skill_table[sn].target) {
    default:
	bug("Obj_cast_spell: bad target for sn %d.", sn);
	return;

    case TAR_IGNORE:
	vo = NULL;

	/* fix to make it impossible to cast multible spells on one self */
	if (is_affected(victim, sn)) {
	    if (victim == ch) {
		send_to_char("This person is already under that effect",
			     ch);
	    } else {
		act("$N is already under that effect.", ch, NULL, victim,
		    TO_CHAR);
	    }
	    return;
	}

	break;

    case TAR_CHAR_OFFENSIVE:
	if (victim == NULL)
	    victim = ch->fighting;
	if (victim == NULL) {
	    send_to_char("You can't do that.\n\r", ch);
	    return;
	}
	if (is_safe(ch, victim) && ch != victim) {
	    send_to_char("Something isn't right...\n\r", ch);
	    return;
	}
	vo = (void *) victim;
	target = TARGET_CHAR;

	/* fix to make it impossible to cast multible spells on one self */
	if (is_affected(victim, sn)) {
	    if (victim == ch) {
		send_to_char("This person is already under that effect",
			     ch);
	    } else {
		act("$N is already under that effect.", ch, NULL, victim,
		    TO_CHAR);
	    }
	    return;
	}

	break;

    case TAR_CHAR_DEFENSIVE:
    case TAR_CHAR_SELF:
	if (victim == NULL)
	    victim = ch;
	vo = (void *) victim;
	target = TARGET_CHAR;


	/* fix to make it impossible to cast multible spells on one self */
	if (is_affected(victim, sn)) {
	    if (victim == ch) {
		send_to_char("This person is already under that effect",
			     ch);
	    } else {
		act("$N is already under that effect.", ch, NULL, victim,
		    TO_CHAR);
	    }
	    return;
	}

	break;

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

	/* fix to make it impossible to cast multible spells on one self */
	if (is_affected(victim, sn)) {
	    if (victim == ch) {
		send_to_char("This person is already under that effect",
			     ch);
	    } else {
		act("$N is already under that effect.", ch, NULL, victim,
		    TO_CHAR);
	    }
	    return;
	}

	break;

    case TAR_OBJ_CHAR_OFF:
	if (victim == NULL && obj == NULL) {
	    if (ch->fighting != NULL)
		victim = ch->fighting;
	    else {
		send_to_char("You can't do that.\n\r", ch);
		return;
	    }
	}
	if (victim != NULL) {
	    if (is_safe_spell(ch, victim, FALSE) && ch != victim) {
		send_to_char("Somehting isn't right...\n\r", ch);
		return;
	    }
	    vo = (void *) victim;
	    target = TARGET_CHAR;
	} else {
	    vo = (void *) obj;
	    target = TARGET_OBJ;
	}

	/* fix to make it impossible to cast multible spells on one self */
	if (is_affected(victim, sn)) {
	    if (victim == ch) {
		send_to_char("This person is already under that effect",
			     ch);
	    } else {
		act("$N is already under that effect.", ch, NULL, victim,
		    TO_CHAR);
	    }
	    return;
	}

	break;


    case TAR_OBJ_CHAR_DEF:
	if (victim == NULL && obj == NULL) {
	    vo = (void *) ch;
	    target = TARGET_CHAR;
	} else if (victim != NULL) {
	    vo = (void *) victim;
	    target = TARGET_CHAR;
	} else {
	    vo = (void *) obj;
	    target = TARGET_OBJ;
	}

	/* fix to make it impossible to cast multible spells on one self */
	if (is_affected(victim, sn)) {
	    if (victim == ch) {
		send_to_char("This person is already under that effect",
			     ch);
	    } else {
		act("$N is already under that effect.", ch, NULL, victim,
		    TO_CHAR);
	    }
	    return;
	}

	break;
    }


    /* fix to make it impossible to cast multible spells on one self */
    if (is_affected(victim, sn)) {
	if (victim == ch) {
	    send_to_char("This person is already under that effect", ch);
	} else {
	    act("$N is already under that effect.", ch, NULL, victim,
		TO_CHAR);
	}
	return;
    }

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



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

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



bool generic_creature_damage_spell(int sn, int level, CHAR_DATA * ch,
				   void *vo, int target, int damtype,
				   int numdice)
{

    CHAR_DATA *victim = (CHAR_DATA *) vo;
    int dam;

    dam = dice(level, numdice);

    if (saves_spell(level, victim, damtype))
	dam /= 2;

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


bool modify_generic_character_affect(int sn, int level, CHAR_DATA * ch,
				     void *vo, int target, char *name_text,
				     int af_duration, int af_modifier,
				     int af_location)
{
    CHAR_DATA *victim = (CHAR_DATA *) vo;
    AFFECT_DATA af;
    char buf[MAX_INPUT_LENGTH];

    if (is_affected(victim, sn)) {
	if (victim == ch) {
	    sprintf(buf, "You are already under the effects of %s.\n\r",
		    name_text);
	    send_to_char(buf, ch);
	    return FALSE;
	} else {
	    sprintf(buf, "$N is already under the effects of %s.\n\r",
		    name_text);
	    act(buf, ch, NULL, victim, TO_CHAR);
	    return FALSE;
	}
    }

    af.where = TO_AFFECTS;
    af.type = sn;
    af.level = level;
    af.duration = af_duration;
    af.modifier = af_modifier;
    af.location = af_location;
    af.bitvector = 0;
    affect_to_char(victim, &af);

    return TRUE;
}