/******************************************************************
  
  Module:  intel.cpp
  
  Author: Sean Craig
  
  Copyright 2005 Sony Online Entertainment.  All rights reserved.
  
*******************************************************************/

//-------------------------------------------------------- Includes
#include "intel.h"
#include "graphic.h"
#include "cameracontroller.h"
#include "gameobj.h"
#include "animcontroller.h"
#include "registry.h"
#include "stats.h"
#include "physics.h"
#include "ai/behaviortypes.h"
#include "ai/aidebugdraw.h"
#include "ai/lostable.h"
#include "script_pawn.h"
#include "droplist.h"
#include "database.h"

//---------------------------------------------- Class Declarations
//--------------------------------------------------------- Globals
//----------------------------------------- Functions Declarations
//------------------------------------ Staic Member Variables Definitions
float cIntelPlayer::mDeadZone = 0.2f; // dead zone in center of controller, as a fraction

static const float AI_DISTANCE_TOLERANCE = 0.33f;
static const float AI_STUCKAVOID_DISTANCE_TOLERANCE = 4.0f;
static const float AI_DISTANCE_TOLERANCE_SQR = AI_DISTANCE_TOLERANCE * AI_DISTANCE_TOLERANCE;
static const float AI_STUCKAVOID_DISTANCE_TOLERANCE_SQR = AI_STUCKAVOID_DISTANCE_TOLERANCE * AI_STUCKAVOID_DISTANCE_TOLERANCE;
//------------------------------------ Member Functions Definitions

//------------------------------------ cIntel

cIntel::cIntel():
  mOwner(NULL),
  mLocal(false)
{
  InitPropObject( mCLASSID );
}

int
cIntel::InitPropClass()
{

/* Add the class */

  AddClass( mCLASSID, 
            "cIntel", 
            NULL,     // CRO: abstract base class has no creator
            mCLASSID, 
            0 ); 

  return 0;
}

SyPropObject* 
cIntel::Creator()
{
  SyAssertf(0,"Trying to class factory abstract base class"); // abstract base class 

  return(NULL);
}
//------------------------------------ cIntelEntity

cIntelEntity::cIntelEntity()
{
  InitPropObject( mCLASSID );
}

int           
cIntelEntity::InitPropClass()
{
/* Add the class */

  AddSubClass( mCLASSID, 
               cIntel::mCLASSID,
               mCLASSID,
               "cIntelEntity", 
               Creator, 
               mCLASSID, 
               0 ); 
  return 0;
}

SyPropObject* 
cIntelEntity::Creator()
{
  SyPropObject *pObject;

  pObject = SyNew cIntelEntity();
  if(pObject == NULL)
  {
    SyAssert(0);
    return(NULL);
  }

  return(pObject);
}

void
cIntelEntity::Init()
{
  mStartLocation = mOwner->GetLocation();
}

void          
cIntelEntity::Reset() // for example, when respawning
{
  mOwner->SetLocation(mStartLocation);
}

tGameObjectID
cIntelEntity::PickAttackTarget(float dir, float max_distance)
{
  static const float max_angle = SY_DEG_TO_RAD(90.0f);
  float best_score = 0; // higher score wins
  cGameObject *best = NULL;
  cGameObjectRegistry *registry = mOwner->GetRegistry();
  int index;
  for (index = registry->Begin();index != registry->End();index = registry->Next(index))
  {
    cGameObject *cur = (*registry)(index);

    if (cur == mOwner)
    {
      continue;
    }

    if  (cur->GetType() != cGameObject::OBJ_NPC &&
         cur->GetType() != cGameObject::OBJ_PLAYER &&
         cur->GetType() != cGameObject::OBJ_PROP)
    {
      continue;
    }

    if (!IsTargetable(cur))
    {
      continue;
    }

    float distance = mOwner->GetDistance(cur);

    if (distance > max_distance)
    {
      continue;
    }

    float towards  = mOwner->GetHeadingTowards(cur);
    float delta_heading = AngleDifference(dir,towards); 

    if (delta_heading > max_angle || delta_heading < -max_angle)
    {
      continue;
    }
       
    float score = max_distance - distance;
    score += SY_COS(delta_heading) * max_distance;

    if (score > best_score)
    {
      best_score = score;
      best = cur;
    }
  }                 

  if (best != NULL)
  {
    return best->GetID();
  }
  return ID_NONE;
}

tGameObjectID
cIntelEntity::PickActionTargetDir(float dir)
{
  //static const float MIN_SWIVEL_AMOUNT = 0.1f;
  
  

  static const float max_distance = 1.0f; // todo: move this into stats
  //static const float max_angle = SY_DEG_TO_RAD(90.0f);
  float best_score = 0; // higher score wins
  cGameObject *best = NULL;
  cGameObjectRegistry *registry = mOwner->GetRegistry();
  int index;
  for (index = registry->Begin();index != registry->End();index = registry->Next(index))
  {
    cGameObject *cur = (*registry)(index);

    if (cur == mOwner)
    {
      continue;
    }

    if (cur->GetStats()->IsDead())
    {
      continue;
    }

    if (cur->GetStats()->GetActivateType() == cStats::ACTIVATE_NONE)
    {
      continue;
    }
 
    // todo: can't target friendlies???

    float distance =mOwner->GetDistance(cur);

    if (distance > max_distance)
    {
      continue;
    }
    float towards  = mOwner->GetHeadingTowards(cur);
    float delta_heading = AngleDifference(dir,towards); 

    /*
    if (delta_heading > max_angle || delta_heading < -max_angle)
    {
      continue;
    }
    */
       
    float score = max_distance - distance;
    score += (1.0f + SY_COS(delta_heading)) * max_distance*0.5f;

    if (score > best_score)
    {
      best_score = score;
      best = cur;
    }
  }                 

  if (best != NULL)
  {
    return best->GetID();
  }
  return ID_NONE;
}

