Go Back   EQEmulator Home > EQEmulator Forums > Development > Development::Server Code Submissions

Reply
 
Thread Tools Display Modes
  #1  
Old 09-22-2011, 08:48 PM
Estalia
Fire Beetle
 
Join Date: Jun 2005
Posts: 7
Default Permarooted mobs

Ok, permarooted mobs act wrong on eqtitan and after looking at the code, i am pretty sure they do on all servers. The solution to making the work like they do on live (have real aggrotables not just hit the nearest pc) is to change line 776 of MobAI.cpp to if (IsRooted() && !permarooted()).

code:
Code:
/*  EQEMu:  Everquest Server Emulator
Copyright (C) 2001-2004  EQEMu Development Team (http://eqemu.org)

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; version 2 of the License.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY except by those people which sell it, which
	are required to give you total support for your newly bought product;
	without even the implied warranty of MERCHANTABILITY or FITNESS FOR
	A PARTICULAR PURPOSE.  See the GNU General Public License for more details.

	  You should have received a copy of the GNU General Public License
	  along with this program; if not, write to the Free Software
	  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/
#include "../common/debug.h"
#include <iostream>
using namespace std;
#include <iomanip>
using namespace std;
#include <stdlib.h>
#include <math.h>
#include <algorithm>
#include "npc.h"
#include "masterentity.h"
#include "NpcAI.h"
#include "map.h"
#include "../common/moremath.h"
#include "parser.h"
#include "StringIDs.h"
#include "../common/MiscFunctions.h"
#include "../common/rulesys.h"
#include "features.h"
#include "QuestParserCollection.h"
#include "watermap.h"

#if !defined(NEW_LoadSPDat) && !defined(DB_LoadSPDat)
	extern SPDat_Spell_Struct spells[SPDAT_RECORDS];
#endif

extern EntityList entity_list;

extern Zone *zone;

#ifdef _EQDEBUG
	#define MobAI_DEBUG_Spells	-1
#else
	#define MobAI_DEBUG_Spells	-1
#endif
#define ABS(x) ((x)<0?-(x):(x))

//NOTE: do NOT pass in beneficial and detrimental spell types into the same call here!
bool NPC::AICastSpell(Mob* tar, int8 iChance, int16 iSpellTypes) {
	_ZP(Mob_AICastSpell);
// Faction isnt checked here, it's assumed you wouldnt pass a spell type you wouldnt want casted on the mob
	if (!tar)
		return false;

	if(AI_HasSpells() == false)
		return false;

	if (iChance < 100) {
		if (MakeRandomInt(0, 100) >= iChance)
			return false;
	}
		
	float dist2;

	if (iSpellTypes & SpellType_Escape) {
	    dist2 = 0; //DistNoRoot(*this);	//WTF was up with this...
    } else 
	    dist2 = DistNoRoot(*tar);

	bool checked_los = false;	//we do not check LOS until we are absolutely sure we need to, and we only do it once.
	
	float manaR = GetManaRatio();
	for (int i=AIspells.size()-1; i >= 0; i--) {
		if (AIspells[i].spellid <= 0 || AIspells[i].spellid >= SPDAT_RECORDS) {
			// this is both to quit early to save cpu and to avoid casting bad spells
			// Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here
			//return false;
			continue;
		}
		if (iSpellTypes & AIspells[i].type) {
			// manacost has special values, -1 is no mana cost, -2 is instant cast (no mana)
			sint32 mana_cost = AIspells[i].manacost;
			if (mana_cost == -1)
				mana_cost = spells[AIspells[i].spellid].mana;
			else if (mana_cost == -2)
				mana_cost = 0;
			if (
				((
					(spells[AIspells[i].spellid].targettype==ST_AECaster || spells[AIspells[i].spellid].targettype==ST_AEBard)
					&& dist2 <= spells[AIspells[i].spellid].aoerange*spells[AIspells[i].spellid].aoerange
				 ) ||
				 dist2 <= spells[AIspells[i].spellid].range*spells[AIspells[i].spellid].range
				)
				&& (mana_cost <= GetMana() || GetMana() == GetMaxMana())
				&& (AIspells[i].time_cancast+(MakeRandomInt(0, 4))) <= Timer::GetCurrentTime() //break up the spelling casting over a period of time.
				) {

#if MobAI_DEBUG_Spells >= 21
				cout << "Mob::AICastSpell: Casting: spellid=" << AIspells[i].spellid
                    << ", tar=" << tar->GetName() 
                    << ", dist2[" << dist2 << "]<=" << spells[AIspells[i].spellid].range *spells[AIspells[i].spellid].range 
                    << ", mana_cost[" << mana_cost << "]<=" << GetMana() 
                    << ", cancast[" << AIspells[i].time_cancast << "]<=" << Timer::GetCurrentTime()
                    << ", type=" << AIspells[i].type << endl;
#endif

				switch (AIspells[i].type) {
					case SpellType_Heal: {
						if (
							(spells[AIspells[i].spellid].targettype == ST_Target || tar == this)
							&& tar->DontHealMeBefore() < Timer::GetCurrentTime()
							&& !(tar->IsPet() && tar->GetOwner()->IsClient())	//no buffing PC's pets
							) {
							int8 hpr = (int8)tar->GetHPRatio();

							if(hpr <= 35 || (!IsEngaged() && hpr <= 50) || (tar->IsClient() && hpr <= 99)) {
								int32 tempTime = 0;
								AIDoSpellCast(i, tar, mana_cost, &tempTime);
								tar->SetDontHealMeBefore(tempTime);
								return true;
							}
						}
						break;
					}
					case SpellType_Root: {
						if (
							!tar->IsRooted() 
							&& dist2 >= 900 
							&& MakeRandomInt(0, 99) < 50
							&& tar->DontRootMeBefore() < Timer::GetCurrentTime()
							&& tar->CanBuffStack(AIspells[i].spellid, GetLevel(), true) >= 0
							) {
							if(!checked_los) {
								if(!CheckLosFN(tar))
									return(false);	//cannot see target... we assume that no spell is going to work since we will only be casting detrimental spells in this call
								checked_los = true;
							}
							int32 tempTime = 0;
							AIDoSpellCast(i, tar, mana_cost, &tempTime);
							tar->SetDontRootMeBefore(tempTime);
							return true;
						}
						break;
					}
					case SpellType_Buff: {
						if (
							(spells[AIspells[i].spellid].targettype == ST_Target || tar == this)
							&& tar->DontBuffMeBefore() < Timer::GetCurrentTime()
							&& !tar->IsImmuneToSpell(AIspells[i].spellid, this)
							&& tar->CanBuffStack(AIspells[i].spellid, GetLevel(), true) >= 0
							&& !(tar->IsPet() && tar->GetOwner()->IsClient() && this != tar)	//no buffing PC's pets, but they can buff themself
							) 
                        {
                            if(!checked_los) {
                                if(!CheckLosFN(tar)) 
                                    return(false); 
                                checked_los = true; 
                            } 
							uint32 tempTime = 0;
							AIDoSpellCast(i, tar, mana_cost, &tempTime);
							tar->SetDontBuffMeBefore(tempTime);
							return true;
						}
						break;
					}

					case SpellType_InCombatBuff: {
						if(MakeRandomInt(0,100) < 50)
						{
                            AIDoSpellCast(i, tar, mana_cost);
							return true;
						}
						break;
					}

					case SpellType_Escape: {
                        if (GetHPRatio() <= 5 )	
                    	{
                            AIDoSpellCast(i, tar, mana_cost);
							return true;
						}
						break;
					}
					case SpellType_Nuke: {
						if (
							manaR >= 10 && MakeRandomInt(0, 99) < 70
							&& tar->CanBuffStack(AIspells[i].spellid, GetLevel(), true) >= 0
							) {
							if(!checked_los) {
								if(!CheckLosFN(tar))
									return(false);	//cannot see target... we assume that no spell is going to work since we will only be casting detrimental spells in this call
								checked_los = true;
							}
							AIDoSpellCast(i, tar, mana_cost);
							return true;
						}
						break;
					}
					case SpellType_Dispel: {
						if(MakeRandomInt(0, 100) < 15)
						{
							if(!checked_los) {
								if(!CheckLosFN(tar))
									return(false);	//cannot see target... we assume that no spell is going to work since we will only be casting detrimental spells in this call
								checked_los = true;
							}
							if(tar->CountDispellableBuffs() > 0)
							{
								AIDoSpellCast(i, tar, mana_cost);
								return true;
							}
						}
						break;
					}
					case SpellType_Mez: {
						if(MakeRandomInt(0, 99) < 20)
						{
							Mob * mezTar = NULL;
							mezTar = entity_list.GetTargetForMez(this);

							if(mezTar && mezTar->CanBuffStack(AIspells[i].spellid, GetLevel(), true) >= 0)
							{
								AIDoSpellCast(i, mezTar, mana_cost);
								return true;
							}
						}
						break;
					}
					
					case SpellType_Charm: 
					{
						if(MakeRandomInt(0, 99) < 20)
						{
							Mob * chrmTar = GetHateRandom();
							if(chrmTar && chrmTar->CanBuffStack(AIspells[i].spellid, GetLevel(), true) >= 0)
							{
								AIDoSpellCast(i, chrmTar, mana_cost);
								return true;
							}
							break;
						}
					}

					case SpellType_Pet: {
						 //keep mobs from recasting pets when they have them.
						if (!IsPet() && !GetPetID() && MakeRandomInt(0, 99) < 25) {
							AIDoSpellCast(i, tar, mana_cost);
							return true;
						}
						break;
					}
					case SpellType_Lifetap: {
						if (   GetHPRatio() <= 95
							&& MakeRandomInt(0, 99) < 50
							&& tar->CanBuffStack(AIspells[i].spellid, GetLevel(), true) >= 0
							) {
							if(!checked_los) {
								if(!CheckLosFN(tar))
									return(false);	//cannot see target... we assume that no spell is going to work since we will only be casting detrimental spells in this call
								checked_los = true;
							}
							AIDoSpellCast(i, tar, mana_cost);
							return true;
						}
						break;
					}
					case SpellType_Snare: {
						if (
							   !tar->IsRooted()
							&& MakeRandomInt(0, 99) < 50
							&& tar->DontSnareMeBefore() < Timer::GetCurrentTime()
							&& tar->CanBuffStack(AIspells[i].spellid, GetLevel(), true) >= 0
							) {
							if(!checked_los) {
								if(!CheckLosFN(tar))
									return(false);	//cannot see target... we assume that no spell is going to work since we will only be casting detrimental spells in this call
								checked_los = true;
							}
							int32 tempTime = 0;
							AIDoSpellCast(i, tar, mana_cost, &tempTime);
							tar->SetDontSnareMeBefore(tempTime);
							return true;
						}
						break;
					}
					case SpellType_DOT: {
						if (
							MakeRandomInt(0, 99) < 60
							&& tar->DontDotMeBefore() < Timer::GetCurrentTime()
							&& tar->CanBuffStack(AIspells[i].spellid, GetLevel(), true) >= 0
							) {
							if(!checked_los) {
								if(!CheckLosFN(tar))
									return(false);	//cannot see target... we assume that no spell is going to work since we will only be casting detrimental spells in this call
								checked_los = true;
							}
							int32 tempTime = 0;
							AIDoSpellCast(i, tar, mana_cost, &tempTime);
							tar->SetDontDotMeBefore(tempTime);
							return true;
						}
						break;
					}
					default: {
						cout<<"Error: Unknown spell type in AICastSpell. caster:"<<this->GetName()<<" type:"<<AIspells[i].type<<" slot:"<<i<<endl;
						break;
					}
				}
			}
#if MobAI_DEBUG_Spells >= 21
			else {
				cout << "Mob::AICastSpell: NotCasting: spellid=" << AIspells[i].spellid << ", tar=" << tar->GetName() << ", dist2[" << dist2 << "]<=" << spells[AIspells[i].spellid].range*spells[AIspells[i].spellid].range << ", mana_cost[" << mana_cost << "]<=" << GetMana() << ", cancast[" << AIspells[i].time_cancast << "]<=" << Timer::GetCurrentTime() << endl;
			}
#endif
		}
	}
	return false;
}

bool NPC::AIDoSpellCast(int8 i, Mob* tar, sint32 mana_cost, int32* oDontDoAgainBefore) {
#if MobAI_DEBUG_Spells >= 1
	cout << "Mob::AIDoSpellCast: spellid=" << AIspells[i].spellid << ", tar=" << tar->GetName() << ", mana=" << mana_cost << ", Name: " << spells[AIspells[i].spellid].name << endl;
#endif
	casting_spell_AIindex = i;
	
	//stop moving if were casting a spell and were not a bard...
	if(!IsBardSong(AIspells[i].spellid)) {
		SetRunAnimSpeed(0);
		SendPosition();
		SetMoving(false);
	}
	
	return CastSpell(AIspells[i].spellid, tar->GetID(), 1, AIspells[i].manacost == -2 ? 0 : -1, mana_cost, oDontDoAgainBefore, -1, -1, 0, 0, &(AIspells[i].resist_adjust));
}

bool EntityList::AICheckCloseBeneficialSpells(NPC* caster, int8 iChance, float iRange, int16 iSpellTypes) {
	_ZP(EntityList_AICheckCloseBeneficialSpells);

	if((iSpellTypes&SpellTypes_Detrimental) != 0) {
		//according to live, you can buff and heal through walls...
		//now with PCs, this only applies if you can TARGET the target, but
		// according to Rogean, Live NPCs will just cast through walls/floors, no problem..
		//
		// This check was put in to address an idle-mob CPU issue
		_log(AI__ERROR, "Error: detrimental spells requested from AICheckCloseBeneficialSpells!!");
		return(false);
	}
	
	if(!caster)
		return false;

	if(caster->AI_HasSpells() == false)
		return false;

    if(caster->SpecAttacks[NPC_NO_BUFFHEAL_FRIENDS])
        return false;

	if (iChance < 100) {
		int8 tmp = MakeRandomInt(0, 99);
		if (tmp >= iChance)
			return false;
	}
	if (caster->GetPrimaryFaction() == 0 )
		return(false); // well, if we dont have a faction set, we're gonna be indiff to everybody

	float iRange2 = iRange*iRange;
	
	float t1, t2, t3;
	

	//Only iterate through NPCs
    LinkedListIterator<NPC*> iterator(npc_list);
    for(iterator.Reset(); iterator.MoreElements(); iterator.Advance()) {
		NPC* mob = iterator.GetData();
		
		//Since >90% of mobs will always be out of range, try to
		//catch them with simple bounding box checks first. These
		//checks are about 6X faster than DistNoRoot on my athlon 1Ghz
		t1 = mob->GetX() - caster->GetX();
		t2 = mob->GetY() - caster->GetY();
		t3 = mob->GetZ() - caster->GetZ();
		//cheap ABS()
		if(t1 < 0)
			t1 = 0 - t1;
		if(t2 < 0)
			t2 = 0 - t2;
		if(t3 < 0)
			t3 = 0 - t3;
		if (   t1 > iRange
			|| t2 > iRange
			|| t3 > iRange
			|| mob->DistNoRoot(*caster) > iRange2
				//this call should seem backwards:
			|| mob->GetReverseFactionCon(caster) >= FACTION_KINDLY
		) {
			continue;
		}

		//since we assume these are beneficial spells, which do not
		//require LOS, we just go for it.
		// we have a winner!
		if((iSpellTypes & SpellType_Buff) && !RuleB(NPC, BuffFriends)){
			if (mob != caster)
				iSpellTypes = SpellType_Heal;
		}

		if (caster->AICastSpell(mob, 100, iSpellTypes))
			return true;
	}
	return false;
}

void Mob::AI_Init() {
	pAIControlled = false;
	AIthink_timer = 0;
	AIwalking_timer = 0;
	AImovement_timer = 0;
	AItarget_check_timer = 0;
	AIfeignremember_timer = NULL;
	AIscanarea_timer = 0;
	minLastFightingDelayMoving = RuleI(NPC, LastFightingDelayMovingMin);
	maxLastFightingDelayMoving = RuleI(NPC, LastFightingDelayMovingMax);

	pDontHealMeBefore = 0;
	pDontBuffMeBefore = 0;
	pDontDotMeBefore = 0;
	pDontRootMeBefore = 0;
	pDontSnareMeBefore = 0;
}

void NPC::AI_Init() {
	Mob::AI_Init();
	
	AIautocastspell_timer = 0;
	casting_spell_AIindex = AIspells.size();

	roambox_max_x = 0;
	roambox_max_y = 0;
	roambox_min_x = 0;
	roambox_min_y = 0;
	roambox_distance = 0;
	roambox_movingto_x = 0;
	roambox_movingto_y = 0;
	roambox_delay = 2500;
}

void Client::AI_Init() {
	Mob::AI_Init();
	minLastFightingDelayMoving = CLIENT_LD_TIMEOUT;
	maxLastFightingDelayMoving = CLIENT_LD_TIMEOUT;
}

void Mob::AI_Start(int32 iMoveDelay) {
	if (pAIControlled)
		return;

	if (iMoveDelay)
		pLastFightingDelayMoving = Timer::GetCurrentTime() + iMoveDelay;
	else
		pLastFightingDelayMoving = 0;

	pAIControlled = true;
	AIthink_timer = new Timer(AIthink_duration);
	AIthink_timer->Trigger();
	AIwalking_timer = new Timer(0);
	AImovement_timer = new Timer(AImovement_duration);
	AItarget_check_timer = new Timer(AItarget_check_duration);
	AIfeignremember_timer = new Timer(AIfeignremember_delay);
	AIscanarea_timer = new Timer(AIscanarea_delay);
#ifdef REVERSE_AGGRO
	if(IsNPC() && !CastToNPC()->WillAggroNPCs())
		AIscanarea_timer->Disable();
#endif

	if (GetAggroRange() == 0)
		pAggroRange = 70;
	if (GetAssistRange() == 0)
		pAssistRange = 70;
	hate_list.Wipe();

	delta_heading = 0;
	delta_x = 0;
	delta_y = 0;
	delta_z = 0;
	pRunAnimSpeed = 0;
	pLastChange = Timer::GetCurrentTime();
}

void Client::AI_Start(int32 iMoveDelay) {
	Mob::AI_Start(iMoveDelay);

	if (!pAIControlled)
		return;
	
	pClientSideTarget = GetTarget() ? GetTarget()->GetID() : 0;
	SendAppearancePacket(AT_Anim, ANIM_FREEZE);	// this freezes the client
	SendAppearancePacket(AT_Linkdead, 1); // Sending LD packet so *LD* appears by the player name when charmed/feared -Kasai
	SetAttackTimer();
	SetFeigned(false);
}

void NPC::AI_Start(int32 iMoveDelay) {
	Mob::AI_Start(iMoveDelay);
	if (!pAIControlled)
		return;
	
	if (AIspells.size() == 0) {
		AIautocastspell_timer = new Timer(1000);
		AIautocastspell_timer->Disable();
	} else {
		AIautocastspell_timer = new Timer(750);
		AIautocastspell_timer->Start(RandomTimer(0, 15000), false);
	}
	
	if (NPCTypedata) {
		AI_AddNPCSpells(NPCTypedata->npc_spells_id);
		NPCSpecialAttacks(NPCTypedata->npc_attacks,0);
	}
	
	SendTo(GetX(), GetY(), GetZ());
	SetChanged();
	SaveGuardSpot();
}

void Mob::AI_Stop() {
	if (!IsAIControlled())
		return;
	pAIControlled = false;
	safe_delete(AIthink_timer);
	safe_delete(AIwalking_timer);
	safe_delete(AImovement_timer);
	safe_delete(AItarget_check_timer)
	safe_delete(AIscanarea_timer);
	safe_delete(AIfeignremember_timer);
	hate_list.Wipe();
}

void NPC::AI_Stop() {
	Waypoints.clear();
	safe_delete(AIautocastspell_timer);
}

void Client::AI_Stop() {
	Mob::AI_Stop();
	this->Message_StringID(13,PLAYER_REGAIN);

	EQApplicationPacket *app = new EQApplicationPacket(OP_Charm, sizeof(Charm_Struct));
	Charm_Struct *ps = (Charm_Struct*)app->pBuffer;
	ps->owner_id = 0;
	ps->pet_id = this->GetID();
	ps->command = 0;
	entity_list.QueueClients(this, app);
	safe_delete(app);

	SetTarget(entity_list.GetMob(pClientSideTarget));
	SendAppearancePacket(AT_Anim, GetAppearanceValue(GetAppearance()));
	SendAppearancePacket(AT_Linkdead, 0); // Removing LD packet so *LD* no longer appears by the player name when charmed/feared -Kasai
	if (!auto_attack) {
		attack_timer.Disable();
		attack_dw_timer.Disable();
	}
	if (IsLD())
	{
		Save();
		Disconnect();
	}
}

//todo: expand the logic here to cover:
//redundant debuffs
//buffing owner
//certain types of det spells that need special behavior.
void Client::AI_SpellCast()
{
	if(!charm_cast_timer.Check())
		return;

	Mob *targ = GetTarget();
	if(!targ)
		return;

	float dist = DistNoRootNoZ(*targ);

	std::vector<int32> valid_spells;
	std::vector<int32> slots;

	for(uint32 x = 0; x < 9; ++x)
	{
		int32 current_spell = m_pp.mem_spells[x];
		if(!IsValidSpell(current_spell))
			continue;

		if(IsBeneficialSpell(current_spell))
		{
			continue;
		}

		if(dist > spells[current_spell].range*spells[current_spell].range)
		{
			continue;
		}

		if(GetMana() < spells[current_spell].mana)
		{
			continue;
		}

		if(IsEffectInSpell(current_spell, SE_Charm))
		{
			continue;
		}

		if(!GetPTimers().Expired(&database, pTimerSpellStart + current_spell, false)) 
		{
			continue;
		}

		if(targ->CanBuffStack(current_spell, GetLevel(), true) < 0)
		{
			continue;
		}

		//bard songs cause trouble atm
		if(IsBardSong(current_spell))
			continue;

		valid_spells.push_back(current_spell);
		slots.push_back(x);
	}

	int32 spell_to_cast = 0xFFFFFFFF;
	int32 slot_to_use = 10;
	if(valid_spells.size() == 1)
	{
		spell_to_cast = valid_spells[0];
		slot_to_use = slots[0];
	}
	else if(valid_spells.size() == 0)
	{
		return;
	}
	else
	{
		int32 idx = MakeRandomInt(0, (valid_spells.size()-1));
		spell_to_cast = valid_spells[idx];
		slot_to_use = slots[idx];
	}

	if(IsMezSpell(spell_to_cast) || IsFearSpell(spell_to_cast))
	{
		Mob *tar = entity_list.GetTargetForMez(this);
		if(!tar)
		{
			tar = GetTarget();
			if(tar && IsFearSpell(spell_to_cast))
			{
				if(!IsBardSong(spell_to_cast)) 
				{
					SetRunAnimSpeed(0);
					SendPosition();
					SetMoving(false);
				}
				CastSpell(spell_to_cast, tar->GetID(), slot_to_use);
				return;
			}
		}
	}
	else
	{
		Mob *tar = GetTarget();
		if(tar)
		{
			if(!IsBardSong(spell_to_cast)) 
			{
				SetRunAnimSpeed(0);
				SendPosition();
				SetMoving(false);
			}
			CastSpell(spell_to_cast, tar->GetID(), slot_to_use);
			return;
		}
	}


}

void Client::AI_Process()
{
	if (!IsAIControlled())
		return;

	if (!(AIthink_timer->Check() || attack_timer.Check(false)))
		return;

	if (IsCasting())
		return;
	
	bool engaged = IsEngaged();

	Mob *ow = GetOwner();
	if(!engaged)
	{
		if(ow)
		{
			if(ow->IsEngaged())
			{
				Mob *tar = ow->GetTarget();
				if(tar)
				{
					AddToHateList(tar, 1, 0, false);
				}
			}
		}
	}

	if(!ow)
	{
		if(!IsFeared() && !IsLD())
		{
			BuffFadeByEffect(SE_Charm);
			return;
		}
	}

	if(RuleB(Combat, EnableFearPathing)){
		if(curfp) {
			if(IsRooted()) {
				//make sure everybody knows were not moving, for appearance sake
				if(IsMoving())
				{
					if(GetTarget())
						SetHeading(CalculateHeadingToTarget(GetTarget()->GetX(), GetTarget()->GetY()));
					SetRunAnimSpeed(0);
					SendPosition();
					SetMoving(false);
					moved=false;
				}
				//continue on to attack code, ensuring that we execute the engaged code
				engaged = true;
			} else {
				if(AImovement_timer->Check()) {
					animation = GetRunspeed() * 21;
					// Check if we have reached the last fear point
					if((ABS(GetX()-fear_walkto_x) < 0.1) && (ABS(GetY()-fear_walkto_y) <0.1)) {
						// Calculate a new point to run to
						CalculateNewFearpoint();
					}
					if(!RuleB(Pathing, Fear) || !zone->pathing)
						CalculateNewPosition2(fear_walkto_x, fear_walkto_y, fear_walkto_z, GetFearSpeed(), true);
					else
					{
						bool WaypointChanged, NodeReached;

						VERTEX Goal = UpdatePath(fear_walkto_x, fear_walkto_y, fear_walkto_z,
									 GetFearSpeed(), WaypointChanged, NodeReached);

						if(WaypointChanged)
							tar_ndx = 20;

						CalculateNewPosition2(Goal.x, Goal.y, Goal.z, GetFearSpeed());
					}
				}
				return;
			}
		}
	}

	if (engaged) 
	{
		if (IsRooted() && !permarooted())
			SetTarget(hate_list.GetClosest(this));
		else
		{
			if(AItarget_check_timer->Check())
			{
				SetTarget(hate_list.GetTop(this));
			}
		}

		if (!GetTarget())
			return;

		if (GetTarget()->IsCorpse()) {
			RemoveFromHateList(this);
			return;
		}
      
		if(DivineAura())
			return;
			
		bool is_combat_range = CombatRange(GetTarget());
		
		if(is_combat_range) {
			if(charm_class_attacks_timer.Check()) {
				DoClassAttacks(GetTarget());
			}

			if (AImovement_timer->Check()) {
				SetRunAnimSpeed(0);
			}
			if(IsMoving()) {
				SetMoving(false);
				moved=false;
				SetHeading(CalculateHeadingToTarget(GetTarget()->GetX(), GetTarget()->GetY()));
				SendPosition();
				tar_ndx =0;
			}

			if(GetTarget() && !IsStunned() && !IsMezzed() && !GetFeigned()) {
				if(attack_timer.Check()) {
					Attack(GetTarget(), 13);
					if(GetTarget()) {
						if(CheckDoubleAttack()) {
							Attack(GetTarget(), 13);
							if(GetTarget()) {
								bool triple_attack_success = false;
								if((((GetClass() == MONK || GetClass() == WARRIOR || GetClass() == RANGER || GetClass() == BERSERKER)
									&& GetLevel() >= 60) || SpecAttacks[SPECATK_TRIPLE])
									&& CheckDoubleAttack(true))
								{
									Attack(GetTarget(), 13, true);
									triple_attack_success = true;
								}

								if(GetTarget())
								{
									int32 flurry_chance = 0;
									switch (GetAA(aaFlurry)) 
									{
									case 1:
										flurry_chance += 10;
										break;
									case 2:
										flurry_chance += 25;
										break;
									case 3:
										flurry_chance += 50;
										break;
									}

									if(triple_attack_success) 
									{
										triple_attack_success = false;
										switch (GetAA(aaRagingFlurry)) 
										{
										case 1:
											flurry_chance += 10;
											break;
										case 2:
											flurry_chance += 25;
											break;
										case 3:
											flurry_chance += 50;
											break;
										}
									}

									if(MakeRandomInt(0, 999) < flurry_chance) 
									{
										Message_StringID(MT_NPCFlurry, 128);
										Attack(GetTarget(), 13, true);
										Attack(GetTarget(), 13, true);
									}

									if (GetTarget() && GetAA(aaRapidStrikes))
									{
										int32 chance_xhit1 = 0;
										int32 chance_xhit2 = 0;
										switch (GetAA(aaRapidStrikes))
										{
										case 1:
											chance_xhit1 = 10;
											chance_xhit2 = 2;
											break;
										case 2:
											chance_xhit1 = 12;
											chance_xhit2 = 4;
											break;
										case 3:
											chance_xhit1 = 14;
											chance_xhit2 = 6;
											break;
										case 4:
											chance_xhit1 = 16;
											chance_xhit2 = 8;
											break;
										case 5:
											chance_xhit1 = 20;
											chance_xhit2 = 10;
											break;
										}
										if (MakeRandomInt(1,100) < chance_xhit1)
											Attack(GetTarget(), 13, true);
										if (MakeRandomInt(1,100) < chance_xhit2)
											Attack(GetTarget(), 13, true);
									}

									if(GetTarget() && (GetAA(aaPunishingBlade) > 0 || GetAA(aaSpeedoftheKnight) > 0)) 
									{
										ItemInst *wpn = GetInv().GetItem(SLOT_PRIMARY);
										if(wpn)
										{
											if(wpn->GetItem()->ItemType == ItemType2HS || 
												wpn->GetItem()->ItemType == ItemType2HB ||
												wpn->GetItem()->ItemType == ItemType2HPierce )
											{
												int32 extatk = GetAA(aaPunishingBlade)*5;
												extatk += GetAA(aaSpeedoftheKnight)*5;
												if(MakeRandomInt(0, 100) < extatk)
												{
													Attack(GetTarget(), 13, true);
												}
											}
										}
									}

									if (GetClass() == WARRIOR || GetClass() == BERSERKER) 
									{
										if(!dead && !berserk && this->GetHPRatio() < 30) 
										{
											entity_list.MessageClose_StringID(this, false, 200, 0, BERSERK_START, GetName());
											berserk = true;
										}
										else if (berserk && this->GetHPRatio() > 30) 
										{
											entity_list.MessageClose_StringID(this, false, 200, 0, BERSERK_END, GetName());
											berserk = false;
										}
									}
								}
							}
						}
					}
				}
			}

			if(CanThisClassDualWield() && attack_dw_timer.Check())
			{
				if(GetTarget())
				{
					float DualWieldProbability = (GetSkill(DUAL_WIELD) + GetLevel()) / 400.0f; // 78.0 max
					DualWieldProbability += (0.1f * GetAA(aaAmbidexterity));
					DualWieldProbability += ((spellbonuses.DualWieldChance + itembonuses.DualWieldChance) / 100.0f);			

					if(MakeRandomFloat(0.0, 1.0) < DualWieldProbability)
					{
						Attack(GetTarget(), 14);
						if(CheckDoubleAttack())
						{
							Attack(GetTarget(), 14);
						}

					}
				}
			}
		}
		else
		{
			if(!IsRooted())
			{
				animation = 21 * GetRunspeed();
				if(!RuleB(Pathing, Aggro) || !zone->pathing)
					CalculateNewPosition2(GetTarget()->GetX(), GetTarget()->GetY(), GetTarget()->GetZ(), GetRunspeed());
				else
				{
					bool WaypointChanged, NodeReached;
					VERTEX Goal = UpdatePath(GetTarget()->GetX(), GetTarget()->GetY(), GetTarget()->GetZ(),
						GetRunspeed(), WaypointChanged, NodeReached);

					if(WaypointChanged)
						tar_ndx = 20;

					CalculateNewPosition2(Goal.x, Goal.y, Goal.z, GetRunspeed());
				}				
			}
			else if(IsMoving())
			{
				SetHeading(CalculateHeadingToTarget(GetTarget()->GetX(), GetTarget()->GetY()));
				SetRunAnimSpeed(0);
				SendPosition();
				SetMoving(false);
				moved=false;				
			}
		}
		AI_SpellCast();
	}
	else
	{
		if(AIfeignremember_timer->Check()) {
			std::set<int32>::iterator RememberedCharID, tmp;
			RememberedCharID=feign_memory_list.begin();
			bool got_one = false;
			while(RememberedCharID != feign_memory_list.end()) {
				Client* remember_client = entity_list.GetClientByCharID(*RememberedCharID);
				if(remember_client == NULL) {
					//they are gone now...
					tmp = RememberedCharID;
					RememberedCharID++;
					feign_memory_list.erase(tmp);
				} else if (!remember_client->GetFeigned()) {
					AddToHateList(remember_client->CastToMob(),1);
					tmp = RememberedCharID;
					RememberedCharID++;
					feign_memory_list.erase(tmp);
					got_one = true;
					break;
				} else {
					//they are still feigned, carry on...
					RememberedCharID++;
				}
			}
		}

		if(IsPet()) 
		{			
			Mob* owner = GetOwner();
			if(owner == NULL)
				return;

			float dist = DistNoRoot(*owner);
			if (dist >= 100) 
			{
				float speed = dist >= 225 ? GetRunspeed() : GetWalkspeed();
				animation = 21 * speed;
				CalculateNewPosition2(owner->GetX(), owner->GetY(), owner->GetZ(), speed);
			}
			else
			{
				SetHeading(owner->GetHeading());
				if(moved)
				{
					moved=false;
					SetMoving(false);
					SendPosition();
					SetRunAnimSpeed(0);
				}
			}
		}
	}
}

void Mob::AI_Process() {
	_ZP(Mob_AI_Process);

	if (!IsAIControlled())
		return;

	if (!(AIthink_timer->Check() || attack_timer.Check(false)))
		return;

	if (IsCasting())
		return;
	
	bool engaged = IsEngaged();

	// Begin: Additions for Wiz Fear Code
	//
	if(RuleB(Combat, EnableFearPathing)){
		if(curfp) {
			if(IsRooted()) {
				//make sure everybody knows were not moving, for appearance sake
				if(IsMoving())
				{
					if(target)
						SetHeading(CalculateHeadingToTarget(target->GetX(), target->GetY()));
					SetRunAnimSpeed(0);
					SendPosition();
					SetMoving(false);
					moved=false;
				}
				//continue on to attack code, ensuring that we execute the engaged code
				engaged = true;
			} else {
				if(AImovement_timer->Check()) {
					// Check if we have reached the last fear point
					if((ABS(GetX()-fear_walkto_x) < 0.1) && (ABS(GetY()-fear_walkto_y) <0.1)) {
						// Calculate a new point to run to
						CalculateNewFearpoint();
					}
					if(!RuleB(Pathing, Fear) || !zone->pathing)
						CalculateNewPosition2(fear_walkto_x, fear_walkto_y, fear_walkto_z, GetFearSpeed(), true);
					else
					{
						bool WaypointChanged, NodeReached;

						VERTEX Goal = UpdatePath(fear_walkto_x, fear_walkto_y, fear_walkto_z,
									 GetFearSpeed(), WaypointChanged, NodeReached);

						if(WaypointChanged)
							tar_ndx = 20;

						CalculateNewPosition2(Goal.x, Goal.y, Goal.z, GetFearSpeed());
					}
				}
				return;
			}
		}
	}
	
	// trigger EVENT_SIGNAL if required
	if(IsNPC()) {
		CastToNPC()->CheckSignal();
	}
	
	if (engaged) 
	{
		_ZP(Mob_AI_Process_engaged);
		if (IsRooted())
			SetTarget(hate_list.GetClosest(this));
		else
		{
			if(AItarget_check_timer->Check())
			{
				SetTarget(hate_list.GetTop(this));
			}
		}

		if (!target)
			return;

		if (target->IsCorpse())
		{
			RemoveFromHateList(this);
			return;
		}
      
		if(DivineAura())
			return;

		if(SpecAttacks[TETHER] || SpecAttacks[LEASH]) {
			if(DistNoRootNoZ(CastToNPC()->GetSpawnPointX(), CastToNPC()->GetSpawnPointY()) > pAggroRange*pAggroRange) {
				GMMove(CastToNPC()->GetSpawnPointX(), CastToNPC()->GetSpawnPointY(), CastToNPC()->GetSpawnPointZ(), CastToNPC()->GetSpawnPointH());
				if(SpecAttacks[LEASH]) {
					SetHP(GetMaxHP());
					BuffFadeAll();
					WipeHateList();
					return;
				}
			}
		}
			
		if (GetHPRatio() < (RuleI(NPC, StartEnrageValue)+1) && 
			(!RuleB(NPC, LiveLikeEnrage) || 
			(RuleB(NPC, LiveLikeEnrage) && 
			((IsPet() && !IsCharmed() && GetOwner() && GetOwner()->IsClient()) ||  
			 (CastToNPC()->GetSwarmOwner() && entity_list.GetMob(CastToNPC()->GetSwarmOwner())->IsClient())))))
		{
			StartEnrage();
		}
		
		bool is_combat_range = CombatRange(target);
		
        if (is_combat_range) 
        {
			if (AImovement_timer->Check()) 
			{
				SetRunAnimSpeed(0);
			}
			if(IsMoving())
			{
				SetMoving(false);
				moved=false;
				SetHeading(CalculateHeadingToTarget(target->GetX(), target->GetY()));
				SendPosition();
				tar_ndx =0;
			}
			
			//casting checked above...
			if(target && !IsStunned() && !IsMezzed() && GetAppearance() != eaDead) {
				
				//we should check to see if they die mid-attacks, previous
				//crap of checking target for null was not gunna cut it
				
				//try main hand first
				if(attack_timer.Check()) {
                    if(IsNPC()) {
                        sint16 n_atk = CastToNPC()->GetNumberOfAttacks();
                        if(n_atk <= 1) {
                            Attack(target, 13);
                        } else {
                            for(int i = 0; i < n_atk; ++i) {
                                Attack(target, 13);
                            }
                        }
                    } else {
                        Attack(target, 13);
                    }
					
					if (target) 
					{
						//we use this random value in three comparisons with different
						//thresholds, and if its truely random, then this should work
						//out reasonably and will save us compute resources.
						sint32 RandRoll = MakeRandomInt(0, 99);
						if (CanThisClassDoubleAttack()
							//check double attack, this is NOT the same rules that clients use...
							&& RandRoll < (GetLevel() + NPCDualAttackModifier))  
						{
							if (Attack(target, 13)) 
							{
								// lets see if we can do a triple attack with the main hand
								//pets are excluded from triple and quads...
								if (SpecAttacks[SPECATK_TRIPLE]
									&& !IsPet() && RandRoll < (GetLevel()+NPCTripleAttackModifier))
								{
									if (Attack(target, 13)) 
									{	// now lets check the quad attack
										if (SpecAttacks[SPECATK_QUAD]
											&& RandRoll < (GetLevel() + NPCQuadAttackModifier))  
										{
											Attack(target, 13);
										}
									}
								}
							}
						}
					}
	
					if (SpecAttacks[SPECATK_FLURRY]) {
					    // perhaps get the values from the db?
					    if (MakeRandomInt(0, 99) < 20)
							Flurry();
					}

					if (IsPet() && GetOwner()->IsClient()) {
						int aa_chance = 0;
						// Magician AA
						int aa_skill = GetOwner()->CastToClient()->GetAA(aaElementalAlacrity);
						// Necromancer AA
						if (aa_skill < 1) {
							aa_skill = GetOwner()->CastToClient()->GetAA(aaQuickeningofDeath);
						}
						// Beastlord AA
						if (aa_skill < 1) {
							aa_skill = GetOwner()->CastToClient()->GetAA(aaWardersAlacrity);
						}
						switch (aa_skill)
						{
						case 1:
							aa_chance = 2;
							break;
						case 2:
							aa_chance = 4;
							break;
						case 3:
							aa_chance = 6;
							break;
						case 4:
							aa_chance = 8;
							break;
						case 5:
							aa_chance = 10;
							break;
						}
						if (MakeRandomInt(1, 100) < aa_chance)
							Flurry();
					}
	
					if (SpecAttacks[SPECATK_RAMPAGE]) 
					{
					    //simply based off dex for now, probably a better calc
					    if(MakeRandomInt(0, 100) < ((int)(GetDEX() / ((GetLevel() * 0.760) + 10.0)) + 5))
							Rampage();
					}

					if (SpecAttacks[SPECATK_AREA_RAMPAGE]) 
					{

						//simply based off dex for now, probably a better calc
					    if(MakeRandomInt(0, 100) < ((int)(GetDEX() / ((GetLevel() * 0.760) + 10.0)) + 5))
							AreaRampage();
					}
				}
				
				//now off hand
				if (attack_dw_timer.Check() && CanThisClassDualWield()) 
				{
					int myclass = GetClass();
					//can only dual wield without a weapon if your a monk
					if(SpecAttacks[SPECATK_INNATE_DW] || (GetEquipment(MATERIAL_SECONDARY) != 0 && GetLevel() > 29) || myclass == MONK || myclass == MONKGM) {
						float DualWieldProbability = (GetSkill(DUAL_WIELD) + GetLevel()) / 400.0f;
						if(MakeRandomFloat(0.0, 1.0) < DualWieldProbability)
						{
							Attack(target, 14);
							if (CanThisClassDoubleAttack()) 
							{
								sint32 RandRoll = MakeRandomInt(0, 99);
								if (RandRoll < (GetLevel() + 20))  
								{
									if (Attack(target, 14));
								}
							}
						}
					}
				}
				
				//now special attacks (kick, etc)
				if(IsNPC())
					CastToNPC()->DoClassAttacks(target);
			}
			AI_EngagedCastCheck();
		}	//end is within combat range
		else {
			//we cannot reach our target...
            //underwater stuff only works with water maps in the zone!
            if(IsNPC() && CastToNPC()->IsUnderwaterOnly() && zone->HasWaterMap()) {
                if(!zone->watermap->InLiquid(target->GetX(), target->GetY(), target->GetZ())) {
                    Mob *tar = hate_list.GetTop(this);
                    if(tar == target) {
                        WipeHateList();
                        Heal();
                        BuffFadeAll();
                        AIwalking_timer->Start(100);
	                    pLastFightingDelayMoving = Timer::GetCurrentTime();
                        return;
                    } else if(tar != NULL) {
                        SetTarget(tar);
                        return;
                    }
                }
            }

			// See if we can summon the mob to us
			if (!HateSummon()) 
			{
				//could not summon them, check ranged...
				if(SpecAttacks[SPECATK_RANGED_ATK])
					RangedAttack(target);

				// Now pursue
				// TODO: Check here for another person on hate list with close hate value
				if(AI_PursueCastCheck()){
					//we did something, so do not process movement.
				}
				else if (AImovement_timer->Check()) 
				{
					if(!IsRooted()) {
						mlog(AI__WAYPOINTS, "Pursuing %s while engaged.", target->GetName());
						if(!RuleB(Pathing, Aggro) || !zone->pathing)
							CalculateNewPosition2(target->GetX(), target->GetY(), target->GetZ(), GetRunspeed());
						else
						{
							bool WaypointChanged, NodeReached;

							VERTEX Goal = UpdatePath(target->GetX(), target->GetY(), target->GetZ(),
											GetRunspeed(), WaypointChanged, NodeReached);

							if(WaypointChanged)
								tar_ndx = 20;

							CalculateNewPosition2(Goal.x, Goal.y, Goal.z, GetRunspeed());
						}

					} 
					else if(IsMoving()) {
						SetHeading(CalculateHeadingToTarget(target->GetX(), target->GetY()));
						SetRunAnimSpeed(0);
						SendPosition();
						SetMoving(false);
						moved=false;
					
					}
				}
			}
		}
	}
	else 
	{
		if(AIfeignremember_timer->Check()) {
			// EverHood - 6/14/06
			// Improved Feign Death Memory
			// check to see if any of our previous feigned targets have gotten up.
			std::set<int32>::iterator RememberedCharID, tmp;
			RememberedCharID=feign_memory_list.begin();
			bool got_one = false;
			while(RememberedCharID != feign_memory_list.end()) {
				Client* remember_client = entity_list.GetClientByCharID(*RememberedCharID);
				if(remember_client == NULL) {
					//they are gone now...
					tmp = RememberedCharID;
					RememberedCharID++;
					feign_memory_list.erase(tmp);
				} else if (!remember_client->GetFeigned()) {
					AddToHateList(remember_client->CastToMob(),1);
					tmp = RememberedCharID;
					RememberedCharID++;
					feign_memory_list.erase(tmp);
					got_one = true;
					break;
				} else {
					//they are still feigned, carry on...
					RememberedCharID++;
				}
			}
		}
		if (AI_IdleCastCheck()) 
		{
			//we processed a spell action, so do nothing else.
		}
		else if (AIscanarea_timer->Check()) 
		{
			/*                                                                              
            * This is where NPCs look around to see if they want to attack anybody.
            *
            * if REVERSE_AGGRO is enabled, then this timer is disabled unless they
            * have the npc_aggro flag on them, and aggro against clients is checked
            * by the clients.
            *
            */
			_ZP(Mob_AI_Process_scanarea);
			
			Mob* tmptar = entity_list.AICheckCloseAggro(this, GetAggroRange(), GetAssistRange());
			if (tmptar) 
				AddToHateList(tmptar);
		}
		else if (AImovement_timer->Check() && !IsRooted()) 
		{
			_ZP(Mob_AI_Process_move);
			SetRunAnimSpeed(0);
			if (IsPet()) 
			{
				_ZP(Mob_AI_Process_pet);
				// we're a pet, do as we're told
				switch (pStandingPetOrder) 
				{
					case SPO_Follow: 
					{
						
						Mob* owner = GetOwner();
						if(owner == NULL)
							break;
						
						//if(owner->IsClient())
						//	printf("Pet start pos: (%f, %f, %f)\n", GetX(), GetY(), GetZ());
						
						float dist = DistNoRoot(*owner);
						if (dist >= 400) 
						{
							float speed = GetWalkspeed();
							if (dist >= 5625)
								speed = GetRunspeed();
							CalculateNewPosition2(owner->GetX(), owner->GetY(), owner->GetZ(), speed);
						}
						else
						{
							if(moved)
							{
								moved=false;
								SetMoving(false);
								SendPosition();
							}
						}
					
						/*
						//fix up Z
						float zdiff = GetZ() - owner->GetZ();
						if(zdiff < 0)
							zdiff = 0 - zdiff;
						if(zdiff > 2.0f) {
							SendTo(GetX(), GetY(), owner->GetZ());
							SendPosition();
						}
						
						if(owner->IsClient())
							printf("Pet pos: (%f, %f, %f)\n", GetX(), GetY(), GetZ());
						*/
						
						break;
					}
					case SPO_Sit: 
					{
						SetAppearance(eaSitting, false);
						break;
					}
					case SPO_Guard: 
					{
						//only NPCs can guard stuff. (forced by where the guard movement code is in the AI)
						if(IsNPC()) {
							CastToNPC()->NextGuardPosition();
						}
						break;
					}
				}
			}
			else if (GetFollowID()) 
			{
				Mob* follow = entity_list.GetMob(GetFollowID());
				if (!follow) SetFollowID(0);
				else 
				{
					float dist2 = DistNoRoot(*follow);
					int followdist = GetFollowDistance();

					if (dist2 >= followdist)	// Default follow distance is 100
					{
						float speed = GetWalkspeed();
						if (dist2 >= followdist + 150)
							speed = GetRunspeed();
						CalculateNewPosition2(follow->GetX(), follow->GetY(), follow->GetZ(), speed);
					}
					else
					{
						if(moved)
						{
							SendPosition();
							moved=false;
							SetMoving(false);
						}
					}					
				}
			}
			else //not a pet, and not following somebody...
			{
				// dont move till a bit after you last fought
				if (pLastFightingDelayMoving < Timer::GetCurrentTime()) 
				{
					if (this->IsClient()) 
					{
						// LD timer expired, drop out of world
						if (this->CastToClient()->IsLD())
							this->CastToClient()->Disconnect();
						return;
					}
					
					if(IsNPC())
					{
						if(RuleB(NPC, SmartLastFightingDelayMoving) && !feign_memory_list.empty())
						{
							minLastFightingDelayMoving = 0;
							maxLastFightingDelayMoving = 0;
						}
						CastToNPC()->AI_DoMovement();
					}
				}
				
         } 
      } // else if (AImovement_timer->Check()) 
   }
}

