roh/conf/area/
roh/game/talk/
roh/help/
roh/monsters/ocean/
roh/objects/ocean/
roh/player/
roh/rooms/area/1/
roh/rooms/misc/
roh/rooms/ocean/
roh/src-2.44b/
/*
 * update.c
 *   Routines to handle game updates
 *   ____            _
 *  |  _ \ ___  __ _| |_ __ ___  ___
 *  | |_) / _ \/ _` | | '_ ` _ \/ __|
 *  |  _ <  __/ (_| | | | | | | \__ \
 *  |_| \_\___|\__,_|_|_| |_| |_|___/
 *
 * Permission to use, modify and distribute is granted via the
 *  Creative Commons - Attribution - Non Commercial - Share Alike 3.0 License
 *    http://creativecommons.org/licenses/by-nc-sa/3.0/
 *
 * 	Copyright (C) 2007-2009 Jason Mitchell, Randi Mitchell
 * 	   Contributions by Tim Callahan, Jonathan Hseu
 *  Based on Mordor (C) Brooke Paul, Brett J. Vickers, John P. Freeman
 *
 */
#include <sys/signal.h>

#include "mud.h"
#include "login.h"
#include "commands.h"
#include "move.h"
#include "factions.h"
#include "ships.h"
#include "calendar.h"
#include "web.h"

#include <libxml/parser.h> // xmlCleanupParser

bool			firstLoop=true;
long 			last_track_update=0;
long 			last_time_update=0;
long			last_weather_update=0;
static long		lastTickUpdate=0;
static long 	last_update=0;
static long 	last_shutdown_update=0;
static long 	last_action_update=0;
long			TX_interval = 4200;
short			Random_update_interval = 6;
short			Action_update_interval = 6;

extern msparse  Mob[MMAX];

// over the course of time, ships begin to become a few seconds
// off from the main clock. this variable will help us keep
// things in check

//*********************************************************************
//						update_game
//*********************************************************************
// This function handles all the updates that occur while players are
// typing.

void Server::updateGame() {
	long    t = time(0);

	if(t == last_update)
		return;
	last_update = t;

	updateFishing(t);

	// update on the hour: ie, 3:00
	// Sometimes on startup, we don't get to this section of the code in 1 second,
	// meaning this wont run until 1 hour after the game has started. Throwing in
	// or-firstLoop gives us 2 seconds of time.
	if(!gConfig->currentMinutes() && (!(t%2) || firstLoop))
		update_time(t);

	// Run ships every other second.
	if(t%2)
		update_ships();

	// Prune Dns once a day
	if(t - lastDnsPrune >= 86400)
		pruneDns();
	if(t - lastTickUpdate >= 5)
		pulseTicks(t);
	// pulse effects
	if(t - lastUserUpdate >= 20) {
		updateUsers(t);
		pulseCreatureEffects(t);
	}
	// same cycle length as creature pulses, but offset 10 seconds
	if(t - lastRoomPulseUpdate >= 20)
		pulseRoomEffects(t);

	if(t - last_track_update >= 20)
		update_track(t);
	if(t - lastRandomUpdate >= Random_update_interval)
		updateRandom(t);
	if(t != lastActiveUpdate)
		updateActive(t);
	if(t - last_weather_update >= 60)
		updateWeather(t);
	if(t - last_action_update >= Action_update_interval)
		update_action(t);
	if(last_dust_output && last_dust_output < t)
		update_dust_oldPrint(t);
	if(t > gConfig->getLotteryRunTime())
		gConfig->runLottery();

	if(Shutdown.ltime && t - last_shutdown_update >= 30)
		if(Shutdown.ltime + Shutdown.interval <= t+500)
			update_shutdown(t);
}


//*********************************************************************
//						weatherize
//*********************************************************************

