roh/conf.old/area/
roh/config/code/python/
roh/config/game/area/
roh/config/game/signs/
roh/help/dmhelp/
roh/help/help/
roh/log/
roh/log/staff/
roh/monsters/ocean/
roh/objects/misc/
roh/objects/ocean/
roh/player/
roh/rooms/area/1/
roh/rooms/misc/
roh/rooms/ocean/
roh/src-2.47e/
/*
 * creature2.cpp
 *	 Additional creature 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-2012 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 "string.h"
typedef struct {
	unsigned short	armor;
	short	thaco;
	unsigned long	experience;
	unsigned short	ndice;
	unsigned short	sdice;
	short	pdice;
} balanceStat;
balanceStat balancedStats[41]  = {
	// armor, thaco, experience, ndice, sdice, pdice
	{0,0,0,0,0,0},				// 0
	{25,20,6,1,4,0}, 			// 1
	{50,19,15,2,3,0}, 			// 2
	{75,18,35,2,3,2}, 			// 3
	{100,17,50,2,3,2}, 			// 4
	{125,16,65,2,3,4}, 			// 5
	{150,15,75,4,2,4}, 			// 6
	{175,14,100,2,4,6}, 		// 7
	{200,13,130,4,3,4}, 		// 8
	{225,12,180,5,3,3}, 		// 9
	{250,11,240,3,5,5}, 		// 10
	{275,10,325,3,6,4}, 		// 11
	{300,9,400,3,6,6}, 			// 12
	{325,8,550,4,4,8}, 			// 13
	{350,7,700,4,5,8}, 			// 14
	{390,6,850,5,5,5}, 			// 15
	{430,5,1000,4,6,8}, 		// 16
	{470,4,1150,4,6,9}, 		// 17
	{510,3,1300,5,6,8}, 		// 18
	{550,2,1450,6,5,10}, 		// 19
	{590,1,1600,4,7,10}, 		// 20
	{630,0,1800,9,5,4}, 		// 21
	{670,-2,2000,8,5,10}, 		// 22
	{710,-4,2500,10,5,6}, 		// 23
	{750,-7,3000,10,5,8}, 		// 24
	{790,-8,3500,15,4,0}, 		// 25
	{830,-8,4000,20,3,4}, 		// 26
	{870,-8,5500,13,4,12}, 		// 27
	{920,-8,7000,13,5,5}, 		// 28
	{960,-9,8500,15,4,10}, 		// 29
	{1000,-10,10000,18,3,16}, 	// 30
	{1040,-10,11625,19,3,17},	// 31
	{1080,-10,12000,20,3,17},	// 32
	{1120,-10,12375,21,3,18},	// 33
	{1160,-11,12750,22,3,18},	// 34
	{1200,-12,13125,23,3,19},	// 35
	{1240,-12,13500,24,3,19},	// 36
	{1280,-12,13875,25,3,20},	// 37
	{1320,-12,14250,26,3,20},	// 38
	{1360,-13,14625,27,3,21},	// 39
	{1400,-14,15000,28,3,23}	// 40
};

void Monster::adjust(int buffswitch) {
	int		buff=0, crthp=0;
	long	xpmod=0;

	if(buffswitch == -1)
		buff = mrand(1,3)-1;
	else
		buff = MAX(0, MIN(buffswitch, 2));


	/*			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.
	 */
	if(!flagIsSet(M_CUSTOM)) {
		if(level >= 1 && level <= MAXALVL) {
			setArmor(balancedStats[level].armor);
			weaponSkill = (level-1)*10;
			defenseSkill = (level-1)*10;
			setExperience(balancedStats[level].experience);
			damage.setNumber(balancedStats[level].ndice);
			damage.setSides(balancedStats[level].sdice);
			damage.setPlus(balancedStats[level].pdice);
		} else {
			setArmor(100);
			weaponSkill = 1;
			defenseSkill = 1;
			setExperience(6);
			damage.setNumber(1);
			damage.setSides(4);
			damage.setPlus(0);
		}


		if(!cClass)
			hp.setInitial(level * monType::getHitdice(type));
		else {
			crthp = class_stats[(int) cClass].hpstart + (level*class_stats[(int) cClass].hp);
			hp.setInitial(MAX(crthp, (level * monType::getHitdice(type))));
		}

		hp.restore();

		xpmod = experience / 10;
		xpmod *= monType::getHitdice(type) - monType::getHitdice(HUMANOID);

		if(xpmod > 0)
			addExperience(xpmod);
		else
			subExperience(xpmod*-1);
	}


	switch(buff) {
	case 1:			// Mob is weak.
		hp.setInitial( hp.getMax() - hp.getMax()/5);
		hp.restore();
		coins.set(coins[GOLD] * 4 / 5, GOLD);

		subExperience(experience / 5);
		setArmor(armor - 100);
		damage.setNumber(damage.getNumber() * 9 / 10);
		damage.setPlus(damage.getPlus() * 9 / 10);
		break;
	case 2:			// Mob is stronger
		hp.setInitial( hp.getMax() + hp.getMax()/5);
		hp.restore();
		coins.set(coins[GOLD] * 6 / 5, GOLD);

		addExperience(experience / 5);
		defenseSkill += 5;
		weaponSkill += 5;
		setArmor(armor + 100);

		damage.setNumber(damage.getNumber() * 11 / 10);
		damage.setPlus(damage.getPlus() * 11 / 10);
		break;
	default:
		break;
	}

	armor = tMAX<unsigned int>(tMIN(armor, MAX_ARMOR), 0);

	if(level >= 7)
		setFlag(M_BLOCK_EXIT);

}