void NPC::AI_DoMovement() {
	float walksp = GetMovespeed();
	if(walksp <= 0.0f)
		return;	//this is idle movement at walk speed, and we are unable to walk right now.
	
	if (roambox_distance > 0) {
		_ZP(Mob_AI_Process_roambox);
		if (
			roambox_movingto_x > roambox_max_x
			|| roambox_movingto_x < roambox_min_x
			|| roambox_movingto_y > roambox_max_y
			|| roambox_movingto_y < roambox_min_y
			) 
		{
			float movedist = roambox_distance*roambox_distance;
			float movex = MakeRandomFloat(0, movedist);
			float movey = movedist - movex;
			movex = sqrtf(movex);
			movey = sqrtf(movey);
			movex *= MakeRandomInt(0, 1) ? 1 : -1;
			movey *= MakeRandomInt(0, 1) ? 1 : -1;
			roambox_movingto_x = GetX() + movex;
			roambox_movingto_y = GetY() + movey;
			if (roambox_movingto_x > roambox_max_x || roambox_movingto_x < roambox_min_x)
				roambox_movingto_x -= movex * 2;
			if (roambox_movingto_y > roambox_max_y || roambox_movingto_y < roambox_min_y)
				roambox_movingto_y -= movey * 2;
			if (roambox_movingto_x > roambox_max_x || roambox_movingto_x < roambox_min_x)
				roambox_movingto_x = roambox_max_x;
			if (roambox_movingto_y > roambox_max_y || roambox_movingto_y < roambox_min_y)
				roambox_movingto_y = roambox_max_y;
		}
		
		mlog(AI__WAYPOINTS, "Roam Box: d=%.3f (%.3f->%.3f,%.3f->%.3f): Go To (%.3f,%.3f)", 
			roambox_distance, roambox_min_x, roambox_max_x, roambox_min_y, roambox_max_y, roambox_movingto_x, roambox_movingto_y);
		if (!CalculateNewPosition2(roambox_movingto_x, roambox_movingto_y, GetZ(), walksp, true)) 
		{
			roambox_movingto_x = roambox_max_x + 1; // force update
			pLastFightingDelayMoving = Timer::GetCurrentTime() + RandomTimer(roambox_delay, roambox_delay + 5000);
			SetMoving(false);
			SendPosition();	// makes mobs stop clientside
		}
	}
	else if (roamer) 
	{
		_ZP(Mob_AI_Process_roamer);
		if (AIwalking_timer->Check())
		{
			movetimercompleted=true;
			AIwalking_timer->Disable();
		}

		
		sint16 gridno = CastToNPC()->GetGrid(); 

		if (gridno > 0 || cur_wp==-2)  {
			if (movetimercompleted==true) {  // time to pause at wp is over
				if (wandertype == 4 && cur_wp == CastToNPC()->GetMaxWp()) {
		           CastToNPC()->Depop(true); //depop and resart spawn timer
				}
				else if (wandertype == 6 && cur_wp == CastToNPC()->GetMaxWp()) {
		           CastToNPC()->Depop(false);//depop without spawn timer
				}
				else {
					movetimercompleted=false; 
					
					mlog(QUESTS__PATHING, "We are departing waypoint %d.", cur_wp);

					//if we were under quest control (with no grid), we are done now..
					if(cur_wp == -2) {
						mlog(QUESTS__PATHING, "Non-grid quest mob has reached its quest ordered waypoint. Leaving pathing mode.");
						roamer = false;
						cur_wp = 0;
					}
					
					if(GetAppearance() != eaStanding)
						SetAppearance(eaStanding, false);
					
					entity_list.OpenDoorsNear(CastToNPC());

					if(!DistractedFromGrid) {
						//kick off event_waypoint depart
						char temp[16]; 
						sprintf(temp, "%d", cur_wp);
                        parse->EventNPC(EVENT_WAYPOINT_DEPART, CastToNPC(), NULL, temp, 0);
					
						//setup our next waypoint, if we are still on our normal grid
						//remember that the quest event above could have done anything it wanted with our grid
						if(gridno > 0) {
							CastToNPC()->CalculateNewWaypoint();
						}
					}
					else {
						DistractedFromGrid = false;
					}
                } 
            }	// endif (movetimercompleted==true)     
			else if (!(AIwalking_timer->Enabled()))
			{	// currently moving
				if (cur_wp_x == GetX() && cur_wp_y == GetY()) 
				{	// are we there yet? then stop
					mlog(AI__WAYPOINTS, "We have reached waypoint %d (%.3f,%.3f,%.3f) on grid %d", cur_wp, GetX(), GetY(), GetZ(), GetGrid());
					SetWaypointPause();
					if(GetAppearance() != eaStanding)
						SetAppearance(eaStanding, false);
					SetMoving(false);
					if (cur_wp_heading >= 0.0) {
						SetHeading(cur_wp_heading);
					}
					SendPosition();
					
					//kick off event_waypoint arrive
					char temp[16]; 
					sprintf(temp, "%d", cur_wp);
					parse->EventNPC(EVENT_WAYPOINT_ARRIVE, CastToNPC(), NULL, temp, 0);
					
					// EverHood - wipe feign memory since we reached our first waypoint
					if(cur_wp == 1)
						ClearFeignMemory();
				} 
				else
				{	// not at waypoint yet, so keep moving
					if(!RuleB(Pathing, AggroReturnToGrid) || !zone->pathing || (DistractedFromGrid == 0))
						CalculateNewPosition2(cur_wp_x, cur_wp_y, cur_wp_z, walksp, true); 
					else
					{
						bool WaypointChanged;
						bool NodeReached;
						VERTEX Goal = UpdatePath(cur_wp_x, cur_wp_y, cur_wp_z, walksp, WaypointChanged, NodeReached);
						if(WaypointChanged)
							tar_ndx = 20;

						if(NodeReached)
							entity_list.OpenDoorsNear(CastToNPC()); 

						CalculateNewPosition2(Goal.x, Goal.y, Goal.z, walksp, true);
					}

				}
			} 
		}		// endif (gridno > 0) 
// handle new quest grid command processing
		else if (gridno < 0) 
		{	// this mob is under quest control
			if (movetimercompleted==true)    
			{ // time to pause has ended
				SetGrid( 0 - GetGrid()); // revert to AI control
				mlog(QUESTS__PATHING, "Quest pathing is finished. Resuming on grid %d", GetGrid());

				if(GetAppearance() != eaStanding)
					SetAppearance(eaStanding, false);

				CalculateNewWaypoint();
			}
		}

  } 
  else if (IsGuarding()) 
  {
	_ZP(Mob_AI_Process_guard);

	bool CP2Moved;
	if(!RuleB(Pathing, Guard) || !zone->pathing)
		CP2Moved = CalculateNewPosition2(guard_x, guard_y, guard_z, walksp);
	else
	{
		if(!((x_pos == guard_x) && (y_pos == guard_y) && (z_pos == guard_z)))
		{
			bool WaypointChanged, NodeReached;
			VERTEX Goal = UpdatePath(guard_x, guard_y, guard_z, walksp, WaypointChanged, NodeReached);
			if(WaypointChanged)
				tar_ndx = 20;

			if(NodeReached)
				entity_list.OpenDoorsNear(CastToNPC()); 

			CP2Moved = CalculateNewPosition2(Goal.x, Goal.y, Goal.z, walksp);
		}
		else
			CP2Moved = false;

	}
	if (!CP2Moved)
	{
		if(moved) {
			mlog(AI__WAYPOINTS, "Reached guard point (%.3f,%.3f,%.3f)", guard_x, guard_y, guard_z);
			ClearFeignMemory();
			moved=false;
			SetMoving(false);
			if (GetTarget() == NULL || DistNoRoot(*GetTarget()) >= 5*5 )
			{
				SetHeading(guard_heading); 
			} else { 
				FaceTarget(GetTarget()); 
			}
			SendPosition();	
            SetAppearance(GetGuardPointAnim());
		}
	 } 
  } 
}

