/******************************************************************
  
  Module:  aigoal.cpp
  
  Author: Borut Pfeifer
  
  Copyright 2005 Sony Online Entertainment.  All rights reserved.
  
*******************************************************************/

//-------------------------------------------------------- Includes
#include "aigoal.h"
#include "aigoal_attack.h"
#include "aigoal_noncombat.h"
#include "aigoal_cast.h"
#include "../intel.h"
#include "../stats.h"
#include "../registry.h"
#include "../graphic.h"
#include "../physics.h"
#include "../titan.h"
#include "../tuning.h"
#include "../database.h"
#include "../cameracontroller.h"
#include "../spell.h"
#include "behaviortypes.h"
#include "lostable.h"
#include "../debugoverlay.h"
#include "aiblackboard.h"
#include "sycamera.h"
#include "../gameerror.h"

//------------------------------------ cGoalQueue
cGoalQueue::cGoalQueue()
: mpOwner(NULL),
  mpCurGoal(NULL)
{
}

cGoalQueue::~cGoalQueue()
{
  for (int ii=mGoals.Begin();ii!=mGoals.End();ii=mGoals.Next(ii))
  {
    delete mGoals(ii);
  }

  mGoals.Clear();  
}

void cGoalQueue::Init(cAi* pOwner)
{
  SyAssert(pOwner != NULL);

  mpOwner = pOwner;
}

int cGoalQueue::GetHighestPriority()
{
  if (mGoals.Size() > 0)
  {
    // Enter new goal if it was started
    return mGoals(mGoals.Prev(mGoals.End()))->GetPriority();
  }

  return -1;
}

void cGoalQueue::Push(int priority, cAiGoal *pGoal)
{
  int i;
  int end = mGoals.Add();

  pGoal->Init(mpOwner, priority);

  GAME_ASSERT(ERROR_CODE, priority >= 0, "cGoalQueue::Push can't add a goal with a negative priority!");

  for (i=mGoals.Begin();i!=end;i=mGoals.Next(i))
  {
    if (mGoals(i)->GetPriority() > priority)
    {
      break;
    }
  }

  pGoal->SetCutSceneFlag(!mpOwner->GetOwner()->GetTitan()->AllowCutSceneControllerMotion());

  Insert(pGoal, i);
}


void cGoalQueue::Pop(cAiGoal *pGoal)
{
  // setting the priority to negative marks it to be deleted
  pGoal->SetPriority(-1);
}

void cGoalQueue::Clear()
{
  int i;
  int end = mGoals.End();

  for (i=mGoals.Begin();i!=end;i=mGoals.Next(i))
  {
    mGoals(i)->SetPriority(-1);
  }
}

void cGoalQueue::Remove(cAiGoal *pGoal)
{
  for (int i=0;i!=mGoals.End();i=mGoals.Next(i))
  {
    if (mGoals(i) == pGoal)
    {
      for (;mGoals.Next(i) != mGoals.End();i=mGoals.Next(i))
      {
        mGoals(i) = mGoals(mGoals.Next(i));
      }
      mGoals.Erase();
      return;
    }
  }

  GAME_ASSERT(ERROR_CODE, false, "Goal not found");
}

void cGoalQueue::Insert(cAiGoal *pGoal, int index)
{
  int i=mGoals.End();
  i=mGoals.Prev(i);

  for (;i!=index;i=mGoals.Prev(i))
  {
    mGoals(i) = mGoals(mGoals.Prev(i));
  }

  mGoals(index) = pGoal;
}

void cGoalQueue::ChangePriority(cAiGoal *pGoal, int priority)
{
  SyAssert(pGoal);

  if (!pGoal)
  {
    return;
  }

  if (pGoal->GetPriority() != priority)
  {
    pGoal->SetPriority(priority);
    Remove(pGoal);
    Push(priority, pGoal);
  }
}

cAiGoal* cGoalQueue::FindGoalType(eGoalType type)
{
  cAiGoal *pGoal;
  for (int i=mGoals.Begin();i!=mGoals.End();i=mGoals.Next(i))
  {
    pGoal = mGoals(i);
    if (pGoal->GetType() == type && pGoal->GetPriority() != -1) 
    {
      return pGoal;
    }
  }

  return NULL;
}

void cGoalQueue::Update(float time, bool bIsCutScene)
{
  GAME_ASSERT(ERROR_CODE, mGoals.Size() < 32, "Whoa, npc AI has too many goals at once.");

  if (mGoals.Size() <= 0)
  {
    return;
  }

  // exit/enter goals if changed
  if (bIsCutScene)
  {
    if (!mpCurGoal || !mpCurGoal->GetCutSceneFlag())
    {
      if (mpCurGoal)
      {
        mpCurGoal->Exit();
        mpCurGoal = NULL;
      }

      int i = mGoals.Prev(mGoals.End());
      
      while (i >= mGoals.Begin())
      {
        if (mGoals(i)->GetCutSceneFlag())
        {
          mpCurGoal = mGoals(i);
          mpCurGoal->Enter();
          break;
        }
        
        if (i <= mGoals.Begin())
        {
          break;
        }

        i = mGoals.Prev(i);
      }
    }
  }
  else
  {
    if (mpCurGoal != mGoals(mGoals.Prev(mGoals.End())))
    {
      if (mpCurGoal)
      {
        mpCurGoal->Exit();
      }

      mpCurGoal = mGoals(mGoals.Prev(mGoals.End()));
      mpCurGoal->Enter();
    }
  }

  // let current goal run
  if (mpCurGoal)
  {
    mpCurGoal->Update(time);
  }

  // now check for popped goals
  cAiGoal* pGoal;
  int i = 0;

  while (i < mGoals.Size())
  {
    pGoal = mGoals(i);

    if (pGoal->GetPriority() < 0)  // was marked to be deleted
    {
      if (mpCurGoal && mpCurGoal == pGoal)
      {
        mpCurGoal->Exit();
        mpCurGoal = NULL;
      }

      for (int j=i; mGoals.Next(j) != mGoals.End(); j=mGoals.Next(j))
      {
        mGoals(j) = mGoals(mGoals.Next(j));
      }

      mGoals.Erase();
      delete pGoal;
    }
    else
    {
      ++i;
    }
  }

#ifdef _DEBUG
  for (int i=mGoals.Begin();i!=mGoals.End();i=mGoals.Next(i))
  {
    if (mGoals.Next(i) != mGoals.End())
    {
      GAME_ASSERT(ERROR_CODE, mGoals(i)->GetPriority() <= mGoals(mGoals.Next(i))->GetPriority(), "AI goal queue has goals out of priority order");
    }
  }
#endif
}
//------------------------------------ cAiInterface


cAiInterface:: ~cAiInterface()
{
}

cAiInterface *cAiInterface::Allocate(cGameObject *owner)
{
  cAi* new_ai = SyNew cAi;
  new_ai->Init(owner);
  return new_ai;
}


cAiInterface::cAiInterface() 
{
}

//------------------------------------ cAi

float cAi::smFollowRadius = 15.0f;
float cAi::smDefendRadius = 7.5f;
float cAi::smActivationRadius = 15.0f;


cAi::cAi() :
  mpOwner(NULL),
  mAttackTarget(),
  mDefendTarget(),
  mLastDodgedProjectileID(ID_NONE),
  mNumHits(0),
  mbTargetWasAttacking(false),
  mTargetBlockTimer(0.0f),
  mLastHealthPercent(1.0f),
  mAbilityAllyWoundedTimer(0.0f),
  mAbilityAllyDeadTimer(0.0f),
  mAbilityAllyBuffTimer(0.0f)
{
}

cAi::~cAi()
{
  // this stuff should've been cleaned up by a reset call elsewhere
  GAME_ASSERT(ERROR_CODE, cAIBlackboard::Get()->CountRecords(cAIBlackboard::BBR_INVALID, mpOwner->GetID()) == 0, "AI has not properly cleaned up blackboard records");
  mAttackTarget.Clear();
  mDefendTarget.Clear();

  mpOwner = NULL;
}

void cAi::Init(cGameObject *pOwner)
{
  mpOwner = pOwner;
  mGoalQueue.Init(this);

  mNumHits = 0;
  mbTargetWasAttacking = false;
  mTargetBlockTimer = 0.0f;
}

bool cAi::IsOutsideDefendRadius()
{
  cGameObject* pDefendTarget = GetDefendTarget();

  if (pDefendTarget != NULL)
  {
    float defendRadiusSqr = GetDefendRadius();
    defendRadiusSqr *= defendRadiusSqr;

    if (pDefendTarget->GetLocation().DistanceSquared(mpOwner->GetLocation())
        > defendRadiusSqr)
    {
      return true;
    }
  }

  return false;
}

const char* cAi::GetCurrentGoalName()
{
  const char* goalName = "None";
  if (mGoalQueue.GetCurrentGoal())
  {
    switch(mGoalQueue.GetCurrentGoal()->GetType())
    {
      case GOAL_ATTACK: goalName = "Attack"; break;
      case GOAL_GOTO: goalName = "Goto"; break;
      case GOAL_PATROL: goalName = "Patrol"; break;
      case GOAL_STAND: goalName = "Stand"; break;
      case GOAL_FLEE: goalName = "Flee"; break;
      case GOAL_WANDER: goalName = "Wander"; break;
      case GOAL_FOLLOW: goalName = "Follow"; break;
      case GOAL_HUNT: goalName = "Hunt"; break;
      case GOAL_CAST: goalName = "Cast"; break;
      case GOAL_DEFEND: goalName = "Defend"; break;
      case GOAL_ACTIVATEPROP: goalName = "Activate Prop"; break;
      case GOAL_ONFIRE: goalName = "On Fire"; break;
      case GOAL_SPAWN: goalName = "Spawn"; break;
      default: goalName = "No Name"; break;
    }
  }

  return goalName;
}

