/******************************************************************
  
  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/lostable.h"
#include "ai/ailodmanager.h"
#include "script_pawn.h"
#include "droplist.h"
#include "database.h"
#include "debugoverlay.h"
#include "SyScene.h"
#include "spell.h"
#include "tuning.h"


//---------------------------------------------- Class Declarations
//--------------------------------------------------------- Globals
//----------------------------------------- Functions Declarations
//------------------------------------ Staic Member Variables Definitions
float cIntelPlayer::mDeadZone = 0.35f; // 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;
static const float AI_EYE_HEIGHT = 1.25f;
static const tGameID s_targetEffectID = SyHashResourceID("fx_target");

static float l_TARGETING_HOLD_DELAY = 0.275f;
static float l_TARGETING_AREA_MOVE_SPEED = 5.0f;

//------------------------------------ Member Functions Definitions

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

cIntel::cIntel():
  mOwner(NULL)
{
  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);
}

cGameObject* cIntel::FindClosestObject(float minDist, float maxDist, 
                                       bool bIsNPC,
                                       bool bIsPlayer,
                                       bool bIsProp,
                                       bool bIsFriendly,
                                       bool bIsDamaged,
                                       bool bIsDead)
{
  cGameObjectRegistry *pRegistry = mOwner->GetRegistry();
  float distSqr;
  float minDistSqr = minDist*minDist;
  float maxDistSqr = maxDist*maxDist;
  float closestDistSqr = 0.0f;
  cGameObject *closest = NULL;

  SyVect3 myLoc(mOwner->GetLocation());
  cGameObject::tObjectType type;
  bool   bTargetMe = false;

  // could also search each object type one at a time
  cGameObject* pObj = pRegistry->Begin();
  for(;pObj != NULL;pObj= pRegistry->Next(pObj))
  {

    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 ((bIsDead && !pObj->GetStats()->IsDead()) ||
        (!bIsDead && pObj->GetStats()->IsDead()))
    {
      continue;
    }

    bTargetMe = false;

    if ((bIsNPC||bIsPlayer) &&
        (type==cGameObject::OBJ_NPC || type==cGameObject::OBJ_PLAYER))
    {
      if ((bIsFriendly && !IsFriendly(pObj)) ||
          (!bIsFriendly && !IsTargetable(pObj)) ||
          (bIsDamaged && pObj->GetStats()->GetHealth() >= static_cast<cStatsCharacter*>(pObj->GetStats())->CalcMaxHealth()))
      {
        continue;
      }

      bTargetMe = pObj->GetStats()->QueryFlag("Target Me");
    }

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

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

    if (bTargetMe)
    {
      closest = pObj;
      break;
    }
    else if (NULL == closest|| distSqr < closestDistSqr)
    {
      closest = pObj;
      closestDistSqr = distSqr;
    }

  }

  // attack closest target if we have no prev target or 
  // it's more than half the distance closer
  return closest;
}

//------------------------------------ cIntelEntity


cIntelEntity::cIntelEntity()
: mStartLocation(0.0f, 0.0f, 0.0f),
  mStartHeading(0.0f),
  mLastWaterType(SyWaterTile::FLUID_NOTFOUND)
{
  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()
{
  mOwner->SetSafeLocation(mOwner->GetLocation());
  mStartLocation = mOwner->GetLocation();
  mStartHeading = mOwner->GetHeading();
}

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

  mOwner->SetHeading(mStartHeading);
}

void
cIntelEntity::SetStartLocation(const SyVect3& loc)
{
  mStartLocation = loc;
}

void
cIntelEntity::SetStartHeading(float heading)
{
  mStartHeading = heading;
}


void
cIntelEntity::CheckWater()
{
  int water = mOwner->GetTitan()->GetScene()->GetWaterSystem()->Plunk(mOwner->GetLocation(), 0.0f);

  if (water != mLastWaterType)
  {
    tGameID spellID = ID_NONE;
    const cSpellMaster* pSpell;

    spellID = SyWaterTile::GetSpellIDForFluidProfile(mLastWaterType);

    if (ID_NONE != spellID)
    {
      pSpell = mOwner->GetTitan()->GetDatabaseSys()->GetSpellMaster(spellID);
      SyAssertf(pSpell!=NULL, "Could not find spell for water type in tuning.xml");
      if (pSpell)
      {
        pSpell->EndSpellEffects(mOwner, mOwner);
      }
    }

    mLastWaterType = water;
    spellID = SyWaterTile::GetSpellIDForFluidProfile(mLastWaterType);

    if (ID_NONE != spellID)
    {
      pSpell = mOwner->GetTitan()->GetDatabaseSys()->GetSpellMaster(spellID);
      SyAssertf(pSpell!=NULL, "Could not find spell for water type in tuning.xml");
      if (pSpell)
      {
        pSpell->CastSpell(mOwner, mOwner);
      }
    }
  }
}

tGameObjectID
cIntelEntity::PickAttackTarget(float dir, float max_distance, float max_angle)
{
  float best_score = -10000.0f; // higher score wins
  cGameObject *best = NULL;
  cGameObjectRegistry *pRegistry = mOwner->GetRegistry();
  cGameObject* pObj = pRegistry->Begin();
  cGameObject::tObjectType type;

  for(;pObj != NULL;pObj= pRegistry->Next(pObj))
  {
    if (pObj == mOwner)
    {
      continue;
    }

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

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

    float distance = mOwner->GetDistance(pObj);

    if (distance > max_distance)
    {
      continue;
    }

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

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

    if (pObj->GetType() == cGameObject::OBJ_PROP)
    {
      score = score*0.01f;  
    }

    if (best == NULL || score > best_score)
    {
      best_score = score;
      best = pObj;
    }
  }                 

  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_angle = SY_DEG_TO_RAD(90.0f);

  float MAX_DISTANCE = 15.0f;
  float best_score = 0; // higher score wins
  cGameObject *best = NULL;
  cGameObjectRegistry *pRegistry = mOwner->GetRegistry();
  cGameObject* pObj = pRegistry->Begin();
  cStatsCharacter* pMyStats = static_cast<cStatsCharacter*>(mOwner->GetStats());
  cGraphicCharacter* pMyGraphic = static_cast<cGraphicCharacter*>(mOwner->GetGraphic());

  if (pMyGraphic->GetAnimController()->GetCarriedObject() != ID_NONE)
  {
    return ID_NONE;
  }

  SyAssert(prop_cast<cPhysicsAnimated*>(mOwner->GetPhysics())!=NULL);
  tGameObjectID floorPropID = static_cast<cPhysicsAnimated*>(mOwner->GetPhysics())->GetFloorPropID();

  for(;pObj != NULL;pObj= pRegistry->Next(pObj))
  {
    if (pObj == mOwner ||
        pObj->GetStats()->IsDead() ||
        pObj->GetStats()->GetActivateType() == cStats::ACTIVATE_NONE ||
        pObj->GetCarriedBy() != ID_NONE)
    {
      continue;
    }
 
    if (pObj->GetStats()->GetActivateType() == cStats::ACTIVATE_PICKUP)
    {
      if (ID_NONE != floorPropID && 
          pObj->GetType() == cGameObject::OBJ_PROP &&
          pObj->GetID() == floorPropID)
      {
        continue;
      }

      if (!pMyStats ||
          (pObj->GetType() == cGameObject::OBJ_PROP && !pMyStats->CanLift(pObj)))
      {
        continue;
      }
    }

    float distance = mOwner->GetDistance(pObj);

    if (distance > MAX_DISTANCE || distance > pObj->GetStats()->GetActivateRadius())
    {
      continue;
    }

    float towards  = mOwner->GetHeadingTowards(pObj);
    float delta_heading = AngleDifference(dir,towards); 
       
    float score = MAX_DISTANCE - distance;
    score += (1.0f + SY_COS(delta_heading)) * MAX_DISTANCE*0.5f;

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

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

  return ID_NONE;
}

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

  if (!pTarget)
  {
    return false;
  }

  if (pTarget->GetType() == cGameObject::OBJ_PROP)
  {
    if (!pTarget->GetStats()->IsDead() && 
        static_cast<cStatsProp*>(pTarget->GetStats())->GetMaster()->mMaxHealth > 0 &&
        pTarget->GetCarriedBy() == ID_NONE)
    {
      return true;
    }
    else
    {
      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 ||
      pTargetStats->IsDead())
  {
    return false;
  }

  bool bCharmed = false;

  if (mOwner->GetType() == cGameObject::OBJ_NPC ||
      mOwner->GetType() == cGameObject::OBJ_PLAYER)
  {
    cStatsCharacter* pMyStats = static_cast<cStatsCharacter*>(mOwner->GetStats());

    if (pTargetStats->GetFaction() == NPCFACTION_NEUTRAL)
    {
      return false;
    }

    if (!pTarget->GetGraphic()->GetVisible() || pTargetStats->QueryFlag("Invisible"))
    {
      return false;
    }

    bCharmed = pMyStats->HasCondition("Charmed");

    if (pTargetStats->GetFaction() == static_cast<cStatsCharacter*>(mOwner->GetStats())->GetFaction())
    {
      if (mOwner->GetType() == cGameObject::OBJ_PLAYER &&
          pTarget->GetType() == cGameObject::OBJ_PLAYER)
      {
        return mOwner->GetTitan()->GetPvPEnabled();
      }

      return bCharmed;
    }
  }

  return !bCharmed;
}

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

  if (!pTarget)
  {
    return false;
  }

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

  bool bCharmed = false;

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

    bCharmed = pMyStats->HasCondition("Charmed");

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

  return !bCharmed;
}

void
cIntelEntity::RegisterTuningVariables()
{
}

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

float cIntelPlayer::smLeftStickTapHigh = 0.95f;
float cIntelPlayer::smLeftStickTapLow = 0.6f;
float cIntelPlayer::smLeftStickTapMaxTime = 0.2f;
float cIntelPlayer::smLeftStickTapDelayBetween = 0.5f; 

cIntelPlayer::cIntelPlayer():
  mControllerID(-1),
  mbRemote(false),
  mLeftStickHeldDownTime(0.0f),
  mLeftStickPrevMag(0.0f),
  mLeftStickHighMagTime(0.0f),
  mLeftStickTapHeading(0.0f),
  mLeftStickTimeSinceTap(0.0f),
  mRightStickHeldDownTime(0.0f),
  mLeftStickPrevHeading(0.0f),
  mRightStickPrevHeading(0.0f),
  mActionTarget(ID_NONE),
  mAmbientMotion(0.0f),
  mPrevLocation(0.0f, 0.0f, 0.0f),
  mPrevRHandLoc(0.0f, 0.0f, 0.0f),
  mPrevLHandLoc(0.0f, 0.0f, 0.0f),
  mbSelectAttackTarget(false),
  mSelectAttackTargetTime(0.0f),
  mSelectAttackSpellID(ID_NONE),
  mSelectAttackTargetID(ID_NONE),
  mSelectAttackLocation(0.0f, 0.0f, 0.0f),
  mTimeSinceSpellCast(0.0f)
{
  InitPropObject( mCLASSID );

  mMappedAbilityIDs.Init(ButtonMax);
  mMappedAbilityRanks.Init(ButtonMax);
  for (int i=0; i<ButtonMax; ++i)
  {
    mMappedAbilityIDs(i) = ID_NONE;
    mMappedAbilityRanks(i) = 0;
  }
}

int           
cIntelPlayer::InitPropClass()
{

/* Add the class */

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

  AddArrayProperty(mCLASSID, 0x2000, SyMemberOffset( cIntelPlayer, mMappedAbilityIDs ), "MappedAbilityIDs", SYPROPTYPE_UINT32 );
  AddArrayProperty(mCLASSID, 0x2001, SyMemberOffset( cIntelPlayer, mMappedAbilityRanks ), "MappedAbilityRanks", SYPROPTYPE_UINT32 );
  AddBoolProperty(mCLASSID, 0x2002, SyMemberOffset( cIntelPlayer, mbRemote ), "mbRemote" );

  return 0;
}