bool
cIntelEntity::IsTargetable(cGameObject* pTarget)
{
  SyAssertf(pTarget!=NULL, "cIntelNPC::IsTargetable needs target");

  if (!pTarget)
  {
    return false;
  }

  // todo: can't target some props?
  if (pTarget->GetType() == cGameObject::OBJ_PROP)
  {
    return true;
  }

  if (pTarget->GetType() != cGameObject::OBJ_NPC &&
      pTarget->GetType() != cGameObject::OBJ_PLAYER)
  {
    return false;
  }

  cStatsCharacter* pTargetStats = static_cast<cStatsCharacter*>(pTarget->GetStats());
  SyAssertf(pTargetStats!=NULL, "cIntelEntity::IsTargetable target has no stats");

  if (!pTargetStats ||
      pTargetStats->IsDead() ||
      pTargetStats->HasCondition("Invisible"))
  {
    return false;
  }

  if ((mOwner->GetType() == cGameObject::OBJ_NPC ||
       mOwner->GetType() == cGameObject::OBJ_PLAYER) &&
      (pTargetStats->GetFaction() == NPCFACTION_NEUTRAL ||
       pTargetStats->GetFaction() == static_cast<cStatsCharacter*>(mOwner->GetStats())->GetFaction()))
  {
    return false;
  }

  return true;
}

bool
cIntelEntity::IsFriendly(cGameObject* pTarget)
{
  SyAssertf(pTarget!=NULL, "cIntelEntity::IsTargetable needs target");

  if (!pTarget)
  {
    return false;
  }

  if (pTarget->GetType() != cGameObject::OBJ_NPC &&
      pTarget->GetType() != cGameObject::OBJ_PLAYER)
  {
    return false;
  }

  if ((mOwner->GetType() == cGameObject::OBJ_NPC ||
       mOwner->GetType() == cGameObject::OBJ_PLAYER))
  {
    cStatsCharacter* pTargetStats = static_cast<cStatsCharacter*>(pTarget->GetStats());
    SyAssertf(pTargetStats!=NULL, "cIntelEntity::IsTargetable target has no stats");

    if (!pTargetStats ||
        pTargetStats->GetFaction() != static_cast<cStatsCharacter*>(mOwner->GetStats())->GetFaction())
    {
      return false;
    }
  }

  return true;
}

//------------------------------------ cIntelPlayer

cIntelPlayer::cIntelPlayer():
  mControllerID(-1),
  mHeldDownTime(0.0f),
  mPrevHeading(0.0f),
  mActionTarget(ID_NONE)
{
  InitPropObject( mCLASSID );
  mLocal = true;
}

int           
cIntelPlayer::InitPropClass()
{

/* Add the class */

  AddSubClass( mCLASSID, 
               cIntelEntity::mCLASSID,
               mCLASSID,
               "cIntelPlayer", 
               Creator, 
               mCLASSID, 
               0 ); 
  return 0;
}

SyPropObject* 
cIntelPlayer::Creator()
{
  SyPropObject *pObject;

  pObject = SyNew cIntelPlayer();
  if(pObject == NULL)
  {
    SyAssert(0);
    return(NULL);
  }

  return(pObject);
}

void 
cIntelPlayer::Update(float time)
{
  static const float MIN_SWIVEL_AMOUNT = 0.1f;

  mHeldDownTime += time;
  if (!mLocal)
  {
    return;
  }

  if (mControllerID < 0)
  {
    return;
  }

  cGraphicCharacter *graphic = (cGraphicCharacter *) mOwner->GetGraphic(); 
  cAnimCharControllerInput *input = graphic->GetAnimInput();

  if (static_cast<cStatsCharacter*>(mOwner->GetStats())->HasCondition("Stunned"))
  { 
    input->mSpeedRequest = 0.0f;
    return;
  }

  float heading = GetControllerHeading(time);
  float speed   = GetControllerMagnitude();
  // todo: use prop object dynamic cast

  if (speed > MIN_SWIVEL_AMOUNT)
  {
    input->mHeadingRequest=heading;
  }
  input->mSpeedRequest = speed;

  TitanControllerI *controller = mOwner->GetTitan()->GetTitanUI()->GetController(mControllerID);
  if (controller == NULL)
  {
    return;
  }
  float forward = (float)controller->GetRightStickForward() / TitanControllerI::DIRECTION_MAX;
  
  static const float THRESHOLD = 0.1f;
  if (forward > THRESHOLD)
  {
    forward = forward-THRESHOLD/(1.0f - THRESHOLD);
  }
  else if (forward < -THRESHOLD)
  {
    forward = forward+THRESHOLD/(1.0f - THRESHOLD);
  }
  else
  {
    forward = 0.0f;
  }

  mOwner->GetTitan()->GetCameraController()->Zoom(forward);

  float right =   (float)controller->GetRightStickRight() / TitanControllerI::DIRECTION_MAX;
  if (right > THRESHOLD)
  {
    right = right-THRESHOLD/(1.0f - THRESHOLD);
    mOwner->GetTitan()->GetCameraController()->Pan(right * time);
  }
  else if (right < -THRESHOLD)
  {
    forward = right+THRESHOLD/(1.0f - THRESHOLD);
    mOwner->GetTitan()->GetCameraController()->Pan(right * time);
  }


#if 0  // sgc - no dodge anymore
  static const float MIN_DODGE_AMOUNT = 0.5f;

  float dodge_heading = GetRightControllerHeading(time);
  float dodge_magnitude   = GetRightControllerMagnitude();
  
  if (dodge_magnitude >= MIN_DODGE_AMOUNT && !input->mDodgeHigh)
  {
    input->mDodging = true;
    input->mDodgeHigh = true;
    input->mDodgeDirection = dodge_heading;
  }

  if (dodge_magnitude < MIN_DODGE_AMOUNT && input->mDodgeHigh)
  {
    input->mDodgeHigh = false;
  }
#endif

  UpdateActionTarget();


};



