rasputin/bin/
rasputin/cnf/
rasputin/doc/cwg/
rasputin/lib/
rasputin/lib/etc/
rasputin/lib/etc/boards/
rasputin/lib/house/
rasputin/lib/misc/
rasputin/lib/plralias/A-E/
rasputin/lib/plralias/F-J/
rasputin/lib/plralias/K-O/
rasputin/lib/plralias/P-T/
rasputin/lib/plralias/U-Z/
rasputin/lib/plralias/ZZZ/
rasputin/lib/plrfiles/
rasputin/lib/plrfiles/A-E/
rasputin/lib/plrfiles/F-J/
rasputin/lib/plrfiles/K-O/
rasputin/lib/plrfiles/P-T/
rasputin/lib/plrfiles/U-Z/
rasputin/lib/plrfiles/ZZZ/
rasputin/lib/plrobjs/
rasputin/lib/plrobjs/A-E/
rasputin/lib/plrobjs/F-J/
rasputin/lib/plrobjs/K-O/
rasputin/lib/plrobjs/P-T/
rasputin/lib/plrobjs/U-Z/
rasputin/lib/plrobjs/ZZZ/
rasputin/lib/plrvars/A-E/
rasputin/lib/plrvars/F-J/
rasputin/lib/plrvars/K-O/
rasputin/lib/plrvars/P-T/
rasputin/lib/plrvars/U-Z/
rasputin/lib/plrvars/ZZZ/
rasputin/lib/world/gld/
rasputin/lib/world/trg/
rasputin/src/
rasputin/src/doc/
/* ************************************************************************
*   File: spells.c                                      Part of CircleMUD *
*  Usage: Implementation of "manual spells".  Circle 2.2 spell compat.    *
*                                                                         *
*  All rights reserved.  See license.doc for complete information.        *
*                                                                         *
*  Copyright (C) 1993, 94 by the Trustees of the Johns Hopkins University *
*  CircleMUD is based on DikuMUD, Copyright (C) 1990, 1991.               *
************************************************************************ */


#include "conf.h"
#include "sysdep.h"

CVSHEADER("$CVSHeader: cwg/rasputin/src/spells.c,v 1.6 2005/01/03 00:19:50 fnord Exp $");

#include "structs.h"
#include "utils.h"
#include "comm.h"
#include "spells.h"
#include "handler.h"
#include "db.h"
#include "constants.h"
#include "interpreter.h"
#include "dg_scripts.h"
#include "feats.h"

/* external variables */
extern room_rnum r_mortal_start_room;
extern int mini_mud;
extern obj_vnum portal_object;

/* external functions */
void clearMemory(struct char_data *ch);
void weight_change_object(struct obj_data *obj, int weight);
void name_to_drinkcon(struct obj_data *obj, int type);
void name_from_drinkcon(struct obj_data *obj);
int compute_armor_class(struct char_data *ch, struct char_data *att);

/*
 * Special spells appear below.
 */

ASPELL(spell_create_water)
{
  int water;

  if (ch == NULL || obj == NULL)
    return;
  /* level = MAX(MIN(level, LVL_IMPL), 1);	 - not used */

  if (GET_OBJ_TYPE(obj) == ITEM_DRINKCON) {
    if ((GET_OBJ_VAL(obj, VAL_DRINKCON_LIQUID) != LIQ_WATER) && (GET_OBJ_VAL(obj, VAL_DRINKCON_HOWFULL) != 0)) {
      name_from_drinkcon(obj);
      GET_OBJ_VAL(obj, VAL_DRINKCON_LIQUID) = LIQ_SLIME;
      name_to_drinkcon(obj, LIQ_SLIME);
    } else {
      water = MAX(GET_OBJ_VAL(obj, VAL_DRINKCON_CAPACITY) - GET_OBJ_VAL(obj, VAL_DRINKCON_HOWFULL), 0);
      if (water > 0) {
	if (GET_OBJ_VAL(obj, VAL_DRINKCON_HOWFULL) >= 0)
	  name_from_drinkcon(obj);
	GET_OBJ_VAL(obj, VAL_DRINKCON_LIQUID) = LIQ_WATER;
	GET_OBJ_VAL(obj, VAL_DRINKCON_HOWFULL) += water;
	name_to_drinkcon(obj, LIQ_WATER);
	weight_change_object(obj, water);
	act("$p is filled.", FALSE, ch, obj, 0, TO_CHAR);
      }
    }
  }
}