// Note: Mob that caused this may not get added to the hate list until after this function call completes
void Mob::AI_Event_Engaged(Mob* attacker, bool iYellForHelp) {
	if (!IsAIControlled())
		return;

	if(GetAppearance() != eaStanding)
    {
        SetAppearance(eaStanding);
    }

	if (iYellForHelp) {
		if(IsPet()) {
			GetOwner()->AI_Event_Engaged(attacker, iYellForHelp);
		} else {
			entity_list.AIYellForHelp(this, attacker);
		}
	}

	if(IsNPC())
	{
		if(CastToNPC()->GetGrid() > 0)
		{
			DistractedFromGrid = true;
		}
		if(attacker && !attacker->IsCorpse())
		{
			//Because sometimes the AIYellForHelp triggers another engaged and then immediately a not engaged
			//if the target dies before it goes off
			if(attacker->GetHP() > 0)
			{
				if(!CastToNPC()->GetCombatEvent() && GetHP() > 0)
				{
                    parse->EventNPC(EVENT_COMBAT, CastToNPC(), attacker, "1", 0);
					CastToNPC()->SetCombatEvent(true);
				}
			}
		}
	}
}

// Note: Hate list may not be actually clear until after this function call completes
void Mob::AI_Event_NoLongerEngaged() {
	if (!IsAIControlled())
		return;
	this->AIwalking_timer->Start(RandomTimer(3000,20000));
	pLastFightingDelayMoving = Timer::GetCurrentTime();
	if (minLastFightingDelayMoving == maxLastFightingDelayMoving)
		pLastFightingDelayMoving += minLastFightingDelayMoving;
	else
		pLastFightingDelayMoving += MakeRandomInt(minLastFightingDelayMoving, maxLastFightingDelayMoving);
	// EverHood - So mobs don't keep running as a ghost until AIwalking_timer fires
	// if they were moving prior to losing all hate
	if(IsMoving()){
		SetRunAnimSpeed(0);
		SetMoving(false);
		SendPosition();
	}
	ClearRampage();

	if(IsNPC())
	{
		if(CastToNPC()->GetCombatEvent() && GetHP() > 0)
		{
            parse->EventNPC(EVENT_COMBAT, CastToNPC(), NULL, "0", 0);
			CastToNPC()->SetCombatEvent(false);
		}
	}
}