void
cIntelPlayer::RegisterTuningVariables()
{
  gTuningSys.AddFloat(&l_TARGETING_HOLD_DELAY, "Targeting_Hold_Delay");
  gTuningSys.AddFloat(&l_TARGETING_AREA_MOVE_SPEED, "Targeting_Area_Move_Speed");

  gTuningSys.AddFloat(&smLeftStickTapHigh, "LeftStickTap_High");
  gTuningSys.AddFloat(&smLeftStickTapLow, "LeftStickTap_Low");
  gTuningSys.AddFloat(&smLeftStickTapMaxTime, "LeftStickTap_MaxTime");
  gTuningSys.AddFloat(&smLeftStickTapDelayBetween, "LeftStickTap_DelayBetween");
}

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

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

  return(pObject);
}

void cIntelPlayer::OnLoseOwnership()
{
  mbRemote = true;
}

void cIntelPlayer::OnGainOwnership()
{
  mbRemote = false;
}

void          
cIntelPlayer::Reset() // for example, when respawning
{
  if (mOwner->GetTitan()->GetNumLocalPlayers() > 1)
  {
    cGameObject* pPlayer = mOwner->GetRegistry()->BeginType(cGameObject::OBJ_PLAYER);
    while (pPlayer)
    {
      if (pPlayer != mOwner &&
          pPlayer->IsLocal() &&
          !pPlayer->GetStats()->IsDead())
      {
        if (!mOwner->SetSafeLocation(pPlayer->GetLocation()))
        {
          mOwner->SetLocation(pPlayer->GetLocation());
        }

        mOwner->SetHeading(pPlayer->GetHeading());
        break;
      }

      pPlayer = mOwner->GetRegistry()->NextType(pPlayer);
    }
  }
  else
  {
    cIntelEntity::Reset();    
  }
}


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

  mLeftStickHeldDownTime += time;
  mRightStickHeldDownTime += time;

  if (!mOwner->IsLocal() ||
      mControllerID < 0 ||
      !mOwner->GetTitan()->AllowCutSceneControllerMotion())
  {
    return;
  }

  CheckWater();

  cGraphicCharacter *graphic = (cGraphicCharacter *) mOwner->GetGraphic(); 
  cStatsCharacter *stats = (cStatsCharacter *) mOwner->GetStats(); 
  cAnimCharControllerInput *input = graphic->GetAnimInput();
  SyAssert(mOwner->GetTitan()->GetTitanUI()->GetController(mControllerID) != NULL);
  TitanControllerI* pController = mOwner->GetTitan()->GetTitanUI()->GetController(mControllerID);

  //float oldHeading = mLeftStickPrevHeading;
  float heading = GetControllerHeading(time);
  float speed = GetControllerMagnitude();

