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/
/*
 * monsters.cpp
 *	 Monster 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 "factions.h"
#include "move.h"
#include "math.h"
#include "unique.h"
#include "magic.h"

/*
 * mTypes formatted as case for use in functions below
 *
	case HUMANOID:
	case GOBLINOID:
	case MONSTROUSHUM:
	case GIANTKIN:
	case ANIMAL:
	case DIREANIMAL:
	case INSECT:
	case INSECTOID:
	case ARACHNID:
	case REPTILE:
	case DINOSAUR:
	case AUTOMATON:
	case AVIAN:
	case FISH:
	case PLANT:
	case DEMON:
	case DEVIL:
	case DRAGON:
	case BEAST:
	case MAGICALBEAST:
	case GOLEM:
	case ETHEREAL:
	case ASTRAL:
	case GASEOUS:
	case ENERGY:
	case FAERIE:
	case DEVA:
	case ELEMENTAL:
	case PUDDING:
	case SLIME:
	case UNDEAD:
 *
 */

//*********************************************************************
//						isIntelligent
//*********************************************************************

bool monType::isIntelligent(mType type) {
	switch(type) {
	case DEMON:
	case DEVA:
	case DEVIL:
	case DRAGON:
	case FAERIE:
	case HUMANOID:
	case GIANTKIN:
	case GOBLINOID:
	case MONSTROUSHUM:
	case UNDEAD:
		return(true);

	default:
		return(false);
	}
}

//*********************************************************************
//						isUndead
//*********************************************************************

bool monType::isUndead(mType type) {
	return(type == UNDEAD);
}

//*********************************************************************
//						size
//*********************************************************************
// can we assume they are a certain size based on their type?

/*			Web Editor
 * 			 _   _  ____ _______ ______
 *			| \ | |/ __ \__   __|  ____|
 *			|  \| | |  | | | |  | |__
 *			| . ` | |  | | | |  |  __|
 *			| |\  | |__| | | |  | |____
 *			|_| \_|\____/  |_|  |______|
 *
 * 		If you change anything here, make sure the changes are reflected in the web
 * 		editor! Either edit the PHP yourself or tell Dominus to make the changes.
 */

Size monType::size(mType type) {
	switch(type) {
	case INSECT:
		return(SIZE_FINE);

	case FISH:
		return(SIZE_TINY);

	case ANIMAL:
	case FAERIE:
	case GOBLINOID:
	case REPTILE:
		return(SIZE_SMALL);

	case ARACHNID:
	case BEAST:
	case DIREANIMAL:
	case HUMANOID:
	case INSECTOID:
	case MAGICALBEAST:
	case MONSTROUSHUM:
	case UNDEAD:
		return(SIZE_MEDIUM);

	case AUTOMATON:
	case DEMON:
	case DEVA:
	case DEVIL:
	case ELEMENTAL:
	case GIANTKIN:
	case GOLEM:
		return(SIZE_LARGE);

	case DINOSAUR:
		return(SIZE_HUGE);

	case DRAGON:
		return(SIZE_GARGANTUAN);

	// we can't really say much about the rest
	default:
		return(NO_SIZE);
	}
}

//*********************************************************************
//						immuneCriticals
//*********************************************************************

bool monType::immuneCriticals(mType type) {
	switch(type) {
	case AUTOMATON:
	case GOLEM:
	case ETHEREAL:
	case GASEOUS:
	case ENERGY:
	case ELEMENTAL:
	case PUDDING:
	case SLIME:
		return(true);

	default:
		return(false);
	}
}

//*********************************************************************
//						noLivingVulnerabilities
//*********************************************************************
// immunity to: gas-breath, drowning, poison, disease

bool monType::noLivingVulnerabilities(mType type) {
	switch(type) {
	case AUTOMATON:
	case GOLEM:
	case ETHEREAL:
	case GASEOUS:
	case ENERGY:
	case ELEMENTAL:
	case PUDDING:
	case SLIME:
	case UNDEAD:
		return(true);

	default:
		return(false);
	}
}

//*********************************************************************
//						pulseTick
//*********************************************************************

bool hearMobTick(Socket* sock) {
	if(!sock->getPlayer() || !isCt(sock))
		return(false);
	return(!sock->getPlayer()->flagIsSet(P_NO_TICK_MSG));
}