bstring Config::weatherize(WeatherString w, const BaseRoom* room) const {
	const CatRefInfo* cri=0;
	const cWeather* weather=0;
	bstring txt = "";

	// get weather data out of catrefinfo
	cri = gConfig->getCatRefInfo(room, 0);
	if(cri) {
		weather = cri->getWeather();
		// if the requested string doesn't exist, default to config weather
		if(weather && weather->get(w) == "")
			weather = 0;
	}

	// try parent
	if(!weather) {
		cri = gConfig->getCatRefInfo(room, 1);
		if(cri) {
			weather = cri->getWeather();
			// if the requested string doesn't exist, default to config weather
			if(weather && weather->get(w) == "")
				weather = 0;
		}
	}

	// if that fails, we grab out of config
	if(!weather)
		weather = gConfig->getWeather();

	if(!weather)
		return("");

	return(weather->get(w));
}

//*********************************************************************
//						pulseCreatureEffects
//*********************************************************************
// lastUserUpdate is set in updateUsers

void Server::pulseCreatureEffects(long t) {
	Monster	*monster=0;
	const Socket *sock=0;
	Player* player=0;
	ctag	*cp = first_active;
	std::list<Socket*>::const_iterator it;

	for(it = sockets.begin(); it != sockets.end() ; ) {
		sock = *it;
		it++;
		if(!sock->isConnected())
			continue;
		player = sock->getPlayer();
		if(player)
			player->pulseEffects(t);
	}

	while(cp) {
		monster = cp->crt->getMonster();
		cp = cp->next_tag;

		monster->pulseEffects(t);
	}
}

//*********************************************************************
//						pulseRoomEffects
//*********************************************************************

void Server::pulseRoomEffects(long t) {
	std::list<BaseRoom*>::iterator it;

	for(it = effectsIndex.begin() ; it != effectsIndex.end() ; ) {
		(*it)->pulseEffects(t);

		if(!(*it)->needsEffectsIndex())
			it = effectsIndex.erase(it);
		else
			it++;
	}

	lastRoomPulseUpdate = t;
}

//*********************************************************************
//						addEffectsIndex
//*********************************************************************

void BaseRoom::addEffectsIndex() {
	if(!needsEffectsIndex())
		return;
	gServer->addEffectsIndex(this);
}

void Server::addEffectsIndex(BaseRoom* room) {
	// you can only be in the list once!
	std::list<BaseRoom*>::const_iterator it;
	for(it = effectsIndex.begin() ; it != effectsIndex.end() ; it++) {
		if((*it) == room)
			return;
	}

	effectsIndex.push_back(room);
}

//*********************************************************************
//						needsEffectsIndex
//*********************************************************************

bool BaseRoom::needsEffectsIndex() const {
	// any room effects?
	if(effects.list.size())
		return(true);

	// any exit effects?
	xtag* xp = first_ext;
	while(xp) {
		if(xp->ext->effects.list.size())
			return(true);
		xp = xp->next_tag;
	}

	return(false);
}

//*********************************************************************
//						removeEffectsIndex
//*********************************************************************

bool BaseRoom::removeEffectsIndex() {
	if(needsEffectsIndex())
		return(false);
	gServer->removeEffectsIndex(this);
	return(true);
}

void Server::removeEffectsIndex(BaseRoom* room) {
	std::list<BaseRoom*>::iterator it;
	for(it = effectsIndex.begin() ; it != effectsIndex.end() ; it++) {
		if((*it) == room) {
			effectsIndex.erase(it);
			return;
		}
	}
}

//*********************************************************************
//						removeEffectsOwner
//*********************************************************************
// on suicide, we remove the owner of the effect

void Server::removeEffectsOwner(const Creature* owner) {
	std::list<BaseRoom*>::iterator it;
	xtag* xp=0;

	for(it = effectsIndex.begin() ; it != effectsIndex.end() ; it++) {
		(*it)->effects.removeOwner(owner);
		xp = (*it)->first_ext;
		while(xp) {
			xp->ext->effects.removeOwner(owner);
			xp = xp->next_tag;
		}
	}
}

//*********************************************************************
//						weather
//*********************************************************************

void weather(WeatherString w) {
	bstring weather;
	char color;

	if(w == WEATHER_SUNRISE)
		color = 'Y';
	else if(w == WEATHER_SUNSET)
		color = 'D';
	else
		color = 'w';

	foreach(Socket* sock, gServer->sockets) {
		Player* player = sock->getPlayer();

		if(!player)
			continue;
		if(player->fd < 0)
			continue;
		if(	(w == WEATHER_SUNRISE || w == WEATHER_SUNSET) &&
			player->isEffected("vampirism")
		) {
			// if sunrise/sunset, vampires always see it
		} else {
			if(!player->getRoom()->isOutdoors())
				continue;
		}

		weather = gConfig->weatherize(w, player->getRoom());
		if(weather != "")
			player->printColor("^%c%s\n", color, weather.c_str());
	}
}