//  bool bLeftStickTap = mLeftStickHighMagTime > 0.0f && mLeftStickHighMagTime < smLeftStickTapMaxTime && speed < smLeftStickTapLow;

  if (stats->CalcMovementSpeed() > 0 &&
      !stats->QueryFlag("Stunned") &&
      (!mbSelectAttackTarget ||
       graphic->GetAnimController()->IsInAir() ||
       graphic->GetAnimController()->GetAnimState() == AS_DODGE))
  {
//    if (speed > smLeftStickTapHigh &&
//        mLeftStickTimeSinceTap < smLeftStickTapDelayBetween &&
//        SY_FABS(AngleDifference(mLeftStickTapHeading, mLeftStickPrevHeading)) < SY_DEG_TO_RAD(30.0f))
    if (pController->GetButtonState(ButtonDodge) && speed > 0.1f)
    {
      input->mDodging = true;
      input->mDodgeDirection = heading;
    }

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

//  if (bLeftStickTap)
//  {
//    mLeftStickTimeSinceTap = 0.0f;
//    mLeftStickTapHeading = mLeftStickPrevHeading;
//  }
//  else
//  {
//    mLeftStickTimeSinceTap += time;
//  }

//  if (mLeftStickHighMagTime > 0.0f && speed > smLeftStickTapLow)
//  {
//    mLeftStickHighMagTime += time;
//  }
//  else if (speed > smLeftStickTapHigh)
//  {
//    mLeftStickHighMagTime += time;
//  }
//  else
//  {
//    mLeftStickHighMagTime = 0.0f;
//  }

  mLeftStickPrevMag = speed;

  /* begin- hand splash code for the player - todo: pb-this should be generalized and moved out of here! */
  if ( graphic->GetAnimController()->GetAnimState() != AS_LAND )
  {
    bool handsSplash = false;

    SyVect3 rHandLoc,lHandLoc,lHandVel,rHandVel;
    if (graphic->GetIdentNodeLocation(CHAR_NODE_RIGHT_HAND, &rHandLoc) &&
        graphic->GetIdentNodeLocation(CHAR_NODE_RIGHT_HAND, &lHandLoc))
    {
      lHandVel = lHandLoc - mPrevLHandLoc;
      rHandVel = rHandLoc - mPrevRHandLoc;
      mPrevRHandLoc = rHandLoc;
      mPrevLHandLoc = lHandLoc;
      handsSplash = true;
    }

    SyWaterSystem * pWaterSystem = mOwner->GetTitan()->GetScene()->GetWaterSystem();
    SyVect3 vel = mOwner->GetLocation() - mPrevLocation;
    vel *= 4.0f;
    
    if ( speed > 0.1f )
    {
      mAmbientMotion = 0.75f;
      if ( pWaterSystem ) 
      {
        pWaterSystem->DemoPlunk( mOwner->GetLocation() + vel, 0.75f );
      }
    }
    else if ( speed > 0.01f )
    {
      mAmbientMotion = 0.22f;
      if ( pWaterSystem ) 
      {
        pWaterSystem->DemoPlunk( mOwner->GetLocation() + vel, 0.22f );
      }
    }
    else
    {
      if ( pWaterSystem ) 
      {
        pWaterSystem->DemoPlunk( mOwner->GetLocation(), mAmbientMotion );
      }
      mAmbientMotion = 0.95f * mAmbientMotion;
      if ( mAmbientMotion < 0.1f )
        mAmbientMotion = 0.0f;
    }

    if (handsSplash)
    {
      float rVel = rHandVel.Magnitude()*60.0f; /* just roughly approximate vel at 60fps for now */
      rVel = SY_CLAMP( rVel, 0.0f, 1.0f );
      float lVel = lHandVel.Magnitude()*60.0f;
      lVel = SY_CLAMP( lVel, 0.0f, 1.0f );
      lHandLoc.Y -= 0.1f;
      rHandLoc.Y -= 0.1f;
      SyWaterSystem * pWaterSystem =  mOwner->GetTitan()->GetScene()->GetWaterSystem();
      pWaterSystem->DemoPlunk( lHandLoc, 1.00f * lVel );
      pWaterSystem->DemoPlunk( rHandLoc, 1.00f * rVel );
    }
  } /* end- hand splash code for the player - todo: pb-this should be generalized and moved out of here! */

#ifdef _DRAWDEBUGOVERLAY
  if ( mOwner->GetTitan()->GetShowWaterInfo() )
  {
    if (mOwner->GetTitan()->GetScene()->GetWaterSystem()->GetName())
    {
      DEBUGOVERLAY_ENABLECHANNEL("fluidInfo",true);
      DEBUGOVERLAY_DRAWSCREENTEXT(0,"fluidInfo",10,150,("Fluid: name(%s)", mOwner->GetTitan()->GetScene()->GetWaterSystem()->GetName() ),cDebugOverlay::BLUE );
      DEBUGOVERLAY_DRAWSCREENTEXT(0,"fluidInfo",9,149,("Fluid: name(%s)", mOwner->GetTitan()->GetScene()->GetWaterSystem()->GetName() ),cDebugOverlay::WHITE );
    }
  }
#endif

#ifdef _DRAWDEBUGOVERLAY
  if ( mOwner->GetTitan()->GetShowWaterInfo() )
  {
    if (mOwner->GetTitan()->GetScene()->GetWaterSystem()->GetName())
    {
      DEBUGOVERLAY_ENABLECHANNEL("fluidInfo",true);
      DEBUGOVERLAY_DRAWSCREENTEXT(0,"fluidInfo",10,150,("Fluid: name(%s)", mOwner->GetTitan()->GetScene()->GetWaterSystem()->GetName() ),cDebugOverlay::BLUE );
      DEBUGOVERLAY_DRAWSCREENTEXT(0,"fluidInfo",9,149,("Fluid: name(%s)", mOwner->GetTitan()->GetScene()->GetWaterSystem()->GetName() ),cDebugOverlay::WHITE );
    }
  }
#endif
  if (mbSelectAttackTarget)
  {
    mSelectAttackTargetTime += time;
  }

  if (mSelectAttackSpellID != ID_NONE)
  {
    mTimeSinceSpellCast += time;
  }

  UpdateCamera(time);

  UpdateActionTarget(time);

  mPrevLocation = mOwner->GetLocation();

  /* show the list of animations */
#ifdef _DRAWDEBUGOVERLAY
  if ( mOwner->GetTitan()->GetShowPlayerAnims() )
  {
    SyActorHandle actorHandle = graphic->GetActorHandle();
    SyScene * pScene = mOwner->GetTitan()->GetScene();
    SyCSprite * pSprite = NULL;
    cAnimCharControllerInterface *animController = ((cGraphicCharacter*) mOwner->GetGraphic())->GetAnimController();
    if ( SyActorNull != actorHandle &&
      pScene->GetActorSpriteType(actorHandle) == SYSPRITETYPE_CSPRITE &&
      ((pSprite = static_cast<SyCSprite*>(pScene->GetActorSpritePtr(actorHandle))) != NULL) && 
      animController != NULL )
    {
      
      DEBUGOVERLAY_ENABLECHANNEL( "ShowAnimList", true );
      
      int I = pSprite->GetFirstPlay();
      int nCount = 0;
      const char * interpModeName[5] = { "set", "user", "in", "out", "noop" };
      int QuarterWidth = pScene->GetRasterDev()->GetScreenWidth() >> 2;
      int Height = pScene->GetRasterDev()->GetScreenHeight() >> 2;
      while( I != -1 )
      {
        int animID = pSprite->GetPlayAnimID( I );
        float fFrame = pSprite->GetPlayFrame( I, *pScene );
        float fInterp;
        float fLen = pSprite->GetAnimDuration(animID, *pScene);
        SyAnimBlendMode mode;
        mode = pSprite->GetPlaybackBlendMode( I );
        fInterp = pSprite->GetPlaybackBlend( I );
        DEBUGOVERLAY_DRAWSCREENTEXT( 0, "ShowAnimList", QuarterWidth, Height - (30*nCount), ("#%d %s frame(%2.2f) interp(%s,%2.2f) len(%2.2f)", nCount, animController->GetAnimName( animID ), fFrame, interpModeName[mode], fInterp, fLen), SyColor32F(0.0f,1.0f,0.0f,1.0f) );
        nCount++;
        I = pSprite->GetNextPlay( I );
      }
    }
  }
#endif // #ifdef _DRAWDEBUGOVERLAY
}