float                
cIntelPlayer::GetControllerHeading(float time)
{
  TitanControllerI *controller = mOwner->GetTitan()->GetTitanUI()->GetController(mControllerID);
  if (controller == NULL)
  {
    return 0.0f;
  }
  float forward = (float)controller->GetForward() / TitanControllerI::DIRECTION_MAX;
  float right =   (float)controller->GetRight() / TitanControllerI::DIRECTION_MAX;

  return _ControllerHeading(time,forward,right);
}

float                
cIntelPlayer::GetRightControllerHeading(float time)
{
  TitanControllerI *controller = mOwner->GetTitan()->GetTitanUI()->GetController(mControllerID);
  if (controller == NULL)
  {
    return 0.0f;
  }
  float forward = (float)controller->GetRightStickForward() / TitanControllerI::DIRECTION_MAX;
  float right =   (float)controller->GetRightStickRight() / TitanControllerI::DIRECTION_MAX;

  return _ControllerHeading(time,forward,right);
}


float                
cIntelPlayer::_ControllerHeading(float time, float forward, float right)
{

  float heading = SY_ATAN2(right,-forward);  // left-right is +x, forward is -z (maya coord system)
  // adjust heading based on camera

  float delta_controller = AngleNormalize(heading-mPrevHeading);
  mPrevHeading = heading;
  static const float MAX_CONTROLLER_DELTA = SY_DEG_TO_RAD(360) * time;  
  if (delta_controller > MAX_CONTROLLER_DELTA || delta_controller < -MAX_CONTROLLER_DELTA)
  {
    mHeldDownTime = 0.0f;
  }

  heading += mOwner->GetTitan()->GetCameraController()->GetControlOffset(mHeldDownTime);
  heading = AngleNormalize(heading);

  return heading;
}

float                
cIntelPlayer::GetControllerMagnitude()
{
  TitanControllerI *controller = mOwner->GetTitan()->GetTitanUI()->GetController(mControllerID);
  if (controller == NULL)
  {
    return 0.0f;
  }
  float forward = (float)controller->GetForward() / TitanControllerI::DIRECTION_MAX;
  float right =   (float)controller->GetRight() / TitanControllerI::DIRECTION_MAX;
  return _ControllerMagnitude(forward,right);
}


float                
cIntelPlayer::GetRightControllerMagnitude()
{
  TitanControllerI *controller = mOwner->GetTitan()->GetTitanUI()->GetController(mControllerID);
  if (controller == NULL)
  {
    return 0.0f;
  }
  float forward = (float)controller->GetRightStickForward() / TitanControllerI::DIRECTION_MAX;
  float right =   (float)controller->GetRightStickRight() / TitanControllerI::DIRECTION_MAX;
  return _ControllerMagnitude(forward,right);
}

float                
cIntelPlayer::_ControllerMagnitude(float forward, float right)
{
 

  float speed   = SY_SQRT(forward*forward + right*right);
  if (speed < mDeadZone)
  {   
    mHeldDownTime = 0.0f;
    return 0.0f;
  }

  if (speed > 1.0f) speed = 1.0f;

  speed -= mDeadZone;  // move negative input closer to 0;
  return ((float)speed / (1.0f-mDeadZone));
}


tGameObjectID
cIntelPlayer::PickAttackTarget(eComboType combo)
{
  static const float MIN_SWIVEL_AMOUNT = 0.1f;
  float dir;
  if (GetControllerMagnitude() > MIN_SWIVEL_AMOUNT)
  {
    // pick guy in that direction
    dir = GetControllerHeading(0.0f);
  }
  else
  {
    // pick guy in direction he's facing
    dir = mOwner->GetHeading();
  }

  float max_distance = 1.0f;

  SyAssert(prop_cast<cGraphicCharacter*>(mOwner->GetGraphic())!=NULL);
  cAnimCharControllerInterface *iface = ((cGraphicCharacter *) mOwner->GetGraphic())->GetAnimController();

  max_distance = iface->GetRange(combo);
  // make the distance a little larger, so we can still target critters a little out of range
  max_distance *= 1.5f;

  return cIntelEntity::PickAttackTarget(dir,max_distance);
}

tGameObjectID
cIntelPlayer::PickAttackTargetRanged()
{
  static const float MIN_SWIVEL_AMOUNT = 0.1f;
  float dir;
  if (GetControllerMagnitude() > MIN_SWIVEL_AMOUNT)
  {
    // pick guy in that direction
    dir = GetControllerHeading(0.0f);
  }
  else
  {
    // pick guy in direction he's facing
    dir = mOwner->GetHeading();
  }
  float max_distance = 10.0f;
  return cIntelEntity::PickAttackTarget(dir,max_distance);
}

void
cIntelPlayer::UpdateActionTarget()
{
  static const float MIN_SWIVEL_AMOUNT = 0.1f;
  float dir;
  if (GetControllerMagnitude() > MIN_SWIVEL_AMOUNT)
  {
    // pick guy in that direction
    dir = GetControllerHeading(0.0f);
  }
  else
  {
    // pick guy in direction he's facing
    dir = mOwner->GetHeading();
  }
  mActionTarget =  PickActionTargetDir(dir);
}
//------------------------------------ cIntelNPC

cIntelNPC::cIntelNPC()
: mAi(NULL),
  mbIsMoving(false),
  mGoalPos(0.0f, 0.0f, 0.0f),
  mMoveSpeed(0.0f),
  mbIsAvoiding(false),
  mbHasLOSToGoal(false),
  mAvoidDir(0.0f, 0.0f, 0.0f),
  mLOSTicketToGoal(-1),
  mAvoidTimer(0.0f),
  mbIsStuck(false),
  mStuckTimer(0.0f),
  mbStuckAvoid(false),
  mBlockTimer(0.0f)
{
  InitPropObject( mCLASSID );
}

cIntelNPC::~cIntelNPC()
{
  if (mLOSTicketToGoal >= 0)
  {
    cLOSTable::CancelLOS(mLOSTicketToGoal);
    mLOSTicketToGoal = -1;
  }

  delete mAi;
}