// *********************************************************
// initMonster
// *********************************************************
// Parameters:	<creature> The creature being initialized
//				<Opt:loadOriginal> Don't randomize align/gold
//				<Opt:prototype> Load all objs in inventory

// Initializes last times, inventory, scrolls, etc
int Monster::initMonster(bool loadOriginal, bool prototype) {
	int n=0, alnum=0, x=0;
	long t=0;
	Object* object=0;

	t = time(0);
	// init the timers
	lasttime[LT_MON_SCAVANGE].ltime =
	lasttime[LT_MON_WANDER].ltime =
	lasttime[LT_MOB_THIEF].ltime =
	lasttime[LT_TICK].ltime = t;
	lasttime[LT_TICK_SECONDARY].ltime = t;
	lasttime[LT_TICK_HARMFUL].ltime = t;

	// Make sure armor is set properly
	if(armor < (unsigned)(balancedStats[MIN(level, MAXALVL)].armor - 150)) {
		armor = balancedStats[MIN(level, MAXALVL)].armor;
	}

	if(dexterity.getCur() < 200)
		setAttackDelay(30);
	else
		setAttackDelay(20);

	if(flagIsSet(M_FAST_TICK))
		lasttime[LT_TICK].interval = lasttime[LT_TICK_SECONDARY].interval = 15L;
	else if(flagIsSet(M_REGENERATES))
		lasttime[LT_TICK].interval = lasttime[LT_TICK_SECONDARY].interval = 15L;
	else
		lasttime[LT_TICK].interval = lasttime[LT_TICK_SECONDARY].interval = 60L - (2*bonus((int)constitution.getCur()));

	lasttime[LT_TICK_HARMFUL].interval = 30;
	// Randomize alignment and gold unless we don' want it
	if(!loadOriginal) {
		if(flagIsSet(M_ALIGNMENT_VARIES) && alignment != 0) {
			alnum = mrand(1,100);
			if(alnum == 1)
				alignment = 0;
			else if(alnum < 51)
				alignment = mrand(1,abs(alignment)) * -1;
			else
				alignment = mrand(1,abs(alignment));
		}

		if(!flagIsSet(M_NO_RANDOM_GOLD) && coins[GOLD])
			coins.set(mrand(coins[GOLD]/10, coins[GOLD]), GOLD);
	}
	// Check for loading of random scrolls
	if(checkScrollDrop()) {
		n = new_scroll(level, &object);
		if(n > 0) {
			object->value.zero();
			addObj(object);
			object->setFlag(O_JUST_LOADED);
		}
	}

	// Now load up any always drop objects (Trading perms don't drop inventory items)
	if(!flagIsSet(M_TRADES)) {
		for(x=0;x<10;x++) {
			if(!loadObject(carry[x].info, &object))
				continue;
			object->init(!prototype);
			if(	object->flagIsSet(O_ALWAYS_DROPPED) &&
				!object->getName().empty() &&
				object->getName()[0] != ' ' )
			{
				addObj(object);
				object->setFlag(O_JUST_LOADED);
			} else {
				delete object;
				continue;
			}
		}

		object = 0;

		int numDrops = mrand(1,100), whichDrop=0;

		     if(numDrops<90) 	numDrops=1;
		else if(numDrops<96)	numDrops=2;
		else					numDrops=3;

		if(prototype)
			numDrops = 10;
		for(x=0; x<numDrops; x++) {
			if(prototype)
				whichDrop=x;
			else
				whichDrop = mrand(0,9);
			if(carry[whichDrop].info.id && !flagIsSet(M_TRADES)) {
				if(!loadObject(carry[whichDrop].info, &object))
					continue;
				if(	object->getName().empty() || object->getName()[0] == ' ')
				{
					delete object;
					continue;
				}

				object->init(!prototype);

				// so we don't get more than one always drop item.
				if(object->flagIsSet(O_ALWAYS_DROPPED)) {
					delete object;
					continue;
				}

				object->value.set(mrand((object->value[GOLD]*9)/10,(object->value[GOLD]*11)/10), GOLD);

				addObj(object);
				object->setFlag(O_JUST_LOADED);
			}
		}
	}

	if(weaponSkill < (level * 3))
		weaponSkill = (level-1) * mrand(9,11);
	return(1);
}