void cIntelPlayer::UpdateCamera(float time)
{
  TitanControllerI *controller = mOwner->GetTitan()->GetTitanUI()->GetController(mControllerID);

  if (!controller)
  {
    return;
  }

  float forward = controller->Normalize(controller->GetRightStickForward());
  mOwner->GetTitan()->GetCameraController()->Zoom(forward);

  float right = controller->Normalize(controller->GetRightStickRight());
  
  if (right > 0.0f)
  {
    mOwner->GetTitan()->GetCameraController()->Pan(right);
  }
  else if (right < 0.0f)
  {
    mOwner->GetTitan()->GetCameraController()->Pan(right);
  }
}

void
cIntelPlayer::MapAbility(TitanButton tb, const cAbilityMaster* pAbility)
{
  SyAssert(mMappedAbilityIDs.Size() == ButtonMax);
  SyAssert(mMappedAbilityRanks.Size() == ButtonMax);
  SyAssert(tb >= 0 && tb < ButtonMax);
  SyAssert(pAbility!=NULL);

  RemoveMappedAbility(pAbility);

  mMappedAbilityIDs(tb) = pAbility->mID.GetID();
  mMappedAbilityRanks(tb) = pAbility->mRank;
}

const cAbilityMaster* 
cIntelPlayer::GetMappedAbility(TitanButton tb)
{
  SyAssert(mMappedAbilityIDs.Size() == ButtonMax);
  SyAssert(mMappedAbilityRanks.Size() == ButtonMax);
  SyAssert(tb >= 0 && tb < ButtonMax);
  if ((uint32)ID_NONE == mMappedAbilityIDs(tb))
  {
    return NULL;
  }

  return mOwner->GetTitan()->GetDatabaseSys()->GetAbilityMaster(mMappedAbilityIDs(tb), mMappedAbilityRanks(tb));
}

void
cIntelPlayer::AutoMapAbility(const cAbilityMaster* pAbility)
{
  SyAssert(pAbility!=NULL);

  if (!pAbility || NUM_COMBOS != pAbility->mCombo)
  {
    return;
  }

  if ((uint32)pAbility->mID.GetID() == mMappedAbilityIDs(ButtonAttackL))
  {
    mMappedAbilityRanks(ButtonAttackL) = pAbility->mRank;
  }
  else if ((uint32)pAbility->mID.GetID() == mMappedAbilityIDs(ButtonAttackS))
  {
    mMappedAbilityRanks(ButtonAttackS) = pAbility->mRank;
  }
  else if ((uint32)pAbility->mID.GetID() == mMappedAbilityIDs(ButtonAction))
  {
    mMappedAbilityRanks(ButtonAction) = pAbility->mRank;
  }
  else if ((uint32)pAbility->mID.GetID() == mMappedAbilityIDs(ButtonAction))
  {
    mMappedAbilityRanks(ButtonAction) = pAbility->mRank;
  }
  else if ((uint32)ID_NONE == mMappedAbilityIDs(ButtonAttackL))
  {
    mMappedAbilityIDs(ButtonAttackL) = pAbility->mID.GetID();
    mMappedAbilityRanks(ButtonAttackL) = pAbility->mRank;
  }
  else if ((uint32)ID_NONE == mMappedAbilityIDs(ButtonAttackS))
  {
    mMappedAbilityIDs(ButtonAttackS) = pAbility->mID.GetID();
    mMappedAbilityRanks(ButtonAttackS) = pAbility->mRank;
  }
  else if ((uint32)ID_NONE == mMappedAbilityIDs(ButtonAction))
  {
    mMappedAbilityIDs(ButtonAction) = pAbility->mID.GetID();
    mMappedAbilityRanks(ButtonAction) = pAbility->mRank;
  }
  else if ((uint32)ID_NONE == mMappedAbilityIDs(ButtonJump))
  {
    mMappedAbilityIDs(ButtonJump) = pAbility->mID.GetID();
    mMappedAbilityRanks(ButtonJump) = pAbility->mRank;
  }
}

void cIntelPlayer::RemoveMappedAbility(const cAbilityMaster* pAbility)
{
  for (int i=0; i<ButtonMax; ++i)
  {
    if ((uint32)pAbility->mID.GetID() == mMappedAbilityIDs(i) &&
        (uint32)pAbility->mRank == mMappedAbilityRanks(i))
    {
      mMappedAbilityIDs(i) = ID_NONE;
      mMappedAbilityRanks(i) = 0;
    }
  }
}

void cIntelPlayer::StartRangedAttack()
{
  if (mbSelectAttackTarget)
  {
    return;
  }

  mbSelectAttackTarget = true;
  mSelectAttackSpellID = ID_NONE;
  mSelectAttackTargetTime = 0.0f;
  mSelectAttackTargetID = ID_NONE;
  mTimeSinceSpellCast = 0.0f;
}

void cIntelPlayer::EndRangedAttack()
{
  if (!mbSelectAttackTarget)
  {
    return;
  }

  cGameObject* pTarget = mOwner->GetRegistry()->Fetch(mSelectAttackTargetID);
  if (pTarget)
  {
    cGraphicActor* pTargetGraphic = prop_cast<cGraphicActor*>(pTarget->GetGraphic());
    if (pTargetGraphic)
    {
      pTargetGraphic->RemoveEffect(1, s_targetEffectID);
    }
  }

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

  input->mAttackRequestRanged = true;
  input->mTarget = mSelectAttackTargetID;

  if (ID_NONE == input->mTarget)
  {
    input->mTargetRange = 20.0f;
    input->mTargetPitch = 0.0f; // todo calc pitch from slope of ground
    if (GetControllerMagnitude() > 0.5f)
    {
      input->mTargetHeading = GetControllerHeading(0.0f);
    }
    else
    {
      input->mTargetHeading = mOwner->GetHeading();
    }
  }

  mbSelectAttackTarget = false;
}

void cIntelPlayer::CancelRangedAttack()
{
  if (mbSelectAttackTarget)
  {
    mbSelectAttackTarget = false;

    cGameObject* pTarget = mOwner->GetRegistry()->Fetch(mSelectAttackTargetID);
    if (pTarget)
    {
      cGraphicActor* pTargetGraphic = prop_cast<cGraphicActor*>(pTarget->GetGraphic());
      if (pTargetGraphic)
      {
        pTargetGraphic->RemoveEffect(1, s_targetEffectID);
      }
    }
  }
}

void cIntelPlayer::StartSpellCast(tGameID spellID)
{
  SyAssert(spellID != ID_NONE);

  if (mbSelectAttackTarget)
  {
    return;
  }

  SyAssert(spellID != ID_NONE);
  const cSpellMaster* pSpell = mOwner->GetTitan()->GetDatabaseSys()->GetSpellMaster(spellID);
  SyAssert(pSpell);

  if (!pSpell)
  {
    return;
  }

  if (mSelectAttackSpellID == spellID && mTimeSinceSpellCast + 0.00001f < pSpell->mRefreshTime)
  {
    return;
  }

  if ((ID_NONE != pSpell->mProjectileTypeID && cSpellMaster::SPELL_DELIVERY_METEOR_SHOWER != pSpell->mDelivery) ||
      cSpellMaster::SPELL_ORIGIN_TARGET == pSpell->mOrigin ||
      ((cSpellMaster::SPELL_ORIGIN_TARGET_LOCATION == pSpell->mOrigin ||
        cSpellMaster::SPELL_ORIGIN_CASTER_LOCATION == pSpell->mOrigin ||
        cSpellMaster::SPELL_ORIGIN_RANDOM_LOCATION == pSpell->mOrigin) && pSpell->mOffset > 0.0f))
  {
    mbSelectAttackTarget = true;
    mSelectAttackSpellID = spellID;
    mSelectAttackTargetTime = 0.0f;
    mSelectAttackTargetID = ID_NONE;

    if (cSpellMaster::SPELL_ORIGIN_CASTER_LOCATION == pSpell->mOrigin)
    {
      SyVect3 dir;
      dir.HPR(mOwner->GetHeading(), 0.0f, 0.0f);
      dir *= pSpell->mOffset;
      mSelectAttackLocation = mOwner->GetLocation()+dir;
    }
    else if (cSpellMaster::SPELL_ORIGIN_RANDOM_LOCATION == pSpell->mOrigin)
    {
      SyVect3 dir;
      float heading = mOwner->GetTitan()->RandomFloat()*SY_PI*2.0f;
      dir.HPR(heading, 0.0f, 0.0f);
      dir *= (mOwner->GetTitan()->RandomFloat() * pSpell->mOffset * 0.25f) + (pSpell->mOffset * 0.25f);
      mSelectAttackLocation = mOwner->GetLocation()+dir;
    }
  }
  else
  {
    mSelectAttackSpellID = spellID;
    mTimeSinceSpellCast = 0.0f;
    pSpell->CastSpell(mOwner);
  }
}