//this gets called from InterruptSpell() for failure or SpellFinished() for success
void NPC::AI_Event_SpellCastFinished(bool iCastSucceeded, int8 slot) {
	if (slot == 1) {
		int32 recovery_time = 0;
		if (iCastSucceeded) {
			if (casting_spell_AIindex < AIspells.size()) {
					recovery_time += spells[AIspells[casting_spell_AIindex].spellid].recovery_time;
					if (AIspells[casting_spell_AIindex].recast_delay >= 0)
					{
						if (AIspells[casting_spell_AIindex].recast_delay < 10000)
							AIspells[casting_spell_AIindex].time_cancast = Timer::GetCurrentTime() + (AIspells[casting_spell_AIindex].recast_delay*1000);
					}
					else
						AIspells[casting_spell_AIindex].time_cancast = Timer::GetCurrentTime() + spells[AIspells[casting_spell_AIindex].spellid].recast_time;
			}
			if (recovery_time < AIautocastspell_timer->GetSetAtTrigger())
				recovery_time = AIautocastspell_timer->GetSetAtTrigger();
			AIautocastspell_timer->Start(recovery_time, false);
		}
		else
			AIautocastspell_timer->Start(800, false);
		casting_spell_AIindex = AIspells.size();
	}
}


bool NPC::AI_EngagedCastCheck() {
	if (AIautocastspell_timer->Check(false)) {
		_ZP(Mob_AI_Process_engaged_cast);
		AIautocastspell_timer->Disable();	//prevent the timer from going off AGAIN while we are casting.
		
		mlog(AI__SPELLS, "Engaged autocast check triggered. Trying to cast healing spells then maybe offensive spells.");
		
		// try casting a heal or gate
		if (!AICastSpell(this, 100, SpellType_Heal | SpellType_Escape | SpellType_InCombatBuff)) {
			// try casting a heal on nearby
			if (!entity_list.AICheckCloseBeneficialSpells(this, 25, MobAISpellRange, SpellType_Heal)) {
				//nobody to heal, try some detrimental spells.
				if(!AICastSpell(GetTarget(), 20, SpellType_Nuke | SpellType_Lifetap | SpellType_DOT | SpellType_Dispel | SpellType_Mez)) {
					//no spell to cast, try again soon.
					AIautocastspell_timer->Start(RandomTimer(500, 1000), false);
				}
			}	
		}
		return(true);
	}
	
	return(false);
}

