LOP/
LOP/area/
LOP/boards/
LOP/channels/
LOP/clans/
LOP/classes/
LOP/color/
LOP/councils/
LOP/deity/
LOP/races/
LOP/src/specials/
/*****************************************************************************
 * DikuMUD (C) 1990, 1991 by:                                                *
 *   Sebastian Hammer, Michael Seifert, Hans Henrik Staefeldt, Tom Madsen,   *
 *   and Katja Nyboe.                                                        *
 *---------------------------------------------------------------------------*
 * MERC 2.1 (C) 1992, 1993 by:                                               *
 *   Michael Chastain, Michael Quan, and Mitchell Tse.                       *
 *---------------------------------------------------------------------------*
 * SMAUG 1.4 (C) 1994, 1995, 1996, 1998 by: Derek Snider.                    *
 *   Team: Thoric, Altrag, Blodkai, Narn, Haus, Scryn, Rennard, Swordbearer, *
 *         gorog, Grishnakh, Nivek, Tricops, and Fireblade.                  *
 *---------------------------------------------------------------------------*
 * SMAUG 1.7 FUSS by: Samson and others of the SMAUG community.              *
 *                    Their contributions are greatly appreciated.           *
 *---------------------------------------------------------------------------*
 * LoP (C) 2006, 2007, 2008 by: the LoP team.                                *
 *---------------------------------------------------------------------------*
 *		        Main structure manipulation module		     *
 *****************************************************************************/

#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <sys/stat.h>
#include <unistd.h>
#include "h/mud.h"

extern int top_exit;
extern int top_ed;
extern int top_affect;
extern int cur_qchars;
extern CHAR_DATA *gch_prev;
extern OBJ_DATA *gobj_prev;
extern REL_DATA *first_relation, *last_relation;

CHAR_DATA *cur_char;
bool cur_char_died;
ch_ret global_retcode;

int cur_obj;
bool cur_obj_extracted;
obj_ret global_objcode;

void update_room_reset( CHAR_DATA *ch );

OBJ_DATA *group_object( OBJ_DATA *obj1, OBJ_DATA *obj2 );
bool in_magic_container( OBJ_DATA *obj );
void delete_reset( RESET_DATA *pReset );
bool stop_obj_fishing( OBJ_DATA *obj );
bool stop_fishing( CHAR_DATA *ch );

void remove_file( char *filename )
{
   unlink( filename );
}

bool check_parse_name( char *name, bool newchar );
bool valid_pfile( char *filename )
{
   char buf[MSL];
   struct stat fst;

   if( !filename || filename[0] == '\0' )
      return false;

   snprintf( buf, sizeof( buf ), "%s%c/%s", PLAYER_DIR, tolower( filename[0] ), capitalize( filename ) );
   if( stat( buf, &fst ) == -1 || !check_parse_name( capitalize( filename ), false ) )
      return false;
   return true;
}

char *shorttime( time_t updated )
{
   static char buf[MIL];
   struct tm *time;

   if( !updated )
      return "";
   time = localtime( &updated );
   snprintf( buf, sizeof( buf ), "%2d/%2d/%4.4d", ( time->tm_mon + 1 ), time->tm_mday, ( time->tm_year + 1900 ) );
   return buf;
}

char *distime( time_t updated )
{
   char wday_name[7][4] =
   {
      "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
   };
   char month_name[12][4] =
   {
      "Jan", "Feb", "Mar", "Apr", "May", "Jun",
      "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
   };
   static char buf[MIL];
   struct tm *time;
   bool am = true;
   int hour;

   if( !updated )
      return "";
   time = localtime( &updated );
   hour = time->tm_hour;
   if( hour >= 12 )
   {
      am = false;
      hour -= 12;
   }
   if( hour == 0 )
      hour = 12;
   snprintf( buf, sizeof( buf ), "%3.3s %3.3s %2d %2d:%.2d:%.2d%2.2s %4.4d",
      wday_name[time->tm_wday], month_name[time->tm_mon], time->tm_mday,
      hour, time->tm_min, time->tm_sec,
      am ? "AM" : "PM",
      ( time->tm_year + 1900 ) );
   return buf;
}

/* Retrieve a character's permission for checking. */
short get_trust( CHAR_DATA *ch )
{
   if( !ch )
      return 0;
   return ch->trust;
}

int get_played( CHAR_DATA *ch )
{
   return ( ch->played + ( current_time - ch->logon ) ) / 3600;
}

/* Retrieve a character's age. */
short get_age( CHAR_DATA *ch )
{
   if( is_npc( ch ) )
      return 0;
   if( time_info.month > ch->pcdata->birth_month )
      return ( time_info.year - ch->pcdata->birth_year );
   if( time_info.month == ch->pcdata->birth_month && time_info.day >= ch->pcdata->birth_day )
      return ( time_info.year - ch->pcdata->birth_year );
   return ( ( time_info.year - 1 ) - ch->pcdata->birth_year );
}

/* Update characters maxes */
void update_maxes( CHAR_DATA *ch )
{
   /* Start again with race's base hit */
   ch->max_hit = race_table[ch->race]->hit;
   /* Toss in affect from constitution */
   ch->max_hit += get_curr_con( ch );

   /* Start again with race's base move */
   ch->max_move = race_table[ch->race]->move;
   /* Toss in affect from constitution and dexterity */
   ch->max_move += ( get_curr_con( ch ) + get_curr_dex( ch ) ) / 4;

   /* Start again with race's base mana */
   ch->max_mana = race_table[ch->race]->mana;
   /* Toss in affect from intelligence and wisdom */
   ch->max_mana += ( ( get_curr_int( ch ) * 2 ) + get_curr_wis( ch ) ) / 8;
}

short get_perm_stat( int stat, CHAR_DATA *ch )
{
   if( stat < 0 || stat >= STAT_MAX )
      return 0;
   if( is_npc( ch ) || is_immortal( ch ) )
      return URANGE( 1, ch->perm_stats[stat], 1025 );
   else
      return URANGE( 1, ch->perm_stats[stat], 1000 );
}

/* Retrieve character's current strength. */
short get_curr_str( CHAR_DATA *ch )
{
   return get_perm_stat( STAT_STR, ch );
}

/* Retrieve character's current intelligence. */
short get_curr_int( CHAR_DATA *ch )
{
   return get_perm_stat( STAT_INT, ch );
}

/* Retrieve character's current wisdom. */
short get_curr_wis( CHAR_DATA *ch )
{
   return get_perm_stat( STAT_WIS, ch );
}

/* Retrieve character's current dexterity. */
short get_curr_dex( CHAR_DATA *ch )
{
   return get_perm_stat( STAT_DEX, ch );
}

/* Retrieve character's current constitution. */
short get_curr_con( CHAR_DATA *ch )
{
   return get_perm_stat( STAT_CON, ch );
}

/* Retrieve character's current charisma. */
short get_curr_cha( CHAR_DATA *ch )
{
   return get_perm_stat( STAT_CHA, ch );
}

/* Retrieve character's current luck. */
short get_curr_lck( CHAR_DATA *ch )
{
   return get_perm_stat( STAT_LCK, ch );
}

/* Retrieve the number of items a character can carry */
int can_carry_n( CHAR_DATA *ch )
{
   int penalty = 0;

   if( !is_npc( ch ) && get_trust( ch ) >= PERM_IMM )
      return get_trust( ch ) * 200;

   if( is_npc( ch ) && xIS_SET( ch->act, ACT_IMMORTAL ) )
      return ch->level * 200;

   if( get_eq_char( ch, WEAR_HOLD_B ) )
      ++penalty;
   if( get_eq_char( ch, WEAR_HOLD_L ) )
      ++penalty;
   if( get_eq_char( ch, WEAR_HOLD_R ) )
      ++penalty;
   return URANGE( 5, ( ch->level + 15 ) / 5 + get_curr_dex( ch ) - 13 - penalty, 20 );
}

/* Retrieve a character's carry capacity. */
int can_carry_w( CHAR_DATA *ch )
{
   int carry;

   if( !is_npc( ch ) && get_trust( ch ) >= PERM_IMM )
      return 1000000;

   if( is_npc( ch ) && xIS_SET( ch->act, ACT_IMMORTAL ) )
      return 1000000;

   carry = get_curr_str( ch );
   return URANGE( carry, carry * ch->level, 1000000 );
}

/* See if a player/mob can take a piece of prototype eq - Thoric */
bool can_take_proto( CHAR_DATA *ch )
{
   if( is_immortal( ch ) )
      return true;
   else if( is_npc( ch ) && xIS_SET( ch->act, ACT_PROTOTYPE ) )
      return true;
   else
      return false;
}

/* See if a string is one of the names of an object. */
bool is_name( const char *str, char *namelist )
{
   char name[MIL];

   while( namelist && namelist[0] != '\0' )
   {
      namelist = one_argument( namelist, name );
      if( !str_cmp( str, name ) )
         return true;
   }
   return false;
}

bool is_name_prefix( const char *str, char *namelist )
{
   char name[MIL];

   while( namelist && namelist[0] != '\0' )
   {
      namelist = one_argument( namelist, name );
      if( !str_prefix( str, name ) )
         return true;
   }
   return false;
}

/*
 * See if a string is one of the names of an object. - Thoric
 * Treats a dash as a word delimiter as well as a space
 */
bool is_name2( const char *str, char *namelist )
{
   char name[MIL];

   while( namelist && namelist[0] != '\0' )
   {
      namelist = one_argument2( namelist, name );
      if( !str_cmp( str, name ) )
         return true;
   }
   return false;
}

bool is_name2_prefix( const char *str, char *namelist )
{
   char name[MIL];


   while( namelist && namelist[0] != '\0' )
   {
      namelist = one_argument2( namelist, name );
      if( !str_prefix( str, name ) )
         return true;
   }
   return false;
}

/* Checks if str is a name in namelist supporting multiple keywords - Thoric */
bool nifty_is_name( char *str, char *namelist )
{
   char name[MIL];

   while( str && str[0] != '\0' )
   {
      str = one_argument2( str, name );
      if( !is_name2( name, namelist ) )
         return false;
   }
   return true;
}

bool nifty_is_name_prefix( char *str, char *namelist )
{
   char name[MIL];

   while( str && str[0] != '\0' )
   {
      str = one_argument2( str, name );
      if( !is_name2_prefix( name, namelist ) )
         return false;
   }
   return true;
}

/* Apply or remove an affect to a character. */
void affect_modify( CHAR_DATA *ch, AFFECT_DATA *paf, bool fAdd )
{
   OBJ_DATA *wield;
   int mod;
   struct skill_type *skill;
   ch_ret retcode;

   mod = paf->modifier;

   if( fAdd )
   {
      xSET_BITS( ch->affected_by, paf->bitvector );
   }
   else
   {
      xREMOVE_BITS( ch->affected_by, paf->bitvector );

      switch( paf->location % REVERSE_APPLY )
      {
         case APPLY_EXT_AFFECT:
            xREMOVE_BIT( ch->affected_by, mod );
            return;

         case APPLY_RESISTANT:
            xREMOVE_BIT( ch->resistant, mod );
            return;

         case APPLY_IMMUNE:
            xREMOVE_BIT( ch->immune, mod );
            return;

         case APPLY_SUSCEPTIBLE:
            xREMOVE_BIT( ch->susceptible, mod );
            return;

         default:
            break;
      }
      mod = 0 - mod;
   }

   switch( paf->location % REVERSE_APPLY )
   {
      default:
         bug( "%s: unknown location %d.", __FUNCTION__, paf->location );
         return;

      case APPLY_NONE:
         break;

      case APPLY_HIT:
         ch->max_hit += mod;
         break;

      case APPLY_MOVE:
         ch->max_move += mod;
         break;

      case APPLY_MANA:
         if( !is_vampire( ch ) )
            ch->max_mana += mod;
         break;

      case APPLY_BLOOD:
         if( is_vampire( ch ) )
            ch->max_mana += mod;
         break;

      case APPLY_ARMOR:
         ch->armor += mod;
         break;

      case APPLY_HITROLL:
         ch->hitroll += mod;
         break;

      case APPLY_DAMROLL:
         ch->damroll += mod;
         break;

         /* Bitvector modifying apply types */
      case APPLY_EXT_AFFECT:
         if( mod < 0 || mod >= AFF_MAX )
            bug( "%s: unknown affect: mod (%d) paf->modifier (%d)", __FUNCTION__, mod, paf->modifier );
         else if( xIS_EMPTY( paf->bitvector ) || ( !xIS_EMPTY( paf->bitvector ) && mod != 0 ) )
            xSET_BIT( ch->affected_by, mod );
         break;

      case APPLY_RESISTANT:
         if( mod < 0 || mod >= RIS_MAX )
            bug( "%s: unknown resistant: mod (%d) paf->modifier (%d)", __FUNCTION__, mod, paf->modifier );
         else
            xSET_BIT( ch->resistant, mod );
         break;

      case APPLY_IMMUNE:
         if( mod < 0 || mod >= RIS_MAX )
            bug( "%s: unknown immune: mod (%d) paf->modifier (%d)", __FUNCTION__, mod, paf->modifier );
         else
            xSET_BIT( ch->immune, mod );
         break;

      case APPLY_SUSCEPTIBLE:
         if( mod < 0 || mod >= RIS_MAX )
            bug( "%s: unknown susceptible: mod (%d) paf->modifier (%d)", __FUNCTION__, mod, paf->modifier );
         else
            xSET_BIT( ch->susceptible, mod );
         break;

      case APPLY_WEAPONSPELL:   /* see fight.c */
         break;

      case APPLY_STRIPSN:
         if( is_valid_sn( mod ) )
            affect_strip( ch, mod );
         else
            bug( "%s: APPLY_STRIPSN invalid sn %d", __FUNCTION__, mod );
         break;

      case APPLY_WEARSPELL:
      case APPLY_REMOVESPELL:
         if( xIS_SET( ch->in_room->room_flags, ROOM_NO_MAGIC )
         || xIS_SET( ch->immune, RIS_MAGIC )
         || ( ( paf->location % REVERSE_APPLY ) == APPLY_WEARSPELL && !fAdd )
         || ( ( paf->location % REVERSE_APPLY ) == APPLY_REMOVESPELL && fAdd )
         || saving_char == ch /* so save/quit doesn't trigger */
         || loading_char == ch )   /* so loading doesn't trigger */
            return;

         mod = abs( mod );
         if( is_valid_sn( mod ) && ( skill = skill_table[mod] ) && skill->type == SKILL_SPELL )
         {
            if( skill->target == TAR_IGNORE || skill->target == TAR_OBJ_INV )
            {
               bug( "APPLY_WEARSPELL trying to apply bad target spell.  SN is %d.", mod );
               return;
            }
            if( ( retcode = ( *skill->spell_fun ) ( mod, ch->level, ch, ch ) ) == rCHAR_DIED || char_died( ch ) )
               return;
         }
         break;
   }

   /*
    * Check for weapon wielding.
    * Guard against recursion (for weapons with affects).
    */
   if( !is_npc( ch ) && saving_char != ch )
   {
      if( ( wield = get_eq_char( ch, WEAR_HOLD_B ) )
      && get_obj_weight( wield ) > ( get_curr_str( ch ) / 8 ) )
      {
         act( AT_ACTION, "You are too weak to hold $p any longer.", ch, wield, NULL, TO_CHAR );
         act( AT_ACTION, "$n stops holding $p.", ch, wield, NULL, TO_ROOM );
         unequip_char( ch, wield );
      }
      if( ( wield = get_eq_char( ch, WEAR_HOLD_L ) )
      && get_obj_weight( wield ) > ( get_curr_str( ch ) / 4 ) )
      {
         act( AT_ACTION, "You are too weak to hold $p any longer.", ch, wield, NULL, TO_CHAR );
         act( AT_ACTION, "$n stops holding $p.", ch, wield, NULL, TO_ROOM );
         unequip_char( ch, wield );
      }
      if( ( wield = get_eq_char( ch, WEAR_HOLD_R ) )
      && get_obj_weight( wield ) > ( get_curr_str( ch ) / 4 ) )
      {
         act( AT_ACTION, "You are too weak to hold $p any longer.", ch, wield, NULL, TO_CHAR );
         act( AT_ACTION, "$n stops holding $p.", ch, wield, NULL, TO_ROOM );
         unequip_char( ch, wield );
      }
   }
}