void cIntelPlayer::EndSpellCast()
{
  if (!mbSelectAttackTarget)
  {
    return;
  }

  cGameObject* pTarget = mOwner->GetRegistry()->Fetch(mSelectAttackTargetID);
  if (pTarget)
  {
    cGraphicActor* pTargetGraphic = prop_cast<cGraphicActor*>(pTarget->GetGraphic());
    if (pTargetGraphic)
    {
      pTargetGraphic->RemoveEffect(1, s_targetEffectID);
    }
  }

  mbSelectAttackTarget = false;
  mTimeSinceSpellCast = 0.0f;

  SyAssert(mSelectAttackSpellID != ID_NONE);

  const cSpellMaster* pSpell = mOwner->GetTitan()->GetDatabaseSys()->GetSpellMaster(mSelectAttackSpellID);

  SyAssert(pSpell);

  if (!pSpell)
  {
    return;
  }

  if (cSpellMaster::SPELL_ORIGIN_CASTER_LOCATION == pSpell->mOrigin ||
      cSpellMaster::SPELL_ORIGIN_RANDOM_LOCATION == pSpell->mOrigin)
  {
    pSpell->CastSpell(mOwner, NULL, &mSelectAttackLocation);
  }
  else
  {
    cGameObject* pTarget = mOwner->GetTitan()->GetRegistry()->Fetch(mSelectAttackTargetID);
    pSpell->CastSpell(mOwner, pTarget);
  }
}

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, &mLeftStickPrevHeading, &mLeftStickHeldDownTime);
}

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, &mRightStickPrevHeading, &mRightStickHeldDownTime);
}

float                
cIntelPlayer::_ControllerHeading(float time,
                                 float forward,
                                 float right,
                                 float* pPrevHeading,
                                 float* pHeldDownTime)
{
  SyAssert(pPrevHeading!=NULL && pHeldDownTime!=NULL);

  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-(*pPrevHeading));
  *pPrevHeading = heading;

  static const float MAX_CONTROLLER_DELTA = SY_DEG_TO_RAD(360) * time;  

  if (delta_controller > MAX_CONTROLLER_DELTA || delta_controller < -MAX_CONTROLLER_DELTA)
  {
    *pHeldDownTime = 0.0f;
  }

  heading += mOwner->GetTitan()->GetCameraController()->GetControlOffset(*pHeldDownTime);
  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, &mLeftStickHeldDownTime);
}


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, &mRightStickHeldDownTime);
}

float                
cIntelPlayer::_ControllerMagnitude(float forward, float right, float* pHeldDownTime)
{
  SyAssert(pHeldDownTime!=NULL);

  float speed = SY_SQRT(forward*forward + right*right);

  if (speed < mDeadZone)
  {   
    *pHeldDownTime = 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, SY_DEG_TO_RAD(90.0f));
}

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 = 20.0f;
  return cIntelEntity::PickAttackTarget(dir,max_distance, SY_DEG_TO_RAD(20.0f));
}

void
cIntelPlayer::UpdateActionTarget(float time)
{
  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();
  }

  if (mbSelectAttackTarget)
  {
    const cSpellMaster* pSpell = NULL;
    cAnimCharControllerInput *input = static_cast<cGraphicCharacter*>(mOwner->GetGraphic())->GetAnimInput();

    if (mSelectAttackSpellID != ID_NONE)
    {
      pSpell = mOwner->GetTitan()->GetDatabaseSys()->GetSpellMaster(mSelectAttackSpellID);
      SyAssert(pSpell);
    }

    if (pSpell && 
        (cSpellMaster::SPELL_ORIGIN_CASTER_LOCATION == pSpell->mOrigin ||
         cSpellMaster::SPELL_ORIGIN_RANDOM_LOCATION == pSpell->mOrigin))
    {
      SyVect3 vertOffset(0.0f, 2.0f, 0.0f);

      if (GetControllerMagnitude() > MIN_SWIVEL_AMOUNT)
      {
        SyVect3 selectTowards;
        selectTowards.HPR(dir, 0.0f, 0.0f);
        selectTowards.Normalize();

        mSelectAttackLocation.MulAdd(selectTowards, l_TARGETING_AREA_MOVE_SPEED*time);

        SyVect3 toTargetLoc(mSelectAttackLocation - mOwner->GetLocation());
        float mag = toTargetLoc.NormalizeMagn();
        if (mag > pSpell->mOffset)
        {
          mSelectAttackLocation = mOwner->GetLocation();
          mSelectAttackLocation.MulAdd(toTargetLoc, pSpell->mOffset);
        }

        input->mHeadingRequest = dir;
      }

      SyCollRay ray;
      cLOSSceneFilter filter(mOwner->GetRegistry());
      ray.Init(mSelectAttackLocation+vertOffset, mSelectAttackLocation-vertOffset);
      filter.Init(mOwner->GetGraphic()->GetActorHandle());
      
      if (mOwner->GetTitan()->GetScene()->Collide(ray, filter))
      {
        mSelectAttackLocation.Y = ray.GetHitPoint().Y;
      }

      if (mSelectAttackTargetTime > l_TARGETING_HOLD_DELAY)
      {
        DEBUGOVERLAY_ENABLECHANNEL("RangedAttack", true);
        DEBUGOVERLAY_DRAWCYLINDER(mOwner->GetID(),
                                  "RangedAttack",
                                  mSelectAttackLocation,
                                  pSpell->mWidth*0.5f,
                                  SyVect3(0.0f, 1.0f, 0.0f),
                                  cDebugOverlay::ORANGE);
      }
    }
    else
    {
      // calc distance based on how long button has been pressed
      float distance = 20.0f + (mSelectAttackTargetTime>l_TARGETING_HOLD_DELAY?20.0f:0.0f);
      tGameObjectID newTargetID;
      
      if (mSelectAttackTargetTime > l_TARGETING_HOLD_DELAY)
      {
        newTargetID = cIntelEntity::PickAttackTarget(dir, distance, SY_DEG_TO_RAD(90.0f));
      }
      else
      {
        newTargetID = cIntelEntity::PickAttackTarget(dir, distance, SY_DEG_TO_RAD(30.0f));
      }

      cGameObject* pNewTarget = mOwner->GetRegistry()->Fetch(newTargetID);

      if (pNewTarget && !pNewTarget->GetStats()->IsDead())
      {
        if (newTargetID != mSelectAttackTargetID)
        {
          // if we've acquired a new target, test for it's validity 
          // (on screen and not seen through walls)
          SyCamera* pCam = mOwner->GetTitan()->GetCameraController()->GetCamera();
          float testScreenRadius = SY_MIN(pNewTarget->GetPhysics()->GetCollisionRadius()*6.0f, 1.0f);
          if (pCam && pCam->GetFrustum().Cull(pNewTarget->GetLocation(), testScreenRadius) == 0)
          {
            SyCollRay ray;
            cLOSSceneFilter filter(mOwner->GetRegistry());
            SyVect3 eyeHeight(0.0f, AI_EYE_HEIGHT, 0.0f);
            ray.Init(mOwner->GetLocation()+eyeHeight, pNewTarget->GetLocation()+eyeHeight);
            filter.Init(mOwner->GetGraphic()->GetActorHandle());

            if (mOwner->GetTitan()->GetScene()->Collide(ray, filter) == 0)
            {
              input->mHeadingRequest = mOwner->GetHeadingTowards(pNewTarget);
              
              if (mSelectAttackTargetTime > l_TARGETING_HOLD_DELAY)
              {
                cGameObject* pOldTarget = mOwner->GetRegistry()->Fetch(mSelectAttackTargetID);
                if (pOldTarget)
                {
                  cGraphicActor* pOldTargetGraphic = prop_cast<cGraphicActor*>(pOldTarget->GetGraphic());
                  if (pOldTargetGraphic)
                  {
                    pOldTargetGraphic->RemoveEffect(1, s_targetEffectID);
                  }
                }

                cGraphicActor* pNewTargetGraphic = prop_cast<cGraphicActor*>(pNewTarget->GetGraphic());
                if (pNewTargetGraphic)
                {
                  pNewTargetGraphic->AddEffect(1, s_targetEffectID);
                }
              }

              mSelectAttackTargetID = newTargetID;
            }
          }
        }
        else if ((mSelectAttackTargetTime > l_TARGETING_HOLD_DELAY) &&
                 ((mSelectAttackTargetTime-time) <= l_TARGETING_HOLD_DELAY))
        {
          cGraphicActor* pNewTargetGraphic = prop_cast<cGraphicActor*>(pNewTarget->GetGraphic());
          if (pNewTargetGraphic)
          {
            pNewTargetGraphic->AddEffect(1, s_targetEffectID);
          }
        }
      }
    }
  }
  else
  {
    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),
  mLOSTicketLedgeTest(-1),
  mbHitLedge(false),
  mBlockTimer(0.0f),
  mSpawnRadius(0.0f),
  mSpawnChainRadius(0.0f),
  mbSpawned(false)
{
  InitPropObject( mCLASSID );
}

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

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

  delete mAi;
}

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

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

  AddFloat32Property(mCLASSID, 0x2000, SyMemberOffset( cIntelNPC, mSpawnRadius ), "mSpawnRadius");
  AddStringProperty(mCLASSID, 0x2001, SyMemberOffset( cIntelNPC, mSpawnParent ), "mSpawnParent");
  AddFloat32Property(mCLASSID, 0x2002, SyMemberOffset( cIntelNPC, mSpawnChainRadius ), "mSpawnChainRadius");
  AddBoolProperty(mCLASSID, 0x2003, SyMemberOffset( cIntelNPC, mbSpawned ), "mbSpawned");

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

  if (mSpawnParent.Length() <= 0 && mSpawnRadius < 0.00001f)
  {
    mbSpawned = true;
    mAi->Stand(0);
  }
  else
  {
    SetSpawned(false);
    mAi->Spawn();
    mOwner->GetGraphic()->SetVisible(false); // turned back on by AI
  }
}

