SmaugWizard/Backup/
SmaugWizard/Backup/L/
SmaugWizard/Boards/
SmaugWizard/Building/
SmaugWizard/Corpses/
SmaugWizard/Councils/
SmaugWizard/Deity/
SmaugWizard/Gods/
SmaugWizard/MudProgs/
SmaugWizard/Player/L/
SmaugWizard/Src/
SmaugWizard/Src/res/
/****************************************************************************
 * [S]imulated [M]edieval [A]dventure multi[U]ser [G]ame      |				*
 * -----------------------------------------------------------|   \\._.//	*
 * SmaugWiz (C) 1998 by Russ Pillsbury (Windows NT version)   |   (0...0)	*
 * -----------------------------------------------------------|    ).:.(	*
 * SMAUG (C) 1994, 1995, 1996 by Derek Snider                 |    {o o}	*
 * -----------------------------------------------------------|   / ' ' \	*
 * SMAUG code team: Thoric, Altrag, Blodkai, Narn, Haus,      |~'~.VxvxV.~'~*
 * Scryn, Swordbearer, Rennard, Tricops, and Gorog.           |				*
 * ------------------------------------------------------------------------ *
 * Merc 2.1 Diku Mud improvments copyright (C) 1992, 1993 by Michael        *
 * Chastain, Michael Quan, and Mitchell Tse.                                *
 * Original Diku Mud copyright (C) 1990, 1991 by Sebastian Hammer,          *
 * Michael Seifert, Hans Henrik Staerfeldt, Tom Madsen, and Katja Nyboe.    *
 * ------------------------------------------------------------------------ *
 *			      Regular update module									    *
 ****************************************************************************/

#include	"stdafx.h"
#include	"smaug.h"
#include	"SysData.h"
#include	"skill.h"
#include	"mobiles.h"
#include	"objects.h"
#include	"rooms.h"
#include	"area.h"
#include	"auction.h"
#include	"races.h"
#include	"class.h"
#include	"Exits.h"
#include	"SmaugWizDoc.h"
#include	"SmaugFiles.h"
#include	"descriptor.h"
#include	"character.h"
#include	"Smaugx.h"


// Local functions.
int		hit_gain (CCharacter *ch);
int		mana_gain (CCharacter *ch);
int		move_gain (CCharacter *ch);
void	mobile_update ();
void	weather_update ();
void	char_update ();
void	obj_update ();
void	aggr_update ();
void	room_act_update ();
void	obj_act_update ();
void	char_check ();
void    drunk_randoms (CCharacter *ch);
void    halucinations (CCharacter *ch);
void	subtract_times (timeval *etime, timeval *stime);

// Global Variables
CCharacter		*gch_prev;
CObjData		*gobj_prev;
CCharacter		*timechar;

char * corpse_descs [] = { 
     "The corpse of %s is in the last stages of decay.", 
     "The corpse of %s is crawling with vermin.",
     "The corpse of %s fills the air with a foul stench.",
     "The corpse of %s is buzzing with flies.",
     "The corpse of %s lies here." };

extern int      top_exit;


// Advancement stuff.
void advance_level (CCharacter *ch)
{
	char	buf [MAX_STRING_LENGTH];
	int		add_hp;
	int		add_mana;
	int		add_move;
	int		add_prac;

	// save_char_obj (ch);
	CClassData	*pClass = ClassTable.GetClassData (ch->GetClass ());
	sprintf (buf, "the %s", pClass->GetTitle (ch->GetLevel (),
		ch->GetSex () == SEX_FEMALE ? 1 : 0));
	set_title (ch, buf);

	add_hp = con_app [ch->GetConstitution ()].hitp + number_range (
		pClass->GetMinHp (), pClass->GetMaxHp ());

	add_mana = pClass->HasManaGain ()
		? number_range (2, (2*ch->GetIntelligence ()+ch->GetWisdom ())/8) : 0;

	add_move = number_range (5, (ch->GetConstitution ()
		+ ch->GetDexterity ()) / 4);

	add_prac = wis_app [ch->GetWisdom ()].practice;

	add_hp = UMAX (1, add_hp);
	add_mana = UMAX (0, add_mana);
	add_move = UMAX (10, add_move);

	// bonus for deadlies
	if (ch->IsPkiller ()) {
		add_mana = (int) (add_mana + add_mana*.3);
		add_move = (int) (add_move + add_move*.3);
		++add_hp;			// bitch at blod if you don't like this :)
		sprintf (buf, "Gravoc's Pandect steels your sinews.\n\r");
	}

	ch->AddMaxHp (add_hp);
	ch->AddMaxMana (add_mana);
	ch->AddMaxMove (add_move);
	ch->AddPractices (add_prac);

	if (! ch->IsNpc ())
		ch->ClrActBit (PLR_BOUGHT_PET);

	if (ch->GetLevel () == LEVEL_AVATAR) {
		sprintf (buf, "%s has just achieved Avatarhood!", ch->GetName ());
		POSITION	pos = DList.GetHeadPosition ();
		while (pos) {
			CDescriptor *d = DList.GetNext (pos);
			if (d->IsDisconnecting ())
				continue;

			if (d->m_Connected == CON_PLAYING && d->m_pCharacter != ch) {
				set_char_color (AT_IMMORT, d->m_pCharacter);
				d->m_pCharacter->SendText (buf);
				d->m_pCharacter->SendText ("\n\r");
			}
		}
		set_char_color (AT_WHITE, ch);
		do_help (ch, "M_ADVHERO_");
	}

	if (ch->GetLevel () < LEVEL_IMMORTAL) {
		if (ch->IsVampire ())
			sprintf (buf,
				"Your gain is: %d/%d hp, %d/%d bp, %d/%d mv %d/%d prac.\n\r",
				add_hp,	ch->GetMaxHp (), 1, ch->GetLevel () + 10,
				add_move, ch->GetMaxMove (),
				add_prac, ch->GetPractices ());
		else
			sprintf (buf,
				"Your gain is: %d/%d hp, %d/%d mana, %d/%d mv %d/%d prac.\n\r",
				add_hp,	ch->GetMaxHp (), add_mana, ch->GetMaxMana (),
				add_move, ch->GetMaxMove (), add_prac, ch->GetPractices ());

		set_char_color (AT_WHITE, ch);
		ch->SendText (buf);

		if (SysData.IsShowLevel () && ch->GetLevel () > 10) {
			sprintf (buf, "Tales of heroic deeds spread as %s's name is on the tip "
				"of every story tellers tongue.\n\r", ch->GetName ());  
			echo_to_all (AT_YELLOW, buf, ECHOTAR_ALL);
		}

		if (SysData.IsLevelMsg ()) {
			supermob->SetTrust (LEVEL_SUPREME);
			do_restore (supermob, NCCP ch->GetName ());
			supermob->SetTrust (0);
		}
	}
}   



void gain_exp (CCharacter *ch, int gain)
{
	int		modgain;
	char	buf [MAX_STRING_LENGTH];

	if (ch->IsNpc () || ch->GetLevel () >= LEVEL_AVATAR)
		return;


	// Bonus for deadly lowbies
	modgain = gain;
	if (modgain > 0 && ch->IsPkiller () && ch->GetLevel () < 17) {
		if (ch->GetLevel () <= 6) {
			sprintf (buf, "The Favor of Gravoc fosters your learning.\n\r");
			modgain *= 2;
		}
		if (ch->GetLevel () <= 10 && ch->GetLevel () >= 7) {
			sprintf (buf, "The Hand of Gravoc hastens your learning.\n\r");
			modgain = (int) (modgain * 1.75);
		}
		if (ch->GetLevel () <= 13 && ch->GetLevel () >= 11) {
			sprintf (buf, "The Cunning of Gravoc succors your learning.\n\r");
			modgain = (int) (modgain * 1.5);
		}
		if (ch->GetLevel () <= 16 && ch->GetLevel () >= 14) {
			sprintf (buf, "The Patronage of Gravoc reinforces your learning.\n\r");
			modgain = (int) (modgain * 1.25);
		}
		ch->SendText (buf);
	}

	// per-race experience multipliers
	modgain = (int) (modgain *
		(RaceTable.GetRaceData (ch->GetRace ())->GetExpMultiplier () / 100.0));

	// Deadly exp loss floor is exp floor of level
	if (ch->IsPkiller () && modgain < 0) {
		if (ch->GetExp () + modgain < exp_level (ch, ch->GetLevel ())) {
			modgain = exp_level (ch, ch->GetLevel ()) - ch->GetExp ();
			sprintf (buf, "Gravoc's Pandect protects your insight.\n\r");
		}
	}

	ch->SetExp (UMAX (0, ch->GetExp () + modgain));

	if (!ch->IsAuthed () && ch->GetExp () >= exp_level (ch, ch->GetLevel ()+1)) {
		ch->SendText ("You can not ascend to a higher level until you "
			"are authorized.\n\r");
		ch->SetExp (exp_level (ch, (ch->GetLevel ()+1)) - 1);
		return;
	}

	while (ch->GetLevel () < LEVEL_AVATAR &&
	  ch->GetExp () >= exp_level (ch, ch->GetLevel ()+1)) {
		set_char_color (AT_WHITE + AT_BLINK, ch);
		ch->AddLevel (1);
		ch->SendTextf ("You have now obtained experience level %d!\n\r",
			ch->GetLevel ());
		advance_level (ch);
	}
}