bool NPC::AI_PursueCastCheck() {
	if (AIautocastspell_timer->Check(false)) {
		_ZP(Mob_AI_Process_pursue_cast);
		AIautocastspell_timer->Disable();	//prevent the timer from going off AGAIN while we are casting.
		
		mlog(AI__SPELLS, "Engaged (pursuing) autocast check triggered. Trying to cast offensive spells.");
		if(!AICastSpell(GetTarget(), 90, SpellType_Root | SpellType_Nuke | SpellType_Lifetap | SpellType_Snare | SpellType_DOT | SpellType_Dispel | SpellType_Mez)) {
			//no spell cast, try again soon.
			AIautocastspell_timer->Start(RandomTimer(500, 2000), false);
		} //else, spell casting finishing will reset the timer.
		return(true);
	}
	return(false);
}

bool NPC::AI_IdleCastCheck() {
	if (AIautocastspell_timer->Check(false)) {
		_ZP(Mob_AI_Process_autocast);
#if MobAI_DEBUG_Spells >= 25
		cout << "Non-Engaged autocast check triggered: " << this->GetName() << endl;
#endif
		AIautocastspell_timer->Disable();	//prevent the timer from going off AGAIN while we are casting.
		if (!AICastSpell(this, 100, SpellType_Heal | SpellType_Buff | SpellType_Pet)) {
			if(!entity_list.AICheckCloseBeneficialSpells(this, 33, MobAISpellRange, SpellType_Heal | SpellType_Buff)) {
				//if we didnt cast any spells, our autocast timer just resets to the 
				//last duration it was set to... try to put up a more reasonable timer...
				AIautocastspell_timer->Start(RandomTimer(1000, 5000), false);
			}	//else, spell casting finishing will reset the timer.
		}	//else, spell casting finishing will reset the timer.
		return(true);
	}
	return(false);
}