/* Give an affect to a char. */
void affect_to_char( CHAR_DATA *ch, AFFECT_DATA *paf )
{
   AFFECT_DATA *paf_new, *paf_check;
   SKILLTYPE *skill, *skill_new;
   bool inserted = false;

   if( !ch )
   {
      bug( "%s: (NULL, %d)", __FUNCTION__, paf ? paf->type : 0 );
      return;
   }

   if( !paf )
   {
      bug( "%s: (%s, NULL)", __FUNCTION__, ch->name );
      return;
   }

   CREATE( paf_new, AFFECT_DATA, 1 );
   paf_new->type = paf->type;
   paf_new->duration = paf->duration;
   paf_new->location = paf->location;
   paf_new->modifier = paf->modifier;
   paf_new->bitvector = paf->bitvector;

   skill_new = get_skilltype( paf_new->type );

   for( paf_check = ch->first_affect; paf_check; paf_check = paf_check->next )
   {
      skill = get_skilltype( paf_check->type );
      if( paf_new->duration < paf_check->duration )
      {
         INSERT( paf_new, paf_check, ch->first_affect, next, prev );
         inserted = true;
         break;
      }
      else if( paf_new->duration == paf_check->duration && skill_new && skill
      && strcmp( skill_new->name, skill->name ) <= 0 )
      {
         INSERT( paf_new, paf_check, ch->first_affect, next, prev );
         inserted = true;
         break;
      }
   }
   if( !inserted )
      LINK( paf_new, ch->first_affect, ch->last_affect, next, prev );

   affect_modify( ch, paf_new, true );
}

/* Remove an affect from a char. */
void affect_remove( CHAR_DATA *ch, AFFECT_DATA *paf )
{
   if( !ch->first_affect )
   {
      bug( "%s: (%s, %d): no affect.", __FUNCTION__, ch->name, paf ? paf->type : 0 );
      return;
   }

   affect_modify( ch, paf, false );

   UNLINK( paf, ch->first_affect, ch->last_affect, next, prev );
   DISPOSE( paf );
}

/* Strip all affects of a given sn. */
void affect_strip( CHAR_DATA *ch, int sn )
{
   AFFECT_DATA *paf, *paf_next;

   for( paf = ch->first_affect; paf; paf = paf_next )
   {
      paf_next = paf->next;
      if( paf->type == sn )
         affect_remove( ch, paf );
   }
}

/* Return true if a char is affected by a spell. */
bool is_affected( CHAR_DATA *ch, int sn )
{
   AFFECT_DATA *paf;

   for( paf = ch->first_affect; paf; paf = paf->next )
      if( paf->type == sn )
         return true;

   return false;
}

/*
 * Add or enhance an affect.
 * Limitations put in place by Thoric, they may be high... but at least
 * they're there :)
 */
void affect_join( CHAR_DATA *ch, AFFECT_DATA *paf )
{
   AFFECT_DATA *paf_old;

   for( paf_old = ch->first_affect; paf_old; paf_old = paf_old->next )
      if( paf_old->type == paf->type )
      {
         paf->duration = UMIN( 1000000, paf->duration + paf_old->duration );
         if( paf->modifier )
            paf->modifier = UMIN( 5000, paf->modifier + paf_old->modifier );
         else
            paf->modifier = paf_old->modifier;
         affect_remove( ch, paf_old );
         break;
      }

   affect_to_char( ch, paf );
}

/* Apply only affected and RIS on a char */
void aris_affect( CHAR_DATA *ch, AFFECT_DATA *paf )
{
   xSET_BITS( ch->affected_by, paf->bitvector );
   switch( paf->location % REVERSE_APPLY )
   {
      case APPLY_EXT_AFFECT:
         xSET_BIT( ch->affected_by, paf->modifier );
         break;

      case APPLY_RESISTANT:
         xSET_BIT( ch->resistant, paf->modifier );
         break;

      case APPLY_IMMUNE:
         xSET_BIT( ch->immune, paf->modifier );
         break;

      case APPLY_SUSCEPTIBLE:
         xSET_BIT( ch->susceptible, paf->modifier );
         break;
   }
}

/*
 * Update affecteds and RIS for a character in case things get messed.
 * This should only really be used as a quick fix until the cause
 * of the problem can be hunted down. - FB
 * Last modified: June 30, 1997
 *
 * Quick fix?  Looks like a good solution for a lot of problems.
 */

/* Temp mod to bypass immortals so they can keep their mset affects,
 * just a band-aid until we get more time to look at it -- Blodkai */
void update_aris( CHAR_DATA *ch )
{
   AFFECT_DATA *paf;
   OBJ_DATA *obj;
   int hiding;

   if( is_npc( ch ) || is_immortal( ch ) )
      return;

   /* So chars using hide skill will continue to hide */
   hiding = IS_AFFECTED( ch, AFF_HIDE );

   xCLEAR_BITS( ch->affected_by );
   xCLEAR_BITS( ch->no_affected_by );
   xCLEAR_BITS( ch->resistant );
   xCLEAR_BITS( ch->no_resistant );
   xCLEAR_BITS( ch->immune );
   xCLEAR_BITS( ch->no_immune );
   xCLEAR_BITS( ch->susceptible );
   xCLEAR_BITS( ch->no_susceptible );

   /* Add in effects from race */
   if( race_table[ch->race] )
   {
      xSET_BITS( ch->affected_by, race_table[ch->race]->affected );
      xSET_BITS( ch->resistant, race_table[ch->race]->resist );
      xSET_BITS( ch->susceptible, race_table[ch->race]->suscept );
   }

   /* Add in effects from deities */
   if( ch->pcdata->deity )
   {
      if( ch->pcdata->favor > ch->pcdata->deity->affectednum )
         xSET_BITS( ch->affected_by, ch->pcdata->deity->affected );
      if( ch->pcdata->favor > ch->pcdata->deity->resistnum )
         xSET_BITS( ch->resistant, ch->pcdata->deity->resist );
      if( ch->pcdata->favor < ch->pcdata->deity->susceptnum )
         xSET_BITS( ch->susceptible, ch->pcdata->deity->suscept );
   }

   /* Add in effect from spells */
   for( paf = ch->first_affect; paf; paf = paf->next )
      aris_affect( ch, paf );

   /* Add in effects from equipment */
   for( obj = ch->first_carrying; obj; obj = obj->next_content )
   {
      if( obj->wear_loc != WEAR_NONE )
      {
         for( paf = obj->first_affect; paf; paf = paf->next )
            aris_affect( ch, paf );

         for( paf = obj->pIndexData->first_affect; paf; paf = paf->next )
            aris_affect( ch, paf );
      }
   }

   /* Add in effects for polymorph */
   if( ch->morph )
   {
      xSET_BITS( ch->affected_by, ch->morph->affected_by );
      xSET_BITS( ch->no_affected_by, ch->morph->no_affected_by );
      xSET_BITS( ch->immune, ch->morph->immune );
      xSET_BITS( ch->no_immune, ch->morph->no_immune );
      xSET_BITS( ch->resistant, ch->morph->resistant );
      xSET_BITS( ch->no_resistant, ch->morph->no_resistant );
      xSET_BITS( ch->susceptible, ch->morph->suscept );
      xSET_BITS( ch->no_susceptible, ch->morph->no_suscept );
   }

   /* If they were hiding before, make them hiding again */
   if( hiding )
      xSET_BIT( ch->affected_by, AFF_HIDE );
}

/* Adjust the rooms light for this ch */
void adjust_room_light( CHAR_DATA *ch, bool increase )
{
   OBJ_DATA *obj;

   for( obj = ch->first_carrying; obj; obj = obj->next_content )
   {
      if( obj->wear_loc != WEAR_NONE && obj->item_type == ITEM_LIGHT && obj->value[2] != 0 )
      {
         if( increase )
            ++ch->in_room->light;
         else if( ch->in_room->light > 0 )
            --ch->in_room->light;
      }
   }
}

/* Move a char out of a room. */
void char_from_room( CHAR_DATA *ch )
{
   if( !ch->in_room )
   {
      bug( "%s: NULL.", __FUNCTION__ );
      return;
   }

   if( !is_npc( ch ) )
      --ch->in_room->area->nplayer;

   adjust_room_light( ch, false );

   UNLINK( ch, ch->in_room->first_person, ch->in_room->last_person, next_in_room, prev_in_room );
   ch->was_in_room = ch->in_room;
   ch->in_room = NULL;
   ch->next_in_room = NULL;
   ch->prev_in_room = NULL;

   if( !is_npc( ch ) && get_timer( ch, TIMER_SHOVEDRAG ) > 0 )
      remove_timer( ch, TIMER_SHOVEDRAG );
}

typedef struct teleport_data TELEPORT_DATA;

/* Delayed teleport type. */
struct teleport_data
{
   TELEPORT_DATA *next, *prev;
   ROOM_INDEX_DATA *room;
   short timer;
};

TELEPORT_DATA *first_teleport, *last_teleport;

void tele_update( void )
{
   TELEPORT_DATA *tele, *tele_next;

   if( !first_teleport )
      return;

   for( tele = first_teleport; tele; tele = tele_next )
   {
      tele_next = tele->next;
      if( --tele->timer <= 0 )
      {
         if( tele->room->first_person )
         {
            if( xIS_SET( tele->room->room_flags, ROOM_TELESHOWDESC ) )
               teleport( tele->room->first_person, tele->room->tele_vnum, TELE_SHOWDESC | TELE_TRANSALL );
            else
               teleport( tele->room->first_person, tele->room->tele_vnum, TELE_TRANSALL );
         }
         UNLINK( tele, first_teleport, last_teleport, next, prev );
         DISPOSE( tele );
      }
   }
}

/* Move a char into a room. */
void char_to_room( CHAR_DATA *ch, ROOM_INDEX_DATA *pRoomIndex )
{
   if( !ch )
   {
      bug( "%s: NULL ch!", __FUNCTION__ );
      return;
   }

   if( !pRoomIndex || !get_room_index( pRoomIndex->vnum ) )
   {
      if( ch->was_in_room && get_room_index( ch->was_in_room->vnum ) )
      {
         bug( "%s: Trying to put (%s) in a NULL room! Putting char back in previous room!", __FUNCTION__, ch->name );
         pRoomIndex = ch->was_in_room;
      }
      else if( !( pRoomIndex = get_room_index( sysdata.room_limbo ) ) )
         bug( "%s: Trying to put %s in a NULL room! Putting char in limbo (%d)", __FUNCTION__, ch->name, sysdata.room_limbo );
      else
         bug( "%s: Trying to put %s in a NULL room! Limbo [%d] doesn't exist prepare for a crash!", __FUNCTION__, ch->name, sysdata.room_limbo );
   }

   stop_fishing( ch );

   ch->in_room = pRoomIndex;

   LINK( ch, pRoomIndex->first_person, pRoomIndex->last_person, next_in_room, prev_in_room );

   if( !is_npc( ch ) )
      if( ++pRoomIndex->area->nplayer > pRoomIndex->area->max_players )
         pRoomIndex->area->max_players = pRoomIndex->area->nplayer;

   adjust_room_light( ch, true );

   if( !is_npc( ch ) && xIS_SET( pRoomIndex->room_flags, ROOM_SAFE ) && get_timer( ch, TIMER_SHOVEDRAG ) <= 0 )
      add_timer( ch, TIMER_SHOVEDRAG, 10, NULL, 0 );

   /*
    * Delayed Teleport rooms             -Thoric
    * Should be the last thing checked in this function
    */
   if( xIS_SET( pRoomIndex->room_flags, ROOM_TELEPORT ) && pRoomIndex->tele_delay > 0 )
   {
      TELEPORT_DATA *tele;

      for( tele = first_teleport; tele; tele = tele->next )
         if( tele->room == pRoomIndex )
            return;

      CREATE( tele, TELEPORT_DATA, 1 );
      LINK( tele, first_teleport, last_teleport, next, prev );
      tele->room = pRoomIndex;
      tele->timer = pRoomIndex->tele_delay;
   }

   if( !ch->was_in_room )
      ch->was_in_room = ch->in_room;
}

