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

//-------------------------------------------------------- Includes
#include "aigoal_noncombat.h"

#include "..\intel.h"
#include "..\stats.h"
#include "..\graphic.h"
#include "..\registry.h"
#include "..\titan.h"
#include "..\cameracontroller.h"
#include "..\gameerror.h"
#include "SyCamera.h"
#include "SyCollRay.h"
#include "SyScene.h"

#include "aiblackboard.h"
#include "lostable.h"
#include "../debugoverlay.h"
#include "../gameerror.h"

//---------------------------------------------- Class Declarations

//------------------------------------------- cGoal_Return


void cGoal_Goto::Update(float time)
{
  static const float CLOSE_ENOUGH = 1.0f; 

  cGameObject *obj = GetGameObject();
  if (mDestination.Distance(obj->GetLocation()) < CLOSE_ENOUGH)
  {
    mpOwner->Pop(this);
    return;
  }

  float speed = mSpeed;
  if (speed <= 0.0f || speed > 1.0f)
  {
    speed = static_cast<cStatsCharacter*>(GetGameObject()->GetStats())->GetAIMovementSpeed();
  }

  if (!GetIntel()->IsAvoiding())
  {
    GetIntel()->GoTo(mDestination, speed);
  }
}


//------------------------------------------- cGoal_Patrol

cGoal_Patrol::cGoal_Patrol()
: mCurWaypoint(-1),
  mCurWait(0.0f),
  mbLoop(false)
{
}


cGoal_Patrol::~cGoal_Patrol()
{
  mWaypoints.Clear();
  mCurWaypoint = -1;
}

void cGoal_Patrol::AddWaypoint(const SyVect3& markerLoc, float speed, float pauseTime)
{
  Waypoint waypt;
  waypt.mLoc = markerLoc;
  waypt.mPauseTime = pauseTime;
  waypt.mSpeed = speed;

  mWaypoints.Add(waypt);
}

void cGoal_Patrol::ClearWaypoints()
{
  mWaypoints.Clear();
  mCurWaypoint = -1;
}

void cGoal_Patrol::SetLooping(bool bLoop)
{
  mbLoop = bLoop;
}

