CAI_BaseNPC * CASW_Sentry_Top::SelectOptimalEnemy() { // search through all npcs, any that are in LOS and have health CAI_BaseNPC **ppAIs = g_AI_Manager.AccessAIs(); for ( int i = 0; i < g_AI_Manager.NumAIs(); i++ ) { if (ppAIs[i]->GetHealth() > 0 && CanSee(ppAIs[i])) { // don't shoot marines if ( !asw_sentry_friendly_target.GetBool() && (ppAIs[i]->Classify() == CLASS_ASW_MARINE || ppAIs[i]->Classify() == CLASS_ASW_COLONIST) ) continue; if ( ppAIs[i]->Classify() == CLASS_SCANNER ) continue; if ( !IsValidEnemy( ppAIs[i] ) ) continue; return ppAIs[i]; break; } } // todo: should evaluate valid targets and pick the best one? // (didn't do this for ASv1 and it was fine...) return NULL; }
void CASW_Sentry_Top::FindEnemy() { bool bFindNewEnemy = true; bool bHadEnemy = (m_hEnemy.IsValid() && m_hEnemy.Get()); // if have an enemy and it is alive if (m_hEnemy.IsValid() && m_hEnemy.Get() && m_hEnemy->GetHealth() > 0 ) { // check for LOS to enemy if (CanSee(m_hEnemy)) bFindNewEnemy = false; // reject if enemy is somehow invalid now CAI_BaseNPC *pNPC = dynamic_cast<CAI_BaseNPC *>(m_hEnemy.Get()); if ( pNPC && !IsValidEnemy(pNPC) ) bFindNewEnemy = true; } if (bFindNewEnemy) { if ( g_vecTargetDummies.Count() > 0 ) { m_hEnemy = g_vecTargetDummies[0]; } else { m_hEnemy = SelectOptimalEnemy(); } } // acquired a new enemy if (!bHadEnemy && m_hEnemy.IsValid() && m_hEnemy.Get()) { PlayTurnSound(); } }
CAI_BaseNPC *CASW_Sentry_Top_Cannon::SelectOptimalEnemy() { // prioritize unfrozen aliens who are going to leave the cone soon. // prioritize aliens less the more frozen they get. CUtlVectorFixedGrowable< CAI_BaseNPC *,16 > candidates; CUtlVectorFixedGrowable< float, 16 > candidatescores; // search through all npcs, any that are in LOS and have health CAI_BaseNPC **ppAIs = g_AI_Manager.AccessAIs(); for ( int i = 0; i < g_AI_Manager.NumAIs(); i++ ) { if (ppAIs[i]->GetHealth() > 0 && CanSee(ppAIs[i])) { // don't shoot marines if ( !asw_sentry_friendly_target.GetBool() && ppAIs[i]->Classify() == CLASS_ASW_MARINE ) continue; if ( ppAIs[i]->Classify() == CLASS_SCANNER ) continue; if ( !IsValidEnemy( ppAIs[i] ) ) continue; candidates.AddToTail( ppAIs[i] ); } } // bail out if we don't have anyone if ( candidates.Count() < 1 ) return NULL; else if ( candidates.Count() == 1 ) // just one candidate is an obvious result return candidates[0]; // score each of the candidates candidatescores.EnsureCount( candidates.Count() ); for ( int i = candidates.Count() - 1; i >= 0 ; --i ) { CAI_BaseNPC * RESTRICT pCandidate = candidates[i]; // is the candidate moving into or out of the cone? Vector vCandVel = GetEnemyVelocity(pCandidate); Vector vMeToTarget = pCandidate->GetAbsOrigin() - GetFiringPosition(); Vector vBaseForward = UTIL_YawToVector( m_fDeployYaw ); // crush everything to 2d for simplicity vMeToTarget.z = 0.0f; vCandVel.z = 0.0f; vBaseForward.z = 0.0f; Vector velCross = vBaseForward.Cross(vCandVel); // this encodes also some info on perpendicularity Vector vAimCross = vBaseForward.Cross(vMeToTarget); bool bTargetHeadedOutOfCone = !vCandVel.IsZero() && velCross.z * vAimCross.z >= 0; // true if same sign float flConeLeavingUrgency; if ( bTargetHeadedOutOfCone ) { flConeLeavingUrgency = fabs( velCross.z / vCandVel.Length2D() ); // just the sin; varies 0..1 where 1 means moving perpendicular to my aim } else { flConeLeavingUrgency = 0; // not at threat of leaving just yet } // the angle between my current yaw and what's needed to hit the target float flSwivelNeeded = fabs( UTIL_AngleDiff( // i wish we weren't storing euler angles UTIL_VecToYaw( vMeToTarget ), m_fDeployYaw ) ); flSwivelNeeded /= ASW_SENTRY_ANGLE; // normalize to 0..2 float fBigness = 0.0f; int nClassify = pCandidate->Classify(); switch( nClassify ) { case CLASS_ASW_SHIELDBUG: case CLASS_ASW_MORTAR_BUG: fBigness = 4.0f; break; case CLASS_ASW_HARVESTER: case CLASS_ASW_RANGER: fBigness = 2.0f; break; } candidatescores[i] = Vector( 3.0f, -1.5f, 4.0f ).Dot( Vector( flConeLeavingUrgency, flSwivelNeeded, fBigness ) ); } // find the highest scoring candidate int best = 0; for ( int i = 1 ; i < candidatescores.Count() ; ++i ) { if ( candidatescores[i] > candidatescores[best] ) best = i; } // NDebugOverlay::EntityBounds(candidates[best], 255, 255, 0, 255, 0.2f ); return candidates[best]; }
//----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CBaseEntity *CAI_FuncTankBehavior::BestEnemy( void ) { // Only use this BestEnemy call when we are on the manned gun. if ( !m_hFuncTank ||!IsMounted() ) return BaseClass::BestEnemy(); CBaseEntity *pBestEnemy = NULL; int iBestDistSq = MAX_COORD_RANGE * MAX_COORD_RANGE; // so first visible entity will become the closest. int iBestPriority = -1000; bool bBestUnreachable = false; // Forces initial check bool bBestSeen = false; bool bUnreachable = false; int iDistSq; AIEnemiesIter_t iter; // Get the current npc for checking from. CAI_BaseNPC *pNPC = GetOuter(); if ( !pNPC ) return NULL; for( AI_EnemyInfo_t *pEMemory = GetEnemies()->GetFirst( &iter ); pEMemory != NULL; pEMemory = GetEnemies()->GetNext( &iter ) ) { CBaseEntity *pEnemy = pEMemory->hEnemy; if ( !pEnemy || !pEnemy->IsAlive() ) continue; // UNDONE: Move relationship checks into IsValidEnemy? if ( ( pEnemy->GetFlags() & FL_NOTARGET ) || ( pNPC->IRelationType( pEnemy ) != D_HT && pNPC->IRelationType( pEnemy ) != D_FR ) || !IsValidEnemy( pEnemy ) ) continue; if ( pEMemory->timeLastSeen < pNPC->GetAcceptableTimeSeenEnemy() ) continue; if ( pEMemory->timeValidEnemy > gpGlobals->curtime ) continue; // Skip enemies that have eluded me to prevent infinite loops if ( GetEnemies()->HasEludedMe( pEnemy ) ) continue; // Establish the reachability of this enemy bUnreachable = pNPC->IsUnreachable( pEnemy ); // Check view cone of the view tank here. bUnreachable = !m_hFuncTank->IsEntityInViewCone( pEnemy ); if ( !bUnreachable ) { // It's in the viewcone. Now make sure we have LOS to it. bUnreachable = !m_hFuncTank->HasLOSTo( pEnemy ); } // If best is reachable and current is unreachable, skip the unreachable enemy regardless of priority if ( !bBestUnreachable && bUnreachable ) continue; // If best is unreachable and current is reachable, always pick the current regardless of priority if ( bBestUnreachable && !bUnreachable ) { bBestSeen = ( pNPC->GetSenses()->DidSeeEntity( pEnemy ) || pNPC->FVisible( pEnemy ) ); // @TODO (toml 04-02-03): Need to optimize CanSeeEntity() so multiple calls in frame do not recalculate, rather cache iBestPriority = pNPC->IRelationPriority( pEnemy ); iBestDistSq = static_cast<int>((pEnemy->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr()); pBestEnemy = pEnemy; bBestUnreachable = bUnreachable; } // If both are unreachable or both are reachable, chose enemy based on priority and distance else if ( pNPC->IRelationPriority( pEnemy ) > iBestPriority ) { // this entity is disliked MORE than the entity that we // currently think is the best visible enemy. No need to do // a distance check, just get mad at this one for now. iBestPriority = pNPC->IRelationPriority ( pEnemy ); iBestDistSq = static_cast<int>(( pEnemy->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr()); pBestEnemy = pEnemy; bBestUnreachable = bUnreachable; } else if ( pNPC->IRelationPriority( pEnemy ) == iBestPriority ) { // this entity is disliked just as much as the entity that // we currently think is the best visible enemy, so we only // get mad at it if it is closer. iDistSq = static_cast<int>(( pEnemy->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr()); bool bCloser = ( iDistSq < iBestDistSq ) ; if ( bCloser || !bBestSeen ) { // @TODO (toml 04-02-03): Need to optimize FVisible() so multiple calls in frame do not recalculate, rather cache bool fSeen = ( pNPC->GetSenses()->DidSeeEntity( pEnemy ) || pNPC->FVisible( pEnemy ) ); if ( ( bCloser && ( fSeen || !bBestSeen ) ) || ( !bCloser && !bBestSeen && fSeen ) ) { bBestSeen = fSeen; iBestDistSq = iDistSq; iBestPriority = pNPC->IRelationPriority( pEnemy ); pBestEnemy = pEnemy; bBestUnreachable = bUnreachable; } } } } return pBestEnemy; }