ASPELL(spell_recall)
{
  if (victim == NULL || IS_NPC(victim))
    return;

  act("$n disappears.", TRUE, victim, 0, 0, TO_ROOM);
  char_from_room(victim);
  char_to_room(victim, real_room(CONFIG_MORTAL_START));
  act("$n appears in the middle of the room.", TRUE, victim, 0, 0, TO_ROOM);
  look_at_room(IN_ROOM(victim), victim, 0);
  entry_memory_mtrigger(victim);
  greet_mtrigger(victim, -1);
  greet_memory_mtrigger(victim);
}


ASPELL(spell_teleport)
{
  room_rnum to_room;

  if (victim == NULL || IS_NPC(victim))
    return;

  do {
    to_room = rand_number(0, top_of_world);
  } while (ROOM_FLAGGED(to_room, ROOM_PRIVATE | ROOM_DEATH | ROOM_GODROOM));

  act("$n slowly fades out of existence and is gone.",
      FALSE, victim, 0, 0, TO_ROOM);
  char_from_room(victim);
  char_to_room(victim, to_room);
  act("$n slowly fades into existence.", FALSE, victim, 0, 0, TO_ROOM);
  look_at_room(IN_ROOM(victim), victim, 0);
  entry_memory_mtrigger(victim);
  greet_mtrigger(victim, -1);
  greet_memory_mtrigger(victim);
  
}

#define SUMMON_FAIL "You failed.\r\n"

ASPELL(spell_summon)
{
  if (ch == NULL || victim == NULL)
    return;

  if (GET_LEVEL(victim) > level + 3) {
    send_to_char(ch, "%s", SUMMON_FAIL);
    return;
  }

  if (!CONFIG_PK_ALLOWED) {
    if (MOB_FLAGGED(victim, MOB_AGGRESSIVE)) {
      act("As the words escape your lips and $N travels\r\n"
	  "through time and space towards you, you realize that $E is\r\n"
	  "aggressive and might harm you, so you wisely send $M back.",
	  FALSE, ch, 0, victim, TO_CHAR);
      return;
    }
    if (!IS_NPC(victim) && !PRF_FLAGGED(victim, PRF_SUMMONABLE) &&
	!PLR_FLAGGED(victim, PLR_KILLER)) {
      send_to_char(victim, "%s just tried to summon you to: %s.\r\n"
	      "%s failed because you have summon protection on.\r\n"
	      "Type NOSUMMON to allow other players to summon you.\r\n",
	      GET_NAME(ch), world[IN_ROOM(ch)].name,
	      (ch->sex == SEX_MALE) ? "He" : "She");

      send_to_char(ch, "You failed because %s has summon protection on.\r\n", GET_NAME(victim));
      mudlog(BRF, ADMLVL_IMMORT, TRUE, "%s failed summoning %s to %s.", GET_NAME(ch), GET_NAME(victim), world[IN_ROOM(ch)].name);
      return;
    }
  }

  if (MOB_FLAGGED(victim, MOB_NOSUMMON) ||
      (IS_NPC(victim) && mag_newsaves(ch, victim, SPELL_SUMMON, level, GET_INT(ch)))) {
    send_to_char(ch, "%s", SUMMON_FAIL);
    return;
  }

  act("$n disappears suddenly.", TRUE, victim, 0, 0, TO_ROOM);

  char_from_room(victim);
  char_to_room(victim, IN_ROOM(ch));

  act("$n arrives suddenly.", TRUE, victim, 0, 0, TO_ROOM);
  act("$n has summoned you!", FALSE, ch, 0, victim, TO_VICT);
  look_at_room(IN_ROOM(victim), victim, 0);
  entry_memory_mtrigger(victim);
  greet_mtrigger(victim, -1);
  greet_memory_mtrigger(victim);

}