void Monster::pulseTick(long t) {
	bool ill = false;
	//bool noTick = false;
	long mainTick = LT(this, LT_TICK);
	long secTick = LT(this, LT_TICK_SECONDARY);

	int hpTickAmt = 0;
	int mpTickAmt = 0;

	ill = isEffected("petrification") || (isPoisoned() && !immuneToPoison());
//	noTick = ill;

	//BaseRoom* room = getRoom();
	// ****** Main Tick ******
	if(mainTick < t && !ill) {
		double power = 0.8;
		int hpTickTime = 60 - 5*bonus(constitution.getCur());

		if(flagIsSet(M_FAST_TICK) && !nearEnemy()) {
			hpTickTime = MAX(1,(15 - (2*bonus(constitution.getCur()))));
			power = 1.0;
		} else if(flagIsSet(M_REGENERATES) && !nearEnemy()) {
			hpTickTime /= 2;
			power = 0.9;
		} else {
			power = 0.8;
		}
		hpTickAmt = static_cast<int>( pow( static_cast<double>(hp.getMax())*0.2, power ) );
		hpTickAmt = hp.increase(hpTickAmt);

		lasttime[LT_TICK].ltime = t;
		lasttime[LT_TICK].interval = hpTickTime;

	}
	// ****** End Main Tick ******

	// ****** Secondary Tick ******
	if(secTick < t && !ill) {
		double power = 0.8;
		int mpTickTime = 60 - 5*bonus(piety.getCur());

		if(flagIsSet(M_FAST_TICK) && !nearEnemy()) {
			mpTickTime = MAX(1,(5 - (2*bonus(piety.getCur()))));
			power = 1.0;
		} else if(flagIsSet(M_REGENERATES) && !nearEnemy()) {
			mpTickTime = mpTickTime * 2 / 3;
			power = 0.9;
		} else {
			mpTickTime -= 5;
			power = 0.8;
		}

		mpTickAmt = static_cast<int>( pow( static_cast<double>(hp.getMax())*0.2, power ) );

		mpTickAmt = mp.increase(mpTickAmt);
		lasttime[LT_TICK_SECONDARY].ltime = t;
		lasttime[LT_TICK_SECONDARY].interval = mpTickTime;
	}
	// ****** End Secondary Tick ******


	if(flagIsSet(M_PERMENANT_MONSTER) && (hpTickAmt || mpTickAmt)) {
		broadcast(hearMobTick, "^y*** %M(L%d,R%s) just ticked. [%d/%dH](%d) [%d/%dM](%d)",
		              this, level, room.str().c_str(),
		              MIN(hp.getCur(),hp.getMax()), hp.getMax(), hpTickAmt,
		              MIN(mp.getCur(),mp.getMax()), mp.getMax(), mpTickAmt);
	}

	// reset hide when mob ticks to full and no enemies are in the room
	if(!nearEnemy(this) && hp.getCur() >= hp.getMax()/4) {
		if(flagIsSet(M_WAS_HIDDEN))	// Reset mob-hide
		{
			broadcast(NULL, getRoom(), "%M hides in shadows.", this);
			setFlag(M_HIDDEN);
			clearFlag(M_WAS_HIDDEN);
		}
	}
	// Mobs shouldn't stay berserk after they tick full
	if(!nearEnemy(this) && hp.getCur() == hp.getMax()) {
		if(flagIsSet(M_BERSERK)) {
			strength.setCur(strength.getMax());
			broadcast(NULL, getRoom(), "^r%M's rage diminishes!", this);
			clearFlag(M_BERSERK);
			setFlag(M_WILL_BERSERK);
		}
	}
}



//*********************************************************************
//						beneficialCaster
//*********************************************************************
// The following function scans the room and casts various beneficial spells on
// any particular players which need them. It is to be used for monsters in
// clinics and such to make them more interactive. The chance to cast is random.

void Monster::beneficialCaster() {
	Player	*player=0;
	ctag	*cp=0;
	int		chance=0, heal=0;

	cp = getRoom()->first_ply;

	if(!cp || mp.getCur() < 2)
		return;

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

		if(	player->flagIsSet(P_DM_INVIS) ||
			player->flagIsSet(P_HIDDEN) ||
			player->isEffected("petrification") ||
			!canSee(player)
		)
			continue;

		// mobs wont cast on people in combat
		if(player->inCombat())
			continue;
		if(!Faction::willBeneCast(player, primeFaction))
			continue;

		if(player->flagIsSet(P_DOCTOR_KILLER)) {
			if(mrand(1,100)<=2) {
				broadcast(player->getSock(), player->getRoom(), "%M spits, \"Doctor killer!\" at %N.", this, player);
				player->print("%M spits, \"Doctor killer!\" at you.\n", this);
			}
			continue;
		}

		if(spellIsKnown(S_SLOW_POISON)) {

			chance = mrand(1,100);
			if((mp.getCur() >= 8) && (chance < 10) && !player->isEffected("slow-poison") && player->isPoisoned()) {
				if(player->immuneToPoison())
					continue;

				if(player->isEffected("slow-poison"))
					continue;


				player->print("%M casts a slow-poison spell on you.\n", this);
				broadcast(player->getSock(), player->getSock(), getRoom(),
					"%M casts a slow-poison spell on %N.", this, player);
				mp.decrease(8);

				player->addPermEffect("slow-poison");
				continue;
			}
		}

		if(spellIsKnown(S_CURE_DISEASE)) {

			chance = mrand(1,100);
			if((mp.getCur() >= 12) && (chance < 10) && (player->isDiseased())) {
				if(player->immuneToDisease())
					continue;

				player->print("%M casts a cure-disease spell on you.\n", this);

				broadcast(player->getSock(), player->getSock(), getRoom(), "%M casts a cure-disease spell on %N.",
					this, player);
				mp.decrease(12);

				player->cureDisease();
				continue;
			}
		}

		if(player->getClass() == LICH)
			continue;
		if(player->flagIsSet(P_POISONED_BY_PLAYER) && mrand(1,100) >= 3)
			continue;

		if(spellIsKnown(S_VIGOR)) {
			chance = mrand(1,100);
			if((mp.getCur() >= 2) && (chance <= 5) && (player->hp.getCur() < player->hp.getMax())) {

				player->print("%M casts a vigor spell on you.\n", this);

				broadcast(player->getSock(), player->getSock(), getRoom(), "%M casts a vigor spell on %N.",
					this, player);
				mp.decrease(2);
				heal = mrand(1,6) + bonus((int) piety.getCur());
				heal = MAX(1, heal);

				if(cClass == CLERIC && clan != 12) {
					heal *= 2;
					heal += (level / 2);
				}
				if(	(cClass == CLERIC && clan == 11) ||
					(cClass == PALADIN)
				)
					heal += mrand(1,4);
				player->hp.increase(heal);
				heal = 0;
				continue;
			}
		}

		if(spellIsKnown(S_MEND_WOUNDS)) {

			chance = mrand(1,100);
			if((mp.getCur() >= 4) && (chance <= 5) && (player->hp.getCur() < (player->hp.getMax()/2))) {

				player->print("%M casts a mend-wounds spell on you.\n", this);

				broadcast(player->getSock(), player->getSock(), getRoom(), "%M casts a mend-wounds spell on %N.",
					this, player);
				mp.decrease(4);
				heal = mrand(6,9) + bonus((int) piety.getCur());
				heal = MAX(1, heal);
				if(cClass == CLERIC && clan != 12) {
					heal *= 2;
					heal += (level / 2);
				}
				if(	(cClass == CLERIC && clan == 11) ||
					(cClass == PALADIN)
				)
					heal += mrand(1,4);
				player->hp.increase(heal);
				heal = 0;
				continue;
			}
		}

		if(spellIsKnown(S_HEAL)) {

			chance = mrand(1,100);
			if((mp.getCur() >= 25) && (chance < 3) && (cClass == CLERIC && clan != 12) &&
			        (player->hp.getCur() < (player->hp.getMax()/4)) && (player->getLevel() >= 5)) {

				player->print("%M casts a heal spell on you.\n", this);

				broadcast(player->getSock(), player->getSock(), getRoom(), "%M casts a heal spell on %N.", this, player);
				mp.decrease(25);

				player->hp.restore();
				continue;
			}
		}
	}
}