int           
cIntelNPC::InitPropClass()
{
/* Add the class */

  AddSubClass( mCLASSID, 
               cIntelEntity::mCLASSID,
               mCLASSID,
               "cIntelNPC", 
               Creator, 
               mCLASSID, 
               0 ); 

  return 0;
}

SyPropObject* 
cIntelNPC::Creator()
{
  SyPropObject *pObject;

  pObject = SyNew cIntelNPC();
  if(pObject == NULL)
  {
    SyAssert(0);
    return(NULL);
  }

  return(pObject);
}

void
cIntelNPC::Init()
{
  cIntelEntity::Init();

  if (mAi == NULL)
  {
    mAi = cAiInterface::Allocate(mOwner);
    mAi->Stand(0);
  }
}

void
cIntelNPC::Exit()
{
  if (mAi != NULL)
  {
    mAi->Reset(); 
  }

  cIntelEntity::Exit();
}

void
cIntelNPC::Reset()
{
  if (mAi != NULL)
  {
    mAi->Reset();
  }

  cIntelEntity::Reset();
}

bool
cIntelNPC::IsMoving()
{
  return mbIsMoving;
}

bool
cIntelNPC::IsAvoiding()
{
  return mbIsMoving && (mbIsAvoiding || mbStuckAvoid);
}

bool
cIntelNPC::IsTargetable(cGameObject* pTarget)
{
  bool bTargetable = cIntelEntity::IsTargetable(pTarget);

  if (!bTargetable)
  {
    return false;
  }

  if (pTarget->GetType() != cGameObject::OBJ_NPC &&
      pTarget->GetType() != cGameObject::OBJ_PLAYER)
  {
    return false;
  }

  cStatsCharacter* pTargetStats = static_cast<cStatsCharacter*>(pTarget->GetStats());
  SyAssertf(pTargetStats!=NULL, "cIntelEntity::IsTargetable target has no stats");
  
  if (pTargetStats->GetFaction() == NPCFACTION_NEUTRAL ||
      pTargetStats->GetFaction() == static_cast<cStatsCharacter*>(mOwner->GetStats())->GetFaction())
  {
    return false;
  }

  return true;
}

void 
cIntelNPC::Update(float time)
{
  if (mOwner->IsRemote())
  {
    return; // let remote peer handle ai.
  }

  if (mOwner->GetTitan()->isCutScenePlaying())
  {
    Stop();
    return;
  }

  if (static_cast<cStatsCharacter*>(mOwner->GetStats())->HasCondition("Stunned"))
  { 
    cGraphicCharacter *graphic = (cGraphicCharacter *) mOwner->GetGraphic(); 
    cAnimCharControllerInput *input = graphic->GetAnimInput();
    input->mSpeedRequest = 0.0f;
    return;
  }

  bool bIsDead = mOwner->GetStats()->IsDead();

  mAi->Update(time);

  if (!bIsDead)
  {
    UpdateMove(time);
    UpdateBlock(time);
  }
  else
  {
    Stop();
  }
}

void
cIntelNPC::UpdateBlock(float time)
{
  if (mBlockTimer > 0.0f)
  {
    mBlockTimer -= time;

    if (mBlockTimer <= 0.0f)
    {
      mBlockTimer = 0.0f;

      cGraphicCharacter *graphic =prop_cast<cGraphicCharacter*>(mOwner->GetGraphic());
      SyAssert(graphic != NULL);
      cAnimCharControllerInput *input = graphic->GetAnimInput();
      input->mBlockRequest = false;
    }
  }
}

void
cIntelNPC::UpdateMove(float time)
{
  if (!mbIsMoving)
  {
    mPrevLocation = mOwner->GetLocation();
    return;    // don't do anything if we're not going anywhere
  }

  static const float WALK_DISTANCE = 3.0f;
  float speed = mMoveSpeed;

  cGraphicCharacter *graphic =prop_cast<cGraphicCharacter*>(mOwner->GetGraphic());
  SyAssert(graphic!=NULL);
  cAnimCharControllerInput *input = graphic->GetAnimInput();

  // check for arrival
  if (mGoalPos.DistanceSquared(mOwner->GetLocation()) < AI_DISTANCE_TOLERANCE_SQR)
  {
    Stop();
  }
  else
  {
    AIDEBUGDRAW_LINE(mOwner->GetID(), mOwner->GetLocation()+SyVect3(0.0f, 1.0f, 0.0f), mGoalPos+SyVect3(0.0f, 1.0f, 0.0f), mbHasLOSToGoal?cAIDebugDraw::GREEN:cAIDebugDraw::RED);

    // only test line of sight until we can see the goal
    // (assumes we won't get blocked after then)
    SyVect3 hitPos, hitNormal;

    cLOSTable::LOSStatus losStatus = cLOSTable::LOS_INVALID;

    if (mLOSTicketToGoal >= 0)  // if it's not valid at this point, means we ran out of space in the table
    {
      losStatus = cLOSTable::HasLOS(mLOSTicketToGoal, &hitPos, &hitNormal);
    }
      
    if (cLOSTable::LOS_CLEAR == losStatus)
    {
      mbHasLOSToGoal = true;      
      mbIsAvoiding = mbIsStuck || mbStuckAvoid;          
    }
    else if (cLOSTable::LOS_BLOCKED == losStatus)
    {
      // set the direction to avoid the obstacle
      if (!mbIsAvoiding && SY_FABS(hitNormal.Y) < 0.8f) // ignore hit results on floors
      {
        CalcAvoidDir(hitNormal);
      }

      // request new LOS
      mbHasLOSToGoal = false;
    }
    else if (cLOSTable::LOS_INVALID == losStatus)
    {
      SyAssert(mLOSTicketToGoal == -1);
    }

    if (cLOSTable::LOS_PENDING != losStatus)
    {
      RequestLOSToGoal();
    }

    if (mbIsAvoiding)
    { 
      AIDEBUGDRAW_LINE(mOwner->GetID(), mOwner->GetLocation()+SyVect3(0.0f, 3.0f, 0.0f), mOwner->GetLocation()+SyVect3(0.0f, 3.0f, 0.0f)+mAvoidDir, cAIDebugDraw::CYAN);

      input->mHeadingRequest = mOwner->GetHeadingTowards(mOwner->GetLocation()+mAvoidDir);

      static const float MAX_AVOID_TIME = 7.0f;
      mAvoidTimer += time;

      if (mAvoidTimer > MAX_AVOID_TIME)
      {
        mbIsAvoiding = false; // reset the avoid flag to recalc direction
        mbStuckAvoid = false;
        mAvoidTimer = 0.0f;
      }
    }
    else
    {
      input->mHeadingRequest = mOwner->GetHeadingTowards(mGoalPos);
    }

    // should we slow down b/c we're close?
    if (mGoalPos.Distance(mOwner->GetLocation()) < WALK_DISTANCE)
    {
      speed = 0.1f;
    }

    input->mSpeedRequest = speed;
  }

  //
  // STUCK LOGIC
  // Check if NPC is stuck because they're supposed to be moving but are not
  //

  static const float STUCK_TIME = 3.0f;

  float deltaDistSqr = mOwner->GetLocation().DistanceSquared(mPrevLocation);
  if (deltaDistSqr <= AI_DISTANCE_TOLERANCE_SQR ||
      (mbStuckAvoid && deltaDistSqr <= AI_STUCKAVOID_DISTANCE_TOLERANCE_SQR))
  {
    mStuckTimer += time;
  }
  else
  {
    mPrevLocation = mOwner->GetLocation();
    mStuckTimer = 0.0f;
    mbStuckAvoid = false;
  }

  if (mStuckTimer >= STUCK_TIME)
  {
    if (!mbIsAvoiding)
    {
      SyVect3 myDir;
      myDir.HPR(mOwner->GetHeadingTowards(mGoalPos), 0.0f, 0.0f);
      mbStuckAvoid = true;
      CalcAvoidDir(-myDir);
    }
    else
    {
      SyVect3 toGoal(mGoalPos-mOwner->GetLocation());
      toGoal.Normalize();
      toGoal *= 0.2f;
      mAvoidDir += toGoal;
      mAvoidDir.Normalize();
    }

    AIDEBUGDRAW_LINE(mOwner->GetID(), mOwner->GetLocation(), mOwner->GetLocation()+SyVect3(0.0f, 5.0f, 0.0f)+mAvoidDir, cAIDebugDraw::YELLOW);
    mbIsStuck = true;
  }
  else
  {
    mbIsStuck = false;
  }
}