// Regeneration stuff.
int hit_gain (CCharacter *ch)
{
	int		gain;

	if (ch->IsNpc ())
		gain = ch->GetLevel () * 3 / 2;

	else {
		gain = UMIN (5, ch->GetLevel ());
		gain += RaceTable.GetHpRegen (ch->GetRace ());

		switch (ch->GetPosition ()) {
		  case POS_DEAD:	return 0;
		  case POS_MORTAL:	return -1;
		  case POS_INCAP:	return -1;
		  case POS_STUNNED:	return 1;
		  case POS_SLEEPING:
			gain += (int) (ch->GetConstitution () * 1.5);
			break;
		  case POS_RESTING:
			gain += ch->GetConstitution ();
			break;
		}

		if (ch->IsVampire ()) {
			if (ch->GetPcData ()->condition [COND_BLOODTHIRST] <= 1)
				gain /= 2;
			else if (ch->GetPcData ()->condition [COND_BLOODTHIRST]
				>= (8 + ch->GetLevel ()))
					gain *= 2;
			if (ch->IsOutside ()) {
    			switch (weather_info.sunlight) {
    			  case SUN_RISE:
    			  case SUN_SET:
      				gain /= 2;
      				break;
    			  case SUN_LIGHT:
      				gain /= 4;
      				break;
      			}
      		}
		}

		if (ch->GetPcData ()->condition [COND_FULL] == 0)
			gain /= 2;

		if (ch->GetPcData ()->condition [COND_THIRST] == 0)
			gain /= 2;

	}

	if (ch->IsPoisoned ())
		gain /= 4;

	return UMIN (gain, ch->GetMaxHp () - ch->GetHp ());
}


int mana_gain (CCharacter *ch)
{
	int		gain;

	if (ch->IsNpc ())
		gain = ch->GetLevel ();

	else {
		if (ch->GetPosition () < POS_SLEEPING)
			return 0;

		gain = UMIN (5, ch->GetLevel () / 2);
		gain += RaceTable.GetManaRegen (ch->GetRace ());

		switch (ch->GetPosition ()) {
		  case POS_SLEEPING:
			gain += ch->GetIntelligence () * 3;
			break;
		  case POS_RESTING:
			gain += (int) (ch->GetIntelligence () * 1.5);
			break;
		}

		if (ch->GetPcData ()->condition [COND_FULL] == 0)
			gain /= 2;

		if (ch->GetPcData ()->condition [COND_THIRST] == 0)
			gain /= 2;
	}

	if (ch->IsPoisoned ())
		gain /= 4;

	return UMIN (gain, ch->GetMaxMana () - ch->GetMana ());
}


int move_gain (CCharacter *ch)
{
	int		gain;

	if (ch->IsNpc ()) {
		gain = ch->GetLevel ();
	} else {
		gain = UMAX (15, 2 * ch->GetLevel ());

		switch (ch->GetPosition ()) {
		  case POS_DEAD:	return 0;
		  case POS_MORTAL:	return -1;
		  case POS_INCAP:	return -1;
		  case POS_STUNNED:	return 1;
		  case POS_SLEEPING:
			gain += ch->GetDexterity () * 2;
			break;
		  case POS_RESTING:
			gain += ch->GetDexterity ();
			break;
		}

		if (ch->IsVampire ()) {
			if (ch->GetPcData ()->condition[COND_BLOODTHIRST] <= 1)
				gain /= 2;
			else if (ch->GetPcData ()->condition [COND_BLOODTHIRST]
				>= (8 + ch->GetLevel ()))
					gain *= 2;
			if (ch->IsOutside ()) {
				switch (weather_info.sunlight) {
				  case SUN_RISE:
				  case SUN_SET:
					gain /= 2;
					break;
				  case SUN_LIGHT:
					gain /= 4;
					break;
				}
      		}
		}

		if (ch->GetPcData ()->condition [COND_FULL]   == 0)
			gain /= 2;

		if (ch->GetPcData ()->condition [COND_THIRST] == 0)
			gain /= 2;
	}

	if (ch->IsPoisoned ())
		gain /= 4;

	return UMIN (gain, ch->GetMaxMove () - ch->GetMove ());
}


void gain_condition (CCharacter *ch, int iCond, int value)
{
	ASSERT (ch);

	int		condition;
	ch_ret	retcode;
	BOOL	bIsVampire = ClassTable.IsVampire (ch->GetClass ());

	if (value == 0 || ch->IsNpc ()
		|| ch->GetLevel () > LEVEL_AVATAR || ! ch->IsAuthed ())
			return;

	condition = ch->GetPcData ()->condition [iCond];
	if (iCond == COND_BLOODTHIRST)
		ch->GetPcData ()->condition [iCond] =
			URANGE (0, condition + value, 10 + ch->GetLevel ());
	else
		ch->GetPcData ()->condition [iCond] =
			URANGE (0, condition + value, 48);

	// Avatars don't get condition messages
	if (ch->GetLevel () == LEVEL_AVATAR)
		return;

	if (ch->GetPcData ()->condition [iCond] == 0) {
		switch (iCond) {
		  case COND_FULL:
			if (! bIsVampire) {
				set_char_color (AT_HUNGRY, ch);
				ch->SendText ("You are STARVING!\n\r");
				act (AT_HUNGRY, "$n is starved half to death!", ch, NULL,
					NULL, TO_ROOM);
				if (!ch->IsPkiller () || number_bits (1) == 0)
					worsen_mental_state (ch, 1);
				retcode = damage (ch, ch, 1, TYPE_UNDEFINED);
			}
			break;

		  case COND_THIRST:
			if (! bIsVampire) {
				set_char_color (AT_THIRSTY, ch);
				ch->SendText ("You are DYING of THIRST!\n\r");
				act (AT_THIRSTY, "$n is dying of thirst!", ch, NULL, NULL,
					TO_ROOM);
				worsen_mental_state (ch, ch->IsPkiller () ? 1: 2);
				retcode = damage (ch, ch, 2, TYPE_UNDEFINED);
			}
			break;

		  case COND_BLOODTHIRST:
			set_char_color (AT_BLOOD, ch);
			ch->SendText ("You are starved to feast on blood!\n\r");
			act (AT_BLOOD, "$n is suffering from lack of blood!", ch,
				NULL, NULL, TO_ROOM);
			worsen_mental_state (ch, 2);
			retcode = damage (ch, ch, ch->GetMaxHp () / 20,
				TYPE_UNDEFINED);
			break;

		  case COND_DRUNK:
			if (condition != 0) {
				set_char_color (AT_SOBER, ch);
				ch->SendText ("You are sober.\n\r");
			}
			retcode = rNONE;
			break;

		  default:
			bug ("Gain_condition: invalid condition type %d", iCond);
			retcode = rNONE;
			break;
		}
	}

	if (retcode != rNONE)
	  return;

	if (ch->GetPcData ()->condition [iCond] == 1) {
		switch (iCond) {
		  case COND_FULL:
			if (! bIsVampire) {
				set_char_color (AT_HUNGRY, ch);
				ch->SendText ("You are really hungry.\n\r");
				act (AT_HUNGRY, "You can hear $n's stomach growling.", ch,
					NULL, NULL, TO_ROOM);
				if (number_bits (1) == 0)
				worsen_mental_state (ch, 1);
			} 
			break;

		  case COND_THIRST:
			if (! bIsVampire) {
				set_char_color (AT_THIRSTY, ch);
				ch->SendText ("You are really thirsty.\n\r");
				worsen_mental_state (ch, 1);
				act (AT_THIRSTY, "$n looks a little parched.", ch, NULL,
					NULL, TO_ROOM);
			} 
			break;

		  case COND_BLOODTHIRST:
			set_char_color (AT_BLOOD, ch);
			ch->SendText ("You have a growing need to feast on blood!\n\r");
			act (AT_BLOOD, "$n gets a strange look in $s eyes...", ch,
				NULL, NULL, TO_ROOM);
			worsen_mental_state (ch, 1);
			break;

		  case COND_DRUNK:
			if (condition != 0) {
				set_char_color (AT_SOBER, ch);
				ch->SendText (
					"You are feeling a little less light headed.\n\r");
			}
			break;
		}
	}


	if (ch->GetPcData ()->condition [iCond] == 2) {
		switch (iCond) {
		  case COND_FULL:
			if (! bIsVampire) {
				set_char_color (AT_HUNGRY, ch);
				ch->SendText ("You are hungry.\n\r");
			} 
			break;

		  case COND_THIRST:
			if (! bIsVampire) {
				set_char_color (AT_THIRSTY, ch);
				ch->SendText ("You are thirsty.\n\r");
			} 
			break;

		  case COND_BLOODTHIRST:
			set_char_color (AT_BLOOD, ch);
			ch->SendText ("You feel an urgent need for blood.\n\r");
			break;
		}
	}

	if (ch->GetPcData ()->condition [iCond] == 3) {
		switch (iCond) {
		  case COND_FULL:
			if (! bIsVampire) {
				set_char_color (AT_HUNGRY, ch);
				ch->SendText ("You are a mite peckish.\n\r");
			} 
			break;

		  case COND_THIRST:
			if (! bIsVampire) {
				set_char_color (AT_THIRSTY, ch);
				ch->SendText ("You could use a sip of something refreshing.\n\r");
			} 
			break;

		  case COND_BLOODTHIRST:
			set_char_color (AT_BLOOD, ch);
			ch->SendText ("You feel an aching in your fangs.\n\r");
			break;
		}
	}

	//  Race alignment restrictions, h
	CRaceData	&Ra = *RaceTable.GetRaceData (ch->GetRace ());
	if ((ch->GetAlignment () < Ra.GetMinAlign ())
	  || (ch->GetAlignment () > Ra.GetMaxAlign ())) {
		set_char_color (AT_BLOOD, ch);
		ch->SendText ("Your actions have been incompatible with the ideals "
			"of your race.  This troubles you.");
	}

	// Nephandi alignment restrictions -h
	if (! stricmp (ClassTable.GetName (ch->GetClass ()), "Nephandi")) {
		if (ch->GetAlignment () > -250) {
			set_char_color (AT_BLOOD, ch);
			ch->SendText ("Damn you heathen! Go forth and do evil or suffer "
				"the consequences!\n\r");
			worsen_mental_state (ch, 35);
			return;
		} 
	}


	// Paladins need some restrictions, this is where we crunch 'em -h
	if (! stricmp (ClassTable.GetName (ch->GetClass ()), "Paladin")) {
		if (ch->GetAlignment () < 250) {
			set_char_color (AT_BLOOD, ch);
			ch->SendText ("You are wracked with guilt and remorse for "
				"your craven actions!\n\r");
			act (AT_BLOOD, "$n prostrates $mself, seeking forgiveness "
				"from $s Lord.", ch, NULL, NULL, TO_ROOM); 
			worsen_mental_state (ch, 15);
			return;
		} 
		if (ch->GetAlignment () < 500) {
			set_char_color (AT_BLOOD, ch);
			ch->SendText ("As you betray your faith, your mind begins to "
				"betray you.\n\r");
			act (AT_BLOOD, "$n shudders, judging $s actions unworthy of "
				"a Paladin.", ch, NULL, NULL, TO_ROOM); 
			worsen_mental_state (ch, 6);
			return;
		}
	} 
}