void Mob::StartEnrage()
{
    // dont continue if already enraged
    if (bEnraged)
        return;
    if (SpecAttackTimers[SPECATK_ENRAGE] && !SpecAttackTimers[SPECATK_ENRAGE]->Check())
        return;
    // see if NPC has possibility to enrage
    if (!SpecAttacks[SPECATK_ENRAGE])
        return;
    // check if timer exists (should be true at all times)
    if (SpecAttackTimers[SPECATK_ENRAGE])
    {
		safe_delete(SpecAttackTimers[SPECATK_ENRAGE]);
        SpecAttackTimers[SPECATK_ENRAGE] = NULL;
    }

    if (!SpecAttackTimers[SPECATK_ENRAGE])
    {
        SpecAttackTimers[SPECATK_ENRAGE] = new Timer(EnragedDurationTimer);
    }
    // start the timer. need to call IsEnraged frequently since we dont have callback timers :-/
    SpecAttackTimers[SPECATK_ENRAGE]->Start();
    bEnraged = true;
	entity_list.MessageClose_StringID(this, true, 200, MT_NPCEnrage, NPC_ENRAGE_START, GetCleanName());
}

void Mob::ProcessEnrage(){
	if(IsEnraged()){
		if(SpecAttackTimers[SPECATK_ENRAGE] && SpecAttackTimers[SPECATK_ENRAGE]->Check()){
			entity_list.MessageClose_StringID(this, true, 200, MT_NPCEnrage, NPC_ENRAGE_END, GetCleanName());
			SpecAttackTimers[SPECATK_ENRAGE]->Start(EnragedTimer);
			bEnraged = false;
		}
	}
}

bool Mob::IsEnraged() 
{
    return bEnraged;
}

bool Mob::Flurry()
{
    // this is wrong, flurry is extra attacks on the current target
    Mob *target = GetTarget();
	if (target) {
		if (!IsPet()) {
			entity_list.MessageClose_StringID(this, true, 200, MT_NPCFlurry, NPC_FLURRY, GetCleanName(), target->GetCleanName());
		} else {
			entity_list.MessageClose_StringID(this, true, 200, MT_PetFlurry, NPC_FLURRY, GetCleanName(), target->GetCleanName());
		}
		for (int i = 0; i < RuleI(Combat, MaxFlurryHits); i++)
			Attack(target);
	}
    return true;
}

bool Mob::AddRampage(Mob *mob)
{
	if(!mob)
		return false;

    if (!SpecAttacks[SPECATK_RAMPAGE])
        return false;

    for (int i = 0; i < RampageArray.size(); i++)
    {
        // if name is already on the list dont add it again
        if (strcasecmp(mob->GetName(), RampageArray[i].c_str()) == 0)
            return false;
	}
	std::string r_name = mob->GetName();
	RampageArray.push_back(r_name);
    return true;
}

void Mob::ClearRampage(){
	RampageArray.clear();
}

bool Mob::Rampage()
{
	int index_hit = 0;
	if (!IsPet()) {
		entity_list.MessageClose_StringID(this, true, 200, MT_NPCRampage, NPC_RAMPAGE, GetCleanName());
	} else {
		entity_list.MessageClose_StringID(this, true, 200, MT_PetFlurry, NPC_RAMPAGE, GetCleanName());
	}
    for (int i = 0; i < RampageArray.size(); i++)
    {
		if(index_hit >= RuleI(Combat, MaxRampageTargets))
			break;	
        // range is important
		Mob *m_target = entity_list.GetMob(RampageArray[i].c_str());
        if(m_target)
        {
			if(m_target == GetTarget())
				continue;
            if (CombatRange(m_target))
			{
                Attack(m_target);
				index_hit++;
			}
        }
    }
	if(index_hit < RuleI(Combat, MaxRampageTargets))
		Attack(GetTarget());
    return true;
}

void Mob::AreaRampage()
{
	int index_hit = 0;
	if (!IsPet()) {
		entity_list.MessageClose_StringID(this, true, 200, MT_NPCRampage, NPC_RAMPAGE, GetCleanName());
	} else {
		entity_list.MessageClose_StringID(this, true, 200, MT_PetFlurry, NPC_RAMPAGE, GetCleanName());
	}
	index_hit = hate_list.AreaRampage(this, GetTarget());

	if(index_hit == 0)
		Attack(GetTarget());
}

