#include "../../idlib/precompiled.h" #pragma hdrstop #include "../Game_local.h" #include "AI.h" #include "AI_Manager.h" idVec4 aiTeamColor[AITEAM_NUM] = { idVec4 ( 0.0f, 1.0f, 0.0f, 1.0f ), idVec4 ( 1.0f, 0.0f, 0.0f, 1.0f ) }; rvAIManager aiManager; /* ================ rvAIManager::rvAIManager ================ */ rvAIManager::rvAIManager ( void ) { Clear(); } /* ================ rvAIManager::IsActive ================ */ bool rvAIManager::IsActive( void ){ if( gameLocal.isMultiplayer ){ return false; } return true; } /* ================ rvAIManager::RunFrame ================ */ void rvAIManager::RunFrame ( void ){ if ( !IsActive() ) { return; } // Pop the top simple think off the list if ( !simpleThink.IsListEmpty() ) { simpleThink.Next()->simpleThinkNode.Remove ( ); } // Display current ai speeds if ( ai_speeds.GetBool ( ) && thinkCount > 0 ) { gameLocal.Printf ( "ai:%6i n:%2i s:%2i all:%5.2f t:%5.2f e:%5.2f m:%5.f\n", gameLocal.framenum, thinkCount, simpleThinkCount, timerThink.Milliseconds(), timerTactical.Milliseconds(), timerFindEnemy.Milliseconds(), timerMove.Milliseconds() ); } // RAVEN BEGIN // cdr: Alternate Routes Bug int i; int j; // look for reaches that are no longer blocked for ( i=0; ib.time) { break; } idEntityPtr blockent = b.blockers[j]; if (!blockent.IsValid() || blockent->DistanceTo(b.positions[j])>10.0f) { break; } if (blockent->IsType(idAI::GetClassType())) { idAI* blockentAI = static_cast(blockent.GetEntity()); if (blockentAI->move.moveDest.Dist2XY(b.positions[j])>100.0f) { break; } } } // if any one of the blockers moved or no longer exists, re-enable this reach if ( !b.blockers.Num() || jSetReachabilityState(b.reach, true); blockedReaches.RemoveIndex(i); break; } // DEBUG GRAPHICS if ( ai_debugMove.GetBool() ) { gameRenderWorld->DebugLine( colorRed, b.reach->start, b.reach->start + idVec3(0,0,40.0f), gameLocal.msec ); for ( j=0; jDebugArrow( colorRed, b.blockers[j]->GetPhysics()->GetOrigin(), b.reach->start, 8, gameLocal.msec ); } } } } // RAVEN END timerThink.Clear ( ); timerTactical.Clear ( ); timerFindEnemy.Clear ( ); timerMove.Clear ( ); gameDebug.SetStatInt( "ai_thinkCount", thinkCount ); thinkCount = 0; simpleThinkCount = 0; // Draw any debugging information DebugDraw ( ); } /* ================ rvAIManager::Clear ================ */ void rvAIManager::Clear( void ) { thinkCount = 0; simpleThinkCount = 0; timerThink.Clear ( ); timerFindEnemy.Clear ( ); timerTactical.Clear ( ); timerMove.Clear ( ); blockedReaches.Clear ( ); helpers.Clear ( ); simpleThink.Clear ( ); avoids.Clear ( ); memset ( &teamTimers, 0, sizeof(teamTimers) ); } /* ================ rvAIManager::UnMarkAllReachBlocked ================ */ void rvAIManager::UnMarkAllReachBlocked( void ) { for ( int i=0; iSetReachabilityState(blockedReaches[i].reach, true); } } /* ================ rvAIManager::ReMarkAllReachBlocked ================ */ void rvAIManager::ReMarkAllReachBlocked( void ) { for ( int i=0; iSetReachabilityState(blockedReaches[i].reach, false); } } /* ================ rvAIManager::MarkReachBlocked ================ */ void rvAIManager::MarkReachBlocked(idAAS* aas, idReachability* reach, const idList& blockers) { // only if not already blocked if (!(reach->travelType&TFL_INVALID)) { aiBlocked_t blocked; blocked.aas = aas; blocked.reach = reach; blocked.time = gameLocal.GetTime() + 5000.0f; for (int i=0; iGetPhysics() && (blockers[i]->GetPhysics()->IsAtRest() || blockers[i]->GetPhysics()->GetLinearVelocity().LengthSqr()<2500.0f)) { blocked.blockers.Append(blockers[i]); blocked.positions.Append(blockers[i]->GetPhysics()->GetOrigin()); } } if (blocked.blockers.Num()) { aas->SetReachabilityState(reach, false); blockedReaches.Append(blocked); } } } /* ================ rvAIManager::ReactToPlayerAttack ================ */ void rvAIManager::ReactToPlayerAttack ( idPlayer* player, const idVec3 &origin, const idVec3 &dir ){ idActor* actor; float expandSize; // Check all enemies and see if they need to react for ( actor = GetEnemyTeam ( (aiTeam_t)player->team ); actor; actor = actor->teamNode.Next() ) { // Skip non ai entities if ( !actor->IsType ( idAI::Type ) ) { continue; } idAI *curAI = static_cast(actor); // See if it will pass through an expanded bounding box expandSize = curAI->spawnArgs.GetFloat( "shotAtReactionRange", "16" ); if ( !curAI->GetPhysics()->GetAbsBounds ( ).Expand(expandSize).LineIntersection ( origin, origin + dir * curAI->combat.visRange ) ) { continue; } curAI->ReactToShotAt ( player, origin, dir ); } } /* ================ rvAIManager::Save ================ */ void rvAIManager::Save( idSaveGame *savefile ) const { int i; int j; // Write out team list for ( i = 0; i < AITEAM_NUM; i ++ ) { idActor* actor; savefile->WriteInt( teams[i].Num() ); for( actor = teams[i].Next(); actor != NULL; actor = actor->teamNode.Next() ) { savefile->WriteObject( actor ); } } // Write out team timers for ( i = 0; i < AITEAM_NUM; i ++ ) { for ( j = 0; j < AITEAMTIMER_MAX; j ++ ) { savefile->WriteInt ( teamTimers[i][j] ); } } // Write out team timers savefile->WriteInt ( avoids.Num ( ) ); for ( i = 0; i < avoids.Num ( ); i ++ ) { savefile->WriteVec3 ( avoids[i].origin ); savefile->WriteFloat ( avoids[i].radius ); savefile->WriteInt ( avoids[i].team ); } } /* ================ rvAIManager::Restore ================ */ void rvAIManager::Restore( idRestoreGame *savefile ){ int i; int j; Clear ( ); // Write out team list for ( i = 0; i < AITEAM_NUM; i ++ ) { idActor* actor; savefile->ReadInt( j ); for ( ; j > 0; j -- ) { savefile->ReadObject ( reinterpret_cast( actor ) ); if ( actor ) { actor->teamNode.AddToEnd ( teams[i] ); } } } // Read team timers memset ( teamTimers, 0, sizeof(teamTimers) ); for ( i = 0; i < AITEAM_NUM; i ++ ) { for ( j = 0; j < AITEAMTIMER_MAX; j ++ ) { savefile->ReadInt ( teamTimers[i][j] ); } } // Read in team timers savefile->ReadInt ( j ); avoids.SetNum ( j ); for ( i = 0; i < j; i ++ ) { savefile->ReadVec3 ( avoids[i].origin ); savefile->ReadFloat ( avoids[i].radius ); savefile->ReadInt ( avoids[i].team ); } } /* ===================== rvAIManager::AddTeammate ===================== */ void rvAIManager::AddTeammate ( idActor* actor ) { // If its already in a team least then ignore the call. // NOTE: You have to call removeteammate before addteammate to switch the // actor from one team to another if ( actor->teamNode.InList ( ) ) { return; } actor->teamNode.AddToEnd ( teams[actor->team] ); } /* ===================== rvAIManager::RemoveTeammate ===================== */ void rvAIManager::RemoveTeammate ( idActor* actor ) { actor->teamNode.Remove ( ); } /* ===================== rvAIManager::IsSimpleThink Determines whether or not the given AI entity should be simple thinking this frame ===================== */ bool rvAIManager::IsSimpleThink ( idAI* ai ) { // no simple think if simple thinking is disabled or we are the head node if ( ai_disableSimpleThink.GetBool() ) { return false; } // Add to simple think list if ( !ai->simpleThinkNode.InList ( ) ) { ai->simpleThinkNode.AddToEnd ( simpleThink ); } // If head of list then its a complex think if ( ai->simpleThinkNode.Prev() == NULL ) { return false; } return true; } /* ================ rvAIManager::GetEnemyTeam ================ */ idActor* rvAIManager::GetEnemyTeam ( aiTeam_t team ) { switch ( team ) { case AITEAM_MARINE: return teams[AITEAM_STROGG].Next(); case AITEAM_STROGG: return teams[AITEAM_MARINE].Next(); } return NULL; } /* ================ rvAIManager::GetAllyTeam ================ */ idActor* rvAIManager::GetAllyTeam ( aiTeam_t team ) { switch ( team ) { case AITEAM_MARINE: return teams[AITEAM_MARINE].Next(); case AITEAM_STROGG: return teams[AITEAM_STROGG].Next(); } return NULL; } /* ================ rvAIManager::ValidateDestination Validate whether or not the destinations is a destination ================ */ bool rvAIManager::ValidateDestination ( idAI* ai, const idVec3& dest, bool skipCurrent, idActor* skipActor ) const { int i; idBounds bounds; idAI* ignore; ignore = (!skipCurrent && ai && !ai->SkipCurrentDestination ( )) ? ai : NULL; bounds = ai->GetPhysics()->GetBounds ( ); bounds.TranslateSelf ( dest ); bounds.ExpandSelf ( 16.0f ); // All teams and all actors on those teams for ( i = 0; i < AITEAM_NUM; i ++ ) { idActor* actor; for ( actor = teams[i].Next(); actor; actor = actor->teamNode.Next() ) { // Ignored? if ( actor == ignore || actor == skipActor || actor->IsHidden ( ) ) { continue; } // If the actor is AI that is moving we should check their destination rather than where they are now if ( actor->IsType ( idAI::Type ) ) { idAI* aiactor = static_cast(actor); if ( aiactor->move.moveCommand >= NUM_NONMOVING_COMMANDS ) { if ( bounds.IntersectsBounds ( aiactor->GetPhysics()->GetBounds().Translate ( aiactor->move.moveDest ) ) ) { return false; } continue; } } // Does the destination overlap this actor? if ( bounds.IntersectsBounds ( actor->GetPhysics()->GetAbsBounds ( ) ) ) { return false; } } } // Destinations inside an avoid area are invalid for ( i = avoids.Num() - 1; i >= 0; i -- ) { const aiAvoid_t& avoid = avoids[i]; // Skip all avoids that arent meant for this team if ( avoid.team != -1 && ai->team != avoid.team ) { continue; } // Skip if within range of the avoid if ( (avoid.origin - dest).LengthSqr ( ) < Square ( avoid.radius ) ) { if ( ai_debugMove.GetBool() || ai_debugTactical.GetBool() ) { gameRenderWorld->DebugCircle( colorRed, avoid.origin, idVec3(0,0,1), avoid.radius, 25 ); } return false; } } return true; } /* ================ rvAIManager::NearestTeammateToPoint Returns the teammate closest to the given point with the given parameters ================ */ idActor* rvAIManager::NearestTeammateToPoint ( idActor* from, idVec3 point, bool nonPlayer, float maxRange, bool checkFOV, bool checkLOS ) { idActor* actor = NULL; idActor* closestActor = NULL; float distSqr; float bestDistSqr; closestActor = 0x0; bestDistSqr = Square ( maxRange ); // Iterate through all teammates for( actor = aiManager.GetAllyTeam ( (aiTeam_t)from->team ); actor; actor = actor->teamNode.Next() ) { //Hidden? if ( actor->fl.hidden ) { continue; } //Self? if ( actor == from ) { continue; } //Player? if ( nonPlayer && actor->IsType( idPlayer::GetClassType() ) ) { continue; } //Dead? if ( actor->health <= 0 ) { continue; } //Calc Range and check to see if closer before doing any complicated checks distSqr = (point - actor->GetPhysics()->GetOrigin()).LengthSqr(); if ( distSqr >= bestDistSqr ) { continue; } //point in actor's in FOV? if ( checkFOV && !actor->CheckFOV( point ) ) { continue; } //actor has clear LOS to point? if ( checkLOS && !actor->CanSeeFrom ( from->GetEyePosition ( ), point, false ) ) { continue; } // New best actor bestDistSqr = distSqr; closestActor = actor; } return closestActor; } /* ================ rvAIManager::NearestTeammateEnemy Returns the closest enemy of an ally. ================ */ idEntity* rvAIManager::NearestTeammateEnemy( idActor* from, float maxRange, bool checkFOV, bool checkLOS, idActor** closestAllyWithEnemy ) { idActor* actor; idAI* allyAI; idEntity* allyEnemy; idEntity* closestAllyEnemy; float distSqr; float bestDistSqr; bestDistSqr = Square ( maxRange ); closestAllyEnemy = NULL; // Optionally return the ally whos enemy it is if ( closestAllyWithEnemy ) { *closestAllyWithEnemy = NULL; } for( actor = aiManager.GetAllyTeam ( (aiTeam_t)from->team ); actor; actor = actor->teamNode.Next() ) { //Hidden? if ( actor->fl.hidden ) { continue; } //Self? if ( actor == from ) { continue; } //Dead? if ( actor->health <= 0 ) { continue; } allyAI = dynamic_cast(actor); if ( !allyAI ) { //player? allyEnemy = actor->EnemyWithMostHealth(); if ( !allyEnemy ) { continue; } if ( allyEnemy->health <= 0 ) { continue; } } else { allyEnemy = allyAI->GetEnemy(); //has enemy? if ( !allyEnemy ) { continue; } //still alive? if ( allyAI->enemy.fl.dead ) { continue; } } //Calc Range and check to see if closer before doing any complicated checks distSqr = (actor->GetPhysics()->GetOrigin() - from->GetPhysics()->GetOrigin()).LengthSqr ( ); if ( distSqr >= bestDistSqr ) { continue; } //point in actor's in FOV? if ( checkFOV ) { if ( !from->CheckFOV( actor->GetPhysics()->GetOrigin() ) ) { continue; } } //actor has clear LOS to point? if ( checkLOS ) { if ( !from->CanSee( actor, false ) ) { continue; } } bestDistSqr = distSqr; closestAllyEnemy = allyEnemy; if ( closestAllyWithEnemy ) { *closestAllyWithEnemy = actor; } } return closestAllyEnemy; } /* ================ rvAIManager::LocalTeamHasEnemies Returns true if nearby members of my team have any enemies or if any nearby enemies have enemies that are nearby members of my team ================ */ bool rvAIManager::LocalTeamHasEnemies ( idAI* self, float maxBuddyRange, float maxEnemyRange, bool checkPVS ) { idActor* actor = NULL; pvsHandle_t pvs; if ( !self ) { return false; } // Iterate through all teammates for( actor = aiManager.GetAllyTeam ( (aiTeam_t)self->team ); actor; actor = actor->teamNode.Next() ) { //Hidden? if ( actor->fl.hidden ) { continue; } //Dead? if ( actor->health <= 0 ) { continue; } if ( actor->IsType( idAI::GetClassType() ) ) { //Has an enemy? if ( !((idAI*)actor)->GetEnemy() ) { continue; } //Has an enemy if ( checkPVS ) { // Setup our local variables used in the search pvs = gameLocal.pvs.SetupCurrentPVS( actor->GetPVSAreas(), actor->GetNumPVSAreas() ); // If this enemy isnt in the same pvps then use them as a backup if ( pvs.i > 0 && pvs.i < MAX_CURRENT_PVS && !gameLocal.pvs.InCurrentPVS( pvs, ((idAI*)actor)->GetEnemy()->GetPVSAreas(), ((idAI*)actor)->GetEnemy()->GetNumPVSAreas() ) ) { gameLocal.pvs.FreeCurrentPVS( pvs ); continue; } gameLocal.pvs.FreeCurrentPVS( pvs ); } } //close enough? if ( actor != self && self->DistanceTo( actor->GetPhysics()->GetOrigin() ) > maxBuddyRange ) { continue; } //Anyone mad at him? if ( !actor->ClosestEnemyToPoint( actor->GetPhysics()->GetOrigin(), maxEnemyRange, true, checkPVS ) ) { continue; } return true; } return false; } /* ================ rvAIManager::ActorIsBehindActor Returns true if the given 'ambuser' is behind the given 'victim' ================ */ bool rvAIManager::ActorIsBehindActor( idActor* ambusher, idActor* victim ) { idVec3 dir2Ambusher = ambusher->GetPhysics()->GetOrigin() - victim->GetPhysics()->GetOrigin(); float dist = dir2Ambusher.Normalize(); if ( DotProduct(dir2Ambusher, victim->viewAxis[0] ) < 0 && dist < 200.0f ) { return true; } return false; } /* =============================================================================== rvAIManager - Helpers =============================================================================== */ /* ===================== rvAIManager::RegisterHelper ===================== */ void rvAIManager::RegisterHelper ( rvAIHelper* helper ) { helper->helperNode.AddToEnd ( helpers ); UpdateHelpers ( ); } /* ===================== rvAIManager::UnregisterHelper ===================== */ void rvAIManager::UnregisterHelper ( rvAIHelper* helper ) { helper->helperNode.Remove ( ); UpdateHelpers ( ); } /* ===================== rvAIManager::FindClosestHelper ===================== */ rvAIHelper* rvAIManager::FindClosestHelper ( const idVec3& origin ) { rvAIHelper* helper; rvAIHelper* bestHelper; float bestDist; float dist; bestDist = idMath::INFINITY; bestHelper = NULL; for ( helper = helpers.Next(); helper; helper = helper->helperNode.Next( ) ) { dist = (origin - helper->GetPhysics()->GetOrigin()).LengthFast ( ); if ( dist < bestDist ) { bestDist = dist; bestHelper = helper; } } return bestHelper; } /* ================ rvAIManager::UpdateHelpers ================ */ void rvAIManager::UpdateHelpers ( void ) { int i; for ( i = 0; i < AITEAM_NUM; i ++ ) { idActor* actor; for ( actor = teams[i].Next(); actor; actor = actor->teamNode.Next() ) { if ( actor->IsHidden ( ) || !actor->IsType ( idAI::Type ) ) { continue; } static_cast(actor)->UpdateHelper ( ); } } } /* =============================================================================== rvAIManager - Debugging =============================================================================== */ /* ================ rvAIManager::DebugDraw ================ */ void rvAIManager::DebugDraw ( void ) { // Draw helpers? if ( ai_debugHelpers.GetBool ( ) ) { DebugDrawHelpers ( ); int i; for ( i = 0; i < avoids.Num(); i ++ ) { const aiAvoid_t& avoid = avoids[i]; gameRenderWorld->DebugCircle ( colorOrange, avoid.origin, idVec3(0,0,1), avoid.radius, 10, 0 ); } } } /* ================ rvAIManager::DebugDrawHelpers ================ */ void rvAIManager::DebugDrawHelpers ( void ) { rvAIHelper* helper; for ( helper = helpers.Next(); helper; helper = helper->helperNode.Next( ) ) { helper->DrawDebugEntityInfo ( ); } }