//*********************************************************************
//						mobDeathScream
//*********************************************************************

int Monster::mobDeathScream() {
	long	i=0, t=0, n=0;
	int		death=0;
	Creature* target=0;
	ctag*	cp=0;

	i = lasttime[LT_BERSERK].ltime;
	t = time(0);

	if(t - i < 30L) // Mob has to wait 30 seconds.
		return(0);


	lasttime[LT_DEATH_SCREAM].ltime = t;
	lasttime[LT_DEATH_SCREAM].interval = 30L;

	if(hp.getCur() <= (hp.getMax()/5)) {
		clearFlag(M_DEATH_SCREAM);			// Mobs will not scream at lower then 20% hp.
		return(0);
	}


	broadcast(NULL, getRoom(), "%M bellows out a deadly ear-splitting scream!!", this);

	cp = getRoom()->first_ply;
	while(cp) {
//		death=0;
		target = cp->crt;
		cp = cp->next_tag;

		n = mrand(1,15) + level; // Damage done.

		if(target->isEffected("resist-magic"))
			n /= 2;

		target->printColor("^yHarmful vibrations double you over in pain for ^Y%d^y damage!\n", n);

		if(n > (level+11))
			target->stun(mrand(4, 6));


		if(target->isPlayer() && target->flagIsSet(P_BERSERKED)) {
			n = n - (n / 5);
			n = MAX(1, n);
		}

		if(isUndead() && target->isEffected("undead-ward"))
			n /= 2;

		if(target->flagIsSet(P_DM_INVIS))
			n=0;

		if(doDamage(target, n, CHECK_DIE)) {
			death = 1;
			continue;
		}

	}

	return(!!death);
}


//*********************************************************************
//						possibleEnemy
//*********************************************************************
// This function looks for possible enemies in a room. It is specifically
// designed for fast wandering, hunting mobs....like inquisitors. -- TC

bool Monster::possibleEnemy() {
	ctag	*cp=0;
	bool	possible=0, nodetect=0;

	ASSERTLOG(this);

	if(	!flagIsSet(M_STEAL_ALWAYS) &&
		!flagIsSet(M_AGGRESSIVE) &&
		!flagIsSet(M_AGGRESSIVE_GOOD) &&
		!flagIsSet(M_AGGRESSIVE_EVIL)
	)
		return(0);

	cp = getRoom()->first_ply;
	while(cp) {
		if(flagIsSet(M_AGGRESSIVE))
			possible = true;
		if(flagIsSet(M_STEAL_ALWAYS))
			possible = true;
		if(flagIsSet(M_AGGRESSIVE_GOOD) && cp->crt->getAlignment() > 99)
			possible = true;
		if(flagIsSet(M_AGGRESSIVE_EVIL) && cp->crt->getAlignment() < -99)
			possible = true;

		if(cp->crt->flagIsSet(P_HIDDEN))
			nodetect = true;
		if(!canSee(cp->crt))
			nodetect = true;

		if(nodetect)
			possible = false;

		if(possible)
			return(true);
		cp = cp->next_tag;
	}

	return(possible);
}

//*********************************************************************
//						castSpell
//*********************************************************************
// This function allows monsters to cast spells at players.