void
cIntelNPC::RequestLOSToGoal()
{
  if (mLOSTicketToGoal >= 0)
  {
    cLOSTable::CancelLOS(mLOSTicketToGoal);
  }

  mLOSTicketToGoal = -1;

  SyVect3 myLoc(mOwner->GetLocation());
  SyVect3 goalDir = mGoalPos - myLoc;
  SyVect3 height(0.0f, 1.0f, 0.0f);
  float goalDist = goalDir.NormalizeMagn();

  goalDir *= SY_MIN(8.0f, goalDist); // max lookahead, not all the way to goal

  cLOSTable::RequestLOS(myLoc+height, myLoc+goalDir+height, mLOSTicketToGoal);      
}

void
cIntelNPC::CalcAvoidDir(const SyVect3& hitNormal)
{
  SyVect3 goalDir, avoidDir, myDir;

  myDir.HPR(mOwner->GetHeading(), mOwner->GetPitch(), mOwner->GetRoll());
  goalDir = mGoalPos - mOwner->GetLocation();
  goalDir.Y = 0.0f;
  goalDir.Normalize();

  avoidDir.Cross(hitNormal, SyVect3(0.0f, 1.0f, 0.0f));
  avoidDir.Normalize();

  if ((goalDir ^ myDir) < -0.5f)
  {
    myDir = goalDir;
  }

  if ((myDir ^ avoidDir) < 0.0f)
  {
    avoidDir = -avoidDir;
  }

  mAvoidDir = avoidDir;
  mbIsAvoiding = true;          
  mAvoidTimer = 0.0f;
}

void 
cIntelNPC::TurnTo(const SyVect3 &lookAt)
{
  if (mbIsMoving)
  {
    return;
  }

  cGraphicCharacter *graphic =prop_cast<cGraphicCharacter*>(mOwner->GetGraphic());
  SyAssert(graphic != NULL);
  cAnimCharControllerInput *input = graphic->GetAnimInput();
  input->mHeadingRequest = mOwner->GetHeadingTowards(lookAt);
  input->mSpeedRequest = 0.0f;
}

void                  
cIntelNPC::GoTo(const SyVect3 &dest, float speed)
{
  if (mbIsMoving && mGoalPos.DistanceSquared(dest) < AI_DISTANCE_TOLERANCE_SQR)
  {
    return;
  }

  SyVect3 height(0.0f, 1.0f, 0.0f);
  float heading = mOwner->GetHeadingTowards(dest);

  cGraphicCharacter *graphic =prop_cast<cGraphicCharacter*>(mOwner->GetGraphic());
  SyAssert(graphic != NULL);
  cAnimCharControllerInput *input = graphic->GetAnimInput();
  input->mHeadingRequest = heading;
  input->mSpeedRequest = speed;

  mbIsMoving = true;
  mGoalPos = dest;  // prob should test ground height here
  mMoveSpeed = speed;

  mbIsAvoiding = false;
  mbStuckAvoid = false;
  mbHasLOSToGoal = false;
  mAvoidDir(0.0f, 0.0f, 0.0f);

  RequestLOSToGoal();
}

void                  
cIntelNPC::AttackMelee(cGameObject *target,float attackDistance)
{
  cGraphicCharacter *graphic =prop_cast<cGraphicCharacter*>(mOwner->GetGraphic());
  SyAssert(graphic != NULL);
  cAnimCharControllerInput *input = graphic->GetAnimInput();

  float distance = mOwner->GetDistance(target);
  if (distance < attackDistance)
  {
    input->mAttackRequestL = true;
    input->mTarget = target->GetID();
  }
}

