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/
/*
 * rooms.cpp
 *	 Room routines.
 *   ____            _
 *  |  _ \ ___  __ _| |_ __ ___  ___
 *  | |_) / _ \/ _` | | '_ ` _ \/ __|
 *  |  _ <  __/ (_| | | | | | | \__ \
 *  |_| \_\___|\__,_|_|_| |_| |_|___/
 *
 * 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 "mud.h"
#include "effects.h"
#include <sstream>

//*********************************************************************
//						Track
//*********************************************************************

Track::Track() {
	num = 0;
	size = NO_SIZE;
	direction = "";
}

short Track::getNum() const { return(num); }
Size Track::getSize() const { return(size); }
bstring Track::getDirection() const { return(direction); }

void Track::setNum(short n) { num = MAX(0, n); }
void Track::setSize(Size s) { size = s; }
void Track::setDirection(bstring dir) { direction = dir; }

//*********************************************************************
//						load
//*********************************************************************

void Track::load(xmlNodePtr curNode) {
	xmlNodePtr childNode = curNode->children;

	while(childNode) {
		if(NODE_NAME(childNode, "Direction")) xml::copyToBString(direction, childNode);
		else if(NODE_NAME(childNode, "Size")) size = whatSize(xml::toNum<int>(childNode));
		else if(NODE_NAME(childNode, "Num")) xml::copyToNum(num, childNode);

		childNode = childNode->next;
	}
}

//*********************************************************************
//						save
//*********************************************************************

void Track::save(xmlNodePtr curNode) const {
	xml::saveNonNullString(curNode, "Direction", direction.c_str());
	xml::saveNonZeroNum(curNode, "Size", size);
	xml::saveNonZeroNum(curNode, "Num", num);
}


BaseRoom::BaseRoom() {
	first_ext = 0;
	first_obj = 0;
	first_mon = 0;
	first_ply = 0;

	memset(misc, 0, sizeof(misc));
	hooks.setParent(this);
}


bstring BaseRoom::getVersion() const { return(version); }
void BaseRoom::setVersion(bstring v) { version = v; }

Room* BaseRoom::getUniqueRoom() {
	return(dynamic_cast<Room*>(this));
}
AreaRoom* BaseRoom::getAreaRoom() {
	return(dynamic_cast<AreaRoom*>(this));
}
const Room* BaseRoom::getConstUniqueRoom() const {
	return(dynamic_cast<const Room*>(this));
}
const AreaRoom* BaseRoom::getConstAreaRoom() const {
	return(dynamic_cast<const AreaRoom*>(this));
}


bstring BaseRoom::fullName() const {
	const Room* uRoom = getConstUniqueRoom();
	const AreaRoom* aRoom = getConstAreaRoom();
	std::ostringstream oStr;

	if(uRoom) {
		oStr << uRoom->info.str() << "(" << uRoom->name << ")";
	} else if(aRoom) {
		oStr << aRoom->mapmarker.str();
	} else
		oStr << "invalid room";
	return(oStr.str());
}


//*********************************************************************
//						Room
//*********************************************************************

Room::Room() {
	memset(name, 0, sizeof(name));
	short_desc = "";
	long_desc = "";
	lowLevel = highLevel = maxmobs = trap = trapweight = trapstrength = 0;
	memset(flags, 0, sizeof(flags));
	roomExp = 0;
	size = NO_SIZE;
	beenhere = 0;
	memset(last_mod, 0, sizeof(last_mod));

	memset(lastModTime, 0, sizeof(lastModTime));

	memset(lastPly, 0, sizeof(lastPly));
	memset(lastPlyTime, 0, sizeof(lastPlyTime));
}

bstring Room::getShortDescription() const { return(short_desc); }
bstring Room::getLongDescription() const { return(long_desc); }
short Room::getLowLevel() const { return(lowLevel); }
short Room::getHighLevel() const { return(highLevel); }
short Room::getMaxMobs() const { return(maxmobs); }
short Room::getTrap() const { return(trap); }
CatRef Room::getTrapExit() const { return(trapexit); }
short Room::getTrapWeight() const { return(trapweight); }
short Room::getTrapStrength() const { return(trapstrength); }
bstring Room::getFaction() const { return(faction); }
long Room::getBeenHere() const { return(beenhere); }
int Room::getRoomExperience() const { return(roomExp); }
Size Room::getSize() const { return(size); }

void Room::setShortDescription(const bstring& desc) { short_desc = desc; }
void Room::setLongDescription(const bstring& desc) { long_desc = desc; }
void Room::appendShortDescription(const bstring& desc) { short_desc += desc; }
void Room::appendLongDescription(const bstring& desc) { long_desc += desc; }
void Room::setLowLevel(short lvl) { lowLevel = MAX(0, lvl); }
void Room::setHighLevel(short lvl) { highLevel = MAX(0, lvl); }
void Room::setMaxMobs(short m) { maxmobs = MAX(0, m); }
void Room::setTrap(short t) { trap = t; }
void Room::setTrapExit(CatRef t) { trapexit = t; }
void Room::setTrapWeight(short weight) { trapweight = MAX(0, weight); }
void Room::setTrapStrength(short strength) { trapstrength = MAX(0, strength); }
void Room::setFaction(bstring f) { faction = f; }
void Room::incBeenHere() { beenhere++; }
void Room::setRoomExperience(int exp) { roomExp = MAX(0, exp); }
void Room::setSize(Size s) { size = s; }

//*********************************************************************
//						deconstructors
//*********************************************************************

void BaseRoom::BaseDestroy() {
	xtag 	*xp = first_ext, *xtemp=0;
	while(xp) {
		xtemp = xp->next_tag;
		delete xp->ext;
		delete xp;
		xp = xtemp;
	}
	first_ext = 0;

	otag	*op = first_obj, *otemp=0;
	while(op) {
		otemp = op->next_tag;
		delete op->obj;
		delete op;
		op = otemp;
	}
	first_obj = 0;

	ctag	*cp = first_mon, *ctemp=0;
	while(cp) {
		ctemp = cp->next_tag;
		free_crt(cp->crt);
		delete cp;
		cp = ctemp;
	}
	first_mon = 0;

	cp = first_ply;
	while(cp) {
		ctemp = cp->next_tag;
		delete cp;
		cp = ctemp;
	}
	first_ply = 0;

	effects.removeAll();
	removeEffectsIndex();
}

Room::~Room() {
	BaseDestroy();
}

AreaRoom::~AreaRoom() {
	BaseDestroy();
	reset();
}

//*********************************************************************
//						WanderInfo
//*********************************************************************

WanderInfo::WanderInfo() {
	traffic = 0;
}

short WanderInfo::getTraffic() const { return(traffic); }
void WanderInfo::setTraffic(short t) { traffic = MAX(0, MIN(100, t)); }

//*********************************************************************
//						getRandom
//*********************************************************************
// This function used to be weighted - it would check 0-9 and if an entry didn't
// exist, no monster would come. If we pick a random entry and the list size is
// less than 10, then we have increased traffic.

CatRef WanderInfo::getRandom() const {
	std::map<int, CatRef>::const_iterator it;
	CatRef cr;
	int	i=0;

	if(!random.size())
		return(cr);

	// past the end
	it = random.end();
	// the last element
	it--;

	if(it == random.begin())
		return(cr);

	// pick a random element, adding weight if <9
	i = (*it).first;
	i = mrand(0, i < 9 ? 9 : i);

	it = random.find(i);
	if(it != random.end())
		cr = (*it).second;
	return(cr);
}

//*********************************************************************
//						load
//*********************************************************************

void WanderInfo::load(xmlNodePtr curNode) {
	xmlNodePtr childNode = curNode->children;

	while(childNode) {
			 if(NODE_NAME(childNode, "Traffic")) xml::copyToNum(traffic, childNode);
		else if(NODE_NAME(childNode, "RandomMonsters")) {
			loadCatRefArray(childNode, random, "Mob", NUM_RANDOM_SLOTS);
		}
		childNode = childNode->next;
	}
}

//*********************************************************************
//						save
//*********************************************************************

void WanderInfo::save(xmlNodePtr curNode) const {
	xml::saveNonZeroNum(curNode, "Traffic", traffic);
	saveCatRefArray(curNode, "RandomMonsters", "Mob", random, NUM_RANDOM_SLOTS);
}

//*********************************************************************
//						reset
//*********************************************************************

void AreaRoom::reset() {
	xtag 	*xp = first_ext, *xtemp=0;

	area = 0;
	decCompass = needsCompass = stayInMemory = false;

	while(xp) {
		xtemp = xp->next_tag;
		delete xp->ext;
		delete xp;
		xp = xtemp;
	}
	first_ext = 0;
}

//*********************************************************************
//						AreaRoom
//*********************************************************************

AreaRoom::AreaRoom(Area *a, const MapMarker *m) {
	reset();

	area = a;
	if(m) {
		setMapMarker(m);
		recycle();
	}
}

bool AreaRoom::getNeedsCompass() const { return(needsCompass); }
bool AreaRoom::getDecCompass() const { return(decCompass); }
bool AreaRoom::getStayInMemory() const { return(stayInMemory); }

void AreaRoom::setNeedsCompass(bool need) { needsCompass = need; }
void AreaRoom::setDecCompass(bool dec) { decCompass = dec; }
void AreaRoom::setStayInMemory(bool stay) { stayInMemory = stay; }

//*********************************************************************
//						recycle
//*********************************************************************

void AreaRoom::recycle() {
    otag*   op = first_obj, *otemp=0;

    while(op) {
        otemp = op->next_tag;
        delete op->obj;
        delete op;
        op = otemp;
    }
    first_obj = 0;

    updateExits();
}

//*********************************************************************
//						setMapMarker
//*********************************************************************

void AreaRoom::setMapMarker(const MapMarker* m) {
	bstring str = mapmarker.str();
	area->rooms.erase(str);
	*&mapmarker = *m;
	str = mapmarker.str();
	area->rooms[str] = this;
}

//*********************************************************************
//						updateExits
//*********************************************************************

bool AreaRoom::updateExit(bstring dir) {
	if(dir == "north") {
		mapmarker.add(0, 1, 0);
		link_rom(this, &mapmarker, dir);
		mapmarker.add(0, -1, 0);

	} else if(dir == "east") {
		mapmarker.add(1, 0, 0);
		link_rom(this, &mapmarker, dir);
		mapmarker.add(-1, 0, 0);

	} else if(dir == "south") {
		mapmarker.add(0, -1, 0);
		link_rom(this, &mapmarker, dir);
		mapmarker.add(0, 1, 0);

	} else if(dir == "west") {
		mapmarker.add(-1, 0, 0);
		link_rom(this, &mapmarker, dir);
		mapmarker.add(1, 0, 0);

	} else if(dir == "northeast") {
		mapmarker.add(1, 1, 0);
		link_rom(this, &mapmarker, dir);
		mapmarker.add(-1, -1, 0);

	} else if(dir == "northwest") {
		mapmarker.add(-1, 1, 0);
		link_rom(this, &mapmarker, dir);
		mapmarker.add(1, -1, 0);

	} else if(dir == "southeast") {
		mapmarker.add(1, -1, 0);
		link_rom(this, &mapmarker, dir);
		mapmarker.add(-1, 1, 0);

	} else if(dir == "southwest") {
		mapmarker.add(-1, -1, 0);
		link_rom(this, &mapmarker, dir);
		mapmarker.add(1, 1, 0);

	} else
		return(false);
	return(true);
}

void AreaRoom::updateExits() {
	updateExit("north");
	updateExit("east");
	updateExit("south");
	updateExit("west");
	updateExit("northeast");
	updateExit("northwest");
	updateExit("southeast");
	updateExit("southwest");
}


//*********************************************************************
//						canSave
//*********************************************************************

const char *exitNameByOrder(int i) {
	if(i==0) return("north");
	if(i==1) return("east");
	if(i==2) return("south");
	if(i==3) return("west");
	if(i==4) return("northeast");
	if(i==5) return("northwest");
	if(i==6) return("southeast");
	if(i==7) return("southwest");
	return("");
}

bool AreaRoom::canSave() const {
	xtag	*xp=0;
	int		i=0;

	if(unique.id)
		return(true);
	if(effects.list.size())
		return(true);

	if(first_ext) {
		xp = first_ext;
		while(xp) {
			if(strcmp(xp->ext->name, exitNameByOrder(i++)))
				return(true);
			// doesnt check rooms leading to other area rooms
			if(xp->ext->target.room.id)
				return(true);
			if(xp->ext->effects.list.size())
				return(true);
			xp = xp->next_tag;
		}
		if(i != 8)
			return(true);
	}

	return(false);
}


//*********************************************************************
//						getRandomWanderInfo
//*********************************************************************

WanderInfo* AreaRoom::getRandomWanderInfo() {
	std::list<AreaZone*>::iterator it;
	AreaZone *zone=0;
	TileInfo* tile=0;
	std::map<int, WanderInfo*> w;
	int i=0;

	// we want to randomly pick one of the zones
	for(it = area->zones.begin() ; it != area->zones.end() ; it++) {
		zone = (*it);
		if(zone->wander.getTraffic() && zone->inside(area, &mapmarker)) {
			w[i++] = &zone->wander;
		}
	}

	tile = area->getTile(area->getTerrain(0, &mapmarker, 0, 0, 0, true), area->getSeasonFlags(&mapmarker));
	if(tile && tile->wander.getTraffic())
		w[i++] = &tile->wander;

	if(!i)
		return(0);
	return(w[mrand(0,i-1)]);
}


//*********************************************************************
//						getWanderInfo
//*********************************************************************

WanderInfo* BaseRoom::getWanderInfo() {
	Room*	uRoom = getUniqueRoom();
	if(uRoom)
		return(&uRoom->wander);
	AreaRoom* aRoom = getAreaRoom();
	if(aRoom)
		return(aRoom->getRandomWanderInfo());
	return(0);
}

//*********************************************************************
//						save
//*********************************************************************

void AreaRoom::save(Player* player) const {
	char			filename[256];

	checkDirExists(AREAROOMPATH, true);
	sprintf(filename, "%s/%d/", AREAROOMPATH, area->id);
	checkDirExists(filename, true);
	strcat(filename, mapmarker.filename().c_str());

	if(!canSave()) {
		if(file_exists(filename)) {
			if(player)
				player->print("Restoring this room to generic status.\n");
			unlink(filename);
		} else {
			if(player)
				player->print("There is no reason to save this room!\n\n");
		}
		return;
	}

	// record rooms saved during roomMove
	if(gConfig->isMovingRoom())
		gConfig->moveRoomLog((bstring)"a" + mapmarker.str(), false);

	xmlDocPtr	xmlDoc;
	xmlNodePtr		rootNode, curNode;

	xmlDoc = xmlNewDoc(BAD_CAST "1.0");
	rootNode = xmlNewDocNode(xmlDoc, NULL, BAD_CAST "AreaRoom", NULL);
	xmlDocSetRootElement(xmlDoc, rootNode);

	unique.save(rootNode, "Unique", false);
	xml::saveNonZeroNum(rootNode, "NeedsCompass", needsCompass);
	xml::saveNonZeroNum(rootNode, "DecCompass", decCompass);
	effects.save(rootNode, "Effects");
	hooks.save(rootNode, "Hooks");

	curNode = xml::newStringChild(rootNode, "MapMarker");
	mapmarker.save(curNode);

	curNode = xml::newStringChild(rootNode, "Exits");
	saveExitsXml(curNode, first_ext);

	xml::saveFile(filename, xmlDoc);
	xmlFreeDoc(xmlDoc);

	if(player)
		player->print("Room saved.\n");
}


//*********************************************************************
//						load
//*********************************************************************

void AreaRoom::load(xmlNodePtr rootNode) {
	xmlNodePtr childNode = rootNode->children;

	if(first_ext) {
		xtag	*prev=0, *xp = first_ext;

		while(xp) {
			prev = xp;
			xp = xp->next_tag;
			delete prev;
		}
		first_ext = 0;
	}

	while(childNode) {
		if(NODE_NAME(childNode, "Exits"))
			readExitsXml(childNode);
		else if(NODE_NAME(childNode, "MapMarker")) {
			mapmarker.load(childNode);
			area->rooms[mapmarker.str()] = this;
		}
		else if(NODE_NAME(childNode, "Unique")) unique.load(childNode);
		else if(NODE_NAME(childNode, "NeedsCompass")) xml::copyToBool(needsCompass, childNode);
		else if(NODE_NAME(childNode, "DecCompass")) xml::copyToBool(decCompass, childNode);
		else if(NODE_NAME(childNode, "Effects")) effects.load(childNode);
		else if(NODE_NAME(childNode, "Hooks")) hooks.load(childNode);

		childNode = childNode->next;
	}
	addEffectsIndex();
}

//*********************************************************************
//						canDelete
//*********************************************************************

bool AreaRoom::canDelete() {
	// don't delete unique rooms
	if(stayInMemory || unique.id)
		return(false);
	if(first_mon)
		return(false);
	if(first_ply)
		return(false);
	if(first_obj) {
		otag	*op = first_obj;
		while(op) {
			if(!op->obj->flagIsSet(O_DISPOSABLE))
				return(false);
			op = op->next_tag;
		}
	}
	if(first_ext) {
		int		i=0;
		xtag	*xp = first_ext;
		while(xp) {
			if(strcmp(xp->ext->name, exitNameByOrder(i++)))
				return(false);
			// doesnt check rooms leading to other area rooms
			if(xp->ext->target.room.id)
				return(false);
			xp = xp->next_tag;
		}
		if(i != 8)
			return(false);
	}
	return(true);
}

//*********************************************************************
//					isInteresting
//*********************************************************************
// This will determine whether the looker will see anything of
// interest in the room. This is used by cmdScout.

bool AreaRoom::isInteresting(Player *looker) {
	ctag	*cp=0;
	xtag	*xp=0;
	int		i=0;

	if(unique.id)
		return(true);

	cp = first_ply;
	while(cp) {
		if(!cp->crt->flagIsSet(P_HIDDEN) && looker->canSee(cp->crt))
			return(true);
		cp = cp->next_tag;
	}

	cp = first_mon;
	while(cp) {
		if(!cp->crt->flagIsSet(M_HIDDEN) && looker->canSee(cp->crt))
			return(true);
		cp = cp->next_tag;
	}

	xp = first_ext;
	for(i=0; i<8; i++) {
		if(!xp)
			return(true);
		if(strcmp(xp->ext->name, exitNameByOrder(i)))
			return(true);
		xp = xp->next_tag;
	}

	// check out the remaining exits
	while(xp) {
		if(looker->showExit(xp->ext))
			return(true);
		xp = xp->next_tag;
	}

	return(false);
}


bool AreaRoom::flagIsSet(int flag) const {
	MapMarker m = mapmarker;
	return(area->flagIsSet(flag, &m));
}

bool Room::flagIsSet(int flag) const {
	return(flags[flag/8] & 1<<(flag%8));
}
void Room::setFlag(int flag) {
	flags[flag/8] |= 1<<(flag%8);
}
void Room::clearFlag(int flag) {
	flags[flag/8] &= ~(1<<(flag%8));
}
bool Room::toggleFlag(int flag) {
	if(flagIsSet(flag))
		clearFlag(flag);
	else
		setFlag(flag);
	return(flagIsSet(flag));
}


//*********************************************************************
//						isMagicDark
//*********************************************************************

bool BaseRoom::isMagicDark() const {
	ctag	*cp=0;
	otag	*op=0;

	if(flagIsSet(R_MAGIC_DARKNESS))
		return(true);

	// check for darkness spell
	cp = first_ply;
	while(cp) {
		// darkness spell on staff does nothing
		if(cp->crt->isEffected("darkness") && !cp->crt->isStaff())
			return(true);
		if(cp->crt->flagIsSet(P_DARKNESS) && !cp->crt->flagIsSet(P_DM_INVIS))
			return(true);
		cp = cp->next_tag;
	}
	cp = first_mon;
	while(cp) {
		if(cp->crt->isEffected("darkness"))
			return(true);
		if(cp->crt->flagIsSet(M_DARKNESS))
			return(true);
		cp = cp->next_tag;
	}

	op = first_obj;
	while(op) {
		if(op->obj->flagIsSet(O_DARKNESS))
			return(true);
		op = op->next_tag;
	}
	return(false);
}


//*********************************************************************
//						isNormalDark
//*********************************************************************

bool BaseRoom::isNormalDark() const {
	if(flagIsSet(R_DARK_ALWAYS))
		return(true);

	if(!isDay() && flagIsSet(R_DARK_AT_NIGHT))
		return(true);

	return(false);
}


void BaseRoom::addExit(Exit *ext) {
	xtag	*xp, *temp, *prev;

	xp = 0;
	xp = new xtag;
	if(!xp) merror("addExit", FATAL);

	xp->ext = ext;

	if(!first_ext) {
		first_ext = xp;
		return;
	}

	temp = first_ext;

	while(temp) {
		prev = temp;
		temp = temp->next_tag;
	}
	prev->next_tag = xp;
}

//
// This function checks the status of the exits in a room.  If any of
// the exits are closable or lockable, and the correct time interval
// has occurred since the last opening/unlocking, then the doors are
// re-shut/re-closed.
//
void BaseRoom::checkExits() {
	xtag	*xp=0;
	long	t = time(0);

	xp = first_ext;
	while(xp) {
		if(!xp->ext) {
			broadcast(isDm, "^G*** Room %s has an exit tag with a null exit!", this->fullName().c_str());
			xp = xp->next_tag;
			continue;
		}
		if(	xp->ext->flagIsSet(X_LOCKABLE) &&
			(xp->ext->ltime.ltime + xp->ext->ltime.interval) < t)
		{
			xp->ext->setFlag(X_LOCKED);
			xp->ext->setFlag(X_CLOSED);
		} else if(	xp->ext->flagIsSet(X_CLOSABLE) &&
					(xp->ext->ltime.ltime + xp->ext->ltime.interval) < t)
			xp->ext->setFlag(X_CLOSED);

		xp = xp->next_tag;
	}
}

//*********************************************************************
//						deityRestrict
//*********************************************************************
// if our particular flag is set, fail = 0
// else if ANY OTHER flags are set, fail = 1

bool BaseRoom::deityRestrict(const Creature* creature) const {

	// if no flags are set
	if(	!flagIsSet(R_ARAMON) &&
		!flagIsSet(R_CERIS) &&
		!flagIsSet(R_ENOCH) &&
		!flagIsSet(R_GRADIUS) &&
		!flagIsSet(R_KAMIRA) &&
		!flagIsSet(R_ARES) &&
		!flagIsSet(R_ARACHNUS) &&
		!flagIsSet(R_LINOTHAN) )
		return(false);

	// if the deity flag is set and they match, they pass
	if(	(flagIsSet(R_ARAMON) && creature->getDeity() == ARAMON) ||
		(flagIsSet(R_CERIS) && creature->getDeity() == CERIS) ||
		(flagIsSet(R_ENOCH) && creature->getDeity() == ENOCH) ||
		(flagIsSet(R_GRADIUS) && creature->getDeity() == GRADIUS) ||
		(flagIsSet(R_KAMIRA) && creature->getDeity() == KAMIRA) ||
		(flagIsSet(R_ARES) && creature->getDeity() == ARES) ||
		(flagIsSet(R_ARACHNUS) && creature->getDeity() == ARACHNUS) ||
		(flagIsSet(R_LINOTHAN) && creature->getDeity() == LINOTHAN) )
		return(false);

	return(true);
}



//*********************************************************************
//							maxCapacity
//*********************************************************************
// 0 means no limit

int BaseRoom::maxCapacity() const {
	if(flagIsSet(R_ONE_PERSON_ONLY))
		return(1);
	if(flagIsSet(R_TWO_PEOPLE_ONLY))
		return(2);
	if(flagIsSet(R_THREE_PEOPLE_ONLY))
		return(3);
	return(0);
}

//*********************************************************************
//							isFull
//*********************************************************************
// Can the player fit in the room?

bool BaseRoom::isFull() const {
	return(maxCapacity() && countVisPly() >= maxCapacity());
}

//*********************************************************************
//						countVisPly
//*********************************************************************
// This function counts the number of (non-DM-invisible) players in a
// room and returns that number.

int BaseRoom::countVisPly() const {
	ctag	*cp;
	int		num = 0;

	cp = first_ply;
	while(cp) {
		if(!cp->crt->flagIsSet(P_DM_INVIS))
			num++;
		cp = cp->next_tag;
	}

	return(num);
}


//*********************************************************************
//						countCrt
//*********************************************************************
// This function counts the number of creatures in a
// room and returns that number.

int BaseRoom::countCrt() const {
	ctag	*cp;
	int	num = 0;

	cp = first_mon;
	while(cp) {
		if(!cp->crt->isPet())
			num++;
		cp = cp->next_tag;
	}

	return(num);
}


//*********************************************************************
//						getMaxMobs
//*********************************************************************

int BaseRoom::getMaxMobs() const {
	const Room* room = getConstUniqueRoom();
	if(!room)
		return(MAX_MOBS_IN_ROOM);
	return(room->getMaxMobs() ? room->getMaxMobs() : MAX_MOBS_IN_ROOM);
}

//*********************************************************************
//						wake
//*********************************************************************

void BaseRoom::wake(bstring str, bool noise) const {
	ctag *cp = first_ply;

	while(cp) {
		cp->crt->wake(str, noise);
		cp = cp->next_tag;
	}
}

//*********************************************************************
//						vampCanSleep
//*********************************************************************

bool BaseRoom::vampCanSleep(Socket* sock) const {
	// not at night
	if(!isDay()) {
		sock->print("Your thirst for blood keeps you from sleeping.\n");
		return(false);
	}
	// during the day, under certain circumstances
	if(isMagicDark())
		return(true);
	if(	flagIsSet(R_DARK_ALWAYS) ||
		flagIsSet(R_INDOORS) ||
		flagIsSet(R_UNDERGROUND) )
		return(true);
	sock->print("The sunlight prevents you from sleeping.\n");
	return(false);
}


//*********************************************************************
//						isCombat
//*********************************************************************
// checks to see if there is any time of combat going on in this room

bool BaseRoom::isCombat() const {
	ctag	*cp=0;

	cp = first_ply;
	while(cp) {
		if(cp->crt->inCombat(true))
			return(true);
		cp = cp->next_tag;
	}

	cp = first_mon;
	while(cp) {
		if(cp->crt->inCombat(true))
			return(true);
		cp = cp->next_tag;
	}

	return(false);
}


//*********************************************************************
//						isUnderwater
//*********************************************************************

bool BaseRoom::isUnderwater() const {
	return(flagIsSet(R_UNDER_WATER_BONUS));
}


//*********************************************************************
//						isOutdoors
//*********************************************************************

bool BaseRoom::isOutdoors() const {
	return(!flagIsSet(R_INDOORS) && !flagIsSet(R_SHOP_STORAGE));
}


//*********************************************************************
//						magicBonus
//*********************************************************************

bool BaseRoom::magicBonus() const {
	return(flagIsSet(R_MAGIC_BONUS));
}


//*********************************************************************
//						isForest
//*********************************************************************

bool BaseRoom::isForest() const {
	return(flagIsSet(R_FOREST_OR_JUNGLE));
}

//*********************************************************************
//						isWater
//*********************************************************************
// drowning in area rooms is different than drowning in unique water rooms,
// therefore we need a different isWater function

bool AreaRoom::isWater() const {
	return(area->isWater(mapmarker.getX(), mapmarker.getY(), mapmarker.getZ(), true));
}

//*********************************************************************
//						isRoad
//*********************************************************************

bool AreaRoom::isRoad() const {
	return(area->isRoad(mapmarker.getX(), mapmarker.getY(), mapmarker.getZ(), true));
}

//*********************************************************************
//						getUnique
//*********************************************************************
// creature is allowed to be null

CatRef AreaRoom::getUnique(Creature *creature, bool skipDec) {
	if(!needsCompass)
		return(unique);
	CatRef	cr;

	if(!creature)
		return(cr);

	bool pet = creature->isPet();
	Player*	player = creature->getMaster();

	if(	!player ||
		!player->ready[HELD-1] ||
		player->ready[HELD-1]->getShotscur() < 1 ||
		!player->ready[HELD-1]->compass ||
		*player->ready[HELD-1]->compass != *&mapmarker
	)
		return(cr);

	// no need to decrement twice if their pet is going through
	if(!skipDec && decCompass && !pet) {
		player->ready[HELD-1]->decShotscur();
		player->breakObject(player->ready[HELD-1], HELD);
	}
	return(unique);
}

bool BaseRoom::isDropDestroy() const {
	if(!flagIsSet(R_DESTROYS_ITEMS))
		return(false);
	const AreaRoom* aRoom = getConstAreaRoom();
	if(aRoom && aRoom->area->isRoad(aRoom->mapmarker.getX(), aRoom->mapmarker.getY(), aRoom->mapmarker.getZ(), true))
		return(false);
	return(true);
}
bool BaseRoom::hasRealmBonus(Realm realm) const {
	return(flagIsSet(R_ROOM_REALM_BONUS) && flagIsSet(getRealmRoomBonusFlag(realm)));
}
bool BaseRoom::hasOppositeRealmBonus(Realm realm) const {
	return(flagIsSet(R_OPPOSITE_REALM_BONUS) && flagIsSet(getRealmRoomBonusFlag(getOppositeRealm(realm ))));
}


Monster* BaseRoom::getTollkeeper() {
	ctag	*cp = first_mon;
	while(cp) {
		if(!cp->crt->isPet() && cp->crt->flagIsSet(M_TOLLKEEPER))
			return(cp->crt->getMonster());
		cp = cp->next_tag;
	}
	return(0);
}
MudObject* BaseRoom::findTarget(Creature* searcher, const cmd* cmnd, int num) {
	return(findTarget(searcher, cmnd->str[num], cmnd->val[num]));
}

MudObject* BaseRoom::findTarget(Creature* searcher, const bstring& name, const int num,  bool monFirst, bool firstAggro, bool exactMatch) {
	int match=0;
	return(findTarget(searcher, name, num, monFirst, firstAggro, exactMatch, match));
}

MudObject* BaseRoom::findTarget(Creature* searcher, const bstring& name, const int num,  bool monFirst, bool firstAggro, bool exactMatch, int& match) {
	MudObject* toReturn = 0;

	if((toReturn = findCreature(searcher, name, num, monFirst, firstAggro, exactMatch, match))) {
		return(toReturn);
	}
	// TODO: Search the room's objects
	return(toReturn);
}


// Wrapper for the real findCreature to support legacy callers
Creature* BaseRoom::findCreature(Creature* searcher, const cmd* cmnd, int num) {
	return(findCreature(searcher, cmnd->str[num], cmnd->val[num]));
}

Creature* BaseRoom::findCreaturePython(Creature* searcher, const bstring& name, bool monFirst, bool firstAggro, bool exactMatch ) {
	int ignored=0;
	int num = 1;

	bstring newName = name;
	newName.trim();
	int sLoc = newName.ReverseFind(" ");
	if(sLoc != bstring::npos) {
		num = newName.right(newName.length() - sLoc).toInt();
		if(num != 0) {
			newName = newName.left(sLoc);
		} else {
			num = 1;
		}
	}

	std::cout << "Looking for '" << newName << "' #" << num << "\n";

	return(findCreature(searcher, newName, num, monFirst, firstAggro, exactMatch, ignored));
}

// Wrapper for the real findCreature for callers that don't care about the value of match
Creature* BaseRoom::findCreature(Creature* searcher, const bstring& name, const int num, bool monFirst, bool firstAggro, bool exactMatch ) {
	int ignored=0;
	return(findCreature(searcher, name, num, monFirst, firstAggro, exactMatch, ignored));
}

// The real findCreature, will take and return the value of match as to allow for findTarget to find the 3rd thing named
// gold with a monster name goldfish, player named goldmine, and some gold in the room!
Creature* BaseRoom::findCreature(Creature* searcher, const bstring& name, const int num, bool monFirst, bool firstAggro, bool exactMatch, int& match) {

	if(!searcher || name.empty())
		return(NULL);

	Creature* target=0;
	ctag	*cp;
	//match=0,
	int		found=0;
	bool	firstSearchDone=false;

	// check for monsters first
	if(monFirst) {
		cp = first_mon;
	} else {
		cp = first_ply;
	}

	// Incase we start off with a null first ply/mon list
	if(!cp) {
		firstSearchDone = true;
		if(monFirst)
			cp = first_ply;
		else
			cp = first_mon;
	}

	while(cp) {
		target = cp->crt;
		cp = cp->next_tag;
		if(cp == NULL && firstSearchDone == false) {
			firstSearchDone = true;
			// We've run to the end of the first search, switch to the next list now
			if(!monFirst) {
				// checking for monster now
				cp = first_mon;
			} else {
				// checking for player now
				cp = first_ply;
			}
		}
		if(!target)
			continue;
		if(!searcher->canSee(target))
			continue;

		if(exactMatch) {
			if(!strcmp(target->name, name.c_str())) {
                match++;
                if(match == num)
                    return(target);
            }

		} else {
			if(keyTxtEqual(target, name.c_str())) {
				match++;
				if(match == num) {
					found = 1;
					break;
				}
			}
		}

	}

	if(firstAggro && found) {
		if(num < 2 && searcher->isPlayer() && searcher->flagIsSet(P_KILL_AGGROS))
			return(getFirstAggro(target, searcher));
		else
			return(target);
	} else if(found) {
		return(target);
	} else {
		return(NULL);
	}
}


Monster* BaseRoom::findMonster(Creature* searcher, const cmd* cmnd, int num) {
	return(findMonster(searcher, cmnd->str[num], cmnd->val[num]));
}
Monster* BaseRoom::findMonster(Creature* searcher, const bstring& name, const int num, bool firstAggro, bool exactMatch) {
	Creature* creature = findCreature(searcher, name, num, true, firstAggro, exactMatch);
	if(creature)
		return(creature->getMonster());
	return(NULL);
}

Player* BaseRoom::findPlayer(Creature* searcher, const cmd* cmnd, int num) {
	return(findPlayer(searcher, cmnd->str[num], cmnd->val[num]));
}
Player* BaseRoom::findPlayer(Creature* searcher, const bstring& name, const int num, bool exactMatch) {
	Creature* creature = findCreature(searcher, name, num, false, false, exactMatch);
	if(creature)
		return(creature->getPlayer());
	return(NULL);
}

//*********************************************************************
//						isSunlight
//*********************************************************************
// the first version is isSunlight is only called when an AreaRoom
// does not exist - when rogues scout, for example

bool Area::isSunlight(const MapMarker* mapmarker) const {
	if(!isDay())
		return(false);

	// if their room has any of the following flags, no kill
	if(	flagIsSet(R_DARK_ALWAYS, mapmarker) ||
		flagIsSet(R_UNDERGROUND, mapmarker) ||
		flagIsSet(R_INDOORS, mapmarker) ||
		flagIsSet(R_ETHEREAL_PLANE, mapmarker) ||
		flagIsSet(R_IS_STORAGE_ROOM, mapmarker) ||
		flagIsSet(R_MAGIC_DARKNESS, mapmarker)
	)
		return(false);

	return(true);
}

// this version of isSunlight is called when a room exists

bool BaseRoom::isSunlight() const {

	// !this - for offline players. make it not sunny so darkmetal isnt destroyed
	if(!this)
		return(false);

	if(!isDay() || !isOutdoors() || isMagicDark())
		return(false);

	const Room* uRoom = getConstUniqueRoom();
	const CatRefInfo* cri=0;
	if(uRoom)
		cri = gConfig->getCatRefInfo(uRoom->info.area);

	// be nice - no killing darkmetal in special rooms
	if(cri && (
		!uRoom->info.id ||
		uRoom->info.id == cri->recall ||
		uRoom->info.id == cri->limbo
	) )
		return(false);

	// if their room has any of the following flags, no kill
	if(	flagIsSet(R_DARK_ALWAYS) ||
		flagIsSet(R_UNDERGROUND) ||
		flagIsSet(R_ETHEREAL_PLANE) ||
		flagIsSet(R_IS_STORAGE_ROOM)
	)
		return(false);

	return(true);
}

//*********************************************************************
//						destroy
//*********************************************************************

void Room::destroy() {
	saveToFile(0, LS_BACKUP);
	char	filename[256];
	strcpy(filename, roomPath(info));
	unlink(filename);
	expelPlayers();
	gConfig->delRoomQueue(info);
	delete this;
}

//*********************************************************************
//						expelPlayers
//*********************************************************************

void BaseRoom::expelPlayers() {
	Player* target=0;
	ctag*	cp = first_ply;
	BaseRoom* newRoom=0;

	while(cp) {
		target = cp->crt->getPlayer();
		cp = cp->next_tag;

		target->printColor("^rYou are expelled from this room as it collapses in on itself!\n");
		broadcast(target->getSock(), this, "^r%M is expelled from the room as it collapses in on itself!", target);

		newRoom = target->bound.loadRoom(target);
		if(!newRoom || newRoom == this)
			newRoom = abortFindRoom(target, "expelPlayers");

		target->deleteFromRoom();
		target->addToRoom(newRoom);
		target->doPetFollow();
	}
}


//*********************************************************************
//						getSpecialArea
//*********************************************************************

Location getSpecialArea(int (CatRefInfo::*toCheck), CatRef cr) {
	return(getSpecialArea(toCheck, 0, cr.area, cr.id));
}

Location getSpecialArea(int (CatRefInfo::*toCheck), const Creature* creature, bstring area, short id) {
	Location l;

	if(creature) {
		if(creature->area_room) {
			l.room.setArea("area");
			l.room.id = creature->area_room->area->id;
		} else {
			l.room.setArea(creature->room.area);
		}
	}
	if(area != "")
		l.room.setArea(area);
	if(id)
		l.room.id = id;


	const CatRefInfo* cri = gConfig->getCatRefInfo(l.room.area, l.room.id, true);

	if(!cri)
		cri = gConfig->getCatRefInfo(gConfig->defaultArea);
	if(cri) {
		l.room.setArea(cri->area);
		l.room.id = (cri->*toCheck);
	} else {
		// failure!
		l.room.setArea(gConfig->defaultArea);
		l.room.id = 0;
	}

	return(l);
}

//*********************************************************************
//						getLimboRoom
//*********************************************************************

Location Creature::getLimboRoom() const {
	return(getSpecialArea(&CatRefInfo::limbo, this, "", 0));
}

//*********************************************************************
//						getRecallRoom
//*********************************************************************

Location Creature::getRecallRoom() const {
	const Player* player = getConstPlayer();
	if(player && player->flagIsSet(P_T_TO_BOUND))
		return(player->bound);
	return(getSpecialArea(&CatRefInfo::recall, this, "", 0));
}