void free_teleports( void )
{
   TELEPORT_DATA *tele, *tele_next;

   for( tele = first_teleport; tele; tele = tele_next )
   {
      tele_next = tele->next;

      UNLINK( tele, first_teleport, last_teleport, next, prev );
      DISPOSE( tele );
   }
}

/* Give an obj to a char. */
OBJ_DATA *obj_to_char( OBJ_DATA *obj, CHAR_DATA *ch )
{
   OBJ_DATA *otmp, *oret = obj;
   bool skipgroup = false, grouped = false;
   int oweight = get_obj_weight( obj );
   int onum = get_obj_number( obj );
   int wear_loc = obj->wear_loc;
   EXT_BV extra_flags = obj->extra_flags;

   if( is_obj_stat( obj, ITEM_PROTOTYPE ) )
   {
      if( !is_immortal( ch ) && ( !is_npc( ch ) || !xIS_SET( ch->act, ACT_PROTOTYPE ) ) )
         return obj_to_room( obj, ch->in_room );
   }

   if( loading_char == ch )
      if( obj->wear_loc != -1 || obj->t_wear_loc != -1 )
         skipgroup = true;

   if( is_npc( ch ) && ch->pIndexData->pShop )
      skipgroup = true;

   if( !skipgroup )
   {
      for( otmp = ch->first_carrying; otmp; otmp = otmp->next_content )
      {
         if( ( oret = group_object( otmp, obj ) ) == otmp )
         {
            grouped = true;
            break;
         }
      }
   }
   if( !grouped )
   {
      if( !is_npc( ch ) || !ch->pIndexData->pShop )
      {
         LINK( obj, ch->first_carrying, ch->last_carrying, next_content, prev_content );
         obj->carried_by = ch;
         obj->in_room = NULL;
         obj->in_obj = NULL;
      }
      else
      {
         /* If ch is a shopkeeper, add the obj using an insert sort */
         for( otmp = ch->first_carrying; otmp; otmp = otmp->next_content )
         {
            if( obj->level > otmp->level )
            {
               INSERT( obj, otmp, ch->first_carrying, next_content, prev_content );
               break;
            }
            else if( obj->level == otmp->level && strcmp( obj->short_descr, otmp->short_descr ) < 0 )
            {
               INSERT( obj, otmp, ch->first_carrying, next_content, prev_content );
               break;
            }
         }

         if( !otmp )
            LINK( obj, ch->first_carrying, ch->last_carrying, next_content, prev_content );

         obj->carried_by = ch;
         obj->in_room = NULL;
         obj->in_obj = NULL;
      }
   }
   if( wear_loc == WEAR_NONE )
   {
      ch->carry_number += onum;
      ch->carry_weight += oweight;
   }
   else if( !xIS_SET( extra_flags, ITEM_MAGIC ) )
      ch->carry_weight += oweight;
   return ( oret ? oret : obj );
}

/* Take an obj from its character. */
void obj_from_char( OBJ_DATA *obj )
{
   CHAR_DATA *ch;

   if( !( ch = obj->carried_by ) )
   {
      bug( "%s: null ch.", __FUNCTION__ );
      return;
   }

   if( obj->wear_loc != WEAR_NONE )
      unequip_char( ch, obj );

   /* obj may drop during unequip... */
   if( !obj->carried_by )
      return;

   UNLINK( obj, ch->first_carrying, ch->last_carrying, next_content, prev_content );

   if( is_obj_stat( obj, ITEM_COVERING ) && obj->first_content )
      empty_obj( obj, NULL, NULL );

   obj->in_room = NULL;
   obj->carried_by = NULL;
   ch->carry_number -= get_obj_number( obj );
   ch->carry_weight -= get_obj_weight( obj );
}

/* Uses a 0 and higher now, higher is better */
void apply_ac( CHAR_DATA *ch, OBJ_DATA *obj, int iWear, bool remove )
{
   if( obj->item_type != ITEM_ARMOR )
      return;

   switch( iWear )
   {
      case WEAR_HEAD:
      case WEAR_EARS:      case WEAR_EAR_L:      case WEAR_EAR_R:
      case WEAR_EYES:      case WEAR_EYE_L:      case WEAR_EYE_R:
      case WEAR_FACE:
      case WEAR_NECK:
      case WEAR_SHOULDERS: case WEAR_SHOULDER_L: case WEAR_SHOULDER_R:
      case WEAR_ABOUT:
      case WEAR_BODY:
      case WEAR_BACK:
      case WEAR_ARMS:      case WEAR_ARM_L:      case WEAR_ARM_R:
      case WEAR_HANDS:     case WEAR_HAND_L:     case WEAR_HAND_R:
      case WEAR_FINGERS:   case WEAR_FINGER_L:   case WEAR_FINGER_R:
      case WEAR_HOLD_B:    case WEAR_HOLD_L:     case WEAR_HOLD_R:
      case WEAR_WAIST:
      case WEAR_LEGS:      case WEAR_LEG_L:      case WEAR_LEG_R:
      case WEAR_ANKLES:    case WEAR_ANKLE_L:    case WEAR_ANKLE_R:
      case WEAR_FEET:      case WEAR_FOOT_L:     case WEAR_FOOT_R:
         if( remove )
            ch->armor -= obj->value[0];
         else
            ch->armor += obj->value[0];
         if( ch->armor < 0 )
            ch->armor = 0;
         break;
   }
}

/*
 * Find a piece of eq on a character.
 * Will pick the top layer if clothing is layered.		-Thoric
 */
OBJ_DATA *get_eq_char( CHAR_DATA *ch, int iWear )
{
   OBJ_DATA *obj, *maxobj = NULL;

   for( obj = ch->first_carrying; obj; obj = obj->next_content )
      if( obj->wear_loc == iWear )
      {
         if( !obj->pIndexData->layers )
            return obj;
         else if( !maxobj || obj->pIndexData->layers > maxobj->pIndexData->layers )
            maxobj = obj;
      }

   return maxobj;
}

/* Equip a char with an obj. */
void equip_char( CHAR_DATA *ch, OBJ_DATA *obj, int iWear )
{
   AFFECT_DATA *paf;
   OBJ_DATA *otmp;
   int stat;

   if( obj->carried_by != ch )
   {
      bug( "%s: obj not being carried by (%s)[%d]!", __FUNCTION__, ch->name,
         ch->pIndexData ? ch->pIndexData->vnum : 0 );
      return;
   }

   if( ( otmp = get_eq_char( ch, iWear ) ) && ( !otmp->pIndexData->layers || !obj->pIndexData->layers ) )
   {
      bug( "%s: already equipped on (%d) (%s) by (%s)[%d].", __FUNCTION__, iWear, wear_locs[iWear], ch->name,
         ch->pIndexData ? ch->pIndexData->vnum : 0 );
      return;
   }

   separate_obj( obj );
   if( ch->level < obj->level )
   {
      ch_printf( ch, "You must be level %d to use this object.\r\n", obj->level );
      act( AT_ACTION, "$n tries to use $p, but is too inexperienced.", ch, obj, NULL, TO_ROOM );
      return;
   }
   for( stat = 0; stat < STAT_MAX; stat++ )
   {
      if( get_perm_stat( stat, ch ) < obj->stat_reqs[stat] )
      {
         ch_printf( ch, "You need to have %d %s to use this object.\r\n", obj->stat_reqs[STAT_STR], stattypes[stat] );
         act( AT_ACTION, "$n tries to use $p, but lacks something to do so.", ch, obj, NULL, TO_ROOM );
         return;
      }
   }
   if( !is_npc( ch ) && xIS_SET( obj->race_restrict, ch->race ) )
   {
      send_to_char( "Your race can't wear this object.\r\n", ch );
      act( AT_ACTION, "$n tries to use $p, but is forbidden to do so.", ch, obj, NULL, TO_ROOM );
      return;
   }
   if( !is_npc( ch ) && xIS_SET( obj->class_restrict, ch->Class ) )
   {
      send_to_char( "Your class can't wear this object.\r\n", ch );
      act( AT_ACTION, "$n tries to use $p, but is forbidden to do so.", ch, obj, NULL, TO_ROOM );
      return;
   }
   if( ( is_obj_stat( obj, ITEM_ANTI_EVIL ) && is_evil( ch ) )
   || ( is_obj_stat( obj, ITEM_ANTI_GOOD ) && is_good( ch ) )
   || ( is_obj_stat( obj, ITEM_ANTI_NEUTRAL ) && is_neutral( ch ) ) )
   {
      if( loading_char != ch )
      {
         act( AT_MAGIC, "You are zapped by $p.", ch, obj, NULL, TO_CHAR );
         act( AT_MAGIC, "$n is zapped by $p.", ch, obj, NULL, TO_ROOM );
      }
      oprog_zap_trigger( ch, obj );
      if( xIS_SET( sysdata.save_flags, SV_ZAPDROP ) && !char_died( ch ) )
         save_char_obj( ch );
      return;
   }

   apply_ac( ch, obj, iWear, false );
   obj->wear_loc = iWear;

   ch->carry_number -= get_obj_number( obj );
   if( is_obj_stat( obj, ITEM_MAGIC ) )
      ch->carry_weight -= get_obj_weight( obj );

   for( paf = obj->pIndexData->first_affect; paf; paf = paf->next )
      affect_modify( ch, paf, true );
   for( paf = obj->first_affect; paf; paf = paf->next )
      affect_modify( ch, paf, true );

   if( obj->item_type == ITEM_LIGHT && obj->value[2] != 0 && ch->in_room )
      ++ch->in_room->light;
}

/* Unequip a char with an obj. */
void unequip_char( CHAR_DATA *ch, OBJ_DATA *obj )
{
   AFFECT_DATA *paf;

   if( obj->wear_loc == WEAR_NONE )
   {
      bug( "%s: already unequipped.", __FUNCTION__ );
      return;
   }

   ch->carry_number += get_obj_number( obj );
   if( is_obj_stat( obj, ITEM_MAGIC ) )
      ch->carry_weight += get_obj_weight( obj );

   apply_ac( ch, obj, obj->wear_loc, true );
   obj->wear_loc = -1;
   obj->t_wear_loc = -1;

   for( paf = obj->pIndexData->first_affect; paf; paf = paf->next )
      affect_modify( ch, paf, false );
   if( obj->carried_by )
      for( paf = obj->first_affect; paf; paf = paf->next )
         affect_modify( ch, paf, false );

   update_aris( ch );

   if( !obj->carried_by )
      return;

   if( obj->item_type == ITEM_LIGHT && obj->value[2] != 0 && ch->in_room && ch->in_room->light > 0 )
      --ch->in_room->light;
}

/* Move an obj out of a room. */
void write_corpses( OBJ_DATA *obj, bool dontsave );

int falling;

void obj_from_room( OBJ_DATA *obj )
{
   ROOM_INDEX_DATA *in_room;

   if( !( in_room = obj->in_room ) )
   {
      bug( "%s: NULL in_room.", __FUNCTION__ );
      return;
   }

   UNLINK( obj, in_room->first_content, in_room->last_content, next_content, prev_content );

   /* uncover contents */
   if( is_obj_stat( obj, ITEM_COVERING ) && obj->first_content )
      empty_obj( obj, NULL, obj->in_room );

   if( obj->item_type == ITEM_FIRE )
      obj->in_room->light -= obj->count;

   obj->carried_by = NULL;
   obj->in_obj = NULL;
   obj->in_room = NULL;
   if( obj->pIndexData->vnum == OBJ_VNUM_CORPSE_PC && falling < 1 )
      write_corpses( obj, true );
}

/* Move an obj into a room. */
OBJ_DATA *obj_to_room( OBJ_DATA *obj, ROOM_INDEX_DATA *pRoomIndex )
{
   OBJ_DATA *otmp, *oret;
   short count = obj->count;
   short item_type = obj->item_type;

   for( otmp = pRoomIndex->first_content; otmp; otmp = otmp->next_content )
   {
      if( ( oret = group_object( otmp, obj ) ) == otmp )
      {
         if( item_type == ITEM_FIRE )
            pRoomIndex->light += count;
         return oret;
      }

   }
   LINK( obj, pRoomIndex->first_content, pRoomIndex->last_content, next_content, prev_content );
   obj->in_room = pRoomIndex;
   obj->carried_by = NULL;
   obj->in_obj = NULL;
   obj->room_vnum = pRoomIndex->vnum;  /* hotboot tracker */
   if( item_type == ITEM_FIRE )
      pRoomIndex->light += count;
   falling++;
   obj_fall( obj, false );
   falling--;
   if( obj->pIndexData->vnum == OBJ_VNUM_CORPSE_PC && falling < 1 )
      write_corpses( obj, false );
   return obj;
}

/* Who's carrying an item -- recursive for nested objects - Thoric */
CHAR_DATA *carried_by( OBJ_DATA *obj )
{
   if( obj->in_obj )
      return carried_by( obj->in_obj );

   return obj->carried_by;
}

/* Move an object into an object. */
OBJ_DATA *obj_to_obj( OBJ_DATA *obj, OBJ_DATA *obj_to )
{
   OBJ_DATA *otmp, *oret;
   CHAR_DATA *who;

   if( obj == obj_to )
   {
      bug( "%s: trying to put object inside itself: vnum %d", __FUNCTION__, obj->pIndexData->vnum );
      return obj;
   }

   if( !in_magic_container( obj_to ) && ( who = carried_by( obj_to ) ) )
      who->carry_weight += get_obj_weight( obj );

   for( otmp = obj_to->first_content; otmp; otmp = otmp->next_content )
      if( ( oret = group_object( otmp, obj ) ) == otmp )
         return oret;

   LINK( obj, obj_to->first_content, obj_to->last_content, next_content, prev_content );

   obj->in_obj = obj_to;
   obj->in_room = NULL;
   obj->carried_by = NULL;

   return obj;
}

/* Move an object out of an object. */
void obj_from_obj( OBJ_DATA *obj )
{
   OBJ_DATA *obj_from;
   bool magic;

   if( !( obj_from = obj->in_obj ) )
   {
      bug( "%s: null obj_from.", __FUNCTION__ );
      return;
   }

   magic = in_magic_container( obj_from );

   UNLINK( obj, obj_from->first_content, obj_from->last_content, next_content, prev_content );

   /* uncover contents */
   if( is_obj_stat( obj, ITEM_COVERING ) && obj->first_content )
      empty_obj( obj, obj->in_obj, NULL );

   obj->in_obj = NULL;
   obj->in_room = NULL;
   obj->carried_by = NULL;

   if( !magic )
      for( ; obj_from; obj_from = obj_from->in_obj )
         if( obj_from->carried_by )
            obj_from->carried_by->carry_weight -= get_obj_weight( obj );
}

