EQEmulator Forums

EQEmulator Forums (https://www.eqemulator.org/forums/index.php)
-   Development::Server Code Submissions (https://www.eqemulator.org/forums/forumdisplay.php?f=669)
-   -   Permarooted mobs (https://www.eqemulator.org/forums/showthread.php?t=34227)

Estalia 09-22-2011 08:48 PM

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;       
}


lerxst2112 09-22-2011 09:38 PM

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...

BrandeX 09-23-2011 08:13 AM

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)

sorvani 09-23-2011 09:56 AM

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.

Caryatis 09-23-2011 10:30 AM

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.

sorvani 09-23-2011 05:53 PM

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.

Caryatis 09-23-2011 06:37 PM

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.

saarr 09-24-2011 12:17 PM

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.

Estalia 09-25-2011 01:17 AM

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.

ChaosSlayerZ 09-25-2011 01:59 AM

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.

Estalia 09-25-2011 02:09 AM

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.

KLS 09-25-2011 02:22 AM

Can someone post a diff and we'll see about getting this in?

Estalia 09-25-2011 03:00 AM

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));
}
}

Estalia 09-25-2011 03:04 AM

Ok, i cannot edit it, but i can post it correctly if needed.

sorvani 09-26-2011 04:20 PM

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
                {


Caryatis 09-26-2011 04:47 PM

A rule makes no sense for this, neither does this change either but at least it will be easy to edit out.

lerxst2112 09-26-2011 04:57 PM

I think a rule is appropriate unless someone can verify how it works on live today. I remember rooted mobs just attacking the closest target, but I also don't recall any that were permarooted.

ChaosSlayerZ 09-26-2011 05:12 PM

Quote:

Originally Posted by Estalia (Post 203447)
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.

Important note: we have an individual Summoning flag for npcs, so perma-rooted mobs should NOT automatically start summoning, unless they have the flag set.
There are raid encounters where, for example, you have a room full of perma-rooted mobs, who just stand where they are and cast, but don't summon

sorvani 09-26-2011 05:16 PM

lerxst: typically the only permarooted mobs you will see are a few raid mobs scattered around in general. Permarooted mobs did behave differently than rooted mobs. Here is a post from 2003 on the subject.
http://everquest.allakhazam.com/db/i...05380013444374

caryatis: the point of a rule is to let people who do not like something, change that something easily. Yes your post about setting a low run speed works. But the point of the original post valid and the permarooted boolean already exists. It should be used appropriately instead of just getting lumped in with rooted.

lerxst2112 09-26-2011 05:58 PM

Quote:

Originally Posted by sorvani (Post 203470)
lerxst: typically the only permarooted mobs you will see are a few raid mobs scattered around in general. Permarooted mobs did behave differently than rooted mobs. Here is a post from 2003 on the subject.
http://everquest.allakhazam.com/db/i...05380013444374

That's why I said "on live today". How it may have worked in 2003 could be the same as today, but it also might not be. IMO some things should be checked before just being implemented. But, that's just my opinion.

Also, glad you fixed the error in the original posted code when you posted the patch. Knowing both pieces of code the OP posted wouldn't compile doesn't fill me with confidence.

Caryatis 09-26-2011 07:27 PM

The point you are missing though sorvani is that on live mobs exhibit both behaviors. Some perma rooted mobs attack the closest target, others a use normal aggro list. If you make it a rule then you limit your entire server to only using one kind(and you can change it at will now, per mob).

I dont see why you dont see 0.0001 as permaroot with normal agro and 0 as permaroot like spell rooted.

ChaosSlayerZ 09-26-2011 07:50 PM

use -1 as a special flag, rather than 0.0001, to prevent some funky behavior where mob accidentally gets agroed by someone, and starts moving towards that player, and eventually leaves his room, if player stays in zone long enough :D

Kayen 09-26-2011 11:31 PM

I have to agree with Caryatis.

This is a fix for something that isn't broken.

Permarooted/Rooted mobs always attack the closest target which is
how eqlive worked long as I played.

If you want to by pass this set run speed to 0.00000001, and I have been doing
this for years with no problems.

Kayen
GM Storm Haven.

Estalia 09-27-2011 12:55 AM

http://www.youtube.com/watch?v=Wa-YXWSRsXs at the 2:44 the tank is the shrunk human warrior who is not the closest.

http://www.youtube.com/watch?v=k8fLD...eature=related at the 1 minute mark, im not sure who is tanking, but it isn't any of the people who are close and behind the mob and noone is standing in front of it.

http://www.youtube.com/watch?v=zH-W7fPES9E at about 1:10 you can see the dwarf warrior getting knocked back and not being the closest, but yelinak doesn't spin to someone else.

I put in 10 years on live and really don't remember any of the killable npc's who were permarooted attacking the closest pc. I am not saying there isn't one, but most of the permarooted mobs used in the top "raid" zones from Kunark to GoD had aggro tables. If they didn't they would be saying give the warrior a shield and who care what kind of threat he has, he won't be loosing aggro.

trevius 09-27-2011 01:42 AM

I don't think people have any issue with adding the option to set perma-rooted mobs to use a normal hate table vs attacking the closest as they currently do. Seems like there are 2 debates; One being that there is already an option to just set the runspeed to a very low rate to simulate perma-rooted with the same properties as an unrooted mob. The other is that a rule could be added to set it to either handle it one way or the other server-wide for all NPCs.

One solution that would work for both would be to make it a special attack code. Though, I think the simplest solution would be to make it a rule that allows you to toggle if you want ALL perma-rooted mobs to act the same way as reported from Live, or if you want to leave the option to have some attack the closest (runspeed 0.0) and some attack the highest hate (runspeed 0.000001).

Maybe something like this would work out for everyone:

MobAI.cpp
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() && (RuleB(Aggro, PermaRootedAttacksNearest) || !permarooted))
                        SetTarget(hate_list.GetClosest(this));
                else
                {

And this to the aggro section of common/ruletypes.h
Code:

RULE_BOOL ( Aggro, PermaRootedAttacksNearest, true )

joligario 09-27-2011 05:18 AM

So if I'm reading this argument right, there is perception that a permarooted mob attacks on hatelist and not proximity? So if a tank generates enough hate and steps back, a permarooted mob will ignore proximity and continue to focus on the hatelist top?

sorvani 09-27-2011 09:56 AM

That is exactly what happens. Of course all of these mobs can also summon so once they are actually damaged, said tank is not going to be able to stay out of melee range.

LordRahl13 10-15-2011 06:40 PM

Quote:

Originally Posted by joligario (Post 203503)
So if I'm reading this argument right, there is perception that a permarooted mob attacks on hatelist and not proximity? So if a tank generates enough hate and steps back, a permarooted mob will ignore proximity and continue to focus on the hatelist top?

Not really, if the tank would step out of melee range, and the mob could summon it would. If the tank steps out of range, and it can't summon(either it's been used already or the mob doesn't summon at all) the mob will turn to the next person in range at the top of the hate list. It's not like the mob will keep the tank targeted out of melee range just because he is on top of the aggro list.

bowmaster 10-18-2011 02:49 AM

if i remember correctly I was a SK + Necro

Perm Rooted Mobs Do go for the Closest Mob.
Because technically its still a root , but with a longer Duration :-)