void cAi::Update(float time)
{
  bool bIsCutScene = !mpOwner->GetTitan()->AllowCutSceneControllerMotion();

  mAttackTarget.Update(mpOwner, time);
  mDefendTarget.Update(mpOwner, time);

  if (!bIsCutScene)
  {
    CheckForIncomingMeleeAttack(time);
    CheckForIncomingProjectiles();
    CheckForNegativeAreaEffects();
  }

  mGoalQueue.Update(time, bIsCutScene);

  if (bIsCutScene && 
      (!mGoalQueue.GetCurrentGoal() || !mGoalQueue.GetCurrentGoal()->GetCutSceneFlag()))
  {
    static_cast<cIntelNPC*>(mpOwner->GetIntel())->Stop();
  }

  if (!bIsCutScene)
  {
    // if health low, perform low health ability
    cStatsCharacter* pStats = static_cast<cStatsCharacter*>(mpOwner->GetStats());
    float healthPercent = ((float)pStats->GetHealth())/((float)pStats->CalcMaxHealth());

    if (healthPercent < 0.25f &&
        mLastHealthPercent >= 0.25f)
    {
      const cStatsAbilitySet* pAbilitySet = pStats->GetAbilitySet();

      if (pAbilitySet)
      {
        tGameID curAbilityID = ID_NONE;
        tGameID nextAbilityID = ID_NONE;
        if (pAbilitySet->GetAbility(cStatsAbilitySet::ABILITY_CONDITION_LOW_HEALTH, curAbilityID, &nextAbilityID))
        {
          SyAssert(ID_NONE != nextAbilityID);

          const cAbilityMaster* pAbility = pStats->GetAbility(nextAbilityID);
          GAME_ASSERT(ERROR_DESIGN, pAbility!=NULL, "Could not find low health ability for NPC");
          GAME_ASSERT(ERROR_DESIGN, pAbility->mSpell!=ID_NONE, "Low health ability for ability set '%s' must have spell", pAbilitySet->mID.GetName());

          if (pAbility && ID_NONE != pAbility->mSpell)
          {
            CastSpell(mGoalQueue.GetHighestPriority()+1, pAbility->mSpell, mpOwner->GetID());
          }
        }
      }
    }

    mLastHealthPercent = healthPercent;

    if (mAbilityAllyWoundedTimer > 0.0f)
    {
      mAbilityAllyWoundedTimer -= time;
    }

    if (mAbilityAllyDeadTimer > 0.0f)
    {
      mAbilityAllyDeadTimer -= time;
    }

    if (mAbilityAllyBuffTimer > 0.0f)
    {
      mAbilityAllyBuffTimer -= time;
    }
  }

#ifdef _DRAWDEBUGOVERLAY

  int priority = -1;
  if (mGoalQueue.GetCurrentGoal())
  {
    priority = mGoalQueue.GetCurrentGoal()->GetPriority();
  }
  DEBUGOVERLAY_DRAWWORLDTEXT(mpOwner->GetID(), "AI", mpOwner->GetLocation()+SyVect3(0.0f, 2.0f, 0.0f), ("Goal: %s (%d)", GetCurrentGoalName(), priority), cDebugOverlay::PURPLE);

  if (mpOwner->GetName())
  {
    DEBUGOVERLAY_DRAWWORLDTEXT(mpOwner->GetID(), "AI", mpOwner->GetLocation()+SyVect3(0.0f, 2.3f, 0.0f), ("Name: %s", mpOwner->GetName()), cDebugOverlay::PURPLE);
  }
#endif
}

void cAi::UpdateLOD(float time)
{
  if (!mpOwner->GetTitan()->AllowCutSceneControllerMotion())
  {
    return;
  }

  FindTarget(false);
  mNearbyAllies.Update(mpOwner);

  // if health low, perform low health ability  
  cStatsCharacter* pStats = static_cast<cStatsCharacter*>(mpOwner->GetStats());
  const cStatsAbilitySet* pAbilitySet = pStats->GetAbilitySet();

  if (pAbilitySet)
  {
    const cAbilityMaster* pAbility;
    tGameID allyWoundedAbilityID = ID_NONE;
    tGameID allyDeadAbilityID = ID_NONE;
    tGameID allyBuffAbilityID = ID_NONE;
    int curPriority = mGoalQueue.GetHighestPriority();

    if (mAbilityAllyDeadTimer < 0.00001f)
    {
      pAbilitySet->GetAbility(cStatsAbilitySet::ABILITY_CONDITION_ALLY_DEAD, ID_NONE, &allyDeadAbilityID);
    }

    if (mAbilityAllyWoundedTimer < 0.00001f)
    {
      pAbilitySet->GetAbility(cStatsAbilitySet::ABILITY_CONDITION_ALLY_WOUNDED, ID_NONE, &allyWoundedAbilityID);
    }

    if (mAbilityAllyBuffTimer < 0.00001f)
    {
      pAbilitySet->GetAbility(cStatsAbilitySet::ABILITY_CONDITION_BUFF_ALLY_IN_COMBAT, ID_NONE, &allyBuffAbilityID);
    }

    if (ID_NONE != allyDeadAbilityID || ID_NONE != allyWoundedAbilityID || ID_NONE != allyBuffAbilityID)
    {
      for (int i=mNearbyAllies.mAllies.Begin(); i!=mNearbyAllies.mAllies.End(); i=mNearbyAllies.mAllies.Next(i))
      {
        if (ID_NONE != allyDeadAbilityID &&
            mNearbyAllies.mAllies(i).mbDied &&
            mAbilityAllyDeadTimer < 0.00001f)
        {
          pAbility = pStats->GetAbility(allyDeadAbilityID);
          GAME_ASSERT(ERROR_DESIGN, pAbility!=NULL, "Could not find ally dead ability for NPC");
          GAME_ASSERT(ERROR_DESIGN, pAbility->mSpell!=ID_NONE, "Ally dead ability for ability set '%s' must have spell", pAbilitySet->mID.GetName());

          if (pAbility && ID_NONE != pAbility->mSpell)
          {
            CastSpell(curPriority+10, pAbility->mSpell, mNearbyAllies.mAllies(i).mID);
            pAbilitySet->GetDelay(allyDeadAbilityID, &mAbilityAllyDeadTimer);
          }

          mNearbyAllies.mAllies(i).mbDied = false; // tried our death ability on this ally, so ignore him now
        }
        else if (ID_NONE != allyWoundedAbilityID &&
                 mNearbyAllies.mAllies(i).mbWounded &&
                 mAbilityAllyWoundedTimer < 0.00001f)
        {
          pAbility = pStats->GetAbility(allyWoundedAbilityID);
          GAME_ASSERT(ERROR_DESIGN, pAbility!=NULL, "Could not find ally wounded ability for NPC");
          GAME_ASSERT(ERROR_DESIGN, pAbility->mSpell!=ID_NONE, "Ally wounded ability for ability set '%s' must have spell", pAbilitySet->mID.GetName());

          if (pAbility && ID_NONE != pAbility->mSpell)
          {
            CastSpell(curPriority+5, pAbility->mSpell, mNearbyAllies.mAllies(i).mID);
            pAbilitySet->GetDelay(allyWoundedAbilityID, &mAbilityAllyWoundedTimer);
          }
        }
        else if (ID_NONE != allyBuffAbilityID && 
                 !mNearbyAllies.mAllies(i).mbBuffed &&
                 !mNearbyAllies.mAllies(i).mbDied &&
                 mAbilityAllyBuffTimer < 0.00001f &&
                 GetAttackTarget() != NULL)
        {
          pAbility = pStats->GetAbility(allyBuffAbilityID);
          GAME_ASSERT(ERROR_DESIGN, pAbility!=NULL, "Could not find ally buff ability for NPC");
          GAME_ASSERT(ERROR_DESIGN, pAbility->mSpell!=ID_NONE, "Ally buff ability for ability set '%s' must have spell", pAbilitySet->mID.GetName());

          if (pAbility && ID_NONE != pAbility->mSpell)
          {
            CastSpell(curPriority+1, pAbility->mSpell, mNearbyAllies.mAllies(i).mID);
            pAbilitySet->GetDelay(allyBuffAbilityID, &mAbilityAllyBuffTimer);
            mNearbyAllies.mAllies(i).mbBuffed = true;
          }
        }
      }
    }
  }
}