ASPELL(spell_locate_object)
{
  struct obj_data *i;
  char name[MAX_INPUT_LENGTH];
  int j;

  /*
   * FIXME: This is broken.  The spell parser routines took the argument
   * the player gave to the spell and located an object with that keyword.
   * Since we're passed the object and not the keyword we can only guess
   * at what the player originally meant to search for. -gg
   */
  if (!obj) {
    send_to_char(ch, "You sense nothing.\r\n");
    return;
  }
  
  strlcpy(name, fname(obj->name), sizeof(name));
  j = level / 2;

  for (i = object_list; i && (j > 0); i = i->next) {
    if (!isname(name, i->name))
      continue;

    send_to_char(ch, "%c%s", UPPER(*i->short_description), (i->short_description)+1);

    if (i->carried_by)
      send_to_char(ch, " is being carried by %s.\r\n", PERS(i->carried_by, ch));
    else if (IN_ROOM(i) != NOWHERE)
      send_to_char(ch, " is in %s.\r\n", world[IN_ROOM(i)].name);
    else if (i->in_obj)
      send_to_char(ch, " is in %s.\r\n", i->in_obj->short_description);
    else if (i->worn_by)
      send_to_char(ch, " is being worn by %s.\r\n", PERS(i->worn_by, ch));
    else
      send_to_char(ch, "'s location is uncertain.\r\n");

    j--;
  }

  if (j == level / 2)
    send_to_char(ch, "You sense nothing.\r\n");
}



ASPELL(spell_charm)
{
  struct affected_type af;

  if (victim == NULL || ch == NULL)
    return;

  if (victim == ch)
    send_to_char(ch, "You like yourself even better!\r\n");
  else if (!IS_NPC(victim) && !PRF_FLAGGED(victim, PRF_SUMMONABLE))
    send_to_char(ch, "You fail because SUMMON protection is on!\r\n");
  else if (AFF_FLAGGED(victim, AFF_SANCTUARY))
    send_to_char(ch, "Your victim is protected by sanctuary!\r\n");
  else if (MOB_FLAGGED(victim, MOB_NOCHARM))
    send_to_char(ch, "Your victim resists!\r\n");
  else if (AFF_FLAGGED(ch, AFF_CHARM))
    send_to_char(ch, "You can't have any followers of your own!\r\n");
  else if (AFF_FLAGGED(victim, AFF_CHARM) || level < GET_LEVEL(victim))
    send_to_char(ch, "You fail.\r\n");
  /* player charming another player - no legal reason for this */
  else if (!CONFIG_PK_ALLOWED && !IS_NPC(victim))
    send_to_char(ch, "You fail - shouldn't be doing it anyway.\r\n");
  else if (IS_ELF(victim) && rand_number(1, 100) <= 90)
    send_to_char(ch, "Your victim resists!\r\n");
  else if (circle_follow(victim, ch))
    send_to_char(ch, "Sorry, following in circles can not be allowed.\r\n");
  else if (mag_newsaves(ch, victim, SPELL_CHARM, level, GET_INT(ch)))
    send_to_char(ch, "Your victim resists!\r\n");
  else {
    if (victim->master)
      stop_follower(victim);

    add_follower(victim, ch);
    victim->master_id = GET_IDNUM(ch);

    af.type = SPELL_CHARM;
    af.duration = 24 * 2;
    if (GET_CHA(ch))
      af.duration *= GET_CHA(ch);
    if (GET_INT(victim))
      af.duration /= GET_INT(victim);
    af.modifier = 0;
    af.location = 0;
    af.bitvector = AFF_CHARM;
    affect_to_char(victim, &af);

    act("Isn't $n just such a nice fellow?", FALSE, ch, 0, victim, TO_VICT);
    if (IS_NPC(victim))
      REMOVE_BIT_AR(MOB_FLAGS(victim), MOB_SPEC);
  }
}