//*********************************************************************
//						updateFishing
//*********************************************************************

void updateFishing(long t) {
	bool someoneFishing=false, isFishing = false;
	// don't loop through players unless someone is fishing
	if(!gConfig->isFishing())
		return;
	Player* player=0;
	foreach(Socket* sock, gServer->sockets) {
		player = sock->getPlayer();
		if(!player)
			continue;
		if(player->getFishing()) {
			// this player is fishing
			isFishing = true;
			if(player->getFishing() <= t) {
				// actually, this player is done fishing
				isFishing = false;
				player->setFishing(false);
				doFish(player);
			}
			// if someone is still fishing, keep track of it
			someoneFishing = someoneFishing || isFishing;
		}
	}
	// if nobody is fishing, let this function know it can stop looping over players
	if(!someoneFishing)
		gConfig->setFishing(false);
}

//*********************************************************************
//						update_time
//*********************************************************************
// This function updates the game time in hours. When it is 6am a getSunrise()
// message is broadcast. When it is 8pm a getSunset() message is broadcast.

void update_time(long t) {
	int		daytime=0;
	last_time_update = t;

	// do not do this immeditately on startup,
	// but do everything below this
	if(!firstLoop) {
		if(!gConfig->currentHour())
			gConfig->calendar->advance();
		// save on the hour
		gConfig->calendar->save();
		gConfig->saveShips();
	}
	firstLoop = false;

	std::pair<bstring, Player*> p;

	daytime = gConfig->currentHour();
	if(daytime == SUNRISE-1) {
		foreach(p, gServer->players) {
			if(p.second->isEffected("vampirism"))
				p.second->printColor("^YThe sun is about to rise.\n");
		}
	} else if(daytime == SUNRISE) {
		weather(WEATHER_SUNRISE);
		gConfig->killMortalObjects();
	} else if(daytime == SUNSET-1) {
		foreach(p, gServer->players) {
			if(p.second->isEffected("vampirism"))
				p.second->printColor("^DThe sun is about to set.\n");
		}
	} else if(daytime == SUNSET) {
		weather(WEATHER_SUNSET);
	}

	foreach(p, gServer->players) {
		if(p.second->isEffected("vampirism"))
			p.second->computeAC();
	}

	// run at midnight
	if(!daytime && !gServer->isValgrind()) {
		runDemographics();
		gConfig->uniqueDecay();
		gConfig->cleanUpAreas();
	}
}

//*********************************************************************
//						update_shutdown
//*********************************************************************
// This function broadcasts a shutdown message every 30 seconds until
// shutdown is achieved. Then it saves off all rooms and players,
// and exits the game.

void update_shutdown(long t) {
	long    i;
	last_shutdown_update = t;

	i = Shutdown.ltime + Shutdown.interval;

	// fix for strange bug where *shut 0 gives you 1 second
	if(i-t==1)
		i = t;

	if(i > t) {
		if(i-t > 60)
			broadcast("### Game shutdown in %d:%02d minutes.", (i-t)/60L, (i-t)%60L);
		else
			broadcast("### Game shutdown in %d seconds.", i-t);
	} else {
		broadcast("### Shutting down now.");
		gServer->processOutput();
		loge("--- Game shut down ---\n");
		// run moveRoomAbort before rooms/players get saved
		gConfig->moveRoomAbort();
		gConfig->resaveAllRooms(1);
		save_all_ply();
		disconnect_all_ply();
		gConfig->save();
		cleanUpMemory();

		printf("Goodbye.\n");
		kill(getpid(), 9);
	}
}


//*********************************************************************
//						updateWeather
//*********************************************************************