void cAi::FindTarget(bool bForceNewTarget)
{
  if (mpOwner->GetStats()->IsDead())
  {
    return;
  }

  cStatsCharacter* pMyStats = static_cast<cStatsCharacter*>(mpOwner->GetStats());

  // only try to find targets if we're not neutral
  if (pMyStats->GetFaction() == NPCFACTION_NEUTRAL)
  {
    return;
  }

  if (IsOutsideDefendRadius())
  {
    // no new targets, must get inside defend radius
    return;
  }

  float testRadius = GetActivationRadius();

  if (IsOnCamera())
  {
    testRadius = SY_MIN(40.0f, testRadius*1.25f);
  }

  // find targetable enemies
  cGameObject* pClosestObj = static_cast<cIntelNPC*>(mpOwner->GetIntel())->FindClosestObject(0.0f, testRadius, true, true, false, false);
  SyVect3 myLoc(mpOwner->GetLocation());

  if (bForceNewTarget && GetAttackTarget() != NULL)
  {
    if (pClosestObj == GetAttackTarget())
    {
      pClosestObj = static_cast<cIntelNPC*>(mpOwner->GetIntel())->FindClosestObject(myLoc.Distance(pClosestObj->GetLocation())+0.01f, testRadius, true, true, false, false);
    }
  }

  bool bTargetMe = false;

  // if we are a melee attacker, check if this target has too many
  // melee attackers already (but only if we don't have a target already
  if (pClosestObj)
  {
    bTargetMe = pClosestObj->GetStats()->QueryFlag("Target Me");

    if (!GetAttackTarget() && !bTargetMe && IsMeleeAttacker())
    {
      if (cAIBlackboard::Get()->CountRecords(cAIBlackboard::BBR_ATTACKING_MELEE, ID_NONE, pClosestObj->GetID()) > 3)
      {
        cGameObject* pNextClosestObj = static_cast<cIntelNPC*>(mpOwner->GetIntel())->FindClosestObject(myLoc.Distance(pClosestObj->GetLocation())+0.01f, testRadius, true, true, false, false);
        if (pNextClosestObj)
        {
          pClosestObj = pNextClosestObj;
        }
      }
    }
  }

  // if we don't already have a target, test nearby friendly melee attackers 
  // if they need help
  if (!pClosestObj)
  {
    static const uint64 CALLFORHELP_DELAY = 1000;
    cAIBlackboard::Record rec;
    bool bCallForHelp = false;
    cGameObject* pAttacker;
    float callForHelpRadiusSqr = (GetActivationRadius()*0.75f)*(GetActivationRadius()*0.75f);
    uint64 curTime = mpOwner->GetTitan()->GetTime();

    if (cAIBlackboard::Get()->GetFirstRecord(cAIBlackboard::BBR_ATTACKING_MELEE, ID_NONE, ID_NONE, rec))
    {
      do
      {
        if ((curTime - rec.mCreatedTimestamp > CALLFORHELP_DELAY) &&
            (myLoc.DistanceSquared(rec.mLocation) < callForHelpRadiusSqr))
        {
          pAttacker = mpOwner->GetRegistry()->Fetch(rec.mSourceID);
          if (pAttacker && mpOwner->GetIntel()->IsFriendly(pAttacker))
          {
            bCallForHelp = true;
            pClosestObj = mpOwner->GetRegistry()->Fetch(rec.mTargetID);
          }
        }
      }
      while (!bCallForHelp && cAIBlackboard::Get()->GetNextRecord(rec));
    }

    if (!bCallForHelp && cAIBlackboard::Get()->GetFirstRecord(cAIBlackboard::BBR_ATTACKING_RANGED, ID_NONE, ID_NONE, rec))
    {
      do
      {
        if ((curTime - rec.mCreatedTimestamp > CALLFORHELP_DELAY) &&
            (myLoc.DistanceSquared(rec.mLocation) < callForHelpRadiusSqr))
        {
          pAttacker = mpOwner->GetRegistry()->Fetch(rec.mSourceID);
          if (pAttacker && mpOwner->GetIntel()->IsFriendly(pAttacker))
          {
            bCallForHelp = true;
            pClosestObj = mpOwner->GetRegistry()->Fetch(rec.mTargetID);
          }
        }
      }
      while (!bCallForHelp && cAIBlackboard::Get()->GetNextRecord(rec));
    }
  }

  if (pClosestObj && 
      (GetAttackTarget() != pClosestObj || !mGoalQueue.FindGoalType(GOAL_ATTACK)))
  {
    float distSqr = myLoc.DistanceSquared(pClosestObj->GetLocation());
    SyVect3 oldTargetLoc;
    bool bHasTargetPos = GetLastSeenAttackTargetPos(oldTargetLoc);

    // attack closest target if we're forced to, we have no prev target or 
    // it's more than half the distance closer
    if (bForceNewTarget ||
        bTargetMe ||
        GetAttackTarget() == pClosestObj ||
        !bHasTargetPos ||
        myLoc.DistanceSquared(oldTargetLoc) > (distSqr+(GetActivationRadius()*GetActivationRadius()*0.25f)))
    { 
      if (!GetDefendTarget() ||
          !IsMeleeAttacker() ||
          GetDefendTarget()->GetDistance(pClosestObj) < GetDefendRadius())
      {
        SetAttackTarget(pClosestObj->GetID());
        AttackTarget(10); // then attack
        return;
      }
    }
  }

  // no target, check if we should return to our start pos
  if (pMyStats->GetAIBehaviorType() != NPCBEH_NONE && 
      pMyStats->GetAIBehaviorType() != NPCBEH_SENTINEL &&
      !GetAttackTarget())
  {
    if (static_cast<cIntelNPC*>(mpOwner->GetIntel())->GetStartLocation().Distance(myLoc) > 5.0f)
    {
      GoTo(0, static_cast<cIntelNPC*>(mpOwner->GetIntel())->GetStartLocation());
    }
  }
}

void cAi::SetAttackTarget(tGameObjectID targetID)
{
  mAttackTarget.SetTarget(targetID);
  mbTargetWasAttacking = false;
  mTargetBlockTimer = 0.0f;
}

cGameObject* cAi::GetAttackTarget()
{
  return mAttackTarget.GetTarget(mpOwner);
}

bool cAi::CanSeeAttackTarget()
{
  return mAttackTarget.CanSeeTarget();
}

bool cAi::HasLOSToAttackTarget()
{
  return mAttackTarget.HasLOS();
}

bool cAi::GetLastSeenAttackTargetPos(SyVect3& pos)
{
  return mAttackTarget.GetLastSeenTargetPos(pos);
}


void cAi::SetDefendTarget(tGameObjectID targetID)
{
  mDefendTarget.SetTarget(targetID);

  GAME_ASSERT(ERROR_CODE, !mDefendTarget.GetTarget(mpOwner) || (mDefendTarget.GetTarget(mpOwner)->GetType() == cGameObject::OBJ_PROP) || static_cast<cIntelNPC*>(mpOwner->GetIntel())->IsFriendly(mDefendTarget.GetTarget(mpOwner)), "Follow/Defend target is not a friendly NPC or prop");
}

cGameObject* cAi::GetDefendTarget()
{
  return mDefendTarget.GetTarget(mpOwner);
}

bool cAi::CanSeeDefendTarget()
{
  return mDefendTarget.CanSeeTarget();
}

bool cAi::GetLastSeenDefendTargetPos(SyVect3& pos)
{
  return mDefendTarget.GetLastSeenTargetPos(pos);
}


void cAi::Pop(cAiGoal *goal)
{
  mGoalQueue.Pop(goal);
}

void cAi::ChangePriority(cAiGoal *pGoal, int priority)
{
  mGoalQueue.ChangePriority(pGoal, priority);
}

void cAi::Reset()
{
  mNumHits = 0;
  
  mAttackTarget.CancelLOSRequest();
  mDefendTarget.CancelLOSRequest();
  cAIBlackboard::Get()->RemoveRecords(cAIBlackboard::BBR_INVALID, mpOwner->GetID());
}

void cAi::CheckForIncomingMeleeAttack(float time)
{
  static const float BLOCK_FOV_COS = SY_COS(SY_DEG_TO_RAD((270.0f*0.5f)));
  static const float ATTACK_FOV_COS = SY_COS(SY_DEG_TO_RAD((180.0f*0.5f)));
  static const float MAX_BLOCK_RANGE = 5.0f;

  cGameObject* pTarget = GetAttackTarget();

  if (!pTarget)
  {
    return;
  }

  SyAssert(prop_cast<cStatsCharacter*>(mpOwner->GetStats())!=NULL);
  SyAssert(prop_cast<cGraphicCharacter*>(mpOwner->GetGraphic())!=NULL);
  SyAssert(prop_cast<cIntelNPC*>(mpOwner->GetIntel())!=NULL);

  cIntelNPC *pIntel = static_cast<cIntelNPC*>(mpOwner->GetIntel());
  cStatsCharacter *pStats = static_cast<cStatsCharacter*>(mpOwner->GetStats());
  const cStatsBlockBehavior* pBlockBehavior = pStats->GetBlockBehavior();

  cAnimCharControllerInterface* pAnimCtrlr = static_cast<cGraphicCharacter*>(mpOwner->GetGraphic())->GetAnimController();
  bool bIsBlocking = pAnimCtrlr->IsBlocking() || pAnimCtrlr->IsDodging();

  cAnimCharControllerInterface *pTargetAnimCtrlr = prop_cast<cGraphicCharacter*>(pTarget->GetGraphic())->GetAnimController();

  bool bTargetStartedAttack = false;
  bool bTargetStoppedAttack = false;
  bool bTargetIsAttacking = pTargetAnimCtrlr->IsAttacking() && pTargetAnimCtrlr->IsAnimStateActive();

  bool bLightAttack = pTargetAnimCtrlr->IsLightCombo(pTargetAnimCtrlr->GetComboType());

  int blockAttackType = bLightAttack ? cStatsBlockBehavior::BLOCK_ATTACK_LIGHT:cStatsBlockBehavior::BLOCK_ATTACK_HEAVY;

  if (mbTargetWasAttacking && !bTargetIsAttacking)
  {
    bTargetStoppedAttack = true;
  }

  if (!mbTargetWasAttacking &&
      bTargetIsAttacking)
  {
    if (pTargetAnimCtrlr->GetAnimTime() >= pBlockBehavior->mBlockReactTime)
    {
      bTargetStartedAttack = true;
    }
    else
    {
      mbTargetWasAttacking = false;
    }
  }
  else
  {
    mbTargetWasAttacking = bTargetIsAttacking;
  }

  if (bIsBlocking && bTargetStoppedAttack &&
      mpOwner->GetTitan()->Random(1, 100) <= pBlockBehavior->mRipostePercentChance[blockAttackType])
  {
    // test for counter attack

    eComboType targetCombo = pTargetAnimCtrlr->GetComboType();
    float range = 0.0f;
    if (NUM_COMBOS == targetCombo && pTargetAnimCtrlr->IsAttacking())
    {
      range = pTargetAnimCtrlr->GetRange(COMBO_L);
    }
    else
    {
      range = pTargetAnimCtrlr->GetRange(targetCombo);
    }

    if (mpOwner->GetDistance(pTarget) <= range)
    {
      pIntel->Block(0.0f);
    }
  }
  else if (!bIsBlocking && bTargetStartedAttack)
  {
    SyVect3 toTarget(pTarget->GetLocation()-mpOwner->GetLocation()), myDir;
    myDir.HPR(mpOwner->GetHeading(), 0.0f, 0.0f);
    bool bDetectedAttack = false;

    if (toTarget.Y < 10.0f)
    {
      toTarget.Y = 0.0f;
      float dist = toTarget.NormalizeMagn();

      if (dist < MAX_BLOCK_RANGE)
      {
        SyVect3 targetFacing;
        targetFacing.HPR(pTarget->GetHeading(), 0.0f, 0.0f);

        if ((myDir^toTarget)>=BLOCK_FOV_COS &&
          (targetFacing ^(-toTarget)) >= ATTACK_FOV_COS)
        {
          bDetectedAttack = true;
        }
      }
    }

    if (bDetectedAttack)
    {
      SyVect3 right;
      right.Cross(myDir, SyVect3(0.0f, 1.0f, 0.0f));
      right.Normalize();
      if ((right ^ toTarget) > 0.0f)
      {
        right = -right;
      }
      
      if (!DodgeOpportunity(blockAttackType, right) &&
          cStatsBlockBehavior::NPCBS_NEXT_HIT == pBlockBehavior->mNPCBlockStrategy &&
          mNumHits >= pBlockBehavior->mNumHitsBeforeBlock)
      {
        BlockOpportunity(blockAttackType);
      }
    }
  }

  // now test to see if target has been blocking for too long
  static const float LONG_PLAYER_BLOCK_TIME = 4.0f;

  if (pTargetAnimCtrlr->IsBlocking())
  {
    mTargetBlockTimer += time;
  }
  else
  {
    mTargetBlockTimer = 0.0f;
  }

  if (mTargetBlockTimer > LONG_PLAYER_BLOCK_TIME)
  {
    switch (pBlockBehavior->mPlayerBlockStrategy)
    {
      case cStatsBlockBehavior::PBS_IGNORE:
        break;

      case cStatsBlockBehavior::PBS_SWITCH_TARGETS:
        FindTarget(true);
        break;

      case cStatsBlockBehavior::PBS_PULL_BACK:
        Flee(mGoalQueue.GetHighestPriority()+1, pTarget->GetID(), LONG_PLAYER_BLOCK_TIME);
        break;

      case cStatsBlockBehavior::PBS_CIRCLE:
        Flee(mGoalQueue.GetHighestPriority()+1, pTarget->GetID(), LONG_PLAYER_BLOCK_TIME);
        break;

      case cStatsBlockBehavior::PBS_TRY_DIFFERENT_ATTACK:
      {
        cGoal_Attack* pGoal = static_cast<cGoal_Attack*>(mGoalQueue.FindGoalType(GOAL_ATTACK));

        if (pGoal)
        {
          pGoal->BreakTargetBlock();
        }

        break;
      }

      default:
        GAME_ASSERT(ERROR_CODE, false, "Bad Player block strategy");
    }
  }
}