int Monster::castSpell(Creature *target) {
	Creature *temp_ptr=0;
	cmd		cmnd;
	int		i=0, spl=0, c=0;
	int		known[20], knowctr=0;
	int		(*fn)(SpellFn);
	char	*enemy;
	int		realm=0, n=0;

	zero(known, sizeof(known));
	for(i=0; i<MAXSPELL; i++) {
		if(knowctr > 19)
			break;
		if(spellIsKnown(i))
			known[knowctr++] = i;
	}

	if(!knowctr)
		return(0);

	spl = known[mrand(0, knowctr-1)];

	// pets are smarter
	if(isPet()) {
		// Pets only cast offensive spells in combat
		if((int(*)(SpellFn, char*, osp_t*)) get_spell_function(spl) != splOffensive) {
			realm = proficiency[1];
			if(realm == CONJUREBARD || realm == CONJUREMAGE || realm == CONJUREANIM)
				realm = getRandomRealm();
			spl = ospell[(realm-1)].splno;
		}

		if(	(int(*)(SpellFn, char*, osp_t*)) get_spell_function(spl) == splOffensive &&
			get_spell_lvl(spl) < 5) {
			// They will cast higher level offensive spells if they have the mana
			for(i=1;i <knowctr; i++) {
				if(	get_spell_lvl(known[i-1]) > get_spell_lvl(spl) &&
					(int(*)(SpellFn, char*, osp_t*))get_spell_function(known[i-1]) == splOffensive
				) {
					for(c=0; ospell[c].splno != get_spell_num(known[i-1]); c++)
						if(ospell[c].splno == -1) {
							return(13);
						}
					if(mp.getCur() >= ospell[c].mp)
						spl = known[i-1];
				}
			}
		}
	}

	int splNo = get_spell_num(spl);
	if(	(int(*)(SpellFn, char*, osp_t*))get_spell_function(spl) == splOffensive ||
		splNo == S_TELEPORT ||
		splNo == S_STUN ||
		splNo == S_WORD_OF_RECALL ||
		splNo == S_DRAIN_EXP ||
		splNo == S_BLINDNESS ||
		splNo == S_SILENCE ||
		splNo == S_FEAR ||
		splNo == S_DISPEL_MAGIC ||
		splNo == S_CANCEL_MAGIC ||
		splNo == S_ANNUL_MAGIC ||
		splNo == S_SCARE ||
		splNo == S_HOLD_PERSON ||
		splNo == S_ENTANGLE ||
		splNo == S_ETHREAL_TRAVEL ||
		splNo == S_DISINTEGRATE ||
		splNo == S_MAGIC_MISSILE ||
		splNo == S_SLOW ||
		splNo == S_WEAKNESS ||
		splNo == S_DAMNATION ||
		splNo == S_FEEBLEMIND ||
		splNo == S_ENFEEBLEMENT ||
		(splNo == S_HEAL && target->getClass() == LICH) ||
		(splNo == S_DISPEL_EVIL && target->getAdjustedAlignment() < NEUTRAL) ||
		(splNo == S_DISPEL_GOOD && target->getAdjustedAlignment() > NEUTRAL) ||
		splNo == S_JUDGEMENT ||
		splNo == S_DEAFNESS ||
		splNo == S_SAP_LIFE ||
		splNo == S_LIFETAP ||
		splNo == S_LIFEDRAW ||
		splNo == S_DRAW_SPIRIT ||
		splNo == S_SIPHON_LIFE ||
		splNo == S_SPIRIT_STRIKE ||
		splNo == S_SOULSTEAL ||
		splNo == S_TOUCH_OF_KESH
	) {
		// we have the exact pointer
		enemy = target->name;
		n = 1;
		do {
			temp_ptr = getRoom()->findCreature(this, enemy, n, false);
			if(!temp_ptr) {
				// something is seriously wrong here
				return(12);
			}

			// move to the next
			n++;

			// keep going till we find the exact match
		} while( target != temp_ptr );

		// we were one too many here
		n--;

		// at this point we know which number is the exact match
		strcpy(cmnd.str[2], target->name);
		cmnd.val[2] = n;
		cmnd.num = 3;
	} else {
		// cast on self
		cmnd.num = 2;
	}
	fn = get_spell_function(spl);

	target->printColor("^r");

	SpellData data;
	data.set(CAST, get_spell_school(spl), 0, this);
	data.splno = spl;

	if((int(*)(SpellFn, char*, osp_t*))fn == splOffensive) {
		for(c=0; ospell[c].splno != get_spell_num(spl); c++)
			if(ospell[c].splno == -1)
				return(13);
		i = ((int(*)(SpellFn, const char*, osp_t*))*fn)(this, &cmnd, &data, get_spell_name(spl), &ospell[c]);
	} else {
		i = ((int(*)(SpellFn))*fn)(this, &cmnd, &data);
	}

	castDelay(PET_CAST_DELAY);
	return(i);
}

//*********************************************************************
//						petCaster
//*********************************************************************