void updateWeather(long t) {
	int	j=0, n=0;

	last_weather_update = t;

	for(j=0; j<5; j++) {
		if(Weather[j].ltime + Weather[j].interval < t) {
			switch (j) {
			case 0:
				if(mrand(1,100) > 80) {
					n = mrand(0,2);
					n -= 1;
					n = Weather[j].misc + n;
					if(n < 0 || n > 4)
						n = 0;
					switch (n) {
					case 0:
						Weather[j].ltime = t;
						weather(WEATHER_EARTH_TREMBLES);
						break;
					case 1:
						Weather[j].ltime = t;
						weather(WEATHER_HEAVY_FOG);
						break;
					}
					Weather[j].misc = (short)n;
					Weather[j].interval = 3200;
				}
				break;

			case 1:
				if(mrand(1,100) > 50 && isDay()) {
					n = mrand(0,2);
					n -= 1;
					n = Weather[j].misc + n;
					if(n < 0 || n > 4)
						n = 0;
					switch (n) {
					case 0:
						Weather[j].ltime = t;
						weather(WEATHER_BEAUTIFUL_DAY);
						break;
					case 1:
						Weather[j].ltime = t;
						weather(WEATHER_BRIGHT_SUN);
						break;
					case 3:
						Weather[j].ltime = t;
						weather(WEATHER_GLARING_SUN);
						break;
					case 4:
						Weather[j].ltime = t;
						weather(WEATHER_HEAT);
						break;
					}
					Weather[j].misc = (short)n;
					Weather[j].interval = 1600;
				}
				break;
			case 2:
				if(mrand(1,100) > 50) {
					n = mrand(0,2);
					n -= 1;
					n = Weather[j].misc +n;
					if(n< 0 || n> 4)
						n = 0;
					switch (n) {
					case 0:
						Weather[j].ltime = t;
						weather(WEATHER_STILL);
						break;
					case 1:
						Weather[j].ltime = t;
						weather(WEATHER_LIGHT_BREEZE);
						break;
					case 2:
						Weather[j].ltime = t;
						weather(WEATHER_STRONG_WIND);
						break;
					case 3:
						Weather[j].ltime = t;
						weather(WEATHER_WIND_GUSTS);
						break;
					case 4:
						Weather[j].ltime = t;
						weather(WEATHER_GALE_FORCE);
						break;
					}
					Weather[j].misc = (short)n;
					Weather[j].interval = 900;
				}
				break;
			case 3:
				if(mrand(1,100) > 50) {
					n = mrand(0,2);
					n -= 1;
					n = Weather[j].misc+n;
					if(n< 0 || n> 6)
						n = 0;
					switch (n) {
					case 0:
						Weather[j].ltime = t;
						weather(WEATHER_CLEAR_SKIES);
						break;
					case 1:
						Weather[j].ltime = t;
						weather(WEATHER_LIGHT_CLOUDS);
						break;
					case 2:
						Weather[j].ltime = t;
						weather(WEATHER_THUNDERHEADS);
						break;
					case 3:
						Weather[j].ltime =t;
						weather(WEATHER_LIGHT_RAIN);
						break;
					case 4:
						Weather[j].ltime = t;
						weather(WEATHER_HEAVY_RAIN);
						break;
					case 5:
						Weather[j].ltime = t;
						weather(WEATHER_SHEETS_RAIN);
						break;
					case 6:
						Weather[j].ltime = t;
						weather(WEATHER_TORRENT_RAIN);
						break;
					}
					Weather[j].misc = (short)n;
					Weather[j].interval = 1100;
				}
				break;
			case 4:
				if(mrand(1,100) > 50 && !isDay()) {
					n = mrand(0,2);
					n -= 1;
					n += Weather[j].misc;
					if(n< 0 || n> 4)
						n = 0;
					switch (n) {
					case 0:
						Weather[j].ltime = t;
						weather(WEATHER_NO_MOON);
						break;
					case 1:
						Weather[j].ltime = t;
						weather(WEATHER_SLIVER_MOON);
						break;
					case 2:
						Weather[j].ltime = t;
						weather(WEATHER_HALF_MOON);
						break;
					case 3:
						Weather[j].ltime = t;
						weather(WEATHER_WAXING_MOON);
						break;
					case 4:
						Weather[j].ltime = t;
						weather(WEATHER_FULL_MOON);
						break;
					}
					Weather[j].misc = (short)n;
					Weather[j].interval = 1200;
				}
				break;
			}
		}
	}

	n = 11 - Weather[1].misc - Weather[2].misc - (Weather[3].misc - 2) + Weather[4].misc;
	if(n>25 || n < 2)
		n=11;
	//Random_update_interval = (short)n;
	Random_update_interval = 5;
	return;
}