ASPELL(spell_identify)
{
  int i, found;
  size_t len;

  if (obj) {
    char bitbuf[MAX_STRING_LENGTH];
    char buf2[MAX_STRING_LENGTH];

    sprinttype(GET_OBJ_TYPE(obj), item_types, bitbuf, sizeof(bitbuf));
    send_to_char(ch, "You feel informed:\r\nObject '%s', Item type: %s\r\n", obj->short_description, bitbuf);

    if (GET_OBJ_PERM(obj)) {
      sprintbitarray(GET_OBJ_PERM(obj), affected_bits, AF_ARRAY_MAX, bitbuf);
      send_to_char(ch, "Item will give you following abilities:  %s\r\n", bitbuf);
    }

    sprintbitarray(GET_OBJ_EXTRA(obj), extra_bits, AF_ARRAY_MAX, bitbuf);
    send_to_char(ch, "Item is: %s\r\n", bitbuf);

    send_to_char(ch, "Weight: %d, Value: %d, Rent: %d, Min Level: %d\r\n", GET_OBJ_WEIGHT(obj), GET_OBJ_COST(obj), GET_OBJ_RENT(obj), GET_OBJ_LEVEL(obj));

    switch (GET_OBJ_TYPE(obj)) {
    case ITEM_SCROLL:
    case ITEM_POTION:
      len = i = 0;

      if (GET_OBJ_VAL(obj, VAL_SCROLL_SPELL1) >= 1) {
	i = snprintf(bitbuf + len, sizeof(bitbuf) - len, " %s", skill_name(GET_OBJ_VAL(obj, VAL_SCROLL_SPELL1)));
        if (i >= 0)
          len += i;
      }

      if (GET_OBJ_VAL(obj, VAL_SCROLL_SPELL2) >= 1 && len < sizeof(bitbuf)) {
	i = snprintf(bitbuf + len, sizeof(bitbuf) - len, " %s", skill_name(GET_OBJ_VAL(obj, VAL_SCROLL_SPELL2)));
        if (i >= 0)
          len += i;
      }

      if (GET_OBJ_VAL(obj, VAL_SCROLL_SPELL3) >= 1 && len < sizeof(bitbuf)) {
	i = snprintf(bitbuf + len, sizeof(bitbuf) - len, " %s", skill_name(GET_OBJ_VAL(obj, VAL_SCROLL_SPELL3)));
        if (i >= 0)
          len += i;
      }

      send_to_char(ch, "This %s casts: %s\r\n", item_types[(int) GET_OBJ_TYPE(obj)], bitbuf);
      break;
    case ITEM_WAND:
    case ITEM_STAFF:
      send_to_char(ch, "This %s casts: %s\r\nIt has %d maximum charge%s and %d remaining.\r\n",
		item_types[(int) GET_OBJ_TYPE(obj)], skill_name(GET_OBJ_VAL(obj, VAL_WAND_SPELL)),
		GET_OBJ_VAL(obj, VAL_WAND_MAXCHARGES), GET_OBJ_VAL(obj, VAL_WAND_MAXCHARGES) == 1 ? "" : "s", GET_OBJ_VAL(obj, VAL_WAND_CHARGES));
      break;
    case ITEM_WEAPON:
      send_to_char(ch, "Damage Dice is '%dD%d' for an average per-round damage of %.1f.\r\n",
		GET_OBJ_VAL(obj, VAL_WEAPON_DAMDICE), GET_OBJ_VAL(obj, VAL_WEAPON_DAMSIZE), ((GET_OBJ_VAL(obj, VAL_WEAPON_DAMSIZE) + 1) / 2.0) * GET_OBJ_VAL(obj, VAL_WEAPON_DAMDICE));
      break;
    case ITEM_ARMOR:
      send_to_char(ch, "AC-apply is %.1f\r\n", ((float)GET_OBJ_VAL(obj, VAL_ARMOR_APPLYAC)) / 10);
      break;
    }
    found = FALSE;
    for (i = 0; i < MAX_OBJ_AFFECT; i++) {
      if ((obj->affected[i].location != APPLY_NONE) &&
	  (obj->affected[i].modifier != 0)) {
	if (!found) {
	  send_to_char(ch, "Can affect you as :\r\n");
	  found = TRUE;
	}
	sprinttype(obj->affected[i].location, apply_types, bitbuf, sizeof(bitbuf));
        switch (obj->affected[i].location) {
        case APPLY_FEAT:
          snprintf(buf2, sizeof(buf2), " (%s)", feat_list[obj->affected[i].specific].name);
          break;
        case APPLY_SKILL:
          snprintf(buf2, sizeof(buf2), " (%s)", spell_info[obj->affected[i].specific].name);
          break;
        default:
          buf2[0] = 0;
          break;
        }
	send_to_char(ch, "   Affects: %s%s By %d\r\n", bitbuf, buf2,
                     obj->affected[i].modifier);
      }
    }
  }
}



/*
 * Cannot use this spell on an equipped object or it will mess up the
 * wielding character's hit/dam totals.
 */