// Mob autonomous action.
// This function takes 25% to 35% of ALL Mud cpu time.
void mobile_update ()
{
	char		buf[MAX_STRING_LENGTH];
	CCharacter	*ch;
	CExitData	*pexit;
	int			door;
	ch_ret		retcode;

	retcode = rNONE;

	try {
		// Examine all mobs.
		for (ch = last_char; ch; ch = gch_prev) {
			set_cur_char (ch);
			if (ch == first_char && ch->GetPrev ()) {
				bug ("mobile_update: first_char->GetPrev () != NULL... fixed");
				ch->SetPrev (NULL);
			}
  
			gch_prev = ch->GetPrev ();

			if (gch_prev && gch_prev->GetNext () != ch) {
				bug ("FATAL: Mobile_update: %s->GetPrev ()->GetNext ()"
					" doesn't point to ch.", ch->GetName ());
				bug ("Short-cutting here");
				gch_prev = NULL;
				ch->SetPrev (NULL);
				sprintf (buf, "%s says, 'Prepare for the worst!'",
					SysData.GetSupremeEntity ());
				do_shout (ch, buf);
			}

			if (!ch->IsNpc ()) {
				drunk_randoms (ch);
				halucinations (ch);
				continue;
			}

			if (!ch->GetInRoom () || ch->IsCharmed () || ch->IsParalysed ())
				continue;

			// Clean up 'animated corpses' that are not charmed' - Scryn
			if (ch->GetMobIndex ()->vnum == MOB_VNUM_ANIMATED_CORPSE
			  && !ch->IsCharmed ()) {
				if (ch->GetInRoom ()->first_person)
					act (AT_MAGIC, "$n returns to the dust from whence $e came.",
						ch, NULL, NULL, TO_ROOM);

				if (ch->IsNpc ())			// Guard against purging switched?
					extract_char (ch, TRUE);
				continue;
			}

			if (! ch->IsAction (ACT_RUNNING)
			  && ! ch->IsAction (ACT_SENTINEL)
			  && !ch->GetFightData () && ch->hunting) {
				WAIT_STATE (ch, 2 * PULSE_VIOLENCE);
				// Commented out temporarily to avoid spam - Scryn 
				//sprintf (buf, "%s hunting %s from %s.", ch->GetName (),
				//ch->hunting->name,
				//ch->GetInRoom ()->name);
				//gpDoc->LogString (buf);
				hunt_victim (ch);
				continue;
			}  

			// Examine call for special procedure
			if (! ch->IsAction (ACT_RUNNING)
			  && ch->GetSpecialMobFunction ()) {
				if ((*ch->GetSpecialMobFunction ()) (ch))
					continue;
				if (char_died (ch))
					continue;
			}

			// Check for mudprogram script on mob. If in a script,
			// don't do any other mobprogs.
			if (mprog_script_trigger (ch))
				continue;

			if (ch != cur_char) {
				bug ("Mobile_update: ch != cur_char after spec_fun");
				continue;
			}

			// That's all for sleeping / busy monster
			if (ch->GetPosition () != POS_STANDING)
				continue;

			if (ch->IsAction (ACT_MOUNTED)) {
				if (ch->IsAction (ACT_AGGRESSIVE))
					do_emote (ch, "snarls and growls.");
				continue;
			}

			if (ch->GetInRoom ()->IsSafe ()
			  && ch->IsAction (ACT_AGGRESSIVE))
				do_emote (ch, "glares around and snarls.");


			// MOBprogram random trigger
			if (ch->GetInRoom ()->GetArea ()->nplayer > 0) {
				mprog_random_trigger (ch);
				if (char_died (ch))
					continue;
				if (ch->GetPosition () < POS_STANDING)
					continue;
			}

			// MOBprogram hour trigger: do something for an hour
			mprog_hour_trigger (ch);

			if (char_died (ch))
				continue;

			rprog_hour_trigger (ch);
			if (char_died (ch))
				continue;

			if (ch->GetPosition () < POS_STANDING)
				continue;

			// Scavenge
			if (ch->IsAction (ACT_SCAVENGER)
			  && ! ch->GetInRoom ()->IsEmpty ()
			  && number_bits (2) == 0) {

				int			max = 1;
				CObjData	*obj_best = NULL;
				CObjData	*obj = NULL;

				POSITION	pos = ch->GetInRoom ()->GetHeadContentPos ();
				while (obj = ch->GetInRoom ()->GetNextContent (pos)) {
					if (obj->CanWear (ITEM_TAKE) && obj->cost > max 
					  && ! obj->IsBuried ()) {
						obj_best = obj;
						max = obj->cost;
					}
				}

				if (obj_best) {
					obj_from_room (obj_best);
					obj_to_char (obj_best, ch);
					act (AT_ACTION, "$n gets $p.", ch, obj_best, NULL, TO_ROOM);
				}
			}

			// Wander
			if (! ch->IsAction (ACT_RUNNING)
			  && ! ch->IsAction (ACT_SENTINEL)
			  && ! ch->IsAction (ACT_PROTOTYPE)
			  && (door = number_bits (5)) <= 9
			  && (pexit = get_exit (ch->GetInRoom (), door)) != NULL
			  && pexit->GetToRoom ()
			  && ! pexit->IsClosed ()
			  && ! pexit->GetToRoom ()->IsNoMob ()
			  && ! pexit->GetToRoom ()->IsDeathRoom ()
			  && (! ch->IsAction (ACT_STAY_AREA)
			  || pexit->GetToRoom ()->GetArea () == ch->GetInRoom ()->GetArea ())) {
				retcode = move_char (ch, pexit, 0);

				// If ch changes position due to it's or someother mob's
				// movement via MOBProgs, continue - Kahn
				if (char_died (ch))
					continue;
				if (retcode != rNONE || ch->IsAction (ACT_SENTINEL)
				  || ch->GetPosition () < POS_STANDING)
					continue;
			}

			// Flee
			if (ch->GetHp () < ch->GetMaxHp () / 2
			  && (door = number_bits (4)) <= 9
			  && (pexit = get_exit (ch->GetInRoom (),door)) != NULL
			  && pexit->GetToRoom ()
			  && ! pexit->IsClosed ()
			  && ! pexit->GetToRoom ()->IsNoMob ()) {

				BOOL		found = FALSE;
				CCharacter	*rch = ch->GetInRoom ()->first_person;

				for (; rch; rch  = rch->GetNextInRoom ()) {
					if (is_fearing (ch, rch)) {
						switch (number_bits (2)) {
						  case 0:
							sprintf (buf, "Get away from me, %s!",
								rch->GetName ());
							break;
						  case 1:
							sprintf (buf, "Leave me be, %s!", rch->GetName ());
							break;
						  case 2:
							sprintf (buf, "%s is trying to kill me!  Help!",
								rch->GetName ());
							break;
						  case 3:
							sprintf (buf, "Someone save me from %s!",
								rch->GetName ());
							break;
						}
						do_yell (ch, buf);
						found = TRUE;
						break;
					}
				}
				if (found)
					retcode = move_char (ch, pexit, 0);
			}
		}
	}
	catch (CException* ex) {
		CSwException	sx;
		char			buf [128];
		UINT			id;

		if (ex->GetErrorMessage (buf, sizeof (buf), &id))
			sx.Printf ("CException %u:%s in mobile_update.", id, buf);
		else sx.Printf ("Unknown CException in mobile_update.");
		ex->Delete ();
		throw sx;
	}
}