/* Extract an obj from the world. */
void extract_obj( OBJ_DATA *obj )
{
   OBJ_DATA *obj_content;
   REL_DATA *RQueue, *rq_next;

   if( !obj )
      return;

   if( obj->item_type == ITEM_FISHINGPOLE )
      stop_obj_fishing( obj );

   if( obj->item_type == ITEM_PORTAL )
      remove_portal( obj );

   if( obj->carried_by )
      obj_from_char( obj );
   if( obj->in_room )
      obj_from_room( obj );
   if( obj->in_obj )
      obj_from_obj( obj );

   while( ( obj_content = obj->last_content ) )
      extract_obj( obj_content );

   /* remove affects */
   {
      AFFECT_DATA *paf, *paf_next;

      for( paf = obj->first_affect; paf; paf = paf_next )
      {
         paf_next = paf->next;
         DISPOSE( paf );
         top_affect--;
      }
      obj->first_affect = obj->last_affect = NULL;
   }

   /* remove extra descriptions */
   {
      EXTRA_DESCR_DATA *ed, *ed_next;

      for( ed = obj->first_extradesc; ed; ed = ed_next )
      {
         ed_next = ed->next;
         STRFREE( ed->description );
         STRFREE( ed->keyword );
         DISPOSE( ed );
      }
      obj->first_extradesc = obj->last_extradesc = NULL;
   }

   if( obj == gobj_prev )
      gobj_prev = obj->prev;

   for( RQueue = first_relation; RQueue; RQueue = rq_next )
   {
      rq_next = RQueue->next;
      if( RQueue->Type == relOSET_ON )
      {
         if( obj == RQueue->Subject )
            ( ( CHAR_DATA * ) RQueue->Actor )->dest_buf = NULL;
         else
            continue;
         UNLINK( RQueue, first_relation, last_relation, next, prev );
         DISPOSE( RQueue );
      }
   }

   UNLINK( obj, first_object, last_object, next, prev );
   if( obj->pIndexData->vnum == OBJ_VNUM_CORPSE_PC )
      UNLINK( obj, first_corpse, last_corpse, next_corpse, prev_corpse );

   obj->pIndexData->count -= obj->count;
   numobjsloaded -= obj->count;
   --physicalobjects;
   STRFREE( obj->name );
   STRFREE( obj->short_descr );
   STRFREE( obj->description );
   STRFREE( obj->action_desc );
   STRFREE( obj->owner );
   DISPOSE( obj );
}

/* Extract a char from the world. */
void extract_char( CHAR_DATA *ch, bool fPull )
{
   CHAR_DATA *wch;
   OBJ_DATA *obj;
   char buf[MSL];
   ROOM_INDEX_DATA *location;
   REL_DATA *RQueue, *rq_next;

   if( !ch )
   {
      bug( "%s: NULL ch.", __FUNCTION__ );
      return;
   }

   if( !ch->in_room )
   {
      bug( "%s: %s in NULL room.", __FUNCTION__, ch->name ? ch->name : "???" );
      return;
   }

   if( ch == supermob )
   {
      bug( "%s: ch == supermob!", __FUNCTION__ );
      return;
   }

   if( char_died( ch ) )
   {
      bug( "%s: %s already died!", __FUNCTION__, ch->name );
      return;
   }

   if( ch == cur_char )
      cur_char_died = true;

   /* shove onto extraction queue */
   queue_extracted_char( ch, fPull );

   for( RQueue = first_relation; RQueue; RQueue = rq_next )
   {
      rq_next = RQueue->next;
      if( fPull && RQueue->Type == relMSET_ON )
      {
         if( ch == RQueue->Subject )
            ( ( CHAR_DATA * ) RQueue->Actor )->dest_buf = NULL;
         else if( ch != RQueue->Actor )
            continue;
         UNLINK( RQueue, first_relation, last_relation, next, prev );
         DISPOSE( RQueue );
      }
   }

   if( gch_prev == ch )
      gch_prev = ch->prev;

   if( fPull )
      die_follower( ch );

   stop_fighting( ch, true );

   if( ch->mount )
   {
      xREMOVE_BIT( ch->mount->act, ACT_MOUNTED );
      ch->mount = NULL;
      ch->position = POS_STANDING;
   }

   /* If player goes link dead and was editing something update it all */
   if( ch->dest_buf )
   {
      if( ( wch = ( CHAR_DATA * )ch->dest_buf ) )
      {
         if( wch->editing )
         {
            stop_editing( ch ); /* This is correct as ch and not wch */
            wch->editing = NULL;
         }
      }
      ch->dest_buf = NULL;
      ch->spare_ptr = NULL;
   }

   /* Update the one who was editing the ch */
   if( ch->editing )
   {
      stop_editing( ch->editing );
      ch->editing->substate = SUB_NONE;
      ch->editing->dest_buf = NULL;
      ch->editing->spare_ptr = NULL;
      send_to_char( "What you were editing has been extracted.\r\n", ch->editing );
      ch->editing = NULL;
   }

   /* check if this NPC was a mount or a pet */
   if( is_npc( ch ) )
   {
      update_room_reset( ch );
      for( wch = first_char; wch; wch = wch->next )
      {
         if( wch->mount == ch )
         {
            wch->mount = NULL;
            wch->position = POS_STANDING;
            if( wch->in_room == ch->in_room )
            {
               act( AT_SOCIAL, "Your faithful mount, $N collapses beneath you...", wch, NULL, ch, TO_CHAR );
               act( AT_SOCIAL, "Sadly you dismount $M for the last time.", wch, NULL, ch, TO_CHAR );
               act( AT_PLAIN, "$n sadly dismounts $N for the last time.", wch, NULL, ch, TO_ROOM );
            }
         }
         if( wch->pcdata )
         {
            CHAR_DATA *pet;

            for( pet = wch->pcdata->first_pet; pet; pet = pet->next_pet )
            {
               if( pet != ch )
                  continue;
               UNLINK( pet, wch->pcdata->first_pet, wch->pcdata->last_pet, next_pet, prev_pet );
               if( wch->in_room == ch->in_room )
                  act( AT_SOCIAL, "You mourn for the loss of $N.", wch, NULL, ch, TO_CHAR );
            }
         }
      }
      xREMOVE_BIT( ch->act, ACT_MOUNTED );
   }

   while( ( obj = ch->last_carrying ) )
      extract_obj( obj );

   char_from_room( ch );

   if( !fPull )
   {
      location = NULL;

      if( !is_npc( ch ) && ch->pcdata->clan )
         location = get_room_index( ch->pcdata->clan->recall );

      if( !location )
         location = get_room_index( sysdata.room_altar );

      if( !location )
         location = get_room_index( sysdata.room_limbo );

      if( !location )
         bug( "No location to send (%s) to...this could cause alot of issues!!!!", ch->name );

      char_to_room( ch, location );
      /* Make things a little fancier - Thoric */
      if( ( wch = get_char_room( ch, "healer" ) ) )
      {
         act( AT_MAGIC, "$n mutters a few incantations, waves $s hands and points $s finger.", wch, NULL, NULL, TO_ROOM );
         act( AT_MAGIC, "$n appears from some strange swirling mists!", ch, NULL, NULL, TO_ROOM );
         snprintf( buf, sizeof( buf ), "Welcome back to the land of the living, %s", capitalize( ch->name ) );
         do_say( wch, buf );
      }
      else
         act( AT_MAGIC, "$n appears from some strange swirling mists!", ch, NULL, NULL, TO_ROOM );
      ch->position = POS_RESTING;
      return;
   }

   if( is_npc( ch ) )
   {
      --ch->pIndexData->count;
      --nummobsloaded;
   }

   for( wch = first_char; wch; wch = wch->next )
   {
      if( wch->reply == ch )
         wch->reply = NULL;
      if( wch->retell == ch )
         wch->retell = NULL;
   }

   UNLINK( ch, first_char, last_char, next, prev );

   if( ch->desc )
   {
      if( ch->desc->character != ch )
         bug( "%s: char's descriptor points to another char", __FUNCTION__ );
      else
      {
         ch->desc->character = NULL;
         close_socket( ch->desc, false );
         ch->desc = NULL;
      }
   }
}

/* Find a char in the room. */
CHAR_DATA *get_char_room( CHAR_DATA *ch, char *argument )
{
   char arg[MIL];
   CHAR_DATA *rch;
   int number, count, vnum;

   number = number_argument( argument, arg );
   if( !str_cmp( arg, "self" ) )
      return ch;

   if( get_trust( ch ) >= PERM_BUILDER && is_number( arg ) )
      vnum = atoi( arg );
   else
      vnum = -1;

   count = 0;

   for( rch = ch->in_room->first_person; rch; rch = rch->next_in_room )
   {
      if( !can_see( ch, rch ) )
         continue;

      if( vnum != -1 )
      {
         if( !is_npc( rch ) || vnum != rch->pIndexData->vnum )
            continue;
      }
      else if( !nifty_is_name( arg, rch->name ) )
         continue;

      if( number == 0 && !is_npc( rch ) )
         return rch;
      else if( ++count == number )
         return rch;
   }

   if( vnum != -1 )
      return NULL;

   /*
    * If we didn't find an exact match, run through the list of characters
    * again looking for prefix matching, ie gu == guard.
    * Added by Narn, Sept/96
    */
   count = 0;
   for( rch = ch->in_room->first_person; rch; rch = rch->next_in_room )
   {
      if( !can_see( ch, rch ) )
         continue;

      if( !nifty_is_name_prefix( arg, rch->name ) )
         continue;

      if( number == 0 && !is_npc( rch ) )
         return rch;
      else if( ++count == number )
         return rch;
   }

   return NULL;
}

/* Find a char in the world. */
CHAR_DATA *get_char_world( CHAR_DATA *ch, char *argument )
{
   char arg[MIL];
   CHAR_DATA *wch;
   int number, count, vnum;

   number = number_argument( argument, arg );
   count = 0;
   if( !str_cmp( arg, "self" ) )
      return ch;

   /* Allow reference by vnum for saints+ -Thoric */
   if( ch && get_trust( ch ) >= PERM_BUILDER && is_number( arg ) )
      vnum = atoi( arg );
   else
      vnum = -1;

   /* check the room for an exact match */
   if( ch )
      for( wch = ch->in_room->first_person; wch; wch = wch->next_in_room )
         if( can_see( ch, wch ) && ( nifty_is_name( arg, wch->name ) || ( is_npc( wch ) && vnum == wch->pIndexData->vnum ) ) )
         {
            if( number == 0 && !is_npc( wch ) )
               return wch;
            else if( ++count == number )
               return wch;
         }

   count = 0;

   /* check the world for an exact match */
   for( wch = first_char; wch; wch = wch->next )
      if( ( !ch || can_see( ch, wch ) ) && ( nifty_is_name( arg, wch->name ) || ( is_npc( wch ) && vnum == wch->pIndexData->vnum ) ) )
      {
         if( number == 0 && !is_npc( wch ) )
            return wch;
         else if( ++count == number )
            return wch;
      }

   /* bail out if looking for a vnum match */
   if( vnum != -1 )
      return NULL;

   /*
    * If we didn't find an exact match, check the room for
    * for a prefix match, ie gu == guard.
    * Added by Narn, Sept/96
    */
   count = 0;
   if( ch )
      for( wch = ch->in_room->first_person; wch; wch = wch->next_in_room )
      {
         if( !can_see( ch, wch ) || !nifty_is_name_prefix( arg, wch->name ) )
            continue;
         if( number == 0 && !is_npc( wch ) )
            return wch;
         else if( ++count == number )
            return wch;
      }

   /*
    * If we didn't find a prefix match in the room, run through the full list
    * of characters looking for prefix matching, ie gu == guard.
    * Added by Narn, Sept/96
    */
   count = 0;
   for( wch = first_char; wch; wch = wch->next )
   {
      if( ( ch && !can_see( ch, wch ) ) || !nifty_is_name_prefix( arg, wch->name ) )
         continue;
      if( number == 0 && !is_npc( wch ) )
         return wch;
      else if( ++count == number )
         return wch;
   }

   return NULL;
}

/* Find an obj in a list. */
OBJ_DATA *get_obj_list( CHAR_DATA *ch, char *argument, OBJ_DATA *list )
{
   char arg[MIL];
   OBJ_DATA *obj;
   int number, count;

   number = number_argument( argument, arg );
   count = 0;
   for( obj = list; obj; obj = obj->next_content )
      if( can_see_obj( ch, obj ) && nifty_is_name( arg, obj->name ) )
         if( ( count += obj->count ) >= number )
            return obj;

   /*
    * If we didn't find an exact match, run through the list of objects
    * again looking for prefix matching, ie swo == sword.
    * Added by Narn, Sept/96
    */
   count = 0;
   for( obj = list; obj; obj = obj->next_content )
      if( can_see_obj( ch, obj ) && nifty_is_name_prefix( arg, obj->name ) )
         if( ( count += obj->count ) >= number )
            return obj;

   return NULL;
}

/* Find an obj in a list...going the other way -Thoric */
OBJ_DATA *get_obj_list_rev( CHAR_DATA *ch, char *argument, OBJ_DATA *list )
{
   char arg[MIL];
   OBJ_DATA *obj;
   int number, count;

   number = number_argument( argument, arg );
   count = 0;
   for( obj = list; obj; obj = obj->prev_content )
      if( can_see_obj( ch, obj ) && nifty_is_name( arg, obj->name ) )
         if( ( count += obj->count ) >= number )
            return obj;

   /*
    * If we didn't find an exact match, run through the list of objects
    * again looking for prefix matching, ie swo == sword.
    * Added by Narn, Sept/96
    */
   count = 0;
   for( obj = list; obj; obj = obj->prev_content )
      if( can_see_obj( ch, obj ) && nifty_is_name_prefix( arg, obj->name ) )
         if( ( count += obj->count ) >= number )
            return obj;

   return NULL;
}