int32 Mob::GetLevelCon(int8 mylevel, int8 iOtherLevel) {
    sint16 diff = iOtherLevel - mylevel;
	int32 conlevel=0;
	
    if (diff == 0)
        return CON_WHITE;
    else if (diff >= 1 && diff <= 2)
        return CON_YELLOW;
    else if (diff >= 3)
        return CON_RED;

    if (mylevel <= 8)
    {
        if (diff <= -4)
            conlevel = CON_GREEN;
        else
            conlevel = CON_BLUE;
    }
    else if (mylevel <= 9)
	{
        if (diff <= -6)
            conlevel = CON_GREEN;
        else if (diff <= -4)
            conlevel = CON_LIGHTBLUE;
        else
            conlevel = CON_BLUE;
	}
    else if (mylevel <= 13)
	{
        if (diff <= -7)
            conlevel = CON_GREEN;
        else if (diff <= -5)
            conlevel = CON_LIGHTBLUE;
        else
            conlevel = CON_BLUE;
	}
	else if (mylevel <= 15)
	{
        if (diff <= -7)
            conlevel = CON_GREEN;
        else if (diff <= -5)
            conlevel = CON_LIGHTBLUE;
        else
            conlevel = CON_BLUE;
	}
	else if (mylevel <= 17)
	{
        if (diff <= -8)
            conlevel = CON_GREEN;
        else if (diff <= -6)
            conlevel = CON_LIGHTBLUE;
        else
            conlevel = CON_BLUE;
	}
	else if (mylevel <= 21)
	{
        if (diff <= -9)
            conlevel = CON_GREEN;
        else if (diff <= -7)
            conlevel = CON_LIGHTBLUE;
        else
            conlevel = CON_BLUE;
	}
	else if (mylevel <= 25)
	{
        if (diff <= -10)
            conlevel = CON_GREEN;
        else if (diff <= -8)
            conlevel = CON_LIGHTBLUE;
        else
            conlevel = CON_BLUE;
	}
	else if (mylevel <= 29)
	{
        if (diff <= -11)
            conlevel = CON_GREEN;
        else if (diff <= -9)
            conlevel = CON_LIGHTBLUE;
        else
            conlevel = CON_BLUE;
	}
	else if (mylevel <= 31)
	{
        if (diff <= -12)
            conlevel = CON_GREEN;
        else if (diff <= -9)
            conlevel = CON_LIGHTBLUE;
        else
            conlevel = CON_BLUE;
	}
	else if (mylevel <= 33)
	{
        if (diff <= -13)
            conlevel = CON_GREEN;
        else if (diff <= -10)
            conlevel = CON_LIGHTBLUE;
        else
            conlevel = CON_BLUE;
	}
	else if (mylevel <= 37)
	{
        if (diff <= -14)
            conlevel = CON_GREEN;
        else if (diff <= -11)
            conlevel = CON_LIGHTBLUE;
        else
            conlevel = CON_BLUE;
	}
	else if (mylevel <= 41)
	{
        if (diff <= -16)
            conlevel = CON_GREEN;
        else if (diff <= -12)
            conlevel = CON_LIGHTBLUE;
        else
            conlevel = CON_BLUE;
	}
	else if (mylevel <= 45)
	{
        if (diff <= -17)
            conlevel = CON_GREEN;
        else if (diff <= -13)
            conlevel = CON_LIGHTBLUE;
        else
            conlevel = CON_BLUE;
	}
	else if (mylevel <= 49)
	{
        if (diff <= -18)
            conlevel = CON_GREEN;
        else if (diff <= -14)
            conlevel = CON_LIGHTBLUE;
        else
            conlevel = CON_BLUE;
	}
	else if (mylevel <= 53)
	{
        if (diff <= -19)
            conlevel = CON_GREEN;
        else if (diff <= -15)
            conlevel = CON_LIGHTBLUE;
        else
            conlevel = CON_BLUE;
	}
	else if (mylevel <= 55)
	{
        if (diff <= -20)
            conlevel = CON_GREEN;
        else if (diff <= -15)
            conlevel = CON_LIGHTBLUE;
        else
            conlevel = CON_BLUE;
	}
	else
	{
        if (diff <= -21)
            conlevel = CON_GREEN;
        else if (diff <= -16)
            conlevel = CON_LIGHTBLUE;
        else
            conlevel = CON_BLUE;
	}
	return conlevel;
}

void NPC::CheckSignal() {
	if (!signal_q.empty()) {
		int signal_id = signal_q.front();
		signal_q.pop_front();
		char buf[32];
		snprintf(buf, 31, "%d", signal_id);
		buf[31] = '\0';
		parse->EventNPC(EVENT_SIGNAL, this, NULL, buf, 0);
	}
}



/*
alter table npc_types drop column usedspells;
alter table npc_types add column npc_spells_id int(11) unsigned not null default 0 after merchant_id;
Create Table npc_spells (
	id int(11) unsigned not null auto_increment primary key,
	name tinytext,
	parent_list int(11) unsigned not null default 0,
	attack_proc smallint(5) not null default -1,
	proc_chance tinyint(3) not null default 3
	);
create table npc_spells_entries (
	id int(11) unsigned not null auto_increment primary key,
	npc_spells_id int(11) not null,
	spellid smallint(5) not null default 0,
	type smallint(5) unsigned not null default 0,
	minlevel tinyint(3) unsigned not null default 0,
	maxlevel tinyint(3) unsigned not null default 255,
	manacost smallint(5) not null default '-1',
	recast_delay int(11) not null default '-1',
	priority smallint(5) not null default 0,
	index npc_spells_id (npc_spells_id)
	);
*/ 

bool IsSpellInList(DBnpcspells_Struct* spell_list, sint16 iSpellID);
bool Compare_AI_Spells(AISpells_Struct i, AISpells_Struct j);

bool NPC::AI_AddNPCSpells(int32 iDBSpellsID) {
	// ok, this function should load the list, and the parent list then shove them into the struct and sort
	npc_spells_id = iDBSpellsID;
	AIspells.clear();
	if (iDBSpellsID == 0) {
		AIautocastspell_timer->Disable();
		return false;
	}
	DBnpcspells_Struct* spell_list = database.GetNPCSpells(iDBSpellsID);
	if (!spell_list) {
		AIautocastspell_timer->Disable();
		return false;
	}
	DBnpcspells_Struct* parentlist = database.GetNPCSpells(spell_list->parent_list);
	uint32 i;
#if MobAI_DEBUG_Spells >= 10
	cout << "Loading NPCSpells onto " << this->GetName() << ": dbspellsid=" << iDBSpellsID;
	if (spell_list) {
		cout << " (found, " << spell_list->numentries << "), parentlist=" << spell_list->parent_list;
		if (spell_list->parent_list) {
			if (parentlist) {
				cout << " (found, " << parentlist->numentries << ")";
			}
			else
				cout << " (not found)";
		}
	}
	else
		cout << " (not found)";
	cout << endl;
#endif
	sint16 attack_proc_spell = -1;
	sint8 proc_chance = 3;
	if (parentlist) {
		attack_proc_spell = parentlist->attack_proc;
		proc_chance = parentlist->proc_chance;
		for (i=0; i<parentlist->numentries; i++) {
			if (GetLevel() >= parentlist->entries[i].minlevel && GetLevel() <= parentlist->entries[i].maxlevel && parentlist->entries[i].spellid > 0) {
				if (!IsSpellInList(spell_list, parentlist->entries[i].spellid))
				{
					AddSpellToNPCList(parentlist->entries[i].priority, 
						parentlist->entries[i].spellid, parentlist->entries[i].type, 
						parentlist->entries[i].manacost, parentlist->entries[i].recast_delay,
						parentlist->entries[i].resist_adjust);
				}
			}
		}
	}
	if (spell_list->attack_proc >= 0) {
		attack_proc_spell = spell_list->attack_proc;
		proc_chance = spell_list->proc_chance;
	}
	for (i=0; i<spell_list->numentries; i++) {
		if (GetLevel() >= spell_list->entries[i].minlevel && GetLevel() <= spell_list->entries[i].maxlevel && spell_list->entries[i].spellid > 0) {
			AddSpellToNPCList(spell_list->entries[i].priority, 
				spell_list->entries[i].spellid, spell_list->entries[i].type, 
				spell_list->entries[i].manacost, spell_list->entries[i].recast_delay, 
				spell_list->entries[i].resist_adjust);
		}
	}
	std::sort(AIspells.begin(), AIspells.end(), Compare_AI_Spells);
	
	if (attack_proc_spell > 0)
		AddProcToWeapon(attack_proc_spell, true, proc_chance);

	if (AIspells.size() == 0)
		AIautocastspell_timer->Disable();
	else
		AIautocastspell_timer->Trigger();
	return true;
}

bool IsSpellInList(DBnpcspells_Struct* spell_list, sint16 iSpellID) {
	for (uint32 i=0; i < spell_list->numentries; i++) {
		if (spell_list->entries[i].spellid == iSpellID)
			return true;
	}
	return false;
}

bool Compare_AI_Spells(AISpells_Struct i, AISpells_Struct j) 
{ 
	return(i.priority > j.priority); 
}

// adds a spell to the list, taking into account priority and resorting list as needed.
void NPC::AddSpellToNPCList(sint16 iPriority, sint16 iSpellID, uint16 iType, 
							sint16 iManaCost, sint32 iRecastDelay, sint16 iResistAdjust) 
{
	
	if(!IsValidSpell(iSpellID))
		return;

	HasAISpell = true;
	AISpells_Struct t;
	
	t.priority = iPriority;
	t.spellid = iSpellID;
	t.type = iType;
	t.manacost = iManaCost;
	t.recast_delay = iRecastDelay;
	t.time_cancast = 0;
	t.resist_adjust = iResistAdjust;

	AIspells.push_back(t);
}

void NPC::RemoveSpellFromNPCList(sint16 spell_id)
{
	std::vector<AISpells_Struct>::iterator iter = AIspells.begin();
	while(iter != AIspells.end())
	{
		if((*iter).spellid == spell_id)
		{
			iter = AIspells.erase(iter);
			continue;
		}
		iter++;
	}
}