void
cIntelNPC::Exit()
{
  Stop();

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

  cAILODManager::RemoveNPC(mOwner);

  cIntelEntity::Exit();
}

void
cIntelNPC::Reset()
{
  Stop();

  if (mAi != NULL)
  {
    mAi->Reset();

    if (!mOwner->GetStats()->IsDead() && mOwner->IsInScope() && !mOwner->IsRemote())
    {
      cAILODManager::AddNPC(mOwner);
    }
  }

  cIntelEntity::Reset();
}

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

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

void cIntelNPC::SetSpawned(bool bSpawned)
{
  mbSpawned = bSpawned;

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

  CheckWater();

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

  bool bIsDead = pStats->IsDead();

  if (!bIsDead)
  {
    UpdateBlock(time);

    if (mBlockTimer <= 0.0f && pStats->CalcMovementSpeed() > 0)
    {
      UpdateMove(time);
      SyWaterSystem * pWaterSystem = mOwner->GetTitan()->GetScene()->GetWaterSystem();
      if (!mbIsStuck && pWaterSystem)
      {
        if ( mMoveSpeed > 0.1f )
          pWaterSystem->DemoPlunk( mOwner->GetTitan()->GetScene()->GetActorLocation( mOwner->GetGraphic()->GetActorHandle() ), 0.75f );
        else if ( mMoveSpeed > 0.09f )
          pWaterSystem->DemoPlunk( mOwner->GetTitan()->GetScene()->GetActorLocation( mOwner->GetGraphic()->GetActorHandle() ), 0.22f );
      }
    }
  }
  else
  {
    Stop();
  }
}

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

    SyAssert(prop_cast<cGraphicCharacter*>(mOwner->GetGraphic()) != NULL);
    cGraphicCharacter *pGraphic = static_cast<cGraphicCharacter*>(mOwner->GetGraphic());
    SyAssert(pGraphic != NULL);
    cAnimCharControllerInput *pInput = pGraphic->GetAnimInput();

    if (mBlockTimer <= 0.0f)
    {
      mBlockTimer = 0.0f;
      pInput->mBlockRequest = false;
    }
    else
    {
      pInput->mBlockRequest = true;
    }
  }
}

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 = 2.5f;
  float goalSpeed = mMoveSpeed;

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

  SyVect3 myLoc(mOwner->GetLocation()), myDir;
  myDir.HPR(mOwner->GetHeadingTowards(mGoalPos), 0.0f, 0.0f);

  
  //
  // Ledge test
  //
  SyVect3 ledgePos, ledgeNormal;
  cLOSTable::LOSStatus losStatus = cLOSTable::LOS_INVALID;

  if (mLOSTicketLedgeTest >= 0) 
  {
    losStatus = cLOSTable::HasLOS(mLOSTicketLedgeTest, &ledgePos, &ledgeNormal);
  }

  if (cLOSTable::LOS_PENDING != losStatus && mLOSTicketLedgeTest > 0)
  {
    cLOSTable::CancelLOS(mLOSTicketLedgeTest);
    mLOSTicketLedgeTest = -1;
  }
  
  if (cLOSTable::LOS_CLEAR == losStatus)
  {
    mbHitLedge = true;
  }
  else if (cLOSTable::LOS_PENDING != losStatus)
  {
    mbHitLedge = false;
  }

  if (mLOSTicketLedgeTest < 0)
  {
    static const float LEDGE_HEIGHT = 3.0f;  
    SyVect3 start(myLoc), end(myLoc);
    float testDist = mOwner->GetPhysics()->GetCollisionRadius() * 6.0f;
    end.MulAdd(myDir, testDist);
    start.Y += LEDGE_HEIGHT;
    end.Y -= LEDGE_HEIGHT;
    cLOSTable::RequestLOS(start, end, false, mLOSTicketLedgeTest);
  }

  //
  // Check for arrival
  //
  if (mGoalPos.DistanceSquared(mOwner->GetLocation()) < AI_DISTANCE_TOLERANCE_SQR)
  {
    Stop();
  }
  else
  {
    // 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)
    {
      DEBUGOVERLAY_DRAWLINE(mOwner->GetID(), "AI", mOwner->GetLocation()+SyVect3(0.0f, AI_EYE_HEIGHT, 0.0f), mGoalPos+SyVect3(0.0f, AI_EYE_HEIGHT, 0.0f), cDebugOverlay::GREEN);
      mbHasLOSToGoal = true;      
      mbIsAvoiding = mbIsStuck || mbStuckAvoid || mbHitLedge;          
    }
    else if (cLOSTable::LOS_BLOCKED == losStatus)
    {
      DEBUGOVERLAY_DRAWLINE(mOwner->GetID(), "AI", mOwner->GetLocation()+SyVect3(0.0f, AI_EYE_HEIGHT, 0.0f), mGoalPos+SyVect3(0.0f, AI_EYE_HEIGHT, 0.0f), cDebugOverlay::RED);
      // 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);
    }
    else
    {
      DEBUGOVERLAY_DRAWLINE(mOwner->GetID(), "AI", mOwner->GetLocation()+SyVect3(0.0f, AI_EYE_HEIGHT, 0.0f), mGoalPos+SyVect3(0.0f, AI_EYE_HEIGHT, 0.0f), cDebugOverlay::YELLOW);
    }

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

    if (mbIsAvoiding)
    { 
      DEBUGOVERLAY_DRAWLINE(mOwner->GetID(), "AI", mOwner->GetLocation()+SyVect3(0.0f, 3.0f, 0.0f), mOwner->GetLocation()+SyVect3(0.0f, 3.0f, 0.0f)+mAvoidDir, cDebugOverlay::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)
//    {
//      goalSpeed = 0.1f;
//    }

    static float const ACCEL_FACTOR = 2.0f;
    goalSpeed = SY_CLAMP(goalSpeed, 0.0f, 1.0f);
    if (goalSpeed > input->mSpeedRequest)
    {
      input->mSpeedRequest += ACCEL_FACTOR*time;
      input->mSpeedRequest = SY_CLAMP(input->mSpeedRequest, 0.0f, goalSpeed);
    }
    else if (goalSpeed < input->mSpeedRequest)
    {
      input->mSpeedRequest -= ACCEL_FACTOR*time;
      input->mSpeedRequest = SY_CLAMP(input->mSpeedRequest, goalSpeed, 1.0f);
    }
  }

  if (mbHitLedge)
  {
    input->mSpeedRequest = 0.0f;
    input->mHeadingRequest = mOwner->GetHeadingTowards(mGoalPos);
  }

  //
  // 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)
    {
      mbStuckAvoid = true;
      CalcAvoidDir(-myDir);
    }
    else
    {
      SyVect3 toGoal(mGoalPos-mOwner->GetLocation());
      toGoal.Normalize();
      toGoal *= 0.2f;
      mAvoidDir += toGoal;
      mAvoidDir.Normalize();
    }

    DEBUGOVERLAY_DRAWLINE(mOwner->GetID(), "AI", myLoc+SyVect3(0.0f, 5.0f, 0.0f), myLoc+SyVect3(0.0f, 5.0f, 0.0f)+mAvoidDir, cDebugOverlay::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, AI_EYE_HEIGHT, 0.0f);
  float goalDist = goalDir.NormalizeMagn();

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

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

void
cIntelNPC::CalcAvoidDir(const SyVect3& hitNormal)
{
  SyAssert(hitNormal.Magnitude() > 0.0001f);
  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)
{
  eAnimState animState = static_cast<cGraphicCharacter*>(mOwner->GetGraphic())->GetAnimController()->GetAnimState();
  if (AS_TURNTO == animState || AS_WALKTO == animState)
  {
    return; //NPC receiving cutscene commands, ignore
  }

  if (mbIsMoving)
  {
    return;
  }

  SyVect3 lookAtDir(lookAt-mOwner->GetLocation());
  lookAtDir.Y = 0.0f;
  float dist = lookAtDir.NormalizeMagn();

  if (dist > 0.1f)
  {
    cGraphicCharacter *graphic =prop_cast<cGraphicCharacter*>(mOwner->GetGraphic());
    SyAssert(graphic != NULL);

    cAnimCharControllerInput *input = graphic->GetAnimInput();

    input->mHeadingRequest = mOwner->GetHeadingTowards(mOwner->GetLocation()+lookAtDir);
    input->mSpeedRequest = 0.0f;
  }
}

void                  
cIntelNPC::GoTo(const SyVect3 &dest, float speed, bool bKeepFacing, float faceHeading)
{
  eAnimState animState = static_cast<cGraphicCharacter*>(mOwner->GetGraphic())->GetAnimController()->GetAnimState();
  if (AS_TURNTO == animState || AS_WALKTO == animState)
  {
    return; //NPC receiving cutscene commands, ignore
  }

  if (mbIsMoving && mGoalPos.DistanceSquared(dest) < AI_DISTANCE_TOLERANCE_SQR)
  {
    return;
  }

  cGraphicCharacter *pGraphic = prop_cast<cGraphicCharacter*>(mOwner->GetGraphic());
  SyAssert(pGraphic != NULL);

  if (bKeepFacing &&
      (pGraphic->GetAnimController()->GetAnimHandle(ANIM_WALK_BACKPEDAL) == -1 ||
       pGraphic->GetAnimController()->GetAnimHandle(ANIM_WALK_STRAFE_RIGHT) == -1 ||
       pGraphic->GetAnimController()->GetAnimHandle(ANIM_WALK_STRAFE_LEFT) == -1))
  {
    Stop();
    return;
  }

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

  cAnimCharControllerInput *input = pGraphic->GetAnimInput();
  input->mHeadingRequest = heading;
  input->mbKeepFacing = bKeepFacing;
  input->mFaceHeading = faceHeading;

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

bool                  
cIntelNPC::AttackMelee(cGameObject *target, eComboType comboRequested)
{
  cGraphicCharacter *graphic = static_cast<cGraphicCharacter*>(mOwner->GetGraphic());
  cAnimCharControllerInput *input = graphic->GetAnimInput();

  float attackRange = graphic->GetAnimController()->GetRange(comboRequested);
  float distance = mOwner->GetDistance(target);

  if (distance > attackRange)
  {
    return false;
  }

  if (target->GetType() == cGameObject::OBJ_PLAYER)
  {
    mOwner->GetTitan()->SetGlobalAIAttackTimer();
  }

  input->mTarget = target->GetID();
  input->mAttackRequestL = true;
  input->mAttackRequestS = false;
  input->mNPCComboOverride = comboRequested;

  return true;
}

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 pitch, float range)
{
  cGraphicCharacter *graphic = prop_cast<cGraphicCharacter*>(mOwner->GetGraphic());
  SyAssert(graphic != NULL);
  cAnimCharControllerInput *input = graphic->GetAnimInput();
  input->mAttackRequestRanged = true;
  input->mTarget = ID_NONE;
  input->mTargetHeading = heading;
  input->mTargetPitch = pitch;
  input->mTargetRange = range; 
}

void
cIntelNPC::Stop()
{
  eAnimState animState = static_cast<cGraphicCharacter*>(mOwner->GetGraphic())->GetAnimController()->GetAnimState();
  if (AS_TURNTO == animState || AS_WALKTO == animState)
  {
    return; //NPC receiving cutscene commands, ignore
  }

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

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

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

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

  SyAssert(mAi!=NULL);

  if (mAi)
  {
    mAi->OnHit(attacker, damage, recoveryTime, bRanged);
  }
}

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::OnDeath()
{
  if (mOwner->IsInScope())
  {
    cAILODManager::RemoveNPC(mOwner);
  }
}

void
cIntelNPC::EnterScope()
{
  cAILODManager::AddNPC(mOwner);
}

void cIntelNPC::ExitScope()
{
  Stop();
  mAi->Reset();
  cAILODManager::RemoveNPC(mOwner);
}

void
cIntelNPC::OnLoseOwnership()
{
  cAILODManager::RemoveNPC(mOwner);
}

void 
cIntelNPC::OnGainOwnership()
{
  cAILODManager::AddNPC(mOwner);
}

//------------------------------------ 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()
: mbOn(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::OnHit(tGameObjectID attacker, int damage, float recoveryTime, bool bRanged)
{
  if (mOwner->IsRemote()) // don't change animation or delete if you don't own the object.
  {
    return;
  }

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

  if (pGraphic && damage > 0)
  {
    cAnimPropControllerInterface* pAnimCtrlr = pGraphic->GetAnimController();
    // if prop has no anims, it may not have an animcontroller
    if (pAnimCtrlr)
    {
      pAnimCtrlr->ChangeAnimState(cAnimPropControllerInterface::PAS_DAMAGED1);
    }
  }
}

void cIntelProp::OnDeath()
{
  if (mOwner->IsRemote()) // don't change animation or delete if you don't own the object.
  {
    return;
  }

  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.
      mOwner->MarkForDelete();
    }
  }
}