// Update the weather.
void weather_update ()
{
	char		buf[MAX_STRING_LENGTH];
	int			diff;
	short		AT_TEMP = AT_PLAIN;

	buf [0] = '\0';

	switch (++time_info.hour) {
	  case  5:
		weather_info.sunlight = SUN_LIGHT;
		strcat (buf, "The day has begun.");
		AT_TEMP = AT_YELLOW;
		break;

	  case  6:
		weather_info.sunlight = SUN_RISE;
		strcat (buf, "The sun rises in the east.");
		AT_TEMP = AT_ORANGE;
		break;

	  case 12:
		weather_info.sunlight = SUN_LIGHT;
		strcat (buf, "It's noon."); 
		AT_TEMP = AT_YELLOW;
		break;

	  case 19:
		weather_info.sunlight = SUN_SET;
		strcat (buf, "The sun slowly disappears in the west.");
		AT_TEMP = AT_BLOOD;
		break;

	  case 20:
		weather_info.sunlight = SUN_DARK;
		strcat (buf, "The night has begun.");
		AT_TEMP = AT_DGREY;
		break;

	  case 24:
		time_info.hour = 0;
		time_info.day++;
		break;
	}

	if (time_info.day >= 30) {
		time_info.day = 0;
		time_info.month++;
	}

	if (time_info.month >= 17) {
		time_info.month = 0;
		time_info.year++;
	}

	if (buf [0] != '\0') {
		POSITION	pos = DList.GetHeadPosition ();
		while (pos) {
			CDescriptor	*d = DList.GetNext (pos);
			if (d->IsDisconnecting ())
				continue;
			if (d->m_Connected == CON_PLAYING
				&& d->m_pCharacter->IsOutside ()
				&& d->m_pCharacter->IsAwake ())
					act (AT_TEMP, buf, d->m_pCharacter, 0, 0, TO_CHAR);
		}
		buf [0] = '\0';
	}

	// Weather change.
	if (time_info.month >= 9 && time_info.month <= 16)
		diff = weather_info.mmhg >  985 ? -2 : 2;
	else
		diff = weather_info.mmhg > 1015 ? -2 : 2;

	weather_info.change += diff * dice (1, 4) + dice (2, 6) - dice (2, 6);
	weather_info.change  = UMAX (weather_info.change, -12);
	weather_info.change  = UMIN (weather_info.change,  12);

	weather_info.mmhg += weather_info.change;
	weather_info.mmhg  = UMAX (weather_info.mmhg,  960);
	weather_info.mmhg  = UMIN (weather_info.mmhg, 1040);

	AT_TEMP = AT_GREY;
	switch (weather_info.sky) {
	  default: 
		bug ("Weather_update: bad sky %d.", weather_info.sky);
		weather_info.sky = SKY_CLOUDLESS;
		break;

	  case SKY_CLOUDLESS:
		if (weather_info.mmhg <  990
		  || (weather_info.mmhg < 1010 && number_bits (2) == 0)) {
			strcat (buf, "The sky is getting cloudy.");
			weather_info.sky = SKY_CLOUDY;
			AT_TEMP = AT_GREY;
		}
		break;

	  case SKY_CLOUDY:
		if (weather_info.mmhg <  970
		  || (weather_info.mmhg <  990 && number_bits (2) == 0)) {
			strcat (buf, "It starts to rain.");
			weather_info.sky = SKY_RAINING;
			AT_TEMP = AT_BLUE;
		}

		if (weather_info.mmhg > 1030 && number_bits (2) == 0) {
			strcat (buf, "The clouds disappear.");
			weather_info.sky = SKY_CLOUDLESS;
			AT_TEMP = AT_WHITE;
		}
		break;

	  case SKY_RAINING:
		if (weather_info.mmhg <  970 && number_bits (2) == 0) {
			strcat (buf, "Lightning flashes in the sky.");
			weather_info.sky = SKY_LIGHTNING;
			AT_TEMP = AT_YELLOW;
		}

		if (weather_info.mmhg > 1030
		  || (weather_info.mmhg > 1010 && number_bits (2) == 0)) {
			strcat (buf, "The rain stopped.");
			weather_info.sky = SKY_CLOUDY;
			AT_TEMP = AT_WHITE;
		}
		break;

	  case SKY_LIGHTNING:
		if (weather_info.mmhg > 1010
		  || (weather_info.mmhg >  990 && number_bits (2) == 0)) {
			strcat (buf, "The lightning has stopped.");
			weather_info.sky = SKY_RAINING;
			AT_TEMP = AT_GREY;
		}
		break;
	}

	if (buf [0] != '\0') {
		POSITION	pos = DList.GetHeadPosition ();
		while (pos) {
			CDescriptor	*d = DList.GetNext (pos);
			if (d->IsDisconnecting ())
				continue;

			if (d->m_Connected == CON_PLAYING
				&& d->m_pCharacter->IsOutside ()
				&& d->m_pCharacter->IsAwake ())
					act (AT_TEMP, buf, d->m_pCharacter, 0, 0, TO_CHAR);
		}
	}
}