bool Monster::petCaster() {
	Player *master = following->getPlayer();
	int		heal=0;
	long	i=0, t = time(0);

	if(!isPet() || !master || !inSameRoom(master))
		return(false);

	i = LT(this, LT_SPELL);
	if(t < i)
		return(false);

	if(mp.getCur() < 2 || mrand(1,100) > 33 || getRoom()->flagIsSet(R_NO_MAGIC))
		return(false);

	if(	spellIsKnown(S_HEAL) &&
		master->hp.getCur() <= (master->hp.getMax()/8) &&
		mp.getCur() >= 25 &&
		mrand(1,100) > 50
	) {
		mp.decrease(25);

		master->print("%M casts a heal spell on you.\n", this);
		broadcast(master->getSock(), getRoom(),
			"%M casts a heal spell on %N.", this, master);
		master->hp.restore();
		castDelay(PET_CAST_DELAY);
		return(true);
	}

	if(	spellIsKnown(S_MEND_WOUNDS) &&
		master->hp.getCur() <= (master->hp.getMax()/2) &&
		mp.getCur() >= 4 &&
		mrand(1,100) > 25
	) {
		master->print("%M casts a mend-wounds spell on you.\n", this);
		broadcast((Socket*)NULL, master->getSock(), getRoom(),
			"%M casts a mend-wounds spell on %N.", this, master);

		mp.decrease(4);

		heal = MAX(bonus((int) intelligence.getCur()), bonus((int) piety.getCur())) +
		       ((cClass == CLERIC) ?
		        level + mrand(1, 1 + level / 2) : 0) +
		       (cClass == PALADIN ?
		        level / 2 + mrand(1, 1 + level / 3) : 0) +
		       ((isEffected("vampirism") || cClass == BARD || cClass == DEATHKNIGHT) ?
		        mrand(1, 1 + level / 5) + 2 : 0) +
		       ((isEffected("lycanthropy") || cClass == MONK) ? level / 5 +
		        mrand(1, 1 + level / 7) + 2 : 0) +
		       dice(2, 7, 0);

		master->hp.increase(heal);
		castDelay(PET_CAST_DELAY);
		return(true);
	}

	if(	spellIsKnown(S_VIGOR) &&
		master->hp.getCur() < master->hp.getMax() &&
		mp.getCur() >= 2
	) {
		master->print("%M casts a vigor spell on you.\n", this);
		broadcast((Socket*)NULL, master->getSock(), getRoom(),
			"%M casts a vigor spell on %N.", this, master);
		mp.decrease(2);

		heal = MAX(bonus((int) intelligence.getCur()),bonus((int) piety.getCur())) +
		       ((cClass == CLERIC) ? level / 2 +
		        mrand(1, 1 + level / 2) : 0) +
		       (cClass == PALADIN ?
		        level / 3 + mrand(1, 1 + level / 4) : 0) +
		       ((isEffected("vampirism") || cClass == BARD || cClass == DEATHKNIGHT) ?
		        mrand(1, 1 + level / 5) : 0) +
		       ((isEffected("lycanthropy") || cClass == MONK) ? level/5 +
		        mrand(1,1+level/7) : 0)
		       + mrand(1, 8);

		master->hp.increase(heal);
		castDelay(PET_CAST_DELAY);
		return(true);
	}

	// After all possible spells have been checked on the players...check themselves
	if(	spellIsKnown(S_MEND_WOUNDS) &&
		hp.getCur() <= (hp.getMax()/2) &&
		mp.getCur() >= 4 &&
		mrand(1,100) > 25
	) {
		broadcast(NULL, getRoom(), "%M casts a mend-wounds spell on %sself.", this, himHer());
		mp.decrease(4);

		heal = MAX(bonus((int) intelligence.getCur()), bonus((int) piety.getCur())) +
		       ((cClass == CLERIC) ?
		        level + mrand(1, 1 + level / 2) : 0) +
		       (cClass == PALADIN ?
		        level / 2 + mrand(1, 1 + level / 3) : 0) +
		       ((isEffected("vampirism") || cClass == BARD || cClass == DEATHKNIGHT) ?
		        mrand(1, 1 + level / 5) + 2 : 0) +
		       ((isEffected("lycanthropy") || cClass == MONK) ? level / 5 +
		        mrand(1, 1 + level / 7) + 2 : 0) +
		       dice(2, 7, 0);

		hp.increase(heal);
		castDelay(PET_CAST_DELAY);
		return(true);
	}

	if(	spellIsKnown(S_VIGOR) &&
		hp.getCur() < hp.getMax() &&
		mp.getCur() >= 2
	) {

		broadcast(NULL, getRoom(), "%M casts a vigor spell on %sself.", this, himHer());
		mp.decrease(2);

		heal = MAX(bonus((int) intelligence.getCur()),bonus((int) piety.getCur())) +
		       ((cClass == CLERIC) ? level / 2 +
		        mrand(1, 1 + level / 2) : 0) +
		       (cClass == PALADIN ?
		        level / 3 + mrand(1, 1 + level / 4) : 0) +
		       ((isEffected("vampirism") || cClass == BARD || cClass == DEATHKNIGHT) ?
		        mrand(1, 1 + level / 5) : 0) +
		       ((isEffected("lycanthropy") || cClass == MONK) ? level/5 +
		        mrand(1,1+level/7) : 0)
		       + mrand(1, 8);

		hp.increase(heal);
		castDelay(PET_CAST_DELAY);
		return(true);
	}

	return(false);
}


//*********************************************************************
//						checkAssist
//*********************************************************************
// See if this monster will assist another monster,
// or if they will be assisted by someone else

bool hearMobAggro(Socket* sock) {
	if(!sock->getPlayer() || !isCt(sock))
		return(false);
	return(!sock->getPlayer()->flagIsSet(P_NO_AGGRO_MSG));
}