//*********************************************************************
//						getMobNum
//*********************************************************************
// This function determines which one of the same kind of monster
// is in a room. For example, if there are three winos, it will
// return which number wino it is - 1, 2, or 3. - Tim C.

int Monster::getNumMobs() const {
	int		i=0;

	if(flagIsSet(M_DM_FOLLOW) || flagIsSet(M_WAS_PORTED))
		return(0);
	if(!this->inRoom())
		return(0);
	const BaseRoom* room = getConstRoomParent();
	if(!room)
		return(0);
	for(Monster* mons : room->monsters) {
	    if(mons->getName() == getName()) {
	        i++;
	        if(mons == this)
	            return(i);
	    }
	}
	return(0);
}

//***********************************************************************
//						getRandomMonster
//***********************************************************************

Creature *getRandomMonster(BaseRoom *inRoom) {
	Creature *foundCrt=0;
	int			count=0, roll=0, num=0;

	num = inRoom->countCrt();
	if(!num)
		return(0);

	roll = mrand(1, num);
	for(Monster* mons : inRoom->monsters) {
	    if(mons->isPet())
	        continue;
	    if(++count == roll) {
	        foundCrt = mons;
	        break;
	    }
	}

	if(foundCrt)
		return(foundCrt);
	return(0);
}

//***********************************************************************
//						getRandomPlayer
//***********************************************************************

Creature *getRandomPlayer(BaseRoom *inRoom) {
	Creature *foundPly=0;
	int			count=0, roll=0, num=0;

	num = inRoom->countVisPly();
	if(!num)
		return(0);
	roll = mrand(1, num);
	for(Player* ply : inRoom->players) {
		if(ply->flagIsSet(P_DM_INVIS)) {
			continue;
		}
		count++;
		if(count == roll) {
			foundPly = ply;
			break;
		}
	}

	if(foundPly)
		return(foundPly);
	return(0);
}


//***********************************************************************
//						validateAc
//***********************************************************************

void Monster::validateAc() {
	unsigned int ac=0;

	ac = get_perm_ac(level);
	if(armor > ac)
		armor = ac;
}

//***********************************************************************
//						doHarmfulAuras
//***********************************************************************