// Update all chars, including mobs.
// This function is performance sensitive.
void char_update ()
{   
	CCharacter	*ch;
	CCharacter	*ch_save;
	short		save_count = 0;

	ch_save	= NULL;
	for (ch = last_char; ch; ch = gch_prev) {
		if (ch == first_char && ch->GetPrev ()) {
			bug ("char_update: first_char->GetPrev () != NULL... fixed");
			ch->SetPrev (NULL);
		}
		gch_prev = ch->GetPrev ();
		set_cur_char (ch);
		if (gch_prev && gch_prev->GetNext () != ch) {
			bug ("char_update: ch->GetPrev ()->GetNext () != ch");
			return;
		}

		//  Do a room_prog rand check right off the bat
		//  if ch disappears (rprog might wax npc's), continue
		if (!ch->IsNpc ())
			rprog_random_trigger (ch);

		if (char_died (ch))
			continue;

		if (ch->IsNpc ())
			mprog_time_trigger (ch);   

		if (char_died (ch))
			continue;

		rprog_time_trigger (ch);

		if (char_died (ch))
			continue;

		// See if player should be auto-saved.
		if (!ch->IsNpc ()
		  && (!ch->GetDesc () || ch->GetDesc ()->m_Connected == CON_PLAYING)
		  && ch->GetLevel () >= 2
		  && (CurrentTime - ch->GetSaveTime ()).GetTotalMinutes () >
			(SysData.SaveFrequency))
				then ch_save = ch;
		else
			ch_save	= NULL;

		if (ch->GetPosition () >= POS_STUNNED) {
			if (ch->GetHp ()  < ch->GetMaxHp ())
				ch->AddHp (hit_gain (ch));

			if (ch->GetMana () < ch->GetMaxMana ())
				ch->AddMana (mana_gain (ch));

			if (ch->GetMove () < ch->GetMaxMove ())
				ch->AddMove (move_gain (ch));
		}

		if (ch->GetPosition () == POS_STUNNED)
			update_pos (ch);

		if (!ch->IsNpc () && ch->GetLevel () < LEVEL_IMMORTAL) {
			CObjData	*obj;

			if ((obj = get_eq_char (ch, WEAR_LIGHT)) != NULL
			  && obj->item_type == ITEM_LIGHT && obj->value[2] > 0) {
				if (--obj->value [2] == 0 && ch->GetInRoom ()) {
					ch->GetInRoom ()->light -= obj->count;
					act (AT_ACTION, "$p goes out.", ch, obj, NULL, TO_ROOM);
					act (AT_ACTION, "$p goes out.", ch, obj, NULL, TO_CHAR);
					if (obj->serial == cur_obj)
						global_objcode = rOBJ_EXPIRED;
					extract_obj (obj);
				}
			}

			ch->AddTimer (1);
			if (ch->GetTimer () >= 12) {
				if (!ch->GetWasInRoom () && ch->GetInRoom ()) {
					ch->SetWasInRoom (ch->GetInRoom ());
					if (ch->GetFightData ())
						stop_fighting (ch, TRUE);
					act (AT_ACTION, "$n disappears into the void.",
						ch, NULL, NULL, TO_ROOM);
					ch->SendText ("You disappear into the void.\n\r");
					if (SysData.IsSaveOnIdle ())
						save_char_obj (ch);
					ch->RemoveFromRoom ();
					ch->SendToRoom (RoomTable.GetRoom (SysData.m_RoomLimbo));
				}
			}

			if (ch->GetPcData ()->condition[COND_DRUNK] > 8)
				worsen_mental_state (ch,
					ch->GetPcData ()->condition [COND_DRUNK] / 8);
			if (ch->GetPcData ()->condition [COND_FULL] > 1) {
				switch (ch->GetPosition ()) {
				  case POS_SLEEPING:  better_mental_state (ch, 4); break;
				  case POS_RESTING:   better_mental_state (ch, 3); break;
				  case POS_SITTING:
				  case POS_MOUNTED:   better_mental_state (ch, 2); break;
				  case POS_STANDING:  better_mental_state (ch, 1); break;
				  case POS_FIGHTING:
				  case POS_EVASIVE:
				  case POS_DEFENSIVE:
				  case POS_AGGRESSIVE:
				  case POS_BERSERK:
					if (number_bits (2) == 0)
						better_mental_state (ch, 1);
					break;
				}
			}

			if (ch->GetPcData ()->condition [COND_THIRST] > 1) {
				switch (ch->GetPosition ()) {
				  case POS_SLEEPING:  better_mental_state (ch, 5);	break;
				  case POS_RESTING:   better_mental_state (ch, 3);	break;
				  case POS_SITTING:
				  case POS_MOUNTED:   better_mental_state (ch, 2);	break;
				  case POS_STANDING:  better_mental_state (ch, 1);	break;
				  case POS_FIGHTING:
				  case POS_EVASIVE:
				  case POS_DEFENSIVE:
				  case POS_AGGRESSIVE:
				  case POS_BERSERK:
					if (number_bits (2) == 0)
						better_mental_state (ch, 1);
					break;
				}
			}
			gain_condition (ch, COND_DRUNK, -1);
			gain_condition (ch, COND_FULL, -1 +
				RaceTable.GetHungerMod (ch->GetRace ()));

			if (ClassTable.IsVampire (ch->GetClass ()) && ch->GetLevel () >= 10) {
				if (time_info.hour < 21 && time_info.hour > 5)
					gain_condition (ch, COND_BLOODTHIRST, -1);
			}
			if (ch->GetInRoom ())
				switch (ch->GetInRoom ()->sector_type) {
				  default:
					gain_condition (ch, COND_THIRST, -1);
					break;
				  case SECT_DESERT:
					gain_condition (ch, COND_THIRST, -2);
					break;
				  case SECT_UNDERWATER:
				  case SECT_OCEANFLOOR:
					if (number_bits (1) == 0)
						gain_condition (ch, COND_THIRST, -1);
					break;
			  }
		}

		if (!char_died (ch)) {
			// Careful with the damages here,
			//   MUST NOT refer to ch after damage taken,
			//   as it may be lethal damage (on NPC).
			if (ch->IsPoisoned ()) {
				act (AT_POISON, "$n shivers and suffers.", ch, NULL, NULL,
					TO_ROOM);
				act (AT_POISON, "You shiver and suffer.", ch, NULL, NULL,
					TO_CHAR);
				ch->SetMentalState (URANGE (20, ch->GetMentalState ()
					+ (ch->IsPkiller () ? 3 : 4), 100));
				damage (ch, ch, 6, gsn_poison);
			}
			else if (ch->GetPosition () == POS_INCAP)
				damage (ch, ch, 1, TYPE_UNDEFINED);
			else if (ch->GetPosition () == POS_MORTAL)
				damage (ch, ch, 4, TYPE_UNDEFINED);

			if (char_died (ch))
				continue;
			if (ch->GetMentalState () >= 30)
				switch ((ch->GetMentalState () + 5) / 10) {
				  case  3:
					ch->SendText ("You feel feverish.\n\r");
					act (AT_ACTION, "$n looks kind of out of it.", ch, NULL,
						NULL, TO_ROOM);
					break;
				  case  4:
					ch->SendText ("You do not feel well at all.\n\r");
					act (AT_ACTION, "$n doesn't look too good.", ch, NULL,
						NULL, TO_ROOM);
					break;
				  case  5:
					ch->SendText ("You need help!\n\r");
					act (AT_ACTION, "$n looks like $e could use your help.",
						ch, NULL, NULL, TO_ROOM);
					break;
				  case  6:
					ch->SendText ("Seekest thou a cleric.\n\r");
					act (AT_ACTION, "Someone should fetch a healer for $n.",
						ch, NULL, NULL, TO_ROOM);
					break;
				  case  7:
					ch->SendText ("You feel reality slipping away...\n\r");
					act (AT_ACTION, "$n doesn't appear to be aware of what's"
						" going on.", ch, NULL, NULL, TO_ROOM);
					break;
				  case  8:
					ch->SendText ("You begin to understand... "
						"everything.\n\r");
					act (AT_ACTION, "$n starts ranting like a madman!",
						ch, NULL, NULL, TO_ROOM);
					break;
				  case  9:
					ch->SendText ("You are ONE with the universe.\n\r");
					act (AT_ACTION, "$n is ranting on about 'the answer', "
						"'ONE' and other mumbo-jumbo...",
						ch, NULL, NULL, TO_ROOM);
					break;
				  case 10:
					ch->SendText ("You feel the end is near.\n\r");
					act (AT_ACTION, "$n is muttering and ranting in "
						"tongues...", ch, NULL, NULL, TO_ROOM);
					break;
				}

			if (ch->GetMentalState () <= -30)
				switch ((abs (ch->GetMentalState ())+5) / 10) {
				  case 10:
					if (ch->GetPosition () > POS_SLEEPING) {
						if ((ch->GetPosition () == POS_STANDING
						  || ch->GetPosition () < POS_FIGHTING)
						  && number_percent ()+10 <
							abs (ch->GetMentalState ()))
								then do_sleep (ch, "");
						else
							ch->SendText ("You're barely conscious.\n\r");
					}
					break;

				  case 9:
					if (ch->GetPosition () > POS_SLEEPING) {
						if ((ch->GetPosition () == POS_STANDING
						  || ch->GetPosition () < POS_FIGHTING)
						  && (number_percent ()+20) <
							abs (ch->GetMentalState ()))
								then do_sleep (ch, "");
						else
							ch->SendText (
								"You can barely keep your eyes open.\n\r");
					}
					break;

				  case 8:
					if (ch->GetPosition () > POS_SLEEPING) {
						if (ch->GetPosition () < POS_SITTING
						  && (number_percent ()+30) <
							abs (ch->GetMentalState ()))
								then do_sleep (ch, "");
						else
							ch->SendText ("You're extremely drowsy.\n\r");
					}
					break;

				  case 7:
					if (ch->GetPosition () > POS_RESTING)
					  ch->SendText ("You feel very unmotivated.\n\r");
					break;

				  case 6:
					if (ch->GetPosition () > POS_RESTING)
						ch->SendText ("You feel sedated.\n\r");
					break;

				  case 5:
					if (ch->GetPosition () > POS_RESTING)
						ch->SendText ("You feel sleepy.\n\r");
					break;

				  case 4:
					if (ch->GetPosition () > POS_RESTING)
						ch->SendText ("You feel tired.\n\r");
					break;

				  case 3:
					if (ch->GetPosition () > POS_RESTING)
						ch->SendText ("You could use a rest.\n\r");
					break;
				}

			if (ch->GetTimer () > 24)
				do_quit (ch, "");
			else
				// save max of 10 per tick
				if (ch == ch_save && SysData.IsAutoSave ()
				  && ++save_count < 10)
					save_char_obj (ch);
		}
	}
}