void cAi::CheckForIncomingProjectiles()
{
  static const float INCOMING_PROJECTILE_DODGE_DOT = 0.98f;
  static const float INCOMING_PROJECTILE_FOV_DOT = 0.1f;
  static const float INCOMING_PROJECTILE_MAX_HEIGHT_DELTA = 10.0f;
  static const float INCOMING_PROJECTILE_FROMBEHIND_NEAR = 3.0f;

  SyAssert(mpOwner->GetRegistry()!=NULL);
  SyAssert(prop_cast<cGraphicCharacter*>(mpOwner->GetGraphic())!=NULL);
  SyAssert(prop_cast<cStatsCharacter*>(mpOwner->GetStats())!=NULL);

  cStatsCharacter *pStats = static_cast<cStatsCharacter*>(mpOwner->GetStats());
  const cStatsBlockBehavior* pBlockBehavior = pStats->GetBlockBehavior();

  cAnimCharControllerInterface* pAnimCtrlr = static_cast<cGraphicCharacter*>(mpOwner->GetGraphic())->GetAnimController();
  bool bIsBlocking = pAnimCtrlr->IsBlocking() || pAnimCtrlr->IsDodging();

  if (bIsBlocking || !pBlockBehavior)
  {
    return;
  }

  cGameObjectRegistry* pRegistry = mpOwner->GetRegistry();


  bool bProjFromBehind = false;
  SyVect3 toProj, projDir, dodge, fromBehindDir;
  float distToProj, dotProjDir;
  SyVect3 myLoc(mpOwner->GetLocation()), myDir;
  myDir.HPR(mpOwner->GetHeading(), 0.0f, 0.0f);

  cGameObject* pProjectile = pRegistry->BeginType(cGameObject::OBJ_PROJECTILE);
  for(;pProjectile != NULL;pProjectile= pRegistry->NextType(pProjectile))
  {

    SyAssert(pProjectile->GetType() == cGameObject::OBJ_PROJECTILE);
    if (!pProjectile ||
        pProjectile->GetStats()->IsDead() ||
        pProjectile->GetID() == mLastDodgedProjectileID)
    {
      continue;
    }

    toProj = pProjectile->GetLocation() - myLoc;

    SyAssertf(pProjectile->GetStats()->DynamicCast(STATSPROJECTILE_CLASSID) != NULL, "Bad obj type or stats type in cAi::CheckForIncomingProjectiles: %d", pProjectile->GetID());
    SyAssertf(static_cast<cStatsProjectile*>(pProjectile->GetStats())->GetMaster() != NULL, "Projectile has no master stats in cAi::CheckForIncomingProjectiles: %d", pProjectile->GetID());

    if (static_cast<cStatsProjectile*>(pProjectile->GetStats())->GetMaster()->mGravity > 0.0f)
    {
      // don't consider elevation of projectile if it's falling on us
      toProj.Y = 0;
    }
    else if (toProj.Y > INCOMING_PROJECTILE_MAX_HEIGHT_DELTA)
    {
      continue;
    }

    distToProj = toProj.NormalizeMagn();
    projDir = pProjectile->GetPhysics()->GetVelocity();

    // do we have enough time to react to it?
    if (distToProj > projDir.Magnitude()*pBlockBehavior->mBlockReactTime)
    {
      continue;
    }

    if ((myDir^toProj) < INCOMING_PROJECTILE_FOV_DOT)
    {
      continue;
    }

    projDir.Y = 0.0f;
    projDir.Normalize();
    dotProjDir = toProj^projDir;
    if (dotProjDir > -INCOMING_PROJECTILE_DODGE_DOT)
    {
      if (distToProj <= INCOMING_PROJECTILE_FROMBEHIND_NEAR)
      {
        bProjFromBehind = true;
        fromBehindDir = projDir;
      }

      continue;
    }

    // found incoming
    dodge.Cross(myDir, SyVect3(0.0f, 1.0f, 0.0f));

    if ((dodge ^ toProj) >= 0.0f)
    {
      dodge = -dodge;
    }

    if (!DodgeOpportunity(cStatsBlockBehavior::BLOCK_ATTACK_RANGED, dodge) &&
        cStatsBlockBehavior::NPCBS_NEXT_HIT == pBlockBehavior->mNPCBlockStrategy &&
        mNumHits >= pBlockBehavior->mNumHitsBeforeBlock)
    {
      BlockOpportunity(cStatsBlockBehavior::BLOCK_ATTACK_RANGED);
    }

    mLastDodgedProjectileID = pProjectile->GetID();

    if (GetAttackTarget()==NULL && mGoalQueue.GetHighestPriority() <= 1)
    {
      // if we're not doing anything hunt them down, but only if it's not a trap that shot it
      projDir *= 10.0f;
      tGameObjectID sourceID = static_cast<cPhysicsProjectile*>(pProjectile->GetPhysics())->GetSourceID();
      cGameObject* pSource = pRegistry->Fetch(sourceID);
      if (pSource &&
          (pSource->GetType() == cGameObject::OBJ_NPC || pSource->GetType() == cGameObject::OBJ_PLAYER) &&
          mpOwner->GetIntel()->IsTargetable(pSource))
      {
        SetAttackTarget(sourceID);
        AttackTarget(9);
        Hunt(10, sourceID, myLoc-projDir);
      }
    }

    return;
  }

  if (bProjFromBehind)
  {
    if (GetAttackTarget()==NULL && mGoalQueue.GetHighestPriority() <= 1)
    {
      static_cast<cIntelNPC*>(mpOwner->GetIntel())->TurnTo(myLoc-fromBehindDir);
    }
  }
}

void cAi::CheckForNegativeAreaEffects()
{
  cAIBlackboard::Record rec;
  SyVect3 myLoc = mpOwner->GetLocation();
  tGameObjectID myID = mpOwner->GetID();
  cGameObjectRegistry* pRegistry = mpOwner->GetRegistry();
  cGameObject* pSourceObj = NULL;
  
  bool bFoundNegativeAreaEffect = false;
  SyVect3 effectLocation(0.0f, 0.0f, 0.0f);
  float effectRadius = 0.0f;

  if (cAIBlackboard::Get()->GetFirstRecord(cAIBlackboard::BBR_NEGATIVE_AREAEFFECT, ID_NONE, ID_NONE, rec))
  {
    do
    {
      if (rec.mSourceID != myID)
      {
        pSourceObj = NULL;
        if (rec.mSourceID != ID_NONE)
        {
          pSourceObj = pRegistry->Fetch(rec.mSourceID);
        }

        effectRadius = (float)rec.mData;
        if (rec.mLocation.DistanceSquared(myLoc) < effectRadius*effectRadius)
        {
          bFoundNegativeAreaEffect = true;
          effectLocation = rec.mLocation;
        }
      }
    }
    while (!bFoundNegativeAreaEffect && cAIBlackboard::Get()->GetNextRecord(rec));
  }

  if (bFoundNegativeAreaEffect)
  {
    if (pSourceObj != NULL )
    {
      Flee(mGoalQueue.GetHighestPriority()+1, pSourceObj->GetID(), 0.0f, effectRadius+2.0f);
    }
    else
    {
      Flee(mGoalQueue.GetHighestPriority()+1, effectLocation, 0.0f, effectRadius+2.0f);
    }
  }
}