void                  
cIntelNPC::AttackRanged(cGameObject *target)
{
  cGraphicCharacter *graphic =prop_cast<cGraphicCharacter*>(mOwner->GetGraphic());
  SyAssert(graphic != NULL);
  cAnimCharControllerInput *input = graphic->GetAnimInput();
  input->mAttackRequestRanged = true;
  input->mTarget = target->GetID();
}

void                  
cIntelNPC::AttackRanged(float heading, float range)
{
  cGraphicCharacter *graphic =prop_cast<cGraphicCharacter*>(mOwner->GetGraphic());
  SyAssert(graphic != NULL);
  cAnimCharControllerInput *input = graphic->GetAnimInput();
  input->mAttackRequestRanged = true;
  input->mTarget = ID_NONE;
  input->mTargetAngle = heading;
  input->mTargetRange = range; 
}

void
cIntelNPC::Stop()
{
  cGraphicCharacter *graphic = prop_cast<cGraphicCharacter*>(mOwner->GetGraphic());
  SyAssert(graphic != NULL);
  cAnimCharControllerInput *input = graphic->GetAnimInput();
  input->mSpeedRequest = 0.0f;
  mbIsMoving = false;
  mbIsAvoiding = false;
  mbIsStuck = false;
  mStuckTimer = 0.0f;
  if (mLOSTicketToGoal >= 0)
  {
    cLOSTable::CancelLOS(mLOSTicketToGoal);
    mLOSTicketToGoal = -1;
  }
}

void                  
cIntelNPC::Block(float blockTime)
{
  cGraphicCharacter *graphic =prop_cast<cGraphicCharacter*>(mOwner->GetGraphic());
  SyAssert(graphic != NULL);
  cAnimCharControllerInput *input = graphic->GetAnimInput();
  input->mBlockRequest = true;
  mBlockTimer = blockTime;
}

void                  
cIntelNPC::StopBlock()
{
  cGraphicCharacter *graphic =prop_cast<cGraphicCharacter*>(mOwner->GetGraphic());
  SyAssert(graphic != NULL);
  cAnimCharControllerInput *input = graphic->GetAnimInput();
  input->mBlockRequest = false;
  mBlockTimer = 0.0f;
}

void 
cIntelNPC::Dodge(const SyVect3& dir)
{
  cGraphicCharacter *graphic =prop_cast<cGraphicCharacter*>(mOwner->GetGraphic());
  SyAssert(graphic != NULL);
  cAnimCharControllerInput *input = graphic->GetAnimInput();
  input->mDodging = true;

  float pitch, roll;
  dir.HPR(&(input->mDodgeDirection), &pitch, &roll);
}

void                  
cIntelNPC::Jump()
{
  cGraphicCharacter *graphic =prop_cast<cGraphicCharacter*>(mOwner->GetGraphic());
  SyAssert(graphic != NULL);
  cAnimCharControllerInput *input = graphic->GetAnimInput();
  input->mJumpRequest = true;
}

cGameObject* cIntelNPC::FindClosestObject(float minDist, float maxDist, 
                                          bool bIsNPC,
                                          bool bIsPlayer,
                                          bool bIsProp,
                                          bool bIsFriendly,
                                          bool bIsDamaged)
{
  cGameObjectRegistry *pRegistry = mOwner->GetRegistry();
  float distSqr;
  float minDistSqr = minDist*minDist;
  float maxDistSqr = maxDist*maxDist;
  float closestDistSqr = 0.0f;
  int closestIndex = -1;

  SyVect3 myLoc(mOwner->GetLocation());
  cGameObject *pObj;
  cGameObject::tObjectType type;

  for (int i = pRegistry->Begin();i != pRegistry->End();i = pRegistry->Next(i))
  {
    pObj = (*pRegistry)(i);

    if (!pObj || pObj == mOwner)
    {
      continue;
    }

    type = pObj->GetType();

    if (!((bIsNPC && type==cGameObject::OBJ_NPC) ||
          (bIsPlayer && type==cGameObject::OBJ_PLAYER) ||
          (bIsProp && type==cGameObject::OBJ_PROP)))
    {
      continue;
    }

    if ((bIsNPC||bIsPlayer) &&
        (type==cGameObject::OBJ_NPC || type==cGameObject::OBJ_PLAYER))
    {
      if ((bIsFriendly && !IsFriendly(pObj)) ||
          (!bIsFriendly && !IsTargetable(pObj)))
      {
        continue;
      }

      if (bIsDamaged && pObj->GetStats()->GetHealth() >= static_cast<cStatsCharacter*>(pObj->GetStats())->CalcMaxHealth())
      {
        continue;
      }
    }

    distSqr = myLoc.DistanceSquared(pObj->GetLocation());

    if (distSqr > maxDistSqr || distSqr < minDistSqr)
    {
      continue;
    }

    if (-1 == closestIndex || distSqr < closestDistSqr)
    {
      closestIndex = i;
      closestDistSqr = distSqr;
    }
  }                 

  // attack closest target if we have no prev target or 
  // it's more than half the distance closer
  if (closestIndex >= 0)
  {    
    return (*pRegistry)(closestIndex);
  }

  return NULL;
}

void          
cIntelNPC::OnAttacked(tGameObjectID attacker,int damage)
{
  if (mOwner->IsRemote())
  {
    // send message to owner saying you've been attacked...
    return;
  }

  SyAssert(mAi!=NULL);

  if (mAi)
  {
    mAi->OnAttacked(attacker, damage);
  }
}