/* Find an obj in player's inventory or wearing via a vnum -Shaddai */
OBJ_DATA *get_obj_vnum( CHAR_DATA *ch, int vnum )
{
   OBJ_DATA *obj;

   for( obj = ch->last_carrying; obj; obj = obj->prev_content )
      if( can_see_obj( ch, obj ) && obj->pIndexData->vnum == vnum )
         return obj;
   return NULL;
}

/* Find an obj in player's inventory. */
OBJ_DATA *get_obj_carry( CHAR_DATA *ch, char *argument )
{
   char arg[MIL];
   OBJ_DATA *obj;
   int number, count, vnum;

   number = number_argument( argument, arg );
   if( get_trust( ch ) >= PERM_BUILDER && is_number( arg ) )
      vnum = atoi( arg );
   else
      vnum = -1;

   count = 0;
   for( obj = ch->last_carrying; obj; obj = obj->prev_content )
      if( obj->wear_loc == WEAR_NONE && can_see_obj( ch, obj )
      && ( nifty_is_name( arg, obj->name ) || obj->pIndexData->vnum == vnum ) )
         if( ( count += obj->count ) >= number )
            return obj;

   if( vnum != -1 )
      return NULL;

   /*
    * If we didn't find an exact match, run through the list of objects
    * again looking for prefix matching, ie swo == sword.
    * Added by Narn, Sept/96
    */
   count = 0;
   for( obj = ch->last_carrying; obj; obj = obj->prev_content )
      if( obj->wear_loc == WEAR_NONE && can_see_obj( ch, obj ) && nifty_is_name_prefix( arg, obj->name ) )
         if( ( count += obj->count ) >= number )
            return obj;

   return NULL;
}

/* Find an obj in player's equipment. */
OBJ_DATA *get_obj_wear( CHAR_DATA *ch, char *argument )
{
   char arg[MIL];
   OBJ_DATA *obj;
   int number, count, vnum;

   number = number_argument( argument, arg );

   if( get_trust( ch ) >= PERM_BUILDER && is_number( arg ) )
      vnum = atoi( arg );
   else
      vnum = -1;

   count = 0;
   for( obj = ch->last_carrying; obj; obj = obj->prev_content )
      if( obj->wear_loc != WEAR_NONE
          && can_see_obj( ch, obj ) && ( nifty_is_name( arg, obj->name ) || obj->pIndexData->vnum == vnum ) )
         if( ++count == number )
            return obj;

   if( vnum != -1 )
      return NULL;

   /*
    * If we didn't find an exact match, run through the list of objects
    * again looking for prefix matching, ie swo == sword.
    * Added by Narn, Sept/96
    */
   count = 0;
   for( obj = ch->last_carrying; obj; obj = obj->prev_content )
      if( obj->wear_loc != WEAR_NONE && can_see_obj( ch, obj ) && nifty_is_name_prefix( arg, obj->name ) )
         if( ++count == number )
            return obj;

   return NULL;
}

/* Find an obj in the room or in inventory. */
OBJ_DATA *get_obj_here( CHAR_DATA *ch, char *argument )
{
   OBJ_DATA *obj;

   obj = get_obj_list_rev( ch, argument, ch->in_room->last_content );
   if( obj )
      return obj;

   if( ( obj = get_obj_carry( ch, argument ) ) )
      return obj;

   if( ( obj = get_obj_wear( ch, argument ) ) )
      return obj;

   return NULL;
}

/* Find an obj in the world. */
OBJ_DATA *get_obj_world( CHAR_DATA *ch, char *argument )
{
   char arg[MIL];
   OBJ_DATA *obj;
   int number, count, vnum;

   if( ( obj = get_obj_here( ch, argument ) ) )
      return obj;

   number = number_argument( argument, arg );

   /* Allow reference by vnum for saints+ -Thoric */
   if( get_trust( ch ) >= PERM_BUILDER && is_number( arg ) )
      vnum = atoi( arg );
   else
      vnum = -1;

   count = 0;
   for( obj = first_object; obj; obj = obj->next )
      if( can_see_obj( ch, obj ) && ( nifty_is_name( arg, obj->name ) || vnum == obj->pIndexData->vnum ) )
         if( ( count += obj->count ) >= number )
            return obj;

   /* bail out if looking for a vnum */
   if( vnum != -1 )
      return NULL;

   /*
    * If we didn't find an exact match, run through the list of objects
    * again looking for prefix matching, ie swo == sword.
    * Added by Narn, Sept/96
    */
   count = 0;
   for( obj = first_object; obj; obj = obj->next )
      if( can_see_obj( ch, obj ) && nifty_is_name_prefix( arg, obj->name ) )
         if( ( count += obj->count ) >= number )
            return obj;

   return NULL;
}


/*
 * How mental state could affect finding an object		-Thoric
 * Used by get/drop/put/quaff/recite/etc
 * Increasingly freaky based on mental state and drunkeness
 */
bool ms_find_obj( CHAR_DATA *ch )
{
   int ms = ch->mental_state;
   int drunk = is_npc( ch ) ? 0 : ch->pcdata->condition[COND_DRUNK];
   char *t;

   /*
    * we're going to be nice and let nothing weird happen unless
    * you're a tad messed up
    */
   drunk = UMAX( 1, drunk );
   if( abs( ms ) + ( drunk / 3 ) < 30 )
      return false;
   if( ( number_percent( ) + ( ms < 0 ? 15 : 5 ) ) > abs( ms ) / 2 + drunk / 4 )
      return false;
   if( ms > 15 )  /* range 1 to 20 -- feel free to add more */
      switch( number_range( UMAX( 1, ( ms / 5 - 15 ) ), ( ms + 4 ) / 5 ) )
      {
         default:
         case 1:
            t = "As you reach for it, you forgot what it was...\r\n";
            break;
         case 2:
            t = "As you reach for it, something inside stops you...\r\n";
            break;
         case 3:
            t = "As you reach for it, it seems to move out of the way...\r\n";
            break;
         case 4:
            t = "You grab frantically for it, but can't seem to get a hold of it...\r\n";
            break;
         case 5:
            t = "It disappears as soon as you touch it!\r\n";
            break;
         case 6:
            t = "You would if it would stay still!\r\n";
            break;
         case 7:
            t = "Whoa!  It's covered in blood!  Ack!  Ick!\r\n";
            break;
         case 8:
            t = "Wow... trails!\r\n";
            break;
         case 9:
            t = "You reach for it, then notice the back of your hand is growing something!\r\n";
            break;
         case 10:
            t = "As you grasp it, it shatters into tiny shards which bite into your flesh!\r\n";
            break;
         case 11:
            t = "What about that huge dragon flying over your head?!?!?\r\n";
            break;
         case 12:
            t = "You stratch yourself instead...\r\n";
            break;
         case 13:
            t = "You hold the universe in the palm of your hand!\r\n";
            break;
         case 14:
            t = "You're too scared.\r\n";
            break;
         case 15:
            t = "Your mother smacks your hand... 'NO!'\r\n";
            break;
         case 16:
            t = "Your hand grasps the worst pile of revoltingness that you could ever imagine!\r\n";
            break;
         case 17:
            t = "You stop reaching for it as it screams out at you in pain!\r\n";
            break;
         case 18:
            t = "What about the millions of burrow-maggots feasting on your arm?!?!\r\n";
            break;
         case 19:
            t = "That doesn't matter anymore... you've found the true answer to everything!\r\n";
            break;
         case 20:
            t = "A supreme entity has no need for that.\r\n";
            break;
      }
   else
   {
      int sub = URANGE( 1, abs( ms ) / 2 + drunk, 60 );
      switch( number_range( 1, sub / 10 ) )
      {
         default:
         case 1:
            t = "In just a second...\r\n";
            break;
         case 2:
            t = "You can't find that...\r\n";
            break;
         case 3:
            t = "It's just beyond your grasp...\r\n";
            break;
         case 4:
            t = "...but it's under a pile of other stuff...\r\n";
            break;
         case 5:
            t = "You go to reach for it, but pick your nose instead.\r\n";
            break;
         case 6:
            t = "Which one?!?  I see two... no three...\r\n";
            break;
      }
   }
   send_to_char( t, ch );
   return true;
}

/*
 * Generic get obj function that supports optional containers.	-Thoric
 * currently only used for "eat" and "quaff".
 */
OBJ_DATA *find_obj( CHAR_DATA *ch, char *argument, bool carryonly )
{
   char arg1[MIL];
   char arg2[MIL];
   OBJ_DATA *obj = NULL;

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

   if( !str_cmp( arg2, "from" ) && argument[0] != '\0' )
      argument = one_argument( argument, arg2 );

   if( arg2[0] == '\0' )
   {
      if( carryonly && !( obj = get_obj_carry( ch, arg1 ) ) )
      {
         send_to_char( "You do not have that item.\r\n", ch );
         return NULL;
      }
      else if( !carryonly && !( obj = get_obj_here( ch, arg1 ) ) )
      {
         act( AT_PLAIN, "I see no $T here.", ch, NULL, arg1, TO_CHAR );
         return NULL;
      }
      return obj;
   }
   else
   {
      OBJ_DATA *container = NULL;

      if( carryonly && !( container = get_obj_carry( ch, arg2 ) ) && !( container = get_obj_wear( ch, arg2 ) ) )
      {
         send_to_char( "You do not have that item.\r\n", ch );
         return NULL;
      }
      if( !carryonly && !( container = get_obj_here( ch, arg2 ) ) )
      {
         act( AT_PLAIN, "I see no $T here.", ch, NULL, arg2, TO_CHAR );
         return NULL;
      }

      if( !is_obj_stat( container, ITEM_COVERING ) && IS_SET( container->value[1], CONT_CLOSED ) )
      {
         act( AT_PLAIN, "The $d is closed.", ch, NULL, container->name, TO_CHAR );
         return NULL;
      }

      obj = get_obj_list( ch, arg1, container->first_content );
      if( !obj )
         act( AT_PLAIN, is_obj_stat( container, ITEM_COVERING ) ?
              "I see nothing like that beneath $p." : "I see nothing like that in $p.", ch, container, NULL, TO_CHAR );
      return obj;
   }
}

int get_obj_number( OBJ_DATA *obj )
{
   return obj->count;
}

/* Return true if an object is, or nested inside a magic container */
bool in_magic_container( OBJ_DATA *obj )
{
   if( obj->item_type == ITEM_CONTAINER && is_obj_stat( obj, ITEM_MAGIC ) )
      return true;
   if( obj->in_obj )
      return in_magic_container( obj->in_obj );
   return false;
}

/* Return weight of an object, including weight of contents (unless magic). */
int get_obj_weight( OBJ_DATA *obj )
{
   int weight;

   weight = obj->count * obj->weight;

   /* magic containers */
   if( obj->item_type != ITEM_CONTAINER || !is_obj_stat( obj, ITEM_MAGIC ) )
      for( obj = obj->first_content; obj; obj = obj->next_content )
         weight += get_obj_weight( obj );

   return weight;
}

char *dis_class_name( int Class )
{
   if( Class < 0 || Class >= MAX_PC_CLASS || !class_table[Class] || !class_table[Class]->name )
      return "Unknown";
   return class_table[Class]->name;
}

char *dis_race_name( int race )
{
   if( race < 0 || race >= MAX_PC_RACE || !race_table[race] || !race_table[race]->name )
      return "Unknown";
   return race_table[race]->name;
}

/* Return real weight of an object, including weight of contents. */
int get_real_obj_weight( OBJ_DATA *obj )
{
   int weight;

   weight = obj->count * obj->weight;

   for( obj = obj->first_content; obj; obj = obj->next_content )
      weight += get_real_obj_weight( obj );

   return weight;
}

/* True if room is dark. */
bool room_is_dark( ROOM_INDEX_DATA *pRoomIndex )
{
   if( !pRoomIndex )
   {
      bug( "%s:: NULL pRoomIndex", __FUNCTION__ );
      return true;
   }

   if( pRoomIndex->light > 0 )
      return false;

   if( xIS_SET( pRoomIndex->room_flags, ROOM_DARK ) )
      return true;

   if( pRoomIndex->sector_type == SECT_INSIDE || pRoomIndex->sector_type == SECT_CITY )
      return false;

   if( time_info.sunlight == SUN_SET || time_info.sunlight == SUN_DARK )
      return true;

   return false;
}

/*
 * If room is "do not disturb" return the pointer to the imm with dnd flag
 * NULL if room is not "do not disturb".
 */
CHAR_DATA *room_is_dnd( CHAR_DATA *ch, ROOM_INDEX_DATA *pRoomIndex )
{
   CHAR_DATA *rch;

   if( !pRoomIndex )
   {
      bug( "%s: NULL pRoomIndex", __FUNCTION__ );
      return NULL;
   }

   if( !xIS_SET( pRoomIndex->room_flags, ROOM_DND ) )
      return NULL;

   for( rch = pRoomIndex->first_person; rch; rch = rch->next_in_room )
   {
      if( !is_npc( rch ) && rch->pcdata && is_immortal( rch )
      && xIS_SET( rch->pcdata->flags, PCFLAG_DND )
      && get_trust( ch ) < get_trust( rch ) && can_see( ch, rch ) )
         return rch;
   }
   return NULL;
}

/* True if room is private. */
bool room_is_private( ROOM_INDEX_DATA *pRoomIndex )
{
   CHAR_DATA *rch;
   int count;

   if( !pRoomIndex )
   {
      bug( "%s: NULL pRoomIndex", __FUNCTION__ );
      return false;
   }

   count = 0;
   for( rch = pRoomIndex->first_person; rch; rch = rch->next_in_room )
      count++;

   if( xIS_SET( pRoomIndex->room_flags, ROOM_PRIVATE ) && count >= 2 )
      return true;

   if( xIS_SET( pRoomIndex->room_flags, ROOM_SOLITARY ) && count >= 1 )
      return true;

   return false;
}