// Update all objs.
// This function is performance sensitive.
void obj_update ()
{   
	CObjData	*obj;
	short		AT_TEMP;
	CParseinfo	Inf;

	while (obj = Inf.ParseAreaLists (AllAreasList)) {
		CCharacter	*rch;
		char		*message;

		set_cur_obj (obj);
		if (obj->carried_by)
			oprog_random_trigger (obj); 
		else if (obj->in_room && obj->in_room->GetArea ()->nplayer > 0)
			oprog_random_trigger (obj); 

		if (obj_extracted (obj))
			continue;

		if (obj->item_type == ITEM_PIPE) {
			if (IS_SET (obj->value [3], PIPE_LIT)) {
				if (--obj->value [1] <= 0) {
					obj->value [1] = 0;
					REMOVE_BIT (obj->value [3], PIPE_LIT);
				}
				else if (IS_SET (obj->value [3], PIPE_HOT))
					REMOVE_BIT (obj->value [3], PIPE_HOT);
				else {
					if (IS_SET (obj->value [3], PIPE_GOINGOUT)) {
						REMOVE_BIT (obj->value [3], PIPE_LIT);
						REMOVE_BIT (obj->value [3], PIPE_GOINGOUT);
					}
					else
						SET_BIT (obj->value [3], PIPE_GOINGOUT);
				}
				if (!IS_SET (obj->value [3], PIPE_LIT))
					SET_BIT (obj->value [3], PIPE_FULLOFASH);
			}
			else
				REMOVE_BIT (obj->value [3], PIPE_HOT);
		}


		// Corpse decay
		// (npc corpses decay at 8 times the rate of pc corpses) - Narn
		if (obj->item_type == ITEM_CORPSE_PC
		  || obj->item_type == ITEM_CORPSE_NPC) {
			short	timerfrac = UMAX (1, obj->timer - 1);
			if (obj->item_type == ITEM_CORPSE_PC)
				timerfrac = (int) (obj->timer / 8 + 1);

			if (obj->timer > 0 && obj->value [2] > timerfrac) {
				char	buf [MAX_STRING_LENGTH];
				char	name [MAX_STRING_LENGTH];
				char	*bufptr;
				bufptr = one_argument (obj->GetShortDescr (), name); 
				bufptr = one_argument (bufptr, name); 
				bufptr = one_argument (bufptr, name); 

				separate_obj (obj);
				obj->value [2] = timerfrac; 
				sprintf (buf, corpse_descs [UMIN (timerfrac - 1, 4)], 
					capitalize (bufptr)); 

				obj->SetDescription (buf); 
			}  
		}

		// don't let inventory decay
		if (obj->IsInventory ())
			continue;

		if ((obj->timer <= 0 || --obj->timer > 0))
			continue;

		// if we get this far, object's timer has expired.
		AT_TEMP = AT_PLAIN;
		switch (obj->item_type) {
		  default:
			message = "$p mysteriously vanishes.";
			AT_TEMP = AT_PLAIN;
			break;
		  case ITEM_PORTAL:
			message = "$p winks out of existence.";
			remove_portal (obj);
			obj->item_type = ITEM_TRASH;		/* so extract_obj	 */
			AT_TEMP = AT_MAGIC;			/* doesn't remove_portal */
			break;
		  case ITEM_FOUNTAIN:
			message = "$p dries up.";
			AT_TEMP = AT_BLUE;
			break;
		  case ITEM_CORPSE_NPC:
			message = "$p decays into dust and blows away.";
			AT_TEMP = AT_OBJECT;
			break;
		  case ITEM_CORPSE_PC:
			message = "$p is sucked into a swirling vortex of colors...";
			AT_TEMP = AT_MAGIC;
			break;
		  case ITEM_FOOD:
			message = "$p is devoured by a swarm of maggots.";
			AT_TEMP = AT_HUNGRY;
			break;
		  case ITEM_BLOOD:
			message = "$p slowly seeps into the ground.";
			AT_TEMP = AT_BLOOD;
			break;
		  case ITEM_BLOODSTAIN:
			message = "$p dries up into flakes and blows away.";
			AT_TEMP = AT_BLOOD;
			break;
		  case ITEM_SCRAPS:
			message = "$p crumbles and decays into nothing.";
			AT_TEMP = AT_OBJECT;
			break;
		  case ITEM_FIRE:
			if (obj->in_room)
				--obj->in_room->light;
			message = "$p burns out.";
			AT_TEMP = AT_FIRE;
		}

		if (obj->carried_by) {
			act (AT_TEMP, message, obj->carried_by, obj, NULL, TO_CHAR);
		}
		else if (obj->in_room
		  && (rch = obj->in_room->first_person) != NULL
		  && ! obj->IsBuried ()) {
			act (AT_TEMP, message, rch, obj, NULL, TO_ROOM);
			act (AT_TEMP, message, rch, obj, NULL, TO_CHAR);
		}

		if (obj->serial == cur_obj)
			global_objcode = rOBJ_EXPIRED;
		extract_obj (obj);
	}
}


// Function to check important stuff happening to a player
// This function should take about 5% of mud cpu time
void char_check ()
{
	CCharacter	*ch, *ch_next;
	CObjData	*obj;
	CExitData	*pexit;
	static int	cnt = 0;
	int			door, retcode;

	try {
		cnt = (cnt+1) % 2;

		for (ch = first_char; ch; ch = ch_next) {
			set_cur_char (ch);
			ch_next = ch->GetNext ();
			will_fall (ch, 0);

			if (char_died (ch))
				continue;

			if (ch->IsNpc ()) {
				if (cnt != 0)
					continue;

				// running mobs	-Thoric
				if (ch->IsAction (ACT_RUNNING)) {
					if (! ch->IsAction (ACT_SENTINEL)
					  && !ch->GetFightData () && ch->hunting) {
						WAIT_STATE (ch, 2 * PULSE_VIOLENCE);
						hunt_victim (ch);
						continue;
					}

					if (ch->GetSpecialMobFunction ()) {
						if ((*ch->GetSpecialMobFunction ()) (ch))
							continue;
						if (char_died (ch))
							continue;
					}

					if (! ch->IsAction (ACT_SENTINEL)
					  && ! ch->IsAction (ACT_PROTOTYPE)
					  && (door = number_bits (4)) <= 9
					  && (pexit = get_exit (ch->GetInRoom (), door)) != NULL
					  && pexit->GetToRoom ()
					  && ! pexit->IsClosed ()
					  && ! pexit->GetToRoom ()->IsNoMob ()
					  && ! pexit->GetToRoom ()->IsDeathRoom ()
					  && (! ch->IsAction (ACT_STAY_AREA)
					  || pexit->GetToRoom ()->GetArea () == ch->GetInRoom ()->GetArea ())) {
						retcode = move_char (ch, pexit, 0);
						if (char_died (ch))
							continue;
						if (retcode != rNONE || ch->IsAction (ACT_SENTINEL)
						  || ch->GetPosition () < POS_STANDING)
							continue;
					}
				}
				continue;
			} else {
				if (ch->mount
				  && ch->GetInRoom () != ch->mount->GetInRoom ()) {
					ch->mount->ClrActBit (ACT_MOUNTED);
					ch->mount = NULL;
					ch->SetPosition (POS_STANDING);
					ch->SendText ("No longer upon your mount, you fall "
						"to the ground...\n\rOUCH!\n\r");
				}

				if ((ch->GetInRoom ()
				  && ch->GetInRoom ()->sector_type == SECT_UNDERWATER)
				  || (ch->GetInRoom () &&
				  ch->GetInRoom ()->sector_type == SECT_OCEANFLOOR)) {
					if (! ch->IsAffected (AFF_AQUA_BREATH)) {
						if (ch->GetLevel () < LEVEL_IMMORTAL) {
							int		dam;

							// Changed level of damage at Brittany's request. -- Narn
							dam = number_range (ch->GetMaxHp () / 100, ch->GetMaxHp () / 50);
							dam = UMAX (1, dam);
							if (number_bits (3) == 0)
							ch->SendText ("You cough and choke as you try to"
								" breathe water!\n\r");
							damage (ch, ch, dam, TYPE_UNDEFINED);
						}
					}
				}

				if (char_died (ch))
					continue; 

				if (ch->GetInRoom ()
				  && ((ch->GetInRoom ()->sector_type == SECT_WATER_NOSWIM)
				  || (ch->GetInRoom ()->sector_type == SECT_WATER_SWIM))) {
					if (! ch->IsFlying ()
					  && ! ch->IsFloating ()
					  && ! ch->IsAffected (AFF_AQUA_BREATH)
					  && !ch->mount) {
						POSITION	pos = ch->GetHeadCarryPos ();
						while (obj = ch->GetNextCarrying (pos))
							if (obj->item_type == ITEM_BOAT)
								break;

						if (! obj) {
							if (ch->GetLevel () < LEVEL_IMMORTAL) {
								int		mov;
								int		dam;

								if (ch->GetMove () > 0) {
									mov = number_range (ch->GetMaxMove () / 20,
										ch->GetMaxMove () / 5);
									mov = UMAX (1, mov);

									ch->SetMove (
										UMAX (0, ch->GetMove () - mov));
								} else {
									dam = number_range (ch->GetMaxHp () / 20,
										ch->GetMaxHp () / 5);
									dam = UMAX (1, dam);

									if (number_bits (3) == 0)
									ch->SendText ("Struggling with exhaustion, "
										"you choke on a mouthful of water.\n\r");
									damage (ch, ch, dam, TYPE_UNDEFINED);
								}
							}
						}
					}
				}

				// beat up on link dead players
				if (!ch->GetDesc ()) {
					CCharacter	*wch, *wch_next;

					for (wch=ch->GetInRoom ()->first_person; wch; wch=wch_next) {
						wch_next = wch->GetNextInRoom ();

						if (! wch->IsNpc () || wch->GetFightData ()
						  || wch->IsCharmed () || ! wch->IsAwake ()
						  || (wch->IsWimpy () && ch->IsAwake ())
						  || !can_see (wch, ch))
							continue;

						if (is_hating (wch, ch)) {
							found_prey (wch, ch);
							continue;
						}

						if (! wch->IsAction (ACT_AGGRESSIVE)
						  || wch->IsAction (ACT_MOUNTED)
						  || wch->GetInRoom ()->IsSafe ())
							continue;
						global_retcode = multi_hit (wch, ch, TYPE_UNDEFINED);
					}
				}
			}
		}
	}
	catch (CException* ex) {
		CSwException	sx;
		char			buf [128];
		UINT			id;

		if (ex->GetErrorMessage (buf, sizeof (buf), &id))
			sx.Printf ("CException %u:%s in char_check.", id, buf);
		else sx.Printf ("Unknown CException in char_check.");
		ex->Delete ();
		throw sx;
	}
}


