text: /*
  * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
  * its licensors.
  *
  * For complete copyright and license terms please see the LICENSE at the root of this
  * distribution (the "License"). All use of this software is governed by the License,
  * or, if provided, by the license below or the license accompanying this file. Do not
  * remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  *
  */
  // Original file Copyright Crytek GMBH or its affiliates, used under license.
  
  // Description : Contains an agent's target tracks and handles updating them
  
  
  #include "CryLegacy_precompiled.h"
  #include "TargetTrackGroup.h"
  #include "TargetTrackManager.h"
  #include "TargetTrack.h"
  #include "ObjectContainer.h"
  
  #ifdef TARGET_TRACK_DOTARGETTHREAT
      #include "Puppet.h"
  #endif //TARGET_TRACK_DOTARGETTHREAT
  
  #ifdef TARGET_TRACK_DEBUG
      #include "DebugDrawContext.h"
      #include "IDebugHistory.h"
      #include "IGame.h"
      #include "IGameFramework.h"
  #endif //TARGET_TRACK_DEBUG
  
  //////////////////////////////////////////////////////////////////////////
  CTargetTrackGroup::CTargetTrackGroup(TargetTrackHelpers::ITargetTrackPoolProxy* pTrackPoolProxy, tAIObjectID aiObjectId, uint32 uConfigHash, int nTargetLimit)
      : m_pTrackPoolProxy(pTrackPoolProxy)
      , m_aiObjectId(aiObjectId)
      , m_aiLastBestTargetId(0)
      , m_uConfigHash(uConfigHash)
      , m_nTargetLimit(max(nTargetLimit, 0))
      , m_bNeedSort(false)
      , m_bEnabled(true)
  {
      assert(m_pTrackPoolProxy);
      assert(m_aiObjectId > 0);
      assert(m_uConfigHash > 0);
  
      CAISystem* pAISystem = GetAISystem();
      assert(pAISystem);
  
      CWeakRef<CAIObject> refAIObject = gAIEnv.pObjectContainer->GetWeakRef(aiObjectId);
      const bool bAIObjectValid = refAIObject.IsValid();
  
      // Create dummy representation for the dummy potential target
      //  NB: during serialization, dummy objects are created after loading to
      //  ensure we keep the same AI object id
      if (!gEnv->pSystem->IsSerializingFile())
      {
          gAIEnv.pAIObjectManager->CreateDummyObject(m_dummyPotentialTarget.refDummyRepresentation, "Target Track Dummy");
          if (bAIObjectValid)
          {
              InitDummy();
          }
      }
  
  #ifdef TARGET_TRACK_DEBUG
      m_fLastGraphUpdate = 0.0f;
      m_pDebugHistoryManager = gEnv->pGame->GetIGameFramework()->CreateDebugHistoryManager();
      assert(m_pDebugHistoryManager);
  
      memset(m_bDebugGraphOccupied, 0, sizeof(m_bDebugGraphOccupied));
  #endif //TARGET_TRACK_DEBUG
  }
  
  //////////////////////////////////////////////////////////////////////////
  CTargetTrackGroup::~CTargetTrackGroup()
  {
      Reset();
  
  #ifdef TARGET_TRACK_DEBUG
      SAFE_RELEASE(m_pDebugHistoryManager);
  #endif //TARGET_TRACK_DEBUG
  }
  
  //////////////////////////////////////////////////////////////////////////
  void CTargetTrackGroup::Reset()
  {
      FUNCTION_PROFILER(GetISystem(), PROFILE_AI);
  
      // Add all active tracks back to the pool
      TTargetTrackContainer::iterator itTrack = m_TargetTracks.begin();
      TTargetTrackContainer::iterator itTrackEnd = m_TargetTracks.end();
      for (; itTrack != itTrackEnd; ++itTrack)
      {
          CTargetTrack* pTrack = itTrack->second;
          assert(pTrack);
  
          m_pTrackPoolProxy->AddTargetTrackToPool(pTrack);
      }
  
      m_TargetTracks.clear();
      m_SortedTracks.clear();
      m_bNeedSort = false;
  
  #ifdef TARGET_TRACK_DEBUG
      m_pDebugHistoryManager->Clear();
  #endif //TARGET_TRACK_DEBUG
  }
  
  //////////////////////////////////////////////////////////////////////////
  void CTargetTrackGroup::Serialize_Write(TSerialize ser)
  {
      assert(ser.IsWriting());
  
      int iTrackCount = m_TargetTracks.size();
      ser.Value("iTrackCount", iTrackCount);
  
      TTargetTrackContainer::iterator itTrack = m_TargetTracks.begin();
      TTargetTrackContainer::iterator itTrackEnd = m_TargetTracks.end();
      for (; itTrack != itTrackEnd; ++itTrack)
      {
          tAIObjectID targetId = itTrack->first;
          CTargetTrack* pTrack = itTrack->second;
          assert(targetId > 0 && pTrack);
  
          ser.BeginGroup("Track");
          {
              ser.Value("targetId", targetId);
  
              pTrack->Serialize(ser);
          }
          ser.EndGroup();
      }
  
      m_dummyPotentialTarget.Serialize(ser);
  }
  
  //////////////////////////////////////////////////////////////////////////
  void CTargetTrackGroup::Serialize_Read(TSerialize ser)
  {
      assert(ser.IsReading());
  
      assert(m_TargetTracks.empty());
  
      int iTrackCount = 0;
      ser.Value("iTrackCount", iTrackCount);
  
      for (int iTrack = 0; iTrack < iTrackCount; ++iTrack)
      {
          tAIObjectID targetId;
  
          ser.BeginGroup("Track");
          {
              ser.Value("targetId", targetId);
              assert(targetId != INVALID_AIOBJECTID);
  
              CTargetTrack* pTrack = GetTargetTrack(targetId);
              assert(pTrack);
              if (pTrack)
              {
                  pTrack->Serialize(ser);
              }
          }
          ser.EndGroup();
      }
  
      m_dummyPotentialTarget.Serialize(ser);
  
      InitDummy();
  }
  
  //////////////////////////////////////////////////////////////////////////
  void CTargetTrackGroup::InitDummy()
  {
      CAIObject* pDummyRep = m_dummyPotentialTarget.refDummyRepresentation.GetAIObject();
      assert(pDummyRep);
      if (pDummyRep)
      {
          CWeakRef<CAIObject> refAIObject = gAIEnv.pObjectContainer->GetWeakRef(m_aiObjectId);
          pDummyRep->SetAssociation(refAIObject);
  
          pDummyRep->SetShouldSerialize(false);
      }
  }
  
  //////////////////////////////////////////////////////////////////////////
  void CTargetTrackGroup::Update(TargetTrackHelpers::ITargetTrackConfigProxy* pConfigProxy)
  {
      FUNCTION_PROFILER(GetISystem(), PROFILE_AI);
  
      const float fCurrTime = GetAISystem()->GetFrameStartTimeSeconds();
  
      m_SortedTracks.clear();
      m_SortedTracks.reserve(m_TargetTracks.size());
  
      // Update the tracks
      TTargetTrackContainer::iterator itTrack = m_TargetTracks.begin();
      TTargetTrackContainer::iterator itTrackEnd = m_TargetTracks.end();
      for (; itTrack != itTrackEnd; ++itTrack)
      {
          CTargetTrack* pTrack = itTrack->second;
          assert(pTrack);
  
          if (pTrack->Update(fCurrTime, pConfigProxy))
          {
              m_SortedTracks.push_back(pTrack);
          }
      }
  
      m_bNeedSort = (!m_SortedTracks.empty());
  }
  
  struct TargetTrackSorter
  {
      bool operator()(const CTargetTrack* left, const CTargetTrack* right) const
      {
          if (left && right)
          {
              return *left < *right;
          }
  
          assert(false);
          return left < right;
      }
  };
  
  //////////////////////////////////////////////////////////////////////////
  void CTargetTrackGroup::UpdateSortedTracks(bool bForced)
  {
      if (m_bNeedSort || bForced)
      {
          m_bNeedSort = false;
          std::sort(m_SortedTracks.begin(), m_SortedTracks.end(), TargetTrackSorter());
      }
  }
  
  //////////////////////////////////////////////////////////////////////////
  CTargetTrack* CTargetTrackGroup::GetTargetTrack(tAIObjectID aiTargetId)
  {
      CTargetTrack* pTrack = NULL;
  
      TTargetTrackContainer::iterator itTrack = m_TargetTracks.find(aiTargetId);
      if (itTrack != m_TargetTracks.end())
      {
          pTrack = itTrack->second;
      }
      else
      {
          // Request a new track to use from the pool
          pTrack = m_pTrackPoolProxy->GetUnusedTargetTrackFromPool();
          assert(pTrack);
  
          pTrack->Init(m_aiObjectId, aiTargetId, m_uConfigHash);
          m_TargetTracks[aiTargetId] = pTrack;
  
  #ifdef TARGET_TRACK_DEBUG
          // Create debug history for it
          uint32 uDebugGraphIndex;
          if (FindFreeGraphSlot(uDebugGraphIndex))
          {
              CWeakRef<CAIObject> refTarget = gAIEnv.pObjectContainer->GetWeakRef(aiTargetId);
              CAIObject* pTarget = refTarget.GetAIObject();
              if (pTarget) // might be null in the case of bookmarked entities
              {
                  m_bDebugGraphOccupied[uDebugGraphIndex] = true;
                  pTrack->SetDebugGraphIndex(uDebugGraphIndex);
  
                  IDebugHistory* pDebugHistory = m_pDebugHistoryManager->CreateHistory(pTarget->GetName());
                  if (pDebugHistory)
                  {
                      pDebugHistory->SetVisibility(false);
                      pDebugHistory->SetupScopeExtent(-360.0f, 360.0f, 0.0f, 200.0f);
                  }
              }
          }
  #endif //TARGET_TRACK_DEBUG
      }
  
      assert(pTrack);
      return pTrack;
  }
  
  //////////////////////////////////////////////////////////////////////////
  bool CTargetTrackGroup::HandleStimulusEvent(const TargetTrackHelpers::STargetTrackStimulusEvent& stimulusEvent, uint32 uStimulusNameHash)
  {
      FUNCTION_PROFILER(GetISystem(), PROFILE_AI);
  
      assert(!stimulusEvent.m_sStimulusName.empty());
  
      bool bResult = false;
  
      CTargetTrack* pTrack = GetTargetTrack(stimulusEvent.m_targetId);
      bResult = HandleStimulusEvent_Target(stimulusEvent, uStimulusNameHash, pTrack);
  
      return bResult;
  }
  
  bool CTargetTrackGroup::TriggerPulse(tAIObjectID targetID, uint32 uStimulusNameHash, uint32 uPulseNameHash)
  {
      FUNCTION_PROFILER(GetISystem(), PROFILE_AI);
  
      bool bResult = false;
  
      if (CTargetTrack* pTrack = GetTargetTrack(targetID))
      {
          bResult = pTrack->TriggerPulse(uStimulusNameHash, uPulseNameHash);
      }
  
      return bResult;
  }
  
  //////////////////////////////////////////////////////////////////////////
  bool CTargetTrackGroup::TriggerPulse(uint32 uStimulusNameHash, uint32 uPulseNameHash)
  {
      FUNCTION_PROFILER(GetISystem(), PROFILE_AI);
  
      bool bResult = true;
  
      TTargetTrackContainer::iterator itTrack = m_TargetTracks.begin();
      TTargetTrackContainer::iterator itTrackEnd = m_TargetTracks.end();
      for (; itTrack != itTrackEnd; ++itTrack)
      {
          CTargetTrack* pTrack = itTrack->second;
          assert(pTrack);
  
          bResult &= pTrack->TriggerPulse(uStimulusNameHash, uPulseNameHash);
      }
  
      return bResult;
  }
  
  //////////////////////////////////////////////////////////////////////////
  bool CTargetTrackGroup::HandleStimulusEvent_All(const TargetTrackHelpers::STargetTrackStimulusEvent& stimulusEvent, uint32 uStimulusNameHash)
  {
      FUNCTION_PROFILER(GetISystem(), PROFILE_AI);
  
      bool bResult = true;
  
      TTargetTrackContainer::iterator itTrack = m_TargetTracks.begin();
      TTargetTrackContainer::iterator itTrackEnd = m_TargetTracks.end();
      for (; itTrack != itTrackEnd; ++itTrack)
      {
          bResult &= HandleStimulusEvent_Target(stimulusEvent, uStimulusNameHash, itTrack->second);
      }
  
      return bResult;
  }
  
  //////////////////////////////////////////////////////////////////////////
  bool CTargetTrackGroup::HandleStimulusEvent_Target(const TargetTrackHelpers::STargetTrackStimulusEvent& stimulusEvent, uint32 uStimulusNameHash, CTargetTrack* pTrack)
  {
      FUNCTION_PROFILER(GetISystem(), PROFILE_AI);
  
      assert(pTrack);
  
      return (pTrack && pTrack->InvokeStimulus(stimulusEvent, uStimulusNameHash));
  }
  
  //////////////////////////////////////////////////////////////////////////
  bool CTargetTrackGroup::IsPotentialTarget(tAIObjectID aiTargetId) const
  {
      assert(aiTargetId > 0);
  
      TTargetTrackContainer::const_iterator itTrack = m_TargetTracks.find(aiTargetId);
      return (itTrack != m_TargetTracks.end());
  }
  
  //////////////////////////////////////////////////////////////////////////
  bool CTargetTrackGroup::IsDesiredTarget(tAIObjectID aiTargetId) const
  {
      assert(aiTargetId > 0);
  
      return (aiTargetId == m_aiLastBestTargetId);
  }
  
  //////////////////////////////////////////////////////////////////////////
  bool CTargetTrackGroup::GetDesiredTarget(TargetTrackHelpers::EDesiredTargetMethod eMethod, CWeakRef<CAIObject>& outTarget, SAIPotentialTarget*& pOutTargetInfo)
  {
      FUNCTION_PROFILER(GetISystem(), PROFILE_AI);
  
      bool bResult = false;
  
      CTargetTrack* pBestTrack;
      uint32 count = GetBestTrack(eMethod, &pBestTrack, 1);
  
      if (count)
      {
          UpdateTargetRepresentation(pBestTrack, outTarget, pOutTargetInfo);
          bResult = true;
      }
  
      m_aiLastBestTargetId = outTarget.GetObjectID();
  
      return bResult;
  }
  
  //////////////////////////////////////////////////////////////////////////
  uint32 CTargetTrackGroup::GetBestTrack(TargetTrackHelpers::EDesiredTargetMethod eMethod,
      CTargetTrack** tracks, uint32 maxCount)
  {
      UpdateSortedTracks();
  
      uint32 count = 0;
      switch (eMethod & TargetTrackHelpers::eDTM_SELECTION_MASK)
      {
      case TargetTrackHelpers::eDTM_Select_Highest:
      {
          TSortedTracks::iterator itBestTrack = m_SortedTracks.begin();
          TSortedTracks::iterator itTrackEnd = m_SortedTracks.end();
  
          for (; itBestTrack != itTrackEnd; ++itBestTrack)
          {
              CTargetTrack* track(*itBestTrack);
  
              if (track && TestTrackAgainstFilters(track, eMethod))
              {
                  tracks[count++] = track;
  
                  if (count >= maxCount)
                  {
                      break;
                  }
              }
          }
      }
      break;
  
      case TargetTrackHelpers::eDTM_Select_Lowest:
      {
          TSortedTracks::reverse_iterator itBestTrack = m_SortedTracks.rbegin();
          TSortedTracks::reverse_iterator itTrackEnd = m_SortedTracks.rend();
  
          for (; itBestTrack != itTrackEnd; ++itBestTrack)
          {
              CTargetTrack* track(*itBestTrack);
  
              if (track && TestTrackAgainstFilters(track, eMethod))
              {
                  tracks[count++] = track;
  
                  if (count >= maxCount)
                  {
                      break;
                  }
              }
          }
      }
      break;
  
      default:
          CRY_ASSERT_MESSAGE(false, "CTargetTrackGroup::GetBestTrack Unhandled desired target method");
          break;
      }
  
      return count;
  }
  
  //////////////////////////////////////////////////////////////////////////
  bool CTargetTrackGroup::TestTrackAgainstFilters(CTargetTrack* pTrack, TargetTrackHelpers::EDesiredTargetMethod eMethod) const
  {
      assert(pTrack);
  
      bool bResult = true;
  
      // [Kevin:26.02.2010] Need a better method than using the track manager here...
      CTargetTrackManager* pManager = gAIEnv.pTargetTrackManager;
      assert(pManager);
  
      const uint32 uFilterBitmask = eMethod & TargetTrackHelpers::eDTM_FILTER_MASK;
  
      if (uFilterBitmask & TargetTrackHelpers::eDTM_Filter_CanAquireTarget)
      {
          CAIActor* pOwner = CastToCAIActorSafe(gAIEnv.pObjectContainer->GetAIObject(m_aiObjectId));
          IAIObject* pTargetAI = pTrack->GetAIObject().GetIAIObject();
  
          if ((pOwner != NULL) && pTargetAI)
          {
              bResult = pOwner->CanAcquireTarget(pTargetAI);
          }
      }
  
      if (uFilterBitmask & TargetTrackHelpers::eDTM_Filter_LimitDesired)
      {
          /*const int iTargetLimit = pManager->GetTargetLimit(aiTargetId);
          bResult &= (iTargetLimit <= 0 || pManager->GetDesiredTargetCount(aiTargetId, m_aiObjectId) < iTargetLimit);*/
      }
  
      if (uFilterBitmask & TargetTrackHelpers::eDTM_Filter_LimitPotential)
      {
          /*const int iTargetLimit = pManager->GetTargetLimit(aiTargetId);
          bResult &= (iTargetLimit <= 0 || pManager->GetPotentialTargetCount(aiTargetId, m_aiObjectId) < iTargetLimit);*/
      }
  
      return bResult;
  }
  
  //////////////////////////////////////////////////////////////////////////
  void CTargetTrackGroup::UpdateTargetRepresentation(const CTargetTrack* pBestTrack, CWeakRef<CAIObject>& outTarget, SAIPotentialTarget*& pOutTargetInfo)
  {
      FUNCTION_PROFILER(GetISystem(), PROFILE_AI);
  
      CWeakRef<CAIObject> refTarget = pBestTrack->GetAITarget();
  
  #ifdef CRYAISYSTEM_DEBUG
      CAIObject* pTarget = refTarget.GetAIObject();
  #endif
  
      CAIObject* pDummyRep = m_dummyPotentialTarget.refDummyRepresentation.GetAIObject();
      assert(pDummyRep);
      if (!pDummyRep)
      {
          return;
      }
  
      pDummyRep->SetPos(pBestTrack->GetTargetPos());
      pDummyRep->SetEntityDir(pBestTrack->GetTargetDir());
      pDummyRep->SetBodyDir(pBestTrack->GetTargetDir());
      pDummyRep->SetViewDir(pBestTrack->GetTargetDir());
      pDummyRep->SetFireDir(pBestTrack->GetTargetDir());
      pDummyRep->SetAssociation(refTarget);
  
  #ifdef CRYAISYSTEM_DEBUG
      stack_string sDummyRepName;
  #endif
  
  
      const EAITargetType eTargetType = pBestTrack->GetTargetType();
      const EAITargetContextType eTargetContextType = pBestTrack->GetTargetContext();
      const EAITargetThreat eTargetThreat = pBestTrack->GetTargetThreat();
  
      switch (eTargetType)
      {
      case AITARGET_VISUAL:
      {
  #ifdef CRYAISYSTEM_DEBUG
          sDummyRepName.Format("Target Track Dummy - Visual \'%s\'", pTarget ? pTarget->GetName() : "(Null)");
  #endif
          pDummyRep->SetSubType(IAIObject::STP_NONE);
          outTarget = refTarget;
      }
      break;
  
      case AITARGET_MEMORY:
      {
  #ifdef CRYAISYSTEM_DEBUG
          sDummyRepName.Format("Target Track Dummy - Memory \'%s\'", pTarget ? pTarget->GetName() : "(Null)");
  #endif
          pDummyRep->SetSubType(IAIObject::STP_MEMORY);
          outTarget =  pDummyRep->GetSelfReference();
      }
      break;
  
      case AITARGET_SOUND:
      {
  #ifdef CRYAISYSTEM_DEBUG
          sDummyRepName.Format("Target Track Dummy - Sound \'%s\'", pTarget ? pTarget->GetName() : "(Null)");
  #endif
          switch (eTargetContextType)
          {
          case AITARGET_CONTEXT_GUNFIRE:
              pDummyRep->SetSubType(IAIObject::STP_GUNFIRE);
              break;
          default:
              pDummyRep->SetSubType(IAIObject::STP_SOUND);
              break;
          }
          outTarget = pDummyRep->GetSelfReference();
      }
      break;
  
      case AITARGET_NONE:
      {
  #ifdef CRYAISYSTEM_DEBUG
          sDummyRepName.Format("Target Track Dummy - No Type \'%s\'", pTarget ? pTarget->GetName() : "(Null)");
  #endif
          pDummyRep->SetSubType(IAIObject::STP_NONE);
          outTarget = pDummyRep->GetSelfReference();
      }
      break;
  
      default:
          CRY_ASSERT_MESSAGE(0, "CTargetTrackGroup::UpdateDummyTargetRep Unhandled AI target type");
          break;
      }
  
  #ifdef CRYAISYSTEM_DEBUG
      pDummyRep->SetName(sDummyRepName.c_str());
  #endif
  
      // Update the dummy target
      m_dummyPotentialTarget.bNeedsUpdating = false;
      m_dummyPotentialTarget.type = eTargetType;
      m_dummyPotentialTarget.threat = eTargetThreat;
      m_dummyPotentialTarget.exposureThreat = eTargetThreat;
  
      pOutTargetInfo = &m_dummyPotentialTarget;
  }
  
  #ifdef TARGET_TRACK_DEBUG
  //////////////////////////////////////////////////////////////////////////
  bool CTargetTrackGroup::FindFreeGraphSlot(uint32& outIndex) const
  {
      outIndex = UINT_MAX;
  
      for (uint32 i = 0; i < DEBUG_GRAPH_OCCUPIED_SIZE; ++i)
      {
          if (!m_bDebugGraphOccupied[i])
          {
              outIndex = i;
              break;
          }
      }
  
      return (outIndex < UINT_MAX);
  }
  
  //////////////////////////////////////////////////////////////////////////
  void CTargetTrackGroup::DebugDrawTracks(TargetTrackHelpers::ITargetTrackConfigProxy* pConfigProxy, bool bLastDraw)
  {
      CDebugDrawContext dc;
      float fColumnX = 1.0f;
      float fColumnY = 11.0f;
      float fColumnGraphX = fColumnX + 475.0f;
      float fColumnGraphY = fColumnY;
      const float fGraphWidth = 150.0f;
      const float fGraphHeight = 150.0f;
      const float fGraphMargin = 5.0f;
  
      static float s_fGraphUpdateInterval = 0.125f;
      bool bUpdateGraph = false;
      const float fCurrTime = GetAISystem()->GetFrameStartTimeSeconds();
      if (fCurrTime - m_fLastGraphUpdate >= s_fGraphUpdateInterval)
      {
          m_fLastGraphUpdate = fCurrTime;
          bUpdateGraph = true;
      }
  
      CWeakRef<CAIObject> refObject = gAIEnv.pObjectContainer->GetWeakRef(m_aiObjectId);
      CAIObject* pObject = refObject.GetAIObject();
      assert(pObject);
      if (!pObject || !pObject->IsEnabled())
      {
          return;
      }
  
      const ColorB textCol(255, 255, 255, 255);
      dc->Draw2dLabel(fColumnX, fColumnY, 1.5f, textCol, false, "Target Tracks for Agent \'%s\':", pObject->GetName());
      fColumnY += 20.0f;
  
      UpdateSortedTracks();
  
      TTargetTrackContainer::const_iterator itTrack = m_TargetTracks.begin();
      TTargetTrackContainer::const_iterator itTrackEnd = m_TargetTracks.end();
      for (; itTrack != itTrackEnd; ++itTrack)
      {
          const CTargetTrack* pTrack = itTrack->second;
          assert(pTrack);
  
          // TODO Not returning right distance?
          const int iIndex = (int)std::distance(m_SortedTracks.begin(), std::find(m_SortedTracks.begin(), m_SortedTracks.end(), pTrack));
          pTrack->DebugDraw(dc, iIndex, fColumnX, fColumnY, pConfigProxy);
          fColumnY += 20.0f;
  
          if (bUpdateGraph)
          {
              CWeakRef<CAIObject> refTarget = pTrack->GetAIObject();
              if (CAIObject* pTarget = refTarget.GetAIObject())
              {
                  const uint32 uDebugGraphIndex = pTrack->GetDebugGraphIndex();
                  const uint32 uGraphX = uDebugGraphIndex % 3;
                  const uint32 uGraphY = uDebugGraphIndex / 4;
  
                  // Debug graph update
                  IDebugHistory* pDebugHistory = m_pDebugHistoryManager->GetHistory(pTarget->GetName());
                  if (pDebugHistory)
                  {
                      pDebugHistory->SetupLayoutAbs(fColumnGraphX + (fGraphWidth + fGraphMargin) * uGraphX, fColumnGraphY + (fGraphHeight + fGraphMargin) * uGraphY, fGraphWidth, fGraphHeight, fGraphMargin);
                      pDebugHistory->AddValue(pTrack->GetTrackValue());
                      pDebugHistory->SetVisibility(!bLastDraw);
                  }
              }
          }
  
          pTrack->SetLastDebugDrawTime(fCurrTime);
      }
  }
  
  //////////////////////////////////////////////////////////////////////////
  void CTargetTrackGroup::DebugDrawTargets(int nMode, int nTargetedCount, bool bExtraInfo)
  {
      assert(nMode > 0);
  
      CDebugDrawContext dc;
      const ColorB visualColor(255, 0, 0, 255);
      const ColorB memoryColor(255, 255, 0, 255);
      const ColorB soundColor(0, 255, 0, 255);
      const ColorB invalidColor(120, 120, 120, 255);
      const float fProbableRatio = 0.45f;
  
      const float fCurrTime = GetAISystem()->GetFrameStartTimeSeconds();
  
      CWeakRef<CAIObject> refObject = gAIEnv.pObjectContainer->GetWeakRef(m_aiObjectId);
      CAIObject* pObject = refObject.GetAIObject();
      assert(pObject);
      if (!pObject || !pObject->IsEnabled())
      {
          return;
      }
  
      const Vec3& vPos = pObject->GetPos();
  
      UpdateSortedTracks();
  
      TSortedTracks::const_iterator itTrack = m_SortedTracks.begin();
      TSortedTracks::const_iterator itTrackEnd = m_SortedTracks.end();
      bool bFirst = true;
      for (; itTrack != itTrackEnd; ++itTrack)
      {
          const CTargetTrack* pTrack = *itTrack;
          assert(pTrack);
  
          ColorB drawColor;
          EAITargetType eType = pTrack->GetTargetType();
  
          CWeakRef<CAIObject> refTarget = pTrack->GetAIObject();
          assert(refTarget.IsValid());
          CAIObject* pTarget = refTarget.GetAIObject();
  
          switch (eType)
          {
          case AITARGET_VISUAL:
          {
              drawColor = visualColor;
          }
          break;
  
          case AITARGET_MEMORY:
          {
              drawColor = memoryColor;
          }
          break;
  
          case AITARGET_SOUND:
          {
              drawColor = soundColor;
          }
          break;
  
          case AITARGET_NONE:
          {
              drawColor = invalidColor;
          }
          break;
  
          default:
              CRY_ASSERT_MESSAGE(0, "CTargetTrackGroup::DebugDrawTargets Unhandled target type");
              break;
          }
          if (!bFirst)
          {
              drawColor.r = uint8((float)drawColor.r * fProbableRatio);
              drawColor.g = uint8((float)drawColor.g * fProbableRatio);
              drawColor.b = uint8((float)drawColor.b * fProbableRatio);
              drawColor.a = uint8((float)drawColor.a * fProbableRatio);
          }
  
          const Vec3& vTargetPos = pTrack->GetTargetPos();
          const Vec3 vTargetToPos = (vPos - vTargetPos).GetNormalizedSafe();
          const Vec3 vTargetToPosRight = Vec3_OneZ.Cross(vTargetToPos).GetNormalizedSafe();
  
          dc->DrawLine(vPos, drawColor, vTargetPos, drawColor, 2.0f);
          dc->DrawLine(vTargetPos, drawColor, vTargetPos + vTargetToPos + vTargetToPosRight * 0.5f, drawColor, 2.0f);
          dc->DrawLine(vTargetPos, drawColor, vTargetPos + vTargetToPos - vTargetToPosRight * 0.5f, drawColor, 2.0f);
  
          if (bExtraInfo)
          {
              string sExtraInfoText;
              sExtraInfoText.Format("%.3f", pTrack->GetTrackValue());
  
              dc->Draw3dLabel(vTargetPos + (vPos - vTargetPos) * 0.5f, 1.25f, sExtraInfoText.c_str());
          }
  
          pTrack->SetLastDebugDrawTime(fCurrTime);
  
          if (bFirst && nMode == 1)
          {
              break;
          }
          bFirst = false;
      }
  
      dc->Draw3dLabel(vPos + Vec3(0.0f, 0.0f, 1.0f), 1.5f, "%d", nTargetedCount);
  }
  #endif //TARGET_TRACK_DEBUG
  