/* True if char can see victim. */
bool can_see( CHAR_DATA *ch, CHAR_DATA *victim )
{
   if( !victim )
      return false;
   if( !ch )
   {
      if( IS_AFFECTED( victim, AFF_INVISIBLE ) || IS_AFFECTED( victim, AFF_HIDE ) || xIS_SET( victim->act, PLR_WIZINVIS ) )
         return false;
      else
         return true;
   }

   if( ch == victim )
      return true;

   /* Wizinvis goes by get_trust */
   if( !is_npc( victim ) && xIS_SET( victim->act, PLR_WIZINVIS ) && get_trust( ch ) < victim->pcdata->wizinvis )
      return false;

   /* Mobinvis goes by level but shows to all immortals */
   if( is_npc( victim ) && xIS_SET( victim->act, ACT_MOBINVIS ) && ch->level < victim->mobinvis && !is_immortal( ch ) )
      return false;

   /* Deadlies link-dead over 2 ticks aren't seen by mortals -- Blodkai */
   if( !is_immortal( ch ) && !is_npc( ch ) && !is_npc( victim ) && is_pkill( victim ) && victim->timer > 1 && !victim->desc )
      return false;

   if( !is_npc( ch ) && xIS_SET( ch->act, PLR_HOLYLIGHT ) )
      return true;

   /* The miracle cure for blindness? -- Altrag */
   if( !IS_AFFECTED( ch, AFF_TRUESIGHT ) )
   {
      if( IS_AFFECTED( ch, AFF_BLIND ) )
         return false;

      if( room_is_dark( ch->in_room ) && !IS_AFFECTED( ch, AFF_INFRARED ) )
         return false;

      if( IS_AFFECTED( victim, AFF_INVISIBLE ) && !IS_AFFECTED( ch, AFF_DETECT_INVIS ) )
         return false;

      if( IS_AFFECTED( victim, AFF_HIDE )
          && !IS_AFFECTED( ch, AFF_DETECT_HIDDEN )
          && !victim->fighting && ( is_npc( ch ) ? !is_npc( victim ) : is_npc( victim ) ) )
         return false;
   }

   /*
    * Redone by Narn to let newbie council members see pre-auths. 
    */
   if( not_authed( victim ) )
   {
      if( not_authed( ch ) || is_immortal( ch ) || is_npc( ch ) )
         return true;

      if( ch->pcdata->council && !str_cmp( ch->pcdata->council->name, "Newbie Council" ) )
         return true;

      return false;
   }

   return true;
}

/* True if char can see obj. */
bool can_see_obj( CHAR_DATA *ch, OBJ_DATA *obj )
{
   if( !is_npc( ch ) && xIS_SET( ch->act, PLR_HOLYLIGHT ) )
      return true;

   if( is_npc( ch ) && ch->pIndexData->vnum == MOB_VNUM_SUPERMOB )
      return true;

   if( is_obj_stat( obj, ITEM_BURIED ) )
      return false;

   if( is_obj_stat( obj, ITEM_HIDDEN ) )
      return false;

   if( IS_AFFECTED( ch, AFF_TRUESIGHT ) )
      return true;

   if( IS_AFFECTED( ch, AFF_BLIND ) )
      return false;

   /* can see lights in the dark */
   if( obj->item_type == ITEM_LIGHT && obj->value[2] != 0 )
      return true;

   if( room_is_dark( ch->in_room ) )
   {
      if( is_obj_stat( obj, ITEM_GLOW ) )
         return true;
      if( !IS_AFFECTED( ch, AFF_INFRARED ) )
         return false;
   }

   if( is_obj_stat( obj, ITEM_INVIS ) && !IS_AFFECTED( ch, AFF_DETECT_INVIS ) )
      return false;

   return true;
}

/* True if char can drop obj. */
bool can_drop_obj( CHAR_DATA *ch, OBJ_DATA *obj )
{
   if( !is_obj_stat( obj, ITEM_NODROP ) )
      return true;

   if( !is_npc( ch ) && get_trust( ch ) >= PERM_IMM )
      return true;

   if( is_npc( ch ) && ch->pIndexData->vnum == MOB_VNUM_SUPERMOB )
      return true;

   return false;
}

/* Return ascii name of an affect location. */
char *affect_loc_name( int location )
{
   if( location >= 0 && location < APPLY_MAX )
      return capitalize( a_types[location] );

   bug( "%s: unknown location %d.", __FUNCTION__, location );
   return "(Unknown)";
}

/* Return ascii name of pulltype exit setting. */
char *pull_type_name( int pulltype )
{
   if( pulltype >= PT_FIRE )
      return ex_pfire[pulltype - PT_FIRE];
   if( pulltype >= PT_AIR )
      return ex_pair[pulltype - PT_AIR];
   if( pulltype >= PT_EARTH )
      return ex_pearth[pulltype - PT_EARTH];
   if( pulltype >= PT_WATER )
      return ex_pwater[pulltype - PT_WATER];
   if( pulltype < 0 )
      return "ERROR";

   return ex_pmisc[pulltype];
}

/* Set off a trap (obj) upon character (ch) -Thoric */
ch_ret spring_trap( CHAR_DATA *ch, OBJ_DATA *obj )
{
   int dam, typ, lev;
   char *txt;
   char buf[MSL];
   ch_ret retcode;

   typ = obj->value[1];
   lev = obj->value[2];

   retcode = rNONE;

   switch( typ )
   {
      default:
         txt = "hit by a trap";
         break;

      case TRAP_TYPE_POISON_GAS:
         txt = "surrounded by a green cloud of gas";
         break;

      case TRAP_TYPE_POISON_DART:
         txt = "hit by a dart";
         break;

      case TRAP_TYPE_POISON_NEEDLE:
         txt = "pricked by a needle";
         break;

      case TRAP_TYPE_POISON_DAGGER:
         txt = "stabbed by a dagger";
         break;

      case TRAP_TYPE_POISON_ARROW:
         txt = "struck with an arrow";
         break;

      case TRAP_TYPE_BLINDNESS_GAS:
         txt = "surrounded by a red cloud of gas";
         break;

      case TRAP_TYPE_SLEEPING_GAS:
         txt = "surrounded by a yellow cloud of gas";
         break;

      case TRAP_TYPE_FLAME:
         txt = "struck by a burst of flame";
         break;

      case TRAP_TYPE_EXPLOSION:
         txt = "hit by an explosion";
         break;

      case TRAP_TYPE_ACID_SPRAY:
         txt = "covered by a spray of acid";
         break;

      case TRAP_TYPE_ELECTRIC_SHOCK:
         txt = "suddenly shocked";
         break;

      case TRAP_TYPE_BLADE:
         txt = "sliced by a razor sharp blade";
         break;
   }

   dam = number_range( obj->value[2], obj->value[2] * 2 );
   snprintf( buf, sizeof( buf ), "You are %s!", txt );
   act( AT_HITME, buf, ch, NULL, NULL, TO_CHAR );
   snprintf( buf, sizeof( buf ), "$n is %s.", txt );
   act( AT_ACTION, buf, ch, NULL, NULL, TO_ROOM );
   --obj->value[0];
   if( obj->value[0] <= 0 )
      extract_obj( obj );
   switch( typ )
   {
      default:
      case TRAP_TYPE_POISON_DART:
      case TRAP_TYPE_POISON_NEEDLE:
      case TRAP_TYPE_POISON_DAGGER:
      case TRAP_TYPE_POISON_ARROW:
         retcode = obj_cast_spell( gsn_poison, lev, ch, ch, NULL );
         if( retcode == rNONE )
            retcode = damage( ch, ch, dam, TYPE_UNDEFINED );
         break;

      case TRAP_TYPE_POISON_GAS:
         retcode = obj_cast_spell( gsn_poison, lev, ch, ch, NULL );
         break;

      case TRAP_TYPE_BLINDNESS_GAS:
         retcode = obj_cast_spell( gsn_blindness, lev, ch, ch, NULL );
         break;

      case TRAP_TYPE_SLEEPING_GAS:
         retcode = obj_cast_spell( skill_lookup( "sleep" ), lev, ch, ch, NULL );
         break;

      case TRAP_TYPE_ACID_SPRAY:
         retcode = obj_cast_spell( skill_lookup( "acidshot" ), lev, ch, ch, NULL );
         break;

      case TRAP_TYPE_FLAME:
      case TRAP_TYPE_EXPLOSION:
         retcode = obj_cast_spell( skill_lookup( "fireball" ), lev, ch, ch, NULL );
         break;

      case TRAP_TYPE_ELECTRIC_SHOCK:
      case TRAP_TYPE_BLADE:
         retcode = damage( ch, ch, dam, TYPE_UNDEFINED );
         break;
   }
   return retcode;
}

/* Check an object for a trap - Thoric */
ch_ret check_for_trap( CHAR_DATA *ch, OBJ_DATA *obj, int flag )
{
   OBJ_DATA *check;
   ch_ret retcode;

   if( !obj->first_content )
      return rNONE;

   retcode = rNONE;

   for( check = obj->first_content; check; check = check->next_content )
      if( check->item_type == ITEM_TRAP && IS_SET( check->value[3], flag ) )
      {
         retcode = spring_trap( ch, check );
         if( retcode != rNONE )
            return retcode;
      }
   return retcode;
}

ch_ret check_room_for_traps( CHAR_DATA *ch, int flag )
{
   OBJ_DATA *check;
   ch_ret retcode;

   retcode = rNONE;

   if( !ch )
      return rERROR;
   if( !ch->in_room || !ch->in_room->first_content )
      return rNONE;

   for( check = ch->in_room->first_content; check; check = check->next_content )
   {
      if( check->item_type == ITEM_TRAP && IS_SET( check->value[3], flag ) )
      {
         retcode = spring_trap( ch, check );
         if( retcode != rNONE )
            return retcode;
      }
   }
   return retcode;
}

/* return true if an object contains a trap - Thoric */
bool is_trapped( OBJ_DATA *obj )
{
   OBJ_DATA *check;

   if( !obj->first_content )
      return false;

   for( check = obj->first_content; check; check = check->next_content )
      if( check->item_type == ITEM_TRAP )
         return true;

   return false;
}

/* If an object contains a trap, return the pointer to the trap - Thoric */
OBJ_DATA *get_trap( OBJ_DATA *obj )
{
   OBJ_DATA *check;

   if( !obj->first_content )
      return NULL;

   for( check = obj->first_content; check; check = check->next_content )
      if( check->item_type == ITEM_TRAP )
         return check;

   return NULL;
}

/*
 * Return a pointer to the first object of a certain type found that
 * a player is carrying/wearing
 */
OBJ_DATA *get_objtype( CHAR_DATA *ch, short type )
{
   OBJ_DATA *obj;

   for( obj = ch->first_carrying; obj; obj = obj->next_content )
      if( obj->item_type == type )
         return obj;

   return NULL;
}

/* Remove an exit from a room - Thoric */
void extract_exit( ROOM_INDEX_DATA *room, EXIT_DATA *pexit )
{
   UNLINK( pexit, room->first_exit, room->last_exit, next, prev );
   if( pexit->rexit )
      pexit->rexit->rexit = NULL;
   STRFREE( pexit->keyword );
   STRFREE( pexit->description );
   DISPOSE( pexit );
}

void clean_room( ROOM_INDEX_DATA *room )
{
   EXTRA_DESCR_DATA *ed, *ed_next;
   EXIT_DATA *pexit, *pexit_next;
   MPROG_DATA *mprog, *mprog_next;

   STRFREE( room->description );
   STRFREE( room->name );
   for( mprog = room->mudprogs; mprog; mprog = mprog_next )
   {
      mprog_next = mprog->next;
      STRFREE( mprog->arglist );
      STRFREE( mprog->comlist );
      DISPOSE( mprog );
   }
   for( ed = room->first_extradesc; ed; ed = ed_next )
   {
      ed_next = ed->next;
      STRFREE( ed->description );
      STRFREE( ed->keyword );
      DISPOSE( ed );
      top_ed--;
   }
   room->first_extradesc = NULL;
   room->last_extradesc = NULL;
   for( pexit = room->first_exit; pexit; pexit = pexit_next )
   {
      pexit_next = pexit->next;
      extract_exit( room, pexit );
      top_exit--;
   }
   room->first_exit = NULL;
   room->last_exit = NULL;
   xCLEAR_BITS( room->room_flags );
   room->sector_type = 0;
   room->light = 0;
}

void clean_obj( OBJ_INDEX_DATA *obj )
{
   AFFECT_DATA *paf, *paf_next;
   EXTRA_DESCR_DATA *ed, *ed_next;
   MPROG_DATA *mprog, *mprog_next;

   STRFREE( obj->name );
   STRFREE( obj->short_descr );
   STRFREE( obj->description );
   STRFREE( obj->action_desc );
   obj->item_type = 0;
   xCLEAR_BITS( obj->extra_flags );
   xCLEAR_BITS( obj->wear_flags );
   obj->count = 0;
   obj->weight = 0;
   obj->cost = 0;
   obj->value[0] = 0;
   obj->value[1] = 0;
   obj->value[2] = 0;
   obj->value[3] = 0;
   obj->value[4] = 0;
   obj->value[5] = 0;
   for( paf = obj->first_affect; paf; paf = paf_next )
   {
      paf_next = paf->next;
      DISPOSE( paf );
      top_affect--;
   }
   obj->first_affect = NULL;
   obj->last_affect = NULL;
   for( ed = obj->first_extradesc; ed; ed = ed_next )
   {
      ed_next = ed->next;
      STRFREE( ed->description );
      STRFREE( ed->keyword );
      DISPOSE( ed );
      top_ed--;
   }
   obj->first_extradesc = NULL;
   obj->last_extradesc = NULL;
   for( mprog = obj->mudprogs; mprog; mprog = mprog_next )
   {
      mprog_next = mprog->next;
      STRFREE( mprog->arglist );
      STRFREE( mprog->comlist );
      DISPOSE( mprog );
   }
}

/* clean out a mobile (index) (leave list pointers intact ) - Thoric */
void clean_mob( MOB_INDEX_DATA *mob )
{
   MPROG_DATA *mprog, *mprog_next;

   STRFREE( mob->name );
   STRFREE( mob->short_descr );
   STRFREE( mob->long_descr );
   STRFREE( mob->description );
   mob->spec_fun = NULL;
   mob->pShop = NULL;
   mob->rShop = NULL;
   xCLEAR_BITS( mob->progtypes );

   for( mprog = mob->mudprogs; mprog; mprog = mprog_next )
   {
      mprog_next = mprog->next;
      STRFREE( mprog->arglist );
      STRFREE( mprog->comlist );
      DISPOSE( mprog );
   }
   mob->count = 0;
   mob->killed = 0;
   mob->sex = 0;
   mob->level = 0;
   xCLEAR_BITS( mob->act );
   xCLEAR_BITS( mob->affected_by );
   mob->alignment = 0;
   mob->ac = 0;
   mob->minhit = 0;
   mob->maxhit = 0;
   mob->gold = 0;
   mob->mgold = 0;
   mob->position = 0;
   mob->defposition = 0;
   mob->height = 0;
   mob->weight = 0;
   xCLEAR_BITS( mob->attacks );
   xCLEAR_BITS( mob->defenses );
}