// Aggress.
//
// for each descriptor
//     for each mob in room
//         aggress on some random PC
//
// This function should take 5% to 10% of ALL mud cpu time.
// Unfortunately, checking on each PC move is too tricky,
//   because we don't want the mob to just attack the first PC
//   who leads the party into the room.
void aggr_update ()
{
	CCharacter		*wch;
	CCharacter		*ch;
	CCharacter		*ch_next;
	CCharacter		*vch;
	CCharacter		*vch_next;
	CCharacter		*victim;
	CActProgData	*apdtmp;

#ifdef UNDEFD
	//  GRUNT!  To do
	if (IS_NPC (wch) && wch->mpactnum > 0
	  && wch->GetInRoom ()->area->nplayer > 0) {
		CActProgData	*tmp_act, *tmp2_act;

		for (tmp_act = wch->mpact; tmp_act; tmp_act = tmp_act->next) {
			oprog_wordlist_check (tmp_act->buf,wch, tmp_act->ch,
				tmp_act->obj, tmp_act->vo, ACT_PROG);
			delete tmp_act->buf;
		}
		for (tmp_act = wch->mpact; tmp_act; tmp_act = tmp2_act) {
			tmp2_act = tmp_act->next;
			delete tmp_act;
		}
		wch->mpactnum = 0;
		wch->mpact    = NULL;
	}
#endif

	// check mobprog act queue
	while ((apdtmp = mob_act_list) != NULL) {
		wch = (CCharacter*) mob_act_list->vo;
		if (! char_died (wch) && wch->GetMobPactnum () > 0) {
			CMobProgActList	*tmp_act;

			while ((tmp_act = wch->GetMobPact ())) {
				if (tmp_act->obj && obj_extracted (tmp_act->obj))
					tmp_act->obj = NULL;
				if (tmp_act->ch && !char_died (tmp_act->ch))
					mprog_wordlist_check (tmp_act->buf, wch, tmp_act->ch,
						tmp_act->obj, tmp_act->vo, ACT_PROG);
				wch->SetMobPact (tmp_act->GetNext ());
				delete tmp_act;
			}
			wch->SetMobPactnum (0);
			wch->SetMobPact (NULL);
		}
		mob_act_list = apdtmp->GetNext ();
		delete apdtmp;
	}


	// Just check descriptors here for victims to aggressive mobs
	// We can check for linkdead victims to mobile_update	-Thoric
	POSITION	pos = DList.GetHeadPosition ();
	while (pos) {
		CDescriptor	*d = DList.GetNext (pos);
		if (d->IsDisconnecting () || d->m_Connected != CON_PLAYING
			|| (wch=d->m_pCharacter) == NULL)
				continue;

		if (char_died (wch) || wch->IsNpc ()
		  || wch->GetLevel () >= LEVEL_IMMORTAL || !wch->GetInRoom ())
			continue;

		for (ch = wch->GetInRoom ()->first_person; ch; ch = ch_next) {
			int count;

			ch_next	= ch->GetNextInRoom ();

			if (! ch->IsNpc () || ch->GetFightData () || ch->IsCharmed ()
			  || ! ch->IsAwake () || (ch->IsWimpy () && wch->IsAwake ())
			  || !can_see (ch, wch))
				continue;

			if (is_hating (ch, wch)) {
				found_prey (ch, wch);
				continue;
			}

			if (! ch->IsAction (ACT_AGGRESSIVE)
			  || ch->IsAction (ACT_MOUNTED)
			  || ch->GetInRoom ()->IsSafe ())
				continue;

			// Ok we have a 'wch' player character and a 'ch' npc aggressor.
			// Now make the aggressor fight a RANDOM pc victim in the room,
			// giving each 'vch' an equal chance of selection.
			count	= 0;
			victim	= NULL;
			for (vch = wch->GetInRoom ()->first_person; vch; vch = vch_next) {
				vch_next = vch->GetNextInRoom ();

				if (! vch->IsNpc () && vch->GetLevel () < LEVEL_IMMORTAL
				  && (! ch->IsWimpy () || ! vch->IsAwake ())
				  && can_see (ch, vch)) {
					if (number_range (0, count) == 0)
						victim = vch;
					count++;
				}
			}

			if (!victim) {
				bug ("Aggr_update: null victim.", count);
				continue;
			}

			if (ch->IsNpc () && ch->CanBackStab ()) {
				CObjData	*obj;

				if (!ch->mount
				  && (obj = get_eq_char (ch, WEAR_WIELD)) != NULL
				  && obj->value[3] == 11
				  && !victim->GetFightData ()
				  && victim->GetHp () >= victim->GetMaxHp ()) {
					check_attacker (ch, victim);
					WAIT_STATE (ch, SkillTable.GetBeats (gsn_backstab));

					if (! victim->IsAwake ()
					  || number_percent ()+5 < ch->GetLevel ()) {
						global_retcode = multi_hit (ch, victim, gsn_backstab);
						continue;
					} else {
						global_retcode = damage (ch, victim, 0, gsn_backstab);
						continue;
					}
				}
			}
			global_retcode = multi_hit (ch, victim, TYPE_UNDEFINED);
		}
	}
}


// drunk randoms	- Tricops
// (Made part of mobile_update	-Thoric)
void drunk_randoms (CCharacter *ch)
{
	CCharacter	*rvch = NULL;
	CCharacter	*vch;
	short		drunk;
	short		position;

	if (ch->IsNpc () || ch->GetPcData ()->condition[COND_DRUNK] <= 0)
		return;

	if (number_percent () < 30)
		return;

	drunk = ch->GetPcData ()->condition[COND_DRUNK];
	position = ch->GetPosition ();
	ch->SetPosition (POS_STANDING);

	if (number_percent () < (2*drunk / 20))
		check_social (ch, "burp", "");
	else
		if (number_percent () < (2*drunk / 20))
			check_social (ch, "hiccup", "");
	else
		if (number_percent () < (2*drunk / 20))
			check_social (ch, "drool", "");
	else
		if (number_percent () < (2*drunk / 20))
			check_social (ch, "fart", "");
	else
		if (drunk > (10+ (ch->GetConstitution ()/5))
		  && number_percent () < (2 * drunk / 18)) {
			vch = ch->GetInRoom ()->first_person;
			for (; vch; vch = vch->GetNextInRoom ())
				if (number_percent () < 10)
			rvch = vch;
			check_social (ch, "puke", (rvch ? rvch->GetName () : ""));
		}

	ch->SetPosition (position);
}


void halucinations (CCharacter *ch)
{
	if (ch->GetMentalState () >= 30
	  && number_bits (5 - (ch->GetMentalState () >= 50)
	  - (ch->GetMentalState () >= 75)) == 0) {
		char	*t;

		switch (number_range (1, UMIN (20, (ch->GetMentalState ()+5) / 5))) {
		  default:
		  case  1: t = "You feel very restless... you can't sit still.\n\r";
			break;
		  case  2: t = "You're tingling all over.\n\r"; break;
		  case  3: t = "Your skin is crawling.\n\r"; break;
		  case  4: t = "You suddenly feel that something is terribly wrong.\n\r"; break;
		  case  5: t = "Those damn little fairies keep laughing at you!\n\r"; break;
		  case  6: t = "You can hear your mother crying...\n\r"; break;
		  case  7: t = "Have you been here before, or not?  You're not sure...\n\r"; break;
		  case  8: t = "Painful childhood memories flash through your mind.\n\r"; break;
		  case  9: t = "You hear someone call your name in the distance...\n\r"; break;
		  case 10: t = "Your head is pulsating... you can't think straight.\n\r"; break;
		  case 11: t = "The ground... seems to be squirming...\n\r"; break;
		  case 12: t = "You're not quite sure what is real anymore.\n\r"; break;
		  case 13: t = "It's all a dream... or is it?\n\r"; break;
		  case 14: t = "They're coming to get you... coming to take you away...\n\r"; break;
		  case 15: t = "You begin to feel all powerful!\n\r"; break;
		  case 16: t = "You're light as air... the heavens are yours for the taking.\n\r"; break;
		  case 17: t = "Your whole life flashes by... and your future...\n\r"; break;
		  case 18: t = "You are everywhere and everything... you know all and are all!\n\r"; break;
		  case 19: t = "You feel immortal!\n\r"; break;
		  case 20: t = "Ahh... the power of a Supreme Entity... what to do...\n\r"; break;
		}
		ch->SendText (t);
	}
}


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

    if (!first_teleport)
      return;
    
    for (tele = first_teleport; tele; tele = tele_next)
    {
	tele_next = tele->GetNext ();
	if (--tele->timer <= 0)
	{
	    if (tele->room->first_person)
	    {
		if (tele->room->IsTeleShow ())
		  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);
	    delete tele;
	}
    }
}


