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/
/*
 * players.cpp
 *	 Player 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 "calendar.h"
//#include <libxml/xmlmemory.h>
//#include <libxml/parser.h>

#include <sstream>

char statStr[][5] = {
	"NONE", "STR", "DEX", "CON", "INT", "PTY", "CHA"
};

char saveStr[][5] = {
	"LCK", "POI", "DEA", "BRE", "MEN", "SPL"
};

int findDeity(bstring &deity);

int findStat(bstring &stat) {
	for(int i = 0 ; i < 7 ; i++) {
		if(stat == statStr[i]) {
			return(i);
		}
	}
	return(-1);
}

int findSave(bstring &save) {
	for(int i = 0 ; i < 6 ; i++) {
		if(save == saveStr[i]) {
			return(i);
		}
	}
	return(-1);
}


SkillGain::SkillGain(xmlNodePtr rootNode) {
	load(rootNode);
}
SkillGain::~SkillGain() {
	deities.clear();
}
void SkillGain::load(xmlNodePtr rootNode) {
	xmlNodePtr curNode = rootNode->children;

	while(curNode) {
		if(NODE_NAME(curNode, "Name")) {
			xml::copyToBString(skillName, curNode);
		} else if(NODE_NAME(curNode, "Gained")) {
			xml::copyToNum(gainLevel, curNode);
		} else if(NODE_NAME(curNode, "Deity")) {
			bstring deityStr;
			xml::copyToBString(deityStr, curNode);
			int deity = gConfig->deitytoNum(deityStr);
			if(deity != -1) {
				deities[deity] = true;
			}
		}

		curNode = curNode->next;
	}
}
PlayerClass::~PlayerClass() {
	std::list<SkillGain*>::iterator bsIt;
	std::map<int, LevelGain*>::iterator lgIt;
	std::map<int, PlayerTitle*>::iterator tt;
	SkillGain* sGain;
	LevelGain* lGain;

	for(bsIt = baseSkills.begin() ; bsIt != baseSkills.end() ; bsIt++) {
		sGain = (*bsIt);
		delete sGain;
	}
	baseSkills.clear();
	for(lgIt = levels.begin() ; lgIt != levels.end() ; lgIt++) {
		lGain = (*lgIt).second;
		delete lGain;
	}
	levels.clear();

	for(tt = titles.begin() ; tt != titles.end() ; tt++) {
		PlayerTitle* p = (*tt).second;
		delete p;
	}
	titles.clear();
}

PlayerClass::PlayerClass(xmlNodePtr rootNode) {
	needDeity = false;
	numProf = 1;
	unarmedWeaponSkill = "bare-hand";
	load(rootNode);
}
void PlayerClass::load(xmlNodePtr rootNode) {
//	// Stuff gained on each additional level
//	std::map<int, LevelGain*> levels;

	id = xml::getIntProp(rootNode, "id");
	xml::copyPropToBString(name, rootNode, "Name");

	xmlNodePtr curNode = rootNode->children;

	while(curNode) {
		if(NODE_NAME(curNode, "Title")) {
			titles[1] = new PlayerTitle;
			titles[1]->load(curNode);
		}
		else if(NODE_NAME(curNode, "UnarmedWeaponSkill")) xml::copyToBString(unarmedWeaponSkill, curNode);
		else if(NODE_NAME(curNode, "NumProfs")) xml::copyToNum(numProf, curNode);
		else if(NODE_NAME(curNode, "NeedsDeity")) {
			int i=0;
			xml::copyToNum(i, curNode);
			if(i)
				needDeity = true;
		} else if(NODE_NAME(curNode, "Base")) {
			xmlNodePtr baseNode = curNode->children;
			while(baseNode) {
				if(NODE_NAME(baseNode, "Stats")) {
					xmlNodePtr statNode = baseNode->children;
					while(statNode) {
						if(NODE_NAME(statNode, "Hp")) {
							xml::copyToNum(baseHp, statNode);
						} else if(NODE_NAME(statNode, "Mp")) {
							xml::copyToNum(baseMp, statNode);
						}
						statNode = statNode->next;
					}
				} else if(NODE_NAME(baseNode, "Dice")) damage.load(baseNode);
				else if(NODE_NAME(baseNode, "Skills")) {
					xmlNodePtr skillNode = baseNode->children;
					while(skillNode) {
						if(NODE_NAME(skillNode, "Skill")) {
							SkillGain* skillGain = new SkillGain(skillNode);
							baseSkills.push_back(skillGain);
						}
						skillNode = skillNode->next;
					}
				}
				baseNode = baseNode->next;
			}
		} else if(NODE_NAME(curNode, "Levels")) {
			xmlNodePtr levelNode = curNode->children;
			int lvl;
			while(levelNode) {
				if(NODE_NAME(levelNode, "Level")) {
					lvl = xml::getIntProp(levelNode, "Num");
					if(lvl > 0) {
						levels[lvl] = new LevelGain(levelNode);
					}

					xmlNodePtr childNode = levelNode->children;
					while(childNode) {
						if(NODE_NAME(childNode, "Title")) {
							titles[lvl] = new PlayerTitle;
							titles[lvl]->load(childNode);
						}
						childNode = childNode->next;
					}
				}
				levelNode = levelNode->next;
			}
		}

		curNode = curNode->next;

	}
}

int PlayerClass::getId() const { return(id); }
bstring PlayerClass::getName() const { return(name); }
bstring PlayerClass::getUnarmedWeaponSkill() const { return(unarmedWeaponSkill); }

LevelGain::LevelGain(xmlNodePtr rootNode) {
	load(rootNode);
}
LevelGain::~LevelGain() {
	std::list<SkillGain*>::iterator sgIt;
	SkillGain* sGain;

	for(sgIt = skills.begin() ; sgIt != skills.end() ; sgIt++) {
		sGain = (*sgIt);
		delete sGain;
	}
	skills.clear();
}
void LevelGain::load(xmlNodePtr rootNode) {
	xmlNodePtr curNode = rootNode->children;
	bstring temp;
	while(curNode) {

		if(NODE_NAME(curNode, "HpGain")) {
			xml::copyToNum(hp, curNode);
		} else if(NODE_NAME(curNode, "MpGain")) {
			xml::copyToNum(mp, curNode);
		} else if(NODE_NAME(curNode, "Stat")) {
			xml::copyToBString(temp, curNode);
			stat = findStat(temp);
		} else if(NODE_NAME(curNode, "Save")) {
			xml::copyToBString(temp, curNode);
			save = findSave(temp);
		} else if(NODE_NAME(curNode, "Skills")) {
			xmlNodePtr skillNode = curNode->children;
			while(skillNode) {
				if(NODE_NAME(skillNode, "Skill")) {
					SkillGain* skillGain = new SkillGain(skillNode);
					skills.push_back(skillGain);
				}
				skillNode = skillNode->next;
			}

		}
		curNode = curNode->next;
	}
}

bool Config::loadClasses() {
	xmlDocPtr xmlDoc;
	xmlNodePtr curNode;

	xmlDoc = xml::loadFile(CONFPATH "classes.xml", "Classes");

	if(xmlDoc == NULL)
		return(false);

	curNode = xmlDocGetRootElement(xmlDoc);

	curNode = curNode->children;
	while(curNode && xmlIsBlankNode(curNode))
		curNode = curNode->next;

	if(curNode == 0) {
		xmlFreeDoc(xmlDoc);
		return(false);
	}

	clearClasses();
	bstring className = "";
	while(curNode != NULL) {
		if(NODE_NAME(curNode, "Class")) {
			xml::copyPropToBString( className, curNode, "Name");
			if(className != "") {
				if(classes.find(className) == classes.end()) {
					//printf("\n\tLoaded %s", className.c_str());
					classes[className] = new PlayerClass(curNode);
				} else {
					printf("Error: Duplicate class: %s\n", className.c_str());
				}
			}
		}
		curNode = curNode->next;
	}
	xmlFreeDoc(xmlDoc);
	xmlCleanupParser();
	//printf("\n");
	return(true);
}

void Config::clearClasses() {
	std::map<bstring, PlayerClass*>::iterator pcIt;

	for(pcIt = classes.begin() ; pcIt != classes.end() ; pcIt++) {
		//printf("Erasing class %s\n", ((*pcIt).first).c_str());
		PlayerClass* pClass = (*pcIt).second;
		delete pClass;
	}
	classes.clear();
}
int dmShowClasses(Player* admin, cmd* cmnd) {

	bool	more = admin->isDm() && cmnd->num > 1 && !strcmp(cmnd->str[1], "more");
	bool	all = admin->isDm() && cmnd->num > 1 && !strcmp(cmnd->str[1], "all");

	std::map<bstring, PlayerClass*>::iterator cIt;
	PlayerClass* pClass;
	SkillGain* sGain;
	bstring tmp;

	admin->printColor("Displaying Classes:%s\n",
		admin->isDm() ? "  Type ^y*classlist more^x to view more, ^y*classlist all^x to view all information." : "");
	for(cIt = gConfig->classes.begin() ; cIt != gConfig->classes.end() ; cIt++) {
		pClass = (*cIt).second;
		admin->printColor("Id: ^c%-2d^x   Name: ^c%s\n", pClass->getId(), (*cIt).first.c_str());
		if(more || all) {
			std::ostringstream cStr;
			std::map<int, PlayerTitle*>::iterator tt;

			//cStr << "Class: " << (*cIt).first << "\n";
			cStr << "    Base: " << pClass->getBaseHp() << "hp, " << pClass->getBaseMp() << "mp, "
				 << "Dice: " << pClass->damage.str() << "\n"
				 << "    NumProfs: " << pClass->numProfs() << "   NeedsDeity: " << (pClass->needsDeity() ? "^gYes" : "^rNo") << "^x\n";
			std::list<SkillGain*>::const_iterator sgIt;
			if(all) {
				cStr << "    Skills: ";
				for(sgIt = pClass->getSkillBegin() ; sgIt != pClass->getSkillEnd() ; sgIt++) {
					sGain = *sgIt;
					cStr << gConfig->getSkillDisplayName(sGain->getName()) << "(" << sGain->getGained() << ") ";
				}
				std::map<int, LevelGain*>::const_iterator lgIt;

				cStr << "\n    Levels: ";
				for(lgIt = pClass->getLevelBegin() ; lgIt != pClass->getLevelEnd() ; lgIt ++) {
					LevelGain* lGain = (*lgIt).second;
					int lvl = (*lgIt).first;
					if(!lGain) {
						cStr << "\n ERROR: No level gain information found for " << pClass->getName() << " : " << lvl << ".";
						continue;
					}
					
					cStr << "\n        " << lvl << ": " << lGain->getHp() << "HP, " << lGain->getMp() << "MP, St:" << lGain->getStatStr() << ", Sa:" << lGain->getSaveStr();
					if(lGain->hasSkills()) {
						cStr << "\n        Skills:";
						for(sgIt = lGain->getSkillBegin() ; sgIt != lGain->getSkillEnd() ; sgIt++) {
							sGain = *sgIt;
							cStr << "\n            " << gConfig->getSkillDisplayName(sGain->getName()) << "(" << sGain->getGained() << ") ";
						}
					}
				}
				cStr << "\n    Titles: ";
				for(tt = pClass->titles.begin() ; tt != pClass->titles.end(); tt++) {
					PlayerTitle* title = (*tt).second;
					cStr << "\n        Level: ^c" << (*tt).first << "^x"
						<< "   Male: ^c" << title->getTitle(true) << "^x"
						<< "   Female: ^c" << title->getTitle(false) << "^x";
				}
			}
			cStr << "\n";
			tmp = cStr.str();
			admin->printColor("%s", tmp.c_str());
			gServer->processOutput();
		}
	}
	return(0);
}

bool LevelGain::hasSkills() { return(!skills.empty()); }
std::list<SkillGain*>::const_iterator PlayerClass::getSkillBegin() { return(baseSkills.begin()); }
std::list<SkillGain*>::const_iterator PlayerClass::getSkillEnd() { return(baseSkills.end()); }
std::map<int, LevelGain*>::const_iterator PlayerClass::getLevelBegin() { return(levels.begin()); }
std::map<int, LevelGain*>::const_iterator PlayerClass::getLevelEnd() { return(levels.end()); }
short PlayerClass::getBaseHp() { return(baseHp); }
short PlayerClass::getBaseMp() { return(baseMp); }
bool PlayerClass::needsDeity() { return(needDeity); }
short PlayerClass::numProfs() { return(numProf); }
int SkillGain::getGained() { return(gainLevel); }
bstring SkillGain::getName() const { return(skillName); }

std::list<SkillGain*>::const_iterator LevelGain::getSkillBegin() { return(skills.begin()); }
std::list<SkillGain*>::const_iterator LevelGain::getSkillEnd() { return(skills.end()); }

bstring LevelGain::getStatStr() { return(statStr[stat]); }
bstring LevelGain::getSaveStr() { return(saveStr[save]); }
int LevelGain::getStat() { return(stat); }
int LevelGain::getSave() { return(save); }
int LevelGain::getHp() { return(hp); }
int LevelGain::getMp() { return(mp); }

LevelGain* PlayerClass::getLevelGain(int lvl) { return(levels[lvl]); }

//*********************************************************************
//						deityIsAllowed
//*********************************************************************

bool SkillGain::deityIsAllowed(int deity) {
	if(deities.find(deity) != deities.end())
		return(true);
	else
		return(false);
}

//*********************************************************************
//						hasDeities
//*********************************************************************

bool SkillGain::hasDeities() {
	return(!this->deities.empty());
}

//*********************************************************************
//						pulseTick
//*********************************************************************
// return true if they were destroyed, false if they still exist

void Player::pulseTick(long t) {
	bool ill = false;
	bool noTick = false;
	long mainTick = LT(this, LT_TICK);
	long secTick = LT(this, LT_TICK_SECONDARY);
	long badTick = LT(this, LT_TICK_HARMFUL);
	bool vamp = isNewVampire() && getRoom()->isSunlight();
	bool hardcore = isHardcore();

	if(!getSock())
		return;

	EffectInfo* deathSickness = getEffect("death-sickness");

	ill = isPoisoned() || isDiseased() || isEffected("wounded") || isEffected("petrification");
	noTick = (vamp || isEffected("petrification") || isEffected("wounded"));

	BaseRoom* room = getRoom();
	// ****** Main Tick ******
	if(mainTick < t && !flagIsSet(P_NO_TICK_HP) && !noTick) {
		int hpTickAmt = 0;
		if(cClass != LICH) {
			hpTickAmt = MAX(1, 3 + bonus(constitution.getCur()) + (cClass == BERSERKER ? 2:0));
			if(!ill && !deathSickness) {
				if(!flagIsSet(P_BLOODSAC)) {
					if(flagIsSet(P_SITTING))
						hpTickAmt += 1;
					else if(flagIsSet(P_SLEEPING))
						hpTickAmt += 3;
				}
				if(room && room->flagIsSet(R_FAST_HEAL))
					hpTickAmt += 3;
			}
		} else if(cClass == LICH && !flagIsSet(P_NO_TICK_MP)) {
			hpTickAmt = MAX(1, 4+(constitution.getCur() > 170 ? 1:0));
			if(!ill && !deathSickness) {
				if(flagIsSet(P_SITTING))
					hpTickAmt += 1;
				if(room && room->flagIsSet(R_FAST_HEAL))
					hpTickAmt += 2;
			}
		}

		hp.increase(hpTickAmt, flagIsSet(P_BLOODSAC));
		if(!ill && !deathSickness)
			hp.increase(getHpTickBonus(), flagIsSet(P_BLOODSAC));;

		lasttime[LT_TICK].ltime = t;

		lasttime[LT_TICK].interval = 45 - 5*bonus((int)constitution.getCur());

		if(getRoom()->flagIsSet(R_FAST_HEAL))
			lasttime[LT_TICK].interval /= 2;

		if(deathSickness && mrand(1,100) < 50) {
			lasttime[LT_TICK].interval += 10;
		}
	}
	// ****** End Main Tick ******

	// ****** Secondary Tick ******
	// It's time to tick and (they don't have NO_MP_TICK set OR they're a pure fighter) and they're not a lich
	if(secTick < t) {
		if(!flagIsSet(P_NO_TICK_MP) && cClass != LICH && !noTick &&
				!(cClass == FIGHTER && !cClass2 && flagIsSet(P_PTESTER))
		) {
			// Non fighters handle here
			int mpTickAmt = 0;
			if(cClass == MAGE ||
				(cClass2 == MAGE && (cClass == FIGHTER || cClass == THIEF)) )
				mpTickAmt += 2;

			mpTickAmt += MAX(1, 2+(intelligence.getCur() > 170 ? 1:0));

			if(!ill && !deathSickness) {
				if(flagIsSet(P_SITTING))
					mpTickAmt += 1;
				else if(flagIsSet(P_SLEEPING))
					mpTickAmt += 3;

				if(room && room->flagIsSet(R_FAST_HEAL))
					mpTickAmt += 2;
			}
			mp.increase(mpTickAmt);
			if(!ill && !deathSickness)
				mp.increase(getMpTickBonus());

			lasttime[LT_TICK_SECONDARY].ltime = t;

			// MP Ticking is now based on piety
			lasttime[LT_TICK_SECONDARY].interval = 45 - 5*bonus((int)piety.getCur());

			if(getRoom()->flagIsSet(R_FAST_HEAL))
				lasttime[LT_TICK_SECONDARY].interval /= 2;

			if(deathSickness && mrand(1,100) < 50)
				lasttime[LT_TICK_SECONDARY].interval += 10;

		} else if(cClass == FIGHTER && !cClass2 && flagIsSet(P_PTESTER)) {
			// Pure fighters handled here
			if(!inCombat())
				decreaseFocus();

			// Set secondary tick timer here for fighters
			lasttime[LT_TICK_SECONDARY].ltime = t;
			// More constitution keeps them focused longer
			lasttime[LT_TICK_SECONDARY].interval =  20 + 2*bonus((int)constitution.getCur());
			// Stay focus longer if in a fast heal room
			if(getRoom()->flagIsSet(R_FAST_HEAL)  && !deathSickness)
				lasttime[LT_TICK_SECONDARY].interval += 10;
			else if(deathSickness)
				lasttime[LT_TICK_SECONDARY].interval -= 10;

		}

	}
	// ****** End Secondary Tick ******

	// ****** Bad Tick ******
	// Poison and room harms and such
	if(badTick < t) {
		// Handle DoT effects
		// a hardcore death will invalidate us
		if(doDoTs() && hardcore) {
			zero(this, sizeof(this));
			return;
		}

		// a hardcore death will invalidate us
		if(doPlayerHarmRooms() && hardcore) {
			zero(this, sizeof(this));
			return;
		}

		// Set bad tick timer here
		lasttime[LT_TICK_HARMFUL].ltime = t;
		if(flagIsSet(P_OUTLAW) && room && room->flagIsSet(R_OUTLAW_SAFE)) {
			// Outlaws hiding in an outlaw safe room get hurt more often
			lasttime[LT_TICK_HARMFUL].interval = 20;
		} else {
			// If you have higher piety, you get hurt less often
			if(cClass != LICH)
				lasttime[LT_TICK_HARMFUL].interval = 45 +  5*bonus((int)piety.getCur());
			else
				lasttime[LT_TICK_HARMFUL].interval = 45 +  5*bonus((int)constitution.getCur());

			if(isEffected("vampirism") && isDay())
				lasttime[LT_TICK_HARMFUL].interval -= 15;
		}
	}
	// ****** End Bad Tick ******

	// a hardcore death will invalidate us
	if(vamp && sunlightDamage() && hardcore) {
		zero(this, sizeof(this));
		return;
	}
}

//*********************************************************************
//						getHpTickBonus
//*********************************************************************

int Player::getHpTickBonus() const {
	int bonus = 0;
	switch(cClass) {
	case FIGHTER:
		if(!cClass2 || cClass2 == THIEF)
			bonus = level/6;
		break;
	// More or less pure fighter classes - fall through cases
	case ASSASSIN:
	case BERSERKER:
	case WEREWOLF:
	case ROGUE:
	case MONK:
	case THIEF:
		bonus = level/6;
		break;
	// Now handle the hybrids
	case RANGER:
	case PALADIN:
	case DEATHKNIGHT:
	case BARD:
	case VAMPIRE:
		bonus = (level+4)/10;
		break;
	// And then the pure casters
	case MAGE:
	case CLERIC:
	case DRUID:
		bonus = 0;
		break;
	// Lich is a special case, using hp as mp...so give them a bonus here
	case LICH:
		bonus = level/6;
		break;
	default:
		bonus = 0;
		break;
	}
	return(bonus);
}

//*********************************************************************
//						getMpTickBonus
//*********************************************************************

int Player::getMpTickBonus() const {
	int bonus = 0;
	switch(cClass) {
	case FIGHTER:
		if(cClass2 && cClass2 == MAGE)
			bonus = level/6;
		break;
	// More or less pure fighter classes - fall through cases
	case ASSASSIN:
	case BERSERKER:
	case WEREWOLF:
	case ROGUE:
	case MONK:
	case THIEF:
		bonus = 0;
		break;
	// Now handle the hybrids
	case RANGER:
	case PALADIN:
	case DEATHKNIGHT:
	case BARD:
	case VAMPIRE:
		bonus = (level+4)/10;
		break;
	// And then the pure casters
	case MAGE:
	case CLERIC:
	case DRUID:
		bonus = level/6;
		break;
	// Lich is a special case, using hp as mp...so no bonus here
	case LICH:
		bonus = 0;
		break;
	default:
		bonus = 0;
		break;
	}
	return(bonus);
}

//*********************************************************************
//						doDoTs
//*********************************************************************
// return true if they died, false if they lived

bool Player::doDoTs() {
	if(isStaff())
		return(false);

	if(doPetrificationDmg())
		return(true);

	return(false);
}

//*********************************************************************
//						winterProtection
//*********************************************************************

double Object::winterProtection() const {
	double percent = 0;

	if(subType != "cloth" && subType != "leather")
		return(0);

	switch(wearflag) {
	case BODY:
		percent = 1;
		break;
	case ARMS:
	case LEGS:
	case NECK:
	case HANDS:
	case HEAD:
	case FEET:
	case FACE:
		percent = getLocationModifier() * 2;
		break;
	default:
		break;
	}

	if(subType == "cloth")
		percent /= 2;

	return(percent);
}

double Player::winterProtection() const {
	double	percent = 0;
	int		i=0;

	if(cClass == LICH || isEffected("lycantrhopy") || race == MINOTAUR)
		return(1);

	// how much protection do this person's equipment provide them?
	for(i=0; i<MAXWEAR; i++)
		if(ready[i])
			percent += ready[i]->winterProtection();

	return(MAX(0, MIN(1, percent)));
}

//*********************************************************************
//						doPlayerHarmRooms
//*********************************************************************
// return true if they died, false if they lived

bool Player::doPlayerHarmRooms() {
	DeathType dt;
	bool	prot = true;
	// gives us a range of 10-18 dmg
	int		dmg = 15 - (constitution.getCur() - 150)/50;

	BaseRoom* room = getRoom();

	// Poison rooms
	if(room->flagIsSet(R_POISONS) && !isPoisoned() && !immuneToPoison()) {
		wake("Terrible nightmares disturb your sleep!");
		printColor("^#^GThe toxic air poisoned you.\n");
		if(!isStaff()) {
			poison(0, 15, 210 + standardPoisonDuration(15, constitution.getCur()));
			poisonedBy = "noxious fumes";
		}
	}

	// Stun rooms
	if(room->flagIsSet(R_STUN)) {
		wake("You awaken suddenly!");
		print("The room starts to spin around you.\nYou feel confused.\n");
		if(!isStaff()) {
			updateAttackTimer(true, MAX(dice(2,6,0), 6));
			stun(mrand(3,9));
		}
	}

	// Mp Drain rooms
	if(room->flagIsSet(R_DRAIN_MANA) && !isStaff()) {
		wake("Terrible nightmares disturb your sleep!");
		if(cClass != LICH)
			mp.decrease(MIN(mp.getCur(),6));

		if(cClass == LICH) {
			hp.decrease(MIN(hp.getCur(), 6));
			if(hp.getCur() < 1)
				die(DRAINED);
		}
	}

	if(	room->flagIsSet(R_PLAYER_HARM) ||
		// these flags don't need PHARM set to work
		room->flagIsSet(R_DEADLY_VINES) ||
		room->flagIsSet(R_WINTER_COLD) ||
		room->flagIsSet(R_DESERT_HARM) ||
		room->flagIsSet(R_ICY_WATER)
	) {

		if( (	room->flagIsSet(R_FIRE_BONUS) ||
				(room->flagIsSet(R_DESERT_HARM) && isDay())
			)
		    && !isEffected("heat-protection") && !isEffected("alwayswarm")
		) {
			wake("You awaken suddenly");
			printColor("^rThe searing heat burns your flesh.\n");
			dt = BURNED;
			prot = false;
		} else if(	!isEffected("breathe-water") &&
					!doesntBreathe() && (
						room->flagIsSet(R_WATER_BONUS) ||(
						room->flagIsSet(R_UNDER_WATER_BONUS) ||
						(area_room && area_room->isWater() && !isEffected("fly"))
					)
		) ) {
			wake("You awaken suddenly!");
			printColor("^bWater fills your lungs.\n");
			dt = DROWNED;
			prot = false;
		} else if(room->flagIsSet(R_ICY_WATER) && cClass != LICH && !isEffected("warmth") && !isEffected("alwayscold")) {
			wake("You awaken suddenly!");
			printColor("^CThe ice cold water freezes your blood.\n");
			dt = COLDWATER;
			prot = false;
		} else if(room->flagIsSet(R_EARTH_BONUS) && !isEffected("earth-shield")) {
			wake("You awaken suddenly!");
			printColor("^YThe earth swells up around you and smothers you.\n");
			dt = SMOTHER;
			prot = false;
		} else if(room->flagIsSet(R_ELEC_BONUS) && !isEffected("static-field")) {
			wake("You awaken suddenly!");
			printColor("^WZAP! You are stuck by bolts of static electricity!\n");
			dt = LIGHTNING;
			prot = false;
		} else if(room->flagIsSet(R_AIR_BONUS) && !isEffected("fly")) {
			wake("You awaken suddenly!");
			printColor("^yTurbulent winds batter and thrash you about.\n");
			dt = WINDBATTERED;
			prot = false;
		} else if( ( room->flagIsSet(R_COLD_BONUS) ||
					(room->flagIsSet(R_DESERT_HARM) && !isDay() && cClass != LICH) ||
					(room->isWinter() && cClass != LICH)
				) &&
				!isEffected("warmth") &&
				!isEffected("alwayscold")
		) {
			prot = false;

			// winter cold is not as severe and is easier to protect against
			if(room->flagIsSet(R_WINTER_COLD)) {
				dmg -= 5;
				dmg -= (int)(dmg * winterProtection());

				if(!dmg)
					prot = true;
			}

			if(!prot) {
				wake("You awaken suddenly!");
				printColor("^bThe freezing temperature chills you to the bone.\n");
				dt = FROZE;
			}

		} else if(room->flagIsSet(R_DEADLY_VINES)) {
			if(cClass != DRUID && !isStaff() && deity != LINOTHAN && !isEffected("pass-without-trace")) {
				wake("You awaken suddenly!");
				printColor("^gLiving vines reach out and crush the air from your lungs.\n");
				dt = VINES;
				prot = false;
			} else {
				printColor("^gLiving vines crawl dangerously close to you, but ignore you.\n");
				prot = true;
			}
		}
		else if(!room->flagIsSet(R_AIR_BONUS) &&
				!room->flagIsSet(R_EARTH_BONUS) &&
				!room->flagIsSet(R_FIRE_BONUS) &&
				!room->flagIsSet(R_WATER_BONUS) &&
				!room->flagIsSet(R_POISONS) &&
				!room->flagIsSet(R_STUN) &&
				!room->flagIsSet(R_DRAIN_MANA) &&
				!room->flagIsSet(R_COLD_BONUS) &&
				!room->flagIsSet(R_ELEC_BONUS) &&
				!room->flagIsSet(R_AIR_BONUS) &&

				!room->flagIsSet(R_DEADLY_VINES) &&
				!room->flagIsSet(R_WINTER_COLD) &&
				!room->flagIsSet(R_DESERT_HARM) &&
				!room->flagIsSet(R_ICY_WATER)
		) {
			wake("You awaken suddenly!");
			printColor("^MAn invisible force saps your life force.\n");
			dt = DRAINED;
			prot = false;
		}
	}

	if(isStaff())
		prot = true;

	// Not protected
	if(!prot) {
		hp.decrease(MAX(1,dmg));
		if(hp.getCur() < 1) {
			die(dt);
			return(true);
		}
	}

	if(flagIsSet(P_OUTLAW) && room->flagIsSet(R_OUTLAW_SAFE)) {
		wake("Terrible nightmares disturb your sleep!");
		printColor("^#^rEnergy bolts blast you!\n");
		printColor("Don't stay in this room!\n");

		hp.decrease(hp.getMax()/4);
//		lasttime[LT_TICK].ltime = t;
//		lasttime[LT_TICK].interval = 20L;
		if(hp.getCur() < 1) {
			print("You are blown away by energy bolts. You died!\n");
			broadcast(getSock(), getRoom(), "%M was blown to bits by energy bolts. %s died.", this, heShe());
			die(BOLTS);
			return(true);
		}
	}

	return(false);
}

//*********************************************************************
//						getIdle
//*********************************************************************

time_t Player::getIdle() {
	return(time(0) - mySock->ltime);
}




//**********************************************************************
//						chooseItem
//**********************************************************************
// This function randomly chooses an item that the player pointed to
// by the first argument is wearing.

int Player::chooseItem() {
	char    checklist[MAXWEAR];
	int numwear=0, i;

	for(i=0; i<MAXWEAR; i++) {
		checklist[i] = 0;
		if(i==WIELD-1 || i==HELD-1)
			continue;
		if(ready[i])
			checklist[numwear++] = i+1;
	}

	if(!numwear)
		return(0);

	i = mrand(0, numwear-1);
	return(checklist[i]);
}

//**********************************************************************
//						resetObjectIds
//**********************************************************************

int doSetObjectIds(otag* op, int id) {
	while(op) {
		op->obj->setUniqueId(id++);
		id = doSetObjectIds(op->obj->first_obj, id);
		op = op->next_tag;
	}
	return(id);
}

void Player::resetObjectIds() {
	uniqueObjId = doSetObjectIds(first_obj, 0);

	for(int i=0; i<MAXWEAR; i++) {
		if(ready[i]) {
			ready[i]->setUniqueId(uniqueObjId++);
			uniqueObjId = doSetObjectIds(ready[i]->first_obj, uniqueObjId);
		}
	}
}

//**********************************************************************
//						setObjectId
//**********************************************************************

void Player::setObjectId(Object* object) {
	// don't let unique IDs get out of hand
	if(uniqueObjId > 1000000)
		resetObjectIds();
	object->setUniqueId(uniqueObjId++);
	uniqueObjId = doSetObjectIds(object->first_obj, uniqueObjId);
}