sqwarmed/sdk_src/game/server/swarm/asw_squadformation.cpp

951 lines
29 KiB
C++

#include "cbase.h"
#include "asw_marine.h"
#include "asw_marine_profile.h"
#include "ai_hint.h"
#include "asw_marine_hint.h"
#include "asw_weapon_healgrenade_shared.h"
#include "asw_shieldbug.h"
#include "asw_boomer_blob.h"
#include "ai_network.h"
#include "triggers.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
ConVar asw_marine_ai_followspot( "asw_marine_ai_followspot", "0", FCVAR_CHEAT );
ConVar asw_follow_hint_max_range( "asw_follow_hint_max_range", "300", FCVAR_CHEAT );
ConVar asw_follow_hint_max_z_dist( "asw_follow_hint_max_z_dist", "120", FCVAR_CHEAT );
ConVar asw_follow_use_hints( "asw_follow_use_hints", "2", FCVAR_CHEAT, "0 = follow formation, 1 = use hints when in combat, 2 = always use hints" );
ConVar asw_follow_hint_debug( "asw_follow_hint_debug", "0", FCVAR_CHEAT );
ConVar asw_follow_velocity_predict( "asw_follow_velocity_predict", "0.3", FCVAR_CHEAT, "Marines travelling in diamond follow formation will predict their leader's movement ahead by this many seconds" );
ConVar asw_follow_threshold( "asw_follow_threshold", "40", FCVAR_CHEAT, "Marines in diamond formation will move after leader has moved this much" );
ConVar asw_squad_debug( "asw_squad_debug", "1", FCVAR_CHEAT, "Draw debug overlays for squad movement" );
#define OUT_OF_BOOMER_BOMB_RANGE FLT_MAX
void CASW_SquadFormation::LevelInitPostEntity()
{
BaseClass::LevelInitPostEntity();
CHintCriteria hintCriteria;
hintCriteria.SetHintType( HINT_FOLLOW_WAIT_POINT );
#ifdef HL2_HINTS
m_bLevelHasFollowHints = ( CAI_HintManager::FindHint( vec3_origin, hintCriteria ) != NULL );
#else
m_bLevelHasFollowHints = ( MarineHintManager()->GetHintCount() > 0 );
#endif
Msg( "Level has follow hints %d\n", m_bLevelHasFollowHints );
}
unsigned int CASW_SquadFormation::Add( CASW_Marine *pMarine )
{
AssertMsg( !!m_hLeader.Get(), "A CASW_SquadFormation has no leader!\n" );
if ( pMarine == Leader() )
{
AssertMsg1( false, "Tried to set %s to follow itself!\n", pMarine->GetMarineProfile()->GetShortName() );
return INVALID_SQUADDIE;
}
unsigned slot = Find(pMarine);
if ( IsValid( slot ) )
{
AssertMsg2( false, "Tried to double-add %s to squad (already in slot %d)\n",
pMarine->GetMarineProfile()->GetShortName(),
slot );
return slot;
}
else
{
for ( slot = 0 ; slot < MAX_SQUAD_SIZE; ++slot )
{
if ( !Squaddie(slot) )
{
m_hSquad[slot] = pMarine;
return slot;
}
}
// if we're down here, the squad is full!
AssertMsg2( false, "Tried to add %s to %s's squad, but that's full! (How?)\n",
pMarine->GetMarineProfile()->GetShortName(), m_hLeader->GetMarineProfile()->GetShortName() );
return INVALID_SQUADDIE;
}
}
bool CASW_SquadFormation::Remove( unsigned int slotnum )
{
Assert(IsValid(slotnum));
if ( Squaddie(slotnum) )
{
m_hSquad[slotnum] = NULL;
return true;
}
else
{
return false;
}
}
bool CASW_SquadFormation::Remove( CASW_Marine *pMarine, bool bIgnoreAssert )
{
unsigned slot = Find(pMarine);
if ( IsValid(slot) )
{
m_hSquad[slot] = NULL;
return true;
}
else
{
AssertMsg1( bIgnoreAssert, "Tried to remove marine %s from squad, but wasn't a member.\n",
pMarine->GetMarineProfile()->GetShortName() );
return false;
}
}
const Vector CASW_SquadFormation::s_MarineFollowOffset[MAX_SQUAD_SIZE]=
{
Vector( -60, -70, 0 ),
Vector( -120, 0, 0 ),
Vector( -60, 70, 0 )
};
const float CASW_SquadFormation::s_MarineFollowDirection[MAX_SQUAD_SIZE]=
{
-70,
180,
70
};
// position offsets when standing around a heal beacon
const Vector CASW_SquadFormation::s_MarineBeaconOffset[MAX_SQUAD_SIZE]=
{
Vector( 30, -52, 0 ),
Vector( -52, 0, 0 ),
Vector( 30, 52, 0 )
};
const float CASW_SquadFormation::s_MarineBeaconDirection[MAX_SQUAD_SIZE]=
{
-70,
180,
70
};
float CASW_SquadFormation::GetYaw( unsigned slotnum )
{
if ( m_bStandingInBeacon[slotnum] )
{
return s_MarineBeaconDirection[slotnum];
}
else if ( m_bFleeingBoomerBombs[ slotnum ] )
{
CASW_Marine *pMarine = Squaddie( slotnum );
if ( pMarine )
{
return pMarine->ASWEyeAngles()[ YAW ];
}
return 0.0f;
}
else if ( m_bLevelHasFollowHints && asw_follow_use_hints.GetBool() && Leader() && ( Leader()->IsInCombat() || asw_follow_use_hints.GetInt() == 2 ) )
{
#ifdef HL2_HINTS
if ( m_hFollowHint[ slotnum ].Get() )
{
// if ( m_bRearGuard[ slotnum ] )
// {
// return m_hFollowHint[ slotnum ]->Yaw() + 180.0f;
// }
return m_hFollowHint[ slotnum ]->Yaw();
}
#else
if ( m_nMarineHintIndex[ slotnum ] != INVALID_HINT_INDEX )
{
// if ( m_bRearGuard[ slotnum ] )
// {
// return m_hFollowHint[ slotnum ]->Yaw() + 180.0f;
// }
return MarineHintManager()->GetHintYaw( m_nMarineHintIndex[ slotnum ] );
}
#endif
else
{
return anglemod( m_flCurrentForwardAbsoluteEulerYaw + s_MarineFollowDirection[ slotnum ] );
// CASW_Marine *pMarine = Squaddie( slotnum );
// if ( pMarine )
// {
// return pMarine->ASWEyeAngles()[ YAW ];
// }
// return 0.0f;
}
}
// face our formation direction
return anglemod( m_flCurrentForwardAbsoluteEulerYaw + s_MarineFollowDirection[ slotnum ] );
}
#if 1
void CASW_SquadFormation::RecomputeFollowerOrder( const Vector &vProjectedLeaderPos, QAngle qLeaderAim ) ///< reorganize the follower slots so that each follower has the least distance to move
{
VPROF("CASW_Marine::RecomputeFollowerOrder");
//#pragma message("TODO: this algorithm should be SIMD optimized.")
// all the possible orderings of three followers ( 3! == 6 )
const static uint8 sFollowerOrderings[6][MAX_SQUAD_SIZE] =
{
{ 0, 1, 2 },
{ 0, 2, 1 },
{ 1, 0, 2 },
{ 1, 2, 0 },
{ 2, 0, 1 },
{ 2, 1, 0 }
};
// keep track of how well each order scored
float fOrderingScores[6] = { FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX };
// score each permutation in turn. (This is an experimental dirty algo,
// and will be SIMDied if we actually go this route)
qLeaderAim[ PITCH ] = 0;
matrix3x4_t matLeader;
Vector vecLeaderAim = GetLdrAnglMatrix( vProjectedLeaderPos, qLeaderAim, &matLeader );
Vector vProjectedFollowPos[ MAX_SQUAD_SIZE ];
for ( int i = 0 ; i < MAX_SQUAD_SIZE ; ++i )
{
if ( m_bLevelHasFollowHints && asw_follow_use_hints.GetBool() && m_hLeader.Get() && ( m_hLeader->IsInCombat() || asw_follow_use_hints.GetInt() == 2 ) )
{
#ifdef HL2_HINTS
if ( m_hFollowHint[i].Get() )
{
// in combat, follow positions come from nearby hint nodes
vProjectedFollowPos[i] = m_hFollowHint[i]->GetAbsOrigin();
}
#else
if ( m_nMarineHintIndex[i] != INVALID_HINT_INDEX )
{
// in combat, follow positions come from nearby hint nodes
vProjectedFollowPos[i] = MarineHintManager()->GetHintPosition( m_nMarineHintIndex[i] );
}
#endif
else
{
CASW_Marine *pMarine = Squaddie( i );
if ( pMarine )
{
vProjectedFollowPos[i] = pMarine->GetAbsOrigin();
}
}
}
else
{
// when not in combat, use our fixed follow offsets
VectorTransform( s_MarineFollowOffset[i], matLeader, vProjectedFollowPos[i] );
}
}
for ( int permute = 0 ; permute < 6 ; ++permute )
{
// each marine is scored by how far he'd have to move to get to his follow position.
// movement perpendicular to the leader's aim vector is weighed more heavily than
// movement along it. lowest score wins.
float score = 0;
for ( int follower = 0 ; follower < MAX_SQUAD_SIZE ; ++follower )
{
const int iFollowSlot = sFollowerOrderings[permute][follower];
const CASW_Marine * RESTRICT pFollower = Squaddie(follower);
if ( pFollower )
{
/*
Vector vecTransformedOffset;
VectorTransform( s_MarineFollowOffset[iFollowSlot], matLeader, vecTransformedOffset );
Vector traverse = vecTransformedOffset - pFollower->GetAbsOrigin();
*/
Vector traverse = vProjectedFollowPos[iFollowSlot] - pFollower->GetAbsOrigin();
// score += traverse.Length2D();
traverse.z = 0;
score += ( traverse + traverse - traverse.ProjectOnto(vecLeaderAim) ).Length();
}
}
fOrderingScores[ permute ] = score;
}
// once done, figure out the best scoring alternative,
int iBestPermute = 0;
for ( int ii = 1 ; ii < 6 ; ++ii )
{
iBestPermute = ( fOrderingScores[ ii ] >= fOrderingScores[ iBestPermute ] ) ? iBestPermute : ii;
}
// and use it
if ( iBestPermute != 0 )
{ // (some change is needed)
CASW_Marine * RESTRICT pOldFollowers[ MAX_SQUAD_SIZE ] = {0};
for ( int ii = 0 ; ii < MAX_SQUAD_SIZE ; ++ii ) // copy off the current array as we're about to permute it
{
pOldFollowers[ii] = m_hSquad[ii];
}
for ( int ii = 0 ; ii < MAX_SQUAD_SIZE ; ++ii )
{
m_hSquad[ii] = pOldFollowers[ sFollowerOrderings[iBestPermute][ii] ];
}
// Msg( "\n" );
if ( asw_marine_ai_followspot.GetBool() )
{
static float colors[3][3] = { { 255, 127, 127 }, { 127, 255, 127 }, { 127, 127, 255 } };
for ( int ii = 0 ; ii < MAX_SQUAD_SIZE ; ++ii )
{
NDebugOverlay::HorzArrow( m_hSquad[ii]->GetAbsOrigin(), m_vFollowPositions[ii], 3,
colors[ii][0], colors[ii][1], colors[ii][2], 196, false, 0.35f );
}
}
}
}
#endif
Vector CASW_SquadFormation::GetLdrAnglMatrix( const Vector &origin, const QAngle &ang, matrix3x4_t * RESTRICT pout ) RESTRICT
{
Vector vecLeaderAim;
{
// "forward" is actual movement velocity if available, facing otherwise
float leaderVelSq = m_vLastLeaderVelocity.LengthSqr();
if ( leaderVelSq > 1.0f )
{
vecLeaderAim = m_vLastLeaderVelocity * FastRSqrtFast(leaderVelSq);
VectorMatrix( vecLeaderAim, *pout );
pout->SetOrigin( origin );
}
else
{
AngleMatrix( ang, origin, *pout );
MatrixGetColumn( *pout, 0, vecLeaderAim );
}
}
m_vCachedForward = vecLeaderAim;
if (m_vCachedForward[1] == 0 && m_vCachedForward[0] == 0)
{
m_flCurrentForwardAbsoluteEulerYaw = 0;
}
else
{
m_flCurrentForwardAbsoluteEulerYaw = (atan2(vecLeaderAim[1], vecLeaderAim[0]) * (180.0 / M_PI));
m_flCurrentForwardAbsoluteEulerYaw =
fsel( m_flCurrentForwardAbsoluteEulerYaw, m_flCurrentForwardAbsoluteEulerYaw, m_flCurrentForwardAbsoluteEulerYaw + 360 );
}
return vecLeaderAim;
}
// returns OUT_OF_BOOMER_BOMB_RANGE if not within blast radius of any boomer bomb
float GetClosestBoomerBlobDistSqr( const Vector &vecPosition )
{
float flClosestBoomerBlobDistSqr = OUT_OF_BOOMER_BOMB_RANGE;
for( int iBoomerBlob = 0; iBoomerBlob < g_aExplosiveProjectiles.Count(); iBoomerBlob++ )
{
CBaseEntity *pExplosive = g_aExplosiveProjectiles[ iBoomerBlob ];
const float flExplosiveRadius = 240.0f; // bad hardcoded to match boomer blob radius
float flDistSqr = pExplosive->GetAbsOrigin().DistToSqr( vecPosition );
if( flDistSqr < Square( flExplosiveRadius ) && flDistSqr < flClosestBoomerBlobDistSqr )
{
flClosestBoomerBlobDistSqr = flDistSqr;
}
}
return flClosestBoomerBlobDistSqr;
}
bool WithinBoomerBombRadius( const Vector &vecPosition )
{
return ( GetClosestBoomerBlobDistSqr( vecPosition ) < OUT_OF_BOOMER_BOMB_RANGE );
}
void CASW_SquadFormation::UpdateFollowPositions()
{
VPROF("CASW_SquadFormation::UpdateFollowPositions");
CASW_Marine * RESTRICT pLeader = Leader();
if ( !pLeader )
{
AssertMsg1(false, "Tried to update positions for a squad with no leader and %d followers.\n",
Count() );
return;
}
m_flLastSquadUpdateTime = gpGlobals->curtime;
if ( m_bLevelHasFollowHints && asw_follow_use_hints.GetBool() && ( pLeader->IsInCombat() || asw_follow_use_hints.GetInt() ) )
{
FindFollowHintNodes();
}
QAngle angLeaderFacing = pLeader->EyeAngles();
angLeaderFacing[PITCH] = 0;
matrix3x4_t matLeaderFacing;
Vector vProjectedLeaderPos = pLeader->GetAbsOrigin() + pLeader->GetAbsVelocity() * asw_follow_velocity_predict.GetFloat();
GetLdrAnglMatrix( vProjectedLeaderPos, angLeaderFacing, &matLeaderFacing );
for ( int i = 0 ; i < MAX_SQUAD_SIZE ; ++i )
{
CASW_Marine *pMarine = Squaddie( i );
if ( !pMarine )
continue;
m_bStandingInBeacon[i] = false;
m_bFleeingBoomerBombs[ i ] = false;
CBaseEntity *pBeaconToStandIn = NULL;
// check for nearby heal beacons
if ( IHealGrenadeAutoList::AutoList().Count() > 0 && pMarine->GetHealth() < pMarine->GetMaxHealth() )
{
const float flHealGrenadeDetectionRadius = 600.0f;
for ( int g = 0; g < IHealGrenadeAutoList::AutoList().Count(); ++g )
{
const CUtlVector< IHealGrenadeAutoList* > &grenades = IHealGrenadeAutoList::AutoList();
CBaseEntity *pBeacon = grenades[g]->GetEntity();
if ( pBeacon && pBeacon->GetAbsOrigin().DistTo( pMarine->GetAbsOrigin() ) < flHealGrenadeDetectionRadius )
{
pBeaconToStandIn = pBeacon;
break;
}
}
}
if ( pBeaconToStandIn )
{
m_vFollowPositions[i] = pBeaconToStandIn->GetAbsOrigin() + s_MarineBeaconOffset[i];
m_bStandingInBeacon[i] = true;
}
else if( g_aExplosiveProjectiles.Count() )
{
bool bSafeNodeFound = false;
bool bUnsafeNodeFound = false;
float flClosestSafeNodeDistSqr = FLT_MAX;
float flBestUnsafeNodeDistSqr = 0.0f;
Vector vecClosestSafeNode;
Vector vecBestUnsafeNode;
const float k_flMaxSearchDistance = 300.0f;
for( int iNode = 0; iNode < pMarine->GetNavigator()->GetNetwork()->NumNodes(); iNode++ )
{
CAI_Node *pAiNode = pMarine->GetNavigator()->GetNetwork()->GetNode( iNode );
Vector vecNodeLocation = pAiNode->GetOrigin();
if( vecNodeLocation.DistToSqr( pMarine->GetAbsOrigin() ) > Square( k_flMaxSearchDistance ) )
continue;
bool bNodeTaken = false;
for( int iSlot = 0; iSlot < MAX_SQUAD_SIZE; iSlot++ )
{
if ( iSlot != i && vecNodeLocation.DistToSqr( m_vFollowPositions[ iSlot ] ) < Square( 30.0f ) ) // don't let marines get too close, even if nodes are close
{
bNodeTaken= true;
break;
}
}
if( !bNodeTaken )
{
float flClosestBoomerBlobDistSqr = GetClosestBoomerBlobDistSqr( vecNodeLocation );
if( flClosestBoomerBlobDistSqr == OUT_OF_BOOMER_BOMB_RANGE )
{
// if closer than the previous closest node, and the current node isn't taken, reserve it
float flSafeNodeDistSqr = vecNodeLocation.DistToSqr( pMarine->GetAbsOrigin() );
if ( flSafeNodeDistSqr < flClosestSafeNodeDistSqr )
{
flClosestSafeNodeDistSqr = flSafeNodeDistSqr;
bSafeNodeFound = true;
vecClosestSafeNode = vecNodeLocation;
}
}
else
{
if( flClosestBoomerBlobDistSqr > flBestUnsafeNodeDistSqr )
{
flBestUnsafeNodeDistSqr = flClosestBoomerBlobDistSqr;
bUnsafeNodeFound = true;
vecBestUnsafeNode = vecNodeLocation;
}
}
}
}
if( bSafeNodeFound )
{
m_vFollowPositions[ i ] = vecClosestSafeNode;
m_bFleeingBoomerBombs[ i ] = true;
}
else if( bUnsafeNodeFound )
{
m_vFollowPositions[ i ] = vecBestUnsafeNode;
m_bFleeingBoomerBombs[ i ] = true;
}
}
else if ( m_bLevelHasFollowHints && asw_follow_use_hints.GetBool() && ( pLeader->IsInCombat() || asw_follow_use_hints.GetInt() == 2 ) )
{
#ifdef HL2_HINTS
if ( m_hFollowHint[i].Get() )
{
m_vFollowPositions[i] = m_hFollowHint[i]->GetAbsOrigin();
}
#else
if ( m_nMarineHintIndex[i] != INVALID_HINT_INDEX )
{
m_vFollowPositions[i] = MarineHintManager()->GetHintPosition( m_nMarineHintIndex[i] );
}
#endif
else
{
if ( pMarine )
{
m_vFollowPositions[i] = pMarine->GetAbsOrigin();
}
}
}
else
{
VectorTransform( s_MarineFollowOffset[i], matLeaderFacing, m_vFollowPositions[i] );
}
if ( asw_marine_ai_followspot.GetBool() )
{
static float colors[3][3] = { { 255, 64, 64 }, { 64, 255, 64 }, { 64, 64, 255 } };
NDebugOverlay::HorzArrow( pLeader->GetAbsOrigin(), m_vFollowPositions[i], 3,
colors[i][0], colors[i][1], colors[i][2], 255, false, 0.35f );
}
}
m_flLastLeaderYaw = pLeader->EyeAngles()[ YAW ];
m_vLastLeaderPos = pLeader->GetAbsOrigin();
m_vLastLeaderVelocity = pLeader->GetAbsVelocity();
}
bool CASW_SquadFormation::SanityCheck() const
{
CASW_Marine *pLeader = Leader();
Assert( pLeader );
if ( !pLeader )
return false;
// for each slot, make sure no dups, and that leader is not following self
for ( int testee = 0 ; testee < MAX_SQUAD_SIZE ; ++testee )
{
CASW_Marine *pTest = m_hSquad[testee];
Assert( pLeader != pTest ); // am not leader
if ( pLeader == pTest )
return false;
if ( pTest )
{
for ( int i = (testee+1)%MAX_SQUAD_SIZE ; i != testee ; i = (i + 1)%MAX_SQUAD_SIZE )
{
Assert( m_hSquad[i] != pTest ); // am not in array twice
if ( m_hSquad[i] == pTest )
return false;
}
}
}
return true;
}
// For such a tiny array, a linear search is actually the fastest
// way to go
unsigned int CASW_SquadFormation::Find( CASW_Marine *pMarine ) const
{
Assert( pMarine != Leader() );
for ( int i = 0 ; i < MAX_SQUAD_SIZE ; ++i )
{
if ( m_hSquad[i] == pMarine )
return i;
}
return INVALID_SQUADDIE;
}
void CASW_SquadFormation::ChangeLeader( CASW_Marine *pNewLeader, bool bUpdateLeaderPos )
{
// if the squad has no leader, become the leader
CASW_Marine *pOldLeader = Leader();
if ( !pOldLeader )
{
Leader( pNewLeader );
return;
}
// if we're trying to wipe out the leader, do so if there are no followers
if ( !pNewLeader )
{
AssertMsg2( Count() == 0, "Tried to unset squad leader %s, but squad has %d followers\n",
pNewLeader->GetMarineProfile()->GetShortName(), Count() );
Leader(NULL);
return;
}
if ( pOldLeader == pNewLeader )
{
AssertMsg1( false, "Tried to reset squad leader to its current value (%s)\n", pNewLeader->GetMarineProfile()->GetShortName() );
return;
}
// if the new leader was previously a follower, swap with the old leader
int slot = Find( pNewLeader );
if ( IsValid(slot) )
{
m_hSquad[slot] = pOldLeader;
Leader( pNewLeader );
}
else
{
// make the old leader a follower
Leader( pNewLeader );
Add( pOldLeader );
}
if ( bUpdateLeaderPos )
{
m_flLastLeaderYaw = pNewLeader->EyeAngles()[ YAW ];
m_vLastLeaderPos = pNewLeader->GetAbsOrigin();
m_vLastLeaderVelocity = pNewLeader->GetAbsVelocity();
m_flLastSquadUpdateTime = gpGlobals->curtime;
}
else
{
m_flLastSquadUpdateTime = 0;
}
}
void CASW_SquadFormation::LevelInitPreEntity()
{
Reset();
}
void CASW_SquadFormation::Reset()
{
m_hLeader = NULL;
for ( int i = 0 ; i < MAX_SQUAD_SIZE ; ++i )
{
m_hSquad[i] = NULL;
m_bRearGuard[i] = false;
m_bStandingInBeacon[i] = false;
m_bFleeingBoomerBombs[ i ] = false;
#ifdef HL2_HINTS
m_hFollowHint[i] = NULL;
#else
m_nMarineHintIndex[i] = INVALID_HINT_INDEX;
#endif
}
m_flLastSquadUpdateTime = 0;
m_bLevelHasFollowHints = false;
m_vLastLeaderVelocity.Zero();
}
//-----------------------------------------------------------------------------
// Purpose: Sorts AI nodes by proximity to leader
//-----------------------------------------------------------------------------
CASW_Marine *g_pSortLeader = NULL;
#ifdef HL2_HINTS
int CASW_SquadFormation::FollowHintSortFunc( CAI_Hint* const *pHint1, CAI_Hint* const *pHint2 )
#else
int CASW_SquadFormation::FollowHintSortFunc( HintData_t* const *pHint1, HintData_t* const *pHint2 )
#endif
{
int nDist1 = (int) (*pHint1)->GetAbsOrigin().DistToSqr( g_pSortLeader->GetAbsOrigin() );
int nDist2 = (int) (*pHint2)->GetAbsOrigin().DistToSqr( g_pSortLeader->GetAbsOrigin() );
return ( nDist1 - nDist2 );
}
//-----------------------------------------------------------------------------
// Purpose: Finds the set of hint nodes to use when following during combat
//-----------------------------------------------------------------------------
void CASW_SquadFormation::FindFollowHintNodes()
{
CASW_Marine *pLeader = Leader();
if ( !pLeader )
return;
// evaluate each squaddie individually to see if his node should be updated
for ( int slotnum = 0; slotnum < MAX_SQUAD_SIZE; slotnum++ )
{
CASW_Marine *pMarine = Squaddie( slotnum );
if ( !pMarine )
{
#ifdef HL2_HINTS
m_hFollowHint[slotnum] = NULL;
#else
m_nMarineHintIndex[slotnum] = INVALID_HINT_INDEX;
#endif
continue;
}
bool bNeedNewNode = ( pMarine->GetAbsOrigin().DistTo( pLeader->GetAbsOrigin() ) > asw_follow_hint_max_range.GetFloat() );
if ( !bNeedNewNode )
{
bNeedNewNode = !pMarine->FVisible( pLeader );
}
// find shield bug (if any) nearest each marine
const float k_flShieldbugScanRangeSqr = 400.0f * 400.0f;
CASW_Shieldbug *pClosestShieldbug = NULL;
float flClosestShieldBugDistSqr = k_flShieldbugScanRangeSqr;
if ( pMarine->IsAlive() )
{
for( int iShieldbug = 0; iShieldbug < IShieldbugAutoList::AutoList().Count(); iShieldbug++ )
{
CASW_Shieldbug *pShieldbug = static_cast< CASW_Shieldbug* >( IShieldbugAutoList::AutoList()[ iShieldbug ] );
if( pShieldbug && pShieldbug->IsAlive() )
{
float flDistSqr = pMarine->GetAbsOrigin().DistToSqr( pShieldbug->GetAbsOrigin() );
if( flDistSqr < flClosestShieldBugDistSqr )
{
flClosestShieldBugDistSqr = flDistSqr;
pClosestShieldbug = pShieldbug;
}
}
}
}
if ( !bNeedNewNode && !pClosestShieldbug )
continue;
// find a new node
#ifdef HL2_HINTS
CHintCriteria hintCriteria;
hintCriteria.SetHintType( HINT_FOLLOW_WAIT_POINT );
hintCriteria.AddIncludePosition( pLeader->GetAbsOrigin(), asw_follow_hint_max_range.GetFloat() );
hintCriteria.AddExcludePosition( pLeader->GetAbsOrigin(), 80.0f );
CUtlVector< CAI_Hint * > hints;
CAI_HintManager::FindAllHints( pLeader, pLeader->GetAbsOrigin(), hintCriteria, &hints );
#else
CUtlVector< HintData_t* > hints;
MarineHintManager()->FindHints( pLeader->GetAbsOrigin(), 80.0f, asw_follow_hint_max_range.GetFloat(), &hints );
#endif
int nCount = hints.Count();
float flMovementYaw = pLeader->GetOverallMovementDirection();
int iClosestFlankingNode = INVALID_HINT_INDEX;
float flClosestFlankingNodeDistSqr = FLT_MAX;
CBaseTrigger *pEscapeVolume = pLeader->IsInEscapeVolume();
if ( pEscapeVolume && pEscapeVolume->CollisionProp() )
{
// remove hints that aren't in the escape volume bounds
for ( int i = nCount - 1; i >= 0; i-- )
{
if ( !pEscapeVolume->CollisionProp()->IsPointInBounds( hints[ i ]->GetAbsOrigin() ) )
{
hints.Remove( i );
nCount--;
}
else if ( asw_follow_hint_debug.GetBool() )
{
NDebugOverlay::Box( hints[ i ]->GetAbsOrigin(), -Vector( 5, 5, 0 ), Vector( 5, 5, 5 ), 255, 0, 0, 255, 3.0f );
}
}
}
else
{
// remove hints that are in front of the leader's overall direction of movement
// TODO: turn this into a hint filter
for ( int i = nCount - 1; i >= 0; i-- )
{
Vector vecDir = ( hints[ i ]->GetAbsOrigin() - pLeader->GetAbsOrigin() ).Normalized();
float flYaw = UTIL_VecToYaw( vecDir );
flYaw = AngleDiff( flYaw, flMovementYaw );
bool bRemoveNode = false;
if ( flYaw < 85.0f && flYaw > -85.0f )
{
bRemoveNode = true;
// remove hints that are in front of the leader's overall direction of movement,
// unless we need to use them to get the AI to flank a shieldbug
if( pClosestShieldbug )
{
// if any of the marines are close, don't delete nodes behind the shieldbug
float flShieldbugDistSqr = hints[ i ]->GetAbsOrigin().DistToSqr( pClosestShieldbug->GetAbsOrigin() );
if( flShieldbugDistSqr < k_flShieldbugScanRangeSqr )
{
// preserve the node if it's behind the shieldbug
Vector vecShieldBugToNode, vecShieldbugFacing;
vecShieldBugToNode = hints[ i ]->GetAbsOrigin() - pClosestShieldbug->GetAbsOrigin();
QAngle angFacing = pClosestShieldbug->GetAbsAngles();
AngleVectors( angFacing, &vecShieldbugFacing );
vecShieldbugFacing.z = 0;
vecShieldBugToNode.z = 0;
VectorNormalize( vecShieldbugFacing );
VectorNormalize( vecShieldBugToNode );
float flForwardDot = vecShieldbugFacing.Dot( vecShieldBugToNode );
if( flForwardDot < 0.5f ) // if node is 60 degrees or more away from shieldbug's facing...
{
float flDistSqr = hints[ i ]->GetAbsOrigin().DistToSqr( pMarine->GetAbsOrigin() );
bool bHasLOS = pMarine->TestShootPosition( pMarine->GetAbsOrigin(), hints[ i ]->GetAbsOrigin() );
// if closer than the previous closest node, and the current node isn't taken, reserve it
if( flDistSqr < flClosestFlankingNodeDistSqr && bHasLOS )
{
bool flNodeTaken = false;
for( int iSlot = 0; iSlot < MAX_SQUAD_SIZE; iSlot++ )
{
#ifdef HL2_HINTS
if ( iSlot != slotnum && hints[ i ] == m_hFollowHint[ iSlot ].Get() )
#else
if ( iSlot != slotnum && hints[ i ]->m_nHintIndex == m_nMarineHintIndex[ iSlot ] )
#endif
{
flNodeTaken = true;
break;
}
}
if( !flNodeTaken )
{
iClosestFlankingNode = hints[ i ]->m_nHintIndex;
flClosestFlankingNodeDistSqr = flDistSqr;
bRemoveNode = false;
}
}
}
}
}
}
// if zdiff is too great, remove
float flZDiff = fabs( hints[ i ]->GetAbsOrigin().z - pLeader->GetAbsOrigin().z );
if ( flZDiff > asw_follow_hint_max_z_dist.GetFloat() )
{
bRemoveNode = true;
}
if( bRemoveNode )
{
hints.Remove( i );
nCount--;
}
}
}
g_pSortLeader = pLeader;
hints.Sort( CASW_SquadFormation::FollowHintSortFunc );
// if this marine is close to a shield bug, grab a flanking node
if( !pEscapeVolume && pClosestShieldbug && iClosestFlankingNode != INVALID_HINT_INDEX )
{
m_nMarineHintIndex[ slotnum ] = iClosestFlankingNode;
continue;
}
// find the first node not used by another other squaddie
int nNode = 0;
for ( int i = 0; i < MAX_SQUAD_SIZE; i++ )
{
bool bValidNode = true;
while ( nNode < hints.Count() )
{
for ( int k = 0; k < MAX_SQUAD_SIZE; k++ )
{
#ifdef HL2_HINTS
if ( k != slotnum && hints[ nNode ] == m_hFollowHint[k].Get() )
#else
if ( k != slotnum && hints[ nNode ]->m_nHintIndex == m_nMarineHintIndex[ k ] )
#endif
{
bValidNode = false;
break;
}
}
if ( bValidNode )
{
#ifdef HL2_HINTS
m_hFollowHint[ slotnum ] = hints[ nNode ];
#else
m_nMarineHintIndex[ slotnum ] = hints[ nNode ]->m_nHintIndex;
#endif
nNode++;
break;
}
nNode++;
}
}
}
}
bool CASW_SquadFormation::ShouldUpdateFollowPositions() const
{
// update if a heal beacon was placed/removed
if ( m_iLastHealBeaconCount != IHealGrenadeAutoList::AutoList().Count() )
return true;
bool bBoomerBombs = g_aExplosiveProjectiles.Count() > 0;
// leader's more than epsilon from previous position,
// and we haven't updated in a quarter second.
// force a reupdate if leader's velocity has changed by 50% or more or boomer bombs are deployed
return ( Leader() &&
( gpGlobals->curtime > m_flLastSquadUpdateTime + 0.33f ||
( Leader()->GetAbsVelocity() - m_vLastLeaderVelocity ).LengthSqr() * FastRecip(m_vLastLeaderVelocity.LengthSqr() ) > ( 0.5f * 0.5f ) ||
( Leader()->GetAbsVelocity().IsZeroFast() && !m_vLastLeaderVelocity.IsZeroFast() ) ) &&
( Leader()->GetAbsOrigin().DistToSqr(m_vLastLeaderPos) > Sqr(asw_follow_threshold.GetFloat()) || bBoomerBombs ) );
}
void CASW_SquadFormation::DrawDebugGeometryOverlays()
{
if ( !asw_squad_debug.GetBool() )
return;
CASW_Marine *pLeader = Leader();
if ( !pLeader )
return;
for ( int i = 0; i < MAX_SQUAD_SIZE; i++ )
{
CASW_Marine *pMarine = Squaddie( i );
if ( pMarine )
{
NDebugOverlay::Line( pMarine->WorldSpaceCenter(), pLeader->WorldSpaceCenter(), 63, 63, 63, false, 0.05f );
#ifdef HL2_HINTS
if ( m_hFollowHint[ i ].Get() )
{
NDebugOverlay::Line( pMarine->WorldSpaceCenter(), m_hFollowHint[ i ]->GetAbsOrigin(), 255, 255, 63, false, 0.05f );
NDebugOverlay::EntityText( pMarine->entindex(), i * 2,
CFmtStr( "Node: %d wc: %d node: %d pos: %f %f %f",
m_hFollowHint[ i ]->GetNodeId(),
m_hFollowHint[i]->GetWCId(),
m_hFollowHint[i]->GetNode() ? m_hFollowHint[i]->GetNode()->GetId() : -1,
m_hFollowHint[i]->GetAbsOrigin().x,
m_hFollowHint[i]->GetAbsOrigin().y,
m_hFollowHint[i]->GetAbsOrigin().z ),
0.05f, 255, 255, 255, 255 );
}
#endif
}
}
/*
int max_marines = ASWGameResource()->GetMaxMarineResources();
for ( int i=0;i<max_marines;i++ )
{
CASW_Marine_Resource* pMR = ASWGameResource()->GetMarineResource( i );
if ( !pMR )
continue;
CASW_Marine *pMarine = pMR->GetMarineEntity();
if ( !pMarine )
continue;
NDebugOverlay::EntityText( pMarine->entindex(), 0, CFmtStr( "Squad slot: %d", p ), 0.05f, 255, 255, 255, 255 );
}
*/
}
CASW_SquadFormation g_ASWSquadFormation;