void cAi::OnHit(tGameObjectID attacker, int damage, float recoveryTime, bool bRanged)
{
  cGameObject* pAttackerObj = mpOwner->GetRegistry()->Fetch(attacker);

  if (!pAttackerObj || mpOwner->GetIntel()->IsFriendly(pAttackerObj))
  {
    return;
  }

  // see if we should block
  SyAssert(prop_cast<cStatsCharacter*>(mpOwner->GetStats())!=NULL);
  SyAssert(prop_cast<cGraphicCharacter*>(mpOwner->GetGraphic())!=NULL);
  SyAssert(prop_cast<cIntelNPC*>(mpOwner->GetIntel())!=NULL);

  cIntelNPC *pIntel = static_cast<cIntelNPC*>(mpOwner->GetIntel());
  cStatsCharacter *pStats = static_cast<cStatsCharacter*>(mpOwner->GetStats());
  const cStatsBlockBehavior* pBlockBehavior = pStats->GetBlockBehavior();
  cAnimCharControllerInterface* pAnimCtrlr = static_cast<cGraphicCharacter*>(mpOwner->GetGraphic())->GetAnimController();

  int blockAttackType = bRanged ? cStatsBlockBehavior::BLOCK_ATTACK_RANGED : cStatsBlockBehavior::BLOCK_ATTACK_LIGHT;

  cGraphicCharacter* pAttackerGraphic = prop_cast<cGraphicCharacter*>(pAttackerObj->GetGraphic());
  if (pAttackerGraphic &&
      cAnimCharControllerInterface::IsHeavyCombo(pAttackerGraphic->GetAnimController()->GetComboType()))
  {
    blockAttackType = cStatsBlockBehavior::BLOCK_ATTACK_HEAVY;
  }

  if (pAnimCtrlr->IsBlocking())
  {
    // if we were blocking, keep on truckin'
    pIntel->Block(pBlockBehavior->mBlockHoldTime);
  }
  else
  {
    ++mNumHits;

    if (mNumHits >= pBlockBehavior->mNumHitsBeforeBlock &&
        (cStatsBlockBehavior::NPCBS_AFTER_HIT == pBlockBehavior->mNPCBlockStrategy))
    {
      BlockOpportunity(blockAttackType);
    }
  }

  // see if we should attack new target
  if (GetAttackTarget()==NULL && mGoalQueue.GetHighestPriority() <= 1)
  {
    // if we're not doing anything hunt them down, but only if it's not a trap that shot it
    if (pAttackerObj && (pAttackerObj->GetType() == cGameObject::OBJ_NPC || pAttackerObj->GetType() == cGameObject::OBJ_PLAYER))
    {
      SetAttackTarget(attacker);
      AttackTarget(9);
    }
  }
}

void cAi::AttackTarget(int priority)
{
  GAME_ASSERT(ERROR_CODE, ID_NONE != mAttackTarget.mTargetID, "cAi::AttackTarget - must call SetTarget before AttackTarget");

  if (ID_NONE == mAttackTarget.mTargetID)
  {
    return;
  }

  cGameObject *pTarget = mAttackTarget.GetTarget(mpOwner); 

  if (pTarget == NULL)
  {
    return;
  }

  bool bTargetIsProp = pTarget->GetType() == cGameObject::OBJ_PROP;

  cAiGoal* pGoal = mGoalQueue.FindGoalType(GOAL_ATTACK);
  if (NULL != pGoal)
  {
    if (static_cast<cGoal_Attack*>(pGoal)->GetTarget() == pTarget)
    {
      if (priority > pGoal->GetPriority())
      {
        mGoalQueue.ChangePriority(pGoal, priority);
      }

      return;
    }
    else
    {
      cGoal_Hunt* pHuntGoal = static_cast<cGoal_Hunt*>(mGoalQueue.FindGoalType(GOAL_HUNT));
      if (pHuntGoal && pHuntGoal->GetTargetID() == pTarget->GetID())
      {
        Pop(pHuntGoal);
      }

      Pop(pGoal);
      pGoal = NULL;
    }
  }
  else if (!bTargetIsProp)
  {
    cAiGoal* pGoal = mGoalQueue.FindGoalType(GOAL_CAST);
    if (NULL != pGoal)
    {
      if (priority > pGoal->GetPriority())
      {
        mGoalQueue.ChangePriority(pGoal, priority);
      }

      return;
    }
  }

  if (bTargetIsProp)
  {
    pGoal = SyNew cGoal_DestroyProp(pTarget->GetID());
  }
  else
  {
    char behType = static_cast<cStatsCharacter*>(mpOwner->GetStats())->GetAIBehaviorType();

    switch (behType)
    {
      case NPCBEH_NONE:
      case NPCBEH_RUN:
      case NPCBEH_CAST:
        if (IsAreaEffectCaster())
        {
          pGoal = SyNew cGoal_AttackArea(pTarget->GetID());
        }
        break;

      case NPCBEH_BASIC:
        if (IsAreaEffectCaster())
        {
          pGoal = SyNew cGoal_AttackArea(pTarget->GetID());
        }
        else
        {
          pGoal = SyNew cGoal_Attack(pTarget->GetID());
        }
        break;

      case NPCBEH_LINE:
        pGoal = SyNew cGoal_AttackLine(pTarget->GetID());
        break;

      case NPCBEH_STUN:
        pGoal = SyNew cGoal_AttackStun(pTarget->GetID());
        break;

      case NPCBEH_CIRCLE:
        pGoal = SyNew cGoal_AttackCircle(pTarget->GetID());
        break;

      case NPCBEH_SWARM:
        pGoal = SyNew cGoal_AttackSwarm(pTarget->GetID());
        break;

      case NPCBEH_FLANK:
        pGoal = SyNew cGoal_AttackFlank(pTarget->GetID());
        break;

      case NPCBEH_CHARGERETREAT:
        pGoal = SyNew cGoal_AttackChargeRetreat(pTarget->GetID());
        break;

      case NPCBEH_STEALTH:
        pGoal = SyNew cGoal_AttackStealth(pTarget->GetID());
        break;

      case NPCBEH_WANDER:
        pGoal = SyNew cGoal_AttackWander(pTarget->GetID());
        break;

      case NPCBEH_SENTINEL:
        pGoal = SyNew cGoal_AttackSentinel(pTarget->GetID());
        break;

      case NPCBEH_KAMIKAZE:
        pGoal = SyNew cGoal_Kamikaze(pTarget->GetID());
        break;

      case NPCBEH_MAXTYPES:
      default:
        GAME_ASSERT(ERROR_CODE, false, "Bad Behavior Type in cAI::AttackTarget");
        break;
    }
  }

  if (pGoal)
  {
    mGoalQueue.Push(priority, pGoal);
  }
}


void cAi::GoTo(int priority, const SyVect3 &destination, float speed)
{
  cAiGoal* pGoal = mGoalQueue.FindGoalType(GOAL_GOTO);

  if (NULL == pGoal)
  {
    cGoal_Goto *newgoal = SyNew cGoal_Goto(destination, speed);
    mGoalQueue.Push(priority,newgoal);
  }
}

void cAi::Wander(int priority, float time)
{
  cAiGoal* pGoal = mGoalQueue.FindGoalType(GOAL_WANDER);

  if (NULL == pGoal)
  {
    cGoal_Wander *newgoal = SyNew cGoal_Wander(time);
    mGoalQueue.Push(priority,newgoal);
  }
}

void cAi::WanderNear(int priority, const SyVect3& pos, float dist, float time)
{
  cAiGoal* pGoal = mGoalQueue.FindGoalType(GOAL_WANDER);

  if (NULL == pGoal)
  {
    cGoal_WanderNear *newgoal = SyNew cGoal_WanderNear(pos, dist, time);
    mGoalQueue.Push(priority,newgoal);
  }
}


void cAi::Follow(int priority, tGameObjectID targetID, float distance)
{
  cAiGoal* pGoal = mGoalQueue.FindGoalType(GOAL_FOLLOW);

  if (NULL == pGoal)
  {
    if (distance < 0.0001f)
    {
      distance = smFollowRadius;
    }

    cGoal_Follow *newgoal = SyNew cGoal_Follow(targetID, distance);
    mGoalQueue.Push(priority, newgoal);
  }
}

void cAi::Defend(int priority, tGameObjectID targetID, float distance)
{
  cAiGoal* pGoal = mGoalQueue.FindGoalType(GOAL_FOLLOW);

  if (NULL == pGoal)
  {
    if (distance < 0.0001f)
    {
      distance = smDefendRadius;
    }

    cGoal_Defend *newgoal = SyNew cGoal_Defend(targetID, distance);
    mGoalQueue.Push(priority, newgoal);
  }
}

void cAi::Hunt(int priority, tGameObjectID targetID, const SyVect3& startLoc)
{
  cAiGoal* pGoal = mGoalQueue.FindGoalType(GOAL_HUNT);

  if (NULL == pGoal)
  {
    cGoal_Hunt *newgoal = SyNew cGoal_Hunt(targetID, startLoc);
    mGoalQueue.Push(priority, newgoal);
  }
}

void cAi::ActivateProp(int priority, tGameObjectID propID)
{
  cAiGoal* pGoal = mGoalQueue.FindGoalType(GOAL_ACTIVATEPROP);

  if (NULL == pGoal)
  {
    cGoal_ActivateProp *newgoal = SyNew cGoal_ActivateProp(propID);
    mGoalQueue.Push(priority, newgoal);
  }
}

void cAi::OnFire(int priority)
{
  cAiGoal* pGoal = mGoalQueue.FindGoalType(GOAL_ONFIRE);

  if (NULL == pGoal)
  {
    cGoal_OnFire *newgoal = SyNew cGoal_OnFire();
    mGoalQueue.Push(priority, newgoal);
  }
  else
  {
    mGoalQueue.ChangePriority(pGoal, priority);
  }
}

void cAi::PatrolAddWaypoint(const SyVect3& markerLoc, float speed, float pauseTime)
{
  cAiGoal* pGoal = mGoalQueue.FindGoalType(GOAL_PATROL);

  if (!pGoal)
  {
    int priority = 1;  
    if (static_cast<cStatsCharacter*>(mpOwner->GetStats())->GetAIBehaviorType() == NPCBEH_RUN)
    {
      priority = 100;
    }

    pGoal = SyNew cGoal_Patrol();
    mGoalQueue.Push(priority, pGoal);
  }

  static_cast<cGoal_Patrol*>(pGoal)->AddWaypoint(markerLoc, speed, pauseTime);
}

void cAi::PatrolClearWaypoints()
{
  cAiGoal* pGoal = mGoalQueue.FindGoalType(GOAL_PATROL);

  if (NULL != pGoal)
  {
    static_cast<cGoal_Patrol*>(pGoal)->ClearWaypoints();
  }
}