void cIntelProp::Activate()
{
  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 (!mbOn)
  {
    mbOn = 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, mOwner->GetLocation(), NULL /*todo? pass in activating obj*/);
      }
      else
      {
        SyAssertf(0,"Unknown treasure set for prop '%s'",mOwner->GetName());
      }    
    }
  }
}

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

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

  cAnimPropControllerInterface* pAnimCtrlr = pGraphic->GetAnimController();

  if (mbOn)
  {
    mbOn = false;

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

  }
};

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

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

  cAnimPropControllerInterface* pAnimCtrlr = pGraphic->GetAnimController();

  if (index >= cAnimPropControllerInterface::PAS_NUM_STATES || index < 0)
  {
    return; // bad index
  }

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


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

  if (!pTarget)
  {
    return false;
  }

  if (pTarget->GetType() == cGameObject::OBJ_PROP)
  {
    if (!pTarget->GetStats()->IsDead() && 
      static_cast<cStatsProp*>(pTarget->GetStats())->GetMaster()->mMaxHealth > 0 &&
      pTarget->GetCarriedBy() == ID_NONE)
    {
      return true;
    }
    else
    {
      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 ||
      pTargetStats->IsDead() ||
      pTargetStats->GetFaction() == NPCFACTION_NEUTRAL ||
      !pTarget->GetGraphic()->GetVisible() ||
      pTargetStats->QueryFlag("Invisible"))
  {
    return false;
  }

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

  if (pMyStats->GetMaster()->mFaction == NPCFACTION_ALLY ||
      pMyStats->GetMaster()->mFaction == NPCFACTION_MONSTER)
  {
    return pTargetStats->GetFaction() != pMyStats->GetMaster()->mFaction;
  }

  return true;
}

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

  if (!pTarget)
  {
    return false;
  }

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

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

  if (pMyStats->GetMaster()->mFaction == NPCFACTION_ALLY ||
      pMyStats->GetMaster()->mFaction == NPCFACTION_MONSTER)
  {
    cStatsCharacter* pTargetStats = static_cast<cStatsCharacter*>(pTarget->GetStats());

    return pTargetStats->GetFaction() == pMyStats->GetMaster()->mFaction;
  }

  return false;
}


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