void cGoal_Patrol::Update(float time)
{
  SyVect3 myLoc(GetGameObject()->GetLocation());

  if (-1 == mCurWaypoint && mWaypoints.Size() > 0)
  {
    if (mbLoop)
    {
      // find closest waypt to start
      int closestWaypt = -1;
      float distSqr, closestDistSqr = 0.0f;
      for (int i=mWaypoints.Begin(); i!=mWaypoints.End(); i=mWaypoints.Next(i))
      {
        distSqr = mWaypoints(i).mLoc.DistanceSquared(myLoc);

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

      SyAssert(closestWaypt >= 0);
      mCurWaypoint = closestWaypt;
    }
    else
    {
      mCurWaypoint = 0;
    }
  }

  if (mCurWaypoint >= 0 && mCurWaypoint < mWaypoints.Size())
  {
    if (mWaypoints(mCurWaypoint).mLoc.DistanceSquared(myLoc) < 0.5f * 0.5f)
    {
      if ((mCurWaypoint < (mWaypoints.Size() - 1)) || mbLoop)
      {
        mCurWaypoint = (mCurWaypoint + 1) % mWaypoints.Size();
      }
      else
      {
        mpOwner->ChangePriority(this, 0);
      }
    }

    GetIntel()->GoTo(mWaypoints(mCurWaypoint).mLoc, mWaypoints(mCurWaypoint).mSpeed);
  }
}


//------------------------------------------- cGoal_Stand
cGoal_Stand::cGoal_Stand(float waitTime)
: mWaitTime(waitTime)
{
}

void cGoal_Stand::Update(float time)
{
  if (mWaitTime >= 0.0f)
  {
    if (mWaitTime > time)
    {
      mWaitTime -= time;
    }
    else
    {
      mpOwner->Pop(this);
    }
  }
  
  cIntelNPC *intel = GetIntel();
  intel->Stop();
}

//------------------------------------------- cGoal_Spawn

bool cGoal_Spawn::smbChainSpawnThisFrame = false;

cGoal_Spawn::cGoal_Spawn()
: mParentID(ID_NONE),
  mbSpawn(false)
{
}

void cGoal_Spawn::Enter()
{
  mbSpawn = false;

  if (GetIntel()->GetSpawnParent().Length() > 0)
  {
    cGameObject* pParent = GetRegistry()->Fetch(GetIntel()->GetSpawnParent().AsChar());

    if (pParent)
    {
      mParentID = pParent->GetID();
      GAME_ASSERT(ERROR_DESIGN, pParent->GetType() == cGameObject::OBJ_NPC, "NPC's mSpawnParent field '%s' must refer to an NPC.", GetIntel()->GetSpawnParent().AsChar());
    }
    else
    {
      GAME_ASSERT(ERROR_DESIGN, false, "Could not find NPC named '%s' to use as another NPC's mSpawnParent.", GetIntel()->GetSpawnParent().AsChar());
    }
  }
}

void cGoal_Spawn::Update(float time)
{
  cGameObjectRegistry* pRegistry = GetRegistry();
  cGameObject* pOwner = GetGameObject();
  SyVect3 location = pOwner->GetLocation();

  if (mbSpawn)
  {
    float chainRadius = GetIntel()->GetSpawnChainRadius();
    float chainRadiusSqr = chainRadius*chainRadius;

    if (chainRadiusSqr > 0.0001f)
    {
      DEBUGOVERLAY_DRAWCYLINDER(pOwner->GetID(), "AreaEffect", location, chainRadius, SyVect3(0.0f, 3.0f, 0.0f), cDebugOverlay::WHITE);

      if (!smbChainSpawnThisFrame)
      {
        cGameObject* pNPC = pRegistry->BeginType(cGameObject::OBJ_NPC);

        while (pNPC)
        {
          if (pNPC != pOwner)
          {
            if (pNPC->GetLocation().DistanceSquared(location) <= chainRadiusSqr)
            {
              static_cast<cIntelNPC*>(pNPC->GetIntel())->GetAi()->Spawn(true);
            }
          }

          pNPC = pRegistry->NextType(pNPC);
        }

        smbChainSpawnThisFrame = true;
        Spawn();
      }
    }
    else
    {
      Spawn();
    }
  }
  else
  {
    if (ID_NONE != mParentID)
    {
      cGameObject* pParent = pRegistry->Fetch(mParentID);

      if (pParent)
      {
        cIntelNPC* pParentIntel = prop_cast<cIntelNPC*>(pParent->GetIntel());

        if (pParentIntel)
        {
          mbSpawn = pParentIntel->IsSpawned();
        }
      }
    }
    else
    {
      float radius = GetIntel()->GetSpawnRadius();
      float radiusSqr = radius * radius;

      DEBUGOVERLAY_DRAWCYLINDER(pOwner->GetID(), "AreaEffect", location, radius, SyVect3(0.0f, 3.0f, 0.0f), cDebugOverlay::WHITE);

      cGameObject* pPlayer = pRegistry->BeginType(cGameObject::OBJ_PLAYER);

      while (pPlayer && !mbSpawn)
      {
        if (pPlayer->GetLocation().DistanceSquared(location) < radiusSqr)
        {
          mbSpawn = true;
        }

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

void cGoal_Spawn::Spawn()
{
  cGameObject* pOwner = GetGameObject();

  pOwner->GetStats()->Summon();

  GetIntel()->SetSpawned(true);
  mpOwner->Pop(this);
}


//------------------------------------------- cGoal_Flee
cGoal_Flee::cGoal_Flee(tGameObjectID target, float time, float goalDistance)
: mTargetID(target),
  mTimer(0.0f),
  mGoalDist(goalDistance),
  mbUseAvoidPosition(false),
  mAvoidPosition(0.0f, 0.0f, 0.0f)
{
  if (time <= 0.0f)
  {
    mTimer = -1.0f;
  }
  else
  {
    mTimer = time;
  }
}

cGoal_Flee::cGoal_Flee(const SyVect3& pos, float time, float goalDistance)
: mTargetID(ID_NONE),
  mTimer(0.0f),
  mGoalDist(goalDistance),
  mbUseAvoidPosition(true),
  mAvoidPosition(pos)
{
  if (time <= 0.0f)
  {
    mTimer = -1.0f;
  }
  else
  {
    mTimer = time;
  }
}
cGameObject* cGoal_Flee::GetTarget()
{
  return GetRegistry()->Fetch(mTargetID);
}

void cGoal_Flee::Update(float time)
{
  if (mTimer >= 0.0f)
  {
    if (mTimer > time)
    {
      mTimer -= time;
    }
    else
    {
      mTimer = 0.0f;
      mpOwner->Pop(this);
      return;
    }
  }

  cGameObject* pTarget = GetTarget();

  if ((ID_NONE != mTargetID && !pTarget) ||
      (pTarget && pTarget->GetStats()->IsDead()) ||
      GetGameObject()->GetStats()->IsDead())
  {
    mpOwner->Pop(this);
    return;
  }

  SyVect3 fleeLoc, fleeDir;
  float speed = ((cStatsCharacter*)GetGameObject()->GetStats())->GetAIMovementSpeed();

  if (!pTarget)
  {
    mpOwner->CalculateFleeDirection(NULL, fleeDir);
  }
  else
  {
    if (mbUseAvoidPosition)
    {
      fleeLoc = mAvoidPosition;
      fleeDir = GetGameObject()->GetLocation()-fleeLoc;
      fleeDir.Normalize();
    }
    else
    {
      fleeLoc = pTarget->GetLocation();
      mpOwner->CalculateFleeDirection(pTarget, fleeDir);
    }

    if (GetGameObject()->GetLocation().DistanceSquared(fleeLoc) >
        mGoalDist*mGoalDist)
    {
      // if we're one a timer, don't expire, the timer will do that instead
      if (mTimer < 0.0f)
      {
        mpOwner->Pop(this);
      }
      return;
    }
  }

  if (!GetIntel()->IsAvoiding())
  {
    fleeDir *= 5.0f;
    GetIntel()->GoTo(GetGameObject()->GetLocation()+fleeDir, speed);
  }
}

//------------------------------------------- cGoal_Wander
cGoal_Wander::cGoal_Wander(float time)
{
  if (time <= 0.0f)
  {
    mTimer = -1.0f;
  }
  else
  {
    mTimer = time;
  }
}

void cGoal_Wander::Enter()
{
  mWander.Init(GetGameObject(), 0.8f, 0.2f);
}

void cGoal_Wander::Update(float time)
{
  if (GetGameObject()->GetStats()->IsDead())
  {
    mpOwner->Pop(this);
    return;
  }

  if (mTimer >= 0.0f)
  {
    if (mTimer > time)
    {
      mTimer -= time;
    }
    else
    {
      mTimer = 0.0f;
      mpOwner->Pop(this);
      return;
    }
  }

  static const float WANDER_CIRCLE_RADIUS = 2.0f;
  mWander.MoveNPC(GetGameObject(), 0.01f, WANDER_CIRCLE_RADIUS);
}


//------------------------------------------- cGoal_WanderNear
cGoal_WanderNear::cGoal_WanderNear(const SyVect3& targetLoc, float dist, float time)
: cGoal_Wander(time),
  mTargetLoc(targetLoc),
  mTargetDist(dist)
{
}



void cGoal_WanderNear::Enter()
{
  mWander.Init(GetGameObject(), 0.4f, 0.2f);

  if (0.0f == mTargetLoc.X && 0.0f == mTargetLoc.Y && 0.0f == mTargetLoc.Z)
  {
    mTargetLoc = GetGameObject()->GetLocation();
  }
  else
  {
    SyVect3 hpr, toTarget(mTargetLoc-GetGameObject()->GetLocation());
    toTarget.Normalize();
    hpr.HPR(toTarget.X, toTarget.Y, toTarget.Z);
    mWander.SetHPR(hpr);
  }
}

void cGoal_WanderNear::Update(float time)
{
  static const float WANDER_CIRCLE_RADIUS = 1.5f;

  if (GetGameObject()->GetStats()->IsDead())
  {
    mpOwner->Pop(this);
    return;
  }

  if (mTimer >= 0.0f)
  {
    if (mTimer > time)
    {
      mTimer -= time;
    }
    else
    {
      mTimer = 0.0f;
      mpOwner->Pop(this);
      return;
    }
  }

  float distToTarget = GetGameObject()->GetLocation().Distance(mTargetLoc);

  if (distToTarget > mTargetDist && !GetIntel()->IsAvoiding())
  {
    SyVect3 hpr, toTarget(mTargetLoc-GetGameObject()->GetLocation());
    toTarget.Normalize();
    hpr.HPR(toTarget.X, toTarget.Y, toTarget.Z);
    mWander.SetHPR(hpr);
  }

  mWander.MoveNPC(GetGameObject(), 0.05f, WANDER_CIRCLE_RADIUS);
}

//------------------------------------------- cGoal_OnFire
cGoal_OnFire::cGoal_OnFire()
{
}

void cGoal_OnFire::Enter()
{
  mWander.Init(GetGameObject(), 1.0f, 0.1f);
}

void cGoal_OnFire::Update(float time)
{
  static const float WANDER_CIRCLE_RADIUS = 2.5f;

  if (GetGameObject()->GetStats()->IsDead())
  {
    mpOwner->Pop(this);
    return;
  }

  mWander.MoveNPC(GetGameObject(), 1.0f, WANDER_CIRCLE_RADIUS);
}



//------------------------------------------- cLocationSnapshotTaker
bool cLocationSnapshotTaker::TargetSnapshot::Approx(const cLocationSnapshotTaker::TargetSnapshot& ss) const
{
  static const float SNAPSHOT_TOLERANCE_SQR = 3.0f * 3.0f;

  return mLocation.DistanceSquared(ss.mLocation) < SNAPSHOT_TOLERANCE_SQR;
}

cLocationSnapshotTaker::cLocationSnapshotTaker()
: mTargetData(25),
  mTimeSinceLastSnapshot(0.0f)
{
}

void cLocationSnapshotTaker::Update(cGameObject* pTarget, float time)
{
  SyAssert(pTarget != NULL);

  static const float SNAPSHOT_TIME = 2.5f;

  if (mTimeSinceLastSnapshot > SNAPSHOT_TIME)
  {
    TakeSnapshot(pTarget);
    mTimeSinceLastSnapshot = 0.0f;
  }
  else
  {
    mTimeSinceLastSnapshot += time;
  }
}

void cLocationSnapshotTaker::TakeSnapshot(cGameObject* pTarget)
{
  SyAssert(pTarget != NULL);
  SyVect3 targetLoc(pTarget->GetLocation());

  SyScene* pScene = pTarget->GetTitan()->GetScene();
  SyCollRay ray;
  cLOSSceneFilter filter(pTarget->GetRegistry());

  ray.Init(targetLoc+SyVect3(0.0f, 2.0f, 0.0f), targetLoc+SyVect3(0.0f, -20.0f, 0.0f));
  filter.Init();

  if (pScene->Collide(ray, filter) > 0)
  {
    TargetSnapshot ss;
    ss.mLocation = ray.GetHitPoint();
    ss.mLocation.Y += 0.2f;
    ss.mTimestamp = pTarget->GetTitan()->GetTime();
    mTargetData.Add(ss);
  }
}

bool cLocationSnapshotTaker::FindTeleportSnapshot(cGameObject* pTarget,
                                                  const SyFrustum& frustum,
                                                  TargetSnapshot& ss)
{
  SyAssert(pTarget != NULL);

  if (!pTarget)
  {
    return false;
  }

  SyVect3 targetLoc(pTarget->GetLocation());
  int bestIndex = -1;
  float distSqr, bestDistSqr = 0.0f;

  for (int i=0; i<mTargetData.Size(); ++i)
  {
    distSqr = targetLoc.DistanceSquared(mTargetData(i).mLocation);

    if ((frustum.Cull(mTargetData(i).mLocation, 3.0f) != 0) &&
        (-1 == bestIndex || distSqr < bestDistSqr))
    {
      bestDistSqr = distSqr;
      bestIndex = i;
    }
  }

  if (bestIndex >= 0)
  {
    ss = mTargetData(bestIndex);
    return true;
  }

  return false;
}

bool cLocationSnapshotTaker::FindNearestSnapshot(const SyVect3& loc, TargetSnapshot& ss)
{
  int bestIndex = -1;
  float distSqr, bestDistSqr = 0.0f;

  for (int i=0; i<mTargetData.Size(); ++i)
  {
    distSqr = loc.DistanceSquared(mTargetData(i).mLocation);

    if (-1 == bestIndex || distSqr < bestDistSqr)
    {
      bestDistSqr = distSqr;
      bestIndex = i;
    }
  }

  if (bestIndex >= 0)
  {
    ss = mTargetData(bestIndex);
    return true;
  }

  return false;
}

bool cLocationSnapshotTaker::FindNextSnapshot(uint64 timestamp, TargetSnapshot& ss)
{
  int bestIndex = -1;
  uint64 deltaTime, bestDeltaTime = 0;

  for (int i=0; i<mTargetData.Size(); ++i)
  {
    deltaTime = mTargetData(i).mTimestamp-timestamp;
    if (deltaTime > 0 &&
        (-1 == bestIndex || deltaTime < bestDeltaTime))
    {
      bestDeltaTime = deltaTime;
      bestIndex = i;
    }
  }

  if (bestIndex >= 0)
  {
    ss = mTargetData(bestIndex);
    return true;
  }

  return false;
}

//------------------------------------------- cGoal_Follow

cGoal_Follow::cGoal_Follow(tGameObjectID targetID, float distance)
: mTargetID(targetID),
  mDistance(distance),
  mbFollowingSnapshots(false),
  mLastFollowedSnapshotTime(0)
{
  SyAssertf(targetID != ID_NONE, "cGoal_Follow must have a target id");
}


void cGoal_Follow::Enter()
{
  mbFollowingSnapshots = false;
  mpOwner->SetDefendTarget(mTargetID);
}

void cGoal_Follow::Exit()
{
}


void cGoal_Follow::Update(float time)
{
  static const float TELEPORT_DISTANCE = 50.0f;
  static const float STOP_DISTANCE = 3.0f;

  cGameObject* pTarget = mpOwner->GetDefendTarget();

  if (!pTarget)
  {
    mpOwner->SetDefendTarget(ID_NONE);
    mpOwner->Pop(this);
    return;
  }

  mSnapshots.Update(pTarget, time);

  SyVect3 targetLoc(pTarget->GetLocation()), lastSeenPos;
  float distToTarget = targetLoc.Distance(GetGameObject()->GetLocation());

  if (distToTarget > TELEPORT_DISTANCE)
  {
    // this may fail, but we'll just try again next update
    Teleport();
  }
  else if (distToTarget < STOP_DISTANCE)
  {
    GetIntel()->Stop();
  }
  else if (!mpOwner->CanSeeDefendTarget())
  {
    if (!GetIntel()->IsAvoiding())
    {
      GotoSnapshot();
    }
  }
  else
  {
    mbFollowingSnapshots = false;

    if (!GetIntel()->IsAvoiding())
    {
      GetIntel()->GoTo(targetLoc, 1.0f);
    }
  }
}

void cGoal_Follow::Teleport()
{
  cGameObject* pTarget = mpOwner->GetDefendTarget();
  SyAssert(pTarget != NULL);

  if (!pTarget)
  {
    return;
  }

  cLocationSnapshotTaker::TargetSnapshot ss;
  if (mSnapshots.FindTeleportSnapshot(pTarget, GetRegistry()->GetTitan()->GetCameraController()->GetCamera()->GetFrustum(), ss))
  {
    GetGameObject()->SetLocation(ss.mLocation);
  }
}

void cGoal_Follow::GotoSnapshot()
{
  SyAssert(!mpOwner->CanSeeDefendTarget());

  SyVect3 lastSeenPos, myLoc(GetGameObject()->GetLocation());
  bool bHasLastSeenPos = mpOwner->GetLastSeenDefendTargetPos(lastSeenPos);

  cLocationSnapshotTaker::TargetSnapshot ss;
  bool bFoundSnapshot = false;

  if (mbFollowingSnapshots)
  {      
    bFoundSnapshot = mSnapshots.FindNextSnapshot(mLastFollowedSnapshotTime, ss);
  }
  else
  {
    bFoundSnapshot = mSnapshots.FindNearestSnapshot(myLoc, ss);
  }

  if (bFoundSnapshot)
  {
    GetIntel()->GoTo(ss.mLocation, 1.0f);

    if (myLoc.DistanceSquared(ss.mLocation) < 1.0f)
    {
      mbFollowingSnapshots = true;
      mLastFollowedSnapshotTime = ss.mTimestamp;
    }
  }
  else if (bHasLastSeenPos)
  {
    GetIntel()->GoTo(lastSeenPos, 1.0f);
  }
  else if (mpOwner->GetDefendTarget()) // worst case, just goto target (but check if he's still there)
  {
    GetIntel()->GoTo(mpOwner->GetDefendTarget()->GetLocation(), 1.0f);
  }
}


//------------------------------------------- cGoal_Hunt

cGoal_Hunt::cGoal_Hunt(tGameObjectID targetID, const SyVect3& startLoc)
: mTargetID(targetID),
  mbFollowingSnapshots(false),
  mLastFollowedSnapshotTime(0),
  mStartLoc(startLoc),
  mSearchTimer(0.0f),
  mRandTime(0.0f)
{
  GAME_ASSERT(ERROR_CODE, targetID != ID_NONE, "cGoal_Hunt must have a target id");
}


void cGoal_Hunt::Enter()
{
  mbVisitedStartLoc = false;
  mbFollowingSnapshots = false;
  mLastFollowedSnapshotTime = 0;
  mSearchTimer = 0.0f;
  mRandTime = 0.0f;
}

void cGoal_Hunt::Exit()
{
}

void cGoal_Hunt::Update(float time)
{
  static const float MAX_HUNT_DISTANCE = 100.0f;

  cGameObject* pTarget = mpOwner->GetAttackTarget();

  if (!pTarget || pTarget->GetID() != mTargetID)
  {
    // target is dead or we've switched to attack someone else
    mpOwner->Pop(this);
    return;
  }

  mSnapshots.Update(pTarget, time);

  SyVect3 targetLoc(pTarget->GetLocation()), lastSeenPos;
  float distToTarget = targetLoc.Distance(GetGameObject()->GetLocation());
  float speed = static_cast<cStatsCharacter*>(GetGameObject()->GetStats())->GetAIMovementSpeed();

  GAME_ASSERT(ERROR_CODE, pTarget->GetType()==cGameObject::OBJ_NPC || pTarget->GetType()==cGameObject::OBJ_PLAYER, "Hunt ai needs character target");

  if (distToTarget > MAX_HUNT_DISTANCE)
  {
    GetIntel()->Stop();
  }
  else if (mpOwner->HasLOSToAttackTarget() && !mpOwner->CanSeeAttackTarget())
  {
    SearchForInvisibleTarget(pTarget, time, speed);
  }
  else if (!mpOwner->HasLOSToAttackTarget())
  {
    if (!mbVisitedStartLoc)
    {
      static const float TOLERANCE_SQR = 4.0f*4.0f;
      if (mStartLoc.DistanceSquared(GetGameObject()->GetLocation()) <= TOLERANCE_SQR)
      {
        mbVisitedStartLoc = true;
      }
      else
      {
        if (!GetIntel()->IsAvoiding())
        {
          GetIntel()->GoTo(mStartLoc, speed);
        }
      }
    }
    else
    {
      if (!GetIntel()->IsAvoiding())
      {
        GotoSnapshot();
      }
    }
  }
  else
  {
    mbFollowingSnapshots = false;

    if (distToTarget >= mpOwner->GetActivationRadius())
    {
      if (!GetIntel()->IsAvoiding())
      {
        GetIntel()->GoTo(pTarget->GetLocation(), speed);
      }
    }
    else
    {
      GetIntel()->Stop();
      GetIntel()->TurnTo(pTarget->GetLocation());
      mpOwner->Pop(this);
    }
  }
}

void cGoal_Hunt::SearchForInvisibleTarget(cGameObject* pTarget, float time, float speed)
{
  if (mSearchTimer == 0.0f)
  {
    mSearchWander.Init(GetGameObject(), 0.6f, 0.05f);
    SyVect3 hpr, toStart(mStartLoc-GetGameObject()->GetLocation());
    toStart.Normalize();
    hpr.HPR(toStart.X, toStart.Y, toStart.Z);
    mSearchWander.SetHPR(hpr);
    mRandTime = ((float)(GetGameObject()->GetTitan()->Random(0, 1000)))*0.001f;
  }

  mSearchTimer += time;

  static float const GOSTRAIGHT_TIME = 1.2f;
  static float const SEARCH_TIME = 4.5f;
  static float const WAIT_TIME = 3.5f;

  if (mSearchTimer > SEARCH_TIME+WAIT_TIME+mRandTime)
  {
    mSearchTimer = 0.0f;
  }
  else if (mSearchTimer > SEARCH_TIME+mRandTime)
  {
    mSearchWander.MoveNPC(GetGameObject(), 0.0f, 0.5f);
  }
  else if (mSearchTimer > GOSTRAIGHT_TIME)
  {
    mSearchWander.MoveNPC(GetGameObject(), speed*0.5f, 1.5f);
  }
  else
  {
    if (!GetIntel()->IsAvoiding())
    {
      GetIntel()->GoTo(mStartLoc, speed*(GOSTRAIGHT_TIME*mSearchTimer + 1)*0.5f);
    }
  }
}

void cGoal_Hunt::GotoSnapshot()
{
  SyVect3 lastSeenPos, myLoc(GetGameObject()->GetLocation());
  bool bHasLastSeenPos = mpOwner->GetLastSeenAttackTargetPos(lastSeenPos);
  float speed = static_cast<cStatsCharacter*>(GetGameObject()->GetStats())->GetAIMovementSpeed();

  cLocationSnapshotTaker::TargetSnapshot ss;
  bool bFoundSnapshot = false;

  if (mbFollowingSnapshots)
  {      
    bFoundSnapshot = mSnapshots.FindNextSnapshot(mLastFollowedSnapshotTime, ss);
  }
  else
  {
    bFoundSnapshot = mSnapshots.FindNearestSnapshot(myLoc, ss);
  }

  if (bFoundSnapshot)
  {
    GetIntel()->GoTo(ss.mLocation, speed);

    if (myLoc.DistanceSquared(ss.mLocation) < 3.0f*3.0f)
    {
      mbFollowingSnapshots = true;
      mLastFollowedSnapshotTime = ss.mTimestamp;
    }
  }
  else if (bHasLastSeenPos)
  {
    GetIntel()->GoTo(lastSeenPos, speed);
  }
  else if (mpOwner->GetAttackTarget()) // worst case, just goto target (but check if he's still there)
  {
    GetIntel()->GoTo(mpOwner->GetAttackTarget()->GetLocation(), speed);
  }
}

//------------------------------------------- cGoal_Defend

cGoal_Defend::cGoal_Defend(tGameObjectID targetID, float distance)
: cGoal_Follow(targetID, distance)  
{
}

void cGoal_Defend::Enter()
{
  cGoal_Follow::Enter();
  cAIBlackboard::Get()->AddRecord(cAIBlackboard::BBR_DEFENDING, GetGameObject()->GetID(), mTargetID);
}

void cGoal_Defend::Exit()
{
  cAIBlackboard::Get()->RemoveRecords(cAIBlackboard::BBR_DEFENDING, GetGameObject()->GetID(), mTargetID);
  cGoal_Follow::Exit();
}

void cGoal_Defend::Update(float time)
{
  static const float TELEPORT_DISTANCE = 50.0f;

  cGameObject* pTarget = GetRegistry()->Fetch(mTargetID);

  if (!pTarget)
  {
    mpOwner->SetDefendTarget(ID_NONE);
    mpOwner->Pop(this);
    return;
  }

  mSnapshots.Update(pTarget, time);

  SyVect3 targetLoc(pTarget->GetLocation()), lastSeenPos;
  float distToTarget = targetLoc.Distance(GetGameObject()->GetLocation());
  float speed = static_cast<cStatsCharacter*>(GetGameObject()->GetStats())->GetAIMovementSpeed();

  if (distToTarget > TELEPORT_DISTANCE)
  {
    // this may fail, but we'll just try again next update
    Teleport();
  }
  else if (pTarget->GetType() != cGameObject::OBJ_PROP && !mpOwner->CanSeeDefendTarget())
  {
    if (!GetIntel()->IsAvoiding())
    {
      GotoSnapshot();
    }
  }
  else
  {
    mbFollowingSnapshots = false;
    MoveToDefendPosition(speed);
  }
}

void cGoal_Defend::MoveToDefendPosition(float speed)
{
  static const float DEFEND_FORWARD_TOLERANCE = 1.5f;
  static const float DEFEND_RIGHT_TOLERANCE = 2.0f;

  // pick locations around the player considering attackers positions
  // or just circle the defend target if there are no attackers

  float defendRadius = mpOwner->GetDefendRadius();
  float maxAttackerRadiusSqr = (defendRadius*2.0f)*(defendRadius*2.0f);
  cIntelNPC* pMyIntel = GetIntel();
  SyVect3 center(0.0f, 0.0f, 0.0f), myLoc(GetGameObject()->GetLocation());
  int numAttackers = 0;

  cGameObjectRegistry* pRegistry = GetGameObject()->GetRegistry();

  SyVect3 toFriendly;

  cGameObject* pObj = pRegistry->BeginType(cGameObject::OBJ_NPC);
  for(;pObj != NULL;pObj= pRegistry->NextType(pObj))
  {
    SyAssert(pObj->GetType()==cGameObject::OBJ_NPC);

    if (!pObj || !pMyIntel->IsTargetable(pObj))
    {
      continue;
    }

    if (pObj->GetLocation().DistanceSquared(myLoc) > maxAttackerRadiusSqr)
    {
      continue;
    }

    ++numAttackers;
    center += pObj->GetLocation();
  }

  SyVect3 adjust(0.0f, 0.0f, 0.0f), right;
  SyVect3 defendLoc(mpOwner->GetDefendTarget()->GetLocation());
  SyVect3 defendToMeDir(myLoc-defendLoc);
  SyVect3 defendToCenterDir;
  cAIBlackboard::Record rec;
  cGameObject* pDefender;
  int numOtherDefenders = 0;
  int defenderOrder = 0;

  if (numAttackers > 0)
  {
    center /= (float)(numAttackers);
    defendToCenterDir = center-defendLoc;
  }
  else
  {
    defendToCenterDir.HPR(mpOwner->GetDefendTarget()->GetHeading(), 0.0f, 0.0f);
  }

  defendToCenterDir.Normalize();
  right.Cross(defendToCenterDir, SyVect3(0.0f, 1.0f, 0.0f));
  right.Normalize();
  float defendToMeDotRight = defendToMeDir^right;

  if (cAIBlackboard::Get()->GetFirstRecord(cAIBlackboard::BBR_DEFENDING, ID_NONE, mTargetID, rec))
  {
    do 
    {
      if (rec.mSourceID != GetGameObject()->GetID())
      {
        pDefender = pRegistry->Fetch(rec.mSourceID);
        if (pDefender)
        {
          ++numOtherDefenders;
          if (((pDefender->GetLocation()-defendLoc)^right) < defendToMeDotRight)
          {
            ++defenderOrder;
          }
        }
      }
    }
    while (cAIBlackboard::Get()->GetNextRecord(rec));
  }

  float adjustForward = (defendRadius*0.5f) - (defendToMeDir^defendToCenterDir);
  float adjustRight = 0.0f;

  if (0 == numOtherDefenders)
  {
    adjustRight = -defendToMeDotRight+(defendToMeDotRight<0.0f?-1.0f:1.0f)*DEFEND_RIGHT_TOLERANCE;    
  }
  else
  {
    float defenderSpace = (defendRadius*2.0f*0.67f) / ((float)(numOtherDefenders+1));
    adjustRight = ((float)defenderOrder)*defenderSpace - (defendRadius*0.67f) + (defenderSpace*0.5f);    
    adjustRight = adjustRight-defendToMeDotRight;
    if ((numOtherDefenders%2) == 1)
    {
      if (defenderOrder == numOtherDefenders/2)
      {
        adjustRight -= defenderSpace * 0.25f;
      }
    }
    else
    {
      if (defenderOrder == numOtherDefenders/2)
      {
        adjustRight += defenderSpace * 0.25f;
      }
      else if (defenderOrder == numOtherDefenders/2 + 1)
      {
        adjustRight -= defenderSpace * 0.25f;
      }
    }
  }
   
  if (SY_FABS(adjustForward) > DEFEND_FORWARD_TOLERANCE ||
      SY_FABS(adjustRight) > DEFEND_RIGHT_TOLERANCE)
  {
    if (!GetIntel()->IsAvoiding())
    {
      right *= adjustRight;
      adjust = defendToCenterDir;
      adjust *= adjustForward;
      adjust += right;
      GetIntel()->GoTo(myLoc+adjust, speed*0.5f);
    }
  }
  else if (numAttackers > 0)
  {
    GetIntel()->TurnTo(center);
  }
  else
  {
    GetIntel()->TurnTo(myLoc+defendToCenterDir);
  }
}

//------------------------------------------- cGoal_ActivateProp

void cGoal_ActivateProp::Update(float time)
{
  cGameObject* pProp = GetGameObject()->GetRegistry()->Fetch(mPropID);

  if (!pProp || pProp->GetStats()->IsDead())
  {
    mpOwner->Pop(this);
    return;
  }

  float dist = GetGameObject()->GetDistance(pProp);

  static const float ACTIVATE_DISTANCE = 1.0f;
  if (dist < ACTIVATE_DISTANCE)
  {
    pProp->Activate(GetGameObject());
    mpOwner->Pop(this);
    return;
  }

  float speed = static_cast<cStatsCharacter*>(GetGameObject()->GetStats())->GetAIMovementSpeed();
  if (!GetIntel()->IsAvoiding())
  {
    GetIntel()->GoTo(pProp->GetLocation(), speed);
  }
}

// EOF