void cAi::PatrolSetLooping(bool bLoop)
{
  cAiGoal* pGoal = mGoalQueue.FindGoalType(GOAL_PATROL);

  if (!pGoal)
  {
    int priority = 1;  
    if (static_cast<cStatsCharacter*>(mpOwner->GetStats())->GetAIBehaviorType() == NPCBEH_RUN)
    {
      priority = 100;
    }

    pGoal = SyNew cGoal_Patrol();
    mGoalQueue.Push(priority, pGoal);
  }

  static_cast<cGoal_Patrol*>(pGoal)->SetLooping(bLoop);
}

void cAi::PatrolAllowAttack(bool bAllow)
{
  cAiGoal* pGoal = mGoalQueue.FindGoalType(GOAL_PATROL);

  int priority = 1;  
  if (!bAllow)
  {
    priority = 100;
  }

  if (!pGoal)
  {
    pGoal = SyNew cGoal_Patrol();
    mGoalQueue.Push(priority, pGoal);
  }
  else
  {
    ChangePriority(pGoal, priority);
  }
}

void cAi::CastSpell(int priority, tGameID spellID, tGameObjectID targetID)
{
  cGoal_Cast *newgoal = SyNew cGoal_Cast(spellID, targetID);
  mGoalQueue.Push(priority, newgoal);
}

void cAi::CastSpell(int priority, tGameID spellID, const SyVect3& targetLoc)
{
  cAiGoal* pGoal = mGoalQueue.FindGoalType(GOAL_CAST);

  if (NULL == pGoal)
  {
    cGoal_Cast *newgoal = SyNew cGoal_Cast(spellID, targetLoc);
    mGoalQueue.Push(priority, newgoal);
  }
}

void            
cAi::Stand(int priority) 
{
  cAiGoal* pGoal = mGoalQueue.FindGoalType(GOAL_STAND);

  if (pGoal != NULL)
  {
    mGoalQueue.ChangePriority(pGoal, priority);
  }
  else
  {
    cGoal_Stand *newgoal = SyNew cGoal_Stand();
    mGoalQueue.Push(priority, newgoal);
  }
}

void            
cAi::Spawn(bool bStartImmediately)
{
  cAiGoal* pGoal = mGoalQueue.FindGoalType(GOAL_SPAWN);

  if (!pGoal)
  {
    pGoal = SyNew cGoal_Spawn();
    mGoalQueue.Push(100000, pGoal);
  }

  if (pGoal && bStartImmediately)
  {
    static_cast<cGoal_Spawn*>(pGoal)->Start();
  }
}

void cAi::Flee(int priority, tGameObjectID targetID, float time, float goalDistance) 
{
  cAiGoal* pGoal = mGoalQueue.FindGoalType(GOAL_FLEE);

  if (NULL == pGoal)
  {
    cGoal_Flee *newgoal = SyNew cGoal_Flee(targetID, time, goalDistance);
    mGoalQueue.Push(priority, newgoal);
  }
}

void cAi::Flee(int priority, const SyVect3& pos, float time, float goalDistance) 
{
  cAiGoal* pGoal = mGoalQueue.FindGoalType(GOAL_FLEE);

  if (NULL == pGoal)
  {
    cGoal_Flee *newgoal = SyNew cGoal_Flee(pos, time, goalDistance);
    mGoalQueue.Push(priority, newgoal);
  }
}

bool cAi::TestMaxClosestAttackers(cAIBlackboard::RecordType type,
                                  int maxAttackers,
                                  tGameObjectID targetID)
{
  cGameObject* pTarget = mpOwner->GetRegistry()->Fetch(targetID);

  GAME_ASSERT(ERROR_CODE, pTarget != NULL, "cAi::TestMaxClosestAttackers has invalid target %d", targetID);
  if (!pTarget)
  {
    return false;
  }

  static const float DIST_THRESHOLD = 0.01f;
  cAIBlackboard::Record rec;
  cGameObject* pAttacker;
  int numCloserAttackers = 0;

  SyVect3 targetLoc(pTarget->GetLocation());
  float distSqr = mpOwner->GetLocation().DistanceSquared(targetLoc);

  if (cAIBlackboard::Get()->GetFirstRecord(type, ID_NONE, targetID, rec))
  { 
    do
    {
      pAttacker = mpOwner->GetRegistry()->Fetch(rec.mSourceID);        
      if (pAttacker &&
          pAttacker != mpOwner &&
          pAttacker->GetLocation().DistanceSquared(targetLoc) < distSqr + DIST_THRESHOLD)
      {
        ++numCloserAttackers;
      }
    }
    while (cAIBlackboard::Get()->GetNextRecord(rec));
  }

  return numCloserAttackers < maxAttackers;
}

bool
cAi::IsOnCamera()
{
  SyCamera* pCam = mpOwner->GetTitan()->GetCameraController()->GetCamera();

  float maxCameraDistSqr = 50.0f*50.0f;

  if (pCam &&
      pCam->GetLocation().DistanceSquared(mpOwner->GetLocation()) < maxCameraDistSqr &&
      pCam->GetFrustum().Cull(mpOwner->GetLocation(), 1.0f) == 0)
  {
    return true;
  }

  return false;
}

bool cAi::IsPointInFOV(const SyVect3& point)
{
  static const float FOV_COS = 0.5f;

  SyVect3 toPoint(point - mpOwner->GetLocation());
  toPoint.Normalize();

  SyVect3 myDir;
  myDir.HPR(mpOwner->GetHeading(), 0.0f, 0.0f);

  return ((toPoint^myDir) >= FOV_COS);
}

bool cAi::IsRangedAttacker()
{
  cStatsCharacter* pStats = static_cast<cStatsCharacter*>(mpOwner->GetStats());
  const cStatsAbilitySet* pAbilitySet = pStats->GetAbilitySet();

  if (!pAbilitySet)
  {
    return false;
  }

  return pAbilitySet->HasAbilityForCondition(cStatsAbilitySet::ABILITY_CONDITION_RANGED_ATTACK);
}

bool cAi::IsMeleeAttacker()
{
  cStatsCharacter* pStats = static_cast<cStatsCharacter*>(mpOwner->GetStats());
  const cStatsAbilitySet* pAbilitySet = pStats->GetAbilitySet();

  if (!pAbilitySet)
  {
    return false;
  }

  return pAbilitySet->HasAbilityForCondition(cStatsAbilitySet::ABILITY_CONDITION_MELEE_ATTACK);
}

bool cAi::IsAreaEffectCaster()
{
  cStatsCharacter* pStats = static_cast<cStatsCharacter*>(mpOwner->GetStats());
  const cStatsAbilitySet* pAbilitySet = pStats->GetAbilitySet();

  if (!pAbilitySet)
  {
    return false;
  }

  const cAbilityMaster* pAbility;
  const cSpellMaster* pSpell;
  cDatabaseSys* pDB = mpOwner->GetTitan()->GetDatabaseSys();

  for (int i=pAbilitySet->mEntries.Begin(); i!=pAbilitySet->mEntries.End(); i=pAbilitySet->mEntries.Next(i))
  {
    if (cStatsAbilitySet::ABILITY_CONDITION_RANGED_ATTACK == pAbilitySet->mEntries(i).mCondition)
    {
      pAbility = pStats->GetAbility(pAbilitySet->mEntries(i).mAbilityID1);
      if (pAbility && ID_NONE != pAbility->mSpell)
      {
        pSpell = pDB->GetSpellMaster(pAbility->mSpell);

        if (pSpell && cSpellMaster::SPELL_ORIGIN_WARD != pSpell->mOrigin &&
            (cSpellMaster::SPELL_DELIVERY_RADIUS == pSpell->mDelivery ||
             cSpellMaster::SPELL_DELIVERY_NOVA == pSpell->mDelivery ||
             cSpellMaster::SPELL_DELIVERY_WALL == pSpell->mDelivery))
        {
          return true;
        }
      }
    }
  }

  return false;
}

void cAi::CalculateFleeDirection(cGameObject* pTarget, SyVect3& fleeDir)
{
  static const float NPC_RADIUS_SQR = 20.0f*20.0f;

  cGameObjectRegistry* pRegistry = mpOwner->GetRegistry();

  SyAssert(pRegistry != NULL);

  if (!pRegistry)
  {
    return;
  }

  cGameObject* pObj;
  cIntelNPC* pMyIntel = static_cast<cIntelNPC*>(mpOwner->GetIntel());
  cAi* pObjAi;
  int numMelee = 0;
  int numRanged = 0;
  SyVect3 meleeDir(0.0f, 0.0f, 0.0f), rangedDir(0.0f, 0.0f, 0.0f);
  SyVect3 myLoc(mpOwner->GetLocation());

  pObj = pRegistry->BeginType(cGameObject::OBJ_NPC);
  for(;pObj != NULL;pObj= pRegistry->NextType(pObj))
  {
    if (!pObj ||
        pObj == mpOwner ||
        pObj->GetStats()->IsDead() ||
        !pMyIntel->IsFriendly(pObj) ||
        myLoc.DistanceSquared(pObj->GetLocation()) > NPC_RADIUS_SQR)
    {
      continue;
    }

    pObjAi = static_cast<cAi*>(prop_cast<cIntelNPC*>(pObj->GetIntel())->GetAi());
      
    if (pObjAi->IsRangedAttacker())
    {
      rangedDir += pObj->GetLocation();
      ++numRanged;
    }
    else if (pObjAi->IsMeleeAttacker())
    {
      meleeDir += pObj->GetLocation();
      ++numMelee;
    }
  }

  // check for running into target, too
  static const float STUPID_FLEE_DOT_TOLERANCE = 0.7f;
  SyVect3 targetDir(0.0f, 0.0f, 0.0f), myDir;
  
  if (pTarget)
  {
    targetDir = pTarget->GetLocation()-myLoc;
    targetDir.Normalize();
  }

  float closestToCurrentDirection, dot;

  myDir.HPR(mpOwner->GetHeading(), mpOwner->GetPitch(), mpOwner->GetRoll());
  fleeDir = -targetDir;
  closestToCurrentDirection = fleeDir ^ myDir;

  if (numMelee > 0)
  {
    meleeDir /= (float)numMelee;
    meleeDir = myLoc - meleeDir;
    meleeDir.Normalize();
    if (((meleeDir^targetDir) < STUPID_FLEE_DOT_TOLERANCE))
    {
      dot = meleeDir ^ myDir;
      if (dot >= closestToCurrentDirection)
      {
        closestToCurrentDirection = dot;
        fleeDir = meleeDir;
      }
    }
  }

  if (numRanged > 0)
  {
    rangedDir /= (float)numRanged;
    rangedDir = myLoc - rangedDir;
    rangedDir.Normalize();
    if (((rangedDir^targetDir) < STUPID_FLEE_DOT_TOLERANCE))
    {
      dot = rangedDir ^ myDir;
      if (dot >= closestToCurrentDirection)
      {
        closestToCurrentDirection = dot;
        fleeDir = rangedDir;
      }
    }
  }
}