wolfwalkereci 10-19-2011 06:15 AM

Really....

saarr 10-19-2011 07:15 AM

there is a Zun mob in tacvi (live) that drops the bbb, him and his four adds are perma rooted and summon at 95%. Every 10% in health he drops his adds come alive and hit like him. my guild used to summon tank the adds and him till the guild as a whole got stronger to straight tank them. Also in barindu there is a zun that is perma rooted till 50% if i remeber right and doesnt (used to) not summon, but was later patched to summon (unoffical patch not noted) and my guild at the time had time gear and he meleed for 7k with an ae ramp and was nasty but summon tanking was the only way to tank him unslowed. this is just my 2cents is all.

image 10-24-2011 09:12 AM

Just a thought, you can always set an NPC's runspeed to 0 which would in effect make them 'perm rooted'.. Then just use scripting if you need them to run again.

trevius 10-24-2011 11:00 AM

Quote:

Originally Posted by image (Post 204293)
Just a thought, you can always set an NPC's runspeed to 0 which would in effect make them 'perm rooted'.. Then just use scripting if you need them to run again.

That has already been discussed in-depth in this thread. It is not an ideal solution, but there are some alternatives that were also discussed.

ChaosSlayerZ 10-24-2011 05:06 PM

hmm, how do you make a perma-rooted mob otherwise? is there an individual flag? I always did them with just 0 speed.

Estalia 10-25-2011 04:37 PM

This thread isn't about making the mob permarooted. It is about making the permarooted mobs use an aggrotable instead of acting like a rooted mob who hits the nearest pc instead of using the aggrotable.

bowmaster 10-27-2011 07:16 PM

Believe so yeahh , havnt raided any perma rooted mobs for a while . unless theyve changed it , in the last few years.


All times are GMT -4. The time now is 01:24 PM.

Powered by vBulletin®, Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.