//*********************************************************************
//						update_action
//*********************************************************************
// update function for logic scripts

void update_action(long t) {
	Creature* creature=0, *victim=0;
	Object	*object=0;
	BaseRoom* room=0;
	const ctag	*cp=0;
	ctag	*vcp=0;
	ttag	*act=0, *tact=0;
	int		i=0, on_cmd=0, thresh=0;
	int		xdir=0, num=0;
	char	*resp;
	const char	*xits[] =  { "n","ne","e","se","s","sw","w","nw","u","d" };
	cmd 	cmnd;


	last_action_update = t;
	for(cp = gServer->getFirstActive();cp;cp = cp->next_tag) {
		creature = cp->crt;
		if(creature) {
			room = creature->getRoom();
			if(room && creature->flagIsSet(M_LOGIC_MONSTER)) {
				if(!cp->crt->first_tlk)
					loadCreature_actions(cp->crt);
				else {
					act = cp->crt->first_tlk;
					on_cmd = act->on_cmd;
					on_cmd--;
					i = 0;
					if(on_cmd)
						while(i < on_cmd) {
							act = act->next_tag;
							i++;
						}
					on_cmd+=2; // set for next command, can be altered later
					// proccess commands based on a higharcy
					if(act->test_for) {
						switch(act->test_for) {
						case 'P': // test for player
							victim = room->findPlayer(creature, act->response, 1);
							if(victim) {
								if(creature->first_tlk->target)
									delete[] creature->first_tlk->target;
								creature->first_tlk->target = new char[strlen(act->response)+1];
								strcpy(creature->first_tlk->target, act->response);
								act->success = 1;
							} else {
								if(creature->first_tlk->target)
									free(creature->first_tlk->target);
								creature->first_tlk->target = 0;
								act->success = 0;
							}
							break;
						case 'C': // test for a player with class
						case 'R': // test for a player with race
							for(vcp = room->first_ply; vcp; vcp = vcp->next_tag) {
								if(act->test_for == 'C')
									if(vcp->crt->getClass() == act->arg1) {
										if(creature->first_tlk->target)
											delete[] creature->first_tlk->target;
										creature->first_tlk->target = new char[strlen(vcp->crt->name)+1];
										strcpy(creature->first_tlk->target, vcp->crt->name);
										act->success = 1;
										break;
									}
								if(act->test_for == 'R')
									if(vcp->crt->getRace() == act->arg1) {
										if(creature->first_tlk->target)
											delete creature->first_tlk->target;
										creature->first_tlk->target = new char[strlen(vcp->crt->name)+1];
										strcpy(creature->first_tlk->target, vcp->crt->name);
										act->success = 1;
										break;
									}
							}
							if(!vcp) {
								if(creature->first_tlk->target)
									delete creature->first_tlk->target;
								creature->first_tlk->target = 0;
								act->success = 0;
							}
							break;
						case 'O': // test for object in room
							object = findObject(creature, room->first_obj, act->response, 1);

							if(object) {
								if(creature->first_tlk->target)
									delete creature->first_tlk->target;
								creature->first_tlk->target = new char[strlen(act->response)+1];
								strcpy(creature->first_tlk->target, act->response);
								act->success = 1;
								// loge(victim->name);
							} else {
								if(creature->first_tlk->target)
									free(creature->first_tlk->target);
								creature->first_tlk->target = 0;
								act->success = 0;
							}
							break;
						case 'o': // test for object on players
							break;
						case 'M': // test for monster
							victim = room->findMonster(creature, act->response, 1);
							if(victim) {
								if(creature->first_tlk->target)
									delete creature->first_tlk->target;
								creature->first_tlk->target = new char[strlen(act->response)+1];
								strcpy(creature->first_tlk->target, act->response);
								act->success = 1;
							} else {
								if(creature->first_tlk->target)
									free(creature->first_tlk->target);
								creature->first_tlk->target = 0;
								act->success = 0;
							}
							break;
						}

					}
					if(act->if_cmd) {
						// test to see if command was successful
						for(tact = creature->first_tlk; tact; tact = tact->next_tag) {
							if(tact->type == act->if_cmd)
								break;
						}
						if(tact) {
							if(act->if_goto_cmd && tact->success)
								on_cmd = act->if_goto_cmd;
							if(act->not_goto_cmd && !tact->success)
								on_cmd = act->not_goto_cmd;
						} else {
							if(act->not_goto_cmd)
								on_cmd = act->not_goto_cmd;
						}
					}
					// run a action
					if(act->do_act) {
						//	num = 0;
						//	thresh = 0;
						act->success = 1;

						resp = act->response;
						if(isdigit(*(resp))) {

							num = 10*(atoi(resp));
							++resp;
							num = (num == 0) ? 100:num;

							thresh = mrand(1,200);

							//	broadcast(isDm, -1, cp->crt->getRoom(), "*DM* NUM: %d Thresh: %d", num, thresh);

						}


						switch(act->do_act) {
						case 'E': // broadcast response to room
							if(thresh <= num)
								broadcast(NULL, cp->crt->getRoom(), "%s", resp);

							break;
						case 'S': // say to room
							if(thresh <= num)
								broadcast(NULL, cp->crt->getRoom(), "%M says, \"%s\"", creature, resp);
							break;
						case 'T':	// Mob Trash-talk
							if(mrand(1,100) <= 10) {

								if(countTotalEnemies(creature) > 0 && !creature->getMonster()->nearEnemy()) {
									if(creature->daily[DL_BROAD].cur > 0) {
										broadcast("### %M broadcasted, \"%s\"", creature, resp);
										subtractMobBroadcast(creature, 0);
									}
								}
							}
							break;
						case 'B':	// Mob general random broadcasts
							if(mrand(1,100) <= 10) {

								if(countTotalEnemies(creature) < 1 && thresh <= num) {
									if(creature->daily[DL_BROAD].cur > 0) {
										broadcast("### %M broadcasted, \"%s\"", creature, resp);
										subtractMobBroadcast(creature, 0);
									}
								}
							}
							break;
						case 'A': // attack monster in target string
							if(creature->first_tlk->target && !creature->first_enm) {
								victim = room->findMonster(creature, creature->first_tlk->target, 1);
								if(!victim)
									return;
								//								if(victim)
								//									addEnmCrt(victim->name, creature);
								//broadcast(NULL, creature->getRoom(), "%M attacks %s.",creature,victim);
								victim->getMonster()->monsterCombat((Monster*)creature);
								if(creature->first_tlk->target)
									free(creature->first_tlk->target);
								creature->first_tlk->target = 0;
							}
							break;
						case 'a': // attack player target
							break;
						case 'c': // cast a spell on target
							break;
						case 'F': // force target to do somthing
							break;
						case '|': // set a flag on target
							break;
						case '&': // remove flag on target
							break;
						case 'P': // perform social
							break;
						case 'O': // open door
							break;
						case 'C': // close door
							break;
						case 'D': // delay action
							break;
						case 'G': // go into a keyword exit
							break;
						case '0': // go n
						case '1': // go ne
						case '2': // go e
						case '3': // go se
						case '4': // go s
						case '5': // go sw
						case '6': // go w
						case '7': // go nw
						case '8': // go up
						case '9': // go down
							xdir = act->do_act - '0';
							strcpy(cmnd.str[0], xits[xdir]);
							// TODO: Dom: fix update
							printf("action move\n");
							//move(creature, &cmnd);
							break;
						}
					}
					// unconditional jump
					if(act->goto_cmd) {
						act->success = 1;
						cp->crt->first_tlk->on_cmd = act->goto_cmd;
					} else
						cp->crt->first_tlk->on_cmd = on_cmd;
				}
			}
		}
	}
}