void
cIntelNPC::OnWallCollision(const SyVect3& normal)
{
  if (!mbIsMoving)
  {
    return;
  }

  // if we're avoiding and we've hit a wall, this means our heuristic
  // avoid direction is off - adjust it slightly for each wall collision.
  if (mbIsAvoiding)
  {
    SyVect3 right, up(0.0f, 1.0f, 0.0f);

    right.Cross(normal, up);
    right.Normalize();

    if ((right^mAvoidDir) < 0.0f)
    {
      right = -right;
    }

    right += normal;

    if (mbStuckAvoid)
    {
      right *= 1.0f;
    }
    else
    {
      right *= 0.1f;
    }

    mAvoidDir += right;
    mAvoidDir.Normalize();
  }
  else
  {
    if (mLOSTicketToGoal >= 0)
    {
      if (cLOSTable::HasLOS(mLOSTicketToGoal) == cLOSTable::LOS_PENDING)
      {
        CalcAvoidDir(normal);
      }
    }
  }
}

void
cIntelNPC::OnWake()
{
}
void cIntelNPC::OnSleep()
{
  Stop();
  mAi->Reset();
}

//------------------------------------ cIntelNone

cIntelNone::cIntelNone()
{
  InitPropObject( mCLASSID );
}

int           
cIntelNone::InitPropClass()
{

/* Add the class */

  AddSubClass( mCLASSID, 
               cIntel::mCLASSID,
               mCLASSID,
               "cIntelNone", 
               Creator, 
               mCLASSID, 
               0 ); 
  return 0;
}

SyPropObject* 
cIntelNone::Creator()
{
  SyPropObject *pObject;

  pObject = SyNew cIntelNone();
  if(pObject == NULL)
  {
    SyAssert(0);
    return(NULL);
  }

  return(pObject);
}

void cIntelNone::Update(float time)
{
};


//------------------------------------ cIntelProp

cIntelProp::cIntelProp()
: mbOpened(false),
  mbDelete(false)
{
  InitPropObject( mCLASSID );
}

int           
cIntelProp::InitPropClass()
{

  /* Add the class */

  AddSubClass( mCLASSID, 
    cIntel::mCLASSID,
    mCLASSID,
    "cIntelProp", 
    Creator, 
    mCLASSID, 
    0 ); 
  return 0;
}

SyPropObject* 
cIntelProp::Creator()
{
  SyPropObject *pObject;

  pObject = SyNew cIntelProp();
  if(pObject == NULL)
  {
    SyAssert(0);
    return(NULL);
  }

  return(pObject);
}

void cIntelProp::Update(float time)
{
};

void cIntelProp::OnDeath()
{
  SyAssert(prop_cast<cGraphicProp*>(mOwner->GetGraphic()) != NULL);
  cGraphicProp* pGraphic = static_cast<cGraphicProp*>(mOwner->GetGraphic());

  if (pGraphic)
  {
    cAnimPropControllerInterface* pAnimCtrlr = pGraphic->GetAnimController();
    // if prop has no anims, it may not have an animcontroller
    if (pAnimCtrlr)
    {
      pAnimCtrlr->ChangeAnimState(cAnimPropControllerInterface::PAS_DESTROY);
    }
    else 
    {
      // if there are no anims, delete the object.
      mbDelete = true;
    }
  }

  SyAssert(prop_cast<cStatsProp*>(mOwner->GetStats()) != NULL);

  cStatsProp* pStats = static_cast<cStatsProp*>(mOwner->GetStats());

  if (pStats->GetMaster()->mTreasureSet != ID_NONE)
  {
    cDatabaseSys *db = mOwner->GetTitan()->GetDatabaseSys();
    cTreasureSet *treasureset = db->GetTreasureSet(pStats->GetMaster()->mTreasureSet);

    if (treasureset != NULL)
    {
      SyVect3 location = mOwner->GetLocation();
      location.Y += 1.5f; 
      treasureset->Drop(mOwner->GetTitan(),location);
    }
    else
    {
      SyAssertf(0,"Unknown treasure set for prop '%s'",mOwner->GetName());
    }    
  }
}

bool          
cIntelProp::CheckForDelete()
{
  return mbDelete;
}

void cIntelProp::Open()
{
  SyAssert(prop_cast<cStatsProp*>(mOwner->GetStats()) != NULL);

  cStatsProp* pStats = static_cast<cStatsProp*>(mOwner->GetStats());

  cGraphicProp* pGraphic = static_cast<cGraphicProp*>(mOwner->GetGraphic());
  SyAssert(pGraphic);

  cAnimPropControllerInterface* pAnimCtrlr = pGraphic->GetAnimController();

  if (!mbOpened)
  {
    mbOpened = true;

    if (pAnimCtrlr)
    {
      pAnimCtrlr->ChangeAnimState(cAnimPropControllerInterface::PAS_ACTIVATE);
    }

    if (pStats->GetMaster()->mTreasureSet != ID_NONE)
    {
      cDatabaseSys *db = mOwner->GetTitan()->GetDatabaseSys();
      cTreasureSet *treasureset = db->GetTreasureSet(pStats->GetMaster()->mTreasureSet);

      if (treasureset != NULL)
      {
        treasureset->Drop(mOwner->GetTitan(),mOwner->GetLocation());
      }
      else
      {
        SyAssertf(0,"Unknown treasure set for prop '%s'",mOwner->GetName());
      }    
    }
  }
  else
  {
    mbOpened = false;

    if (pAnimCtrlr)
    {
      pAnimCtrlr->ChangeAnimState(cAnimPropControllerInterface::PAS_DEACTIVATE);
    }

  }
};


//------------------------------------ cIntelProjectile

cIntelProjectile::cIntelProjectile()
: mTargetID(ID_NONE)
{
  InitPropObject( mCLASSID );
}

int           
cIntelProjectile::InitPropClass()
{

  /* Add the class */

  AddSubClass( mCLASSID, 
    cIntel::mCLASSID,
    mCLASSID,
    "cIntelProjectile", 
    Creator, 
    mCLASSID, 
    0 ); 
  return 0;
}

SyPropObject* 
cIntelProjectile::Creator()
{
  SyPropObject *pObject;

  pObject = SyNew cIntelProjectile();
  if(pObject == NULL)
  {
    SyAssert(0);
    return(NULL);
  }

  return(pObject);
}