bool Monster::checkAssist() {
	int	i=0, assist=0;

	etag	*enp=0;
	Creature *crt=0;

	for(i=0; i<NUM_ASSIST_MOB; i++) {
		if(assist_mob[i].id) {
			assist = 1;
			break;
		}
	}

	if(assist) {
		Creature  *toAssist=0;
		ctag *ap = getRoom()->first_mon;
		while(ap) {
			toAssist = ap->crt;
			if(toAssist == this) {
				ap = ap->next_tag;
				continue;
			}
			if(toAssist->first_enm && willAssist(toAssist->getMonster())) {

				enp= toAssist->first_enm;
				if(enp) {
					crt = gServer->findPlayer(enp->enemy);
					if(!crt)
						crt = gServer->findPlayer(enp->owner);
				} else
					crt = 0;
				// Possibly attack someone else in the player's group
				crt = enm_in_group(crt);

				if(crt && inSameRoom(crt) && !isEnmCrt(crt->name)) {

					crt->printColor("^r%M attacks you.\n", this);
					broadcast(crt->getSock(), crt->getRoom(), "%M attacks %N.", this, crt);
					// if assisting, don't let them swing right away
					updateAttackTimer(true, DEFAULT_WEAPON_DELAY);
					addEnmCrt(crt);

					broadcast(hearMobAggro, "^y*** %s(R:%s) added %s to %s attack list (same room).",
						name, getRoom()->fullName().c_str(), crt->name, hisHer());

					setFlag(M_ALWAYS_ACTIVE);
					crt = 0;
				}
			}
			ap = ap->next_tag;
		}
	}


	if(flagIsSet(M_WILL_BE_ASSISTED) || primeFaction != "") {
		Creature* target=0;
		Monster* assister=0;
		ctag*	ast;
		if(first_enm) {
			target = findCloseEnm();
			if(target)
				target = target->getPlayer();
			if(target) {
				ast = target->getRoom()->first_mon;
				while(ast) {
					assister = ast->crt->getMonster();
					ast = ast->next_tag;

					if(!assister || assister == this)
						continue;
					if(assister->first_enm || !assister->canSee(target))
						continue;

					if(	(assister->flagIsSet(M_WILL_ASSIST) && flagIsSet(M_WILL_BE_ASSISTED)) ||
						(assister->flagIsSet(M_FACTION_ASSIST) && primeFaction == assister->getPrimeFaction()))
					{
						target->printColor("^r%M quickly assists %N!\n%M attacks you.\n", assister, this, assister);

						assister->setFlag(M_ALWAYS_ACTIVE);

						broadcast(target->getSock(), target->getRoom(), "%M attacks %N.", assister, target);
						assister->updateAttackTimer(true, DEFAULT_WEAPON_DELAY);
						assister->getMonster()->addEnmCrt(enm_in_group(target));
					}
				}
			}
		}
	}
	return(true);
}

//*********************************************************************
//						willAssist
//*********************************************************************

bool Monster::willAssist(const Monster *victim) const {

	if(!victim->info.id)
		return(false);

	for(int i=0; i<NUM_ASSIST_MOB; i++) {
		if(assist_mob[i] == victim->info)
			return(true);
	}

	return(false);
}

//*********************************************************************
//						checkScavange
//*********************************************************************

void Monster::checkScavange(long t) {
	BaseRoom* room = getRoom();
	Object* object=0;
	long i=0;

	if(room->flagIsSet(R_SHOP_STORAGE))
		return;

	if(flagIsSet(M_SCAVANGER)) {
		i = lasttime[LT_MON_SCAVANGE].ltime;
		if(	t - i > 20 &&
			mrand(1, 100) <= 15 &&
			room->first_obj &&
			canScavange(room->first_obj->obj) &&
			!(room->first_obj->obj->getType() == WEAPON && flagIsSet(M_WILL_WIELD))
		) {
			object = room->first_obj->obj;
			object->deleteFromRoom();

			setFlag(M_HAS_SCAVANGED);
			broadcast(NULL, room, "%M picked up %1P.", this, object);

			// Object is gold
			if(!object->info.id) {
				coins.add(object->value);
				delete object;
			} else
				addObj(object);
		}
		if(t - i > 20)
			lasttime[LT_MON_SCAVANGE].ltime = t;
	}

	// thief code
	if(flagIsSet(M_THIEF) || flagIsSet(M_STREET_SWEEPER)) {
		i = lasttime[LT_MOB_THIEF].ltime;
		if(t - i > 5) {
			otag	*op = room->first_obj, *onext;
			Object	*hide_obj = NULL;
			char	buf[2048], *s = buf;
			int		buflen = 0;
			int		namelen;


			lasttime[LT_MOB_THIEF].ltime = t;
			buf[0] = 0;
			while(op) {
				onext = op->next_tag;
				if((flagIsSet(M_STREET_SWEEPER) ||
					(op->obj->getType() == MONEY || op->obj->value[GOLD] >= 100)) &&
						canScavange(op->obj)
				) {
					object = op->obj;
					if(getWeight() + object->getActualWeight() > maxWeight()) {
						if(!hide_obj && !flagIsSet(M_STREET_SWEEPER)) {
							hide_obj = object;
						}
						op = onext;
						continue;
					}
					namelen = strlen(op->obj->name);
					if(buflen + namelen + 2 >= 2048)
						break;
					strcpy(s, op->obj->name);
					s += namelen;
					strcpy(s, "^x, ");
					s += 4;
					buflen += namelen + 4;
					object->deleteFromRoom();
					if(object->getType() == MONEY || !object->info.id) {
						coins.add(object->value);
						delete object;
					} else {
						addObj(object);
					}
				}
				op = onext;
			}
			if(buflen) {
				buf[buflen - 2] = 0;
				broadcast(NULL, room, "%M picked up %s.", this, buf);
			}
			if(hide_obj) {
				object = hide_obj;
				broadcast(getSock(), room, "%M attempts to hide %1P.", this, object);
				if(mrand(1, 100) <= level * 10) {
					object->setFlag(O_HIDDEN);
				}
			}
		}
	}

}

//*********************************************************************
//						canScavange
//*********************************************************************

