BVB Source Codes

CRYENGINE Show TacticalPointSystem.cpp Source code

Return Download CRYENGINE: download TacticalPointSystem.cpp Source code - Download CRYENGINE Source code - Type:.cpp
  1. // Copyright 2001-2016 Crytek GmbH / Crytek Group. All rights reserved.
  2.  
  3. /* Notes
  4.    MTJ 22/07/07
  5.  
  6.    A fair bit of the code is a simple parser for the query language
  7.    It doesn't yet have a well-rounded error reporting system
  8.    It reports warnings immediately on failure conditions and then OK booleans trickle
  9.    down to generate an error in the original query request
  10.  
  11.    MTJ 11/09/07 - TODO: Refactoring the queries themselves into a separate class to the system would be sensible
  12.  
  13.    MTJ 30/06/09 - QueryContext's IAIActor should be a CAIActor internally
  14.  */
  15.  
  16. #include "StdAfx.h"
  17. #include "TacticalPointSystem.h"
  18. #include "TacticalPointQuery.h"
  19. #include "TacticalPointQueryEnum.h"
  20.  
  21. #include <algorithm>
  22.  
  23. // For persistent debugging
  24. #include <CryGame/IGameFramework.h>
  25.  
  26. #include "../Cover/CoverSystem.h"
  27. #include "../Cover/CoverSurface.h"
  28.  
  29. #include "DebugDrawContext.h"
  30. #include "StatsManager.h"
  31. #include "Puppet.h"
  32.  
  33. #include "Navigation/NavigationSystem/NavigationSystem.h"
  34.  
  35. // Maximum time that an async query can execute before it is aborted as an error
  36. const float MAX_SYNC_TIME_MS = 20;
  37.  
  38. CTacticalPointSystem::CVarsDef CTacticalPointSystem::CVars;
  39.  
  40. //----------------------------------------------------------------------------------------------//
  41.  
  42. CTacticalPointSystem::CTacticalPointSystem()
  43. {
  44.         // Initialise the word/token list
  45.         // Please keep exactly the same order as the enum, wherein order is essential
  46.  
  47.         m_CostMap[eTPQC_CHEAP] = 1;
  48.         m_CostMap[eTPQC_MEDIUM] = 256;
  49.         m_CostMap[eTPQC_EXPENSIVE] = 1024;
  50.         m_CostMap[eTPQC_DEFERRED] = -1024;
  51.  
  52.         m_GameQueryIdMap[eTPQT_PROP_BOOL] = eTPQ_GAMESTART_PROP_BOOL;
  53.         m_GameQueryIdMap[eTPQT_PROP_REAL] = eTPQ_GAMESTART_PROP_REAL;
  54.         m_GameQueryIdMap[eTPQT_TEST] = eTPQ_GAMESTART_TEST;
  55.         m_GameQueryIdMap[eTPQT_MEASURE] = eTPQ_GAMESTART_MEASURE;
  56.         m_GameQueryIdMap[eTPQT_GENERATOR] = eTPQ_GAMESTART_GENERATOR;
  57.         m_GameQueryIdMap[eTPQT_GENERATOR_O] = eTPQ_GAMESTART_GENERATOR_O;
  58.         m_GameQueryIdMap[eTPQT_PRIMARY_OBJECT] = eTPQ_GAMESTART_PRIMARY_OBJECT;
  59.  
  60.         // Misc
  61.         m_mTokenToString[eTPQ_Glue] = "glue";     // Any glue word translates back to this - see cheat in inverse mapping
  62.         m_mTokenToString[eTPQ_Around] = "around";
  63.  
  64.         // Bool Properties
  65.         m_mTokenToString[eTPQ_PB_CoverSoft] = "coverSoft";
  66.         m_mTokenToString[eTPQ_PB_CoverSuperior] = "coverSuperior";
  67.         m_mTokenToString[eTPQ_PB_CoverInferior] = "coverInferior";
  68.         m_mTokenToString[eTPQ_PB_CurrentlyUsedObject] = "currentlyUsedObject";
  69.         m_mTokenToString[eTPQ_PB_Reachable] = "reachable";
  70.         m_mTokenToString[eTPQ_PB_IsInNavigationMesh] = "isInNavigationMesh";
  71.  
  72.         // Real Properties
  73.         m_mTokenToString[eTPQ_PR_CoverRadius] = "coverRadius";
  74.         m_mTokenToString[eTPQ_PR_CoverDensity] = "coverDensity";
  75.         m_mTokenToString[eTPQ_PR_BulletImpacts] = "bulletImpacts";
  76.         m_mTokenToString[eTPQ_PR_CameraCenter] = "cameraCenter";
  77.         m_mTokenToString[eTPQ_PR_HostilesDistance] = "hostilesDistance";
  78.         m_mTokenToString[eTPQ_PR_FriendlyDistance] = "friendlyDistance";
  79.         m_mTokenToString[eTPQ_PR_Random] = "random";
  80.         m_mTokenToString[eTPQ_PR_Type] = "type";
  81.  
  82.         // Tests (Boolean Queries)
  83.         m_mTokenToString[eTPQ_T_Visible] = "visible";
  84.         m_mTokenToString[eTPQ_T_CanShoot] = "canShoot";
  85.         m_mTokenToString[eTPQ_T_CanShootTwoRayTest] = "canShootTwoRayTest";
  86.         m_mTokenToString[eTPQ_T_Towards] = "towards";
  87.         m_mTokenToString[eTPQ_T_CanReachBefore] = "canReachBefore";
  88.         m_mTokenToString[eTPQ_T_CrossesLineOfFire] = "crossesLineOfFire";
  89.         m_mTokenToString[eTPQ_T_HasShootingPosture] = "hasShootingPosture";
  90.         m_mTokenToString[eTPQ_T_OtherSide] = "otherSide";
  91.  
  92.         // Measures (Real Queries)
  93.         m_mTokenToString[eTPQ_M_Distance] = "distance";
  94.         m_mTokenToString[eTPQ_M_Distance2d] = "distance2d";
  95.         m_mTokenToString[eTPQ_M_PathDistance] = "pathDistance";
  96.         m_mTokenToString[eTPQ_M_ChangeInDistance] = "changeInDistance";
  97.         m_mTokenToString[eTPQ_M_DistanceInDirection] = "distanceInDirection";
  98.         m_mTokenToString[eTPQ_M_DistanceLeft] = "distanceLeft";
  99.         m_mTokenToString[eTPQ_M_RatioOfDistanceFromActorAndDistance] = "ratioOfDistanceFromActorAndDistance";
  100.         m_mTokenToString[eTPQ_M_Directness] = "directness";
  101.         m_mTokenToString[eTPQ_M_Dot] = "dot";
  102.         m_mTokenToString[eTPQ_M_ObjectsDot] = "objectsDot";
  103.         m_mTokenToString[eTPQ_M_ObjectsMoveDirDot] = "objectsMoveDirDot";
  104.         m_mTokenToString[eTPQ_M_HeightRelative] = "heightRelative";
  105.         m_mTokenToString[eTPQ_M_AngleOfElevation] = "angleOfElevation";
  106.         m_mTokenToString[eTPQ_M_PointDirDot] = "pointDirDot";
  107.         m_mTokenToString[eTPQ_M_CoverHeight] = "coverHeight";
  108.         m_mTokenToString[eTPQ_M_EffectiveCoverHeight] = "effectiveCoverHeight";
  109.         m_mTokenToString[eTPQ_M_DistanceToRay] = "distanceToRay";
  110.         m_mTokenToString[eTPQ_M_AbsDistanceToPlaneAtClosestRayPos] = "absDistanceToPlaneAtClosestRayPos";
  111.  
  112.         // Generators (_no_ auxiliary Object)
  113.         m_mTokenToString[eTPQ_G_Grid] = "grid";
  114.         m_mTokenToString[eTPQ_G_Entities] = "entities";
  115.         m_mTokenToString[eTPQ_G_Indoor] = "indoor";
  116.         m_mTokenToString[eTPQ_G_CurrentPos] = "currentPos";
  117.         m_mTokenToString[eTPQ_G_CurrentCover] = "currentCover";
  118.         m_mTokenToString[eTPQ_G_CurrentFormationPos] = "currentFormationPos";
  119.         m_mTokenToString[eTPQ_G_Objects] = "objects";
  120.         m_mTokenToString[eTPQ_G_PointsInNavigationMesh] = "pointsInNavigationMesh";
  121.  
  122.         // Generators independent from AI Systems
  123.         m_mTokenToString[eTPQ_G_PureGrid] = "pureGrid";
  124.  
  125.         // GeneratorsO (_with_ auxiliary Object)
  126.         // Note that these get combined with the aux Object later so
  127.         // might be confusing when debugging. See enum.
  128.         m_mTokenToString[eTPQ_GO_Hidespots] = "hidespots";
  129.         m_mTokenToString[eTPQ_GO_Cover] = "cover";
  130.  
  131.         // Objects
  132.         m_mTokenToString[eTPQ_O_None] = "none";
  133.         m_mTokenToString[eTPQ_O_Actor] = "puppet";
  134.         m_mTokenToString[eTPQ_O_AttentionTarget] = "attentionTarget";
  135.         m_mTokenToString[eTPQ_O_RealTarget] = "realTarget";
  136.         m_mTokenToString[eTPQ_O_ReferencePoint] = "referencePoint";
  137.         m_mTokenToString[eTPQ_O_ReferencePointOffsettedByItsForwardDirection] = "referencePointOffsettedByItsForwardDirection";
  138.  
  139.         // Objects independent from AI Systems
  140.         m_mTokenToString[eTPQ_O_Entity] = "entity";
  141.  
  142.         m_mTokenToString[eTPQ_O_CurrentFormationRef] = "currentFormationRef";
  143.         m_mTokenToString[eTPQ_O_Player] = "player";
  144.         m_mTokenToString[eTPQ_O_Leader] = "leader";
  145.         m_mTokenToString[eTPQ_O_LastOp] = "lastOp";
  146.  
  147.         // Limits
  148.         m_mTokenToString[eTPQ_L_Min] = "min";
  149.         m_mTokenToString[eTPQ_L_Max] = "max";
  150.         m_mTokenToString[eTPQ_L_Equal] = "equal";
  151.  
  152.         // Populate the inverse mapping
  153.         std::map<TTacticalPointQuery, string>::const_iterator itM;
  154.         for (itM = m_mTokenToString.begin(); itM != m_mTokenToString.end(); ++itM)
  155.                 m_mStringToToken[itM->second] = itM->first;
  156.         assert(m_mNameToID.size() == m_mIDToQuery.size());
  157.  
  158.         // Cheat with gluewords, where the mapping is asymmetric
  159.         m_mStringToToken["from"] = eTPQ_Glue;
  160.         m_mStringToToken["to"] = eTPQ_Glue;
  161.         m_mStringToToken["at"] = eTPQ_Glue;
  162.         m_mStringToToken["the"] = eTPQ_Glue;
  163.         m_mStringToToken["of"] = eTPQ_Glue;
  164.         m_mStringToToken["for"] = eTPQ_Glue;
  165.  
  166.         // From the above, "glue" also maps to eTPQ_Glue, which does no harm
  167.         // and might actually be handy but its use is not encouraged
  168.  
  169.         // Parameter names
  170.         m_mParamStringToToken["density"] = eTPQP_Density;
  171.         m_mParamStringToToken["objectsType"] = eTPQP_ObjectsType;
  172.         m_mParamStringToToken["height"] = eTPQP_Height;
  173.         m_mParamStringToToken["horizontalSpacing"] = eTPQP_HorizontalSpacing;
  174.         m_mParamStringToToken["optionLabel"] = eTPQP_OptionLabel;
  175.         m_mParamStringToToken["tagPointPostfix"] = eTPQP_TagPointPostfix;
  176.         m_mParamStringToToken["extenderStringParameter"] = eTPQP_ExtenderStringParameter;
  177.         m_mParamStringToToken["navigationAgentType"] = eTPQP_NavigationAgentType;
  178.  
  179.         m_mRelativeValueSourceStringToToken["objectRadius"] = eTPSRVS_objectRadius;
  180.  
  181.         // Define cost of queries
  182.         ApplyCost(eTPQ_PB_CoverSoft, eTPQC_CHEAP);
  183.         ApplyCost(eTPQ_PB_CoverSuperior, eTPQC_CHEAP);
  184.         ApplyCost(eTPQ_PB_CoverInferior, eTPQC_CHEAP);
  185.         ApplyCost(eTPQ_PR_Type, eTPQC_CHEAP);
  186.         ApplyCost(eTPQ_PB_CurrentlyUsedObject, eTPQC_CHEAP);
  187.         ApplyCost(eTPQ_PR_CoverRadius, eTPQC_CHEAP);
  188.         ApplyCost(eTPQ_M_Distance, eTPQC_CHEAP);
  189.         ApplyCost(eTPQ_M_Distance2d, eTPQC_CHEAP);
  190.         ApplyCost(eTPQ_M_ChangeInDistance, eTPQC_CHEAP);
  191.         ApplyCost(eTPQ_PR_Random, eTPQC_CHEAP);
  192.         ApplyCost(eTPQ_M_DistanceInDirection, eTPQC_CHEAP);
  193.         ApplyCost(eTPQ_M_DistanceLeft, eTPQC_CHEAP);
  194.         ApplyCost(eTPQ_M_Directness, eTPQC_CHEAP);
  195.         ApplyCost(eTPQ_M_Dot, eTPQC_CHEAP);
  196.         ApplyCost(eTPQ_M_ObjectsDot, eTPQC_CHEAP);
  197.         ApplyCost(eTPQ_M_ObjectsMoveDirDot, eTPQC_CHEAP);
  198.         ApplyCost(eTPQ_M_PointDirDot, eTPQC_CHEAP);
  199.         ApplyCost(eTPQ_M_CoverHeight, eTPQC_CHEAP);
  200.         ApplyCost(eTPQ_M_EffectiveCoverHeight, eTPQC_EXPENSIVE);
  201.         ApplyCost(eTPQ_M_DistanceToRay, eTPQC_CHEAP);
  202.         ApplyCost(eTPQ_M_AbsDistanceToPlaneAtClosestRayPos, eTPQC_CHEAP);
  203.         ApplyCost(eTPQ_T_CanReachBefore, eTPQC_CHEAP);
  204.         ApplyCost(eTPQ_M_RatioOfDistanceFromActorAndDistance, eTPQC_CHEAP);
  205.         ApplyCost(eTPQ_T_Towards, eTPQC_CHEAP);
  206.         ApplyCost(eTPQ_M_HeightRelative, eTPQC_CHEAP);
  207.         ApplyCost(eTPQ_M_AngleOfElevation, eTPQC_CHEAP);
  208.         ApplyCost(eTPQ_T_CrossesLineOfFire, eTPQC_CHEAP);
  209.         ApplyCost(eTPQ_T_OtherSide, eTPQC_CHEAP);
  210.  
  211.         // Medium costs (e.g. iteration of simple operation)
  212.         ApplyCost(eTPQ_PR_HostilesDistance, eTPQC_MEDIUM);
  213.         ApplyCost(eTPQ_PR_FriendlyDistance, eTPQC_MEDIUM);
  214.         ApplyCost(eTPQ_PR_CameraCenter, eTPQC_MEDIUM);
  215.         ApplyCost(eTPQ_PB_IsInNavigationMesh, eTPQC_MEDIUM);
  216.  
  217.         // Very expensive
  218.         ApplyCost(eTPQ_PR_BulletImpacts, eTPQC_EXPENSIVE);    // Spatial lookup
  219.         ApplyCost(eTPQ_PR_CoverDensity, eTPQC_EXPENSIVE);     // Uses some cheap graph queries
  220.         ApplyCost(eTPQ_T_Visible, eTPQC_DEFERRED);            // Uses raytest
  221.         ApplyCost(eTPQ_T_CanShoot, eTPQC_DEFERRED);           // Uses raytests
  222.         ApplyCost(eTPQ_T_CanShootTwoRayTest, eTPQC_DEFERRED); // Uses raytests
  223.         ApplyCost(eTPQ_PB_Reachable, eTPQC_EXPENSIVE);        // Uses point-in-polygon test
  224.         ApplyCost(eTPQ_M_PathDistance, eTPQC_DEFERRED);       // Uses pathfinding
  225.         ApplyCost(eTPQ_T_HasShootingPosture, eTPQC_DEFERRED); // Uses raytests
  226.  
  227.         // All queries should have a cost, so check and issue warnings here
  228.         for (itM = m_mTokenToString.begin(); itM != m_mTokenToString.end(); ++itM)
  229.         {
  230.                 TTacticalPointQuery query = itM->first;
  231.                 if (query & (eTPQ_FLAG_PROP_BOOL | eTPQ_FLAG_PROP_REAL | eTPQ_FLAG_TEST | eTPQ_FLAG_MEASURE))
  232.                 {
  233.                         if (m_mIDToCost.find(query) == m_mIDToCost.end())
  234.                         {
  235.                                 AIWarningID("<TacticalPointSystem> ", "No cost assigned to query: %s", itM->second.c_str());
  236.                                 m_mIDToCost[query] = eTPQC_EXPENSIVE;
  237.                         }
  238.                 }
  239.         }
  240. }
  241.  
  242. //----------------------------------------------------------------------------------------------//
  243.  
  244. CTacticalPointSystem::~CTacticalPointSystem()
  245. {
  246. }
  247.  
  248. CTacticalPointSystem::SQueryEvaluation::SQueryEvaluation()
  249. {
  250.         Reset();
  251. }
  252.  
  253. CTacticalPointSystem::SQueryEvaluation::~SQueryEvaluation()
  254. {
  255.         if (postureQueryID)
  256.         {
  257.                 if (queryInstance.queryContext.pAIActor)
  258.                 {
  259.                         if (CPuppet* puppet = static_cast<CAIActor*>(queryInstance.queryContext.pAIActor)->CastToCPuppet())
  260.                                 puppet->GetPostureManager().CancelPostureQuery(postureQueryID);
  261.                 }
  262.         }
  263.  
  264.         if (visibleRayID)
  265.                 gAIEnv.pRayCaster->Cancel(visibleRayID);
  266.  
  267.         if (canShootRayID)
  268.                 gAIEnv.pRayCaster->Cancel(canShootRayID);
  269.  
  270.         if (canShootSecondRayID)
  271.                 gAIEnv.pRayCaster->Cancel(canShootSecondRayID);
  272.  
  273.         if (deferredExtenderCancelFunc)
  274.                 deferredExtenderCancelFunc();
  275. }
  276.  
  277. //----------------------------------------------------------------------------------------------//
  278.  
  279. void CTacticalPointSystem::Reset()
  280. {
  281.         CRY_ASSERT_MESSAGE(m_LanguageExtenderDummyObjects.empty(), "A language extender hasn't unregistered its dummy objects");
  282.  
  283.         for (std::map<TPSQueryTicket, const SQueryInstance>::iterator iter = m_mQueryInstanceQueue.begin();
  284.              iter != m_mQueryInstanceQueue.end();
  285.              ++iter)
  286.         {
  287.                 const SQueryInstance& instance = iter->second;
  288.                 instance.pReceiver->AcceptResults(false, instance.nQueryInstanceID, NULL, 0, -1);
  289.         }
  290.         m_mQueryInstanceQueue.clear();
  291.  
  292.         for (std::map<TPSQueryTicket, SQueryEvaluation>::iterator iter = m_mQueryEvaluationsInProgress.begin();
  293.              iter != m_mQueryEvaluationsInProgress.end();
  294.              ++iter)
  295.         {
  296.                 SQueryInstance& instance = iter->second.queryInstance;
  297.                 instance.pReceiver->AcceptResults(false, instance.nQueryInstanceID, NULL, 0, -1);
  298.         }
  299.         m_mQueryEvaluationsInProgress.clear();
  300.  
  301.         m_locked.clear();
  302.  
  303.         stl::free_container(m_avoidCircles);
  304.         stl::free_container(m_cover);
  305.         stl::free_container(m_points);
  306.         stl::free_container(m_LanguageExtenderDummyObjects);
  307. }
  308.  
  309. //----------------------------------------------------------------------------------------------//
  310.  
  311. void CTacticalPointSystem::RegisterCVars()
  312. {
  313.         REGISTER_CVAR2("ai_DebugTacticalPoints", &CVars.DebugTacticalPoints, 0, VF_CHEAT | VF_CHEAT_NOCHECK | VF_DUMPTODISK,
  314.                        "Display debugging information on tactical point selection system");
  315.         REGISTER_CVAR2("ai_DebugTacticalPointsBlocked", &CVars.DebugTacticalPointsBlocked, 0, VF_CHEAT | VF_CHEAT_NOCHECK | VF_DUMPTODISK,
  316.                        "Highlight with red spheres any points blocked by generation phase, e.g. occupied points");
  317.         REGISTER_CVAR2("ai_TacticalPointsDebugDrawMode", &CVars.TacticalPointsDebugDrawMode, 1, VF_CHEAT | VF_CHEAT_NOCHECK | VF_DUMPTODISK,
  318.                        "Debugging draw mode: 1=sphere transparency, 2=sphere size");
  319.         REGISTER_CVAR2("ai_TacticalPointsDebugFadeMode", &CVars.TacticalPointsDebugFadeMode, 2, VF_CHEAT | VF_CHEAT_NOCHECK | VF_DUMPTODISK,
  320.                        "Debugging fade mode: 1=vanish, 2=alpha fade, 3=blink");
  321.         REGISTER_CVAR2("ai_TacticalPointsDebugScaling", &CVars.TacticalPointsDebugScaling, 1, VF_CHEAT | VF_CHEAT_NOCHECK | VF_DUMPTODISK,
  322.                        "Scale the size of debugging spheres for visibility");
  323.         REGISTER_CVAR2("ai_TacticalPointsDebugTime", &CVars.TacticalPointsDebugTime, 5, VF_CHEAT | VF_CHEAT_NOCHECK | VF_DUMPTODISK,
  324.                        "Time to display debugging spheres for (if not 'persistent'");
  325.         REGISTER_CVAR2("ai_TacticalPointsWarnings", &CVars.TacticalPointsWarnings, 1, VF_CHEAT | VF_CHEAT_NOCHECK | VF_DUMPTODISK,
  326.                        "Toggles TPS Warnings on and off");
  327. }
  328.  
  329. //----------------------------------------------------------------------------------------------//
  330.  
  331. bool CTacticalPointSystem::ExtendQueryLanguage(const char* szName, ETacticalPointQueryType eType, ETacticalPointQueryCost eCost)
  332. {
  333.         bool bValid = (szName && szName[0]);
  334.         assert(bValid);
  335.         if (!bValid)
  336.                 return false;
  337.  
  338.         // Check for overflow
  339.         const int iNewQueryId = m_GameQueryIdMap[eType] + 1;
  340.         const uint32 uTestField = (eType == eTPQT_GENERATOR_O ? eTPQ_MASK_QUERYSPACE_GEN_O : eTPQ_MASK_QUERYSPACE); // Test the correct set of low-order nibbles for overflow
  341.         if (!(iNewQueryId & uTestField))
  342.                 return false;
  343.  
  344.         // Must not already exist in the vocab
  345.         if (m_mStringToToken.end() != m_mStringToToken.find(szName))
  346.                 return false;
  347.  
  348.         // Generate unique game token Id for it
  349.         TTacticalPointQuery queryId = m_GameQueryIdMap[eType]++;
  350.         m_mStringToToken[szName] = queryId;
  351.         m_mTokenToString[queryId] = szName;
  352.  
  353.         // Apply the cost
  354.         bool bResult = (eCost != eTPQC_IGNORE ? ApplyCost(queryId, eCost) : true);
  355.         if (bResult)
  356.         {
  357.                 if (queryId & (eTPQ_FLAG_PROP_BOOL | eTPQ_FLAG_PROP_REAL | eTPQ_FLAG_TEST | eTPQ_FLAG_MEASURE))
  358.                 {
  359.                         if (m_mIDToCost.find(queryId) == m_mIDToCost.end())
  360.                         {
  361.                                 AIWarningID("<TacticalPointSystem> ", "No cost assigned to query: %s", szName);
  362.                                 m_mIDToCost[queryId] = eTPQC_EXPENSIVE;
  363.                         }
  364.                 }
  365.         }
  366.         return bResult;
  367. }
  368.  
  369. //----------------------------------------------------------------------------------------------//
  370. bool CTacticalPointSystem::ApplyCost(uint32 uQueryId, ETacticalPointQueryCost eCost)
  371. {
  372.         const bool bValid = (eCost > eTPQC_IGNORE && eCost < eTPQC_COUNT);
  373.         assert(bValid);
  374.         if (!bValid)
  375.                 return false;
  376.  
  377.         m_mIDToCost[uQueryId] = m_CostMap[eCost]++;
  378.         return true;
  379. }
  380.  
  381. //----------------------------------------------------------------------------------------------//
  382. int CTacticalPointSystem::GetCost(uint32 uQueryId) const
  383. {
  384.         std::map<TTacticalPointQuery, int>::const_iterator it = m_mIDToCost.find(uQueryId);
  385.         if (it == m_mIDToCost.end())
  386.         {
  387.                 assert(false);
  388.                 return eTPQC_EXPENSIVE;
  389.         }
  390.         return it->second;
  391. }
  392.  
  393. //----------------------------------------------------------------------------------------------//
  394.  
  395. bool CTacticalPointSystem::AddLanguageExtender(ITacticalPointLanguageExtender* pExtender)
  396. {
  397.         return stl::push_back_unique(m_LanguageExtenders, pExtender);
  398. }
  399.  
  400. //----------------------------------------------------------------------------------------------//
  401.  
  402. bool CTacticalPointSystem::RemoveLanguageExtender(ITacticalPointLanguageExtender* pExtender)
  403. {
  404.         return stl::find_and_erase(m_LanguageExtenders, pExtender);
  405. }
  406.  
  407. //----------------------------------------------------------------------------------------------//
  408. IAIObject* CTacticalPointSystem::CreateExtenderDummyObject(const char* szDummyName)
  409. {
  410.         m_LanguageExtenderDummyObjects.push_back(TDummyRef());
  411.         TDummyRef& newDummyRef = m_LanguageExtenderDummyObjects.back();
  412.  
  413.         gAIEnv.pAIObjectManager->CreateDummyObject(newDummyRef, szDummyName);
  414.         return newDummyRef.GetAIObject();
  415. }
  416.  
  417. //----------------------------------------------------------------------------------------------//
  418. void CTacticalPointSystem::ReleaseExtenderDummyObject(tAIObjectID id)
  419. {
  420.         for (TDummyObjects::iterator it = m_LanguageExtenderDummyObjects.begin(), itEnd = m_LanguageExtenderDummyObjects.end(); it != itEnd; ++it)
  421.         {
  422.                 if (it->GetObjectID() == id)
  423.                 {
  424.                         m_LanguageExtenderDummyObjects.erase(it);
  425.                         return;
  426.                 }
  427.         }
  428.  
  429.         CRY_ASSERT_MESSAGE(false, "Trying to release a dummy object that isn't registered for this language extender");
  430. }
  431.  
  432. //----------------------------------------------------------------------------------------------//
  433.  
  434. int CTacticalPointSystem::SyncQuery(TPSQueryID queryID, const QueryContext& context, CTacticalPoint& point)
  435. {
  436.         // (MATT) If this was a single element long and maintained in a buffer, we might avoid allocations {2009/11/13}
  437.         TTacticalPoints results;
  438.  
  439.         int nOptionUsed = SyncQueryShortlist(queryID, context, results, 1);
  440.  
  441.         if ((nOptionUsed >= 0) && !results.empty())
  442.                 point = results[0];
  443.         else
  444.                 point = CTacticalPoint();
  445.  
  446.         return nOptionUsed;
  447. }
  448.  
  449. //----------------------------------------------------------------------------------------------//
  450.  
  451. int CTacticalPointSystem::SyncQueryShortlist
  452.   (TPSQueryID queryID, const QueryContext& context, TTacticalPoints& vPoints, int n)
  453. {
  454.         FUNCTION_PROFILER(gEnv->pSystem, PROFILE_AI);
  455.  
  456.         CAISystem* pAISystem = GetAISystem();
  457.         vPoints.clear();
  458.  
  459.         const CTacticalPointQuery* pQuery = GetQuery(queryID);
  460.         if (!pQuery || !pQuery->IsValid() || n < 1)
  461.         {
  462.                 gEnv->pLog->LogError("AI: <TacticalPointSystem> Invalid TPS query");
  463.                 return -1; // Should be OPTION_ERROR or somesuch
  464.         }
  465.  
  466.         const bool bDebug = CVars.DebugTacticalPoints != 0;
  467.         if (bDebug)
  468.         {
  469.                 gEnv->pLog->Log("TPS Query: %s", pQuery->GetName());
  470.                 // (MATT) Handy for tracking exactly how many TPS queries are happening {2008/10/03}
  471.                 if (context.pAIActor)
  472.                 {
  473.                         CAIActor* pAIActor = static_cast<CAIActor*>(context.pAIActor);
  474.                         gEnv->pLog->Log("Frame: %d TPS Query: %s Actor: %s\n", gEnv->pRenderer->GetFrameID(), pQuery->GetName(), pAIActor->GetName());
  475.                 }
  476.         }
  477.  
  478.         // Advance ticket - even queries with errors get a ticket, which should aid debugging
  479.         m_nQueryInstanceTicket.Advance();
  480.  
  481.         // Populate an instance
  482.         SQueryInstance instance;
  483.         instance.nQueryInstanceID = m_nQueryInstanceTicket;
  484.         instance.nPoints = n;
  485.         instance.nQueryID = queryID;
  486.         instance.pReceiver = NULL;
  487.         instance.queryContext = context;
  488.  
  489.         // Verify it
  490.         if (!VerifyQueryInstance(instance))
  491.         {
  492.                 gEnv->pLog->LogError("AI: <TacticalPointSystem> TPS query failed verification");
  493.                 return -1;
  494.         }
  495.  
  496.         // Create a query evaluation
  497.         SQueryEvaluation evaluation;
  498.         bool bOk = SetupQueryEvaluation(instance, evaluation);
  499.         assert(evaluation.eEvalState == SQueryEvaluation::eHeapEvaluation);
  500.         if (!bOk)
  501.         {
  502.                 gEnv->pLog->LogError("AI: <TacticalPointSystem> TPS query setup failed");
  503.                 return -1;
  504.         }
  505.  
  506.         // Skipping the queue, we immediately evaluate the query
  507.         // It is very important that synchronous queries are completed in a timely manner
  508.         // However, for development purposes, set a very high time limit, then check the actual time taken to issue a warning
  509.         CTimeValue startTime = gEnv->pTimer->GetAsyncTime();
  510.         CTimeValue timeLimit = startTime + 1.0f;   // Up to 1 second!
  511.         bOk = ContinueQueryEvaluation(evaluation, timeLimit);
  512.  
  513.         // Check the actual time taken and issue a warning below
  514.         CTimeValue elapsed = gEnv->pTimer->GetAsyncTime() - startTime;
  515.  
  516.         CAIActor* pAIActor = static_cast<CAIActor*>(context.pAIActor);
  517.         if (!bOk)
  518.         {
  519.                 gEnv->pLog->LogError("AI: <TacticalPointSystem> Query '%s' failed during evaluation on actor '%s'",
  520.                                      pQuery->GetName(), pAIActor ? pAIActor->GetName() : "<null>");
  521.                 return -1;
  522.         }
  523. #ifdef CRYAISYSTEM_DEBUG
  524.         if (elapsed.GetMilliSecondsAsInt64() >= MAX_SYNC_TIME_MS)
  525.         {
  526.                 AIWarningID("<TacticalPointSystem> ", "Query '%s' performed by '%s' took too long! (%dms >= %dms)",
  527.                             pQuery->GetName(), pAIActor ? pAIActor->GetName() : "<null>", (uint32)elapsed.GetMilliSeconds(), (uint32)(MAX_SYNC_TIME_MS));
  528.         }
  529. #endif
  530.  
  531.         switch (evaluation.eEvalState)
  532.         {
  533.         case SQueryEvaluation::eCompleted:
  534.                 // Returned valid result within the time limit
  535.                 if (evaluation.iCurrentQueryOption >= 0)
  536.                 {
  537.                         // Locate the range of the best N points
  538.                         std::vector<SPointEvaluation>::iterator it = evaluation.GetIterRejectedEndAcceptedBegin();
  539.                         const std::vector<SPointEvaluation>::iterator end = evaluation.vPoints.end();
  540.                         int nPoints = int(std::distance(it, end));
  541.                         assert(nPoints > 0 && nPoints >= instance.nPoints);
  542.  
  543. #ifdef CRYAISYSTEM_DEBUG
  544.                         if (bDebug)
  545.                                 AddQueryForDebug(evaluation);
  546. #endif
  547.  
  548.                         vPoints.resize(nPoints);
  549.                         for (int i = 0; i < nPoints; ++i, ++it)
  550.                                 vPoints[i] = it->m_Point;
  551.                 }
  552.                 break;
  553.  
  554.         case SQueryEvaluation::eError:
  555.                 // Genuine error in query
  556.                 bOk = false;
  557.                 break;
  558.  
  559.         case SQueryEvaluation::eHeapEvaluation:
  560.         case SQueryEvaluation::eInitialized:
  561.         case SQueryEvaluation::eCompletedOption:
  562.                 // If we were left in this state it indicates incomplete evaluation, which should be due to running out of time
  563.                 // Might be possible to return useful debugging points, with some work
  564.                 bOk = false;
  565.                 break;
  566.  
  567.         case SQueryEvaluation::eDebugging:
  568.         case SQueryEvaluation::eEmpty:
  569.         default:
  570.                 // System error
  571.                 assert(false);
  572.                 bOk = false;
  573.                 break;
  574.         }
  575.  
  576.         // Since async query is currently for internal use, we return CTacticalPoints without conversion
  577.  
  578.         return (bOk ? evaluation.iCurrentQueryOption : -1);
  579. }
  580.  
  581. #ifdef CRYAISYSTEM_DEBUG
  582. //-----------------------------------------------------------------------------------------------------------
  583. void CTacticalPointSystem::AddQueryForDebug(SQueryEvaluation& evaluation)
  584. {
  585.         // (MATT) Not very efficient when there's a lot of queries going on. But it does need to be pretty versatile... {2009/07/16}
  586.  
  587.         EntityId targetAI = evaluation.queryInstance.queryContext.actorEntityId;
  588.         evaluation.owner = targetAI;
  589.         evaluation.timePlaced = gEnv->pTimer->GetAsyncTime(); // (MATT) Would use frame time, but that appears junk! {2009/11/22}
  590.         evaluation.timeErase = evaluation.timePlaced;
  591.         evaluation.timeErase += CTimeValue(CVars.TacticalPointsDebugTime);
  592.  
  593.         // Find any existing entry for this entity
  594.         std::list<SQueryEvaluation>::iterator it = m_lstQueryEvaluationsForDebug.begin();
  595.  
  596.         for (; it != m_lstQueryEvaluationsForDebug.end() && it->owner != targetAI; ++it)
  597.                 ;
  598.  
  599.         // Replace the existing entry or add a new one
  600.         if (it != m_lstQueryEvaluationsForDebug.end())
  601.                 *it = evaluation;
  602.         else
  603.                 m_lstQueryEvaluationsForDebug.push_back(evaluation);
  604. }
  605. #endif
  606.  
  607. //----------------------------------------------------------------------------------------------//
  608.  
  609. bool CTacticalPointSystem::GeneratePoints(const std::vector<CCriterion>& vGeneration, const QueryContext& context, const COptionCriteria* pOption, TTacticalPoints& vPoints) const
  610. {
  611.         vPoints.clear();
  612.         std::vector<CCriterion>::const_iterator itC;
  613.         for (itC = vGeneration.begin(); itC != vGeneration.end(); ++itC)
  614.         {
  615.                 if (!Generate(*itC, context, pOption, vPoints))
  616.                 {
  617.                         string sBuffer;
  618.                         Unparse(*itC, sBuffer);
  619.                         if (CVars.TacticalPointsWarnings > 0)
  620.                         {
  621.                                 AIWarningID("<TacticalPointSystem> ", "Generation criterion failed: %s", sBuffer.c_str());
  622.                         }
  623.                         return false;
  624.                 }
  625.         }
  626.  
  627.         return true;
  628. }
  629.  
  630. //----------------------------------------------------------------------------------------------//
  631.  
  632. const CTacticalPointQuery* CTacticalPointSystem::GetQuery(TPSQueryID nQueryID) const
  633. {
  634.         std::map<TPSQueryID, CTacticalPointQuery>::const_iterator itQ = m_mIDToQuery.find(nQueryID);
  635.         return (itQ == m_mIDToQuery.end() ? NULL : &(itQ->second));
  636. }
  637.  
  638. //----------------------------------------------------------------------------------------------//
  639. // Take a query instance (request) and convert it into an evaluation structure
  640.  
  641. bool CTacticalPointSystem::SetupQueryEvaluation(const SQueryInstance& instance, SQueryEvaluation& evaluation) const
  642. {
  643.         // We may no assumptions about previous state
  644.         CAISystem* pAISystem = GetAISystem();
  645.  
  646.         // Check this query still exists (system error)
  647.         const CTacticalPointQuery* pQuery = GetQuery(instance.nQueryID);
  648.         if (!pQuery)
  649.         {
  650.                 evaluation.eEvalState = SQueryEvaluation::eError;
  651.                 assert(false);
  652.                 return false;
  653.         }
  654.  
  655.         evaluation.queryInstance = instance;
  656.  
  657.         int nOption = evaluation.iCurrentQueryOption;
  658.         const COptionCriteria* pOption = pQuery->GetOption(nOption);
  659.  
  660.         bool bDebugging = (CVars.DebugTacticalPoints != 0);
  661.         if (bDebugging && nOption == 0) // Just give this info the first time
  662.         {
  663.                 CAIActor* pAIActor = static_cast<CAIActor*>(instance.queryContext.pAIActor);
  664.                 if (pAIActor)
  665.                         gEnv->pLog->Log("Frame: %d TPS Query: %s Actor: %s\n", gEnv->pRenderer->GetFrameID(), pQuery->GetName(), pAIActor->GetName());
  666.                 else
  667.                         gEnv->pLog->Log("TPS Query: %s", pQuery->GetName());
  668.         }
  669.  
  670.         if (bDebugging)
  671.                 gEnv->pLog->Log("[%d]: %s", nOption, pOption->GetDescription().c_str());
  672.  
  673.         // Hmm. Will this actually be correctly set?
  674.  
  675.         TTacticalPoints points;
  676.         if (!GeneratePoints(pOption->GetAllGeneration(), instance.queryContext, pOption, points))
  677.         {
  678.                 if (CVars.TacticalPointsWarnings > 0)
  679.                 {
  680.                         pAISystem->Warning("<TacticalPointSystem> ",
  681.                                            "Generation failed, option %i skipped. "
  682.                                            "Behaviours should only use queries that are valid in their context. \n", nOption);
  683.                         TPSDescriptionWarning(*pQuery, instance.queryContext, pOption);
  684.                 }
  685.                 return false;
  686.         }
  687.  
  688.         if (!SetupHeapEvaluation(pOption->GetAllConditions(), pOption->GetAllWeights(), instance.queryContext, points, instance.nPoints, evaluation))
  689.         {
  690.                 if (CVars.TacticalPointsWarnings > 0)
  691.                 {
  692.                         pAISystem->Warning("<TacticalPointSystem> ",
  693.                                            "Evaluation failed, option %i skipped. "
  694.                                            "Behaviours should only use queries that are valid in their context. \n", nOption);
  695.                         TPSDescriptionWarning(*pQuery, instance.queryContext, pOption);
  696.                 }
  697.                 // Possibly we should return false here, but people are keen on not treating this as a hard error to allow wider use of fallbacks
  698.         }
  699.  
  700.         evaluation.eEvalState = SQueryEvaluation::eHeapEvaluation;
  701.  
  702.         return true;
  703. }
  704.  
  705. //----------------------------------------------------------------------------------------------//
  706.  
  707. bool CTacticalPointSystem::ContinueQueryEvaluation(SQueryEvaluation& eval, CTimeValue timeLimit) const
  708. {
  709.         bool bOk = true;
  710.         CTimeValue currTime;
  711.  
  712.         do
  713.         {
  714.                 switch (eval.eEvalState)
  715.                 {
  716.                 default:
  717.                 case SQueryEvaluation::eEmpty:                                             // We should have no empty queries
  718.                 case SQueryEvaluation::eDebugging:                                         // Queries preserved for debugging need no further processing
  719.                         assert(false);
  720.                         // Jump to error handling, which should switch to state and return nothing
  721.                         bOk = false;
  722.                         break;
  723.  
  724.                 case SQueryEvaluation::eCompleted:                                         // Valid result ready to return, of 0 or more points
  725.                         // (MATT) Shouldn't actually be needed, covered by the while condition {2009/11/25}
  726.                         break;
  727.  
  728.                 case SQueryEvaluation::eCompletedOption:
  729.                         // We completed an option, but found no points - try the next one
  730.                         eval.iCurrentQueryOption++;
  731.                         if (!GetQuery(eval.queryInstance.nQueryID)->GetOption(eval.iCurrentQueryOption))
  732.                         {
  733.                                 // We've exhausted all the options
  734.                                 // Mark complete and return
  735.                                 eval.eEvalState = SQueryEvaluation::eCompleted;
  736.                                 eval.iCurrentQueryOption = -1;
  737.                                 break;
  738.                         }
  739.                 // Fall through
  740.                 case SQueryEvaluation::eReady:
  741.                         // (Re)start generation, setup points according to query instance
  742.                         bOk = SetupQueryEvaluation(eval.queryInstance, eval);
  743.                         break;
  744.  
  745.                 case SQueryEvaluation::eInitialized:
  746.                 // (MATT) Currently unused {2009/11/25}
  747.                 case SQueryEvaluation::eWaitingForDeferred:
  748.                 case SQueryEvaluation::eHeapEvaluation:
  749.                         // Continue evaluation of heap
  750.                         bOk = ContinueHeapEvaluation(eval, timeLimit);
  751.                         break;
  752.  
  753.                 case SQueryEvaluation::eError:
  754.                         eval.iCurrentQueryOption = -1;
  755.                         bOk = false;
  756.                         break;
  757.                 }
  758.         }
  759.         while (bOk && (eval.eEvalState != SQueryEvaluation::eCompleted) &&
  760.                (eval.eEvalState != SQueryEvaluation::eWaitingForDeferred) && (currTime = gEnv->pTimer->GetAsyncTime()) < timeLimit);
  761.  
  762.         return bOk;
  763. }
  764.  
  765. //----------------------------------------------------------------------------------------------//
  766.  
  767. bool CTacticalPointSystem::SetupHeapEvaluation(const std::vector<CCriterion>& vConditions, const std::vector<CCriterion>& vWeights, const QueryContext& context, const std::vector<CTacticalPoint>& vPoints, int n, SQueryEvaluation& eval) const
  768. {
  769.         // Do we have proper error handling, when the cheap tests fail? Probably need goto.
  770.         FUNCTION_PROFILER(gEnv->pSystem, PROFILE_AI);
  771.  
  772.         // Note: Points are always currently in consideration, or chosen as results, or rejected, which are all mutually exclusive.
  773.  
  774.         // ------ in consideration ----- / ----- rejected (for debugging) ----- / ----- valid ----- /
  775.  
  776.         // Sort criteria into cheap and expensive
  777.         AILogComment("<TacticalPointSystem> Evaluating %" PRISIZE_T " points with %" PRISIZE_T " conditions and %" PRISIZE_T " weights, for a result of the %d best points",
  778.                      vPoints.size(), vConditions.size(), vWeights.size(), n);
  779.  
  780.         // Sort criteria into cheap and expensive
  781.         // Which, again, could be done in the original query...
  782.  
  783.         eval.vCheapConds.clear();
  784.         eval.vExpConds.clear();
  785.         eval.vDefConds.clear();
  786.         eval.vCheapWeights.clear();
  787.         eval.vExpWeights.clear();
  788.  
  789.         std::vector<CCriterion>::const_iterator itC;
  790.         for (itC = vConditions.begin(); itC != vConditions.end(); ++itC)
  791.         {
  792.                 int cost = GetCost(itC->GetQuery());
  793.                 if (cost < 0)
  794.                         eval.vDefConds.push_back(*itC);
  795.                 else if (cost < 256)
  796.                         eval.vCheapConds.push_back(*itC);
  797.                 else
  798.                         eval.vExpConds.push_back(*itC);
  799.         }
  800.  
  801.         {
  802.                 std::vector<CCriterion>::const_iterator itD;
  803.                 for (itD = vWeights.begin(); itD != vWeights.end(); ++itD)
  804.                 {
  805.                         if (GetCost(itD->GetQuery()) < 256) // Erk!
  806.                                 eval.vCheapWeights.push_back(*itD);
  807.                         else if (itD->GetValueAsFloat() != 0.0f)
  808.                                 eval.vExpWeights.push_back(*itD);
  809.                         else
  810.                                 AIWarningID("<TacticalPointSystem> ", "Zero weight criterion (ignored) in query");
  811.                 }
  812.         }
  813.  
  814.         AILogComment("<TacticalPointSystem> 3 Sorted into %" PRISIZE_T " / %" PRISIZE_T " / %" PRISIZE_T " / %" PRISIZE_T " cheap conditions/weights, expensive conditions/weights",
  815.                      eval.vCheapConds.size(), eval.vCheapWeights.size(), eval.vExpConds.size(), eval.vExpWeights.size());
  816.  
  817.         // Sort those vectors into order!
  818.         // Actually, the option itself should store them sorted, before they even arrive.
  819.  
  820.         // Establish total limits of the expensive weights
  821.         // TODO: Could renormalise here to save float ops later. Think about it.
  822.         // This could probably happen per-query also, rather than per-instance.
  823.         // Note that all query results are normalised to the 0.0-1.0 range, before weighting, so this is easy
  824.         float fMinExpWeight = 0.0f, fMaxExpWeight = 0.0f;
  825.         for (itC = eval.vExpWeights.begin(); itC != eval.vExpWeights.end(); ++itC)
  826.         {
  827.                 float w = itC->GetValueAsFloat();
  828.                 if (w > 0.0f)
  829.                         fMaxExpWeight += w;
  830.                 else
  831.                         fMinExpWeight += w;
  832.         }
  833.  
  834.         // Same for cheap weights, but we'll only use this for debugging purposes?
  835.         float fMinCheapWeight = 0.0f, fMaxCheapWeight = 0.0f;
  836.         for (itC = eval.vCheapWeights.begin(); itC != eval.vCheapWeights.end(); ++itC)
  837.         {
  838.                 float w = itC->GetValueAsFloat();
  839.                 if (w > 0.0f)
  840.                         fMaxCheapWeight += w;
  841.                 else
  842.                         fMinCheapWeight += w;
  843.         }
  844.  
  845.         // Make sure the processing block is empty, then adjust it's size
  846.         // This should never again grow and we will fill it from both ends
  847.         eval.vPoints.clear();
  848.         eval.vPoints.resize(vPoints.size());                     // Confusing - shouldn't both be vPoints
  849.  
  850.         // Transform all points into SEvaluationPoints
  851.         // Apply cheap tests and weights to points before putting them into the block
  852.         // Which is more important here - code locality or data locality?
  853.  
  854.         // Iterate through input points
  855.         std::vector<CTacticalPoint>::const_iterator itInputPoints = vPoints.begin();
  856.         std::vector<CTacticalPoint>::const_iterator itInputPointsEnd = vPoints.end();
  857.  
  858.         // Iterators defining valid ranges in the heap - both ranges are initially zero, and we fill in from both ends of the vector
  859.         std::vector<SPointEvaluation>::iterator itHeapBegin = eval.vPoints.begin();       // Start of valid heap area
  860.         std::vector<SPointEvaluation>::iterator itHeapEnd = eval.vPoints.begin();         // Initially heap is 0-size
  861.         std::vector<SPointEvaluation>::iterator itRejectedBegin = eval.vPoints.end();     // Initially there are 0 rejected points
  862.         std::vector<SPointEvaluation>::iterator itRejectedEnd = eval.vPoints.end();       // Rejected points end the vector
  863.         // Note that accepted points will later fill in from the end of the vector, but only in the evaluation phase
  864.  
  865.         // Are there any expensive weights or conditions?
  866.         // If only cheap, we won't have any partial evaluation
  867.         SPointEvaluation::EPointEvaluationState initialEvalState =
  868.           (eval.vExpConds.empty() && eval.vExpWeights.empty() ? SPointEvaluation::eValid : SPointEvaluation::ePartial);
  869.  
  870.         // I.e. whole range is invalid
  871.         for (; itInputPoints != itInputPointsEnd; ++itInputPoints)
  872.         {
  873.                 const CTacticalPoint& inputPoint = *itInputPoints;
  874.  
  875.                 // Test each cheap condition on this point
  876.                 bool bResult = true;
  877.                 for (itC = eval.vCheapConds.begin(); itC != eval.vCheapConds.end(); ++itC)
  878.                 {
  879.                         if (!Test(*itC, inputPoint, context, bResult)) // Actually perform a test
  880.                                 return false;                                // On error condition
  881.                         if (!bResult)
  882.                                 break;
  883.                 }
  884.  
  885.                 // If point failed any test, reject it now
  886.                 if (!bResult)
  887.                 {
  888.                         // Create rejected point on the end of the block
  889.                         // Perhaps assertion to check for the overlap that shouldn't be possible
  890.                         SPointEvaluation evalPt(inputPoint, -100.0f, -100.0f, SPointEvaluation::eRejected);   // Is that everything?
  891.                         *(--itRejectedBegin) = evalPt;
  892.                         continue; // On point failed test
  893.                 }
  894.  
  895.                 // Evaluate every cheap weight on this point
  896.                 float fWeight = 0.0f;
  897.                 float fResult;
  898.                 for (itC = eval.vCheapWeights.begin(); itC != eval.vCheapWeights.end(); ++itC)
  899.                 {
  900.                         if (!Weight(*itC, inputPoint, context, fResult))
  901.                                 return false;
  902.                         fWeight += fResult * itC->GetValueAsFloat();
  903.                 }
  904.  
  905.                 // (MATT) What happens if expensive condition but no weights at all? {2009/11/20}
  906.  
  907.                 // (MATT) fMinExpWeight is -ve, fMaxExpWeight is +ve, they represent the extremes we might reach from this initial value {2009/11/20}
  908.                 SPointEvaluation evalPt(*itInputPoints, fWeight + fMinExpWeight, fWeight + fMaxExpWeight, initialEvalState);
  909.                 (*itHeapEnd++) = evalPt;
  910.                 std::push_heap(itHeapBegin, itHeapEnd);
  911.                 // assert here too
  912.         }
  913.  
  914.         // Ensure we met in the middle
  915.         assert(itHeapEnd == itRejectedBegin);
  916.  
  917.         // Finish initialising our structure
  918.         eval.SetIterHeapEndRejectedBegin(itHeapEnd);                // We've now filled in from both ends - heap and rejected areas are valid
  919.         eval.SetIterRejectedEndAcceptedBegin(eval.vPoints.end());   // Accepted points are 0-size
  920.  
  921.         // Actually, latter points aren't necessarily rejected - if only cheap tests...?
  922.         assert(itHeapEnd == itRejectedBegin);
  923.         // At end the two iterators should have met and we have a range of heapified points and some rejected at the end of the block
  924.         return true;
  925. }
  926.  
  927. //----------------------------------------------------------------------------------------------//
  928.  
  929. bool CTacticalPointSystem::ContinueHeapEvaluation(SQueryEvaluation& eval, CTimeValue timeLimit) const
  930. {
  931.         // No concept of sorting interleaved expensive weights and conditions :/
  932.         // OTOH, we still get something out of it. Only run conditions on the most promising point.
  933.  
  934.         // ------ in heap (for consideration) ----- / ----- rejected (for debugging) ----- / ----- accepted (results) ----- /
  935.  
  936.         bool bOk = true; // True if we return without errors
  937.  
  938.         // For readability, provide these aliases for the beginning and end of the entire points vector
  939.         const std::vector<SPointEvaluation>::iterator itHeapBegin = eval.vPoints.begin();       // Start of valid heap area
  940.         const std::vector<SPointEvaluation>::iterator itAcceptedEnd = eval.vPoints.end();       // End of the accepted points area
  941.  
  942.         // Note these iterators have need to be written back to the structure at the end of this call
  943.         std::vector<SPointEvaluation>::iterator itHeapEndRejectedBegin = eval.GetIterHeapEndRejectedBegin();
  944.         std::vector<SPointEvaluation>::iterator itRejectedEndAcceptedBegin = eval.GetIterRejectedEndAcceptedBegin();
  945.  
  946.         int iImplicitExpCondsSize = 1;  // We're adding an extra implicit expensive condition
  947.         int iExpCondsSize = (int)eval.vExpConds.size();
  948.         int iDefCondsSize = (int)eval.vDefConds.size();
  949.         int iExpWeightsSize = (int)eval.vExpWeights.size();
  950.  
  951.         CTimeValue currTime = gEnv->pTimer->GetAsyncTime();
  952.  
  953.         // Check that there are actually some unrejected points to consider
  954.         // If they all failed on cheap tests or none were generated, go on to next option
  955.         if (itHeapEndRejectedBegin == itHeapBegin)
  956.         {
  957.                 eval.eEvalState = SQueryEvaluation::eCompletedOption;
  958.                 goto HeapReturn;
  959.         }
  960.  
  961.         do
  962.         {
  963.                 SPointEvaluation& topPoint = eval.vPoints[0];  // Careful, this becomes invalid later
  964.  
  965.                 int iQueryIndex = topPoint.m_iQueryIndex++;
  966.                 if (iQueryIndex < iExpCondsSize + iImplicitExpCondsSize + iDefCondsSize)   // less than, surely...?
  967.                 {
  968.                         bool bResult = true;
  969.  
  970.                         if (iQueryIndex < iExpCondsSize)
  971.                         {
  972.                                 // Next query to be done is a condition
  973.  
  974.                                 CCriterion& condition = eval.vExpConds[iQueryIndex];
  975.                                 if (!Test(condition, topPoint.m_Point, eval.queryInstance.queryContext, bResult))
  976.                                 {
  977.                                         bOk = false;
  978.                                         goto HeapReturn;  // On error condition
  979.                                 }
  980.                         }
  981.                         else if (iQueryIndex < iExpCondsSize + iImplicitExpCondsSize) // Implicit Expensive Conditions go here!
  982.                         {
  983.                                 // We're checking for locked results at the latest possible stage!
  984.                                 // Since it's potentially the most expensive test
  985.                                 {
  986.                                         TLockedPoints::const_iterator itLocked = m_locked.begin();
  987.                                         TLockedPoints::const_iterator itLockedEnd = m_locked.end();
  988.  
  989.                                         for (; bResult && (itLocked != itLockedEnd); ++itLocked)
  990.                                         {
  991.                                                 const TTacticalPoints& lockedPoints = itLocked->second;
  992.  
  993.                                                 TTacticalPoints::const_iterator it = lockedPoints.begin();
  994.                                                 TTacticalPoints::const_iterator end = lockedPoints.end();
  995.  
  996.                                                 for (; it != end; ++it)
  997.                                                 {
  998.                                                         if (topPoint.m_Point == *it)
  999.                                                         {
  1000.                                                                 bResult = false;
  1001.                                                                 break;
  1002.                                                         }
  1003.                                                 }
  1004.                                         }
  1005.                                 }
  1006.                         }
  1007.                         else // Deferred Expensive Conditions
  1008.                         {
  1009.                                 CCriterion& condition = eval.vDefConds[iQueryIndex - iExpCondsSize - iImplicitExpCondsSize];
  1010.                                 AsyncState state = DeferredTest(condition, topPoint.m_Point, eval, bResult);
  1011.  
  1012.                                 if (state == AsyncFailed)
  1013.                                 {
  1014.                                         bOk = false;
  1015.                                         goto HeapReturn;  // On error condition
  1016.                                 }
  1017.  
  1018.                                 if (state != AsyncComplete)
  1019.                                 {
  1020.                                         eval.eEvalState = SQueryEvaluation::eWaitingForDeferred;
  1021.                                         --topPoint.m_iQueryIndex; // keep on the same criterion
  1022.                                         break;
  1023.                                 }
  1024.  
  1025.                                 eval.eEvalState = SQueryEvaluation::eHeapEvaluation;
  1026.                         }
  1027.  
  1028.                         if (!bResult)
  1029.                         {
  1030.                                 // Test failed - reject the point:
  1031.                                 // Move out of heap, into the area for rejected points.
  1032.                                 std::pop_heap(eval.vPoints.begin(), itHeapEndRejectedBegin);
  1033.                                 --itHeapEndRejectedBegin;
  1034.  
  1035.                                 // Mark point as rejected
  1036.                                 itHeapEndRejectedBegin->m_eEvalState = SPointEvaluation::eRejected;
  1037.                                 itHeapEndRejectedBegin->m_fMin = -100.0f;
  1038.                                 itHeapEndRejectedBegin->m_fMax = -100.0f;
  1039.  
  1040.                                 //Have we used up all the valid points?
  1041.                                 if (eval.vPoints.begin() == itHeapEndRejectedBegin)
  1042.                                         break;
  1043.                         }
  1044.                         // Otherwise we will go on to the next query on this point, which will still be top
  1045.                 }
  1046.                 else if (iQueryIndex < iExpCondsSize + iImplicitExpCondsSize + iDefCondsSize + iExpWeightsSize)
  1047.                 {
  1048.                         // Next query to be done is a weight
  1049.                         float fResult;
  1050.                         CCriterion& weight = eval.vExpWeights[iQueryIndex - iExpCondsSize - iImplicitExpCondsSize - iDefCondsSize];
  1051.                         if (!Weight(weight, topPoint.m_Point, eval.queryInstance.queryContext, fResult))
  1052.                         {
  1053.                                 bOk = false;
  1054.                                 goto HeapReturn;  // On error condition
  1055.                         }
  1056.                         // We might consider being less strict on weights, for instance if the object doesn't exist.
  1057.  
  1058.                         float fWeight = weight.GetValueAsFloat();
  1059.                         // Narrow the potential score range
  1060.                         // There's some double negation here that might be optimised out
  1061.                         if (fWeight > 0.0f)
  1062.                         {
  1063.                                 // +ve weight
  1064.                                 topPoint.m_fMin += fWeight * fResult;              // The min possible score is higher now
  1065.                                 topPoint.m_fMax -= fWeight * (1.0f - fResult);     // The max possible score is lower now
  1066.                                 // E.g. for a good result on a _positive_ weight (i.e. close to 1),
  1067.                                 // our min will go up a lot and our max down just a little
  1068.                         }
  1069.                         else
  1070.                         {
  1071.                                 // -ve weight
  1072.                                 topPoint.m_fMin -= fWeight * (1.0f - fResult);     // The min possible score is higher now
  1073.                                 topPoint.m_fMax += fWeight * fResult;              // The max possible score is lower now
  1074.                                 // E.g. for a good result on a _negative_ weight (i.e. close to 0)
  1075.                                 // (just as above) our min will go up a lot and our max down just a little
  1076.                         }
  1077.  
  1078.                         // (MATT) Hang on! We're not using m_fMin! Basically, if m_fMin of this point is higher than m_fMax of the next, we can accept it. {2009/07/15}
  1079.  
  1080.                         // This is likely to change the relative placing of the point in our heap, so re-heap it
  1081.                         // Possible optimisation: check positions 1 and 2 in the heap to avoid a pop/push
  1082.                         // Oddly, STL library doesn't seem to include any alternative
  1083.  
  1084.                         // Invalidates topPoint
  1085.                         std::pop_heap(itHeapBegin, itHeapEndRejectedBegin);
  1086.                         std::push_heap(itHeapBegin, itHeapEndRejectedBegin);
  1087.                 }
  1088.                 else
  1089.                 {
  1090.                         // Have finished all queries on the point of highest potential
  1091.                         // Beats all other points, so must be one of the best n points
  1092.                         topPoint.m_eEvalState = SPointEvaluation::eAccepted;
  1093.  
  1094.                         // Move points into accepted range, which will be formed in descending order. Invalidates topPoint
  1095.                         // Note we have to first move it into the rejected range, then swap first and last rejected point
  1096.                         // End result is to correctly adjust the ranges and add another point to the front of the accepted range
  1097.                         std::pop_heap(itHeapBegin, itHeapEndRejectedBegin);
  1098.                         --itHeapEndRejectedBegin;         // Now inserted into rejected range
  1099.                         --itRejectedEndAcceptedBegin;
  1100.                         std::swap(*itHeapEndRejectedBegin, *itRejectedEndAcceptedBegin); // Now swapped with a rejected element into accepted range
  1101.                         // Note that if there were no rejected points, we will swap it with itself, which is fine
  1102.  
  1103.                         // Do we now have enough points to return?
  1104.                         if (++eval.nFoundBestN == eval.queryInstance.nPoints)
  1105.                                 eval.eEvalState = SQueryEvaluation::eCompleted;
  1106.  
  1107.                         // Have we used up all the valid points?
  1108.                         if (itHeapBegin == itHeapEndRejectedBegin)
  1109.                                 eval.eEvalState = SQueryEvaluation::eCompleted;
  1110.                 }
  1111.         }
  1112.         while (eval.eEvalState == SQueryEvaluation::eHeapEvaluation && (currTime = gEnv->pTimer->GetAsyncTime()) < timeLimit);
  1113.  
  1114.         // The heap is left as any points we didn't have to fully evaluate and invalid range, of the ones we rejected
  1115.  
  1116.         // If we actually found some points...
  1117.         if (eval.eEvalState == SQueryEvaluation::eCompleted)
  1118.         {
  1119.                 // This is slightly inefficient but will help avoid confusion: reverse the Accepted points to put them in descending score order
  1120.                 std::reverse(itRejectedEndAcceptedBegin, eval.vPoints.end());
  1121.         }
  1122.  
  1123. HeapReturn:
  1124.         // Write-back iterators
  1125.         eval.SetIterHeapEndRejectedBegin(itHeapEndRejectedBegin);
  1126.         eval.SetIterRejectedEndAcceptedBegin(itRejectedEndAcceptedBegin);
  1127.         return bOk;
  1128. }
  1129.  
  1130. //----------------------------------------------------------------------------------------------//
  1131.  
  1132. bool CTacticalPointSystem::Test(const CCriterion& criterion, const CTacticalPoint& point, const QueryContext& context, bool& result) const
  1133. {
  1134.         CAISystem* pAISystem = GetAISystem();
  1135.  
  1136.         // Query we will use for the test and type of query
  1137.         TTacticalPointQuery query = criterion.GetQuery();
  1138.         TTacticalPointQuery queryType = (TTacticalPointQuery) (query & eTPQ_MASK_QUERY_TYPE);
  1139.  
  1140.         // Variables we might need
  1141.         bool boolQueryResult;
  1142.         float floatQueryResult;
  1143.  
  1144.         // A sufficiently clever compiler would reduce all this syntax down to about 8 lines of assembler
  1145.         // At some point should check the results, in case writing a s.c.c. needs to be added to backlog
  1146.  
  1147.         switch (queryType)
  1148.         {
  1149.         case eTPQ_FLAG_PROP_BOOL:
  1150.                 if (!BoolProperty(query, point, context, boolQueryResult))
  1151.                         goto TestFail;
  1152.                 result = (criterion.GetValueAsBool() == boolQueryResult);
  1153.                 break;
  1154.  
  1155.         case eTPQ_FLAG_PROP_REAL:
  1156.                 if (!RealProperty(query, point, context, floatQueryResult))
  1157.                         goto TestFail;
  1158.                 result = Limit(criterion.GetLimits(), floatQueryResult, criterion.GetValueAsFloat());
  1159.                 break;
  1160.  
  1161.         case eTPQ_FLAG_TEST:
  1162.                 if (!BoolTest(query, criterion.GetObject(), point, context, boolQueryResult))
  1163.                         goto TestFail;
  1164.                 result = (criterion.GetValueAsBool() == boolQueryResult);
  1165.                 break;
  1166.  
  1167.         case eTPQ_FLAG_MEASURE:
  1168.                 if (!RealMeasure(query, criterion.GetObject(), point, context, floatQueryResult))
  1169.                         goto TestFail;
  1170.                 result = Limit(criterion.GetLimits(), floatQueryResult, criterion.GetValueAsFloat());
  1171.                 break;
  1172.  
  1173.         default:
  1174.                 assert(false);
  1175.                 return false;
  1176.         }
  1177.  
  1178.         return true;
  1179.  
  1180. TestFail:
  1181.         string sBuffer;
  1182.         Unparse(criterion, sBuffer);
  1183.         if (CVars.TacticalPointsWarnings > 0)
  1184.         {
  1185.                 AIWarningID("<TacticalPointSystem> ", "Test criterion failed: %s", sBuffer.c_str());
  1186.         }
  1187.         return false;
  1188. }
  1189.  
  1190. AsyncState CTacticalPointSystem::DeferredTest(const CCriterion& criterion, const CTacticalPoint& point, SQueryEvaluation& eval,
  1191.                                               bool& result) const
  1192. {
  1193.         CAISystem* pAISystem = GetAISystem();
  1194.  
  1195.         // Query we will use for the test and type of query
  1196.         TTacticalPointQuery query = criterion.GetQuery();
  1197.         TTacticalPointQuery queryType = (TTacticalPointQuery) (query & eTPQ_MASK_QUERY_TYPE);
  1198.  
  1199.         // Variables we might need
  1200.         bool boolQueryResult;
  1201.  
  1202.         // A sufficiently clever compiler would reduce all this syntax down to about 8 lines of assembler
  1203.         // At some point should check the results, in case writing a s.c.c. needs to be added to backlog
  1204.  
  1205.         AsyncState state;
  1206.  
  1207.         switch (queryType)
  1208.         {
  1209.         case eTPQ_FLAG_TEST:
  1210.                 {
  1211.                         state = DeferredBoolTest(query, criterion.GetObject(), point, eval, boolQueryResult);
  1212.                         if (state == AsyncFailed)
  1213.                                 goto TestFail;
  1214.  
  1215.                         if (state == AsyncComplete)
  1216.                                 result = (criterion.GetValueAsBool() == boolQueryResult);
  1217.                 }
  1218.                 break;
  1219.  
  1220.         default:
  1221.                 assert(false);
  1222.                 return AsyncFailed;
  1223.         }
  1224.  
  1225.         return state;
  1226.  
  1227. TestFail:
  1228.         string sBuffer;
  1229.         Unparse(criterion, sBuffer);
  1230.         if (CVars.TacticalPointsWarnings > 0)
  1231.         {
  1232.                 AIWarningID("<TacticalPointSystem> ", "Test criterion failed: %s", sBuffer.c_str());
  1233.         }
  1234.         return AsyncFailed;
  1235. }
  1236.  
  1237. //----------------------------------------------------------------------------------------------//
  1238.  
  1239. bool CTacticalPointSystem::Weight(const CCriterion& criterion, const CTacticalPoint& point, const QueryContext& context, float& result) const
  1240. {
  1241.  
  1242.         // if a boolean query, call Test and return 0 or 1
  1243.         // if real, will need to call absolute and normalise to 0-1
  1244.  
  1245.         CAISystem* pAISystem = GetAISystem();
  1246.  
  1247.         // Query we will use for the test and type of query
  1248.         TTacticalPointQuery query = criterion.GetQuery();
  1249.         TTacticalPointQuery queryType = (TTacticalPointQuery) (query & eTPQ_MASK_QUERY_TYPE);
  1250.  
  1251.         // Variables we might need
  1252.         bool boolQueryResult = false;
  1253.         float floatQueryResult = 0.0f;
  1254.  
  1255.         switch (queryType)
  1256.         {
  1257.         case eTPQ_FLAG_PROP_BOOL:
  1258.                 if (!BoolProperty(query, point, context, boolQueryResult))
  1259.                         goto WeightFail;
  1260.                 break;
  1261.  
  1262.         case eTPQ_FLAG_PROP_REAL:
  1263.                 if (!RealProperty(query, point, context, floatQueryResult))
  1264.                         goto WeightFail;
  1265.                 break;
  1266.  
  1267.         case eTPQ_FLAG_TEST:
  1268.                 if (!BoolTest(query, criterion.GetObject(), point, context, boolQueryResult))
  1269.                         goto WeightFail;
  1270.                 break;
  1271.  
  1272.         case eTPQ_FLAG_MEASURE:
  1273.                 if (!RealMeasure(query, criterion.GetObject(), point, context, floatQueryResult))
  1274.                         goto WeightFail;
  1275.                 break;
  1276.  
  1277.         default:
  1278.                 assert(false);
  1279.         }
  1280.  
  1281.         if (queryType & (eTPQ_FLAG_PROP_BOOL | eTPQ_FLAG_TEST))
  1282.         {
  1283.                 result = (boolQueryResult ? 1.0f : 0.0f);
  1284.         }
  1285.         else
  1286.         {
  1287.                 if (criterion.GetLimits())
  1288.                 {
  1289.                         result = (Limit(criterion.GetLimits(), floatQueryResult, criterion.GetValueAsFloat()) ? 1.0f : 0.0f);
  1290.                 }
  1291.                 else
  1292.                 {
  1293.                         // Keep it within the prescribed range
  1294.                         float fMin, fMax;
  1295.                         if (!RealRange(criterion.GetQuery(), fMin, fMax))
  1296.                                 goto WeightFail;
  1297.                         if (floatQueryResult < fMin) floatQueryResult = 0.0f;       // Can skip normalisation
  1298.                         else if (floatQueryResult > fMax)
  1299.                                 floatQueryResult = 1.0f;                                  // Can skip normalisation
  1300.                         else
  1301.                         {
  1302.                                 // Normalise it within that range
  1303.                                 floatQueryResult = (floatQueryResult - fMin) / (fMax - fMin);
  1304.                         }
  1305.                         assert(floatQueryResult >= 0.0f && floatQueryResult <= 1.0f);
  1306.  
  1307.                         result = floatQueryResult;
  1308.                 }
  1309.         }
  1310.  
  1311.         return true;
  1312.  
  1313. WeightFail:
  1314.         string sBuffer;
  1315.         Unparse(criterion, sBuffer);
  1316.         if (CVars.TacticalPointsWarnings > 0)
  1317.         {
  1318.                 AIWarningID("<TacticalPointSystem> ", "Test criterion failed: %s", sBuffer.c_str());
  1319.         }
  1320.         return false;
  1321. }
  1322.  
  1323. //----------------------------------------------------------------------------------------------//
  1324.  
  1325. bool CTacticalPointSystem::Generate(const CCriterion& criterion, const QueryContext& context, const COptionCriteria* pOption, TTacticalPoints& accumulator) const
  1326. {
  1327.         FUNCTION_PROFILER(gEnv->pSystem, PROFILE_AI);
  1328.  
  1329.         // (MATT) Should pOption be a reference, or optional? {2008/04/23}
  1330.         assert(pOption);
  1331.  
  1332.         // Fetch data regarding object
  1333.         CAIObject* pObject = NULL;
  1334.         Vec3 vObjectPos(ZERO);
  1335.         if (!GetObject(criterion.GetObject(), context, pObject, vObjectPos)) return false;
  1336.  
  1337.         // Fetch data regarding aux object (where applicable)
  1338.         CAIObject* pObjectAux = NULL;
  1339.         Vec3 vObjectAuxPos(ZERO);
  1340.         GetObject(criterion.GetObjectAux(), context, pObjectAux, vObjectAuxPos);
  1341.         // Can be NULL if query does not use auxiliary object
  1342.  
  1343.         TTacticalPointQuery query = criterion.GetQuery();
  1344.         float fSearchDist = criterion.GetValueAsFloat();
  1345.         ETPSRelativeValueSource nRVS = criterion.GetValueAsRelativeValueSource();
  1346.         if (nRVS != eTPSRVS_Invalid)
  1347.         {
  1348.                 switch (nRVS)
  1349.                 {
  1350.                 case eTPSRVS_objectRadius:
  1351.                         fSearchDist = context.actorRadius;
  1352.                         break;
  1353.                 default:
  1354.                         AIWarningID("<TacticalPointSystem> ", "Unhandled RelativeValueSource");
  1355.                 }
  1356.         }
  1357.  
  1358.         const float fHeight = pOption->GetParams()->fHeight;
  1359.  
  1360.         // Attempt default implementations
  1361.         if (!GenerateInternal(query, context, fSearchDist, pOption, pObject, vObjectPos, pObjectAux, vObjectAuxPos, accumulator))
  1362.         {
  1363.                 const float fDensity = pOption->GetParams()->fDensity;
  1364.  
  1365.                 CTacticalPointGenerateResult generateResults;
  1366.                 ITacticalPointGenerateResult* pResults = &generateResults;
  1367.  
  1368.                 ITacticalPointLanguageExtender::TGenerateParameters parameters(Translate(query), context, pResults);
  1369.                 ITacticalPointLanguageExtender::SGenerateDetails details(fSearchDist, fDensity, fHeight, pOption->GetParams()->tagPointPostfix);
  1370.                 details.extenderStringParameter = pOption->GetParams()->extenderStringParamenter;
  1371.  
  1372.                 // Allow the language extenders to attempt
  1373.                 TLanguageExtenders::const_iterator itExtender = m_LanguageExtenders.begin();
  1374.                 TLanguageExtenders::const_iterator itExtenderEnd = m_LanguageExtenders.end();
  1375.                 for (; itExtender != itExtenderEnd; ++itExtender)
  1376.                 {
  1377.                         ITacticalPointLanguageExtender* pExtender = (*itExtender);
  1378.                         assert(pExtender);
  1379.  
  1380.                         if (pExtender->GeneratePoints(parameters, details, (IAIObject*)pObject, vObjectPos, (IAIObject*)pObjectAux, vObjectAuxPos) &&
  1381.                             generateResults.HasPoints())
  1382.                         {
  1383.                                 // Got results, stop trying.
  1384.                                 generateResults.GetPoints(accumulator);
  1385.                                 break;
  1386.                         }
  1387.                 }
  1388.         }
  1389.  
  1390.         // apply the height parameter
  1391.         if (!accumulator.empty() && fabs_tpl(fHeight) > 0.001f)
  1392.         {
  1393.                 TTacticalPoints::iterator it = accumulator.begin();
  1394.                 TTacticalPoints::iterator itEnd = accumulator.end();
  1395.  
  1396.                 for (; it != itEnd; ++it)
  1397.                 {
  1398.                         Vec3 vPos(it->GetPos());
  1399.                         vPos.z = vPos.z + fHeight;
  1400.                         it->SetPos(vPos);
  1401.                 }
  1402.         }
  1403.  
  1404.         return true;
  1405. }
  1406.  
  1407. //----------------------------------------------------------------------------------------------//
  1408.  
  1409. bool CTacticalPointSystem::GenerateInternal(TTacticalPointQuery query, const QueryContext& context, float fSearchDist, const COptionCriteria* pOption,
  1410.                                             CAIObject* pObject, const Vec3& vObjectPos, CAIObject* pObjectAux, const Vec3& vObjectAuxPos, TTacticalPoints& accumulator) const
  1411. {
  1412.         FUNCTION_PROFILER(gEnv->pSystem, PROFILE_AI);
  1413.         CAISystem* pAISystem = GetAISystem();
  1414.  
  1415.         // ACTOR HACK
  1416.         Vec3 objPos = vObjectPos;
  1417.         if (pObject && pObject->CastToCAIActor() != NULL)
  1418.                 objPos.z -= 1.8f;
  1419.  
  1420.         // Fetch actor parameters
  1421.         float fAgentRadius = context.actorRadius;
  1422.  
  1423.         switch (query)
  1424.         {
  1425.         case eTPQ_GO_Cover:
  1426.         case eTPQ_GO_Hidespots:
  1427.                 if (gAIEnv.CVars.CoverSystem)
  1428.                 {
  1429.                         FRAME_PROFILER("TPS Generate Cover Locations", gEnv->pSystem, PROFILE_AI);
  1430.  
  1431.                         m_cover.resize(0);
  1432.                         gAIEnv.pCoverSystem->GetCover(objPos, fSearchDist, m_cover);
  1433.                         uint32 coverCount = m_cover.size();
  1434.  
  1435.                         CPipeUser* pipeUser = static_cast<CAIActor*>(context.pAIActor)->CastToCPipeUser();
  1436.                         if (!pipeUser)
  1437.                                 return false;
  1438.  
  1439.                         m_avoidCircles.resize(0);
  1440.                         GatherAvoidCircles(objPos, fSearchDist, pipeUser, m_avoidCircles);
  1441.  
  1442.                         Vec3 eyes[8];
  1443.                         const uint32 MaxEyeCount = std::min<uint32>(gAIEnv.CVars.CoverMaxEyeCount, CRY_ARRAY_COUNT(eyes));
  1444.  
  1445.                         uint32 eyeCount = pipeUser->GetCoverEyes(pObjectAux, vObjectAuxPos, eyes, MaxEyeCount);
  1446.  
  1447.                         if (eyeCount)
  1448.                         {
  1449.                                 FRAME_PROFILER("TPS Generate Cover Locations [GetOcclusion]", gEnv->pSystem, PROFILE_AI);
  1450.  
  1451.                                 IPersistantDebug* pPD = 0;
  1452.                                 if (CVars.DebugTacticalPointsBlocked)
  1453.                                 {
  1454.                                         pPD = gEnv->pGameFramework->GetIPersistantDebug();
  1455.                                         pPD->Begin("BadHideSpots", false);
  1456.                                 }
  1457.  
  1458.                                 if (CAIActor* pAIActor = static_cast<CAIActor*>(context.pAIActor))
  1459.                                 {
  1460.                                         float distanceToCover = std::max<float>(context.distanceToCover, fAgentRadius);
  1461.                                         float inCoverRadius = std::min<float>(context.inCoverRadius, context.actorRadius) + 0.05f;
  1462.                                         float occupyRadius = fAgentRadius + gAIEnv.CVars.CoverSpacing;
  1463.  
  1464.                                         for (uint32 i = 0; i < coverCount; ++i)
  1465.                                         {
  1466.                                                 const CoverID& coverID = m_cover[i];
  1467.  
  1468.                                                 if (pipeUser && pipeUser->IsCoverBlacklisted(coverID))
  1469.                                                         continue;
  1470.  
  1471.                                                 Vec3 normal;
  1472.                                                 Vec3 location = gAIEnv.pCoverSystem->GetCoverLocation(coverID, distanceToCover, 0, &normal);
  1473.  
  1474.                                                 if (normal.dot(vObjectAuxPos - location) >= 0.0001f)
  1475.                                                         continue;
  1476.  
  1477.                                                 AvoidCircles::const_iterator acit = m_avoidCircles.begin();
  1478.                                                 AvoidCircles::const_iterator acend = m_avoidCircles.end();
  1479.  
  1480.                                                 bool occupied = false;
  1481.  
  1482.                                                 for (; acit != acend; ++acit)
  1483.                                                 {
  1484.                                                         const AvoidCircle& circle = *acit;
  1485.  
  1486.                                                         if ((location - circle.pos).GetLengthSquared2D() <= sqr(circle.radius + occupyRadius))
  1487.                                                         {
  1488.                                                                 if (fabs_tpl(location.z - circle.pos.z) < 3.0f)
  1489.                                                                 {
  1490.                                                                         occupied = true;
  1491.                                                                         break;
  1492.                                                                 }
  1493.                                                         }
  1494.                                                 }
  1495.  
  1496.                                                 if (occupied)
  1497.                                                         continue;
  1498.  
  1499.                                                 bool inCover = true;
  1500.  
  1501.                                                 float lowestSq = FLT_MAX;
  1502.                                                 float heightSq;
  1503.  
  1504.                                                 const CoverSurface& surface = gAIEnv.pCoverSystem->GetCoverSurface(coverID);
  1505.  
  1506.                                                 for (uint e = 0; e < eyeCount; ++e)
  1507.                                                 {
  1508.                                                         if (!surface.GetCoverOcclusionAt(eyes[e], location, inCoverRadius, &heightSq))
  1509.                                                         {
  1510.                                                                 inCover = false;
  1511.                                                                 break;
  1512.                                                         }
  1513.  
  1514.                                                         if (heightSq < lowestSq)
  1515.                                                                 lowestSq = heightSq;
  1516.                                                 }
  1517.  
  1518.                                                 if (inCover)
  1519.                                                 {
  1520.                                                         if ((heightSq >= sqr(context.effectiveCoverHeight)) || (context.effectiveCoverHeight <= 0.0001f))
  1521.                                                                 accumulator.push_back(CTacticalPoint(coverID, location));
  1522.                                                         else
  1523.                                                         {
  1524.                                                                 float height = sqrt_tpl(heightSq);
  1525.                                                                 location.z += height;
  1526.  
  1527.                                                                 if (pPD)
  1528.                                                                         pPD->AddCone(location, Vec3(0.0f, 0.0f, -1.0f), 0.25f, height, Col_Red, 3.5f);
  1529.                                                         }
  1530.                                                 }
  1531.                                                 else if (pPD)
  1532.                                                         pPD->AddSphere(location, fAgentRadius, Col_Red, 3.5f);
  1533.                                         }
  1534.                                 }
  1535.                                 else
  1536.                                 {
  1537.                                         for (uint32 i = 0; i < coverCount; ++i)
  1538.                                         {
  1539.                                                 const CoverID& coverID = m_cover[i];
  1540.                                                 const CoverSurface& surface = gAIEnv.pCoverSystem->GetCoverSurface(coverID);
  1541.  
  1542.                                                 Vec3 location = gAIEnv.pCoverSystem->GetCoverLocation(coverID, context.distanceToCover);
  1543.  
  1544.                                                 if (surface.IsCircleInCover(vObjectAuxPos, location, fAgentRadius))
  1545.                                                         accumulator.push_back(CTacticalPoint(coverID, location));
  1546.                                                 else if (pPD)
  1547.                                                         pPD->AddSphere(location, 0.75f, Col_Red, 3.0f);
  1548.                                         }
  1549.                                 }
  1550.                         }
  1551.                         else
  1552.                         {
  1553.                                 for (uint32 i = 0; i < coverCount; ++i)
  1554.                                 {
  1555.                                         const CoverID& coverID = m_cover[i];
  1556.  
  1557.                                         Vec3 location = gAIEnv.pCoverSystem->GetCoverLocation(coverID, fAgentRadius);
  1558.  
  1559.                                         accumulator.push_back(CTacticalPoint(coverID, location));
  1560.                                 }
  1561.                         }
  1562.                 }
  1563.                 else
  1564.                 {
  1565.                         // We could go deeper than this and reap greater benefits
  1566.                         CAIActor* pAIActor = static_cast<CAIActor*>(context.pAIActor);
  1567.                         CPipeUser* pPipeUser = pAIActor->CastToCPipeUser();
  1568.  
  1569.                         if (pPipeUser)
  1570.                         {
  1571.                                 // MATT Actor is checked for NULL internally
  1572.                                 pAISystem->GetOccupiedHideObjectPositions(pPipeUser, m_occupiedSpots);
  1573.                         }
  1574.  
  1575.                         IPersistantDebug* pPD = NULL;
  1576.                         if (CVars.DebugTacticalPointsBlocked)
  1577.                         {
  1578.                                 pPD = gEnv->pGameFramework->GetIPersistantDebug();
  1579.                                 pPD->Begin("OccupiedOrUnreachablePoints", false);
  1580.                         }
  1581.  
  1582.                         MultimapRangeHideSpots hidespots;
  1583.                         MapConstNodesDistance traversedNodes;
  1584.  
  1585.                         IEntity* pEntity = gEnv->pEntitySystem->GetEntity(context.actorEntityId);
  1586.  
  1587.                         bool skipNavigationTest = 0 != (context.actorNavCaps & IAISystem::NAV_VOLUME);
  1588.                         pAISystem->GetHideSpotsInRange(hidespots, traversedNodes, objPos, fSearchDist,
  1589.                                                        context.actorNavCaps, fAgentRadius, skipNavigationTest, pEntity);
  1590.  
  1591.                         Vec3 dir;
  1592.                         MultimapRangeHideSpots::iterator itHEnd = hidespots.end();
  1593.                         for (MultimapRangeHideSpots::iterator itH = hidespots.begin(); itH != itHEnd; ++itH)
  1594.                         {
  1595.                                 SHideSpot& hideSpot = itH->second;
  1596.  
  1597.                                 // Check if this spot is occupied
  1598.                                 if (HasPointInRange(m_occupiedSpots, hideSpot.info.pos, 2.0f) // In original code, this was 0.5f, but that doesn't work...
  1599.                                                                                               // Marcio: Avoid hidepoints the actor couldn't reach recently
  1600.                                     || ((pPipeUser != NULL) && pPipeUser->WasHideObjectRecentlyUnreachable(hideSpot.info.pos)))
  1601.                                 {
  1602.                                         if (pPD) pPD->AddSphere(hideSpot.info.pos + Vec3(0, 0, 0.05f), 2.00f, ColorF(0, 1, 0, 1), 1.0f);
  1603.                                         continue;
  1604.                                 }
  1605.  
  1606.                                 // We use an auxiliary object (to hide from)
  1607.                                 // Kevin - Can be None, meaning we just want a hidespot
  1608.                                 if (!vObjectAuxPos.IsZero())
  1609.                                 {
  1610.                                         // Check if this is directional (an anchor)
  1611.                                         // if ( !hideSpot.dir.IsZero() )
  1612.                                         if (fabsf(hideSpot.info.dir.x) + fabsf(hideSpot.info.dir.y) + fabsf(hideSpot.info.dir.z) > 0.0003f)
  1613.                                         {
  1614.                                                 // check that the enemy is vaguely in the dir of the hide anchor
  1615.                                                 Vec3 dirSpotToAuxObj = vObjectAuxPos - hideSpot.info.pos;
  1616.                                                 dirSpotToAuxObj.NormalizeSafe();
  1617.                                                 float fDot = dirSpotToAuxObj.Dot(hideSpot.info.dir);
  1618.                                                 if (fDot < HIDESPOT_COVERAGE_ANGLE_COS)
  1619.                                                 {
  1620.                                                         if (pPD) pPD->AddSphere(hideSpot.info.pos + Vec3(0, 0, 0.05f), 0.75f, ColorF(1, 0, 0, 1), 1.0f);
  1621.                                                         continue;
  1622.                                                 }
  1623.                                         }
  1624.  
  1625.                                         if (hideSpot.pObstacle) // If obstacle, move point from centre to behind it, w.r.t. object
  1626.                                                 pAISystem->AdjustOmniDirectionalCoverPosition(hideSpot.info.pos, dir,
  1627.                                                                                               max(hideSpot.pObstacle->fApproxRadius, 0.0f), fAgentRadius, vObjectAuxPos, true);
  1628.                                 }
  1629.  
  1630.                                 CTacticalPoint point(hideSpot);
  1631.                                 accumulator.push_back(point);
  1632.                         }
  1633.  
  1634.                         m_occupiedSpots.resize(0);
  1635.                 }
  1636.                 break;
  1637.  
  1638.         case eTPQ_G_Grid:
  1639.                 {
  1640.                         float fDensity = pOption->GetParams()->fDensity;
  1641.                         Vec3 vCenter = objPos;//pPuppet->GetPhysicsPos();
  1642.  
  1643.                         // Copy-past from above [5/7/2010 evgeny]
  1644.                         // We could go deeper than this and reap greater benefits
  1645.                         CAIActor* pAIActor = static_cast<CAIActor*>(context.pAIActor);
  1646.  
  1647.                         int nSteps = (int)(fSearchDist * 2.f / fDensity);
  1648.                         float fGridHalfWidth = fDensity * nSteps / 2;
  1649.                         I3DEngine* pEngine = gEnv->p3DEngine;
  1650.  
  1651.                         float fi = 0.0f;
  1652.                         for (int i = 0; i <= nSteps; ++i, fi += 1.0f)
  1653.                         {
  1654.                                 float fk = 0.0f;
  1655.                                 for (int k = 0; k <= nSteps; ++k, fk += 1.0f)
  1656.                                 {
  1657.                                         // generate point on a plane
  1658.                                         Vec3 vPoint = vCenter + Vec3(-fGridHalfWidth + fDensity * fi, -fGridHalfWidth + fDensity * fk, 0.f);
  1659.                                         float z = vPoint.z;
  1660.  
  1661.                                         // if point generated is beneath ground
  1662.                                         // ...but not too low [5/7/2010 evgeny]
  1663.                                         float fTerrainHeightPlus1m = pEngine->GetTerrainElevation(vPoint.x, vPoint.y) + .2f;
  1664.                                         AABB aabb;
  1665.                                         pAIActor->GetLocalBounds(aabb);
  1666.                                         float fActorHeight = aabb.max.z - aabb.min.z;
  1667.                                         if (z > fTerrainHeightPlus1m - fActorHeight)
  1668.                                         {
  1669.                                                 vPoint.z = max(z, fTerrainHeightPlus1m);
  1670.                                         }
  1671.  
  1672.                                         accumulator.push_back(CTacticalPoint(vPoint));
  1673.                                 }
  1674.                         }
  1675.                 }
  1676.                 break;
  1677.  
  1678.         // a version of the grid generator with no external dependencies and actually generates a grid
  1679.         case eTPQ_G_PureGrid:
  1680.                 {
  1681.                         float density = pOption->GetParams()->fDensity;
  1682.  
  1683.                         int gridPointsCount = (int)(fSearchDist * 2.f / density);
  1684.                         float gridHalfWidth = density * gridPointsCount / 2;
  1685.  
  1686.                         float x = 0.0f;
  1687.                         for (int i = 0; i <= gridPointsCount; ++i, x += 1.0f)
  1688.                         {
  1689.                                 float y = 0.0f;
  1690.                                 for (int j = 0; j <= gridPointsCount; ++j, y += 1.0f)
  1691.                                 {
  1692.                                         Vec3 point = objPos + Vec3(-gridHalfWidth + density * x, -gridHalfWidth + density * y, 0.f);
  1693.                                         accumulator.push_back(CTacticalPoint(point));
  1694.                                 }
  1695.                         }
  1696.                 }
  1697.                 break;
  1698.  
  1699.         case eTPQ_G_Entities:
  1700.                 {
  1701.                         if (pObject)
  1702.                         {
  1703.                                 IEntity* pEntity = pObject->GetEntity();
  1704.                                 EntityId nObjectID = pObject->GetEntityID();
  1705.  
  1706.                                 float fSearchDistSqr = fSearchDist * fSearchDist;
  1707.  
  1708.                                 ActorLookUp& lookUp = *gAIEnv.pActorLookUp;
  1709.  
  1710.                                 size_t activeCount = lookUp.GetActiveCount();
  1711.  
  1712.                                 for (size_t actorIndex = 0; actorIndex < activeCount; ++actorIndex)
  1713.                                 {
  1714.                                         CAIActor* pAIActor = lookUp.GetActor<CAIActor>(actorIndex);
  1715.  
  1716.                                         // excluding self and 'nonActor' actors
  1717.                                         if (nObjectID != lookUp.GetEntityID(actorIndex))
  1718.                                         {
  1719.                                                 IEntity* pOtherEntity = pAIActor->GetEntity();
  1720.  
  1721.                                                 if ((pEntity != pOtherEntity) && objPos.GetSquaredDistance(pOtherEntity->GetPos()) < fSearchDistSqr)
  1722.                                                         accumulator.push_back(CTacticalPoint(pOtherEntity->GetId(), pOtherEntity->GetPos()));
  1723.                                         }
  1724.                                 }
  1725.                         }
  1726.                 }
  1727.                 break;
  1728.  
  1729.         case eTPQ_G_Indoor:
  1730.                 return false;
  1731.                 break;
  1732.  
  1733.         case eTPQ_G_CurrentPos:
  1734.                 accumulator.push_back(CTacticalPoint(objPos));
  1735.                 break;
  1736.  
  1737.         case eTPQ_G_CurrentCover:
  1738.                 {
  1739.                         // This isn't quite right I think, because last hidespot doesn't imply you got there
  1740.                         CPipeUser* pObjectPipeUser = pObject ? pObject->CastToCPipeUser() : 0;
  1741.                         if (pObjectPipeUser)
  1742.                                 accumulator.push_back(CTacticalPoint(pObjectPipeUser->m_CurrentHideObject.GetObjectPos()));
  1743.                 }
  1744.                 break;
  1745.         case eTPQ_G_Objects:
  1746.                 {
  1747.                         short objectType = pOption->GetParams()->iObjectsType;
  1748.  
  1749.                         AIObjectOwners::const_iterator itObject;
  1750.                         AIObjectOwners::const_iterator itEnd = gAIEnv.pAIObjectManager->m_Objects.end();
  1751.  
  1752.                         if (objectType > 0)
  1753.                                 itObject = gAIEnv.pAIObjectManager->m_Objects.lower_bound(objectType);
  1754.                         else
  1755.                                 itObject = gAIEnv.pAIObjectManager->m_Objects.begin();
  1756.  
  1757.                         float fSearchDistSq = fSearchDist * fSearchDist;
  1758.  
  1759.                         for (; itObject != itEnd; ++itObject)
  1760.                         {
  1761.                                 if (objectType > 0 && (objectType != itObject->first))
  1762.                                         break;
  1763.  
  1764.                                 CWeakRef<CAIObject> refObject = itObject->second.GetWeakRef();
  1765.                                 CAIObject* pResultObject = refObject.GetAIObject();
  1766.  
  1767.                                 if (!pResultObject || !pResultObject->IsEnabled())
  1768.                                         continue;
  1769.  
  1770.                                 float distSq = objPos.GetSquaredDistance(pResultObject->GetPos());
  1771.                                 if (distSq < fSearchDistSq)
  1772.                                         accumulator.push_back(CTacticalPoint(refObject));
  1773.                         }
  1774.                 }
  1775.                 break;
  1776.         case eTPQ_G_PointsInNavigationMesh:
  1777.                 {
  1778.                         const NavigationAgentTypeID undefinedNavAgentTypeId;
  1779.  
  1780.                         NavigationAgentTypeID navAgentTypeId(undefinedNavAgentTypeId);
  1781.                         Vec3 searchPivotPos(ZERO);
  1782.  
  1783.                         const string& navAgentTypeName = pOption->GetParams()->sNavigationAgentType;
  1784.                         if (!navAgentTypeName.empty())
  1785.                         {
  1786.                                 navAgentTypeId = gAIEnv.pNavigationSystem->GetAgentTypeID(navAgentTypeName.c_str());
  1787.                         }
  1788.  
  1789.                         if (IAIActor* pAIActor = context.pAIActor)
  1790.                         {
  1791.                                 searchPivotPos = context.actorPos;
  1792.                                 if (navAgentTypeId == undefinedNavAgentTypeId)
  1793.                                 {
  1794.                                         navAgentTypeId = pAIActor->GetNavigationTypeID();
  1795.                                 }
  1796.                         }
  1797.                         else
  1798.                         {
  1799.                                 searchPivotPos = objPos;
  1800.                         }
  1801.  
  1802.                         if (navAgentTypeId != undefinedNavAgentTypeId)
  1803.                         {
  1804.                                 AABB searchAABB;
  1805.                                 searchAABB.max = objPos + Vec3(+fSearchDist, +fSearchDist, 0.2f);
  1806.                                 searchAABB.min = objPos + Vec3(-fSearchDist, -fSearchDist, -20.0f);
  1807.  
  1808.                                 if (const NavigationMeshID meshID = gAIEnv.pNavigationSystem->GetEnclosingMeshID(navAgentTypeId, searchPivotPos))
  1809.                                 {
  1810.                                         const size_t maxCenterLocationCount = 512;
  1811.                                         Vec3 centerLocations[maxCenterLocationCount];
  1812.                                         if (size_t locationCount = gAIEnv.pNavigationSystem->GetTriangleCenterLocationsInMesh(meshID, objPos, searchAABB, centerLocations, maxCenterLocationCount))
  1813.                                         {
  1814.                                                 for (size_t i = 0; i < locationCount; ++i)
  1815.                                                 {
  1816.                                                         accumulator.push_back(CTacticalPoint(centerLocations[i]));
  1817.                                                 }
  1818.                                         }
  1819.                                 }
  1820.                         }
  1821.                 }
  1822.                 break;
  1823.  
  1824.         default:
  1825.                 return false;
  1826.         }
  1827.  
  1828.         return true;
  1829. }
  1830.  
  1831. //----------------------------------------------------------------------------------------------//
  1832.  
  1833. bool CTacticalPointSystem::BoolProperty(TTacticalPointQuery query, const CTacticalPoint& point, const QueryContext& context, bool& result) const
  1834. {
  1835.         assert(query & eTPQ_FLAG_PROP_BOOL);
  1836.  
  1837.         // Set result to false by default to avoid lots of else clauses
  1838.         result = false;
  1839.  
  1840.         // Attempt default implementations
  1841.         bool bHandled = BoolPropertyInternal(query, point, context, result);
  1842.         if (!bHandled)
  1843.         {
  1844.                 ITacticalPointLanguageExtender::TBoolParameters parameters(Translate(query), context, result);
  1845.  
  1846.                 // Allow the language extenders to attempt
  1847.                 TLanguageExtenders::const_iterator itExtender = m_LanguageExtenders.begin();
  1848.                 TLanguageExtenders::const_iterator itExtenderEnd = m_LanguageExtenders.end();
  1849.                 for (; !bHandled && itExtender != itExtenderEnd; ++itExtender)
  1850.                 {
  1851.                         ITacticalPointLanguageExtender* pExtender = (*itExtender);
  1852.                         assert(pExtender);
  1853.  
  1854.                         bHandled = pExtender->BoolProperty(parameters, point);
  1855.                 }
  1856.         }
  1857.  
  1858.         return bHandled;
  1859. }
  1860.  
  1861. //----------------------------------------------------------------------------------------------//
  1862.  
  1863. bool CTacticalPointSystem::BoolPropertyInternal(TTacticalPointQuery query, const CTacticalPoint& point, const QueryContext& context, bool& result) const
  1864. {
  1865.         assert(query & eTPQ_FLAG_PROP_BOOL);
  1866.  
  1867.         // Variables we may need
  1868.         const SHideSpot* pHS;
  1869.  
  1870.         switch (query)
  1871.         {
  1872.         case eTPQ_PB_CoverSoft:
  1873.                 {
  1874.                         pHS = point.GetHidespot();
  1875.                         result = pHS ? pHS->IsSecondary() : false;
  1876.                 }
  1877.                 break;
  1878.  
  1879.         case eTPQ_PB_CoverSuperior:
  1880.         case eTPQ_PB_CoverInferior:
  1881.                 {
  1882.                         pHS = point.GetHidespot();
  1883.                         if (pHS->info.type == SHideSpotInfo::eHST_TRIANGULAR && pHS->pObstacle && pHS->pObstacle->IsCollidable())
  1884.                         {
  1885.                                 result = pHS->pObstacle->fApproxRadius > 0.25f;   // Qualifies as superior
  1886.                                 if (query == eTPQ_PB_CoverInferior) result = !result;
  1887.                         }
  1888.                         else if (pHS->info.type == SHideSpotInfo::eHST_ANCHOR)
  1889.                         {
  1890.                                 result = (query != eTPQ_PB_CoverInferior); // I.e. anchors are always superior
  1891.                         }
  1892.                 }
  1893.                 break;
  1894.  
  1895.         case eTPQ_PB_CurrentlyUsedObject:
  1896.                 {
  1897.                         result = false;
  1898.  
  1899.                         if (context.pAIActor)
  1900.                         {
  1901.                                 CAIActor* pAIActor = static_cast<CAIActor*>(context.pAIActor);
  1902.                                 CPipeUser* pPipeUser = pAIActor->CastToCPipeUser();
  1903.                                 if ((pPipeUser != NULL) && pPipeUser->m_CurrentHideObject.IsValid())
  1904.                                 {
  1905.                                         // This isn't quite right I think, because last hidespot doesn't imply you got there
  1906.                                         pHS = point.GetHidespot();
  1907.                                         if (pHS)
  1908.                                         {
  1909.                                                 const ObstacleData* pObst = pHS->pObstacle;
  1910.                                                 if (pObst && IsEquivalent(pPipeUser->m_CurrentHideObject.GetObjectPos(), pObst->vPos))
  1911.                                                 {
  1912.                                                         result = true;
  1913.                                                 }
  1914.                                         }
  1915.                                 }
  1916.                         }
  1917.                 }
  1918.                 break;
  1919.  
  1920.         case eTPQ_PB_Reachable:
  1921.                 {
  1922.                         // In principle this could be from an object rather than necessarily the actor
  1923.  
  1924.                         const Vec3 testPosition(point.GetPos());
  1925.  
  1926.                         // Succeed the test if either no territory is assigned or if the point is within current territory (which is assumed to be 2D)
  1927.                         if (context.pAIActor)
  1928.                         {
  1929.                                 CAIActor* pAIActor = static_cast<CAIActor*>(context.pAIActor);
  1930.  
  1931.                                 SShape* pTerritory = pAIActor->GetTerritoryShape();
  1932.                                 result = (pTerritory == NULL) || pTerritory->IsPointInsideShape(testPosition, false);
  1933.                         }
  1934.                 }
  1935.                 break;
  1936.  
  1937.         case eTPQ_PB_IsInNavigationMesh:
  1938.                 {
  1939.                         result = false;
  1940.  
  1941.                         const NavigationSystem* pNavigationSystem = gAIEnv.pNavigationSystem;
  1942.  
  1943.                         if (pNavigationSystem)
  1944.                         {
  1945.                                 CAIActor* pAIActor = static_cast<CAIActor*>(context.pAIActor);
  1946.                                 if (pAIActor)
  1947.                                 {
  1948.                                         const Vec3 testPosition(point.GetPos());
  1949.                                         result = pNavigationSystem->IsLocationValidInNavigationMesh(pAIActor->GetNavigationTypeID(), testPosition);
  1950.                                 }
  1951.                         }
  1952.                 }
  1953.                 break;
  1954.  
  1955.         default:
  1956.                 return false;
  1957.         }
  1958.  
  1959.         return true;
  1960. }
  1961.  
  1962. //----------------------------------------------------------------------------------------------//
  1963.  
  1964. bool CTacticalPointSystem::BoolTest(TTacticalPointQuery query, TTacticalPointQuery object, const CTacticalPoint& point, const QueryContext& context, bool& result) const
  1965. {
  1966.         assert(query & eTPQ_FLAG_TEST);
  1967.  
  1968.         // Set result to false by default to avoid lots of else clauses
  1969.         result = false;
  1970.  
  1971.         // Attempt default implementations
  1972.         bool bHandled = BoolTestInternal(query, object, point, context, result);
  1973.         if (!bHandled)
  1974.         {
  1975.                 CAIObject* pObject = NULL;
  1976.                 Vec3 vObjectPos(ZERO);
  1977.                 if (GetObject(object, context, pObject, vObjectPos))
  1978.                 {
  1979.                         ITacticalPointLanguageExtender::TBoolParameters parameters(Translate(query), context, result);
  1980.  
  1981.                         // Allow the language extenders to attempt
  1982.                         TLanguageExtenders::const_iterator itExtender = m_LanguageExtenders.begin();
  1983.                         TLanguageExtenders::const_iterator itExtenderEnd = m_LanguageExtenders.end();
  1984.                         for (; !bHandled && itExtender != itExtenderEnd; ++itExtender)
  1985.                         {
  1986.                                 ITacticalPointLanguageExtender* pExtender = (*itExtender);
  1987.                                 assert(pExtender);
  1988.  
  1989.                                 bHandled = pExtender->BoolTest(parameters, pObject, vObjectPos, point);
  1990.                         }
  1991.                 }
  1992.         }
  1993.  
  1994.         return bHandled;
  1995. }
  1996.  
  1997. //----------------------------------------------------------------------------------------------//
  1998.  
  1999. bool CTacticalPointSystem::BoolTestInternal(TTacticalPointQuery query, TTacticalPointQuery object, const CTacticalPoint& point, const QueryContext& context, bool& result) const
  2000. {
  2001.         assert(query & eTPQ_FLAG_TEST);
  2002.  
  2003.         CAIObject* pObject = NULL;
  2004.         Vec3 vObjectPos(ZERO);
  2005.         if (!GetObject(object, context, pObject, vObjectPos)) return false;
  2006.  
  2007.         Vec3 vTmpA; // Handy temp vector 1
  2008.         Vec3 vTmpB; // Handy temp vector 2
  2009.         float fTmp;
  2010.  
  2011.         // Variables we may need
  2012.         //SHideSpot * pHS;
  2013.  
  2014.         // Set result to false by default to avoid lots of else clauses
  2015.         result = false;
  2016.  
  2017.         switch (query)
  2018.         {
  2019.         case eTPQ_T_Towards:
  2020.                 // Will the point move us some amount towards the object (not away)
  2021.                 vTmpA = point.GetPos() - context.actorPos;
  2022.                 vTmpB = vObjectPos - context.actorPos;
  2023.                 fTmp = vTmpA.Dot(vTmpB);
  2024.                 if (fTmp < 0.0f) break;
  2025.                 // Will it also not pass the object, and not move some silly amount sideways
  2026.                 if (vTmpA.GetLengthSquared() > vTmpB.GetLengthSquared()) break;
  2027.                 result = true;
  2028.                 break;
  2029.  
  2030.         case eTPQ_T_CanReachBefore:
  2031.                 // For now, re-implements part of the old Compromising test, but can be expanded later
  2032.                 // to use some central prediction code
  2033.                 vTmpA = point.GetPos() - context.actorPos;
  2034.                 vTmpB = point.GetPos() - vObjectPos;
  2035.                 result = (vTmpA.GetLengthSquared() < vTmpB.GetLengthSquared());
  2036.                 break;
  2037.  
  2038.         case eTPQ_T_CrossesLineOfFire:
  2039.                 {
  2040.                         Vec3 vDir(ZERO);
  2041.                         if (pObject)
  2042.                         {
  2043.                                 vDir = pObject->GetFireDir();
  2044.                         }
  2045.                         else
  2046.                         {
  2047.                                 vDir = GetObjectInternalDir(object, context);
  2048.                         }
  2049.  
  2050.                         Vec3 vFireEndPoint = vObjectPos + vDir.normalized() * 30.0f;
  2051.  
  2052.                         Lineseg lineOfFire(vObjectPos, vFireEndPoint);
  2053.                         Lineseg directPath(context.actorPos, point.GetPos());
  2054.  
  2055.                         float tA, tB;
  2056.                         result = Intersect::Lineseg_Lineseg2D(lineOfFire, directPath, tA, tB);
  2057.                 }
  2058.                 break;
  2059.  
  2060.         case eTPQ_T_OtherSide:
  2061.                 {
  2062.                         const Vec3 objectToActor = (context.actorPos - vObjectPos).GetNormalized();
  2063.                         const Vec3 objectToPoint = (point.GetPos() - vObjectPos).GetNormalized();
  2064.  
  2065.                         result = objectToActor.Dot(objectToPoint) < 0.0f;
  2066.  
  2067.                         break;
  2068.                 }
  2069.  
  2070.         default:
  2071.                 return false;
  2072.         }
  2073.  
  2074.         return true;
  2075. }
  2076.  
  2077. //----------------------------------------------------------------------------------------------//
  2078.  
  2079. AsyncState CTacticalPointSystem::DeferredBoolTest(TTacticalPointQuery query, TTacticalPointQuery object,
  2080.                                                   const CTacticalPoint& point, SQueryEvaluation& eval, bool& result) const
  2081. {
  2082.         assert(query & eTPQ_FLAG_TEST);
  2083.  
  2084.         // Set result to false by default to avoid lots of else clauses
  2085.         result = false;
  2086.  
  2087.         // Attempt default implementations
  2088.         ETacticalPointDeferredState state = DeferredBoolTestInternal(query, object, point, eval, result);
  2089.         if (state == eTPDS_UnknownRequest)
  2090.         {
  2091.                 CAIObject* pObject = NULL;
  2092.                 Vec3 vObjectPos(ZERO);
  2093.  
  2094.                 const QueryContext& context = eval.queryInstance.queryContext;
  2095.  
  2096.                 if (GetObject(object, context, pObject, vObjectPos))
  2097.                 {
  2098.                         ITacticalPointLanguageExtender::TBoolParameters parameters(Translate(query), context, result);
  2099.  
  2100.                         // Allow the language extenders to attempt
  2101.                         TLanguageExtenders::const_iterator itExtender = m_LanguageExtenders.begin();
  2102.                         TLanguageExtenders::const_iterator itExtenderEnd = m_LanguageExtenders.end();
  2103.                         for (; state == eTPDS_UnknownRequest && itExtender != itExtenderEnd; ++itExtender)
  2104.                         {
  2105.                                 ITacticalPointLanguageExtender* pExtender = (*itExtender);
  2106.                                 assert(pExtender);
  2107.  
  2108.                                 state = pExtender->DeferredBoolTest(parameters, pObject, vObjectPos, point, eval.deferredExtenderCancelFunc);
  2109.                         }
  2110.                         if (state != eTPDS_InProgress)
  2111.                         {
  2112.                                 eval.deferredExtenderCancelFunc = ITacticalPointLanguageExtender::TDeferredCancelFunc();
  2113.                         }
  2114.                 }
  2115.         }
  2116.         switch (state)
  2117.         {
  2118.         case eTPDS_Failed:
  2119.                 return AsyncFailed;
  2120.         case eTPDS_InProgress:
  2121.                 return AsyncInProgress;
  2122.         case eTPDS_Complete:
  2123.                 return AsyncComplete;
  2124.         case eTPDS_UnknownRequest:
  2125.                 return AsyncFailed;
  2126.         default:
  2127.                 return AsyncFailed;
  2128.         }
  2129. }
  2130.  
  2131. //----------------------------------------------------------------------------------------------//
  2132.  
  2133. ETacticalPointDeferredState CTacticalPointSystem::DeferredBoolTestInternal(TTacticalPointQuery query, TTacticalPointQuery object,
  2134.                                                                            const CTacticalPoint& point, SQueryEvaluation& eval, bool& result) const
  2135. {
  2136.         assert(query & eTPQ_FLAG_TEST);
  2137.  
  2138.         const QueryContext& context = eval.queryInstance.queryContext;
  2139.  
  2140.         CAIObject* pObject = NULL;
  2141.         Vec3 vObjectPos(ZERO);
  2142.         if (!GetObject(object, eval.queryInstance.queryContext, pObject, vObjectPos))
  2143.                 return eTPDS_Failed;
  2144.  
  2145.         Vec3 vTmpA; // Handy temp vector 1
  2146.         Vec3 vTmpB; // Handy temp vector 2
  2147.  
  2148.         switch (query)
  2149.         {
  2150.         case eTPQ_T_Visible:
  2151.                 {
  2152.                         if (eval.visibleResult != -1)
  2153.                         {
  2154.                                 result = eval.visibleResult != 0;
  2155.                                 eval.visibleResult = -1;
  2156.                                 eval.visibleRayID = 0;
  2157.  
  2158.                                 return eTPDS_Complete;
  2159.                         }
  2160.                         else if (!eval.visibleRayID)
  2161.                         {
  2162.                                 Vec3 vWaistPos = point.GetPos() + Vec3(0, 0, 1.0f);
  2163.                                 Vec3 vDelta = vObjectPos - vWaistPos;
  2164.  
  2165.                                 eval.visibleRayID = gAIEnv.pRayCaster->Queue(RayCastRequest::HighestPriority,
  2166.                                                                              RayCastRequest(
  2167.                                                                                vWaistPos, vDelta, COVER_OBJECT_TYPES,
  2168.                                                                                AI_VISION_RAY_CAST_FLAG_BLOCKED_BY_SOLID_COVER),
  2169.                                                                              functor(*const_cast<CTacticalPointSystem*>(this), &CTacticalPointSystem::VisibleRayComplete));
  2170.  
  2171.                                 if (CVars.DebugTacticalPoints != 0 && gAIEnv.CVars.DebugDraw != 0)
  2172.                                 {
  2173.                                         IPersistantDebug* debug = gEnv->pGameFramework->GetIPersistantDebug();
  2174.                                         debug->Begin("eTPQ_T_Visible", false);
  2175.                                         debug->AddLine(vWaistPos, vWaistPos + vDelta, (result ? Col_Green : Col_Red), 10.0f);
  2176.                                 }
  2177.                         }
  2178.  
  2179.                         return eTPDS_InProgress;
  2180.                 }
  2181.                 break;
  2182.  
  2183.         case eTPQ_T_CanShoot:
  2184.                 {
  2185.                         // Does this really work for a range of characters?
  2186.  
  2187.                         if (context.pAIActor)
  2188.                         {
  2189.                                 if (eval.canShootResult != -1)
  2190.                                 {
  2191.                                         result = eval.canShootResult != 0;
  2192.                                         eval.canShootResult = -1;
  2193.                                         eval.canShootRayID = 0;
  2194.  
  2195.                                         return eTPDS_Complete;
  2196.                                 }
  2197.                                 else if (!eval.canShootRayID)
  2198.                                 {
  2199.                                         CAIActor* pAIActor = static_cast<CAIActor*>(context.pAIActor);
  2200.  
  2201.                                         const SAIBodyInfo& bodyInfo = pAIActor->GetBodyInfo();
  2202.                                         const Vec3 vWeaponPos = bodyInfo.vFirePos;
  2203.                                         const Vec3 vWeaponDelta = vWeaponPos - pAIActor->GetPhysicsPos();
  2204.  
  2205.                                         // Project weapon delta onto test point
  2206.                                         const Vec3 vPoint = point.GetPos() + vWeaponDelta;
  2207.                                         Vec3 vDelta = vObjectPos - vPoint;
  2208.  
  2209.                                         eval.canShootRayID = gAIEnv.pRayCaster->Queue(RayCastRequest::HighestPriority,
  2210.                                                                                       RayCastRequest(
  2211.                                                                                         vPoint, vDelta, COVER_OBJECT_TYPES,
  2212.                                                                                         AI_VISION_RAY_CAST_FLAG_BLOCKED_BY_SOLID_COVER),
  2213.                                                                                       functor(*const_cast<CTacticalPointSystem*>(this), &CTacticalPointSystem::CanShootRayComplete));
  2214.  
  2215.                                         if (CVars.DebugTacticalPoints != 0 && gAIEnv.CVars.DebugDraw != 0)
  2216.                                         {
  2217.                                                 IPersistantDebug* debug = gEnv->pGameFramework->GetIPersistantDebug();
  2218.                                                 debug->Begin("eTPQ_T_CanShoot", false);
  2219.                                                 debug->AddLine(vPoint, vPoint + vDelta, Col_Blue, 10.0f);
  2220.                                         }
  2221.  
  2222.                                 }
  2223.  
  2224.                                 return eTPDS_InProgress;
  2225.                         }
  2226.                 }
  2227.                 break;
  2228.  
  2229.         case eTPQ_T_CanShootTwoRayTest:
  2230.                 {
  2231.                         if (eval.canShootResult != -1 && eval.canShootSecondRayResult != -1)
  2232.                         {
  2233.                                 result = (eval.canShootResult != 0 && eval.canShootSecondRayResult != 0);
  2234.                                 eval.canShootResult = -1;
  2235.                                 eval.canShootRayID = 0;
  2236.                                 eval.canShootSecondRayResult = -1;
  2237.                                 eval.canShootSecondRayID = 0;
  2238.  
  2239.                                 return eTPDS_Complete;
  2240.                         }
  2241.                         else if (!eval.canShootRayID && !eval.canShootSecondRayID)
  2242.                         {
  2243.                                 const float horizontalDestinationOffset = 0.15f;
  2244.                                 float horizontalSourceOffset = 4.0f;
  2245.  
  2246.                                 const CTacticalPointQuery* pQuery = GetQuery(eval.queryInstance.nQueryID);
  2247.                                 CRY_ASSERT(pQuery);
  2248.  
  2249.                                 const int nOption = eval.iCurrentQueryOption;
  2250.                                 const COptionCriteria* pOption = pQuery->GetOption(nOption);
  2251.                                 if (pOption)
  2252.                                 {
  2253.                                         horizontalSourceOffset = pOption->GetParams()->fHorizontalSpacing;
  2254.                                 }
  2255.  
  2256.                                 Vec3 vRight;
  2257.                                 Vec3 vWeaponPosition;
  2258.  
  2259.                                 const Vec3 pointPos = point.GetPos();
  2260.  
  2261.                                 if (context.pAIActor)
  2262.                                 {
  2263.                                         const CAIActor* pAIActor = static_cast<CAIActor*>(context.pAIActor);
  2264.                                         const SAIBodyInfo& bodyInfo = pAIActor->GetBodyInfo();
  2265.                                         vRight = pAIActor->GetEntity()->GetWorldTM().GetColumn0();
  2266.                                         const Vec3 vWeaponPos = bodyInfo.vFirePos;
  2267.                                         vWeaponPosition = pAIActor->GetPos() - vWeaponPos;
  2268.                                 }
  2269.                                 else
  2270.                                 {
  2271.                                         // cross forwardVec with upVec to get rightVec
  2272.                                         const Vec3 pointToObjectDelta = vObjectPos - pointPos;
  2273.                                         const Vec3 zAxis = Vec3Constants<float>::fVec3_OneZ;
  2274.                                         vRight = (pointToObjectDelta % zAxis).GetNormalized();
  2275.  
  2276.                                         vWeaponPosition = Vec3(0, 0, 1.0f);
  2277.                                 }
  2278.  
  2279.                                 const Vec3 vWeaponSideOffset = vRight * horizontalSourceOffset;
  2280.                                 const Vec3 vWeaponDeltaRight = vWeaponPosition + vWeaponSideOffset;
  2281.                                 const Vec3 vWeaponDeltaLeft = vWeaponPosition - vWeaponSideOffset;
  2282.  
  2283.                                 const Vec3 vPointRight = pointPos + vWeaponDeltaRight;
  2284.                                 const Vec3 vPointLeft = pointPos + vWeaponDeltaLeft;
  2285.                                 const Vec3 vDeltaOffset = vRight * horizontalDestinationOffset;
  2286.                                 const Vec3 vDeltaRight = vObjectPos + vDeltaOffset - vPointRight;
  2287.                                 const Vec3 vDeltaLeft = vObjectPos - vDeltaOffset - vPointLeft;
  2288.  
  2289.                                 eval.canShootRayID = gAIEnv.pRayCaster->Queue(RayCastRequest::HighestPriority,
  2290.                                                                               RayCastRequest(
  2291.                                                                                 vPointLeft, vDeltaLeft, COVER_OBJECT_TYPES,
  2292.                                                                                 AI_VISION_RAY_CAST_FLAG_BLOCKED_BY_SOLID_COVER),
  2293.                                                                               functor(*const_cast<CTacticalPointSystem*>(this), &CTacticalPointSystem::CanShootRayComplete));
  2294.  
  2295.                                 eval.canShootSecondRayID = gAIEnv.pRayCaster->Queue(RayCastRequest::HighestPriority,
  2296.                                                                                     RayCastRequest(
  2297.                                                                                       vPointRight, vDeltaRight, COVER_OBJECT_TYPES,
  2298.                                                                                       AI_VISION_RAY_CAST_FLAG_BLOCKED_BY_SOLID_COVER),
  2299.                                                                                     functor(*const_cast<CTacticalPointSystem*>(this), &CTacticalPointSystem::CanShootSecondRayComplete));
  2300.  
  2301.                                 if (CVars.DebugTacticalPoints != 0 && gAIEnv.CVars.DebugDraw != 0)
  2302.                                 {
  2303.                                         IPersistantDebug* debug = gEnv->pGameFramework->GetIPersistantDebug();
  2304.                                         debug->Begin("eTPQ_T_CanShootTwoRayTest", false);
  2305.                                         debug->AddLine(vPointRight, vPointRight + vDeltaRight, Col_Blue, 10.0f);
  2306.                                         debug->AddLine(vPointLeft, vPointLeft + vDeltaLeft, Col_Blue, 10.0f);
  2307.                                 }
  2308.                         }
  2309.  
  2310.                         return eTPDS_InProgress;
  2311.                 }
  2312.                 break;
  2313.  
  2314.         case eTPQ_T_HasShootingPosture:
  2315.                 {
  2316.                         if (context.pAIActor)
  2317.                         {
  2318.                                 CAIActor* pAIActor = static_cast<CAIActor*>(context.pAIActor);
  2319.                                 if (CPuppet* pPuppet = pAIActor->CastToCPuppet())
  2320.                                 {
  2321.                                         if (!eval.postureQueryID)
  2322.                                         {
  2323.                                                 PostureManager::PostureQuery postureQuery;
  2324.                                                 postureQuery.actor = pAIActor;
  2325.                                                 postureQuery.position = point.GetPos();
  2326.                                                 postureQuery.target = vObjectPos;
  2327.                                                 postureQuery.checks = PostureManager::CheckVisibility | PostureManager::CheckAimability;
  2328.                                                 postureQuery.allowLean = true;
  2329.                                                 postureQuery.allowProne = false;
  2330.                                                 postureQuery.distancePercent = 1.0f;
  2331.                                                 postureQuery.type = PostureManager::AimPosture;
  2332.  
  2333.                                                 if (point.GetType() == ITacticalPoint::eTPT_CoverID)
  2334.                                                         postureQuery.coverID = point.GetCoverID();
  2335.  
  2336.                                                 eval.postureQueryID = pPuppet->GetPostureManager().QueryPosture(postureQuery);
  2337.                                         }
  2338.                                         else
  2339.                                         {
  2340.                                                 PostureManager::PostureID postureID;
  2341.                                                 PostureManager::PostureInfo* postureInfo;
  2342.  
  2343.                                                 AsyncState state = pPuppet->GetPostureManager().GetPostureQueryResult(eval.postureQueryID, &postureID,
  2344.                                                                                                                       &postureInfo);
  2345.  
  2346.                                                 if (state != AsyncInProgress)
  2347.                                                         eval.postureQueryID = 0;
  2348.  
  2349.                                                 if (state == AsyncComplete)
  2350.                                                 {
  2351.                                                         result = true;
  2352.                                                         return eTPDS_Complete;
  2353.                                                 }
  2354.                                                 else if (state == AsyncFailed)
  2355.                                                 {
  2356.                                                         result = false;
  2357.                                                         return eTPDS_Complete;
  2358.                                                 }
  2359.                                         }
  2360.  
  2361.                                         if (!eval.postureQueryID)
  2362.                                         {
  2363.                                                 result = false;
  2364.                                                 return eTPDS_Complete;
  2365.                                         }
  2366.                                 }
  2367.                         }
  2368.                 }
  2369.                 break;
  2370.  
  2371.         default:
  2372.                 return eTPDS_UnknownRequest;
  2373.         }
  2374.  
  2375.         return eTPDS_InProgress;
  2376. }
  2377.  
  2378. //----------------------------------------------------------------------------------------------//
  2379.  
  2380. bool CTacticalPointSystem::RealProperty(TTacticalPointQuery query, const CTacticalPoint& point, const QueryContext& context, float& result) const
  2381. {
  2382.         assert(query & eTPQ_FLAG_PROP_REAL);
  2383.  
  2384.         // Set result to 0 by default to avoid lots of else clauses
  2385.         result = 0.0f;
  2386.  
  2387.         // Attempt default implementations
  2388.         bool bHandled = RealPropertyInternal(query, point, context, result);
  2389.         if (!bHandled)
  2390.         {
  2391.                 ITacticalPointLanguageExtender::TRealParameters parameters(Translate(query), context, result);
  2392.  
  2393.                 // Allow the language extenders to attempt
  2394.                 TLanguageExtenders::const_iterator itExtender = m_LanguageExtenders.begin();
  2395.                 TLanguageExtenders::const_iterator itExtenderEnd = m_LanguageExtenders.end();
  2396.                 for (; !bHandled && itExtender != itExtenderEnd; ++itExtender)
  2397.                 {
  2398.                         ITacticalPointLanguageExtender* pExtender = (*itExtender);
  2399.                         assert(pExtender);
  2400.  
  2401.                         bHandled = pExtender->RealProperty(parameters, point);
  2402.                 }
  2403.         }
  2404.  
  2405.         return bHandled;
  2406. }
  2407.  
  2408. //----------------------------------------------------------------------------------------------//
  2409.  
  2410. bool CTacticalPointSystem::RealPropertyInternal(TTacticalPointQuery query, const CTacticalPoint& point, const QueryContext& context, float& result) const
  2411. {
  2412.         assert(query & eTPQ_FLAG_PROP_REAL);
  2413.  
  2414.         result = 0.0f;
  2415.  
  2416.         switch (query)
  2417.         {
  2418.         case eTPQ_PR_CoverRadius:
  2419.                 break;
  2420.  
  2421.         case eTPQ_PR_CoverDensity:
  2422.                 {
  2423.                         assert(false);
  2424.                         // INTEGRATION : (MATT) Translating this is postsponed until the basics are running {2007/08/23:16:17:01}
  2425.                         /*
  2426.  
  2427.                            // This should move out to a method library
  2428.                            // Also, it should be more general in types of navigation and hidespot
  2429.                            // and it could potentially use some precomputation/caching
  2430.  
  2431.                            // Check if this point is valid for our query
  2432.                            const SHideSpot *pHideSpot = point.GetHidespot();
  2433.                            if (!pHideSpot)  break;
  2434.                            if (!pHideSpot->pNavNodes) break;
  2435.  
  2436.                            // Form a set of unique object indices relevant to this location
  2437.                            // Effectively this goes 2 levels through the navgraph
  2438.                            CGraphLinkManager& linkManager = gAIEnv.pGraph->GetLinkManager();
  2439.                            set<int> obstacleIdxSet;
  2440.                            vector<const GraphNode *>::const_iterator itN = pHideSpot->pNavNodes->begin();
  2441.                            for (; itN != pHideSpot->pNavNodes->end(); itN++)
  2442.                            {
  2443.                            // Fetch this nearby node and skip if not valid for this query
  2444.                            const GraphNode * pNode = (*itN);
  2445.                            if (! (pNode->navType & IAISystem::NAV_TRIANGULAR)) continue;
  2446.  
  2447.                            const STriangularNavData *pData = pNavNode->GetTriangularNavData();
  2448.  
  2449.                            const ObstacleIndexVector::const_iterator vertEnd = pData->vertices.end();
  2450.                            for (ObstacleIndexVector::const_iterator vertIt = pData->vertices.begin() ; vertIt != vertEnd ; ++vertIt)
  2451.                            {
  2452.  
  2453.  
  2454.  
  2455.                            }
  2456.  
  2457.  
  2458.  
  2459.  
  2460.  
  2461.                            const GraphNode* pPathfinderCurrent = m_pathfinderCurrent->graphNode;//m_request.m_pGraph->GetNodeManager().GetNode(m_pathfinderCurrent);
  2462.                            for (unsigned linkId = pPathfinderCurrent->firstLinkIndex ; linkId ; linkId = m_request.m_pGraph->GetLinkManager().GetNextLink(linkId))
  2463.                            {
  2464.                            unsigned nextNodeIndex = m_request.m_pGraph->GetLinkManager().GetNextNode(linkId);
  2465.                            const GraphNode* nextNode = m_request.m_pGraph->GetNodeManager().GetNode(nextNodeIndex);
  2466.  
  2467.  
  2468.                            // Run through links to all the neighboring nodes of this node
  2469.                            unsigned nLinks = pNode->GetLinkCount(linkManager);
  2470.                            for (unsigned iLink = 0 ; iLink < nLinks ; ++iLink)
  2471.                            {
  2472.                             // Fetch and check this node
  2473.                             const GraphLink& link = pNode->GetLinkIndex()
  2474.                             const GraphNode* pNext = link.pNextNode;
  2475.                             if (! (pNext->navType & IAISystem::NAV_TRIANGULAR))
  2476.                               continue;
  2477.  
  2478.                             // Insert all its obstacles into the set
  2479.                             const ObstacleIndexVector &vObstacles = pNext->GetTriangularNavData()->vertices;
  2480.                             obstacleIdxSet.insert(vObstacles.begin(), vObstacles.end());
  2481.                            }
  2482.                            }
  2483.  
  2484.                            // Start at 0, but a conventional hidepoint will always have at least one obstacle very close by.
  2485.                            // We could consider radius here, and many other things. Could become a very complex query.
  2486.                            float fDensity = 0.0f;
  2487.                            CAISystem *pAISystem = GetAISystem();
  2488.                            for (set<int>::const_iterator itO = obstacleIdxSet.begin(); itO != obstacleIdxSet.end(); itO++)
  2489.                            {
  2490.                            const ObstacleData &od = pAISystem->GetObstacle(*itO);
  2491.                            float fDist = od.vPos.GetDistance(point.GetPos());
  2492.                            if (fDist > 8.0f) continue;                                          // Ignore obstacles that are miles away
  2493.                            if (fDist < 2.0f) fDist = 2.0f;                              // Don't allow one very close obstacle to skew results
  2494.                            fDensity += 1.0f / (fDist * fDist);          // Squaring might be a little excessive
  2495.                            }
  2496.                            // Finally, write the result
  2497.                            result = fDensity;
  2498.                          */
  2499.                 }
  2500.                 break;
  2501.  
  2502.         case eTPQ_PR_BulletImpacts:
  2503.                 break;
  2504.  
  2505.         case eTPQ_PR_CameraCenter:
  2506.                 {
  2507.                         CDebugDrawContext dc;
  2508.  
  2509.                         // Halved screen dimensions, in floats
  2510.                         float fWidth = static_cast<float>(dc->GetWidth());
  2511.                         float fHeight = static_cast<float>(dc->GetHeight());
  2512.  
  2513.                         Vec3 vPos = point.GetPos();
  2514.                         float fX, fY, fZ;
  2515.                         // fX, fY are in range [0, 100]
  2516.                         if (!dc->ProjectToScreen(vPos.x, vPos.y, vPos.z, &fX, &fY, &fZ))
  2517.                         {
  2518.                                 result = -1;
  2519.                                 break;
  2520.                         }
  2521.  
  2522.                         fX *= fWidth * 0.01f;
  2523.                         fY *= fHeight * 0.01f;
  2524.  
  2525.                         // [2/5/2009 evgeny] Reject points that are behind the frustrum near plane
  2526.                         CCamera& camera = GetISystem()->GetViewCamera();
  2527.                         Vec3 vCameraNormalizedDirection = camera.GetViewdir().normalize();
  2528.                         Vec3 vPointRelativeToCameraNearPlane =
  2529.                           point.GetPos() - camera.GetPosition() - camera.GetNearPlane() * vCameraNormalizedDirection;
  2530.                         if (vCameraNormalizedDirection.dot(vPointRelativeToCameraNearPlane) <= 0)
  2531.                         {
  2532.                                 result = -1;
  2533.                         }
  2534.                         else
  2535.                         {
  2536.                                 // [2/5/2009 evgeny] Make fX, fY be from range [0, 1]
  2537.                                 fX = 0.02f * fabs(fX - 50.0f);
  2538.                                 fY = 0.02f * fabs(fY - 50.0f);
  2539.  
  2540.                                 // The result is positive for the points on the screen
  2541.                                 result = 1.0f - max(fX, fY);
  2542.  
  2543.                                 if (result < -1)
  2544.                                         result = -1;
  2545.                         }
  2546.                 }
  2547.                 break;
  2548.  
  2549.         case eTPQ_PR_Random:
  2550.                 result = cry_random(0.0f, 1.0f);
  2551.                 break;
  2552.  
  2553.         case eTPQ_PR_Type:
  2554.                 {
  2555.                         const CAIObject* pObj = point.GetAI().GetAIObject();
  2556.                         result = (pObj ? static_cast<float>(pObj->GetType()) : 0.0f);
  2557.                 }
  2558.                 break;
  2559.  
  2560.         case eTPQ_PR_HostilesDistance:
  2561.         case eTPQ_PR_FriendlyDistance:
  2562.                 {
  2563.                         // Find some AIs, using a generous range
  2564.                         // Choose the nearest hostile one and return a distance metric
  2565.                         // If none, return the generous range
  2566.                         const float fRange = 100.0f;
  2567.                         Vec3 vPointPos = point.GetPos();
  2568.                         AutoAIObjectIter iter(
  2569.                           gAIEnv.pAIObjectManager->GetFirstAIObjectInRange(OBJFILTER_TYPE, 0, context.actorPos, fRange, true));
  2570.                         IAIObject* pOtherObj = iter->GetObject();
  2571.                         float fMinDistSq = fRange * fRange;
  2572.                         // This isn't a nice loop - clean it up
  2573.                         while (pOtherObj)
  2574.                         {
  2575.                                 EntityId otherObjectId = pOtherObj->GetEntityID();
  2576.                                 iter->Next();
  2577.                                 pOtherObj = iter->GetObject();
  2578.                                 if (!pOtherObj || !otherObjectId || otherObjectId == context.actorEntityId) continue;
  2579.  
  2580.                                 // Check whether this actor is relevant, based on the specific flavour of query
  2581.                                 bool bHostile = IsHostile(context.actorEntityId, otherObjectId);
  2582.                                 if (((eTPQ_PR_HostilesDistance == query) && bHostile) || ((eTPQ_PR_FriendlyDistance == query) && !bHostile))
  2583.                                 {
  2584.                                         float fDistSq = pOtherObj->GetPos().GetSquaredDistance(vPointPos);
  2585.                                         if (fDistSq < fMinDistSq)
  2586.                                         {
  2587.                                                 fMinDistSq = fDistSq;
  2588.                                         }
  2589.                                 }
  2590.                         }
  2591.                         result = sqrtf(fMinDistSq);
  2592.                 }
  2593.                 break;
  2594.  
  2595.         default:
  2596.                 return false;
  2597.         }
  2598.  
  2599.         return true;
  2600. }
  2601.  
  2602. //----------------------------------------------------------------------------------------------//
  2603. bool CTacticalPointSystem::IsHostile(EntityId entityId1, EntityId entityId2) const
  2604. {
  2605.         assert(entityId1 > 0);
  2606.         assert(entityId2 > 0);
  2607.  
  2608.         bool bRet = false;
  2609.  
  2610.         if (entityId1 != entityId2)
  2611.         {
  2612.                 IEntitySystem* pEntitySystem = gEnv->pEntitySystem;
  2613.                 assert(pEntitySystem);
  2614.                 IEntity* pEntity1 = pEntitySystem->GetEntity(entityId1);
  2615.                 IEntity* pEntity2 = pEntitySystem->GetEntity(entityId2);
  2616.  
  2617.                 // Check their actors if available
  2618.                 const CAIActor* pActor1 = CastToCAIActorSafe(pEntity1 ? pEntity1->GetAI() : 0);
  2619.                 const CAIActor* pActor2 = CastToCAIActorSafe(pEntity2 ? pEntity2->GetAI() : 0);
  2620.                 if (pActor1 != NULL && pActor2 != NULL)
  2621.                         return pActor1->IsHostile(pActor2);
  2622.         }
  2623.  
  2624.         return(bRet);
  2625. }
  2626.  
  2627. //----------------------------------------------------------------------------------------------//
  2628.  
  2629. bool CTacticalPointSystem::RealMeasure(TTacticalPointQuery query, TTacticalPointQuery object, const CTacticalPoint& point, const QueryContext& context, float& result) const
  2630. {
  2631.         assert(query & eTPQ_FLAG_MEASURE);
  2632.  
  2633.         // Set result to 0 by default to avoid lots of else clauses
  2634.         result = 0.0f;
  2635.  
  2636.         // Attempt default implementations
  2637.         bool bHandled = RealMeasureInternal(query, object, point, context, result);
  2638.         if (!bHandled)
  2639.         {
  2640.                 CAIObject* pObject = NULL;
  2641.                 Vec3 vObjectPos(ZERO);
  2642.                 if (GetObject(object, context, pObject, vObjectPos))
  2643.                 {
  2644.                         ITacticalPointLanguageExtender::TRealParameters parameters(Translate(query), context, result);
  2645.  
  2646.                         // Allow the language extenders to attempt
  2647.                         TLanguageExtenders::const_iterator itExtender = m_LanguageExtenders.begin();
  2648.                         TLanguageExtenders::const_iterator itExtenderEnd = m_LanguageExtenders.end();
  2649.                         for (; !bHandled && itExtender != itExtenderEnd; ++itExtender)
  2650.                         {
  2651.                                 ITacticalPointLanguageExtender* pExtender = (*itExtender);
  2652.                                 assert(pExtender);
  2653.  
  2654.                                 bHandled = pExtender->RealMeasure(parameters, pObject, vObjectPos, point);
  2655.                         }
  2656.                 }
  2657.         }
  2658.  
  2659.         return bHandled;
  2660. }
  2661.  
  2662. //----------------------------------------------------------------------------------------------//
  2663.  
  2664. bool CTacticalPointSystem::RealMeasureInternal(TTacticalPointQuery query, TTacticalPointQuery object, const CTacticalPoint& point, const QueryContext& context, float& result) const
  2665. {
  2666.         assert(query & eTPQ_FLAG_MEASURE);
  2667.  
  2668.         CAIObject* pObject = 0;
  2669.         Vec3 vObjectPos(ZERO);
  2670.         if (!GetObject(object, context, pObject, vObjectPos)) return false;
  2671.  
  2672.         Vec3 vTmpA; // Handy temp vector 1
  2673.         Vec3 vTmpB; // Handy temp vector 2
  2674.  
  2675.     &