DBnpcspells_Struct* ZoneDatabase::GetNPCSpells(int32 iDBSpellsID) {
	if (iDBSpellsID == 0)
		return 0;
	if (!npc_spells_cache) {
		npc_spells_maxid = GetMaxNPCSpellsID();
		npc_spells_cache = new DBnpcspells_Struct*[npc_spells_maxid+1];
		npc_spells_loadtried = new bool[npc_spells_maxid+1];
		for (uint32 i=0; i<=npc_spells_maxid; i++) {
			npc_spells_cache[i] = 0;
			npc_spells_loadtried[i] = false;
		}
	}
	if (iDBSpellsID > npc_spells_maxid)
		return 0;
	if (npc_spells_cache[iDBSpellsID]) { // it's in the cache, easy =)
		return npc_spells_cache[iDBSpellsID];
	}
	else if (!npc_spells_loadtried[iDBSpellsID]) { // no reason to ask the DB again if we have failed once already
		npc_spells_loadtried[iDBSpellsID] = true;
		char errbuf[MYSQL_ERRMSG_SIZE];
		char *query = 0;
		MYSQL_RES *result;
		MYSQL_ROW row;
		
		if (RunQuery(query, MakeAnyLenString(&query, "SELECT id, parent_list, attack_proc, proc_chance from npc_spells where id=%d", iDBSpellsID), errbuf, &result)) {
			safe_delete_array(query);
			if (mysql_num_rows(result) == 1) {
				row = mysql_fetch_row(result);
				int32 tmpparent_list = atoi(row[1]);
				sint16 tmpattack_proc = atoi(row[2]);
				int8 tmpproc_chance = atoi(row[3]);
				mysql_free_result(result);
				if (RunQuery(query, MakeAnyLenString(&query, "SELECT spellid, type, minlevel, maxlevel, manacost, recast_delay, priority, resist_adjust from npc_spells_entries where npc_spells_id=%d ORDER BY minlevel", iDBSpellsID), errbuf, &result)) {
					safe_delete_array(query);
					int32 tmpSize = sizeof(DBnpcspells_Struct) + (sizeof(DBnpcspells_entries_Struct) * mysql_num_rows(result));
					npc_spells_cache[iDBSpellsID] = (DBnpcspells_Struct*) new uchar[tmpSize];
					memset(npc_spells_cache[iDBSpellsID], 0, tmpSize);
					npc_spells_cache[iDBSpellsID]->parent_list = tmpparent_list;
					npc_spells_cache[iDBSpellsID]->attack_proc = tmpattack_proc;
					npc_spells_cache[iDBSpellsID]->proc_chance = tmpproc_chance;
					npc_spells_cache[iDBSpellsID]->numentries = mysql_num_rows(result);
					int j = 0;
					while ((row = mysql_fetch_row(result))) {
						int spell_id = atoi(row[0]);
						npc_spells_cache[iDBSpellsID]->entries[j].spellid = spell_id;
						npc_spells_cache[iDBSpellsID]->entries[j].type = atoi(row[1]);
						npc_spells_cache[iDBSpellsID]->entries[j].minlevel = atoi(row[2]);
						npc_spells_cache[iDBSpellsID]->entries[j].maxlevel = atoi(row[3]);
						npc_spells_cache[iDBSpellsID]->entries[j].manacost = atoi(row[4]);
						npc_spells_cache[iDBSpellsID]->entries[j].recast_delay = atoi(row[5]);
						npc_spells_cache[iDBSpellsID]->entries[j].priority = atoi(row[6]);
						if(row[7])
						{
							npc_spells_cache[iDBSpellsID]->entries[j].resist_adjust = atoi(row[7]);
						}
						else
						{
							if(IsValidSpell(spell_id))
							{
								npc_spells_cache[iDBSpellsID]->entries[j].resist_adjust = spells[spell_id].ResistDiff;
							}
						}
						j++;
					}
					mysql_free_result(result);
					return npc_spells_cache[iDBSpellsID];
				}
				else {
					cerr << "Error in AddNPCSpells query1 '" << query << "' " << errbuf << endl;
					safe_delete_array(query);
					return 0;
				}
			}
			else {
				mysql_free_result(result);
			}
		}
		else {
			cerr << "Error in AddNPCSpells query1 '" << query << "' " << errbuf << endl;
			safe_delete_array(query);
			return 0;
		}
		
		return 0;	
	}
	return 0;
}

int32 ZoneDatabase::GetMaxNPCSpellsID() {
	char errbuf[MYSQL_ERRMSG_SIZE];
    char *query = 0;
    MYSQL_RES *result;
    MYSQL_ROW row;
	
	if (RunQuery(query, MakeAnyLenString(&query, "SELECT max(id) from npc_spells"), errbuf, &result)) {
		safe_delete_array(query);
		if (mysql_num_rows(result) == 1) {
			row = mysql_fetch_row(result);
			int32 ret = 0;
			if (row[0])
				ret = atoi(row[0]);
			mysql_free_result(result);
			return ret;
		}
		mysql_free_result(result);
	}
	else {
		cerr << "Error in GetMaxNPCSpellsID query '" << query << "' " << errbuf << endl;
		safe_delete_array(query);
		return 0;
	}
	
	return 0;	
}
Reply With Quote
  #2  
Old 09-22-2011, 09:38 PM
lerxst2112
Demi-God
 
Join Date: Aug 2010
Posts: 1,742
Default

It's generally better to just post a diff. If you're using TortoiseSVN, right-click the folder where you've downloaded the source code ->TortoiseSVN -> Create patch...
Reply With Quote
  #3  
Old 09-23-2011, 08:13 AM
BrandeX
Sarnak
 
Join Date: Aug 2009
Location: China
Posts: 33
Default

Quote:
Ok, permarooted mobs act wrong... ...The solution to making the work like they do on live (have real aggrotables not just hit the nearest pc...)
rooted mobs just hit the nearest pc on live (at least when I last played)
Reply With Quote
  #4  
Old 09-23-2011, 09:56 AM
sorvani
Dragon
 
Join Date: May 2010
Posts: 965
Default

Mobs with a root spell cast on them always do, yes. But mobs that are perma-rooted is what he is saying should act different.

On the EQ Titan forums they have some posts linking back to sources that support this.

I played a cleric on live so I had no idea about this side of these types of mobs.. I was too busy healing.

Should be easy enough to test if two people have active accounts. Go pick a random perma-rooted mob, have one person aggro and back up then have the next get closer.
Reply With Quote
  #5  
Old 09-23-2011, 10:30 AM
Caryatis
Dragon
 
Join Date: May 2009
Location: Milky Way
Posts: 539
Default

Or you can just set the runspeed really low(like 0.001) to get this exact same behaviour while retaining the ability to have permarooted mobs to have the rooted agro behaviour(ie if you make this change it removes the ability to have a permarooted mob behave as if it is a spell rooted).

This seems unnecessary.
Reply With Quote
  #6  
Old 09-23-2011, 05:53 PM
sorvani
Dragon
 
Join Date: May 2010
Posts: 965
Default

I would see if someone can test live functionality. Program it that way. Then set a rule to make it the other way if the server operator desires.
Reply With Quote
  #7  
Old 09-23-2011, 06:37 PM
Caryatis
Dragon
 
Join Date: May 2009
Location: Milky Way
Posts: 539
Default

The idea is to retain the functionality per mob, not serverwide. As it is right now, you can have a perma rooted mob retain non rooted agro behavior(runspeed = 0.0001) or act like undead and hit closest(runspeed = 0).

This change would just remove the ability to choose which way you want on a mob by mob basis.

I think the problem here is more about the awareness of how the code functions and not necessarily the code itself. If you really wanted to update it to make it clearer, it would be best to add a new special atk code I think.
Reply With Quote
  #8  
Old 09-24-2011, 12:17 PM
saarr
Fire Beetle
 
Join Date: Jul 2011
Posts: 11
Default

sorvani on live perma rooted mobs did have and aggro table when i was playing on live specially in tacvi i had to set rampage (first one to aggro the mob) by casting terror spells on it, once i did that for over a minute could bring the whole raid into the room and the main tank could start attacking the mob to tank it, but all in all perma rooted mobs attack on aggroing not nearest player or pet rooting the mob always did.
Reply With Quote
  #9  
Old 09-25-2011, 01:17 AM
Estalia
Fire Beetle
 
Join Date: Jun 2005
Posts: 7
Default

I do not have dev status to post the updated to svn, and since this does change how a decent number of mobs act, i would rather have it posted and only added if the dev's want this change. I have been under the impression that eqemu wanted live like operations that are well coded. I feel this meets that, but I submit it to the community to do with as they please.
Reply With Quote
  #10  
Old 09-25-2011, 01:59 AM
ChaosSlayerZ's Avatar
ChaosSlayerZ
Demi-God
 
Join Date: Mar 2009
Location: Umm
Posts: 1,492
Default

if someone could explain to me what is a practical difference between a rooted and perma-rooted mob?

From what I understand, the rooted mob will hit "whoever is closer", ONLY if he cannot hit the person on top of the hate list - isn't it so?

Unless, he can summon.

Amd, of course, all perma-rooted mobs could summon - so whoever was standing closer point is moot, since whoever is on top of hate list - simply gets summoned. Otherwise any caster could solo any such mob with ease.
Reply With Quote
  #11  
Old 09-25-2011, 02:09 AM
Estalia
Fire Beetle
 
Join Date: Jun 2005
Posts: 7
Default

A rooted mob should always be trying to hit whichever pc is closest to it.

A permarooted mob should be trying to hit whoever has the highest aggro. At 96 percent that permarooted mob should start trying to summon if the player at the top of the aggro table is out of its melee range. There are a few dragons in ToV that have a spell summon that will go off every 30 seconds in addition to the normal summon mechanic, but it will go off as soon as someone is on their aggro table.
Reply With Quote
  #12  
Old 09-25-2011, 02:22 AM
KLS
Administrator
 
Join Date: Sep 2006
Posts: 1,348
Default

Can someone post a diff and we'll see about getting this in?
Reply With Quote
  #13  
Old 09-25-2011, 03:00 AM
Estalia
Fire Beetle
 
Join Date: Jun 2005
Posts: 7
Default

the difference between the code currently in mobai.cpp is in VOID MOB::AI_Process() and is in italics. and the reason i don't trust my programing skill should be apparent in that i placed it in the wrong place in my post. going to try to edit it after i post this.

if (engaged)
{
if (IsRooted() && !permarooted())
SetTarget(hate_list.GetClosest(this));
else
{
if(AItarget_check_timer->Check())
{
SetTarget(hate_list.GetTop(this));
}
}
Reply With Quote
  #14  
Old 09-25-2011, 03:04 AM
Estalia
Fire Beetle
 
Join Date: Jun 2005
Posts: 7
Default

Ok, i cannot edit it, but i can post it correctly if needed.
Reply With Quote
  #15  
Old 09-26-2011, 04:20 PM
sorvani
Dragon
 
Join Date: May 2010
Posts: 965
Default

patch file contents below.

edit: Hmm still think a rule would be nice on this. Any other opinions?
edit2: i rebuilt my server with 2021 and this change and it is not working.

Code:
Index: trunk/EQEmuServer/zone/MobAI.cpp
===================================================================
--- trunk/EQEmuServer/zone/MobAI.cpp	(revision 2021)
+++ trunk/EQEmuServer/zone/MobAI.cpp	(working copy)
@@ -773,7 +773,7 @@
 
 	if (engaged) 
 	{
-		if (IsRooted())
+		if (IsRooted() && !permarooted)
 			SetTarget(hate_list.GetClosest(this));
 		else
 		{
Reply With Quote
Reply


Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump

   

All times are GMT -4. The time now is 12:44 PM.


 

Everquest is a registered trademark of Daybreak Game Company LLC.
EQEmulator is not associated or affiliated in any way with Daybreak Game Company LLC.
Except where otherwise noted, this site is licensed under a Creative Commons License.
       
Powered by vBulletin®, Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
Template by Bluepearl Design and vBulletin Templates - Ver3.3