bool Monster::canScavange(Object* object) {
    return( !object->flagIsSet(O_NO_TAKE) &&
    		!object->flagIsSet(O_SCENERY) &&
    		!object->flagIsSet(O_HIDDEN) &&
    		!object->flagIsSet(O_PERM_INV_ITEM) &&
    		!object->flagIsSet(O_PERM_ITEM) &&
    		!Unique::is(object)
    );
}

// TODO: Put this in the config/server class
static int Mobilechance = 30;

//*********************************************************************
//						checkWander
//*********************************************************************
// See if the monster will go mobile or wander away
// Return Value: 1 - Move to the next monster
//				 2 - Free this monster and start again at start of active list

int Monster::checkWander(long t) {
	int n=0, wanderchance=0;
	BaseRoom* room = getRoom();
	long i = lasttime[LT_MON_WANDER].ltime;

			// If we're a mobile monster
	if( flagIsSet(M_MOBILE_MONSTER) &&
		// if fast wander, mobile even if perm.
		(!flagIsSet(M_PERMENANT_MONSTER) || flagIsSet(M_FAST_WANDER)) &&
		// can fast-wander if enemies are not nearby
		(flagIsSet(M_FAST_WANDER) ? !nearEnemy() :
			// if not fast-wander, can mobile if no enemies
			first_enm == NULL) &&
		// can't wander if following someone
		!flagIsSet(M_DM_FOLLOW) && !following &&
		// or if they're set no wander
		!flagIsSet(M_NO_WANDER)
	) {
		// Fast wander can wander once a second
		if((flagIsSet(M_FAST_WANDER) && t - i > 1) ||
			// Non fast wander has a 20% chance after 20 seconds
			(t - i > 20 && mrand(1, 100) > 20)
		) {
			// If we're a fast wanderer, see if there's anyone in the room we
			// might want to stick around and attack. If there isn't we can
			// wander, otherwise don't move.
			if(!flagIsSet(M_FAST_WANDER) || (flagIsSet(M_FAST_WANDER) && !possibleEnemy()))
				n = mobileCrt();
			else
				n = 0;

			if(!n)
				clearFlag(M_MOBILE_MONSTER);

			if(n)
				return(1);
		}
		return(0);
	}


	// If we're chasing a shoplifter, we don't have an enemy in the room,
	// and sufficient time has passed
	if(	flagIsSet(M_ATTACKING_SHOPLIFTER) &&
		!nearEnemy() &&
		(t - i > 60 && mrand(1, 100) <= (!flagIsSet(M_PERMENANT_MONSTER) ? 40:30))
	) {
		// If we have a mobile chance or we're a fast wanderer
		if((mrand(1, 100) < Mobilechance || flagIsSet(M_FAST_WANDER))) {
			// We can go looking for the guy
			setFlag(M_MOBILE_MONSTER);
		} else if(!flagIsSet(M_CHASING_SOMEONE)) {
			// If we're nto chasing someone
			// Then we might start chasing them or let our guard down

			if(!flagIsSet(M_PERMENANT_MONSTER)) {
				broadcast(NULL, room, "%1M mutters obscenities under %s breath.", this, hisHer());
				// If we're not a perm, become a fast wander and go looking for the guy
				setFlag(M_FAST_WANDER);
				// Clear any aggressive flags so we don't go hit someone who
				// doesn't deserve it!  Chase the shoplifter down
				clearFlag(M_AGGRESSIVE_GOOD);
				clearFlag(M_AGGRESSIVE_EVIL);
				clearFlag(M_WILL_BE_AGGRESSIVE);
				clearFlag(M_WILL_ASSIST);
				setFlag(M_CHASING_SOMEONE);
				setFlag(M_OUTLAW_AGGRO);
			} else if(flagIsSet(M_ATTACKING_SHOPLIFTER)) {
				broadcast(NULL, room, "%1M lets down %s guard.", this, hisHer());
				clearFlag(M_ATTACKING_SHOPLIFTER);
				clearEnemyList();
			}
		}

		// If we're chasing someone we have a chance to wander away
		if(flagIsSet(M_CHASING_SOMEONE) && mrand(1,100) < 10) {
			broadcast(NULL, room, "%1M gives up %s search for %s and wanders away.", this, hisHer(), first_enm->enemy);
			return(2);
		}
	}

	// Special case for aggressive monsters
	// If we're an aggressive non perm and haven't had any combat in 20 minutes
	if(	flagIsSet(M_AGGRESSIVE) &&
		t - lasttime[LT_AGGRO_ACTION].ltime > 1200 &&
		!flagIsSet(M_PERMENANT_MONSTER)
	) {
		// Then we've got a chance to wander away
		if(mrand(1, 100) < 5) {
			if(race || monType::isIntelligent(type))
				broadcast(NULL, room, "%1M %s away in search of someone to bully.", this, Move::getString(this).c_str());
			else
				broadcast(NULL, room, "%1M just %s away.", this, Move::getString(this).c_str());
			return(2);
		}
	}


	// Now see if we'll either wander away or if we can become a wanderer
		  // No wandering away if we have scavanged
	if(	(!flagIsSet(M_HAS_SCAVANGED) &&
		// if fast wander, wander even if perm.
		(!flagIsSet(M_PERMENANT_MONSTER) || flagIsSet(M_FAST_WANDER)) &&
		// pets and other followers don't wander
		!isPet() && !flagIsSet(M_DM_FOLLOW))
	) {
		if(	// If it's time to wander, and we have no enemey
			(t - i > 60 && (parent_rom && mrand(1, 100) <= parent_rom->wander.getTraffic()) && !first_enm) ||
			// or we fast-wander if enemies are not nearby
			(flagIsSet(M_FAST_WANDER) && !nearEnemy())
		) {
			lasttime[LT_MON_WANDER].ltime = t;

			// Special rules if we fast wander
			if(flagIsSet(M_FAST_WANDER)) {
				// We always get the mobile creature flag set
				setFlag(M_MOBILE_MONSTER);

				// Now we've got a small chance here to wander away
				if(first_enm)
					wanderchance = 1;
				else
					wanderchance = 5;
					// If we're not a permenant monster
				if(	!flagIsSet(M_PERMENANT_MONSTER) &&
				    // and there is no dminvis person in the room
				    !room->dmInRoom() &&
				    // and we've got a wander chance
				    (mrand(1,100) <= wanderchance)
				) {
					broadcast(NULL, room, "%1M just %s away.", this, Move::getString(this).c_str());
					return(2);
				}
			}

			// If we're not a fast wanderer
			if(!flagIsSet(M_FAST_WANDER)) {
				// And we've got a mobile chance
				if(mrand(1, 100) < Mobilechance) {
					// Then we can wander next round
					setFlag(M_MOBILE_MONSTER);
				}
				// Otherwise if we have no enemy and we're not a perm, and there's no
				// dm invis person in the room, we can wander away
				else if(!room->dmInRoom() && !first_enm && !flagIsSet(M_PERMENANT_MONSTER)) {
					// Time to wander away
					broadcast(NULL, room, "%1M just %s away.", this, Move::getString(this).c_str());
					return(2);
				}
			}
		} else if(t - i > 60)
			lasttime[LT_MON_WANDER].ltime = t;
	}
	return(0);
}

