/* * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or * its licensors. * * For complete copyright and license terms please see the LICENSE at the root of this * distribution (the "License"). All use of this software is governed by the License, * or, if provided, by the license below or the license accompanying this file. Do not * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * */ // Original file Copyright Crytek GMBH or its affiliates, used under license. // Description : implementation of the CAIActor class. #include "CryLegacy_precompiled.h" #include "AIActor.h" #include "PipeUser.h" #include "GoalOp.h" #include "SelectionTree/SelectionTreeManager.h" #include "BehaviorTree/IBehaviorTree.h" #include "BehaviorTree/Node.h" #include "BehaviorTree/XmlLoader.h" #include "BehaviorTree/BehaviorTreeNodes_AI.h" #include "TargetSelection/TargetTrackManager.h" #include "Navigation/NavigationSystem/NavigationSystem.h" #include "Group/GroupManager.h" #include "Factions/FactionMap.h" #include "CryCrc32.h" #include "IEntity.h" #include #include "SelectionTree/SelectionTreeDebugger.h" #include #define GET_READY_TO_CHANGE_BEHAVIOR_SIGNAL "OnBehaviorChangeRequest" //#pragma optimize("", off) //#pragma inline_depth(0) static const float UNINITIALIZED_COS_CACHE = 2.0f; ////////////////////////////////////////////////////////////////////// // Construction/Destruction ////////////////////////////////////////////////////////////////////// #define _ser_value_(val) ser.Value(#val, val) #pragma warning (disable : 4355) CAIActor::CAIActor() : m_bCheckedBody(true) , #ifdef CRYAISYSTEM_DEBUG m_healthHistory(0) , #endif m_lightLevel(AILL_LIGHT) , m_usingCombatLight(false) , m_perceptionDisabled(0) , m_cachedWaterOcclusionValue(0.0f) , m_vLastFullUpdatePos(ZERO) , m_lastFullUpdateStance(STANCE_NULL) , m_observer(false) , m_bCloseContact(false) , m_FOVPrimaryCos(UNINITIALIZED_COS_CACHE) , m_FOVSecondaryCos(UNINITIALIZED_COS_CACHE) , m_territoryShape(0) , m_lastBodyDir(ZERO) , m_bodyTurningSpeed(0) , m_stimulusStartTime(-100.f) , m_activeCoordinationCount(0) , m_navigationTypeID(0) , m_behaviorTreeEvaluationMode(EvaluateWhenVariablesChange) , m_currentCollisionAvoidanceRadiusIncrement(0.0f) , m_runningBehaviorTree(false) { _fastcast_CAIActor = true; AILogComment("CAIActor (%p)", this); } #pragma warning (default : 4355) CAIActor::~CAIActor() { StopBehaviorTree(); AILogComment("~CAIActor %s (%p)", GetName(), this); gAIEnv.pGroupManager->RemoveGroupMember(GetGroupId(), GetAIObjectID()); CAISystem* pAISystem = GetAISystem(); CAIGroup* pGroup = pAISystem->GetAIGroup(GetGroupId()); if (pGroup) { pGroup->RemoveMember(this); } SetObserver(false); #ifdef CRYAISYSTEM_DEBUG delete m_healthHistory; #endif m_State.ClearSignals(); pAISystem->NotifyEnableState(this, false); pAISystem->UnregisterAIActor(StaticCast(GetSelfReference())); } void CAIActor::SetBehaviorVariable(const char* variableName, bool value) { if (m_behaviorSelectionTree.get()) { SelectionVariableID variableID = m_behaviorSelectionTree->GetTemplate().GetVariableDeclarations().GetVariableID(variableName); assert(m_behaviorSelectionTree->GetTemplate().GetVariableDeclarations().IsDeclared(variableID)); #ifndef _RELEASE if (!m_behaviorSelectionTree->GetTemplate().GetVariableDeclarations().IsDeclared(variableID)) { CryWarning(VALIDATOR_MODULE_AI, VALIDATOR_WARNING, "Variable \"%s\" missing from %s's Behaviour Selection Tree.", variableName, GetName()); } #endif m_behaviorSelectionVariables->SetVariable(variableID, value); } { Variables::Collection* variableCollection = GetAISystem()->GetIBehaviorTreeManager()->GetBehaviorVariableCollection_Deprecated(AZ::EntityId(GetEntityID())); const Variables::Declarations* variableDeclarations = GetAISystem()->GetIBehaviorTreeManager()->GetBehaviorVariableDeclarations_Deprecated(AZ::EntityId(GetEntityID())); if (!variableCollection || !variableDeclarations) { return; } Variables::VariableID variableID = Variables::GetVariableID(variableName); if (variableDeclarations->IsDeclared(variableID)) { variableCollection->SetVariable(variableID, value); } else { AIWarning("Variable '%s' missing from %s's Behavior Tree.", variableName, GetName()); } } } bool CAIActor::GetBehaviorVariable(const char* variableName) const { bool value = false; if (m_behaviorSelectionTree.get()) { SelectionVariableID variableID = m_behaviorSelectionTree->GetTemplate().GetVariableDeclarations().GetVariableID(variableName); m_behaviorSelectionVariables->GetVariable(variableID, &value); return value; } { Variables::Collection* variableCollection = GetAISystem()->GetIBehaviorTreeManager()->GetBehaviorVariableCollection_Deprecated(AZ::EntityId(GetEntityID())); const Variables::Declarations* variableDeclarations = GetAISystem()->GetIBehaviorTreeManager()->GetBehaviorVariableDeclarations_Deprecated(AZ::EntityId(GetEntityID())); if (!variableCollection || !variableDeclarations) { return false; } Variables::VariableID variableID = Variables::GetVariableID(variableName); if (variableDeclarations->IsDeclared(variableID)) { variableCollection->GetVariable(variableID, &value); } else { AIWarning("Variable '%s' missing from %s's Behavior Tree.", variableName, GetName()); } return value; } } SelectionTree* CAIActor::GetBehaviorSelectionTree() const { return m_behaviorSelectionTree.get(); } SelectionVariables* CAIActor::GetBehaviorSelectionVariables() const { return m_behaviorSelectionVariables.get(); } void CAIActor::ResetModularBehaviorTree(EObjectResetType type) { if (type == AIOBJRESET_SHUTDOWN) { StopBehaviorTree(); } else { // Try to load a Modular Behavior Tree if (IScriptTable* table = GetEntity()->GetScriptTable()) { SmartScriptTable properties; if (table->GetValue("Properties", properties) && properties) { const char* behaviorTreeName = NULL; if (properties->GetValue("esModularBehaviorTree", behaviorTreeName) && behaviorTreeName && behaviorTreeName[0]) { StartBehaviorTree(behaviorTreeName); } } } } } void CAIActor::ResetBehaviorSelectionTree(EObjectResetType type) { m_behaviorTreeEvaluationMode = EvaluateWhenVariablesChange; bool bRemoveBehaviorSelectionTree = (type == AIOBJRESET_SHUTDOWN); IAIActorProxy* proxy = GetProxy(); if (!bRemoveBehaviorSelectionTree && proxy) { // Try to load a Selection Tree const char* behaviorSelectionTreeName = proxy->GetBehaviorSelectionTreeName(); bool treeChanged = ((behaviorSelectionTreeName && !m_behaviorSelectionTree.get()) || (behaviorSelectionTreeName != 0 && azstricmp(m_behaviorSelectionTree->GetTemplate().GetName(), behaviorSelectionTreeName))); if (treeChanged) { SelectionTreeTemplateID templateID = gAIEnv.pSelectionTreeManager->GetTreeTemplateID(behaviorSelectionTreeName); if (gAIEnv.pSelectionTreeManager->HasTreeTemplate(templateID)) { const SelectionTreeTemplate& treeTemplate = gAIEnv.pSelectionTreeManager->GetTreeTemplate(templateID); if (treeTemplate.Valid()) { m_behaviorSelectionTree.reset(new SelectionTree(treeTemplate.GetSelectionTree())); m_behaviorSelectionVariables.reset(new SelectionVariables(treeTemplate.GetVariableDeclarations().GetDefaults())); m_behaviorSelectionVariables->ResetChanged(true); } } else { bRemoveBehaviorSelectionTree = true; } } } if (bRemoveBehaviorSelectionTree) { m_behaviorSelectionTree.reset(); m_behaviorSelectionVariables.reset(); } } bool CAIActor::ProcessBehaviorSelectionTreeSignal(const char* signalName, uint32 signalCRC) { FUNCTION_PROFILER(gEnv->pSystem, PROFILE_AI); if (m_behaviorSelectionVariables.get()) { #if defined(CRYAISYSTEM_DEBUG) m_behaviorSelectionVariables->DebugTrackSignalHistory(signalName); #endif const SelectionTreeTemplate& treeTemplate = m_behaviorSelectionTree->GetTemplate(); return treeTemplate.GetSignalVariables().ProcessSignal(signalName, signalCRC, *m_behaviorSelectionVariables); } return false; } bool CAIActor::UpdateBehaviorSelectionTree() { FUNCTION_PROFILER(gEnv->pSystem, PROFILE_AI); if (m_behaviorSelectionTree.get()) { const bool evaluateTree = m_behaviorSelectionVariables.get() && m_behaviorSelectionVariables->Changed() && m_behaviorTreeEvaluationMode == EvaluateWhenVariablesChange; if (evaluateTree) { const char* behaviorName = ""; SelectionNodeID currentNodeID = m_behaviorSelectionTree->GetCurrentNodeID(); BST_DEBUG_START_EVAL(this, *m_behaviorSelectionTree.get(), *m_behaviorSelectionVariables.get()); SelectionNodeID selectedNodeID = m_behaviorSelectionTree->Evaluate(*m_behaviorSelectionVariables.get()); BST_DEBUG_END_EVAL(this, selectedNodeID); if (selectedNodeID) { m_behaviorSelectionVariables->ResetChanged(); if (currentNodeID == selectedNodeID) { return false; } const SelectionTreeNode& node = m_behaviorSelectionTree->GetNode(selectedNodeID); behaviorName = node.GetName(); const SelectionTreeTemplate& treeTemplate = m_behaviorSelectionTree->GetTemplate(); if (const char* translatedName = treeTemplate.GetTranslator().GetTranslation(selectedNodeID)) { behaviorName = translatedName; } } IAIActorProxy* pProxy = GetProxy(); assert(pProxy); if (pProxy) { pProxy->SetBehaviour(behaviorName); } return true; } } return false; } #if defined(CRYAISYSTEM_DEBUG) void CAIActor::DebugDrawBehaviorSelectionTree() { if (m_behaviorSelectionVariables.get()) { const SelectionTreeTemplate& treeTemplate = m_behaviorSelectionTree->GetTemplate(); m_behaviorSelectionVariables->DebugDraw(true, treeTemplate.GetVariableDeclarations()); } if (m_behaviorSelectionTree.get()) { m_behaviorSelectionTree->DebugDraw(); } } #endif const SAIBodyInfo& CAIActor::QueryBodyInfo() { m_proxy->QueryBodyInfo(m_bodyInfo); return m_bodyInfo; } const SAIBodyInfo& CAIActor::GetBodyInfo() const { return m_bodyInfo; } void CAIActor::SetPos(const Vec3& pos, const Vec3& dirFwrd) { gAIEnv.pActorLookUp->Prepare(0); Vec3 position(pos); Vec3 vEyeDir(dirFwrd); if (IAIActorProxy* pProxy = GetProxy()) { SAIBodyInfo bodyInfo; pProxy->QueryBodyInfo(bodyInfo); assert(bodyInfo.vEyeDir.IsValid()); assert(bodyInfo.vEyePos.IsValid()); assert(bodyInfo.vFireDir.IsValid()); assert(bodyInfo.vFirePos.IsValid()); position = bodyInfo.vEyePos; vEyeDir = bodyInfo.GetEyeDir(); assert(vEyeDir.IsUnit()); SetViewDir(vEyeDir); SetBodyDir(bodyInfo.GetBodyDir()); SetFirePos(bodyInfo.vFirePos); SetFireDir(bodyInfo.vFireDir); SetMoveDir(bodyInfo.vMoveDir); SetEntityDir(bodyInfo.GetBodyDir()); assert(bodyInfo.vFireDir.IsUnit()); assert(bodyInfo.vMoveDir.IsUnit() || bodyInfo.vMoveDir.IsZero()); assert(bodyInfo.vEntityDir.IsUnit() || bodyInfo.vEntityDir.IsZero()); } CAIObject::SetPos(position, vEyeDir); // can set something else than passed position if (m_pFormation) { m_pFormation->Update(); } if (m_observer) { ObserverParams observerParams; observerParams.eyePosition = position; observerParams.eyeDirection = vEyeDir; gAIEnv.pVisionMap->ObserverChanged(GetVisionID(), observerParams, eChangedPosition | eChangedOrientation); } gAIEnv.pActorLookUp->UpdatePosition(this, position); } // //------------------------------------------------------------------------------------------------------------------------ void CAIActor::Reset(EObjectResetType type) { CAIObject::Reset(type); if (type == AIOBJRESET_INIT) { m_initialPosition.pos = GetEntity()->GetPos(); m_initialPosition.isValid = true; } m_bCheckedBody = true; CAISystem* pAISystem = GetAISystem(); m_State.FullReset(); // TODO : (MATT) Hack - move into the method {2007/10/30:21:01:16} m_State.eLookStyle = LOOKSTYLE_DEFAULT; m_State.bAllowLowerBodyToTurn = true; ReleaseFormation(); if (!m_proxy) { AILogComment("CAIActor(%p) Creating AIActorProxy", this); m_proxy.reset(pAISystem->GetActorProxyFactory()->CreateActorProxy(GetEntityID())); gAIEnv.pActorLookUp->UpdateProxy(this); } m_proxy->Reset(type); m_bEnabled = true; m_bUpdatedOnce = false; #ifdef AI_COMPILE_WITH_PERSONAL_LOG m_personalLog.Clear(); #endif #ifdef CRYAISYSTEM_DEBUG if (m_healthHistory) { m_healthHistory->Reset(); } #endif // synch self with owner entity if there is one IEntity* pEntity(GetEntity()); if (pEntity) { m_bEnabled = pEntity->IsActive(); SetPos(pEntity->GetPos()); } m_lightLevel = AILL_LIGHT; m_usingCombatLight = false; assert(m_perceptionDisabled == 0); m_perceptionDisabled = 0; m_cachedWaterOcclusionValue = 0.0f; m_vLastFullUpdatePos.zero(); m_lastFullUpdateStance = STANCE_NULL; m_probableTargets.clear(); m_blackBoard.Clear(); m_blackBoard.GetForScript()->SetValue("Owner", this->GetName()); m_perceptionHandlerModifiers.clear(); ResetPersonallyHostiles(); ResetBehaviorSelectionTree(type); ResetModularBehaviorTree(type); const char* navigationTypeName = m_proxy->GetNavigationTypeName(); if (navigationTypeName && *navigationTypeName) { if (NavigationAgentTypeID id = gAIEnv.pNavigationSystem->GetAgentTypeID(navigationTypeName)) { m_navigationTypeID = id; } } pAISystem->NotifyEnableState(this, m_bEnabled && (type == AIOBJRESET_INIT)); // Clear the W/T names we use for lookup m_territoryShape = 0; m_territoryShapeName.clear(); m_lastBodyDir.zero(); m_bodyTurningSpeed = 0; if (GetType() != AIOBJECT_PLAYER) { SetObserver(type == AIOBJRESET_INIT); SetObservable(type == AIOBJRESET_INIT); } m_bCloseContact = false; m_stimulusStartTime = -100.f; m_bodyInfo = SAIBodyInfo(); m_activeCoordinationCount = 0; m_currentCollisionAvoidanceRadiusIncrement = 0.0f; } void CAIActor::EnablePerception(bool enable) { if (enable) { --m_perceptionDisabled; } else { ++m_perceptionDisabled; } assert(m_perceptionDisabled >= 0); // Below zero? More disables then enables! assert(m_perceptionDisabled < 16); // Just a little sanity check } bool CAIActor::IsPerceptionEnabled() const { return m_perceptionDisabled <= 0; } void CAIActor::ResetPerception() { m_probableTargets.clear(); } // //------------------------------------------------------------------------------------------------------------------------ void CAIActor::ParseParameters(const AIObjectParams& params, bool bParseMovementParams) { SetParameters(params.m_sParamStruct); if (bParseMovementParams) { m_movementAbility = params.m_moveAbility; } GetAISystem()->NotifyEnableState(this, m_bEnabled); } //==================================================================== // CAIObject::OnObjectRemoved //==================================================================== void CAIActor::OnObjectRemoved(CAIObject* pObject) { CAIObject::OnObjectRemoved(pObject); // make sure no pending signal left from removed AIObjects if (!m_State.vSignals.empty()) { if (EntityId removedEntityID = pObject->GetEntityID()) { DynArray::iterator it = m_State.vSignals.begin(); DynArray::iterator end = m_State.vSignals.end(); for (; it != end; ) { AISIGNAL& curSignal = *it; if (curSignal.senderID == removedEntityID) { delete static_cast(curSignal.pEData); it = m_State.vSignals.erase(it); end = m_State.vSignals.end(); } else { ++it; } } } } for (unsigned i = 0; i < m_probableTargets.size(); ) { if (m_probableTargets[i] == pObject) { m_probableTargets[i] = m_probableTargets.back(); m_probableTargets.pop_back(); } else { ++i; } } RemovePersonallyHostile(pObject->GetAIObjectID()); } // //------------------------------------------------------------------------------------------------------------------------ void CAIActor::Update(EObjectUpdate type) { FUNCTION_PROFILER(gEnv->pSystem, PROFILE_AI); IAIActorProxy* pAIActorProxy = GetProxy(); if (!CastToCPipeUser()) { if (!IsEnabled()) { AIWarning("CAIActor::Update: Trying to update disabled AI Actor: %s", GetName()); AIAssert(0); return; } // There should never be AI Actors without proxies. if (!pAIActorProxy) { AIWarning("CAIActor::Update: AI Actor does not have proxy: %s", GetName()); AIAssert(0); return; } #if ENABLE_CRY_PHYSICS // There should never be AI Actors without physics. if (!GetPhysics()) { AIWarning("CAIActor::Update: AI Actor does not have physics: %s", GetName()); AIAssert(0); return; } #endif // dead AI Actors should never be updated if (pAIActorProxy->IsDead()) { AIWarning("CAIActor::Update: Trying to update dead AI Actor: %s ", GetName()); AIAssert(0); return; } } QueryBodyInfo(); UpdateBehaviorSelectionTree(); UpdateCloakScale(); CAISystem* pAISystem = GetAISystem(); // Determine if position has changed const Vec3& vPos = GetPos(); if (type == AIUPDATE_FULL) { if (!IsEquivalent(m_vLastFullUpdatePos, vPos, 1.f)) { // Recalculate the water occlusion at the new point m_cachedWaterOcclusionValue = pAISystem->GetWaterOcclusionValue(vPos); m_vLastFullUpdatePos = vPos; m_lastFullUpdateStance = m_bodyInfo.stance; } // update close contact info if (m_bCloseContact && ((pAISystem->GetFrameStartTime() - m_CloseContactTime).GetMilliSecondsAsInt64() > 1500)) { m_bCloseContact = false; } } const float dt = pAISystem->GetFrameDeltaTime(); if (dt > 0.f) { // Update body angle and body turn speed float turnAngle = Ang3::CreateRadZ(m_lastBodyDir, GetEntityDir()); m_bodyTurningSpeed = turnAngle / dt; } else { m_bodyTurningSpeed = 0; } m_lastBodyDir = GetEntityDir(); if (!CastToCPipeUser()) { if (type == AIUPDATE_FULL) { m_lightLevel = pAISystem->GetLightManager()->GetLightLevelAt(GetPos(), this, &m_usingCombatLight); } // make sure to update direction when entity is not moved const SAIBodyInfo& bodyInfo = GetBodyInfo(); SetPos(bodyInfo.vEyePos); SetEntityDir(bodyInfo.vEntityDir); SetBodyDir(bodyInfo.GetBodyDir()); // AI Actor goto stuff if (!m_State.vMoveTarget.IsZero()) { Vec3 vToMoveTarget = m_State.vMoveTarget - GetPos(); if (!m_movementAbility.b3DMove) { vToMoveTarget.z = 0.f; } if (vToMoveTarget.GetLengthSquared() < sqr(m_movementAbility.pathRadius)) { ResetLookAt(); SetBodyTargetDir(bodyInfo.vEntityDir); } else { SetBodyTargetDir(vToMoveTarget.GetNormalized()); } } // End of AI Actor goto stuff SetMoveDir(bodyInfo.vMoveDir); m_State.vMoveDir = bodyInfo.vMoveDir; SetViewDir(bodyInfo.GetEyeDir()); CAIObject* pAttTarget = m_refAttentionTarget.GetAIObject(); if (pAttTarget && pAttTarget->IsEnabled()) { if (CanSee(pAttTarget->GetVisionID())) { m_State.eTargetType = AITARGET_VISUAL; m_State.nTargetType = pAttTarget->GetType(); m_State.bTargetEnabled = true; } else { switch (m_State.eTargetType) { case AITARGET_VISUAL: m_State.eTargetThreat = AITHREAT_AGGRESSIVE; m_State.eTargetType = AITARGET_MEMORY; m_State.nTargetType = pAttTarget->GetType(); m_State.bTargetEnabled = true; m_stimulusStartTime = GetAISystem()->GetFrameStartTimeSeconds(); break; case AITARGET_MEMORY: case AITARGET_SOUND: if (GetAISystem()->GetFrameStartTimeSeconds() - m_stimulusStartTime >= 5.f) { m_State.nTargetType = -1; m_State.bTargetEnabled = false; m_State.eTargetThreat = AITHREAT_NONE; m_State.eTargetType = AITARGET_NONE; SetAttentionTarget(NILREF); } break; } } } else { m_State.nTargetType = -1; m_State.bTargetEnabled = false; m_State.eTargetThreat = AITHREAT_NONE; m_State.eTargetType = AITARGET_NONE; SetAttentionTarget(NILREF); } } m_bUpdatedOnce = true; } void CAIActor::UpdateProxy(EObjectUpdate type) { IAIActorProxy* pAIActorProxy = GetProxy(); SetMoveDir(m_State.vMoveDir); // Always update the AI proxy, also during dry updates. The Animation system // needs correct and constantly updated predictions to correctly set animation // parameters. // (MATT) Try avoiding UpdateMind, which triggers script, signal and behaviour code, if only a dry update {2009/12/06} assert(pAIActorProxy); if (pAIActorProxy) { pAIActorProxy->Update(m_State, (type == AIUPDATE_FULL)); } } // //------------------------------------------------------------------------------------------------------------------------ void CAIActor::UpdateCloakScale() { m_Parameters.m_fCloakScale = m_Parameters.m_fCloakScaleTarget; } // //------------------------------------------------------------------------------------------------------------------------ void CAIActor::UpdateDisabled(EObjectUpdate type) { FUNCTION_PROFILER(GetISystem(), PROFILE_AI); // (MATT) I'm assuming that AIActor should always have a proxy, or this could be bad for performance {2009/04/03} IAIActorProxy* pProxy = GetProxy(); if (pProxy) { pProxy->CheckUpdateStatus(); } } //=================================================================== // SetNavNodes //=================================================================== void CAIActor::UpdateDamageParts(DamagePartVector& parts) { #if ENABLE_CRY_PHYSICS IAIActorProxy* pProxy = GetProxy(); if (!pProxy) { return; } IPhysicalEntity* phys = pProxy->GetPhysics(true); if (!phys) { return; } bool queryDamageValues = true; static ISurfaceTypeManager* pSurfaceMan = gEnv->p3DEngine->GetMaterialManager()->GetSurfaceTypeManager(); pe_status_nparts statusNParts; int nParts = phys->GetStatus(&statusNParts); if ((int)parts.size() != nParts) { parts.resize(nParts); queryDamageValues = true; } // The global damage table SmartScriptTable pSinglePlayerTable; SmartScriptTable pDamageTable; if (queryDamageValues) { gEnv->pScriptSystem->GetGlobalValue("SinglePlayer", pSinglePlayerTable); if (pSinglePlayerTable.GetPtr()) { if (GetType() == AIOBJECT_PLAYER) { pSinglePlayerTable->GetValue("DamageAIToPlayer", pDamageTable); } else { pSinglePlayerTable->GetValue("DamageAIToAI", pDamageTable); } } if (!pDamageTable.GetPtr()) { queryDamageValues = false; } } pe_status_pos statusPos; pe_params_part paramsPart; for (statusPos.ipart = 0, paramsPart.ipart = 0; statusPos.ipart < nParts; ++statusPos.ipart, ++paramsPart.ipart) { if (!phys->GetParams(¶msPart) || !phys->GetStatus(&statusPos)) { continue; } primitives::box box; statusPos.pGeomProxy->GetBBox(&box); box.center *= statusPos.scale; box.size *= statusPos.scale; parts[statusPos.ipart].pos = statusPos.pos + statusPos.q * box.center; parts[statusPos.ipart].volume = (box.size.x * 2) * (box.size.y * 2) * (box.size.z * 2); if (queryDamageValues) { const char* matName = 0; phys_geometry* pGeom = paramsPart.pPhysGeomProxy ? paramsPart.pPhysGeomProxy : paramsPart.pPhysGeom; if (pGeom->surface_idx >= 0 && pGeom->surface_idx < paramsPart.nMats) { matName = pSurfaceMan->GetSurfaceType(pGeom->pMatMapping[pGeom->surface_idx])->GetName(); parts[statusPos.ipart].surfaceIdx = pGeom->surface_idx; } else { parts[statusPos.ipart].surfaceIdx = -1; } float damage = 0.0f; // The this is a bit dodgy, but the keys in the damage table exclude the 'mat_' part of the material name. if (pDamageTable.GetPtr() && matName && strlen(matName) > 4) { pDamageTable->GetValue(matName + 4, damage); } parts[statusPos.ipart].damageMult = damage; } } #else AZ_UNUSED(parts); #endif // ENABLE_CRY_PHYSICS } void CAIActor::OnAIHandlerSentSignal(const char* signalText, uint32 crc) { IF_UNLIKELY (crc == 0) { crc = CCrc32::Compute(signalText); } else { assert(crc == CCrc32::Compute(signalText)); } if (gAIEnv.CVars.LogSignals) { gEnv->pLog->Log("OnAIHandlerSentSignal: '%s' [%s].", signalText, GetName()); } ProcessBehaviorSelectionTreeSignal(signalText, crc); if (IsRunningBehaviorTree()) { BehaviorTree::Event event( crc #ifdef USING_BEHAVIOR_TREE_EVENT_DEBUGGING , signalText #endif ); GetAISystem()->GetIBehaviorTreeManager()->HandleEvent(AZ::EntityId(GetEntityID()), event); } } // nSignalID = 10 allow duplicating signals // nSignalID = 9 use the signal only to notify wait goal operations // //------------------------------------------------------------------------------------------------------------------------ void CAIActor::SetSignal(int nSignalID, const char* szText, IEntity* pSender, IAISignalExtraData* pData, uint32 crcCode) { CCCPOINT(SetSignal); // Ensure we delete the pData object if we early out struct DeleteBeforeReturning { IAISignalExtraData** _p; DeleteBeforeReturning(IAISignalExtraData** p) : _p(p) {} ~DeleteBeforeReturning() { if (*_p) { GetAISystem()->FreeSignalExtraData((AISignalExtraData*)*_p); } } } autoDelete(&pData); #ifdef _DEBUG if (strlen(szText) + 1 > sizeof(((AISIGNAL*)0)->strText)) { AIWarning("####>CAIObject::SetSignal SIGNAL STRING IS TOO LONG for <%s> :: %s sz-> %" PRISIZE_T, GetName(), szText, strlen(szText)); // AILogEvent("####>CAIObject::SetSignal <%s> :: %s sz-> %d",m_sName.c_str(),szText,strlen(szText)); } #endif // _DEBUG // always process signals sent only to notify wait goal operation if (!crcCode) { crcCode = CCrc32::Compute(szText); } // (MATT) This is the only place that the CRCs are used and their implementation is very clumsy {2008/08/09} if (nSignalID != AISIGNAL_NOTIFY_ONLY) { if (nSignalID != AISIGNAL_ALLOW_DUPLICATES) { DynArray::iterator ai; for (ai = m_State.vSignals.begin(); ai != m_State.vSignals.end(); ++ai) { // if ((*ai).strText == szText) //if (!azstricmp((*ai).strText,szText)) #ifdef _DEBUG if (!azstricmp((*ai).strText, szText) && !(*ai).Compare(crcCode)) { AIWarning("Hash values are different, but strings are identical! %s - %s ", (*ai).strText, szText); } if (azstricmp((*ai).strText, szText) && (*ai).Compare(crcCode)) { AIWarning("Please report to aws.amazon.com/support. Hash values are identical, but strings are different! %s - %s ", (*ai).strText, szText); } #endif // _DEBUG } } if (!m_bEnabled && nSignalID != AISIGNAL_INCLUDE_DISABLED) { // (Kevin) This seems like an odd assumption to be making. INCLUDE_DISABLED needs to be a bit or a separate passed-in value. // WarFace compatibility cannot have duplicate signals sent to disabled AI. (08/14/2009) if (gAIEnv.configuration.eCompatibilityMode == ECCM_WARFACE || nSignalID != AISIGNAL_ALLOW_DUPLICATES) { AILogComment("AIActor %p %s dropped signal \'%s\' due to being disabled", this, GetName(), szText); return; } } } AISIGNAL signal; signal.nSignal = nSignalID; cry_strcpy(signal.strText, szText); signal.m_nCrcText = crcCode; signal.senderID = pSender ? pSender->GetId() : 0; signal.pEData = pData; #ifdef CRYAISYSTEM_DEBUG IAIRecordable::RecorderEventData recorderEventData(szText); #endif if (nSignalID != AISIGNAL_RECEIVED_PREV_UPDATE)// received last update { #ifdef CRYAISYSTEM_DEBUG RecordEvent(IAIRecordable::E_SIGNALRECIEVED, &recorderEventData); GetAISystem()->Record(this, IAIRecordable::E_SIGNALRECIEVED, szText); #endif } // don't let notify signals enter the queue if (nSignalID == AISIGNAL_NOTIFY_ONLY) { OnAIHandlerSentSignal(szText, crcCode); // still a polymorphic call that ends up in the most derived class (which is intended) return; } // If all our early-outs passed and it wasn't just a "notify" then actually enter the signal into the stack! pData = NULL; // set to NULL to prevent autodeletion of pData on return // need to make sure constructor signal is always at the back - to be processed first if (!m_State.vSignals.empty()) { AISIGNAL backSignal(m_State.vSignals.back()); if (!azstricmp("Constructor", backSignal.strText)) { m_State.vSignals.pop_back(); m_State.vSignals.push_back(signal); m_State.vSignals.push_back(backSignal); } else { m_State.vSignals.push_back(signal); } } else { m_State.vSignals.push_back(signal); } } //==================================================================== // IsHostile //==================================================================== bool CAIActor::IsHostile(const IAIObject* pOtherAI, bool bUsingAIIgnorePlayer) const { FUNCTION_PROFILER(gEnv->pSystem, PROFILE_AI); bool hostile = false; if (pOtherAI) { const CAIObject* other = static_cast(pOtherAI); if (other->GetType() == AIOBJECT_ATTRIBUTE) { if (CAIObject* association = other->GetAssociation().GetAIObject()) { other = association; } } if (bUsingAIIgnorePlayer && (other->GetType() == AIOBJECT_PLAYER) && (gAIEnv.CVars.IgnorePlayer != 0)) { return false; } uint8 myFaction = GetFactionID(); uint8 otherFaction = other->GetFactionID(); if (!other->IsThreateningForHostileFactions()) { return false; } hostile = (gAIEnv.pFactionMap->GetReaction(myFaction, otherFaction) == IFactionMap::Hostile); if (!hostile && !m_forcefullyHostiles.empty()) { if (m_forcefullyHostiles.find(pOtherAI->GetAIObjectID()) != m_forcefullyHostiles.end()) { hostile = true; } } if (hostile) { if (const CAIActor* actor = other->CastToCAIActor()) { if (bUsingAIIgnorePlayer && (m_Parameters.m_bAiIgnoreFgNode || actor->GetParameters().m_bAiIgnoreFgNode)) { hostile = false; } } } } return hostile; } // //------------------------------------------------------------------------------------------------------------------------ void CAIActor::Event(unsigned short eType, SAIEVENT* pEvent) { CAISystem* pAISystem = GetAISystem(); IAIActorProxy* pAIActorProxy = GetProxy(); bool bWasEnabled = m_bEnabled; CAIObject::Event(eType, pEvent); // Inspired by CPuppet::Event's switch [4/25/2010 evgeny] switch (eType) { case AIEVENT_DISABLE: { // Reset and disable the agent's target track const tAIObjectID aiObjectId = GetAIObjectID(); gAIEnv.pTargetTrackManager->ResetAgent(aiObjectId); gAIEnv.pTargetTrackManager->SetAgentEnabled(aiObjectId, false); pAISystem->UpdateGroupStatus(GetGroupId()); pAISystem->NotifyEnableState(this, false); SetObserver(false); } break; case AIEVENT_ENABLE: if (pAIActorProxy->IsDead()) { // can happen when rendering dead bodies? AI should not be enabled // AIAssert(!"Trying to enable dead character!"); return; } m_bEnabled = true; gAIEnv.pTargetTrackManager->SetAgentEnabled(GetAIObjectID(), true); pAISystem->UpdateGroupStatus(GetGroupId()); pAISystem->NotifyEnableState(this, true); SetObserver(GetType() != AIOBJECT_PLAYER); SetObservable(true); break; case AIEVENT_SLEEP: m_bCheckedBody = false; m_bEnabled = false; pAISystem->NotifyEnableState(this, m_bEnabled); break; case AIEVENT_WAKEUP: m_bEnabled = true; pAISystem->NotifyEnableState(this, m_bEnabled); m_bCheckedBody = true; pAISystem->UpdateGroupStatus(GetGroupId()); break; case AIEVENT_ONVISUALSTIMULUS: HandleVisualStimulus(pEvent); break; case AIEVENT_ONSOUNDEVENT: HandleSoundEvent(pEvent); break; case AIEVENT_ONBULLETRAIN: HandleBulletRain(pEvent); break; case AIEVENT_AGENTDIED: { ResetModularBehaviorTree(AIOBJRESET_SHUTDOWN); pAISystem->NotifyTargetDead(this); m_bCheckedBody = false; m_bEnabled = false; pAISystem->NotifyEnableState(this, m_bEnabled); pAISystem->RemoveFromGroup(GetGroupId(), this); pAISystem->ReleaseFormationPoint(this); CancelRequestedPath(false); ReleaseFormation(); m_State.ClearSignals(); const EntityId killerID = pEvent->targetId; pAISystem->OnAgentDeath(GetEntityID(), killerID); if (pAIActorProxy) { pAIActorProxy->Reset(AIOBJRESET_SHUTDOWN); } SetObservable(false); SetObserver(false); } break; } // Update the group status if (bWasEnabled != m_bEnabled) { GetAISystem()->UpdateGroupStatus(GetGroupId()); } } void CAIActor::EntityEvent(const SEntityEvent& event) { switch (event.event) { case ENTITY_EVENT_ATTACH_THIS: case ENTITY_EVENT_DETACH_THIS: QueryBodyInfo(); break; case ENTITY_EVENT_ENABLE_PHYSICS: UpdateObserverSkipList(); break; case ENTITY_EVENT_DONE: case ENTITY_EVENT_RETURNING_TO_POOL: StopBehaviorTree(); break; default: break; } CAIObject::EntityEvent(event); } //==================================================================== // //==================================================================== bool CAIActor::CanAcquireTarget(IAIObject* pOther) const { if (!pOther || !pOther->IsEnabled() || pOther->GetEntity()->IsHidden()) { return false; } CCCPOINT(CAIActor_CanAcquireTarget); CAIObject* pOtherAI = (CAIObject*)pOther; if (pOtherAI->GetType() == AIOBJECT_ATTRIBUTE && pOtherAI->GetAssociation().IsValid()) { pOtherAI = (CAIObject*)pOtherAI->GetAssociation().GetAIObject(); } if (!pOtherAI || !pOtherAI->IsTargetable()) { return false; } CAIActor* pOtherActor = pOtherAI->CastToCAIActor(); if (!pOtherActor) { return (pOtherAI->GetType() == AIOBJECT_TARGET); } if (GetAISystem()->GetCombatClassScale(m_Parameters.m_CombatClass, pOtherActor->GetParameters().m_CombatClass) > 0) { return true; } return false; } // //------------------------------------------------------------------------------------------------------------------------ void CAIActor::SetGroupId(int id) { if (id != GetGroupId()) { gAIEnv.pGroupManager->RemoveGroupMember(GetGroupId(), GetAIObjectID()); if (id > 0) { gAIEnv.pGroupManager->AddGroupMember(id, GetAIObjectID()); } CAIObject::SetGroupId(id); GetAISystem()->AddToGroup(this); CAIObject* pBeacon = (CAIObject*)GetAISystem()->GetBeacon(id); if (pBeacon) { GetAISystem()->UpdateBeacon(id, pBeacon->GetPos(), this); } m_Parameters.m_nGroup = id; } } void CAIActor::SetFactionID(uint8 factionID) { CAIObject::SetFactionID(factionID); if (IsObserver()) { ObserverParams params; uint8 faction = GetFactionID(); params.factionsToObserveMask = GetFactionVisionMask(faction); params.faction = faction; gAIEnv.pVisionMap->ObserverChanged(GetVisionID(), params, eChangedFaction | eChangedFactionsToObserveMask); } } void CAIActor::RegisterBehaviorListener(IActorBehaviorListener* listener) { m_behaviorListeners.insert(listener); } void CAIActor::UnregisterBehaviorListener(IActorBehaviorListener* listener) { m_behaviorListeners.erase(listener); } void CAIActor::BehaviorEvent(EBehaviorEvent event) { BehaviorListeners::iterator it = m_behaviorListeners.begin(); BehaviorListeners::iterator end = m_behaviorListeners.end(); for (; it != end; ) { BehaviorListeners::iterator next = it; ++next; IActorBehaviorListener* listener = *it; listener->BehaviorEvent(this, event); it = next; } } void CAIActor::BehaviorChanged(const char* current, const char* previous) { BehaviorListeners::iterator it = m_behaviorListeners.begin(); BehaviorListeners::iterator end = m_behaviorListeners.end(); for (; it != end; ) { BehaviorListeners::iterator next = it; ++next; IActorBehaviorListener* listener = *it; listener->BehaviorChanged(this, current, previous); it = next; } } // //------------------------------------------------------------------------------------------------------------------------ void CAIActor::SetParameters(const AgentParameters& sParams) { SetGroupId(sParams.m_nGroup); SetFactionID(sParams.factionID); m_Parameters = sParams; m_Parameters.m_fAccuracy = clamp_tpl(m_Parameters.m_fAccuracy, 0.0f, 1.0f); GetAISystem()->AddToFaction(this, sParams.factionID); CacheFOVCos(sParams.m_PerceptionParams.FOVPrimary, sParams.m_PerceptionParams.FOVSecondary); float range = std::max(sParams.m_PerceptionParams.sightRange, sParams.m_PerceptionParams.sightRangeVehicle); VisionChanged(range, m_FOVPrimaryCos, m_FOVSecondaryCos); } #ifdef CRYAISYSTEM_DEBUG void CAIActor::UpdateHealthHistory() { if (!GetProxy()) { return; } if (!m_healthHistory) { m_healthHistory = new CValueHistory(100, 0.1f); } //better add float functions here float health = (GetProxy()->GetActorHealth() + GetProxy()->GetActorArmor()); float maxHealth = (float)GetProxy()->GetActorMaxHealth(); m_healthHistory->Sample(health / maxHealth, GetAISystem()->GetFrameDeltaTime()); } #endif // //------------------------------------------------------------------------------------------------------------------------ void CAIActor::Serialize(TSerialize ser) { ser.Value("m_bCheckedBody", m_bCheckedBody); m_State.Serialize(ser); m_Parameters.Serialize(ser); SerializeMovementAbility(ser); CAIObject::Serialize(ser); if (ser.IsReading()) { SetParameters(m_Parameters); } if (ser.IsReading()) { const EntityId entityId = GetEntityID(); assert(entityId > 0); // The proxy may already exist, if this actor is being created due to an entity // being prepared from the pool. if (!m_proxy.get()) { AILogComment("CAIActor(%p) Creating AIActorProxy for serialization.", this); m_proxy.reset(GetAISystem()->GetActorProxyFactory()->CreateActorProxy(entityId)); } gAIEnv.pActorLookUp->UpdateProxy(this); } assert(m_proxy != 0); if (m_proxy) { m_proxy->Serialize(ser); } else { AIWarning("CAIActor::Serialize Missing proxy for \'%s\' after loading", GetName()); } assert((m_behaviorSelectionTree.get() != NULL) == (m_behaviorSelectionVariables.get() != NULL)); ser.EnumValue("m_behaviorTreeEvaluationMode", m_behaviorTreeEvaluationMode, FirstBehaviorTreeEvaluationMode, BehaviorTreeEvaluationModeCount); if (ser.BeginOptionalGroup("BehaviorSelectionTree", m_behaviorSelectionTree.get() != NULL)) { if (ser.IsReading()) { ResetBehaviorSelectionTree(AIOBJRESET_INIT); } assert(m_behaviorSelectionTree.get() != NULL); assert(m_behaviorSelectionVariables.get() != NULL); if (m_behaviorSelectionTree.get() != NULL) { m_behaviorSelectionTree->Serialize(ser); } else { AIWarning("CAIActor::Serialize Missing Behavior Selection Tree for \'%s\' after loading", GetName()); } if (m_behaviorSelectionVariables.get() != NULL) { m_behaviorSelectionVariables->Serialize(ser); } else { AIWarning("CAIActor::Serialize Missing Behavior Selection Variables for \'%s\' after loading", GetName()); } if (ser.IsReading()) { UpdateBehaviorSelectionTree(); } ser.EndGroup(); } else if (ser.IsReading()) { m_behaviorSelectionTree.reset(); m_behaviorSelectionVariables.reset(); } if (ser.IsReading()) { ResetBehaviorSelectionTree(AIOBJRESET_INIT); } if (ser.IsReading()) { ResetModularBehaviorTree(AIOBJRESET_INIT); } bool observer = m_observer; ser.Value("m_observer", observer); if (ser.IsReading()) { SetObserver(observer); // only alive puppets or leaders should be added to groups bool addToGroup = GetProxy() != NULL ? !GetProxy()->IsDead() : CastToCLeader() != NULL; if (addToGroup) { GetAISystem()->AddToGroup(this); } ReactionChanged(0, IFactionMap::Hostile); m_probableTargets.clear(); m_usingCombatLight = false; m_lightLevel = AILL_LIGHT; } ser.Value("m_cachedWaterOcclusionValue", m_cachedWaterOcclusionValue); ser.Value("m_vLastFullUpdatePos", m_vLastFullUpdatePos); uint32 lastFullUpdateStance = m_lastFullUpdateStance; ser.Value("m_lastFullUpdateStance", lastFullUpdateStance); if (ser.IsReading()) { m_lastFullUpdateStance = static_cast(lastFullUpdateStance); } if (ser.IsReading()) { SetAttentionTarget(NILREF); } ser.Value("m_bCloseContact", m_bCloseContact); // Territory ser.Value("m_territoryShapeName", m_territoryShapeName); if (ser.IsReading()) { m_territoryShape = GetAISystem()->GetGenericShapeOfName(m_territoryShapeName.c_str()); } ser.Value("m_forcefullyHostiles", m_forcefullyHostiles); ser.Value("m_activeCoordinationCount", m_activeCoordinationCount); uint32 navigationTypeId = m_navigationTypeID; ser.Value("m_navigationTypeID", navigationTypeId); if (ser.IsReading()) { m_navigationTypeID = NavigationAgentTypeID(navigationTypeId); } ser.Value("m_currentCollisionAvoidanceRadiusIncrement", m_currentCollisionAvoidanceRadiusIncrement); ser.Value("m_initialPosition.isValid", m_initialPosition.isValid); ser.Value("m_initialPosition.pos", m_initialPosition.pos); } void CAIActor::SetAttentionTarget(CWeakRef refTarget) { CCCPOINT(CAIActor_SetAttentionTarget); CAIObject* pAttTarget = refTarget.GetAIObject(); m_refAttentionTarget = refTarget; #ifdef CRYAISYSTEM_DEBUG RecorderEventData recorderEventData(pAttTarget ? pAttTarget->GetName() : ""); RecordEvent(IAIRecordable::E_ATTENTIONTARGET, &recorderEventData); #endif } Vec3 CAIActor::GetFloorPosition(const Vec3& pos) { #if ENABLE_CRY_PHYSICS Vec3 floorPos = pos; return (GetFloorPos(floorPos, pos, WalkabilityFloorUpDist, WalkabilityFloorDownDist, WalkabilityDownRadius, AICE_STATIC)) ? floorPos : pos; #else return pos; #endif // ENABLE_CRY_PHYSICS } void CAIActor::CheckCloseContact(IAIObject* pTarget, float distSq) { FUNCTION_PROFILER(gEnv->pSystem, PROFILE_AI); if (!m_bCloseContact && distSq < sqr(GetParameters().m_fMeleeRange)) { SetSignal(1, "OnCloseContact", pTarget->GetEntity(), 0, gAIEnv.SignalCRCs.m_nOnCloseContact); SetCloseContact(true); } } void CAIActor::SetCloseContact(bool bCloseContact) { if (bCloseContact && !m_bCloseContact) { m_CloseContactTime = GetAISystem()->GetFrameStartTime(); } m_bCloseContact = bCloseContact; } IAIObject::EFieldOfViewResult CAIActor::IsObjectInFOV(CAIObject* pTarget, float fDistanceScale) const { CCCPOINT(CAIActor_IsObjectInFOVCone); FUNCTION_PROFILER(gEnv->pSystem, PROFILE_AI); const Vec3& vTargetPos = pTarget->GetPos(); const float fSightRange = GetMaxTargetVisibleRange(pTarget) * fDistanceScale; return (fSightRange > 0.0f ? CheckPointInFOV(vTargetPos, fSightRange) : eFOV_Outside); } CWeakRef CAIActor::GetLiveTarget(const CWeakRef& refTarget) { CCCPOINT(CPuppet_GetLiveTarget); CWeakRef refResult; CAIObject* pTarget = refTarget.GetAIObject(); if (pTarget) { CAIActor* pAIActor = pTarget->CastToCAIActor(); if (pAIActor && pAIActor->IsActive() && pAIActor->IsAgent()) { refResult = StaticCast(refTarget); } else { CAIActor* pAssociatedAIActor = CastToCAIActorSafe(pTarget->GetAssociation().GetAIObject()); if (pAssociatedAIActor && pAssociatedAIActor->IsEnabled() && pAssociatedAIActor->IsAgent()) { refResult = GetWeakRef(pAssociatedAIActor); } } } return refResult; } const CAIObject* CAIActor::GetLiveTarget(const CAIObject* pTarget) { if (!pTarget) { return 0; } if (const CAIActor* pTargetAIActor = pTarget->CastToCAIActor()) { if (!pTargetAIActor->IsActive()) { return 0; } } if (pTarget->IsAgent()) { return pTarget; } const CAIObject* pAssociation = pTarget->GetAssociation().GetAIObject(); return (pAssociation && pAssociation->IsEnabled() && pAssociation->IsAgent()) ? pAssociation : 0; } uint32 CAIActor::GetFactionVisionMask(uint8 factionID) const { uint32 mask = 0; uint32 factionCount = gAIEnv.pFactionMap->GetFactionCount(); for (uint32 i = 0; i < factionCount; ++i) { if (i != factionID) { if (gAIEnv.pFactionMap->GetReaction(factionID, i) < IFactionMap::Neutral) { mask |= 1 << i; } } } PersonallyHostiles::const_iterator it = m_forcefullyHostiles.begin(); PersonallyHostiles::const_iterator end = m_forcefullyHostiles.end(); for (; it != end; ++it) { if (const CAIObject* aiObject = static_cast(GetAISystem()->GetAIObjectManager()->GetAIObject(*it))) { if (aiObject->GetFactionID() != IFactionMap::InvalidFactionID) { mask |= 1 << aiObject->GetFactionID(); } } } return mask; } void CAIActor::SerializeMovementAbility(TSerialize ser) { ser.BeginGroup("AgentMovementAbility"); AgentMovementAbility& moveAbil = m_movementAbility; ser.Value("b3DMove", moveAbil.b3DMove); ser.Value("bUsePathfinder", moveAbil.bUsePathfinder); ser.Value("usePredictiveFollowing", moveAbil.usePredictiveFollowing); ser.Value("allowEntityClampingByAnimation", moveAbil.allowEntityClampingByAnimation); ser.Value("maxAccel", moveAbil.maxAccel); ser.Value("maxDecel", moveAbil.maxDecel); ser.Value("minTurnRadius", moveAbil.minTurnRadius); ser.Value("maxTurnRadius", moveAbil.maxTurnRadius); ser.Value("avoidanceRadius", moveAbil.avoidanceRadius); ser.Value("pathLookAhead", moveAbil.pathLookAhead); ser.Value("pathRadius", moveAbil.pathRadius); ser.Value("pathSpeedLookAheadPerSpeed", moveAbil.pathSpeedLookAheadPerSpeed); ser.Value("cornerSlowDown", moveAbil.cornerSlowDown); ser.Value("slopeSlowDown", moveAbil.slopeSlowDown); ser.Value("optimalFlightHeight", moveAbil.optimalFlightHeight); ser.Value("minFlightHeight", moveAbil.minFlightHeight); ser.Value("maxFlightHeight", moveAbil.maxFlightHeight); ser.Value("maneuverTrh", moveAbil.maneuverTrh); ser.Value("velDecay", moveAbil.velDecay); ser.Value("pathFindPrediction", moveAbil.pathFindPrediction); ser.Value("pathRegenIntervalDuringTrace", moveAbil.pathRegenIntervalDuringTrace); ser.Value("teleportEnabled", moveAbil.teleportEnabled); ser.Value("lightAffectsSpeed", moveAbil.lightAffectsSpeed); ser.Value("resolveStickingInTrace", moveAbil.resolveStickingInTrace); ser.Value("directionalScaleRefSpeedMin", moveAbil.directionalScaleRefSpeedMin); ser.Value("directionalScaleRefSpeedMax", moveAbil.directionalScaleRefSpeedMax); ser.Value("avoidanceAbilities", moveAbil.avoidanceAbilities); ser.Value("pushableObstacleWeakAvoidance", moveAbil.pushableObstacleWeakAvoidance); ser.Value("pushableObstacleAvoidanceRadius", moveAbil.pushableObstacleAvoidanceRadius); ser.Value("pushableObstacleMassMin", moveAbil.pushableObstacleMassMin); ser.Value("pushableObstacleMassMax", moveAbil.pushableObstacleMassMax); ser.BeginGroup("AgentMovementSpeeds"); AgentMovementSpeeds& moveSpeeds = m_movementAbility.movementSpeeds; for (int i = 0; i < AgentMovementSpeeds::AMU_NUM_VALUES; i++) { for (int j = 0; j < AgentMovementSpeeds::AMS_NUM_VALUES; j++) { ser.BeginGroup("range"); AgentMovementSpeeds::SSpeedRange& range = moveSpeeds.GetRange(j, i); ser.Value("def", range.def); ser.Value("min", range.min); ser.Value("max", range.max); ser.EndGroup(); } } ser.EndGroup(); ser.BeginGroup("AgentPathfindingProperties"); AgentPathfindingProperties& pfProp = m_movementAbility.pathfindingProperties; pfProp.navCapMask.Serialize (ser); ser.Value("triangularResistanceFactor", pfProp.triangularResistanceFactor); ser.Value("waypointResistanceFactor", pfProp.waypointResistanceFactor); ser.Value("flightResistanceFactor", pfProp.flightResistanceFactor); ser.Value("volumeResistanceFactor", pfProp.volumeResistanceFactor); ser.Value("roadResistanceFactor", pfProp.roadResistanceFactor); ser.Value("waterResistanceFactor", pfProp.waterResistanceFactor); ser.Value("maxWaterDepth", pfProp.maxWaterDepth); ser.Value("minWaterDepth", pfProp.minWaterDepth); ser.Value("exposureFactor", pfProp.exposureFactor); ser.Value("dangerCost", pfProp.dangerCost); ser.Value("zScale", pfProp.zScale); ser.EndGroup(); ser.EndGroup(); } // //------------------------------------------------------------------------------------------------------------------------ float CAIActor::AdjustTargetVisibleRange(const CAIActor& observer, float fVisibleRange) const { return fVisibleRange; } // //------------------------------------------------------------------------------------------------------------------------ float CAIActor::GetMaxTargetVisibleRange(const IAIObject* pTarget, bool bCheckCloak) const { float fRange = 0.0f; const AgentParameters& parameters = GetParameters(); const AgentPerceptionParameters& perception = parameters.m_PerceptionParams; IAIActor::CloakObservability cloakObservability; cloakObservability.cloakMaxDistCrouchedAndMoving = perception.cloakMaxDistCrouchedAndMoving; cloakObservability.cloakMaxDistCrouchedAndStill = perception.cloakMaxDistCrouchedAndStill; cloakObservability.cloakMaxDistMoving = perception.cloakMaxDistMoving; cloakObservability.cloakMaxDistStill = perception.cloakMaxDistStill; // Check if I'm invisible from the observer's position const CAIActor* pTargetActor = CastToCAIActorSafe(pTarget); if (!pTargetActor || !pTargetActor->IsInvisibleFrom(GetPos(), bCheckCloak, true, cloakObservability)) { fRange = perception.sightRange; if (pTarget) { // Use correct range for vehicles if (pTarget->GetAIType() == AIOBJECT_VEHICLE && parameters.m_PerceptionParams.sightRangeVehicle > FLT_EPSILON) { fRange = parameters.m_PerceptionParams.sightRangeVehicle; } // Allow target to adjust the visible range as needed. This allows effects like // underwater occlusion or light levels to take effect. if (pTargetActor && fRange > FLT_EPSILON) { fRange = pTargetActor->AdjustTargetVisibleRange(*this, fRange); } } } return fRange; } // //------------------------------------------------------------------------------------------------------------------------ bool CAIActor::IsCloakEffective(const Vec3& pos) const { return (m_Parameters.m_fCloakScaleTarget > 0.f && !IsUsingCombatLight() && (!GetGrabbedEntity() || !IsGrabbedEntityInView(pos))); } // //------------------------------------------------------------------------------------------------------------------------ bool CAIActor::IsInvisibleFrom(const Vec3& pos, bool bCheckCloak, bool bCheckCloakDistance, const IAIActor::CloakObservability& cloakObservability) const { bool bInvisible = (m_Parameters.m_bInvisible); if (!bInvisible && bCheckCloak) { bInvisible = (m_Parameters.m_bCloaked && IsCloakEffective(pos)); // Cloaked targets can still be seen if they are close enough to the point if (bInvisible && bCheckCloakDistance) { float cloakMaxDist = 0.0f; if (m_bodyInfo.stance == STANCE_CROUCH) { cloakMaxDist = m_bodyInfo.isMoving ? cloakObservability.cloakMaxDistCrouchedAndMoving : cloakObservability.cloakMaxDistCrouchedAndStill; } else { cloakMaxDist = m_bodyInfo.isMoving ? cloakObservability.cloakMaxDistMoving : cloakObservability.cloakMaxDistStill; } bInvisible = Distance::Point_PointSq(GetPos(), pos) > sqr(cloakMaxDist); } } return bInvisible; } // //------------------------------------------------------------------------------------------------------------------------ void CAIActor::NotifyDeath() { } // //------------------------------------------------------------------------------------------------------------------------ static void CheckAndAddPhysEntity(PhysSkipList& skipList, IPhysicalEntity* physics) { if (physics) { pe_status_pos stat; if ((physics->GetStatus(&stat) != 0) && (((1 << stat.iSimClass) & COVER_OBJECT_TYPES) != 0)) { stl::push_back_unique(skipList, physics); } } } // //------------------------------------------------------------------------------------------------------------------------ void CAIActor::GetPhysicalSkipEntities(PhysSkipList& skipList) const { CAIObject::GetPhysicalSkipEntities(skipList); #if ENABLE_CRY_PHYSICS const SAIBodyInfo& bi = GetBodyInfo(); if (IEntity* pLinkedVehicleEntity = bi.GetLinkedVehicleEntity()) { CheckAndAddPhysEntity(skipList, pLinkedVehicleEntity->GetPhysics()); } // if holding something in hands - skip it for the vis check IEntity* pGrabbedEntity(GetGrabbedEntity()); if (pGrabbedEntity) { CheckAndAddPhysEntity(skipList, pGrabbedEntity->GetPhysics()); } #endif // (Kevin) Adding children in most cases causes us to add too many entities to the skip list (i.e., vehicles) // If this is needed, investigate how we can keep this under 5 skippable entities. /*if (IEntity* entity = GetEntity()) { for (int i = 0, ni = entity->GetChildCount(); i < ni; ++i) { if (IEntity* child = entity->GetChild(i)) CheckAndAddPhysEntity(skipList, child->GetPhysics()); } }*/ CRY_ASSERT_MESSAGE(skipList.size() <= 5, "Too many physical skipped entities determined. See SRwiRequest definition."); } void CAIActor::UpdateObserverSkipList() { if (m_observer) { PhysSkipList skipList; GetPhysicalSkipEntities(skipList); assert(skipList.size() <= ObserverParams::MaxSkipListSize); ObserverParams observerParams; observerParams.skipListSize = std::min(skipList.size(), ObserverParams::MaxSkipListSize); for (size_t i = 0; i < static_cast(observerParams.skipListSize); ++i) { observerParams.skipList[i] = skipList[i]; } gAIEnv.pVisionMap->ObserverChanged(GetVisionID(), observerParams, eChangedSkipList); } } void CAIActor::GetLocalBounds(AABB& bbox) const { bbox.min.zero(); bbox.max.zero(); IEntity* pEntity = GetEntity(); #if ENABLE_CRY_PHYSICS IPhysicalEntity* pPhysicalEntity = pEntity->GetPhysics(); if (pPhysicalEntity) { pe_status_pos pstate; if (pPhysicalEntity && pPhysicalEntity->GetStatus(&pstate)) { bbox.min = pstate.BBox[0] / pstate.scale; bbox.max = pstate.BBox[1] / pstate.scale; } } else #endif { return pEntity->GetLocalBounds(bbox); } } IEntity* CAIActor::GetPathAgentEntity() const { return GetEntity(); } const char* CAIActor::GetPathAgentName() const { return GetName(); } unsigned short CAIActor::GetPathAgentType() const { return GetType(); } float CAIActor::GetPathAgentPassRadius() const { return GetParameters().m_fPassRadius; } Vec3 CAIActor::GetPathAgentPos() const { return GetPhysicsPos(); } Vec3 CAIActor::GetPathAgentVelocity() const { return GetVelocity(); } void CAIActor::GetPathAgentNavigationBlockers(NavigationBlockers& navigationBlockers, const struct PathfindRequest* pRequest) { } size_t CAIActor::GetNavNodeIndex() const { if (m_lastNavNodeIndex) { return (m_lastNavNodeIndex < ~0ul) ? m_lastNavNodeIndex : 0; } m_lastNavNodeIndex = ~0ul; return 0; } const AgentMovementAbility& CAIActor::GetPathAgentMovementAbility() const { return m_movementAbility; } unsigned int CAIActor::GetPathAgentLastNavNode() const { return GetNavNodeIndex(); } void CAIActor::SetPathAgentLastNavNode(unsigned int lastNavNode) { m_lastNavNodeIndex = lastNavNode; } void CAIActor::SetPathToFollow(const char* pathName) { } void CAIActor::SetPathAttributeToFollow(bool bSpline) { } void CAIActor::SetPFBlockerRadius(int blockerType, float radius) { } ETriState CAIActor::CanTargetPointBeReached(CTargetPointRequest& request) { request.SetResult(eTS_false); return eTS_false; } bool CAIActor::UseTargetPointRequest(const CTargetPointRequest& request) { return false; } IPathFollower* CAIActor::GetPathFollower() const { return NULL; } bool CAIActor::GetValidPositionNearby(const Vec3& proposedPosition, Vec3& adjustedPosition) const { return false; } bool CAIActor::GetTeleportPosition(Vec3& teleportPos) const { return false; } //=================================================================== // GetSightFOVCos //=================================================================== void CAIActor::GetSightFOVCos(float& fPrimaryFOVCos, float& fSecondaryFOVCos) const { fPrimaryFOVCos = m_FOVPrimaryCos; fSecondaryFOVCos = m_FOVSecondaryCos; } //=================================================================== // TransformFOV //=================================================================== void CAIActor::CacheFOVCos(float FOVPrimary, float FOVSecondary) { if (FOVPrimary < 0.0f || FOVPrimary > 360.0f) // see all around { m_FOVPrimaryCos = -1.0f; m_FOVSecondaryCos = -1.0f; } else { if (FOVSecondary >= 0.0f && FOVPrimary > FOVSecondary) { FOVSecondary = FOVPrimary; } m_FOVPrimaryCos = cosf(DEG2RAD(FOVPrimary * 0.5f)); if (FOVSecondary < 0.0f || FOVSecondary > 360.0f) // see all around { m_FOVSecondaryCos = -1.0f; } else { m_FOVSecondaryCos = cosf(DEG2RAD(FOVSecondary * 0.5f)); } } } void CAIActor::ReactionChanged(uint8 factionID, IFactionMap::ReactionType reaction) { if (m_observer) { ObserverParams params; uint8 faction = GetFactionID(); params.factionsToObserveMask = GetFactionVisionMask(faction); params.faction = faction; gAIEnv.pVisionMap->ObserverChanged(GetVisionID(), params, eChangedFaction | eChangedFactionsToObserveMask); } } void CAIActor::VisionChanged(float sightRange, float primaryFOVCos, float secondaryFOVCos) { if (m_observer) { ObserverParams observerParams; observerParams.sightRange = sightRange; observerParams.fovCos = primaryFOVCos; gAIEnv.pVisionMap->ObserverChanged(GetVisionID(), observerParams, eChangedSightRange | eChangedFOV); } } void CAIActor::SetObserver(bool observer) { if (m_observer != observer) { if (observer) { uint8 faction = GetFactionID(); ObserverParams observerParams; observerParams.entityId = GetEntityID(); observerParams.factionsToObserveMask = GetFactionVisionMask(faction); observerParams.faction = faction; observerParams.typesToObserveMask = GetObserverTypeMask(); observerParams.typeMask = GetObservableTypeMask(); observerParams.eyePosition = GetPos(); observerParams.eyeDirection = GetViewDir(); observerParams.fovCos = m_FOVPrimaryCos; observerParams.sightRange = m_Parameters.m_PerceptionParams.sightRange; PhysSkipList skipList; GetPhysicalSkipEntities(skipList); observerParams.skipListSize = std::min(skipList.size(), ObserverParams::MaxSkipListSize); for (size_t i = 0; i < static_cast(observerParams.skipListSize); ++i) { observerParams.skipList[i] = skipList[i]; } VisionID visionID = GetVisionID(); if (!visionID) { visionID = gAIEnv.pVisionMap->CreateVisionID(GetName()); SetVisionID(visionID); } gAIEnv.pVisionMap->RegisterObserver(visionID, observerParams); } else { if (VisionID visionID = GetVisionID()) { gAIEnv.pVisionMap->UnregisterObserver(visionID); } } m_observer = observer; } } uint32 CAIActor::GetObserverTypeMask() const { return General | AliveAgent | DeadAgent | Player; } uint32 CAIActor::GetObservableTypeMask() const { return General | AliveAgent; } bool CAIActor::IsObserver() const { return m_observer; } bool CAIActor::CanSee(const VisionID& otherID) const { return gEnv->pAISystem->GetVisionMap()->IsVisible(GetVisionID(), otherID); } void CAIActor::AddPersonallyHostile(tAIObjectID hostileID) { m_forcefullyHostiles.insert(hostileID); ReactionChanged(0, IFactionMap::Hostile); } void CAIActor::RemovePersonallyHostile(tAIObjectID hostileID) { m_forcefullyHostiles.erase(hostileID); ReactionChanged(0, IFactionMap::Hostile); } void CAIActor::ResetPersonallyHostiles() { m_forcefullyHostiles.clear(); ReactionChanged(0, IFactionMap::Hostile); } bool CAIActor::IsPersonallyHostile(tAIObjectID hostileID) const { return m_forcefullyHostiles.find(hostileID) != m_forcefullyHostiles.end(); } void CAIActor::SetProxy(IAIActorProxy* proxy) { m_proxy.reset(proxy); } IAIActorProxy* CAIActor::GetProxy() const { return m_proxy; } void CAIActor::ClearProbableTargets() { m_probableTargets.clear(); } void CAIActor::AddProbableTarget(CAIObject* pTarget) { m_probableTargets.push_back(pTarget); } IAIObject::EFieldOfViewResult CAIActor::CheckPointInFOV(const Vec3& point, float sightRange) const { FUNCTION_PROFILER(gEnv->pSystem, PROFILE_AI); const Vec3& eyePosition = GetPos(); const Vec3 eyeToPointDisplacement = point - eyePosition; const float squaredEyeToPointDistance = eyeToPointDisplacement.GetLengthSquared(); const bool pointIsAtEyePosition = (squaredEyeToPointDistance <= std::numeric_limits::epsilon()); IF_UNLIKELY (pointIsAtEyePosition) { return eFOV_Outside; } if (squaredEyeToPointDistance <= sqr(sightRange)) { float primaryFovCos = 1.0f; float secondaryFovCos = 1.0f; GetSightFOVCos(primaryFovCos, secondaryFovCos); assert(primaryFovCos >= secondaryFovCos); const Vec3 eyeToPointDirection = eyeToPointDisplacement.GetNormalized(); const Vec3& eyeDirection = GetViewDir(); const float dotProduct = eyeDirection.Dot(eyeToPointDirection); if (dotProduct >= secondaryFovCos) { if (dotProduct >= primaryFovCos) { return eFOV_Primary; } else { return eFOV_Secondary; } } } return eFOV_Outside; } void CAIActor::HandlePathDecision(MNMPathRequestResult& result) { } void CAIActor::HandleVisualStimulus(SAIEVENT* pAIEvent) { FUNCTION_PROFILER(gEnv->pSystem, PROFILE_AI); const float fGlobalVisualPerceptionScale = gEnv->pAISystem->GetGlobalVisualScale(this); const float fVisualPerceptionScale = m_Parameters.m_PerceptionParams.perceptionScale.visual * fGlobalVisualPerceptionScale; if (gAIEnv.CVars.IgnoreVisualStimulus != 0 || m_Parameters.m_bAiIgnoreFgNode || fVisualPerceptionScale <= 0.0f) { return; } if (gAIEnv.pTargetTrackManager->IsEnabled()) { // Check if in range (using perception scale) if (eFOV_Outside != IsPointInFOV(pAIEvent->vPosition, fVisualPerceptionScale)) { gAIEnv.pTargetTrackManager->HandleStimulusFromAIEvent(GetAIObjectID(), pAIEvent, TargetTrackHelpers::eEST_Visual); IEntity* pEventOwnerEntity = gEnv->pEntitySystem->GetEntity(pAIEvent->sourceId); if (!pEventOwnerEntity) { return; } IAIObject* pEventOwnerAI = pEventOwnerEntity->GetAI(); if (!pEventOwnerAI) { return; } if (IsHostile(pEventOwnerAI)) { m_State.nTargetType = static_cast(pEventOwnerAI)->GetType(); m_stimulusStartTime = GetAISystem()->GetFrameStartTimeSeconds(); m_State.eTargetThreat = AITHREAT_AGGRESSIVE; m_State.eTargetType = AITARGET_VISUAL; CWeakRef refAttentionTarget = GetWeakRef(static_cast(pEventOwnerAI)); if (refAttentionTarget != m_refAttentionTarget) { SetAttentionTarget(refAttentionTarget); } } } } } void CAIActor::HandleSoundEvent(SAIEVENT* pAIEvent) { FUNCTION_PROFILER(gEnv->pSystem, PROFILE_AI); const float fGlobalAudioPerceptionScale = gEnv->pAISystem->GetGlobalAudioScale(this); const float fAudioPerceptionScale = m_Parameters.m_PerceptionParams.perceptionScale.audio * fGlobalAudioPerceptionScale; if (gAIEnv.CVars.IgnoreSoundStimulus != 0 || m_Parameters.m_bAiIgnoreFgNode || fAudioPerceptionScale <= 0.0f) { return; } if (gAIEnv.pTargetTrackManager->IsEnabled()) { // Check if in range (using perception scale) const Vec3& vMyPos = GetPos(); const float fSoundDistance = vMyPos.GetDistance(pAIEvent->vPosition) * (1.0f / fAudioPerceptionScale); if (fSoundDistance <= pAIEvent->fThreat) { gAIEnv.pTargetTrackManager->HandleStimulusFromAIEvent(GetAIObjectID(), pAIEvent, TargetTrackHelpers::eEST_Sound); IEntity* pEventOwnerEntity = gEnv->pEntitySystem->GetEntity(pAIEvent->sourceId); if (!pEventOwnerEntity) { return; } IAIObject* pEventOwnerAI = pEventOwnerEntity->GetAI(); if (!pEventOwnerAI) { return; } if (IsHostile(pEventOwnerAI)) { if ((m_State.eTargetType != AITARGET_MEMORY) && (m_State.eTargetType != AITARGET_VISUAL)) { m_State.nTargetType = static_cast(pEventOwnerAI)->GetType(); m_stimulusStartTime = GetAISystem()->GetFrameStartTimeSeconds(); m_State.nTargetType = static_cast(pEventOwnerAI)->GetType(); m_State.eTargetThreat = AITHREAT_AGGRESSIVE; m_State.eTargetType = AITARGET_SOUND; SetAttentionTarget(GetWeakRef(static_cast(pEventOwnerAI))); } } } } } void CAIActor::HandleBulletRain(SAIEVENT* pAIEvent) { FUNCTION_PROFILER(gEnv->pSystem, PROFILE_AI); if (gAIEnv.CVars.IgnoreBulletRainStimulus || m_Parameters.m_bAiIgnoreFgNode) { return; } IAISignalExtraData* pData = GetAISystem()->CreateSignalExtraData(); pData->point = pAIEvent->vPosition; pData->point2 = pAIEvent->vStimPos; pData->nID = pAIEvent->sourceId; pData->fValue = pAIEvent->fThreat; // pressureMultiplier SetSignal(0, "OnBulletRain", GetEntity(), pData, gAIEnv.SignalCRCs.m_nOnBulletRain); if (gAIEnv.pTargetTrackManager->IsEnabled()) { gAIEnv.pTargetTrackManager->HandleStimulusFromAIEvent(GetAIObjectID(), pAIEvent, TargetTrackHelpers::eEST_BulletRain); } } void CAIActor::CancelRequestedPath(bool actorRemoved) { } IAIObject::EFieldOfViewResult CAIActor::IsPointInFOV(const Vec3& vPos, float fDistanceScale) const { FUNCTION_PROFILER(gEnv->pSystem, PROFILE_AI); const float fSightRange = m_Parameters.m_PerceptionParams.sightRange * fDistanceScale; return CheckPointInFOV(vPos, fSightRange); } void CAIActor::GetMovementSpeedRange(float fUrgency, bool bSlowForStrafe, float& normalSpeed, float& minSpeed, float& maxSpeed) const { AgentMovementSpeeds::EAgentMovementUrgency urgency; AgentMovementSpeeds::EAgentMovementStance stance; bool vehicle = GetType() == AIOBJECT_VEHICLE; if (fUrgency < 0.5f * (AISPEED_SLOW + AISPEED_WALK)) { urgency = AgentMovementSpeeds::AMU_SLOW; } else if (fUrgency < 0.5f * (AISPEED_WALK + AISPEED_RUN)) { urgency = AgentMovementSpeeds::AMU_WALK; } else if (fUrgency < 0.5f * (AISPEED_RUN + AISPEED_SPRINT)) { urgency = AgentMovementSpeeds::AMU_RUN; } else { urgency = AgentMovementSpeeds::AMU_SPRINT; } if (IsAffectedByLight() && m_movementAbility.lightAffectsSpeed) { // Disable sprinting in dark light conditions. if (urgency == AgentMovementSpeeds::AMU_SPRINT) { EAILightLevel eAILightLevel = GetLightLevel(); if ((eAILightLevel == AILL_DARK) || (eAILightLevel == AILL_SUPERDARK)) { urgency = AgentMovementSpeeds::AMU_RUN; } } } const SAIBodyInfo& bodyInfo = GetBodyInfo(); switch (bodyInfo.stance) { case STANCE_STEALTH: stance = AgentMovementSpeeds::AMS_STEALTH; break; case STANCE_CROUCH: stance = AgentMovementSpeeds::AMS_CROUCH; break; case STANCE_PRONE: stance = AgentMovementSpeeds::AMS_PRONE; break; case STANCE_SWIM: stance = AgentMovementSpeeds::AMS_SWIM; break; case STANCE_RELAXED: stance = AgentMovementSpeeds::AMS_RELAXED; break; case STANCE_ALERTED: stance = AgentMovementSpeeds::AMS_ALERTED; break; case STANCE_LOW_COVER: stance = AgentMovementSpeeds::AMS_LOW_COVER; break; case STANCE_HIGH_COVER: stance = AgentMovementSpeeds::AMS_HIGH_COVER; break; default: stance = AgentMovementSpeeds::AMS_COMBAT; break; } const float artificialMinSpeedMult = 1.0f; AgentMovementSpeeds::SSpeedRange fwdRange = m_movementAbility.movementSpeeds.GetRange(stance, urgency); fwdRange.min *= artificialMinSpeedMult; normalSpeed = fwdRange.def; minSpeed = fwdRange.min; maxSpeed = fwdRange.max; if (m_movementAbility.directionalScaleRefSpeedMin > 0.0f) { float desiredSpeed = normalSpeed; float desiredTurnSpeed = m_bodyTurningSpeed; float travelAngle = Ang3::CreateRadZ(GetEntityDir(), GetMoveDir()); //m_State.vMoveDir); const float refSpeedMin = m_movementAbility.directionalScaleRefSpeedMin; const float refSpeedMax = m_movementAbility.directionalScaleRefSpeedMax; // When a locomotion is slow (0.5-2.0f), then we can do this motion in all direction more or less at the same speed float t = sqr(clamp_tpl((desiredSpeed - refSpeedMin) / (refSpeedMax - refSpeedMin), 0.0f, 1.0f)); float scaleLimit = clamp_tpl(0.8f * (1 - t) + 0.1f * t, 0.3f, 1.0f); //never scale more then 0.4 down float turnSlowDownFactor = (gAIEnv.configuration.eCompatibilityMode == ECCM_CRYSIS2) ? 0.2f : 0.4f; //adjust desired speed for turns float speedScale = 1.0f - fabsf(desiredTurnSpeed * turnSlowDownFactor) / gf_PI; speedScale = clamp_tpl(speedScale, scaleLimit, 1.0f); //adjust desired speed when strafing and running backward float strafeSlowDown = (gf_PI - fabsf(travelAngle * 0.60f)) / gf_PI; strafeSlowDown = clamp_tpl(strafeSlowDown, scaleLimit, 1.0f); //adjust desired speed when running uphill & downhill float slopeSlowDown = (gf_PI - fabsf(DEG2RAD(bodyInfo.slopeAngle) / 12.0f)) / gf_PI; slopeSlowDown = clamp_tpl(slopeSlowDown, scaleLimit, 1.0f); float scale = min(speedScale, min(strafeSlowDown, slopeSlowDown)); normalSpeed *= scale; Limit(normalSpeed, minSpeed, maxSpeed); } } void CAIActor::ResetLookAt() { m_State.vLookTargetPos.zero(); } bool CAIActor::SetLookAtPointPos(const Vec3& vPoint, bool bPriority) { m_State.vLookTargetPos = vPoint; // Inspired by CPipeUser::SetLookAtPointPos [5/3/2010 evgeny] Vec3 vDesired = vPoint - GetPos(); if (!m_movementAbility.b3DMove) { vDesired.z = 0; } vDesired.NormalizeSafe(); const SAIBodyInfo& bodyInfo = GetBodyInfo(); Vec3 vCurrent = bodyInfo.vEyeDirAnim; if (!m_movementAbility.b3DMove) { vCurrent.z = 0; } vCurrent.NormalizeSafe(); // cos( 11.5deg ) ~ 0.98 return 0.98f <= vCurrent.Dot(vDesired); } bool CAIActor::SetLookAtDir(const Vec3& vDir, bool bPriority) { Vec3 vDirCopy = vDir; return vDirCopy.NormalizeSafe() ? SetLookAtPointPos(GetPos() + vDirCopy * 100.0f) : true; } void CAIActor::ResetBodyTargetDir() { m_State.vBodyTargetDir.zero(); } void CAIActor::SetBodyTargetDir(const Vec3& vDir) { m_State.vBodyTargetDir = vDir; } void CAIActor::SetMoveTarget(const Vec3& vMoveTarget) { m_State.vMoveTarget = vMoveTarget; } const Vec3& CAIActor::GetBodyTargetDir() const { return m_State.vBodyTargetDir; } void CAIActor::GoTo(const Vec3& vTargetPos) { m_State.vLookTargetPos = vTargetPos; m_State.vMoveTarget = vTargetPos; } void CAIActor::SetSpeed(float fSpeed) { m_State.fMovementUrgency = fSpeed; } #if ENABLE_CRY_PHYSICS IPhysicalEntity* CAIActor::GetPhysics(bool bWantCharacterPhysics) const { IAIActorProxy* pAIActorProxy = GetProxy(); return pAIActorProxy ? pAIActorProxy->GetPhysics(bWantCharacterPhysics) : CAIObject::GetPhysics(bWantCharacterPhysics); } #endif // ENABLE_CRY_PHYSICS bool CAIActor::CanDamageTarget(IAIObject* target) const { return true; } bool CAIActor::CanDamageTargetWithMelee() const { return true; } void CAIActor::SetTerritoryShapeName(const char* szName) { assert(szName); if (m_territoryShapeName.compare(szName)) { m_territoryShapeName = szName; if (m_territoryShapeName.compare("")) { m_territoryShape = GetAISystem()->GetGenericShapeOfName(szName); // i.e. m_territoryShapeName if (m_territoryShape) { // Territory shapes should be really simple size_t size = m_territoryShape->shape.size(); if (size > 8) { AIWarning("Territory shape %s for %s has %" PRISIZE_T " points. Territories should not have more than 8 points", szName, GetName(), size); } } else { m_territoryShapeName += " (not found)"; } } else { m_territoryShape = 0; } } } const char* CAIActor::GetTerritoryShapeName() const { return (gEnv->IsEditor() && !gEnv->IsEditing()) ? m_Parameters.m_sTerritoryName.c_str() : m_territoryShapeName.c_str(); } bool CAIActor::IsPointInsideTerritoryShape(const Vec3& vPos, bool bCheckHeight) const { bool bResult = true; const SShape* pTerritory = GetTerritoryShape(); if (pTerritory) { bResult = pTerritory->IsPointInsideShape(vPos, bCheckHeight); } return bResult; } bool CAIActor::ConstrainInsideTerritoryShape(Vec3& vPos, bool bCheckHeight) const { bool bResult = true; const SShape* pTerritory = GetTerritoryShape(); if (pTerritory) { bResult = pTerritory->ConstrainPointInsideShape(vPos, bCheckHeight); } return bResult; } //=================================================================== // GetObjectType //=================================================================== CAIActor::EAIObjectType CAIActor::GetObjectType(const CAIObject* ai, unsigned short type) { if (type == AIOBJECT_PLAYER) { return AIOT_PLAYER; } else if (type == AIOBJECT_ACTOR) { return AIOT_AGENTSMALL; } else if (type == AIOBJECT_VEHICLE) { // differentiate between medium and big vehicles (e.g. tank can drive over jeep) return AIOT_AGENTMED; } else { return AIOT_UNKNOWN; } } //=================================================================== // GetNavInteraction //=================================================================== CAIActor::ENavInteraction CAIActor::GetNavInteraction(const CAIObject* navigator, const CAIObject* obstacle) { if (const CAIActor* actor = navigator->CastToCAIActor()) { const SAIBodyInfo& info = actor->GetBodyInfo(); if (info.GetLinkedVehicleEntity()) { return NI_IGNORE; } } unsigned short navigatorType = navigator->GetType(); unsigned short obstacleType = obstacle->GetType(); bool enemy = navigator->IsHostile(obstacle); EAIObjectType navigatorOT = GetObjectType(navigator, navigatorType); EAIObjectType obstacleOT = GetObjectType(obstacle, obstacleType); switch (navigatorOT) { case AIOT_UNKNOWN: case AIOT_PLAYER: return NI_IGNORE; case AIOT_AGENTSMALL: // don't navigate around their enemies, unless the enemy is bigger /* if (enemy) return obstacleOT > navigatorOT ? NI_STEER : NI_IGNORE; else*/ return NI_STEER; case AIOT_AGENTMED: case AIOT_AGENTBIG: // don't navigate around their enemies, unless the enemy is same size or bigger if (enemy) { return obstacleOT >= navigatorOT ? NI_STEER : NI_IGNORE; } else { return NI_STEER; } default: AIError("GetNavInteraction: Unhandled switch case %d", navigatorOT); return NI_IGNORE; } } void CAIActor::CoordinationEntered(const char* signalName) { ++m_activeCoordinationCount; assert(m_activeCoordinationCount < 10); SetSignal(AISIGNAL_ALLOW_DUPLICATES, signalName); } void CAIActor::CoordinationExited(const char* signalName) { //assert(m_activeCoordinationCount < 10 && m_activeCoordinationCount > 0); // Morgan - 10-28-10 This should not require a check against zero. // If its already zero, then coordinations were stopped, that had already actors who had finished their roles. // Need addition to coordination system to prevent stop methods being called for already complete sequences. if (m_activeCoordinationCount > 0) { --m_activeCoordinationCount; } if (m_activeCoordinationCount == 0) { SetSignal(AISIGNAL_ALLOW_DUPLICATES, signalName); } } bool CAIActor::GetInitialPosition(Vec3& initialPosition) const { if (m_initialPosition.isValid) { initialPosition = m_initialPosition.pos; return true; } else { return false; } } void CAIActor::StartBehaviorTree(const char* behaviorName) { if (GetAISystem()->GetIBehaviorTreeManager()->StartModularBehaviorTree(AZ::EntityId(GetEntityID()), behaviorName)) { m_runningBehaviorTree = true; } } void CAIActor::StopBehaviorTree() { if (m_runningBehaviorTree) { GetAISystem()->GetIBehaviorTreeManager()->StopModularBehaviorTree(AZ::EntityId(GetEntityID())); m_runningBehaviorTree = false; } } bool CAIActor::IsRunningBehaviorTree() const { return m_runningBehaviorTree; }