float cAi::GetActivationRadius()
{
  float override = prop_cast<cStatsCharacter*>(mpOwner->GetStats())->GetMaster()->mActivationRadius;

  if (override >= 0.1f)
  {
    return override;
  }
  
  return smActivationRadius;
}

float cAi::GetDefendRadius()
{
  cGoal_Follow* pGoalFollow = static_cast<cGoal_Follow*>(mGoalQueue.FindGoalType(GOAL_FOLLOW));

  if (pGoalFollow)
  {
    return pGoalFollow->GetDistance();
  }

  return smDefendRadius;
}

bool cAi::BlockOpportunity(int blockAttackType)
{
  SyAssert(blockAttackType >= 0 && blockAttackType <= cStatsBlockBehavior::BLOCK_ATTACK_MAX);
  SyAssert(prop_cast<cIntelNPC*>(mpOwner->GetIntel())!=NULL);
  SyAssert(prop_cast<cStatsCharacter*>(mpOwner->GetStats())!=NULL);

  cIntelNPC *pIntel = static_cast<cIntelNPC*>(mpOwner->GetIntel());
  cStatsCharacter *pStats = static_cast<cStatsCharacter*>(mpOwner->GetStats());
  const cStatsBlockBehavior* pBlockBehavior = pStats->GetBlockBehavior();

  bool bIsBlocking = mpOwner->GetTitan()->Random(1, 100) <= pBlockBehavior->mBlockPercentChance[blockAttackType];

  if (bIsBlocking)
  {
    pIntel->Block(pBlockBehavior->mBlockHoldTime);
  }

  return bIsBlocking;
}

bool cAi::DodgeOpportunity(int blockAttackType, const SyVect3& dir)
{
  SyAssert(blockAttackType >= 0 && blockAttackType <= cStatsBlockBehavior::BLOCK_ATTACK_MAX);
  SyAssert(prop_cast<cIntelNPC*>(mpOwner->GetIntel())!=NULL);
  SyAssert(prop_cast<cStatsCharacter*>(mpOwner->GetStats())!=NULL);

  cIntelNPC *pIntel = static_cast<cIntelNPC*>(mpOwner->GetIntel());
  cStatsCharacter *pStats = static_cast<cStatsCharacter*>(mpOwner->GetStats());
  const cStatsBlockBehavior* pBlockBehavior = pStats->GetBlockBehavior();

  bool bIsDodging = mpOwner->GetTitan()->Random(1, 100) <= pBlockBehavior->mDodgePercentChance[blockAttackType];

  if (bIsDodging)
  {
    pIntel->Dodge(dir);
  }

  return bIsDodging;
}

//------------------------------------------- cAiGoal

cAiGoal::cAiGoal()
: mpOwner(NULL),
  mPriority(0),
  mbCutSceneFlag(false)
{
}

cAiGoal::~cAiGoal()
{
  mpOwner = NULL;
}

void cAiGoal::Init(cAi *pOwner,int priority)
{
  mpOwner = pOwner;
  mPriority = priority;
}

void cAiGoal::SetPriority(int priority)
{
  mPriority=priority;
}

cIntelNPC* cAiGoal::GetIntel()
{
  SyAssert(prop_cast<cIntelNPC*>(GetGameObject()->GetIntel())!=NULL);
  return static_cast<cIntelNPC*>(GetGameObject()->GetIntel());
}

cAnimCharControllerInterface* cAiGoal::GetAnimInterface()
{
  cGraphicCharacter* pCharGraphic = prop_cast<cGraphicCharacter *>(GetGameObject()->GetGraphic());
  SyAssert(pCharGraphic!=NULL);

  if (pCharGraphic != NULL)
  {
    return pCharGraphic->GetAnimController();
  }

  return NULL;
}


//--------------------------------------------------------cAILOSMemory
cAILOSMemory::cAILOSMemory()
: mTargetID(ID_NONE),
  mTargetPos(0.0f, 0.0f, 0.0f),
  mLastSeenTargetPos(0.0f, 0.0f, 0.0f),
  mLastSeenTargetTime(0.0f),
  mTargetLOSTicket(-1),
  mbHasLOS(false),
  mbHasSeenTarget(false),
  mbIsTargetInvisible(false),
  mbIsTouchingInvisibleTarget(false),
  mbUseTargetPos(false),
  mbAllowDeadTarget(false)
{
}

cAILOSMemory::~cAILOSMemory()
{
  Clear();
}

void cAILOSMemory::SetTarget(tGameObjectID targetID)
{
  if (mTargetID != targetID)
  {
    mTargetID = targetID;
    mbHasLOS = false;
    mbHasSeenTarget = false;
    mbUseTargetPos = false;
    mbIsTargetInvisible = false;
    mbIsTouchingInvisibleTarget = false;
  }
}

void cAILOSMemory::Clear()
{
  mTargetID = ID_NONE;
  mLastSeenTargetTime = 0.0f;
  mLastSeenTargetPos(0.0f, 0.0f, 0.0f);
  mbHasLOS = false;
  mbHasSeenTarget = false;
  mbIsTargetInvisible = false;
  mbIsTouchingInvisibleTarget = false;
  mbUseTargetPos = false;
  mTargetPos(0.0f, 0.0f, 0.0f);

  if (mTargetLOSTicket >= 0)
  {
    cLOSTable::CancelLOS(mTargetLOSTicket);
    mTargetLOSTicket = -1;
  }
}

void cAILOSMemory::CancelLOSRequest()
{
  if (mTargetLOSTicket >= 0)
  {
    cLOSTable::CancelLOS(mTargetLOSTicket);
    mTargetLOSTicket = -1;
  }
}

void cAILOSMemory::SetTargetPosition(const SyVect3& targetPos)
{
  static const float LOS_TARGET_POSITION_TOLERANCE = 0.1f*0.1f;

  mbUseTargetPos = true;
  mTargetID = ID_NONE;
  mbIsTargetInvisible = false;
  mbIsTouchingInvisibleTarget = false;

  if (mTargetPos.DistanceSquared(targetPos) > LOS_TARGET_POSITION_TOLERANCE)
  {
    mTargetPos = targetPos;
    mbHasLOS = false;
    mbHasSeenTarget = false;
  }
}

cGameObject* cAILOSMemory::GetTarget(cGameObject* pOwner)
{
  if (ID_NONE != mTargetID)
  {
    cGameObject* pTarget = pOwner->GetRegistry()->Fetch(mTargetID); 
    if (pTarget)
    {
      return pTarget;
    }
    else
    {
      mTargetID = ID_NONE;  // got deleted out from under us
    }
  }

  return NULL;
}


bool cAILOSMemory::CanSeeTarget()
{
  if (mbUseTargetPos)
  {
    return mbHasLOS;
  }
  else if (mTargetID != ID_NONE)
  {
    return mbHasLOS && (!mbIsTargetInvisible || mbIsTouchingInvisibleTarget);
  }
  else
  {
    return false;
  }
}

bool cAILOSMemory::HasLOS()
{
  if (mTargetID != ID_NONE || mbUseTargetPos)
  {
    return mbHasLOS;
  }

  return false;
}

bool
cAILOSMemory::GetCollisionPoint(SyVect3 point)
{
  if (!HasLOS() && !IsLOSPending())
  {
    if ((mTargetID != ID_NONE || mbUseTargetPos) &&
        mTargetLOSTicket >= 0)
    {
      return cLOSTable::HasLOS(mTargetLOSTicket, &point) != cLOSTable::LOS_PENDING;
    }
  }

  return false;
}


bool cAILOSMemory::IsLOSPending()
{
  if ((mTargetID != ID_NONE || mbUseTargetPos) &&
      mTargetLOSTicket >= 0)
  {
    return cLOSTable::HasLOS(mTargetLOSTicket) == cLOSTable::LOS_PENDING;
  }

  return true;
}

bool cAILOSMemory::GetLastSeenTargetPos(SyVect3& pos)
{
  if (mbHasSeenTarget)
  {
    pos = mLastSeenTargetPos;
  }

  return mbHasSeenTarget;
}