//*********************************************************************
//						update_dust_oldPrint
//*********************************************************************

void update_dust_oldPrint(long t) {
	last_dust_output=0;
	broadcast("Ominous thunder rumbles in the distance.");
}

//*********************************************************************
//						handle_pipe
//*********************************************************************

void handle_pipe( int sig) {
	// What should we do here?
	// Should just continue on
}

//*********************************************************************
//						doCrash
//*********************************************************************

void doCrash(int sig) {
	long t = time(0);
	std::ostringstream oStr;
	Crash++;
	if(Crash > 1) exit(-12);

	// Uninstall signal handlers so we don't get back here again
	signal(SIGALRM, SIG_DFL);
	signal(SIGFPE, SIG_DFL);
	signal(SIGSEGV, SIG_DFL);
	gServer->sendCrash();

	gConfig->save();
	broadcast("                   C R A S H !\n");
	broadcast(isStaff, "%s", ctime(&t));

	oStr << "People online: ";
	std::pair<bstring, Player*> p;
	Player* player=0;
	int i=0;
	foreach(Socket* sock, gServer->sockets) {
		player = sock->getPlayer();
		if(!player)
			continue;
		if(i++)
			oStr << ", ";

		if(!sock->isConnected())
			oStr << player->name << " (connecting)";
		else
			oStr << player->name << " (^xcmd:" + player->getLastCommand() << ":" << (t-sock->ltime) << "^W)";
	}
	if(!i)
		oStr << "No one";
	oStr << ".";
	broadcast(isDm, "^W%s", oStr.str().c_str());
	gServer->processOutput();

	logn("log.inbytes", "--- %ld\n", InBytes);
	logn("log.outbytes", "-- %ld\n", OutBytes);
	logn("log.crash","--- !CRASH! Game closed ---\n");
	loge("--- !CRASH! Game closed ---\n");
// for some reason, on cygwin this will (sometimes) corrupt room files
#ifndef __CYGWIN__
// Turning this off because of the xp->ext = NULL bug which is erasing exits
//	gConfig->resaveAllRooms(1);
#endif
	save_all_ply();

	cleanUpMemory();


	oStr << "\n";
	//delimit(who);
	logn("log.crash", oStr.str().c_str());

	printf("The mud has crashed :(.\n");
//	abort();
	exit(77);
}