void auth_update () 
{ 
	CCharacter	*victim; 
	CString		Msg, Log;
	BOOL		found_hit = FALSE;         // was at least one found?

	Log = "Pending authorizations:\n";
	POSITION	pos = DList.GetHeadPosition ();
	while (pos) {
		CDescriptor	*d = DList.GetNext (pos);
		if (d->IsDisconnecting ())
			continue;
		if ((victim = d->m_pCharacter) && victim->IsWaitingForAuth ()) {
			found_hit = TRUE;
			Msg.Format (" %s%s new %s %s\n", victim->GetName (),
				victim->GetDesc ()->m_pHost,
				RaceTable.GetName (victim->GetRace ()), 
				ClassTable.GetName (victim->GetClass ()));
			Log += Msg;
		}
	}
	if (found_hit) {
		gpDoc->LogString (Log, LOG_PLAYER); 
		to_channel (Log, CHANNEL_MONITOR, "Monitor", 1);
	}
} 


// Handle all kinds of updates.
// Called once per pulse from game loop.
void update_handler ()
{
	static int	pulse_area;
	static int	pulse_mobile;
	static int	pulse_violence;
	static int	pulse_point;
	static int	pulse_second;
	timeval		stime;
	timeval		etime;

	try {
		if (timechar) {
			set_char_color (AT_PLAIN, timechar);
			timechar->SendText ("Starting update timer.\n\r");
			gpDoc->gettimeofday (&stime, NULL);
		}

		if (--pulse_area <= 0) {
			pulse_area	= number_range (PULSE_AREA / 2, 3 * PULSE_AREA / 2);
			area_update ();
		}

		if (--pulse_mobile <= 0) {
			pulse_mobile	= PULSE_MOBILE;
			mobile_update  ();
		}

		if (--pulse_violence <= 0) {
			pulse_violence	= PULSE_VIOLENCE;
			violence_update ();
		}

		try {
			if (--pulse_point <= 0) {
				pulse_point = (int) (number_range ((int) (PULSE_TICK * 0.75),
					(int) (PULSE_TICK * 1.25)));

				auth_update ();				// Gorog
				weather_update ();
				char_update ();
				obj_update ();
				clear_vrooms ();			// remove virtual rooms
			}
		}
		catch (CException* ex) {
			CSwException	sx;
			char			buf [128];
			UINT			id;

			if (ex->GetErrorMessage (buf, sizeof (buf), &id))
				sx.Printf ("CException %u:%s in Auth, weather...", id, buf);
			else sx.Printf ("Unknown CException in Auth, weather...");
			ex->Delete ();
			throw sx;
		}

		if (--pulse_second <= 0) {
			pulse_second = PULSE_PER_SECOND;
			char_check ();
			RebootCheck ();
		}

		try {
			auction->Update ();				// update any auction action

			tele_update ();
			aggr_update ();
			obj_act_update ();
			room_act_update ();
			ExtractedObjList.RemoveAll ();	// dispose of extracted objects
			clean_char_queue ();			// dispose of dead mobs/quitting chars
		}
		catch (CException* ex) {
			CSwException	sx;
			char			buf [128];
			UINT			id;

			if (ex->GetErrorMessage (buf, sizeof (buf), &id))
				sx.Printf ("CException %u:%s in Auction, aggr...", id, buf);
			else sx.Printf ("Unknown CException in Auction, aggr...");
			ex->Delete ();
			throw sx;
		}

		if (timechar) {
			gpDoc->gettimeofday (&etime, NULL);
			set_char_color (AT_PLAIN, timechar);
			timechar->SendText ("Update timing complete.\n\r");
			subtract_times (&etime, &stime);
			timechar->SendTextf ("Timing took %d.%06d seconds.\n\r",
				etime.tv_sec, etime.tv_usec);
			timechar = NULL;
		}
	}
	catch (CException* ex) {
		CSwException	sx;
		char			buf [128];
		UINT			id;

		if (ex->GetErrorMessage (buf, sizeof (buf), &id))
			sx.Printf ("CException %u:%s in Update Handler.", id, buf);
		else sx.Printf ("Unknown CException in Update Handler.");
		ex->Delete ();
		throw sx;
	}
}


void remove_portal (CObjData *portal)
{
	CRoomIndexData	*fromRoom, *toRoom;
	CCharacter		*ch;
	CExitData		*pexit;
	BOOL			found;

	if (!portal) {
		bug ("remove_portal: portal is NULL");
		return;
	}

	fromRoom = portal->in_room;
	found = FALSE;
	if (!fromRoom) {
		bug ("remove_portal: portal->in_room is NULL");
		return;
	}

	for (pexit = fromRoom->first_exit; pexit; pexit = pexit->GetNext ())
		if (pexit->IsPortal ()) {
			found = TRUE;
			break;
		}

	if (!found) {
		bug ("remove_portal: portal not found in room %d!", fromRoom->vnum);
		return;
	}

	if (pexit->vdir != DIR_PORTAL)
		bug ("remove_portal: exit in dir %d != DIR_PORTAL", pexit->vdir);

	if ((toRoom = pexit->GetToRoom ()) == NULL)
		bug ("remove_portal: toRoom is NULL");

	extract_exit (fromRoom, pexit);
	// send a message to toRoom
	if (toRoom && (ch = toRoom->first_person))
		act (AT_PLAIN, "A magical portal above winks from existence.",
			ch, NULL, NULL, TO_ROOM);
}


class CBootData {
public:

	char	*msg;
	BOOL	bSent;
	int		time;
};

static CBootData Bd [] = {
	{ "You feel the ground shake as the end comes near!", 0, 60 },
	{ "Lightning crackles in the sky above!", 0, 120 },
	{ "Crashes of thunder sound across the land!", 0, 180 },
	{ "The sky has suddenly turned midnight black.", 0, 240 },
	{ "You notice the life forms around you slowly dwindling away.", 0, 300 },
	{ "The seas across the ($T) have turned frigid.", 0, 600 },
	{ "The aura of magic that surrounds the ($T) seems slightly unstable.", 0, 900 },
	{ "You sense a change in the magical forces surrounding you.", 0, 1800 },
};
#ifdef XXXX
static CBootData Bd [] = {
	{ "You feel the ground shake as the end comes near!", 0, 10 },
	{ "Lightning crackles in the sky above!", 0, 20 },
	{ "Crashes of thunder sound across the land!", 0, 30 },
	{ "The sky has suddenly turned midnight black.", 0, 40 },
	{ "You notice the life forms around you slowly dwindling away.", 0, 50 },
	{ "The seas across the ($T) have turned frigid.", 0, 60 },
	{ "The aura of magic that surrounds the ($T) seems slightly unstable.", 0, 70 },
	{ "You sense a change in the magical forces surrounding you.", 0, 80 },
};
#endif

void RebootCheck ()
{
	if (gpDoc->IsShuttingDown ())
		return;

	CSwTime	&Bt = gpDoc->m_RebootTime;

	if (Bt <= CurrentTime) {
		CCharacter	*vch;

		if (auction->IsActive ()) {
			CString	Sm;
			Sm.Format ("Sale of %s has been stopped by mud.",
				auction->GetItem ()->GetShortDescr ());
			talk_auction (Sm);
			obj_to_char (auction->GetItem (), auction->GetSeller ());
			auction->Stop ();
		}

		// Save all the player files
		for (vch = first_char; vch; vch = vch->GetNext ())
			if (! vch->IsNpc ())
				save_char_obj (vch);

		ResetBootMessages ();

		gpDoc->SetReboot ();
		gpDoc->ShutSmaugDown ();
		return;
	}

	if ((CurrentTime.GetTotalSeconds () % 1800) == 0) {
		CString	Pm;
		Pm.Format ("%s: %d players", NCCP CurrentTime.GetString (),
			DList.GetCount ());
		append_to_file (FileTable.GetName (SM_USAGE_FILE), NCCP Pm);
	}

	CTime	BootTime = Bt.GetCtime ();
	CTime	TestTime = CurrentTime.GetCtime ();

	for (int i=0; i < DIM (Bd); ++i) {
		if ((TestTime + Bd [i].time) > BootTime) {
			if (! Bd [i].bSent) {
				CString	Msg = Bd [i].msg;
				int pos = Msg.Find ("($T)");
				if (pos > 0) {
					Msg = Msg.Left (pos) + SysData.GetShortTitle () +
						Msg.Mid (pos + 4);
				}
				echo_to_all (AT_YELLOW, Msg, ECHOTAR_ALL);
				if (i < 6)
					gpDoc->m_bDenyNewPlayers = TRUE;
				Bd [i].bSent = TRUE;
			}
			break;
		}
	}
}


void ResetBootMessages ()
{
	for (int i=0; i < DIM (Bd); ++i)
		Bd [i].bSent = FALSE;			// reset for next reboot
}

  
void subtract_times (struct timeval *etime, struct timeval *stime)
{
  etime->tv_sec -= stime->tv_sec;
  etime->tv_usec -= stime->tv_usec;
  while (etime->tv_usec < 0)
  {
    etime->tv_usec += 1000000;
    etime->tv_sec--;
  }
  return;
}