extern int top_reset;

/* Remove all resets from a room -Thoric */
void clean_resets( ROOM_INDEX_DATA *room )
{
   RESET_DATA *pReset, *pReset_next;

   for( pReset = room->first_reset; pReset; pReset = pReset_next )
   {
      pReset_next = pReset->next;
      delete_reset( pReset );
      --top_reset;
   }
   room->first_reset = NULL;
   room->last_reset = NULL;
}

/* Set up players base stats */
void set_base_stats( CHAR_DATA *ch )
{
   int stat;

   if( !ch )
      return;
   for( stat = 0; stat < STAT_MAX; stat++ )
   {
      if( race_table[ch->race] )
         ch->perm_stats[stat] = race_table[ch->race]->base_stats[stat];
      else
         ch->perm_stats[stat] = 13;
   }
   if( race_table[ch->race] )
   {
      ch->max_hit = race_table[ch->race]->hit;
      ch->max_mana = race_table[ch->race]->mana;
      ch->max_move = race_table[ch->race]->move;
      ch->height = number_range( race_table[ch->race]->minheight, race_table[ch->race]->maxheight );
      ch->weight = number_range( race_table[ch->race]->minweight, race_table[ch->race]->maxweight );
      ch->alignment = URANGE( race_table[ch->race]->minalign, race_table[ch->race]->alignment, race_table[ch->race]->maxalign );
      ch->armor = race_table[ch->race]->ac_plus;
   }
   else
   {
      ch->max_hit = 100;
      ch->max_mana = 100;
      ch->max_move = 100;
      ch->height = number_range( 50, 100 );
      ch->weight = number_range( 150, 300 );
      ch->alignment = 0;
   }

   ch->hit = UMAX( 1, ch->max_hit );
   ch->mana = UMAX( 1, ch->max_mana );
   ch->move = UMAX( 1, ch->max_move );
}

bool is_obj_stat( OBJ_DATA *obj, int stat )
{
   if( xIS_SET( obj->extra_flags, stat ) )
      return true;
   return false;
}

bool can_wear( OBJ_DATA *obj, int part )
{
   if( xIS_SET( obj->wear_flags, part ) )
      return true;
   return false;
}

bool is_retired( CHAR_DATA *ch )
{
   if( ch->pcdata && xIS_SET( ch->pcdata->flags, PCFLAG_RETIRED ) )
      return true;
   return false;
}

bool is_guest( CHAR_DATA *ch )
{
   if( ch->pcdata && xIS_SET( ch->pcdata->flags, PCFLAG_GUEST ) )
      return true;
   return false;
}

bool is_idle( CHAR_DATA *ch )
{
   if( ch->pcdata && xIS_SET( ch->pcdata->flags, PCFLAG_IDLE ) )
      return true;
   return false;
}

bool is_pkill( CHAR_DATA *ch )
{
   if( ch->pcdata && xIS_SET( ch->pcdata->flags, PCFLAG_DEADLY ) )
      return true;
   return false;
}

bool can_pkill( CHAR_DATA *ch )
{
   if( is_pkill( ch ) && ch->level >= 5 && get_age( ch ) >= 18 )
      return true;
   return false;
}

bool is_awake( CHAR_DATA *ch )
{
   if( ch->position > POS_SLEEPING )
      return true;
   return false;
}

bool is_outside( CHAR_DATA *ch )
{
   if( !xIS_SET( ch->in_room->room_flags, ROOM_INDOORS )
   && !xIS_SET( ch->in_room->room_flags, ROOM_TUNNEL ) )
      return true;
   return false;
}

bool no_weather_sect( ROOM_INDEX_DATA *room )
{
   if( room->sector_type == SECT_INSIDE || room->sector_type == SECT_UNDERWATER
   || room->sector_type == SECT_OCEANFLOOR || room->sector_type == SECT_UNDERGROUND )
      return true;
   return false;
}

bool is_drunk( CHAR_DATA *ch, int drunk )
{
   if( number_percent( ) < ( ( ch->pcdata->condition[COND_DRUNK] * 2 ) / drunk ) )
      return true;
   return false;
}

bool is_devoted( CHAR_DATA *ch )
{
   if( !is_npc( ch ) && ch->pcdata->deity )
      return true;
   return false;
}

bool is_vampire( CHAR_DATA *ch )
{
   if( !is_npc( ch ) && race_table[ch->race] && race_table[ch->race]->uses == 2 )
      return true;
   return false;
}

bool is_good( CHAR_DATA *ch )
{
   if( ch->alignment >= 350 )
      return true;
   return false;
}

bool is_evil( CHAR_DATA *ch )
{
   if( ch->alignment <= -350 )
      return true;
   return false;
}

bool is_neutral( CHAR_DATA *ch )
{
   if( !is_good( ch ) && !is_evil( ch ) )
      return true;
   return false;
}

bool can_cast( CHAR_DATA *ch )
{
   if( race_table[ch->race] && ( race_table[ch->race]->uses == 1 || race_table[ch->race]->uses == 2 ) )
      return true;
   return false;
}

bool in_clan( CHAR_DATA *ch )
{
   if( !is_npc( ch ) && ch->pcdata->clan )
      return true;
   return false;
}

bool is_clanned( CHAR_DATA *ch )
{
   if( in_clan( ch ) && ch->pcdata->clan->clan_type == CLAN_PLAIN )
      return true;
   return false;
}

bool in_nation( CHAR_DATA *ch )
{
   if( !is_npc( ch ) && ch->pcdata->nation )
      return true;
   return false;
}

bool is_nationed( CHAR_DATA *ch )
{
   if( in_nation( ch ) && ch->pcdata->nation->clan_type == CLAN_NATION )
      return true;
   return false;
}

bool in_council( CHAR_DATA *ch )
{
   if( !is_npc( ch ) && ch->pcdata->council )
      return true;
   return false;
}

bool is_counciled( CHAR_DATA *ch )
{
   if( in_council( ch ) )
      return true;
   return false;
}

bool not_authed( CHAR_DATA *ch )
{
   if( !is_npc( ch ) && ch->pcdata->auth_state <= 3 && xIS_SET( ch->pcdata->flags, PCFLAG_UNAUTHED ) )
      return true;
   return false;
}

bool is_waiting_for_auth( CHAR_DATA *ch )
{
   if( !is_npc( ch ) && ch->desc && ch->pcdata->auth_state == 1 && xIS_SET( ch->pcdata->flags, PCFLAG_UNAUTHED ) )
      return true;
   return false;
}

bool is_pacifist( CHAR_DATA *ch )
{
   if( is_npc( ch ) && xIS_SET( ch->act, ACT_PACIFIST ) )
      return true;
   return false;
}

bool is_valid_herb( int sn )
{
   if( sn < 0 || sn >= MAX_HERB )
      return false;
   if( !herb_table[sn] || !herb_table[sn]->name )
      return false;
   return true;
}

bool is_valid_sn( int sn )
{
   if( sn < 0 || sn >= MAX_SKILL )
      return false;
   if( !skill_table[sn] || !skill_table[sn]->name )
      return false;
   return true;
}

void wait_state( CHAR_DATA *ch, int npulse )
{
   if( !is_npc( ch ) && is_immortal( ch ) )
      ch->wait = 1;
   else
      ch->wait = UMAX( ch->wait, npulse );
   return;
}

bool can_go( ROOM_INDEX_DATA *room, int door )
{
   EXIT_DATA *exit;

   if( !room || !( exit = get_exit( room, door ) ) )
      return false;
   if( !exit->to_room || xIS_SET( exit->exit_info, EX_CLOSED ) )
      return false;
   return true;
}

bool is_floating( CHAR_DATA *ch )
{
   if( !IS_AFFECTED( ch, AFF_FLYING ) && !IS_AFFECTED( ch, AFF_FLOATING ) )
      return false;
   return true;
}

/* Show an affect verbosely to a character -Thoric */
void showaffect( CHAR_DATA *ch, AFFECT_DATA *paf )
{
   char buf[MSL];

   if( !paf )
   {
      bug( "%s: NULL paf", __FUNCTION__ );
      return;
   }
   if( paf->location != APPLY_NONE )
   {
      switch( paf->location )
      {
         default:
            snprintf( buf, sizeof( buf ), "Affects %s by %d.\r\n", affect_loc_name( paf->location ), paf->modifier );
            break;
         case APPLY_EXT_AFFECT:
            snprintf( buf, sizeof( buf ), "Affects %s by %s\r\n", affect_loc_name( paf->location ), a_flags[paf->modifier] );
            break;
         case APPLY_WEAPONSPELL:
         case APPLY_WEARSPELL:
         case APPLY_REMOVESPELL:
            snprintf( buf, sizeof( buf ), "Casts spell '%s'\r\n", is_valid_sn( paf->modifier ) ? skill_table[paf->modifier]->name : "unknown" );
            break;
         case APPLY_RESISTANT:
         case APPLY_IMMUNE:
         case APPLY_SUSCEPTIBLE:
            snprintf( buf, sizeof( buf ), "Affects %s by %s\r\n", affect_loc_name( paf->location ), ris_flags[paf->modifier] );
            break;
      }
      send_to_char( buf, ch );
   }
}

/* Set the current global character to ch - Thoric */
void set_cur_char( CHAR_DATA *ch )
{
   cur_char = ch;
   cur_char_died = false;
   global_retcode = rNONE;
}

/* Check to see if ch died recently - Thoric */
bool char_died( CHAR_DATA *ch )
{
   EXTRACT_CHAR_DATA *ccd;

   if( ch == cur_char && cur_char_died )
      return true;

   for( ccd = extracted_char_queue; ccd; ccd = ccd->next )
      if( ccd->ch == ch )
         return true;
   return false;
}

/* Add ch to the queue of recently extracted characters - Thoric */
void queue_extracted_char( CHAR_DATA *ch, bool extract )
{
   EXTRACT_CHAR_DATA *ccd;

   if( !ch )
   {
      bug( "%s: ch = NULL", __FUNCTION__ );
      return;
   }
   CREATE( ccd, EXTRACT_CHAR_DATA, 1 );
   ccd->ch = ch;
   ccd->room = ch->in_room;
   ccd->extract = extract;
   if( ch == cur_char )
      ccd->retcode = global_retcode;
   else
      ccd->retcode = rCHAR_DIED;
   ccd->next = extracted_char_queue;
   extracted_char_queue = ccd;
   cur_qchars++;
}

/* clean out the extracted character queue */
void clean_char_queue( void )
{
   EXTRACT_CHAR_DATA *ccd;

   for( ccd = extracted_char_queue; ccd; ccd = extracted_char_queue )
   {
      extracted_char_queue = ccd->next;
      if( ccd->extract )
         free_char( ccd->ch );
      DISPOSE( ccd );
      --cur_qchars;
   }
}

/*
 * Add a timer to ch						-Thoric
 * Support for "call back" time delayed commands
 */
void add_timer( CHAR_DATA *ch, short type, int count, DO_FUN *fun, int value )
{
   TIMER *timer;
    
   for( timer = ch->first_timer; timer; timer = timer->next )
   {
      if( timer->type == type )
      {
         timer->count = count;
         timer->do_fun = fun;
         timer->value = value;
         break;
      }
   }
   if( !timer )
   {
      CREATE( timer, TIMER, 1 );
      timer->count = count;
      timer->type = type;
      timer->do_fun = fun;
      timer->value = value;
      LINK( timer, ch->first_timer, ch->last_timer, next, prev );
   }
   return;
}

TIMER *get_timerptr( CHAR_DATA *ch, short type )
{
   TIMER *timer;

   for( timer = ch->first_timer; timer; timer = timer->next )
      if( timer->type == type )
         return timer;
   return NULL;
}

short get_timer( CHAR_DATA *ch, short type )
{
   TIMER *timer;

   if( ( timer = get_timerptr( ch, type ) ) )
      return timer->count;
   else
      return 0;
}

void extract_timer( CHAR_DATA *ch, TIMER *timer )
{
   if( !ch )
   {
      bug( "%s: NULL ch", __FUNCTION__ );
      return;
   }
   if( !timer )
   {
      bug( "%s: NULL timer", __FUNCTION__ );
      return;
   }

   UNLINK( timer, ch->first_timer, ch->last_timer, next, prev );
   DISPOSE( timer );
   return;
}

void remove_timer( CHAR_DATA *ch, short type )
{
   TIMER *timer;

   for( timer = ch->first_timer; timer; timer = timer->next )
      if( timer->type == type )
         break;

   if( timer )
      extract_timer( ch, timer );
}

bool in_soft_range( CHAR_DATA *ch, AREA_DATA *tarea )
{
   if( is_immortal( ch ) )
      return true;
   else if( is_npc( ch ) )
      return true;
   else if( ch->level >= tarea->low_soft_range || ch->level <= tarea->hi_soft_range )
      return true;
   else
      return false;
}

bool can_astral( CHAR_DATA *ch, CHAR_DATA *victim )
{
   if( victim == ch
   || !victim->in_room
   || xIS_SET( victim->in_room->room_flags, ROOM_PRIVATE )
   || xIS_SET( victim->in_room->room_flags, ROOM_SOLITARY )
   || xIS_SET( victim->in_room->room_flags, ROOM_NO_ASTRAL )
   || xIS_SET( victim->in_room->room_flags, ROOM_DEATH )
   || xIS_SET( ch->in_room->room_flags, ROOM_NO_RECALL )
   || victim->level >= ch->level + 15
   || ( can_pkill( victim ) && !is_npc( ch ) && !can_pkill( ch ) )
   || ( is_npc( victim ) && xIS_SET( victim->act, ACT_PROTOTYPE ) )
   || ( is_npc( victim ) && saves_spell_staff( ch->level, victim ) )
   || ( xIS_SET( victim->in_room->area->flags, AFLAG_NOPKILL ) && is_pkill( ch ) ) )
      return false;
   else
      return true;
}