void cAILOSMemory::Update(cGameObject* pOwner, float time)
{
  static const float INVISIBLE_TOUCHING_DISTANCE = 0.25f;

  bool bIsDead = pOwner->GetStats()->IsDead();
  cGameObject* pTarget = GetTarget(pOwner); 

  SyVect3 targetPos;
  SyVect3 height(0.0f, 1.0f, 0.0f);
  bool bHasTarget = false;

  mbIsTargetInvisible = false;
  mbIsTouchingInvisibleTarget = false;

  if (NULL != pTarget)
  {
    if (!pTarget->GetStats()->IsDead() || mbAllowDeadTarget)
    {
      bHasTarget = true;
      targetPos = pTarget->GetLocation();

      // test for an invisible target, and if we're touching him
      if (pTarget->GetType() == cGameObject::OBJ_NPC ||
          pTarget->GetType() == cGameObject::OBJ_PLAYER)
      {
        mbIsTargetInvisible = pTarget->GetStats()->QueryFlag("Invisible");
        if (mbIsTargetInvisible && pOwner->GetDistance(pTarget) < INVISIBLE_TOUCHING_DISTANCE)
        {
          mbIsTouchingInvisibleTarget = true;
          mbHasSeenTarget = true;
          mLastSeenTargetPos = targetPos;
        }
      }
    }
  }
  else if (mbUseTargetPos)
  {
    bHasTarget = true;
    targetPos = mTargetPos;
  }

  if (bHasTarget && !bIsDead)
  {
    if (mTargetLOSTicket >= 0)
    {   
      cLOSTable::LOSStatus losStatus = cLOSTable::HasLOS(mTargetLOSTicket);

      if (cLOSTable::LOS_CLEAR == losStatus)
      {
        mbHasLOS = true;
        mbHasSeenTarget = true;
        mLastSeenTargetPos = targetPos;
        cLOSTable::CancelLOS(mTargetLOSTicket);
        mTargetLOSTicket = -1;
//          mLastSeenTargetTime =??;

//          DEBUGOVERLAY_DRAWLINE(pOwner->GetID(), "AI", pOwner->GetLocation()+height, targetPos+height, cAIDebugDraw::GREEN);
      }
      else if (cLOSTable::LOS_BLOCKED == losStatus)
      {
        mbHasLOS = false;
        cLOSTable::CancelLOS(mTargetLOSTicket);
        mTargetLOSTicket = -1;

//          DEBUGOVERLAY_DRAWLINE(pOwner->GetID(), "AI", pOwner->GetLocation()+height, targetPos+height, cAIDebugDraw::RED);
      }
      else
      {
//          DEBUGOVERLAY_DRAWLINE(pOwner->GetID(), "AI", pOwner->GetLocation()+height, targetPos+height, cAIDebugDraw::YELLOW);
      }
    }

    if (mTargetLOSTicket < 0)
    {
      cLOSTable::RequestLOS(pOwner->GetLocation()+height, targetPos+height, false, mTargetLOSTicket);      
    }
  }
  else      // target or we died
  {
    mbHasLOS = false;
    mbHasSeenTarget = false;
    mTargetID = ID_NONE;

    if (mTargetLOSTicket >= 0)
    {
      cLOSTable::CancelLOS(mTargetLOSTicket);
      mTargetLOSTicket = -1;
    }
  }
}

//-----------------------------------------------------------------cAINearbyAllyList

cAINearbyAllyList::Entry::Entry()
: mID(ID_NONE),
  mHealthPercent(1.0f),
  mDistance(0.0f),
  mbWounded(false),
  mbDied(false),
  mbBuffed(false)
{
}

cAINearbyAllyList::cAINearbyAllyList()
{
}

cAINearbyAllyList::~cAINearbyAllyList()
{
  mAllies.Clear();
}

void cAINearbyAllyList::Update(cGameObject* pOwner)
{
  SyAssert(pOwner!=NULL);

  cGameObjectRegistry* pRegistry = pOwner->GetTitan()->GetRegistry();
  SyAssert(prop_cast<cIntelNPC*>(pOwner->GetIntel())!=NULL);
  cIntelNPC* pIntel = static_cast<cIntelNPC*>(pOwner->GetIntel());

  cGameObject* pObj = pRegistry->BeginType(cGameObject::OBJ_NPC);

  while (pObj != NULL)
  {
    if (pOwner != pObj && pIntel->IsFriendly(pObj))
    {
      UpdateAlly(pOwner, pObj);
    }

    pObj = pRegistry->NextType(pObj);
  }

  pObj = pRegistry->BeginType(cGameObject::OBJ_PLAYER);

  while (pObj != NULL)
  {
    SyAssert(pOwner!=pObj);
    if (pIntel->IsFriendly(pObj))
    {
      UpdateAlly(pOwner, pObj);
    }

    pObj = pRegistry->NextType(pObj);
  }
}

void cAINearbyAllyList::UpdateAlly(cGameObject* pOwner, cGameObject* pAlly)
{
  float dist, healthPct;
  int updateIndex = -1;

  updateIndex = -1;
  dist = pOwner->GetDistance(pAlly);

  int i, j;

  for (i=mAllies.Begin(); i!=mAllies.End(); i=mAllies.Next(i))
  {
    if (pAlly->GetID() == mAllies(i).mID)
    {
      updateIndex = i;
      break;
    }
  }

  if (updateIndex < 0)
  {
    for (i=mAllies.Begin(); i!=mAllies.End(); i=mAllies.Next(i))
    {
      if (dist <= mAllies(i).mDistance)
      {
        for (j=mAllies.Prev(mAllies.End()); j>i; j=mAllies.Prev(j))
        {
          mAllies(j) = mAllies(mAllies.Prev(j));
        }
        updateIndex = i;
        break;
      }
    }
  }

  if (updateIndex < 0 && mAllies.Size() < MAX_NEARBY_ALLIES)
  {
    updateIndex = mAllies.Size();
    mAllies.Add(Entry());
  }

  if (updateIndex >= 0)
  {
    cStatsCharacter* pAllyStats = static_cast<cStatsCharacter*>(pAlly->GetStats());
    cGraphicCharacter* pAllyGraphic = static_cast<cGraphicCharacter*>(pAlly->GetGraphic());
    healthPct = ((float)pAllyStats->GetHealth())/((float)pAllyStats->CalcMaxHealth());

    mAllies(updateIndex).mID = pAlly->GetID();
    mAllies(updateIndex).mDistance = dist;

    if (pAllyStats->IsDead())
    {
      mAllies(updateIndex).mbWounded = false;

      if (pAllyGraphic->GetAnimController()->GetAnimState() == AS_DEATH &&
          pAllyGraphic->GetAnimController()->GetAnimTime() > 4.0f)
      {
        if (mAllies(updateIndex).mHealthPercent > 0.0f)
        {
          mAllies(updateIndex).mHealthPercent = 0.0f;
          mAllies(updateIndex).mbDied = true;
        }
      }
    }
    else
    {
      if (healthPct <= 0.5f && mAllies(updateIndex).mHealthPercent > 0.5f)
      {
        mAllies(updateIndex).mbWounded = true;
      }
      else
      {
        mAllies(updateIndex).mbWounded = false;
      }

      if (healthPct > 0.0f) // health could be negative if we're getting beat up during a combo
      {
        mAllies(updateIndex).mHealthPercent = healthPct;
      }
    }
  }
}

//-----------------------------------------------------------------cAIWanderSteering
cAIWanderSteering::cAIWanderSteering()
: mWanderHPR(0.0f, 0.0f, 0.0f),
  mWanderRate(0.8f),
  mLastWanderAdjust(0.0f)
{
}

void cAIWanderSteering::Init(cGameObject* pNPC, float baseRate, float randAdjust)
{
  mLastWanderAdjust = 0.0f;
  mWanderHPR.HPR(pNPC->GetHeading(), 0.0f, 0.0f);

  // randomize wander rate (variance in direction each frame)
  // to make each npc look a little different
  mWanderRate = (((float)(pNPC->GetTitan()->Random(0, (int)(randAdjust*100.0f))))/100.0f) - (randAdjust*0.5f) + baseRate;
}

void cAIWanderSteering::SetHPR(const SyVect3& hpr)
{
  mWanderHPR = hpr;
}

void cAIWanderSteering::MoveNPC(cGameObject* pNPC, float speed, float radius)
{
  SyAssert(pNPC != NULL);
  
  SyVect3 myDir, wanderOffsetLoc, wanderDir;
  myDir.HPR(pNPC->GetHeading(), 0.0f, 0.0f);

  // the wander angle is just the distance of the arc length we want (the wander rate)
  float randAngle = ((((float)(pNPC->GetTitan()->Random(0, (int)(mWanderRate*2.0f*100.0f))))/100.0f) - mWanderRate);            
  cIntelNPC* pIntel = prop_cast<cIntelNPC*>(pNPC->GetIntel());

  if (pIntel->IsAvoiding())
  {
    if ((randAngle < 0.0f && mLastWanderAdjust > 0.0f) ||
        (randAngle < 0.0f && mLastWanderAdjust > 0.0f))
    {
      randAngle = -randAngle;
    }
  }

  mLastWanderAdjust = randAngle;
  mWanderHPR.X += randAngle;
  wanderDir.HPR(mWanderHPR.X, mWanderHPR.Y, mWanderHPR.Z);
  wanderDir *= radius;

//  DEBUGOVERLAY_DRAWLINE(pNPC->GetID(), "AI", pNPC->GetLocation()+myDir+SyVect3(0.0f, 3.0f, 0.0f), pNPC->GetLocation()+myDir+SyVect3(0.0f, 3.0f, 0.0f)+wanderDir, cAIDebugDraw::WHITE);
//  DEBUGOVERLAY_DRAWLINE(pNPC->GetID(), "AI", pNPC->GetLocation()+SyVect3(0.0f, 3.0f, 0.0f), pNPC->GetLocation()+myDir+SyVect3(0.0f, 3.0f, 0.0f), cAIDebugDraw::MAGENTA);
//  DEBUGOVERLAY_DRAWLINE(pNPC->GetID(), "AI", pNPC->GetLocation()+SyVect3(0.0f, 3.0f, 0.0f), pNPC->GetLocation()+myDir+wanderDir+SyVect3(0.0f, 3.0f, 0.0f), cAIDebugDraw::CYAN);

  myDir *= radius + 1.0f;
  wanderDir += myDir;
  wanderDir *= 3.0f;

  if (speed <= 0.0f)
  {
    pIntel->Stop();
    pIntel->TurnTo(pNPC->GetLocation()+wanderDir);
  }
  else
  {
    pIntel->GoTo(pNPC->GetLocation()+wanderDir, speed);
  }
}

void cAi::RegisterTuningVariables()
{
  gTuningSys.AddFloat(&smFollowRadius, "AI_FollowRadius");
  gTuningSys.AddFloat(&smDefendRadius, "AI_DefendRadius");
  gTuningSys.AddFloat(&smActivationRadius, "AI_ActivationRadius");
}

void cAiInterface::RegisterTuningVariables()
{
  cAi::RegisterTuningVariables();
  cGoal_Attack::RegisterTuningVariables();
}


// EOF