//*********************************************************************
//						checkEnemyMobs
//*********************************************************************
// If we're not currently fighthing someone, see if we hate any of the
// monsters in the room and attack them!

bool Monster::checkEnemyMobs() {
	int i=0, aggroSize=0;
	BaseRoom* room = getRoom();

	for(i = 0 ; i<NUM_ENEMY_MOB ;i++) {
		if(enemy_mob[i].id != 0) {
			aggroSize++;
		}
	}
	// Nothing to aggro, null room, or we have a nearby enemy
	if(aggroSize == 0 || !room || nearEnemy())
		return(false);

	// 25% chance each update cycle to aggro an enemy monster
	if(mrand(1,100) > 25)
		return(false);

	// We know we have some enemies, now lets see if we have any
	// enemies in the room with us to attack!

	ctag* cp = room->first_mon;
	Monster* target = 0;
	while(cp) {
		target = cp->crt->getMonster();
		cp = cp->next_tag;
		if(target != this && isEnemyMob(target)) {
			broadcast(NULL, room, "%M yells, \"Die!\"\n%M attacks %N!", this, this, target);
			addEnmCrt(target);
			updateAttackTimer(true, DEFAULT_WEAPON_DELAY);
			return(true);
		}
	}

	return(false);
}

//*********************************************************************
//						isEnemyMob
//*********************************************************************

bool Monster::isEnemyMob(const Monster* target) const {
	if(target->info.id == 0)
		return(false);
	for(int i=0 ; i<NUM_ENEMY_MOB ; i++) {
		if(enemy_mob[i] == target->info)
			return(true);
	}
	return(false);
}


//*********************************************************************
//						toJail
//*********************************************************************
// Sends a guy off to jail

int Monster::toJail(Player* player) {
	Room* room=0;
	CatRef jailroom;
	jailroom.id = 2650;
	long jailtime=0;

	if(player->isStaff())
		return(0);

	if(jail.id)
		jailroom = jail;

	if(!loadRoom(jailroom, &room)) {
		player->print("%M gets very confused.\n", this);
		return(-1);
	}

	logn("log.jail", "%s was hauled off to jail (%s) by %s.\n", player->name, jailroom.str().c_str(), name);
	player->deleteFromRoom();
	player->addToRoom(room);
	player->doPetFollow();

	if(room->flagIsSet(R_MOB_JAIL)) {
		jailtime = (8 + 2*player->getLevel())*60;

		if(player->isCt())
			jailtime = 15L;

		player->lasttime[LT_MOB_JAILED].ltime = time(0);
		player->lasttime[LT_MOB_JAILED].interval = jailtime;

		player->print("You are sentenced to around %ld day%s hard labor!\n",
		      (((jailtime / 60)/24 > 1) ? ((jailtime / 60)/24): 1),
		      (((jailtime / 60)/24 > 1) ? "s":""));
	}

	return(1);
}

//*********************************************************************
//						grabCoins
//*********************************************************************
// steals someone's gold

int Monster::grabCoins(Player* player) {
	if(!player->coins[GOLD])
		return(0);

	unsigned long grab = mrand(1, player->coins[GOLD]), num = player->coins[GOLD];
	// never steal all gold if they have more than 100k
	grab = MIN(grab, 100000);

	coins.add(grab, GOLD);
	player->coins.sub(grab, GOLD);

	logn("log.mug", "%s was mugged by %s for %d gold.\n", player->name, name, grab);

	if(grab == num) {
		player->print("%M grabs all your coins!\n", this);
		return(2);
	} else {
		player->print("%M takes %d gold coins from you!\n", this, grab);
		return(1);
	}
}