int Monster::doHarmfulAuras() {
	int			a=0,dmg=0,aura=0, saved=0;
	long		i=0,t=0;
	BaseRoom	*inRoom=0;
	Creature* player=0;

	if(isPet())
		return(0);
	if(hp.getCur() < hp.getMax()/10)
		return(0);
	if(flagIsSet(M_CHARMED))
		return(0);

	for(a=0;a<MAX_AURAS;a++) {
		if(flagIsSet(M_FIRE_AURA + a))
			aura++;
	}

	if(!aura)
		return(0);

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

	if(t - i < 20L) // Mob has to wait 20 seconds.
		return(0);
	else {
		lasttime[LT_M_AURA_ATTACK].ltime = t;
		lasttime[LT_M_AURA_ATTACK].interval = 20L;
	}

	inRoom = getRoomParent();
	if(!inRoom)
		return(0);

	for(a=0;a<MAX_AURAS;a++) {

		if(!flagIsSet(M_FIRE_AURA + a))
			continue;
		PlayerSet::iterator pIt = inRoom->players.begin();
		PlayerSet::iterator pEnd = inRoom->players.end();
		while(pIt != pEnd) {
			player = (*pIt++);

			if(player->isEffected("petrification") || player->isCt())
				continue;

			dmg = mrand(level/2, (level*3)/2);

			dmg = MAX(2,dmg);

			switch(a+M_FIRE_AURA) {
			case M_FIRE_AURA:
				if(player->isEffected("heat-protection") || player->isEffected("alwayswarm"))
					continue;

				saved = player->chkSave(BRE, this, 0);
				if(saved)
					dmg /=2;
				player->printColor("^R%M's firey aura singes you for %s%d^R damage!\n", this, player->customColorize("*CC:DAMAGE*").c_str(), dmg);
				break;
			case M_COLD_AURA:
				if(player->isEffected("warmth") || player->isEffected("alwayscold"))
					continue;

				saved = player->chkSave(BRE, this, 0);
				if(saved)
					dmg /=2;
				player->printColor("^C%M's freezing aura chills you for %s%d^C damage!\n", this, player->customColorize("*CC:DAMAGE*").c_str(), dmg);
				break;
			case M_NAUSEATING_AURA:
				if(player->immuneToPoison())
					continue;

				saved = player->chkSave(POI, this, 0);
				if(saved)
					dmg /=2;
				player->printColor("^g%M's foul stench chokes and nauseates you for %s%d^g damage.\n", this, player->customColorize("*CC:DAMAGE*").c_str(), dmg);
				break;
			case M_NEGATIVE_LIFE_AURA:
				if(player->isUndead() || player->isEffected("drain-shield"))
					continue;

				saved = player->chkSave(DEA, this, 0);
				if(saved)
					dmg /=2;
				player->printColor("^m%M's negative aura taps your life for %s%d^m damage.\n", this, player->customColorize("*CC:DAMAGE*").c_str(), dmg);
				break;
			case M_TURBULENT_WIND_AURA:
				if(player->isEffected("wind-protection"))
					continue;

				saved = player->chkSave(BRE, this, 0);
				if(saved)
					dmg /=2;
				player->printColor("^W%M's turbulent winds buff you about for %s%d^W damage.\n", this, player->customColorize("*CC:DAMAGE*").c_str(), dmg);
				break;
			}

			player->hp.decrease(dmg);
			if(player->checkDie(this))
				return(1);

		}// End while
	}// End for

	return(0);
}


//***********************************************************************
//						isGuardLoot
//***********************************************************************

bool isGuardLoot(BaseRoom *inRoom, Creature* player, const char *fmt) {
	for(Monster* mons : inRoom->monsters) {
	    if(mons->flagIsSet(M_GUARD_TREATURE) && !player->checkStaff(fmt, mons))
	        return(true);
	}
	return(false);
}

//***********************************************************************
//						npcPresent
//***********************************************************************

bool npcPresent(UniqueRoom *inRoom, short trade) {
	for(Monster* mons : inRoom->monsters) {
	    if(mons->getMobTrade() == trade)
	        return(true);
	}

	return(false);
}


//*********************************************************************
//						race aggro
//*********************************************************************

bool Monster::isRaceAggro(int x, bool checkInvert) const {
	x--;
	bool set = (raceAggro[x/8] & 1<<(x%8));
	if(!checkInvert)
		return(set);
	return(set ? !flagIsSet(M_RACE_AGGRO_INVERT) : flagIsSet(M_RACE_AGGRO_INVERT));
}
void Monster::setRaceAggro(int x) {
	x--;
	raceAggro[x/8] |= 1<<(x%8);
}
void Monster::clearRaceAggro(int x) {
	x--;
	raceAggro[x/8] &= ~(1<<(x%8));
}


//*********************************************************************
//						class aggro
//*********************************************************************

bool Monster::isClassAggro(int x, bool checkInvert) const {
	x--;
	bool set = (cClassAggro[x/8] & 1<<(x%8));
	if(!checkInvert)
		return(set);
	return(set ? !flagIsSet(M_CLASS_AGGRO_INVERT) : flagIsSet(M_CLASS_AGGRO_INVERT));
}
void Monster::setClassAggro(int x) {
	x--;
	cClassAggro[x/8] |= 1<<(x%8);
}
void Monster::clearClassAggro(int x) {
	x--;
	cClassAggro[x/8] &= ~(1<<(x%8));
}


//*********************************************************************
//						deity aggro
//*********************************************************************

bool Monster::isDeityAggro(int x, bool checkInvert) const {
	x--;
	bool set = (deityAggro[x/8] & 1<<(x%8));
	if(!checkInvert)
		return(set);
	return(set ? !flagIsSet(M_DEITY_AGGRO_INVERT) : flagIsSet(M_DEITY_AGGRO_INVERT));
}
void Monster::setDeityAggro(int x) {
	x--;
	deityAggro[x/8] |= 1<<(x%8);
}
void Monster::clearDeityAggro(int x) {
	x--;
	deityAggro[x/8] &= ~(1<<(x%8));
}