cIntelProjectile::cIntelProjectile()
: mTargetID(ID_NONE),
  mWave(0.0f)
{
  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 toTargetHPR, toTargetDir(pTarget->GetLocation()-mOwner->GetLocation());
        SyVect3 vel(pPhysics->GetVelocity());
        float speed = vel.NormalizeMagn();
        
        if (SY_FABS(toTargetDir.Y) < 1.25f)
        {
          toTargetDir.Y = 0.0f;
        }

        if (speed < 0.00001f)
        {
          vel = toTargetDir;
          speed = pStats->GetMaster()->mSpeed;
        }

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

        float arc = SY_DEG_TO_RAD(pStats->GetMaster()->mSeeking)*time;
        SyVect3 curVelHPR;
        vel.HPR(&curVelHPR.X, &curVelHPR.Y, &curVelHPR.Z);

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

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

        float deltaZ = AngleDifference(curVelHPR.Z, toTargetHPR.Z);
        if (deltaZ < -arc)
        {
          curVelHPR.Z += arc;
        }
        else if (deltaZ > 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
    {
      mTargetID = cIntelEntity::PickAttackTarget(mOwner->GetHeading(), pStats->GetMaster()->mHomingRange, SY_DEG_TO_RAD(180.0f));
    }
  }
  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);
      }
    }
  }
  
  if (pStats->GetMaster()->mVerticalWave)
  {
    static const float WAVE_RADIANS_PER_SEC = SY_PI*0.5f;

    float oldWave = SY_SIN(mWave) * pStats->GetMaster()->mVerticalWave;

    mWave += WAVE_RADIANS_PER_SEC * time;

    float newWave = SY_SIN(mWave) * pStats->GetMaster()->mVerticalWave;

    if (mWave > (SY_PI*2.0f))
    {
      mWave -= SY_PI*2.0f;
    }

    SyVect3 loc(mOwner->GetLocation());

    loc.Y = loc.Y - oldWave + newWave;   
    mOwner->SetLocation(loc);
  }
}

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

bool
cIntelProjectile::IsTargetable(cGameObject* pTarget)
{
  SyAssert(prop_cast<cPhysicsProjectile*>(mOwner->GetPhysics()) != NULL);
  
  bool bTargetable = false;

  tGameObjectID sourceID = static_cast<cPhysicsProjectile*>(mOwner->GetPhysics())->GetSourceID();

  if (ID_NONE != sourceID)
  {
    cGameObject* pSource = mOwner->GetTitan()->GetRegistry()->Fetch(static_cast<cPhysicsProjectile*>(mOwner->GetPhysics())->GetSourceID());
  
    if (pSource)
    {
      bTargetable = pSource->GetIntel()->IsTargetable(pTarget);
    }
    else
    {
      bTargetable = cIntelEntity::IsTargetable(pTarget);
    }
  }
  else
  {
    bTargetable = cIntelEntity::IsTargetable(pTarget);
  }

  if (!bTargetable)
  {
    return false;
  }

  static const tGameID HEALTH_DROP_PROJECTILE = SyHashResourceID("HealthDropProjectile");
  static const tGameID MANA_DROP_PROJECTILE = SyHashResourceID("ManaDropProjectile");
  static const tGameID ESSENCE_DROP_PROJECTILE = SyHashResourceID("EssenceDropProjectile");
  tGameID masterID = static_cast<cStatsProjectile*>(mOwner->GetStats())->GetMaster()->mID.GetID();

  if (HEALTH_DROP_PROJECTILE == masterID)
  {
    if (pTarget && pTarget->GetType() == cGameObject::OBJ_PLAYER)
    {
      bool bTargetable = static_cast<cStatsCharacter*>(pTarget->GetStats())->GetHealth() < static_cast<cStatsCharacter*>(pTarget->GetStats())->CalcMaxHealth();
      if (!bTargetable && mTargetID == pTarget->GetID())
      {
        mOwner->GetPhysics()->SetVelocity(SyVect3(0.0f, 0.0f, 0.0f));
      }
      return bTargetable;
    }
    else
    {
      return false;
    }
  }
  else if (MANA_DROP_PROJECTILE == masterID)
  {
    if (pTarget && pTarget->GetType() == cGameObject::OBJ_PLAYER)
    {
      bool bTargetable = static_cast<cStatsCharacter*>(pTarget->GetStats())->GetMana() < static_cast<cStatsCharacter*>(pTarget->GetStats())->CalcMaxMana();
      if (!bTargetable && mTargetID == pTarget->GetID())
      {
        mOwner->GetPhysics()->SetVelocity(SyVect3(0.0f, 0.0f, 0.0f));
      }
      return bTargetable;
    }
    else
    {
      return false;
    }
  }
  else if (ESSENCE_DROP_PROJECTILE == masterID)
  {
     return pTarget && pTarget->GetType() == cGameObject::OBJ_PLAYER;
  }
  else
  {
    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();
}

void Intel_RegisterTuningVariables()
{
  cIntelEntity::RegisterTuningVariables();
  cIntelPlayer::RegisterTuningVariables();
}
// EOF
