//----------------------------------------------------------------------------- // Computes the link type //----------------------------------------------------------------------------- Navigation_t CAI_Pathfinder::ComputeWaypointType( CAI_Node **ppNodes, int parentID, int destID ) { Navigation_t navType = NAV_NONE; CAI_Node *pNode = ppNodes[parentID]; for (int link=0; link < pNode->NumLinks();link++) { if (pNode->GetLinkByIndex(link)->DestNodeID(parentID) == destID) { // BRJ 10/1/02 // FIXME: pNPC->CapabilitiesGet() is actually the mechanism by which fliers // filter out the bitfields in the waypoint type (most importantly, bits_MOVE_CAP_GROUND) // that would cause the waypoint distance to be computed in a 2D, as opposed to 3D fashion // This is a super-scary weak link if you ask me. int linkMoveTypeBits = pNode->GetLinkByIndex(link)->m_iAcceptedMoveTypes[GetHullType()]; int moveTypeBits = ( linkMoveTypeBits & CapabilitiesGet()); if ( !moveTypeBits && linkMoveTypeBits == bits_CAP_MOVE_JUMP ) { Assert( pNode->GetHint() && pNode->GetHint()->HintType() == HINT_JUMP_OVERRIDE ); ppNodes[destID]->Lock(0.3); moveTypeBits = linkMoveTypeBits; } Navigation_t linkType = MoveBitsToNavType( moveTypeBits ); // This will only trigger if the links disagree about their nav type Assert( (navType == NAV_NONE) || (navType == linkType) ); navType = linkType; break; } } // @TODO (toml 10-15-02): one would not expect to come out of the above logic // with NAV_NONE. However, if a graph is newly built, it can contain malformed // links that are referred to by the destination node, not the source node. // This has to be fixed if ( navType == NAV_NONE ) { pNode = ppNodes[destID]; for (int link=0; link < pNode->NumLinks();link++) { if (pNode->GetLinkByIndex(link)->DestNodeID(parentID) == destID) { int npcMoveBits = CapabilitiesGet(); int nodeMoveBits = pNode->GetLinkByIndex(link)->m_iAcceptedMoveTypes[GetHullType()]; int moveTypeBits = ( npcMoveBits & nodeMoveBits ); Navigation_t linkType = MoveBitsToNavType( moveTypeBits ); Assert( (navType == NAV_NONE) || (navType == linkType) ); navType = linkType; DevMsg( "Note: Strange link found between nodes in AI node graph\n" ); break; } } } AssertMsg( navType != NAV_NONE, "Pathfinder appears to have output a path with consecutive nodes thate are not actually connected\n" ); return navType; }
//----------------------------------------------------------------------------- // TASK_RANGE_ATTACK1 / TASK_RANGE_ATTACK2 / etc. //----------------------------------------------------------------------------- void CAI_BaseHumanoid::RunTaskRangeAttack1( const Task_t *pTask ) { if ( ( CapabilitiesGet() & bits_CAP_USE_SHOT_REGULATOR ) == 0 ) { BaseClass::RunTask( pTask ); return; } AutoMovement( ); Vector vecEnemyLKP = GetEnemyLKP(); // If our enemy was killed, but I'm not done animating, the last known position comes // back as the origin and makes the me face the world origin if my attack schedule // doesn't break when my enemy dies. (sjb) if( vecEnemyLKP != vec3_origin ) { if ( ( pTask->iTask == TASK_RANGE_ATTACK1 || pTask->iTask == TASK_RELOAD ) && ( CapabilitiesGet() & bits_CAP_AIM_GUN ) && FInAimCone( vecEnemyLKP ) ) { // Arms will aim, so leave body yaw as is GetMotor()->SetIdealYawAndUpdate( GetMotor()->GetIdealYaw(), AI_KEEP_YAW_SPEED ); } else { GetMotor()->SetIdealYawToTargetAndUpdate( vecEnemyLKP, AI_KEEP_YAW_SPEED ); } } if ( IsActivityFinished() ) { if ( !GetEnemy() || !GetEnemy()->IsAlive() ) { TaskComplete(); return; } if ( !GetShotRegulator()->IsInRestInterval() ) { if ( GetShotRegulator()->ShouldShoot() ) { OnRangeAttack1(); ResetIdealActivity( ACT_RANGE_ATTACK1 ); } return; } TaskComplete(); } }
void CNPC_Infected::RunAttackTask( int task ) { AutoMovement( ); Vector vecEnemyLKP = GetEnemyLKP(); // If our enemy was killed, but I'm not done animating, the last known position comes // back as the origin and makes the me face the world origin if my attack schedule // doesn't break when my enemy dies. (sjb) if( vecEnemyLKP != vec3_origin ) { if ( ( task == TASK_RANGE_ATTACK1 || task == TASK_RELOAD ) && ( CapabilitiesGet() & bits_CAP_AIM_GUN ) && FInAimCone( vecEnemyLKP ) ) { // Arms will aim, so leave body yaw as is GetMotor()->SetIdealYawAndUpdate( GetMotor()->GetIdealYaw(), AI_KEEP_YAW_SPEED ); } else { GetMotor()->SetIdealYawToTargetAndUpdate( vecEnemyLKP, AI_KEEP_YAW_SPEED ); } } CAnimationLayer *pPlayer = GetAnimOverlay( m_iAttackLayer ); if ( pPlayer->m_bSequenceFinished ) { if ( task == TASK_RELOAD && GetShotRegulator() ) { GetShotRegulator()->Reset( false ); } TaskComplete(); } }
//----------------------------------------------------------------------------- // TASK_RANGE_ATTACK1 //----------------------------------------------------------------------------- void CAI_BaseHumanoid::StartTaskRangeAttack1( const Task_t *pTask ) { if ( ( CapabilitiesGet() & bits_CAP_USE_SHOT_REGULATOR ) == 0 ) { BaseClass::StartTask( pTask ); return; } // Can't shoot if we're in the rest interval; fail the schedule if ( GetShotRegulator()->IsInRestInterval() ) { TaskFail( "Shot regulator in rest interval" ); return; } if ( GetShotRegulator()->ShouldShoot() ) { OnRangeAttack1(); ResetIdealActivity( ACT_RANGE_ATTACK1 ); } else { // This can happen if we start while in the middle of a burst // which shouldn't happen, but given the chaotic nature of our AI system, // does occasionally happen. ResetIdealActivity( ACT_IDLE_ANGRY ); } }
//----------------------------------------------------------------------------- // TASK_RANGE_ATTACK1 //----------------------------------------------------------------------------- void CAI_BaseHumanoid::BuildScheduleTestBits( ) { BaseClass::BuildScheduleTestBits(); if ( CapabilitiesGet() & bits_CAP_USE_SHOT_REGULATOR ) { if ( GetShotRegulator()->IsInRestInterval() ) { ClearCustomInterruptCondition( COND_CAN_RANGE_ATTACK1 ); } } }
bool CAI_StandoffBehavior::CanSelectSchedule() { if ( !m_fActive ) return false; if ( !( CapabilitiesGet() & bits_CAP_DUCK ) || !HaveSequenceForActivity( ACT_COVER_LOW ) ) { m_fActive = false; return false; } return ( GetNpcState() == NPC_STATE_COMBAT && GetOuter()->GetActiveWeapon() != NULL ); }
void CAI_StandoffBehavior::UpdateTranslateActivityMap() { BaseClass::UpdateTranslateActivityMap(); Activity lowCoverActivity = GetMappedActivity( AIP_CROUCHING, ACT_COVER_LOW ); if ( lowCoverActivity == ACT_INVALID ) lowCoverActivity = ACT_COVER_LOW; m_bHasLowCoverActivity = ( ( CapabilitiesGet() & bits_CAP_DUCK ) && (GetOuter()->TranslateActivity( lowCoverActivity ) != ACT_INVALID)); CBaseCombatWeapon *pWeapon = GetOuter()->GetActiveWeapon(); if ( pWeapon && (GetOuter()->TranslateActivity( lowCoverActivity ) == ACT_INVALID )) DevMsg( "Note: NPC class %s lacks ACT_COVER_LOW, therefore cannot participate in standoff\n", GetOuter()->GetClassname() ); }
bool CASW_Alien_Jumper::DoJumpTo(Vector &vecDest) { //Too soon to try to jump if ( m_flJumpTime > gpGlobals->curtime ) return false; // only jump if you're on the ground if (!(GetFlags() & FL_ONGROUND) || GetNavType() == NAV_JUMP ) return false; // Don't jump if I'm not allowed if ( ( CapabilitiesGet() & bits_CAP_MOVE_JUMP ) == false ) return false; // Try the jump AIMoveTrace_t moveTrace; GetMoveProbe()->MoveLimit( NAV_JUMP, GetAbsOrigin(), vecDest, MASK_NPCSOLID, NULL, &moveTrace ); //See if it succeeded if ( IsMoveBlocked( moveTrace.fStatus ) ) { if ( asw_debug_aliens.GetInt() == 2 ) { NDebugOverlay::Box( vecDest, GetHullMins(), GetHullMaxs(), 255, 0, 0, 0, 5 ); NDebugOverlay::Line( GetAbsOrigin(), vecDest, 255, 0, 0, 0, 5 ); } m_flJumpTime = gpGlobals->curtime + random->RandomFloat( 1.0f, 2.0f ); return false; } if ( asw_debug_aliens.GetInt() == 2 ) { NDebugOverlay::Box( vecDest, GetHullMins(), GetHullMaxs(), 0, 255, 0, 0, 5 ); NDebugOverlay::Line( GetAbsOrigin(), vecDest, 0, 255, 0, 0, 5 ); } //Save this jump in case the next time fails m_vecSavedJump = moveTrace.vJumpVelocity; m_vecLastJumpAttempt = vecDest; SetSchedule(SCHED_ASW_ALIEN_JUMP); m_bForcedStuckJump = true; //Msg("Drone saving jump vec %f %f %f\n", m_vecSavedJump.x, m_vecSavedJump.y, m_vecSavedJump.z); return true; }
void CHL1NPCTalker::IdleHeadTurn( const Vector &vTargetPos, float flDuration, float flImportance ) { if (!(CapabilitiesGet() & bits_CAP_TURN_HEAD)) return; if ( flDuration == 0.0f ) flDuration = random->RandomFloat( 2.0, 4.0 ); if ( vTargetPos == vec3_origin || m_NPCState == NPC_STATE_SCRIPT ) { SetHeadDirection( vTargetPos, GetAnimTimeInterval() ); } else { AddLookTarget( vTargetPos, 1.0, flDuration ); } }
void CHL1NPCTalker::IdleHeadTurn( CBaseEntity *pTarget, float flDuration, float flImportance ) { // Must be able to turn our head if (!(CapabilitiesGet() & bits_CAP_TURN_HEAD)) return; // If the target is invalid, or we're in a script, do nothing if ( ( !pTarget ) || ( m_NPCState == NPC_STATE_SCRIPT ) ) return; // Fill in a duration if we haven't specified one if ( flDuration == 0.0f ) { flDuration = random->RandomFloat( 2.0, 4.0 ); } // Add a look target AddLookTarget( pTarget, 1.0, flDuration ); }
bool CASW_Alien_Jumper::DoForcedJump( Vector &vecVelocity ) { //Too soon to try to jump if ( m_flJumpTime > gpGlobals->curtime ) return false; // only jump if you're on the ground if (!(GetFlags() & FL_ONGROUND) || GetNavType() == NAV_JUMP ) return false; // Don't jump if I'm not allowed if ( ( CapabilitiesGet() & bits_CAP_MOVE_JUMP ) == false ) return false; m_vecSavedJump = vecVelocity; m_vecLastJumpAttempt = GetAbsOrigin() + vecVelocity * 100; SetSchedule(SCHED_ASW_ALIEN_JUMP); m_bForcedStuckJump = true; return true; }
//----------------------------------------------------------------------------- // Purpose: // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CASW_Alien_Jumper::ShouldJump( void ) { if ( GetEnemy() == NULL ) return false; // don't jump if we're stunned if (m_bElectroStunned) return false; //Too soon to try to jump if ( m_flJumpTime > gpGlobals->curtime ) return false; // only jump if you're on the ground if (!(GetFlags() & FL_ONGROUND) || GetNavType() == NAV_JUMP ) return false; // Don't jump if I'm not allowed if ( ( CapabilitiesGet() & bits_CAP_MOVE_JUMP ) == false ) return false; Vector vEnemyForward, vForward; GetEnemy()->GetVectors( &vEnemyForward, NULL, NULL ); GetVectors( &vForward, NULL, NULL ); float flDot = DotProduct( vForward, vEnemyForward ); if ( flDot < 0.5f ) flDot = 0.5f; Vector vecPredictedPos; //Get our likely position in two seconds UTIL_PredictedPosition( GetEnemy(), flDot * 2.5f, &vecPredictedPos ); // Don't jump if we're already near the target if ( ( GetAbsOrigin() - vecPredictedPos ).LengthSqr() < (512*512) ) return false; //Don't retest if the target hasn't moved enough //FIXME: Check your own distance from last attempt as well if ( ( ( m_vecLastJumpAttempt - vecPredictedPos ).LengthSqr() ) < (128*128) ) { m_flJumpTime = gpGlobals->curtime + random->RandomFloat( 1.0f, 2.0f ); return false; } Vector targetDir = ( vecPredictedPos - GetAbsOrigin() ); float flDist = VectorNormalize( targetDir ); // don't jump at target it it's very close if (flDist < ANTLION_JUMP_MIN) return false; Vector targetPos = vecPredictedPos + ( targetDir * (GetHullWidth()*4.0f) ); // Try the jump AIMoveTrace_t moveTrace; GetMoveProbe()->MoveLimit( NAV_JUMP, GetAbsOrigin(), targetPos, MASK_NPCSOLID, GetNavTargetEntity(), &moveTrace ); //See if it succeeded if ( IsMoveBlocked( moveTrace.fStatus ) ) { if ( asw_debug_aliens.GetInt() == 2 ) { NDebugOverlay::Box( targetPos, GetHullMins(), GetHullMaxs(), 255, 0, 0, 0, 5 ); NDebugOverlay::Line( GetAbsOrigin(), targetPos, 255, 0, 0, 0, 5 ); } m_flJumpTime = gpGlobals->curtime + random->RandomFloat( 1.0f, 2.0f ); return false; } if ( asw_debug_aliens.GetInt() == 2 ) { NDebugOverlay::Box( targetPos, GetHullMins(), GetHullMaxs(), 0, 255, 0, 0, 5 ); NDebugOverlay::Line( GetAbsOrigin(), targetPos, 0, 255, 0, 0, 5 ); } //Save this jump in case the next time fails m_vecSavedJump = moveTrace.vJumpVelocity; m_vecLastJumpAttempt = targetPos; return true; }
AI_Waypoint_t *CAI_Pathfinder::FindBestPath(int startID, int endID) { if ( !GetNetwork()->NumNodes() ) return NULL; int nNodes = GetNetwork()->NumNodes(); CAI_Node **pAInode = GetNetwork()->AccessNodes(); CVarBitVec openBS(nNodes); CVarBitVec closeBS(nNodes); // ------------- INITIALIZE ------------------------ float* nodeG = (float *)stackalloc( nNodes * sizeof(float) ); float* nodeH = (float *)stackalloc( nNodes * sizeof(float) ); float* nodeF = (float *)stackalloc( nNodes * sizeof(float) ); int* nodeP = (int *)stackalloc( nNodes * sizeof(int) ); // Node parent for (int node=0;node<nNodes;node++) { nodeG[node] = FLT_MAX; nodeP[node] = -1; } nodeG[startID] = 0; nodeH[startID] = 0.1*(pAInode[startID]->GetPosition(GetHullType())-pAInode[endID]->GetPosition(GetHullType())).Length(); // Don't want to over estimate nodeF[startID] = nodeG[startID] + nodeH[startID]; openBS.Set(startID); closeBS.Set( startID ); // --------------- FIND BEST PATH ------------------ while (!openBS.IsAllClear()) { int smallestID = CAI_Network::FindBSSmallest(&openBS,nodeF,nNodes); openBS.Clear(smallestID); CAI_Node *pSmallestNode = pAInode[smallestID]; if (GetOuter()->IsUnusableNode(smallestID, pSmallestNode->m_pHint)) continue; if (smallestID == endID) { AI_Waypoint_t* route = MakeRouteFromParents(&nodeP[0], endID); return route; } // Check this if the node is immediately in the path after the startNode // that it isn't blocked for (int link=0; link < pSmallestNode->NumLinks();link++) { CAI_Link *nodeLink = pSmallestNode->GetLinkByIndex(link); if (!IsLinkUsable(nodeLink,smallestID)) continue; // FIXME: the cost function should take into account Node costs (danger, flanking, etc). int moveType = nodeLink->m_iAcceptedMoveTypes[GetHullType()] & CapabilitiesGet(); int testID = nodeLink->DestNodeID(smallestID); Vector r1 = pSmallestNode->GetPosition(GetHullType()); Vector r2 = pAInode[testID]->GetPosition(GetHullType()); float dist = GetOuter()->GetNavigator()->MovementCost( moveType, r1, r2 ); // MovementCost takes ref parameters!! if ( dist == FLT_MAX ) continue; float new_g = nodeG[smallestID] + dist; if ( !closeBS.IsBitSet(testID) || (new_g < nodeG[testID]) ) { nodeP[testID] = smallestID; nodeG[testID] = new_g; nodeH[testID] = (pAInode[testID]->GetPosition(GetHullType())-pAInode[endID]->GetPosition(GetHullType())).Length(); nodeF[testID] = nodeG[testID] + nodeH[testID]; closeBS.Set( testID ); openBS.Set( testID ); } } } return NULL; }
bool CAI_Pathfinder::IsLinkUsable(CAI_Link *pLink, int startID) { // -------------------------------------------------------------------------- // Skip if link turned off // -------------------------------------------------------------------------- if (pLink->m_LinkInfo & bits_LINK_OFF) { CDynamicLink *pDynamicLink = dynamic_cast<CDynamicLink *>(CEntity::Instance(pLink->m_pDynamicLink)); if ( !pDynamicLink || *(pDynamicLink->m_strAllowUse.ptr) == NULL_STRING ) return false; const char *pszAllowUse = STRING( *(pDynamicLink->m_strAllowUse) ); if ( *(pDynamicLink->m_bInvertAllow) ) { // Exlude only the specified entity name or classname if ( GetOuter()->NameMatches(pszAllowUse) || GetOuter()->ClassMatches( pszAllowUse ) ) return false; } else { // Exclude everything but the allowed entity name or classname if ( !GetOuter()->NameMatches( pszAllowUse) && !GetOuter()->ClassMatches( pszAllowUse ) ) return false; } } // -------------------------------------------------------------------------- // Get the destination nodeID // -------------------------------------------------------------------------- int endID = pLink->DestNodeID(startID); // -------------------------------------------------------------------------- // Make sure I have the ability to do the type of movement specified by the link // -------------------------------------------------------------------------- int linkMoveTypes = pLink->m_iAcceptedMoveTypes[GetHullType()]; int moveType = ( linkMoveTypes & CapabilitiesGet() ); CAI_Node *pStartNode,*pEndNode; pStartNode = GetNetwork()->GetNode(startID); pEndNode = GetNetwork()->GetNode(endID); if ( (linkMoveTypes & bits_CAP_MOVE_JUMP) && !moveType ) { CE_AI_Hint *pStartHint = dynamic_cast<CE_AI_Hint *>(pStartNode->GetHint()); CE_AI_Hint *pEndHint = dynamic_cast<CE_AI_Hint *>(pEndNode->GetHint()); if ( pStartHint && pEndHint ) { if ( pStartHint->HintType() == HINT_JUMP_OVERRIDE && pEndHint->HintType() == HINT_JUMP_OVERRIDE && ( ( ( pStartHint->GetSpawnFlags() | pEndHint->GetSpawnFlags() ) & SF_ALLOW_JUMP_UP ) || pStartHint->GetAbsOrigin().z > pEndHint->GetAbsOrigin().z ) ) { if ( !pStartNode->IsLocked() ) { if ( pStartHint->GetTargetNode() == -1 || pStartHint->GetTargetNode() == endID ) moveType = bits_CAP_MOVE_JUMP; } } } } if (!moveType) { return false; } // -------------------------------------------------------------------------- // Check if NPC has a reason not to use the desintion node // -------------------------------------------------------------------------- CEntity *cent = pEndNode->GetHint(); if (GetOuter()->IsUnusableNode(endID, ((cent) ? cent->BaseEntity() : NULL))) { return false; } // -------------------------------------------------------------------------- // If a jump make sure the jump is within NPC's legal parameters for jumping // -------------------------------------------------------------------------- if (moveType == bits_CAP_MOVE_JUMP) { if (!GetOuter()->IsJumpLegal(pStartNode->GetPosition(GetHullType()), pEndNode->GetPosition(GetHullType()), pEndNode->GetPosition(GetHullType()))) { return false; } } // -------------------------------------------------------------------------- // If an NPC suggested that this link is stale and I haven't checked it yet // I should make sure the link is still valid before proceeding // -------------------------------------------------------------------------- if (pLink->m_LinkInfo & bits_LINK_STALE_SUGGESTED) { if (IsLinkStillStale(moveType, pLink)) { return false; } } return true; }