bool in_hard_range( CHAR_DATA *ch, AREA_DATA *tarea )
{
   if( is_immortal( ch ) )
      return true;
   else if( is_npc( ch ) )
      return true;
   else if( ch->level >= tarea->low_hard_range && ch->level <= tarea->hi_hard_range )
      return true;
   else
      return false;
}

/* Scryn, standard luck check 2/2/96 */
bool chance( CHAR_DATA *ch, short percent )
{
   short deity_factor, ms;

   if( !ch )
   {
      bug( "%s: null ch!", __FUNCTION__ );
      return false;
   }

   /*
    * Mental state bonus/penalty:  Your mental state is a ranged value with
    * zero (0) being at a perfect mental state (bonus of 10).
    * negative values would reflect how sedated one is, and
    * positive values would reflect how stimulated one is.
    * In most circumstances you'd do best at a perfectly balanced state.
    */
   if( is_devoted( ch ) )
      deity_factor = ch->pcdata->favor / -500;
   else
      deity_factor = 0;

   ms = 10 - abs( ch->mental_state );

   if( ( ( number_percent( ) * 3 ) - get_curr_lck( ch ) - ms ) + deity_factor <= percent )
      return true;
   else
      return false;
}

/* Make a simple clone of an object (no extras...yet) -Thoric */
OBJ_DATA *clone_object( OBJ_DATA *obj )
{
   OBJ_DATA *clone;

   CREATE( clone, OBJ_DATA, 1 );
   clone->pIndexData = obj->pIndexData;
   clone->name = QUICKLINK( obj->name );
   clone->short_descr = QUICKLINK( obj->short_descr );
   clone->description = QUICKLINK( obj->description );
   clone->action_desc = QUICKLINK( obj->action_desc );
   clone->owner = QUICKLINK( obj->owner );
   clone->item_type = obj->item_type;
   clone->extra_flags = obj->extra_flags;
   clone->wear_flags = obj->wear_flags;
   clone->wear_loc = obj->wear_loc;
   clone->t_wear_loc = obj->t_wear_loc;
   clone->weight = obj->weight;
   clone->cost = obj->cost;
   clone->level = obj->level;
   clone->timer = obj->timer;
   clone->value[0] = obj->value[0];
   clone->value[1] = obj->value[1];
   clone->value[2] = obj->value[2];
   clone->value[3] = obj->value[3];
   clone->value[4] = obj->value[4];
   clone->value[5] = obj->value[5];
   clone->count = 1;
   ++obj->pIndexData->count;
   ++numobjsloaded;
   ++physicalobjects;
   LINK( clone, first_object, last_object, next, prev );
   if( clone->pIndexData->vnum == OBJ_VNUM_CORPSE_PC )
      LINK( clone, first_corpse, last_corpse, next_corpse, prev_corpse );
   return clone;
}

/*
 * If possible group obj2 into obj1				-Thoric
 * This code, along with clone_object, obj->count, and special support
 * for it implemented throughout handler.c and save.c should show improved
 * performance on MUDs with players that hoard tons of potions and scrolls
 * as this will allow them to be grouped together both in memory, and in
 * the player files.
 */
OBJ_DATA *group_object( OBJ_DATA *obj1, OBJ_DATA *obj2 )
{
   if( !obj1 || !obj2 )
      return NULL;
   if( obj1 == obj2 )
      return obj1;

   if( obj1->pIndexData == obj2->pIndexData
   && !str_cmp( obj1->name, obj2->name )
   && !str_cmp( obj1->short_descr, obj2->short_descr )
   && !str_cmp( obj1->description, obj2->description )
   && !str_cmp( obj1->action_desc, obj2->action_desc )
   && !str_cmp( obj1->owner, obj2->owner )
   && obj1->item_type == obj2->item_type 
   && xSAME_BITS( obj1->extra_flags, obj2->extra_flags )
   && xSAME_BITS( obj1->wear_flags, obj2->wear_flags )
   && obj1->wear_loc == obj2->wear_loc
   && obj1->t_wear_loc == obj2->t_wear_loc
   && obj1->weight == obj2->weight
   && obj1->cost == obj2->cost
   && obj1->level == obj2->level
   && obj1->timer == obj2->timer
   && obj1->value[0] == obj2->value[0]
   && obj1->value[1] == obj2->value[1]
   && obj1->value[2] == obj2->value[2]
   && obj1->value[3] == obj2->value[3]
   && obj1->value[4] == obj2->value[4]
   && obj1->value[5] == obj2->value[5]
   && !obj1->first_extradesc && !obj2->first_extradesc
   && !obj1->first_affect && !obj2->first_affect
   && !obj1->first_content && !obj2->first_content
   && obj1->count + obj2->count > 0 )   /* prevent count overflow */
   {
      obj1->count += obj2->count;
      obj1->pIndexData->count += obj2->count;   /* to be decremented in */
      numobjsloaded += obj2->count; /* extract_obj */
      extract_obj( obj2 );
      return obj1;
   }
   return obj2;
}

/*
 * Split off a grouped object - Thoric
 * decreased obj's count to num, and creates a new object containing the rest
 */
void split_obj( OBJ_DATA *obj, int num )
{
   int count = obj->count;
   OBJ_DATA *rest;

   if( count <= num || num == 0 )
      return;

   rest = clone_object( obj );
   --obj->pIndexData->count;  /* since clone_object() ups this value */
   --numobjsloaded;
   rest->count = obj->count - num;
   obj->count = num;

   if( obj->carried_by )
   {
      LINK( rest, obj->carried_by->first_carrying, obj->carried_by->last_carrying, next_content, prev_content );
      rest->carried_by = obj->carried_by;
      rest->in_room = NULL;
      rest->in_obj = NULL;
   }
   else if( obj->in_room )
   {
      LINK( rest, obj->in_room->first_content, obj->in_room->last_content, next_content, prev_content );
      rest->carried_by = NULL;
      rest->in_room = obj->in_room;
      rest->in_obj = NULL;
   }
   else if( obj->in_obj )
   {
      LINK( rest, obj->in_obj->first_content, obj->in_obj->last_content, next_content, prev_content );
      rest->in_obj = obj->in_obj;
      rest->in_room = NULL;
      rest->carried_by = NULL;
   }
}

void separate_obj( OBJ_DATA *obj )
{
   split_obj( obj, 1 );
}

/* Empty an obj's contents... optionally into another obj, or a room */
bool empty_obj( OBJ_DATA *obj, OBJ_DATA *destobj, ROOM_INDEX_DATA *destroom )
{
   OBJ_DATA *otmp, *otmp_next;
   CHAR_DATA *ch = obj->carried_by;
   bool movedsome = false;

   if( !obj )
   {
      bug( "%s: NULL obj", __FUNCTION__ );
      return false;
   }
   if( destobj || ( !destroom && !ch && ( destobj = obj->in_obj ) ) )
   {
      for( otmp = obj->first_content; otmp; otmp = otmp_next )
      {
         otmp_next = otmp->next_content;
         /* only keys on a keyring */
         if( destobj->item_type == ITEM_KEYRING && otmp->item_type != ITEM_KEY )
            continue;
         if( destobj->item_type == ITEM_QUIVER && otmp->item_type != ITEM_PROJECTILE )
            continue;
         if( ( destobj->item_type == ITEM_CONTAINER || destobj->item_type == ITEM_KEYRING
         || destobj->item_type == ITEM_QUIVER )
         && get_real_obj_weight( otmp ) + get_real_obj_weight( destobj ) > destobj->value[0] )
            continue;
         obj_from_obj( otmp );
         obj_to_obj( otmp, destobj );
         movedsome = true;
      }
      return movedsome;
   }
   if( destroom || ( !ch && ( destroom = obj->in_room ) ) )
   {
      for( otmp = obj->first_content; otmp; otmp = otmp_next )
      {
         otmp_next = otmp->next_content;
         if( ch && HAS_PROG( otmp->pIndexData, DROP_PROG ) && otmp->count > 1 )
         {
            separate_obj( otmp );
            obj_from_obj( otmp );
            if( !otmp_next )
               otmp_next = obj->first_content;
         }
         else
            obj_from_obj( otmp );
         otmp = obj_to_room( otmp, destroom );
         if( ch )
         {
            oprog_drop_trigger( ch, otmp );  /* mudprogs */
            if( char_died( ch ) )
               ch = NULL;
         }
         movedsome = true;
      }
      return movedsome;
   }
   if( ch )
   {
      for( otmp = obj->first_content; otmp; otmp = otmp_next )
      {
         otmp_next = otmp->next_content;
         obj_from_obj( otmp );
         obj_to_char( otmp, ch );
         movedsome = true;
      }
      return movedsome;
   }
   bug( "%s: could not determine a destination for vnum %d", __FUNCTION__, obj->pIndexData->vnum );
   return false;
}

/* Improve mental state - Thoric */
void better_mental_state( CHAR_DATA *ch, int mod )
{
   int c = URANGE( 0, abs( mod ), 20 );
   int con = get_curr_con( ch );

   c += number_percent( ) < con ? 1 : 0;

   if( ch->mental_state < 0 )
      ch->mental_state = URANGE( -100, ch->mental_state + c, 0 );
   else if( ch->mental_state > 0 )
      ch->mental_state = URANGE( 0, ch->mental_state - c, 100 );
}

/* Deteriorate mental state - Thoric */
void worsen_mental_state( CHAR_DATA *ch, int mod )
{
   int c = URANGE( 0, abs( mod ), 20 );
   int con = get_curr_con( ch );

   c -= number_percent( ) < con ? 1 : 0;
   if( c < 1 )
      return;

   if( ch->mental_state < 0 )
      ch->mental_state = URANGE( -100, ch->mental_state - c, 100 );
   else if( ch->mental_state > 0 )
      ch->mental_state = URANGE( -100, ch->mental_state + c, 100 );
   else
      ch->mental_state -= c;
}

/*
 * Add another notch on that there belt... ;)
 * Keep track of the last so many kills by vnum - Thoric
 */
void add_kill( CHAR_DATA *ch, CHAR_DATA *mob )
{
   int x, vnum;
   short track;

   if( is_npc( ch ) )
   {
      bug( "%s: trying to add kill to npc", __FUNCTION__ );
      return;
   }

   if( !is_npc( mob ) )
   {
      bug( "%s: trying to add kill non-npc", __FUNCTION__ );
      return;
   }

   vnum = mob->pIndexData->vnum;
   track = URANGE( 2, ( ( ch->level + 3 ) * MAX_KILLTRACK ) / MAX_LEVEL, MAX_KILLTRACK );

   /* Already in the history? */
   for( x = 0; x < track; x++ )
   {
      if( ch->pcdata->killed[x].vnum == vnum )
      {
         if( ch->pcdata->killed[x].count < 50 )
            ++ch->pcdata->killed[x].count;
         return;
      }
      else if( ch->pcdata->killed[x].vnum == 0 )
         break;
   }

   /* Move the history down so 0 is empty */
   memmove( (char *)ch->pcdata->killed + sizeof( KILLED_DATA ), ch->pcdata->killed, (track - 1) * sizeof( KILLED_DATA ) );

   /* Set the new kill history (in 0 spot) */
   ch->pcdata->killed[0].vnum = vnum;
   ch->pcdata->killed[0].count = 1;

   /* Clear out the rest from track to MAX_KILLTRACK */
   while( track < MAX_KILLTRACK )
   {
      ch->pcdata->killed[track].vnum = 0;
      ch->pcdata->killed[track].count = 0;
      track++;
   }
}

/*
 * Return how many times this player has killed this mob	-Thoric
 * Only keeps track of so many (MAX_KILLTRACK), and keeps track by vnum
 */
int times_killed( CHAR_DATA *ch, CHAR_DATA *mob )
{
   int vnum, x;

   if( is_npc( ch ) )
   {
      bug( "%s: ch is not a player", __FUNCTION__ );
      return 0;
   }

   if( !is_npc( mob ) )
   {
      bug( "%s: mob is not a mobile", __FUNCTION__ );
      return 0;
   }

   vnum = mob->pIndexData->vnum;
   for( x = 0; x < MAX_KILLTRACK; x++ )
   {
      if( !ch->pcdata->killed[x].vnum )
         continue;

      if( ch->pcdata->killed[x].vnum == vnum )
         return ch->pcdata->killed[x].count;
   }
   return 0;
}

/*
 * returns area with name matching input string
 * Last Modified : July 21, 1997
 * Fireblade
 */
AREA_DATA *get_area( char *name )
{
   AREA_DATA *pArea;

   if( !name )
   {
      bug( "%s: NULL input string.", __FUNCTION__ );
      return NULL;
   }

   for( pArea = first_area; pArea; pArea = pArea->next )
   {
      if( nifty_is_name( name, pArea->name ) )
         break;
   }

   if( !pArea )
   {
      for( pArea = first_build; pArea; pArea = pArea->next )
      {
         if( nifty_is_name( name, pArea->name ) )
            break;
      }
   }

   return pArea;
}

AREA_DATA *get_area_obj( OBJ_INDEX_DATA *pObjIndex )
{
   AREA_DATA *pArea;

   if( !pObjIndex )
   {
      bug( "%s: pObjIndex is NULL.", __FUNCTION__ );
      return NULL;
   }
   for( pArea = first_area; pArea; pArea = pArea->next )
   {
      if( pObjIndex->vnum >= pArea->low_vnum && pObjIndex->vnum <= pArea->hi_vnum )
         break;
   }
   return pArea;
}

int umin( int check, int ncheck )
{
   if( check < ncheck )
      return check;
   return ncheck;
}

int umax( int check, int ncheck )
{
   if( check > ncheck )
      return check;
   return ncheck;
}

int urange( int mincheck, int check, int maxcheck )
{
   if( check < mincheck )
      return mincheck;
   if( check > maxcheck )
      return maxcheck;
   return check;
}

double dumin( double check, double ncheck )
{
   if( check < ncheck )
      return check;
   return ncheck;
}

double dumax( double check, double ncheck )
{
   if( check > ncheck )
      return check;
   return ncheck;
}

double durange( double mincheck, double check, double maxcheck )
{
   if( check < mincheck )
      return mincheck;
   if( check > maxcheck )
      return maxcheck;
   return check;
}