void cIntelProjectile::Update(float time)
{
  if (mOwner->IsRemote())
  {
    return; // let remote peer handle ai.
  }

  SyAssert(prop_cast<cStatsProjectile*>(mOwner->GetStats()) != NULL);
  cStatsProjectile* pStats = static_cast<cStatsProjectile*>(mOwner->GetStats());
  SyAssert(prop_cast<cPhysicsProjectile*>(mOwner->GetPhysics()) != NULL);
  cPhysicsProjectile* pPhysics = static_cast<cPhysicsProjectile*>(mOwner->GetPhysics());

  if (pStats->IsDead())
  {
    return;
  }

  if (pStats->GetMaster()->mSeeking > 0.0f)
  {
    if (mTargetID != ID_NONE)
    {
      cGameObject* pTarget = mOwner->GetRegistry()->Fetch(mTargetID);

      if (!pTarget || !IsTargetable(pTarget))
      {
        mTargetID = ID_NONE;
      }
      else 
      {
        SyVect3 vel(pPhysics->GetVelocity());
        float speed = vel.NormalizeMagn();

        if (speed > 0.0f)
        {
          SyVect3 toTargetHPR, toTargetDir(pTarget->GetLocation()-mOwner->GetLocation());
          float arc = SY_DEG_TO_RAD(pStats->GetMaster()->mSeeking)*time;
          SyVect3 curVelHPR;
          vel.HPR(&curVelHPR.X, &curVelHPR.Y, &curVelHPR.Z);

          if (toTargetDir.Y < 1.0f)
          {
            toTargetDir.Y = 0.0f;
          }

          toTargetDir.Normalize();
          toTargetDir.HPR(&toTargetHPR.X, &toTargetHPR.Y, &toTargetHPR.Z);

          if (curVelHPR.X - toTargetHPR.X < -arc)
          {
            curVelHPR.X += arc;
          }
          else if (curVelHPR.X - toTargetHPR.X > arc)
          {
            curVelHPR.X -= arc;
          }
          else
          {
            curVelHPR.X = toTargetHPR.X;
          }

          if (curVelHPR.Y - toTargetHPR.Y < -arc)
          {
            curVelHPR.Y += arc;
          }
          else if (curVelHPR.Y - toTargetHPR.Y > arc)
          {
            curVelHPR.Y -= arc;
          }
          else
          {
            curVelHPR.Y = toTargetHPR.Y;
          }

          if (curVelHPR.Z - toTargetHPR.Z < -arc)
          {
            curVelHPR.Z += arc;
          }
          else if (curVelHPR.Z - toTargetHPR.Z > arc)
          {
            curVelHPR.Z -= arc;
          }
          else
          {
            curVelHPR.Z = toTargetHPR.Z;
          }
          
          vel.HPR(curVelHPR.X, curVelHPR.Y, curVelHPR.Z);
          vel *= speed;
          pPhysics->SetVelocity(vel);
          mOwner->SetHPR(curVelHPR);
        }
      }
    }
    else
    {
      static const float MAX_HOMING_DISTANCE = 10.0f;
      mTargetID = cIntelEntity::PickAttackTarget(mOwner->GetHeading(),MAX_HOMING_DISTANCE);
    }
  }
  else if (pStats->GetMaster()->mOrbiting > 0.0f)
  {
    cGameObject* pSourceObj = mOwner->GetRegistry()->Fetch(pPhysics->GetSourceID());

    if (pSourceObj)
    {
      SyVect3 toSourceDir(pSourceObj->GetLocation()-mOwner->GetLocation());
      toSourceDir.Y = 0.0f;

      if (time > 0.0f && prop_cast<cGraphicCharacter*>(pSourceObj->GetGraphic()))
      {
        SyVect3 sourceDisplacement(static_cast<cGraphicCharacter*>(pSourceObj->GetGraphic())->GetDisplacement());
        mOwner->SetLocation(mOwner->GetLocation()+sourceDisplacement);
      }
      
      if (!(toSourceDir.X > -0.001f && toSourceDir.X < 0.001f && 
            toSourceDir.Z > -0.001f && toSourceDir.Z < 0.001f))
      {        
        SyVect3 tangent, hpr;
        float distToSource = toSourceDir.NormalizeMagn();
        float speed = mOwner->GetPhysics()->GetVelocity().Magnitude();
        float travelDist;
        
        tangent.Cross(toSourceDir, SyVect3(0.0f, 1.0f, 0.0f));
        tangent.Normalize();

        if (distToSource - pStats->GetMaster()->mOrbiting < 0.0f)
        {
          travelDist = SY_MAX(distToSource - pStats->GetMaster()->mOrbiting, -speed*time);
        }
        else
        {
          travelDist = SY_MIN(distToSource - pStats->GetMaster()->mOrbiting, speed*time);
        }

        toSourceDir *= travelDist;
        mOwner->SetLocation(mOwner->GetLocation()+toSourceDir);

        tangent.HPR(&hpr.X, &hpr.Y, &hpr.Z);
        tangent *= speed;
        pPhysics->SetVelocity(tangent);
        mOwner->SetHeading(hpr.X);
      }
    }
  }
};

void cIntelProjectile::SetTarget(tGameObjectID targetID)
{
  mTargetID = targetID;
}

bool
cIntelProjectile::IsTargetable(cGameObject* pTarget)
{
  bool bTargetable = cIntelEntity::IsTargetable(pTarget);

  if (!bTargetable)
  {
    return false;
  }


  SyAssert(prop_cast<cPhysicsProjectile*>(mOwner->GetPhysics()) != NULL);
  if (pTarget->GetID() == static_cast<cPhysicsProjectile*>(mOwner->GetPhysics())->GetSourceID())
  {
    return false;
  }

  return true;
}

//------------------------------------ Global Function Defs
void 
RegPropClasses_Intel()
{
  cIntel::InitPropClass();
  cIntelNone::InitPropClass();
  cIntelEntity::InitPropClass();
  cIntelPlayer::InitPropClass();
  cIntelNPC::InitPropClass();
  cIntelProp::InitPropClass();
  cIntelProjectile::InitPropClass();
}

// EOF