//*********************************************************************
//						crash
//*********************************************************************

void crash(int sig) {
	if(gConfig->sendTxtOnCrash())
		webCrash("");
	doCrash(sig);
}

void cleanup_spelling(void);

//*********************************************************************
//						cleanUpMemory
//*********************************************************************
// Clean up all memory we know is in use -- this way valgrind and any other
// tools can accurately find memory leaks

void cleanUpMemory() {
	gConfig->clearQuestTable();

	gConfig->flushRoom();
	gConfig->flushObject();
	gConfig->flushMonster();
	//flush_ext();

	Config::destroyInstance();
	Server::destroyInstance();
	// Clean up xml memory
	xmlCleanupParser();
	cleanup_spelling();
	// TODO: Clean up spelling memory

	printf("Memory cleaned up.\n");
}

//*********************************************************************
//						subtractMobBroadcast
//*********************************************************************

void subtractMobBroadcast(Creature *monster, int num) {
	if(monster->daily[DL_BROAD].cur > 0) {
		if(num)
			monster->daily[DL_BROAD].cur -= num;
		else
			monster->daily[DL_BROAD].cur --;
	}
}

//*********************************************************************
//						countTotalEnemies
//*********************************************************************

int countTotalEnemies(Creature *monster) {
	int			count = 0;
	etag		*ep=0;

	if(monster->isPlayer())
		return(0);

	ep = monster->first_enm;
	while(ep) {
		count++;
		ep = ep->next_tag;
	}

	return(count);
}

//*********************************************************************
//						clearEnemyPlayer
//*********************************************************************
// This function removes a player's name from every single monster
// on the active list when the player dies, suicides, or is dusted.

void Player::clearEnemyPlayer() {
	const ctag		*cp=0;
	Creature* creature=0;

	if(!(cp = gServer->getFirstActive()))
		return;
	while(cp) {

		if(!cp) {
			merror("cp in active", NONFATAL);
			break;
		}
		if(!(cp->crt)) {
			merror("cp in active", NONFATAL);
			break;
		}

		creature = cp->crt;

		if(!creature) {
			merror("creature in active", NONFATAL);
			break;
		}

		if(!(creature->parent_rom && creature->parent_rom->info.id) && !creature->area_room)
			break;

		creature->getMonster()->delEnmCrt(name);

		cp = cp->next_tag;

	}

	return;
}