ASPELL(spell_enchant_weapon)
{
  int i;

  if (ch == NULL || obj == NULL)
    return;

  /* Either already enchanted or not a weapon. */
  if (GET_OBJ_TYPE(obj) != ITEM_WEAPON || OBJ_FLAGGED(obj, ITEM_MAGIC))
    return;

  /* Make sure no other affections. */
  for (i = 0; i < MAX_OBJ_AFFECT; i++)
    if (obj->affected[i].location != APPLY_NONE)
      return;

  SET_BIT_AR(GET_OBJ_EXTRA(obj), ITEM_MAGIC);

  for (i = 0; i < MAX_OBJ_AFFECT; i++) {
    if (obj->affected[i].location == APPLY_NONE) {
      obj->affected[i].location = APPLY_ACCURACY;
      obj->affected[i].modifier = 1 + (level >= 18);
      break;
    }
  }

  for (i = 0; i < MAX_OBJ_AFFECT; i++) {
    if (obj->affected[i].location == APPLY_NONE) {
      obj->affected[i].location = APPLY_DAMAGE;
      obj->affected[i].modifier = 1 + (level >= 20);
      break;
    }
  }

  if (IS_GOOD(ch)) {
    SET_BIT_AR(GET_OBJ_EXTRA(obj), ITEM_ANTI_EVIL);
    act("$p glows blue.", FALSE, ch, obj, 0, TO_CHAR);
  } else if (IS_EVIL(ch)) {
    SET_BIT_AR(GET_OBJ_EXTRA(obj), ITEM_ANTI_GOOD);
    act("$p glows red.", FALSE, ch, obj, 0, TO_CHAR);
  } else
    act("$p glows yellow.", FALSE, ch, obj, 0, TO_CHAR);
}


ASPELL(spell_detect_poison)
{
  if (victim) {
    if (victim == ch) {
      if (AFF_FLAGGED(victim, AFF_POISON))
        send_to_char(ch, "You can sense poison in your blood.\r\n");
      else
        send_to_char(ch, "You feel healthy.\r\n");
    } else {
      if (AFF_FLAGGED(victim, AFF_POISON))
        act("You sense that $E is poisoned.", FALSE, ch, 0, victim, TO_CHAR);
      else
        act("You sense that $E is healthy.", FALSE, ch, 0, victim, TO_CHAR);
    }
  }

  if (obj) {
    switch (GET_OBJ_TYPE(obj)) {
    case ITEM_DRINKCON:
    case ITEM_FOUNTAIN:
    case ITEM_FOOD:
      if (GET_OBJ_VAL(obj, VAL_FOOD_POISON))
	act("You sense that $p has been contaminated.",FALSE,ch,obj,0,TO_CHAR);
      else
	act("You sense that $p is safe for consumption.", FALSE, ch, obj, 0,
	    TO_CHAR);
      break;
    default:
      send_to_char(ch, "You sense that it should not be consumed.\r\n");
    }
  }
}

ASPELL(spell_portal)
{
  struct obj_data *portal, *tportal;

  if (ch == NULL || victim == NULL)
    return;

  /* create the portal */
  portal = read_object(portal_object, VIRTUAL);
  GET_OBJ_VAL(portal, VAL_PORTAL_DEST) = GET_ROOM_VNUM(IN_ROOM(victim));
  GET_OBJ_VAL(portal, VAL_PORTAL_HEALTH) = 100;
  GET_OBJ_VAL(portal, VAL_PORTAL_MAXHEALTH) = 100;
  GET_OBJ_TIMER(portal) = (int) (level / 10);
  add_unique_id(portal);
  obj_to_room(portal, IN_ROOM(ch));
  act("$n opens a portal in thin air.",
       TRUE, ch, 0, 0, TO_ROOM);
  act("You open a portal out of thin air.",
       TRUE, ch, 0, 0, TO_CHAR);
  /* create the portal at the other end */
  tportal = read_object(portal_object, VIRTUAL);
  GET_OBJ_VAL(tportal, VAL_PORTAL_DEST) = GET_ROOM_VNUM(IN_ROOM(ch));
  GET_OBJ_VAL(tportal, VAL_PORTAL_HEALTH) = 100;
  GET_OBJ_VAL(tportal, VAL_PORTAL_MAXHEALTH) = 100;
  GET_OBJ_TIMER(tportal) = (int) (level / 10);
  add_unique_id(portal);
  obj_to_room(tportal, IN_ROOM(victim));
  act("A shimmering portal appears out of thin air.",
       TRUE, victim, 0, 0, TO_ROOM);
  act("A shimmering portal opens here for you.",
       TRUE, victim, 0, 0, TO_CHAR);
}


ASPELL(art_abundant_step)
{
  int steps, i, rep, max;
  room_rnum r, nextroom;
  char buf[MAX_INPUT_LENGTH];
  const char *p;

  steps = 0;
  r = IN_ROOM(ch);
  p = arg;
  max = 10 + GET_CLASS_RANKS(ch, CLASS_MONK) / 2;

  while (p && *p && !isdigit(*p) && !isalpha(*p)) p++;

  if (!p || !*p) {
    send_to_char(ch, "You must give directions from your current location. Examples:\r\n"
                 "  w w nw n e\r\n"
                 "  2w nw n e\r\n");
    return;
  }

  while (*p) {
    while (*p && !isdigit(*p) && !isalpha(*p)) p++;
    if (isdigit(*p)) {
      rep = atoi(p);
      while (isdigit(*p)) p++;
    } else
      rep = 1;
    if (isalpha(*p)) {
      for (i = 0; isalpha(*p); i++, p++) buf[i] = LOWER(*p);
      buf[i] = 0;
      for (i = 1; complete_cmd_info[i].command_pointer == do_move && strcmp(complete_cmd_info[i].sort_as, buf); i++);
      if (complete_cmd_info[i].command_pointer == do_move) {
        i = complete_cmd_info[i].subcmd - 1;
      } else
        i = -1;
    }
    if (i > -1)
      while (rep--) {
        if (++steps > max)
          break;
        nextroom = W_EXIT(r, i)->to_room;
        if (nextroom == NOWHERE)
          break;
        r = nextroom;
      }
    if (steps > max)
      break;
  }
  send_to_char(ch, "Your will bends reality as you travel through the ethereal plane.\r\n");
  act("$n is suddenly absent.", TRUE, ch, 0, 0, TO_ROOM);

  char_from_room(ch);
  char_to_room(ch, r);

  act("$n is suddenly present.", TRUE, ch, 0, 0, TO_ROOM);

  look_at_room(IN_ROOM(ch), ch, 0);

  return;
}


int roll_skill(const struct char_data *ch, int snum)
{
  int roll, skval, i;
  skval = GET_SKILL(ch, snum);
  if (snum < 0 || snum >= SKILL_TABLE_SIZE)
    return 0;
  if (IS_SET(spell_info[snum].skilltype, SKTYPE_SPELL)) {
    /*
     * There's no real roll for a spell to succeed, so instead we will
     * return the spell resistance roll; the defender must have resistance
     * higher than this roll to avoid it. Most spells should also have some
     * kind of save called after roll_skill.
     */
      for (i = 0, roll = 0; i < NUM_CLASSES; i++)
        if (GET_CLASS_RANKS(ch, i) &&
            (spell_info[snum].min_level[i] < GET_CLASS_RANKS(ch, i)))
          roll += GET_CLASS_RANKS(ch, i); /* Caster level for eligable classes */
      return roll + rand_number(1, 20);
  } else if (IS_SET(spell_info[snum].skilltype, SKTYPE_SKILL)) {
      if (!skval && IS_SET(spell_info[snum].flags, SKFLAG_NEEDTRAIN)) {
        return -1;
      } else {
        roll = skval;
        if (IS_SET(spell_info[snum].flags, SKFLAG_STRMOD))
          roll += ability_mod_value(GET_STR(ch));
        if (IS_SET(spell_info[snum].flags, SKFLAG_DEXMOD))
          roll += dex_mod_capped(ch);
        if (IS_SET(spell_info[snum].flags, SKFLAG_CONMOD))
          roll += ability_mod_value(GET_CON(ch));
        if (IS_SET(spell_info[snum].flags, SKFLAG_INTMOD))
          roll += ability_mod_value(GET_INT(ch));
        if (IS_SET(spell_info[snum].flags, SKFLAG_WISMOD))
          roll += ability_mod_value(GET_WIS(ch));
        if (IS_SET(spell_info[snum].flags, SKFLAG_CHAMOD))
          roll += ability_mod_value(GET_CHA(ch));
        if (IS_SET(spell_info[snum].flags, SKFLAG_ARMORALL))
          roll -= GET_ARMORCHECKALL(ch);
        else if (IS_SET(spell_info[snum].flags, SKFLAG_ARMORBAD))
          roll -= GET_ARMORCHECK(ch);
	return roll + rand_number(1, 20);
      }
  } else {
      log("Trying to roll uncategorized skill/spell #%d for %s", snum, GET_NAME(ch));
      return 0;
  }
}

int roll_resisted(const struct char_data *actor, int sact, const struct char_data *resistor, int sres)
{
  return roll_skill(actor, sact) >= roll_skill(resistor, sres);
}