//*********************************************************************
//						dmInRoom
//*********************************************************************
// This function will return 1 if anyone is in the room while dmInvis

int BaseRoom::dmInRoom() const {
	ctag *cp = first_ply;

	while(cp) {
		if(cp->crt->flagIsSet(P_DM_INVIS))
			return(1);
		cp = cp->next_tag;
	}

	return(0);
}

//*********************************************************************
//						checkOutlawAggro
//*********************************************************************

void Player::checkOutlawAggro() {
	if(flagIsSet(P_OUTLAW_WILL_BE_ATTACKED)) {
		ctag *outlawmob;
		outlawmob = getRoom()->first_mon;
		while(outlawmob) {
			if(outlawmob->crt->flagIsSet(M_OUTLAW_AGGRO) && !outlawmob->crt->first_enm) {
				printColor("^r%M attacks you.\n", outlawmob->crt);
				broadcast(getSock(), getRoom(), "%M attacks %N.", outlawmob->crt, this);

				outlawmob->crt->updateAttackTimer(true, DEFAULT_WEAPON_DELAY);
				outlawmob->crt->getMonster()->addEnmCrt(this);
			}
			outlawmob = outlawmob->next_tag;
		}
	}
}


//*********************************************************************
//						update_track
//*********************************************************************

void update_track(long t) {
	std::list<Area*>::iterator it;

	if(last_track_update)
		for(it = gConfig->areas.begin() ; it != gConfig->areas.end() ; it++)
			(*it)->updateTrack((int)t - last_track_update);

	last_track_update = t;
}

//*********************************************************************
//						update_ships
//*********************************************************************

void update_ships() {
	std::list<Ship*>::iterator it;
	Ship	*ship=0;
	ShipStop *stop=0;

	// we must delay ships for a while
	if(gConfig->calendar->shipUpdates > gConfig->expectedShipUpdates())
		return;
	gConfig->calendar->shipUpdates++;

	for(it = gConfig->ships.begin() ; it != gConfig->ships.end() ; it++) {
		ship = (*it);
		ship->timeLeft--;
		stop = ship->stops.front();
		// only do last call if we're in port
		if(ship->inPort && ship->timeLeft==60)
			shipBroadcastRange(ship, stop, stop->lastcall);
		// while loop because a ship might take 0 time to get to the next stop
		while(ship->timeLeft <= 0) {
			if(ship->inPort) {
				ship->timeLeft = stop->to_next;
				shipDeleteExits(ship, stop);

				ship->stops.push_back(stop);
				ship->stops.pop_front();
			} else {
				ship->timeLeft = stop->in_dock;
				shipSetExits(ship, stop);
			}
			stop = ship->stops.front();
			ship->inPort = !ship->inPort;
		}
	}

	// we haven't updated enough!
	if(gConfig->calendar->shipUpdates < gConfig->expectedShipUpdates())
		update_ships();
}


//*********************************************************************
//							list_act
//*********************************************************************

int list_act(Player* player, cmd* cmnd) {
	const ctag *cp = gServer->getFirstActive();

	player->print("### Active monster list ###\n");
	player->print("Monster    -    Room Number\n");

	while(cp) {
		if(	(!cp->crt->parent_rom || !cp->crt->parent_rom->info.id) &&
			!cp->crt->area_room
		) {
			if(!cp->crt->name)
				player->print("Bad Mob - Room %s.\n", cp->crt->getRoom()->fullName().c_str());
			else
				player->print("Bad Mob - %s - Room %s.\n", cp->crt->name, cp->crt->getRoom()->fullName().c_str());
			cp = cp->next_tag;
			continue;
		}
		if(!cp->crt->name) {
			player->print("Bad Mob - Room %d.\n", cp->crt->getRoom()->fullName().c_str());
			cp = cp->next_tag;
			continue;
		}
		player->print("%s - %s.\n", cp->crt->name, cp->crt->getRoom()->fullName().c_str());
		cp = cp->next_tag;
	}
	gServer->processOutput();
	return(0);
}