summaryrefslogtreecommitdiff
path: root/game/server/tf/raid/tf_raid_logic.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'game/server/tf/raid/tf_raid_logic.cpp')
-rw-r--r--game/server/tf/raid/tf_raid_logic.cpp2255
1 files changed, 2255 insertions, 0 deletions
diff --git a/game/server/tf/raid/tf_raid_logic.cpp b/game/server/tf/raid/tf_raid_logic.cpp
new file mode 100644
index 0000000..381508c
--- /dev/null
+++ b/game/server/tf/raid/tf_raid_logic.cpp
@@ -0,0 +1,2255 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// tf_raid_logic.cpp
+// Raid game mode singleton manager
+// Michael Booth, November 2009
+
+#include "cbase.h"
+
+#ifdef TF_RAID_MODE
+
+#include "team.h"
+#include "nav_pathfind.h"
+#include "tf_gamerules.h"
+#include "team_control_point_master.h"
+#include "bot/tf_bot.h"
+#include "nav_mesh/tf_nav_mesh.h"
+#include "raid/tf_raid_logic.h"
+#include "bot_npc/bot_npc_minion.h"
+#include "tf_obj_sentrygun.h"
+#include "filesystem.h"
+
+#include "func_respawnroom.h"
+#include "pathtrack.h"
+
+extern ConVar mp_teams_unbalance_limit;
+extern ConVar mp_autoteambalance;
+extern ConVar sv_alltalk;
+extern ConVar mp_timelimit;
+
+CRaidLogic *g_pRaidLogic = NULL;
+
+ConVar tf_debug_sniper_spots( "tf_debug_sniper_spots", "0"/*, FCVAR_CHEAT*/ );
+
+
+ConVar tf_raid_max_wanderers( "tf_raid_max_wanderers", "10"/*, FCVAR_CHEAT*/ );
+ConVar tf_raid_max_defense_engineers( "tf_raid_max_defense_engineers", "6"/*, FCVAR_CHEAT*/ );
+ConVar tf_raid_max_defense_demomen( "tf_raid_max_defense_demomen", "1"/*, FCVAR_CHEAT*/ );
+ConVar tf_raid_max_defense_heavies( "tf_raid_max_defense_heavies", "1"/*, FCVAR_CHEAT*/ );
+ConVar tf_raid_max_defense_soldiers( "tf_raid_max_defense_soldiers", "1"/*, FCVAR_CHEAT*/ );
+ConVar tf_raid_max_defense_pyros( "tf_raid_max_defense_pyros", "1"/*, FCVAR_CHEAT*/ );
+ConVar tf_raid_max_defense_spies( "tf_raid_max_defense_spies", "1"/*, FCVAR_CHEAT*/ );
+ConVar tf_raid_max_defense_snipers( "tf_raid_max_defense_snipers", "3"/*, FCVAR_CHEAT*/ );
+ConVar tf_raid_max_defense_squads( "tf_raid_max_defense_squads", "1"/*, FCVAR_CHEAT*/ );
+
+
+ConVar tf_raid_wandering_density( "tf_raid_wandering_density", "0.00001", FCVAR_CHEAT, "Wandering defenders per unit area" );
+ConVar tf_raid_spawn_wanderers( "tf_raid_spawn_wanderers", "1"/*, FCVAR_CHEAT*/ );
+
+ConVar tf_raid_defender_density( "tf_raid_defender_density", "0.000001", 0/*FCVAR_CHEAT*/, "Wandering defenders per unit area" );
+ConVar tf_raid_max_defender_count( "tf_raid_max_defender_count", "18", 0/*FCVAR_CHEAT*/ );
+ConVar tf_raid_spawn_defenders( "tf_raid_spawn_defenders", "1"/*, FCVAR_CHEAT*/ );
+
+
+ConVar tf_raid_sentry_density( "tf_raid_sentry_density", "0.0000005", 0/*FCVAR_CHEAT*/, "Sentry guns per unit area" );
+ConVar tf_raid_sentry_spacing( "tf_raid_sentry_spacing", "750", 0/*FCVAR_CHEAT*/, "Minimum travel distance between sentry gun spots" );
+ConVar tf_raid_debug_sentry_placement( "tf_raid_debug_sentry_placement", "0"/*, FCVAR_CHEAT*/ );
+ConVar tf_raid_spawn_sentries( "tf_raid_spawn_sentries", "1"/*, FCVAR_CHEAT*/ );
+ConVar tf_raid_spawn_engineers( "tf_raid_spawn_engineers", "0"/*, FCVAR_CHEAT*/ );
+ConVar tf_raid_engineer_spawn_interval( "tf_raid_engineer_spawn_interval", "20"/*, FCVAR_CHEAT*/ );
+
+ConVar tf_raid_mob_spawn_min_interval( "tf_raid_mob_spawn_min_interval", "60"/*, FCVAR_CHEAT*/ );
+ConVar tf_raid_mob_spawn_max_interval( "tf_raid_mob_spawn_max_interval", "90"/*, FCVAR_CHEAT*/ );
+ConVar tf_raid_mob_spawn_count( "tf_raid_mob_spawn_count", "15"/*, FCVAR_CHEAT*/ );
+ConVar tf_raid_mob_spawn_below_tolerance( "tf_raid_mob_spawn_below_tolerance", "150"/*, FCVAR_CHEAT*/ );
+ConVar tf_raid_mob_spawn_min_range( "tf_raid_mob_spawn_min_range", "1000"/*, FCVAR_CHEAT*/ );
+ConVar tf_raid_spawn_mobs( "tf_raid_spawn_mobs", "1"/*, FCVAR_CHEAT*/ );
+
+ConVar tf_raid_spawn_mob_as_squad_chance_start( "tf_raid_spawn_mob_as_squad_chance_start", "100" ); // /*, FCVAR_CHEAT*/ );
+ConVar tf_raid_spawn_mob_as_squad_chance_halfway( "tf_raid_spawn_mob_as_squad_chance_halfway", "100" ); // /*, FCVAR_CHEAT*/ );
+ConVar tf_raid_spawn_mob_as_squad_chance_final( "tf_raid_spawn_mob_as_squad_chance_final", "100" ); // /*, FCVAR_CHEAT*/ );
+ConVar tf_raid_squad_medic_intro_percent( "tf_raid_squad_medic_intro_percent", "0.5" ); // /*, FCVAR_CHEAT*/ );
+
+
+ConVar tf_raid_capture_mob_interval( "tf_raid_capture_mob_interval", "20"/*, FCVAR_CHEAT*/ );
+
+ConVar tf_raid_special_spawn_min_interval( "tf_raid_special_spawn_min_interval", "20"/*, FCVAR_CHEAT*/ );
+ConVar tf_raid_special_spawn_max_interval( "tf_raid_special_spawn_max_interval", "30"/*, FCVAR_CHEAT*/ );
+ConVar tf_raid_spawn_specials( "tf_raid_spawn_specials", "0"/*, FCVAR_CHEAT*/ );
+
+ConVar tf_raid_sniper_spawn_ahead_incursion( "tf_raid_sniper_spawn_ahead_incursion", "6000"/*, FCVAR_CHEAT*/ );
+ConVar tf_raid_sniper_spawn_behind_incursion( "tf_raid_sniper_spawn_behind_incursion", "6000"/*, FCVAR_CHEAT*/ );
+ConVar tf_raid_sniper_spawn_max_range( "tf_raid_sniper_spawn_max_range", "6000"/*, FCVAR_CHEAT*/ );
+ConVar tf_raid_sniper_spawn_min_range( "tf_raid_sniper_spawn_min_range", "1000"/*, FCVAR_CHEAT*/ );
+ConVar tf_raid_show_escape_route( "tf_raid_show_escape_route", "0"/*, FCVAR_CHEAT*/ );
+
+ConVar tf_raid_sentry_build_ahead_incursion( "tf_raid_sentry_build_ahead_incursion", "5000"/*, FCVAR_CHEAT*/ );
+ConVar tf_raid_sentry_build_behind_incursion( "tf_raid_sentry_build_behind_incursion", "-1000"/*, FCVAR_CHEAT*/ );
+
+ConVar tf_raid_debug( "tf_raid_debug", "0"/*, FCVAR_CHEAT*/ );
+ConVar tf_raid_debug_escape_route( "tf_raid_debug_escape_route", "0"/*, FCVAR_CHEAT*/ );
+ConVar tf_raid_debug_director( "tf_raid_debug_director", "0"/*, FCVAR_CHEAT*/ );
+
+ConVar tf_raid_spawn_enable( "tf_raid_spawn_enable", "1"/*, FCVAR_CHEAT*/ );
+
+extern ConVar tf_populator_active_buffer_range;
+
+
+extern bool IsSpaceToSpawnHere( const Vector &where );
+
+
+//--------------------------------------------------------------------------------------------------------
+// Check actual line of sight to team
+bool IsPlayerVisibleToTeam( CTFPlayer *subject, int teamIndex )
+{
+ CTeam *team = GetGlobalTeam( teamIndex );
+ for( int t=0; t<team->GetNumPlayers(); ++t )
+ {
+ CTFPlayer *teamMember = (CTFPlayer *)team->GetPlayer(t);
+
+ if ( !teamMember->IsAlive() )
+ continue;
+
+ CTFBot *bot = ToTFBot( teamMember );
+ if ( bot && bot->HasAttribute( CTFBot::IS_NPC ) )
+ continue;
+
+ if ( teamMember->IsInFieldOfView( subject->EyePosition() ) )
+ {
+ if ( teamMember->IsLineOfSightClear( subject, CBaseCombatCharacter::IGNORE_ACTORS ) )
+ {
+ // visible to team
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+
+
+//--------------------------------------------------------------------------------------------------------------
+int GetAvailableRedSpawnSlots( void )
+{
+ int available = 0;
+
+ // count dead bots we can re-use
+ CTeam *deadTeam = GetGlobalTeam( TEAM_SPECTATOR );
+ for( int i=0; i<deadTeam->GetNumPlayers(); ++i )
+ {
+ if ( !deadTeam->GetPlayer(i)->IsBot() )
+ continue;
+
+ // reuse this guy
+ ++available;
+ }
+
+ // count unused player slots
+ int totalPlayerCount = 0;
+ totalPlayerCount += GetGlobalTeam( TEAM_SPECTATOR )->GetNumPlayers();
+ totalPlayerCount += 4; // always leave room for 4 blue players
+ totalPlayerCount += GetGlobalTeam( TF_TEAM_RED )->GetNumPlayers();
+
+ available += gpGlobals->maxClients - totalPlayerCount;
+
+ return available;
+}
+
+
+//-------------------------------------------------------------------------
+//-------------------------------------------------------------------------
+BEGIN_DATADESC( CRaidLogic )
+ DEFINE_THINKFUNC( Update ),
+END_DATADESC()
+
+LINK_ENTITY_TO_CLASS( tf_logic_raid, CRaidLogic );
+
+
+//-------------------------------------------------------------------------
+CRaidLogic::CRaidLogic()
+{
+ ListenForGameEvent( "teamplay_point_captured" );
+ ListenForGameEvent( "teamplay_round_win" );
+ ListenForGameEvent( "teamplay_round_start" );
+ m_didFailLastTime = false;
+}
+
+
+//-------------------------------------------------------------------------
+CRaidLogic::~CRaidLogic()
+{
+ g_pRaidLogic = NULL;
+}
+
+
+//-------------------------------------------------------------------------
+void CRaidLogic::Spawn( void )
+{
+ BaseClass::Spawn();
+
+ Reset();
+
+ SetThink( &CRaidLogic::Update );
+ SetNextThink( gpGlobals->curtime );
+
+ m_didFailLastTime = false;
+
+ g_pRaidLogic = this;
+
+ m_miniBossIndex = 0;
+}
+
+
+//-------------------------------------------------------------------------
+void CRaidLogic::Reset( void )
+{
+ m_isWaitingForRaidersToLeaveSpawnRoom = true;
+ m_wasCapturingPoint = false;
+ m_mobSpawnTimer.Invalidate();
+ m_mobLifetimeTimer.Invalidate();
+ m_specialSpawnTimer.Invalidate();
+ m_mobCountRemaining = 0;
+ m_mobArea = NULL;
+ m_mobClass = TF_CLASS_SCOUT;
+ m_priorRaiderAliveCount = -1;
+ m_farthestAlongRaider = NULL;
+ m_farthestAlongEscapeRouteArea = NULL;
+ m_incursionDistanceAtEnd = -1.0f;
+
+ m_wandererCount = 0;
+ m_engineerCount = 0;
+ m_demomanCount = 0;
+ m_heavyCount = 0;
+ m_soldierCount = 0;
+ m_pyroCount = 0;
+ m_spyCount = 0;
+ m_sniperCount = 0;
+ m_squadCount = 0;
+
+ m_miniBossIndex = 0;
+}
+
+
+#if 0
+//--------------------------------------------------------------------------------------------------------
+bool SpawnWanderer( const Vector &spot )
+{
+ if ( !tf_raid_spawn_wanderers.GetBool() )
+ return false;
+
+ return SpawnRedTFBot( TF_CLASS_SCOUT, spot ) ? true : false;
+
+/*
+ CBaseCombatCharacter *minion = static_cast< CBaseCombatCharacter * >( CreateEntityByName( "bot_npc_minion" ) );
+ if ( minion )
+ {
+ minion->SetAbsOrigin( spot );
+ minion->SetOwnerEntity( NULL );
+
+ DispatchSpawn( minion );
+
+ return true;
+ }
+
+ return false;
+*/
+}
+#endif // 0
+
+
+//-------------------------------------------------------------------------
+void CRaidLogic::OnRoundStart( void )
+{
+ if ( !TFGameRules() || !TFGameRules()->IsRaidMode() )
+ return;
+
+ Reset();
+
+ // unspawn entire red team
+ CTeam *defendingTeam = GetGlobalTeam( TF_TEAM_RED );
+ int i;
+ for( i=0; i<defendingTeam->GetNumPlayers(); ++i )
+ {
+ engine->ServerCommand( UTIL_VarArgs( "kickid %d\n", defendingTeam->GetPlayer(i)->GetUserID() ) );
+ }
+
+ // remove all minions
+ CBaseEntity *minion = NULL;
+ while( ( minion = gEntList.FindEntityByClassname( minion, "bot_npc_minion" ) ) != NULL )
+ {
+ UTIL_Remove( minion );
+ }
+
+ // kick last round's NPCs
+ CTeam *raidingTeam = GetGlobalTeam( TF_TEAM_BLUE );
+ for( i=0; i<raidingTeam->GetNumPlayers(); ++i )
+ {
+ CTFBot *bot = ToTFBot( raidingTeam->GetPlayer(i) );
+ if ( bot && bot->HasAttribute( CTFBot::IS_NPC ) )
+ {
+ engine->ServerCommand( UTIL_VarArgs( "kickid %d\n", raidingTeam->GetPlayer(i)->GetUserID() ) );
+ }
+ }
+
+
+ BuildEscapeRoute();
+
+ // collect special areas
+ m_sniperSpotVector.RemoveAll();
+ m_sentrySpotVector.RemoveAll();
+ m_rescueClosetVector.RemoveAll();
+
+ m_miniBossHomeVector.RemoveAll();
+ m_miniBossIndex = 0;
+
+ float availableSentrySpotArea = 0.0f;
+ for( i=0; i<TheNavAreas.Count(); ++i )
+ {
+ CTFNavArea *area = (CTFNavArea *)TheNavAreas[i];
+
+ if ( area->HasAttributeTF( TF_NAV_SNIPER_SPOT ) )
+ m_sniperSpotVector.AddToTail( area );
+
+ if ( area->HasAttributeTF( TF_NAV_SENTRY_SPOT ) )
+ {
+ m_sentrySpotVector.AddToTail( area );
+ availableSentrySpotArea += area->GetSizeX() * area->GetSizeY();
+ }
+
+ if ( area->HasAttributeTF( TF_NAV_RESCUE_CLOSET ) )
+ m_rescueClosetVector.AddToTail( area );
+
+ if ( area->HasAttributeTF( TF_NAV_RED_SETUP_GATE ) )
+ m_miniBossHomeVector.AddToTail( area );
+ }
+
+ // compute total geometric area of entire nav mesh, and clear all wander counts
+ float totalSpace = 0.0f;
+ for( i=0; i<TheNavAreas.Count(); ++i )
+ {
+ CTFNavArea *area = (CTFNavArea *)TheNavAreas[ i ];
+
+ totalSpace += area->GetSizeX() * area->GetSizeY();
+
+ area->SetWanderCount( 0 );
+ }
+
+#if 0
+ //----------------------------------------------
+ // fill the world with wandering defenders
+ int totalPopulation = (int)( tf_raid_wandering_density.GetFloat() * totalSpace + 0.5f );
+
+ CUtlVector< CNavArea * > minionAreaVector;
+ SelectSeparatedShuffleSet< CNavArea >( totalPopulation, tf_raid_sentry_spacing.GetFloat(), TheNavAreas, &minionAreaVector );
+
+ for( int i=0; i<minionAreaVector.Count(); ++i )
+ {
+ static_cast< CTFNavArea * >( minionAreaVector[i] )->AddToWanderCount( 1 );
+// SpawnWanderer( minionAreaVector[i]->GetRandomPoint() );
+ }
+
+ DevMsg( "RAID: Total minion population = %d\n", minionAreaVector.Count() );
+
+
+ //----------------------------------------------
+ // determine where sentry guns will be
+
+ // the total sentry population is based on the total actual space, not just sentry areas
+ totalPopulation = (int)( tf_raid_sentry_density.GetFloat() * totalSpace + 0.5f );
+
+ SelectSeparatedShuffleSet< CTFNavArea >( totalPopulation, tf_raid_sentry_spacing.GetFloat(), m_sentrySpotVector, &m_actualSentrySpotVector );
+
+ for( int i=0; i<m_actualSentrySpotVector.Count(); ++i )
+ {
+ SpawnSentry( m_actualSentrySpotVector[i]->GetCenter() );
+ }
+
+ DevMsg( "RAID: Total sentry population = %d\n", m_actualSentrySpotVector.Count() );
+
+
+ //----------------------------------------------
+ // fill the world with defending bots
+ if ( tf_raid_spawn_defenders.GetBool() )
+ {
+ const int classRosterCount = 5;
+ int classRoster[ classRosterCount ] = { TF_CLASS_SNIPER, TF_CLASS_DEMOMAN, TF_CLASS_SNIPER, TF_CLASS_DEMOMAN, TF_CLASS_PYRO };
+
+ CUtlVector< CTFNavArea * > validSpawnAreaVector;
+ for( int i=0; i<TheNavAreas.Count(); ++i )
+ {
+ CTFNavArea *area = (CTFNavArea *)TheNavAreas[i];
+ if ( area->IsValidForWanderingPopulation() && IsSpaceToSpawnHere( area->GetCenter() ) )
+ {
+ validSpawnAreaVector.AddToTail( area );
+ }
+ }
+
+ totalPopulation = (int)( tf_raid_defender_density.GetFloat() * totalSpace + 0.5f );
+ totalPopulation = clamp( totalPopulation, 0, tf_raid_max_defender_count.GetInt() );
+
+ CUtlVector< CTFNavArea * > defenderAreaVector;
+ SelectSeparatedShuffleSet< CTFNavArea >( totalPopulation, tf_raid_sentry_spacing.GetFloat(), validSpawnAreaVector, &defenderAreaVector );
+
+ for( int i=0; i<defenderAreaVector.Count(); ++i )
+ {
+ CTFNavArea *homeArea = defenderAreaVector[i];
+
+ CTFBot *bot = SpawnRedTFBot( classRoster[ i % classRosterCount ], homeArea->GetCenter() + Vector( 0, 0, 10.0f ) );
+ if ( bot )
+ {
+ bot->SetHomeArea( homeArea );
+ }
+ else
+ {
+ DevMsg( "RAID: Failed to spawn defender!\n" );
+ }
+ }
+
+ DevMsg( "RAID: Total defender population = %d\n", defenderAreaVector.Count() );
+ }
+#endif
+
+
+ // collect all capture point gates
+ m_gateVector.RemoveAll();
+ CBaseEntity *entity = NULL;
+ while( ( entity = gEntList.FindEntityByClassname( entity, "func_door*" ) ) != NULL )
+ {
+ CBaseDoor *door = (CBaseDoor *)entity;
+
+ if ( door->GetEntityName() != NULL_STRING && V_stristr( STRING( door->GetEntityName() ), "raid" ) )
+ {
+ m_gateVector.AddToTail( door );
+ }
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------
+void CRaidLogic::FireGameEvent( IGameEvent *event )
+{
+ const char *eventName = event->GetName();
+
+ if ( !Q_strcmp( eventName, "teamplay_point_captured" ) )
+ {
+ // they just capped - give them a break and reset the mob spawner
+ StartMobTimer( RandomFloat( tf_raid_mob_spawn_min_interval.GetFloat(), tf_raid_mob_spawn_max_interval.GetFloat() ) );
+ DevMsg( "RAID: %3.2f: Reset Mob timer after successful point capture\n", gpGlobals->curtime );
+ }
+ else if ( !Q_strcmp( eventName, "teamplay_round_win" ) )
+ {
+ if ( event->GetInt( "team" ) == TF_TEAM_RED )
+ {
+ // the raiders didn't make it
+ m_didFailLastTime = true;
+ }
+ }
+ else if ( !Q_strcmp( eventName, "teamplay_round_start" ) )
+ {
+ OnRoundStart();
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------
+int CompareIncursionDistances( CTFNavArea * const *area1, CTFNavArea * const *area2 )
+{
+ float d1 = (*area1)->GetIncursionDistance( TF_TEAM_BLUE );
+ float d2 = (*area2)->GetIncursionDistance( TF_TEAM_BLUE );
+
+ if ( d1 < d2 )
+ return -1;
+
+ if ( d1 > d2 )
+ return 1;
+
+ return 0;
+}
+
+
+#if 0
+//--------------------------------------------------------------------------------------------------------
+class CPopulator : public ISearchSurroundingAreasFunctor
+{
+public:
+ CPopulator( float leaderIncursionRange, int maxWanderersToSpawn )
+ {
+ m_leaderIncursionRange = leaderIncursionRange;
+ m_spaceLeft = maxWanderersToSpawn;
+
+ m_floor = FLT_MAX;
+
+ CTeam *invaderTeam = GetGlobalTeam( TF_TEAM_BLUE );
+ for( int i=0; i<invaderTeam->GetNumPlayers(); ++i )
+ {
+ if ( !invaderTeam->GetPlayer(i)->IsAlive() )
+ continue;
+
+ if ( invaderTeam->GetPlayer(i)->GetAbsOrigin().z < m_floor )
+ {
+ m_floor = invaderTeam->GetPlayer(i)->GetAbsOrigin().z;
+ }
+ }
+
+ m_floor -= tf_raid_mob_spawn_below_tolerance.GetFloat();
+ }
+
+ virtual bool operator() ( CNavArea *baseArea, CNavArea *priorArea, float travelDistanceSoFar )
+ {
+ CTFNavArea *area = (CTFNavArea *)baseArea;
+
+ if ( area->IsBlocked( TF_TEAM_RED ) )
+ return true;
+
+ if ( area->HasAttributeTF( TF_NAV_NO_SPAWNING | TF_NAV_SPAWN_ROOM_BLUE ) )
+ return true;
+
+ if ( tf_raid_debug.GetInt() > 1 )
+ {
+ if ( area->IsPotentiallyVisibleToTeam( TF_TEAM_BLUE ) )
+ area->DrawFilled( 255, 100, 0, 100, 1.0f );
+ else
+ area->DrawFilled( 0, 100, 255, 100, 1.0f );
+ }
+
+ // require minimum size
+ if ( area->GetSizeX() < 45.0f && area->GetSizeY() < 45.0f )
+ return true;
+
+ // don't use areas far below team
+// if ( area->GetCenter().z < m_floor )
+// return true;
+
+ if ( area->IsPotentiallyVisibleToTeam( TF_TEAM_BLUE ) )
+ {
+ // don't spawn wanderers in view of raiders
+ // clear any unspawned wanderers here
+ area->SetWanderCount( 0 );
+ return true;
+ }
+
+ // collect out-of-sight areas
+ m_hiddenAreaVector.AddToTail( area );
+
+ // collect out-of-sight areas ahead of the team for special spawns
+ const float aheadBuffer = 500.0f;
+ if ( area->GetIncursionDistance( TF_TEAM_BLUE ) > m_leaderIncursionRange + aheadBuffer )
+ {
+ m_hiddenAreaAheadVector.AddToTail( area );
+ }
+
+ if ( m_spaceLeft <= 0 )
+ return true;
+
+ if ( !tf_raid_spawn_wanderers.GetBool() )
+ {
+ return true;
+ }
+
+ if ( TFGameRules()->GetRaidLogic()->IsMobSpawning() )
+ {
+ // don't spawn wanderers if a mob is spawning to keep slots free
+ return true;
+ }
+
+ if ( !area->IsValidForWanderingPopulation() )
+ {
+ return true;
+ }
+
+ int maxSpawnCount = 5;
+ while( area->GetWanderCount() > 0 && --maxSpawnCount && m_spaceLeft )
+ {
+ // attempt to spawn a wanderer here
+ if ( SpawnWanderer( area->GetRandomPoint() + Vector( 0, 0, StepHeight ) ) )
+ {
+ area->SetWanderCount( area->GetWanderCount() - 1 );
+ --m_spaceLeft;
+ }
+ }
+
+ return true;
+ }
+
+ // return true if 'adjArea' should be included in the ongoing search
+ virtual bool ShouldSearch( CNavArea *adjArea, CNavArea *currentArea, float travelDistanceSoFar )
+ {
+ CTFNavArea *area = (CTFNavArea *)adjArea;
+ float incursionDistance = area->GetIncursionDistance( TF_TEAM_BLUE );
+
+ return incursionDistance > m_leaderIncursionRange - tf_populator_active_buffer_range.GetFloat() &&
+ incursionDistance < m_leaderIncursionRange + tf_populator_active_buffer_range.GetFloat() &&
+ !adjArea->IsBlocked( TEAM_ANY );
+ }
+
+ virtual void PostSearch( void )
+ {
+ // collect the highest hidden & ahead areas
+ float minZ = 999999.9f, maxZ = -999999.9f;
+
+ int i;
+ for( i=0; i<m_hiddenAreaAheadVector.Count(); ++i )
+ {
+ CTFNavArea *area = m_hiddenAreaAheadVector[i];
+ float areaZ = area->GetCenter().z;
+
+ if ( areaZ < minZ )
+ minZ = areaZ;
+ if ( areaZ > maxZ )
+ maxZ = areaZ;
+ }
+
+ float floorZ = minZ + 0.7f * ( maxZ - minZ );
+
+ for( i=0; i<m_hiddenAreaAheadVector.Count(); ++i )
+ {
+ CTFNavArea *area = m_hiddenAreaAheadVector[i];
+ float areaZ = area->GetCenter().z;
+
+ if ( areaZ > floorZ )
+ {
+ m_hiddenAreaAheadHighVector.AddToTail( area );
+ }
+ }
+
+ // sort hidden areas by incursion distance
+ m_hiddenAreaVector.Sort( CompareIncursionDistances );
+ }
+
+
+ float m_leaderIncursionRange;
+ float m_floor;
+ int m_spaceLeft;
+
+ CUtlVector< CTFNavArea * > m_hiddenAreaVector;
+ CUtlVector< CTFNavArea * > m_hiddenAreaAheadVector;
+ CUtlVector< CTFNavArea * > m_hiddenAreaAheadHighVector;
+};
+#endif // 0
+
+
+//--------------------------------------------------------------------------------------------------------
+bool CRaidLogic::Unspawn( CTFPlayer *who )
+{
+ if ( who->IsAlive() && who->GetTeamNumber() == TF_TEAM_RED )
+ {
+ // only cull Engineers who are far behind the team
+ if ( who->IsPlayerClass( TF_CLASS_ENGINEER ) )
+ {
+ CTFNavArea *area = (CTFNavArea *)who->GetLastKnownArea();
+ if ( area && area->GetIncursionDistance( TF_TEAM_BLUE ) > GetMaximumRaiderIncursionDistance() - tf_populator_active_buffer_range.GetFloat() )
+ return false;
+ }
+ else if ( !who->IsPlayerClass( TF_CLASS_SCOUT ) )
+ {
+ // do not unspawn these classes - they lurk at far distances
+ return false;
+ }
+ }
+
+ // check actual line of sight to team
+ if ( IsPlayerVisibleToTeam( who, TF_TEAM_BLUE ) )
+ return false;
+
+ who->ChangeTeam( TEAM_SPECTATOR, false, true );
+
+ // destroy all buildings (for relocated engineers)
+ who->RemoveAllObjects();
+
+ return true;
+}
+
+
+//--------------------------------------------------------------------------------------------------------
+CTFNavArea *CRaidLogic::FindSpawnAreaAhead( void )
+{
+ CTFPlayer *leader = GetFarthestAlongRaider();
+
+ if ( leader == NULL || m_escapeRouteVector.Count() == 0 )
+ return NULL;
+
+ const float minTravel = 1000.0f;
+ float minIncursion = GetMaximumRaiderIncursionDistance() + minTravel;
+
+ // find first non-visible area ahead of leader along escape path beyond a minimum distance
+ for( int i=0; i<m_escapeRouteVector.Count(); ++i )
+ {
+ CTFNavArea *area = m_escapeRouteVector[i];
+
+ if ( area->IsBlocked( TF_TEAM_RED ) )
+ return NULL;
+
+ if ( area->HasAttributeTF( TF_NAV_NO_SPAWNING ) )
+ continue;
+
+ if ( area->GetIncursionDistance( TF_TEAM_BLUE ) < minIncursion )
+ continue;
+
+ if ( area->IsPotentiallyVisibleToTeam( TF_TEAM_BLUE ) )
+ continue;
+
+ if ( IsSpaceToSpawnHere( area->GetCenter() ) )
+ {
+ // found valid squad spawn
+ return area;
+ }
+ }
+
+ return NULL;
+}
+
+
+//--------------------------------------------------------------------------------------------------------
+CTFNavArea *CRaidLogic::FindSpawnAreaBehind( void )
+{
+ CTFPlayer *leader = GetFarthestAlongRaider();
+
+ if ( leader == NULL || m_escapeRouteVector.Count() == 0 )
+ return NULL;
+
+ const float minTravel = 1000.0f;
+ float maxIncursion = GetMaximumRaiderIncursionDistance() - minTravel;
+
+ // find first non-visible area behind leader along escape path beyond a minimum distance
+ for( int i=m_escapeRouteVector.Count()-1; i >= 0; --i )
+ {
+ CTFNavArea *area = m_escapeRouteVector[i];
+
+ if ( area->HasAttributeTF( TF_NAV_NO_SPAWNING ) )
+ continue;
+
+ if ( area->GetIncursionDistance( TF_TEAM_BLUE ) > maxIncursion )
+ continue;
+
+ if ( area->IsPotentiallyVisibleToTeam( TF_TEAM_BLUE ) )
+ continue;
+
+ if ( IsSpaceToSpawnHere( area->GetCenter() ) )
+ {
+ // found valid squad spawn
+ return area;
+ }
+ }
+
+ return NULL;
+}
+
+
+#if 0
+//--------------------------------------------------------------------------------------------------------
+bool CRaidLogic::SpawnSquad( CTFNavArea *spawnArea )
+{
+ if ( spawnArea == NULL )
+ return false;
+
+ int squadSize = 4;
+
+ int freeSlots = GetAvailableRedSpawnSlots();
+ if ( freeSlots < squadSize )
+ {
+ DevMsg( "RAID: %3.2f: *** Not enough free slots to spawn a squad\n", gpGlobals->curtime );
+ return false;
+ }
+
+ const int squadClassMaxCount = 4;
+ int squadClasses[ squadClassMaxCount ];
+ int squadClassCount = 0;
+
+ squadClasses[ squadClassCount++ ] = TF_CLASS_HEAVYWEAPONS;
+ squadClasses[ squadClassCount++ ] = TF_CLASS_SOLDIER;
+ squadClasses[ squadClassCount++ ] = TF_CLASS_PYRO;
+ squadClasses[ squadClassCount++ ] = TF_CLASS_DEMOMAN;
+
+ // randomly shuffle the class order
+ int n = squadClassCount;
+ while( n > 1 )
+ {
+ int k = RandomInt( 0, n-1 );
+ n--;
+
+ int tmp = squadClasses[n];
+ squadClasses[n] = squadClasses[k];
+ squadClasses[k] = tmp;
+ }
+
+ if ( GetMaximumRaiderIncursionDistance() > tf_raid_squad_medic_intro_percent.GetFloat() * GetIncursionDistanceAtEnd() )
+ {
+ // Medics join squads farther into the raid
+ squadClasses[0] = TF_CLASS_MEDIC;
+ }
+
+
+ CTFBotSquad *squad = new CTFBotSquad;
+ CTFBot *bot;
+
+ DevMsg( "RAID: %3.2f: <<<< Spawning Squad >>>>\n", gpGlobals->curtime );
+
+ for( int i=0; i<squadSize; ++i )
+ {
+ int which = squadClasses[ i ];
+
+ bot = SpawnRedTFBot( which, spawnArea->GetCenter() );
+ if ( !bot )
+ return false;
+
+ bot->JoinSquad( squad );
+
+ DevMsg( "RAID: %3.2f: Squad member %s(%d)\n", gpGlobals->curtime, bot->GetPlayerName(), bot->entindex() );
+ }
+
+ IGameEvent* event = gameeventmanager->CreateEvent( "raid_spawn_squad" );
+ if ( event )
+ {
+ gameeventmanager->FireEvent( event );
+ }
+
+ return true;
+}
+#endif // 0
+
+
+//--------------------------------------------------------------------------------------------------------
+void CRaidLogic::StartMobTimer( float duration )
+{
+ if ( IsMobSpawning() )
+ {
+ DevMsg( "RAID: %3.2f: Skipping mob spawn because an existing mob is still in progress\n", gpGlobals->curtime );
+ return;
+ }
+
+ m_mobSpawnTimer.Start( duration );
+ m_mobLifetimeTimer.Invalidate();
+ m_mobCountRemaining = 0;
+ m_mobArea = NULL;
+}
+
+
+#if 0
+//--------------------------------------------------------------------------------------------------------
+CTFNavArea *CRaidLogic::SelectMobSpawn( CUtlVector< CTFNavArea * > *spawnAreaVector, RelativePositionType where )
+{
+ if ( spawnAreaVector->Count() == 0 )
+ {
+ // use high-reliability locations
+ CTFNavArea *spawnArea = FindSpawnAreaBehind();
+ if ( spawnArea )
+ return spawnArea;
+
+ return NULL; // FindSpawnAreaAhead();
+ }
+
+ const int maxRetries = 5;
+ CTFNavArea *spawnArea = NULL;
+ CTFPlayer *farRaider = GetFarthestAlongRaider();
+
+ if ( !farRaider )
+ return NULL;
+
+ for( int r=0; r<maxRetries; ++r )
+ {
+ int which = 0;
+ switch( where )
+ {
+ case AHEAD:
+ // areas are sorted from behind to ahead - weight the selection to choose ahead
+ which = SkewedRandomValue() * spawnAreaVector->Count();
+ break;
+
+ case BEHIND:
+ // areas are sorted from behind to ahead - weight the selection to choose behind
+ which = ( 1.0f - SkewedRandomValue() ) * spawnAreaVector->Count();
+ break;
+
+ case ANYWHERE:
+ // areas are sorted from behind to ahead - weight the selection to choose ahead
+ which = RandomFloat( 0.0f, 1.0f ) * spawnAreaVector->Count();
+ break;
+ }
+
+ if ( which == spawnAreaVector->Count() )
+ --which;
+
+ spawnArea = spawnAreaVector->Element( which );
+
+ if ( ( farRaider->GetAbsOrigin() - spawnArea->GetCenter() ).IsLengthGreaterThan( tf_raid_mob_spawn_min_range.GetFloat() ) )
+ {
+ // well behaved spawn area
+ return spawnArea;
+ }
+ }
+
+ // return whatever we've found so far
+ return spawnArea;
+}
+
+
+//--------------------------------------------------------------------------------------------------------
+void CRaidLogic::SpawnMobs( CUtlVector< CTFNavArea * > *spawnAreaVector )
+{
+ if ( !tf_raid_spawn_mobs.GetBool() )
+ return;
+
+ if ( m_mobLifetimeTimer.HasStarted() )
+ {
+ if ( m_mobLifetimeTimer.IsElapsed() )
+ {
+ // time is up for this mob to spawn, clear the rest
+ DevMsg( "RAID: %3.2f: Mob spawn lifetime is up. Mob count remaining unspawned: %d\n", gpGlobals->curtime, m_mobCountRemaining );
+ m_mobLifetimeTimer.Invalidate();
+ m_mobCountRemaining = 0;
+ m_mobArea = NULL;
+
+ // start next mob
+ m_mobSpawnTimer.Start( RandomFloat( tf_raid_mob_spawn_min_interval.GetFloat(), tf_raid_mob_spawn_max_interval.GetFloat() ) );
+ }
+
+ // can't create another mob until this mob has expired
+ return;
+ }
+
+ if ( m_mobSpawnTimer.HasStarted() && m_mobSpawnTimer.IsElapsed() && spawnAreaVector && spawnAreaVector->Count() > 0 )
+ {
+ // chance of mob changes as we progress through the map
+ int mobChance;
+ float progressRatio = GetMaximumRaiderIncursionDistance() / GetIncursionDistanceAtEnd();
+ if ( progressRatio < 0.5f )
+ {
+ mobChance = tf_raid_spawn_mob_as_squad_chance_start.GetInt() + 2.0f * progressRatio * ( tf_raid_spawn_mob_as_squad_chance_halfway.GetInt() - tf_raid_spawn_mob_as_squad_chance_start.GetInt() );
+ }
+ else
+ {
+ mobChance = tf_raid_spawn_mob_as_squad_chance_halfway.GetInt() + 2.0f * ( progressRatio - 0.5f ) * ( tf_raid_spawn_mob_as_squad_chance_final.GetInt() - tf_raid_spawn_mob_as_squad_chance_halfway.GetInt() );
+ }
+
+ if ( RandomInt( 1, 100 ) <= mobChance )
+ {
+ // this mob is a squad
+ CTFNavArea *spawnArea = SelectMobSpawn( spawnAreaVector, BEHIND ); // m_wasCapturingPoint ? ANYWHERE : BEHIND );
+
+ if ( SpawnSquad( spawnArea ) )
+ {
+ StartMobTimer( RandomFloat( tf_raid_mob_spawn_min_interval.GetFloat(), tf_raid_mob_spawn_max_interval.GetFloat() ) );
+ }
+ else
+ {
+ // couldn't spawn - try again soon
+ StartMobTimer( 1.0f );
+ DevMsg( "RAID: %3.2f: No place to spawn Squad!\n", gpGlobals->curtime );
+ }
+ return;
+ }
+
+ // time to throw in a mob rush
+ m_mobArea = SelectMobSpawn( spawnAreaVector, m_wasCapturingPoint ? ANYWHERE : BEHIND );
+ if ( m_mobArea )
+ {
+ const int mobClassCount = 4;
+ static int mobClassList[ mobClassCount ] =
+ {
+ TF_CLASS_SCOUT,
+ TF_CLASS_HEAVYWEAPONS,
+ TF_CLASS_PYRO,
+ TF_CLASS_SPY,
+ };
+
+ m_mobLifetimeTimer.Start( 7.0f );
+ m_mobCountRemaining = tf_raid_mob_spawn_count.GetInt();
+ m_mobClass = mobClassList[ RandomInt( 0, mobClassCount-1 ) ];
+ DevMsg( "RAID: %3.2f: <<<< Creating mob! >>>>\n", gpGlobals->curtime );
+
+ IGameEvent* event = gameeventmanager->CreateEvent( "raid_spawn_mob" );
+ if ( event )
+ {
+ gameeventmanager->FireEvent( event );
+ }
+ }
+ else
+ {
+ // couldn't spawn - try again soon
+ StartMobTimer( 1.0f );
+ DevMsg( "RAID: %3.2f: No place to spawn Mob!\n", gpGlobals->curtime );
+ }
+ }
+}
+#endif // 0
+
+
+//--------------------------------------------------------------------------------------------------------
+class CNearbyHiddenScan : public ISearchSurroundingAreasFunctor
+{
+public:
+ CNearbyHiddenScan( void )
+ {
+ m_hiddenArea = NULL;
+ }
+
+ virtual bool operator() ( CNavArea *baseArea, CNavArea *priorArea, float travelDistanceSoFar )
+ {
+ CTFNavArea *area = (CTFNavArea *)baseArea;
+
+ if ( area->GetIncursionDistance( TF_TEAM_BLUE ) < TFGameRules()->GetRaidLogic()->GetMaximumRaiderIncursionDistance() )
+ {
+ // defenders can't spawn in region under raider's control
+ return true;
+ }
+
+ if ( !area->IsPotentiallyVisibleToTeam( TF_TEAM_BLUE ) && area->IsValidForWanderingPopulation() )
+ {
+ // found a hidden spot
+ m_hiddenArea = area;
+ return false;
+ }
+
+ return true;
+ }
+
+ CTFNavArea *m_hiddenArea;
+};
+
+
+//--------------------------------------------------------------------------------------------------------
+//
+// Choose unpopulated sentry area nearest the invaders
+//
+CTFNavArea *CRaidLogic::SelectRaidSentryArea( void ) const
+{
+ CTFNavArea *nearestSentryArea = NULL;
+ float nearestSentryAreaIncDist = FLT_MAX;
+
+ float invaderIncDist = GetMaximumRaiderIncursionDistance();
+ float aheadLimit = invaderIncDist + tf_raid_sentry_build_ahead_incursion.GetFloat();
+ float behindLimit = invaderIncDist - tf_raid_sentry_build_behind_incursion.GetFloat();
+
+ // collect a vector of alive enemies
+ CUtlVector< CTFPlayer * > enemyVector;
+ CollectPlayers( &enemyVector, TF_TEAM_RED, COLLECT_ONLY_LIVING_PLAYERS );
+
+ // check for unpopulated sentry areas in the active area set
+ for( int i=0; i<m_actualSentrySpotVector.Count(); ++i )
+ {
+ CTFNavArea *sentryArea = m_actualSentrySpotVector[i];
+
+ // is this area in play?
+ if ( sentryArea->GetIncursionDistance( TF_TEAM_BLUE ) > aheadLimit )
+ continue;
+
+ if ( sentryArea->GetIncursionDistance( TF_TEAM_BLUE ) < behindLimit )
+ continue;
+
+ // is this area 'owned' by another, active, engineer?
+ int e;
+ for( e=0; e<enemyVector.Count(); ++e )
+ {
+ if ( !enemyVector[e]->IsBot() )
+ continue;
+
+ if ( !enemyVector[e]->IsPlayerClass( TF_CLASS_ENGINEER ) )
+ continue;
+
+ CTFBot *engineer = (CTFBot *)enemyVector[e];
+ if ( engineer->GetHomeArea() && engineer->GetHomeArea()->GetID() == sentryArea->GetID() )
+ {
+ break;
+ }
+ }
+
+ if ( e < enemyVector.Count() )
+ {
+ // another engineer is using this area
+ continue;
+ }
+
+ // this is an unreserved sentry area in the active area set, keep the nearest one that is ahead of the team
+ float incDist = sentryArea->GetIncursionDistance( TF_TEAM_BLUE );
+ if ( incDist < nearestSentryAreaIncDist && incDist >= behindLimit )
+ {
+ nearestSentryArea = sentryArea;
+ nearestSentryAreaIncDist = incDist;
+ }
+ }
+
+ if ( nearestSentryArea )
+ {
+ // find a nearby non-visible spawn spot for the engineer to enter from
+ CNearbyHiddenScan hide;
+ const float hideRange = 1000.0f;
+ SearchSurroundingAreas( nearestSentryArea, hide, hideRange );
+
+ if ( !hide.m_hiddenArea )
+ {
+ DevMsg( "RAID: %3.2f: Can't find hidden area to spawn in engineer", gpGlobals->curtime );
+ return NULL;
+ }
+
+ // actual spawn-in, hidden area is this area's parent
+ nearestSentryArea->SetParent( hide.m_hiddenArea );
+
+ return nearestSentryArea;
+ }
+
+ return NULL;
+}
+
+
+#if 0
+//--------------------------------------------------------------------------------------------------------
+void CRaidLogic::SpawnEngineers( void )
+{
+ if ( !tf_raid_spawn_engineers.GetBool() || !m_engineerSpawnTimer.IsElapsed() )
+ return;
+
+ m_engineerSpawnTimer.Start( tf_raid_engineer_spawn_interval.GetFloat() );
+
+ int engineerCount = m_engineerCount;
+
+ while( engineerCount < tf_raid_max_defense_engineers.GetInt() )
+ {
+ // parent of sentry area is hidden area where engineer spawns
+ CTFNavArea *sentryArea = SelectRaidSentryArea();
+
+ if ( !sentryArea )
+ {
+ // no available areas
+ break;
+ }
+
+ const int maxTries = 10;
+ int tryCount;
+ for( tryCount=0; tryCount<maxTries; ++tryCount )
+ {
+ CTFBot *bot = SpawnRedTFBot( TF_CLASS_ENGINEER, sentryArea->GetParent()->GetCenter() + Vector( 0, 0, RandomFloat( 0.0f, 30.0f ) ) );
+ if ( bot )
+ {
+ // engineer bot will move to the sentry area and build
+ bot->SetHomeArea( sentryArea );
+ ++engineerCount;
+ DevMsg( "RAID: %3.2f: Spawned engineer", gpGlobals->curtime );
+ break;
+ }
+ }
+
+ if ( tryCount == maxTries )
+ {
+ DevMsg( "RAID: %3.2f: Can't spawn engineer", gpGlobals->curtime );
+ break;
+ }
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------
+void CRaidLogic::SpawnSpecials( CUtlVector< CTFNavArea * > *spawnAheadVector, CUtlVector< CTFNavArea * > *spawnAnywhereVector )
+{
+ // spawn specials
+ if ( tf_raid_spawn_specials.GetBool() && m_specialSpawnTimer.HasStarted() && m_specialSpawnTimer.IsElapsed() )
+ {
+ // time to add in a "special"
+ m_specialSpawnTimer.Start( RandomFloat( tf_raid_special_spawn_min_interval.GetFloat(), tf_raid_special_spawn_max_interval.GetFloat() ) );
+
+ DevMsg( "RAID: %3.2f: <<<< Spawning Special >>>>\n", gpGlobals->curtime );
+
+ const int specialClassCount = 8;
+ int availableSpecialClassList[ specialClassCount ];
+ int availableCount = 0;
+
+ if ( m_sniperCount < tf_raid_max_defense_snipers.GetInt() )
+ {
+ // increased chance of a sniper
+ availableSpecialClassList[ availableCount++ ] = TF_CLASS_SNIPER;
+ availableSpecialClassList[ availableCount++ ] = TF_CLASS_SNIPER;
+ }
+
+ if ( m_demomanCount < tf_raid_max_defense_demomen.GetInt() )
+ {
+ availableSpecialClassList[ availableCount++ ] = TF_CLASS_DEMOMAN;
+ }
+
+ if ( m_heavyCount < tf_raid_max_defense_heavies.GetInt() )
+ {
+ availableSpecialClassList[ availableCount++ ] = TF_CLASS_HEAVYWEAPONS;
+ }
+
+ if ( m_soldierCount < tf_raid_max_defense_soldiers.GetInt() )
+ {
+ availableSpecialClassList[ availableCount++ ] = TF_CLASS_SOLDIER;
+ }
+
+ if ( m_pyroCount < tf_raid_max_defense_pyros.GetInt() )
+ {
+ availableSpecialClassList[ availableCount++ ] = TF_CLASS_PYRO;
+ }
+
+ if ( m_spyCount < tf_raid_max_defense_spies.GetInt() )
+ {
+ availableSpecialClassList[ availableCount++ ] = TF_CLASS_SPY;
+ }
+
+ if ( availableCount == 0 )
+ {
+ // nothing to spawn
+ DevMsg( "RAID: %3.2f: All specials in play already.\n", gpGlobals->curtime );
+ return;
+ }
+
+ int whichClass = availableSpecialClassList[ RandomInt( 0, availableCount-1 ) ];
+
+ if ( whichClass == TF_CLASS_SNIPER )
+ {
+ CTFNavArea *homeArea = FindSniperSpawn();
+ if ( homeArea )
+ {
+ // actual spawn-in, hidden area is this area's parent
+ for( int tryCount=0; tryCount<10; ++tryCount )
+ {
+ CTFBot *bot = SpawnRedTFBot( whichClass, homeArea->GetParent()->GetCenter() + Vector( 0, 0, RandomFloat( 0.0f, 30.0f ) ) );
+ if ( bot )
+ {
+ // Bot will move to his home area to do his business
+ bot->SetHomeArea( homeArea );
+ return;
+ }
+ }
+ }
+ }
+ else if ( spawnAheadVector && spawnAheadVector->Count() > 0 )
+ {
+ CTFNavArea *where = spawnAheadVector->Element( RandomInt( 0, spawnAheadVector->Count()-1 ) );
+
+ CTFBot *bot = SpawnRedTFBot( whichClass, where->GetCenter() + Vector( 0, 0, StepHeight ) );
+ if ( bot )
+ {
+ bot->SetHomeArea( where );
+ return;
+ }
+ }
+
+ // failed to create special - try again soon
+ m_specialSpawnTimer.Start( RandomFloat( 1.0f, 2.0f ) );
+ DevMsg( "RAID: %3.2f: Failed to spawn Special.\n", gpGlobals->curtime );
+ }
+}
+#endif // 0
+
+
+//--------------------------------------------------------------------------------------------------------
+void CRaidLogic::CullObsoleteEnemies( float minIncursion, float maxIncursion )
+{
+return;
+
+ // cull wanderers outside of the active area set - use slightly larger range to avoid thrashing
+ CTeam *defenseTeam = GetGlobalTeam( TF_TEAM_RED );
+
+ float cullMinIncursionDistance = minIncursion - 1.1f * tf_populator_active_buffer_range.GetFloat();
+ float cullMaxIncursionDistance = maxIncursion + 1.1f * tf_populator_active_buffer_range.GetFloat();
+
+ for( int i=0; i<defenseTeam->GetNumPlayers(); ++i )
+ {
+ if ( !defenseTeam->GetPlayer(i)->IsBot() )
+ continue;
+
+ CTFBot *defender = (CTFBot *)defenseTeam->GetPlayer(i);
+
+ if ( !defender->IsAlive() )
+ continue;
+
+ CTFNavArea *defenderArea = (CTFNavArea *)defender->GetLastKnownArea();
+ if ( defenderArea == NULL )
+ {
+ // bad placement, underground most likely
+ Unspawn( defender );
+ }
+ else if ( !defender->IsMoving() || defender->GetLocomotionInterface()->IsStuck() )
+ {
+ if ( defenderArea->GetIncursionDistance( TF_TEAM_BLUE ) < cullMinIncursionDistance ||
+ defenderArea->GetIncursionDistance( TF_TEAM_BLUE ) > cullMaxIncursionDistance )
+ {
+ if ( Unspawn( defender ) )
+ {
+ if ( !defender->HasAttribute( CTFBot::AGGRESSIVE ) )
+ {
+ defenderArea->AddToWanderCount( 1 );
+ }
+ }
+ }
+ }
+ }
+
+ // aggressively cull wanderers if we need to spawn a mob and have no room
+ if ( IsMobSpawning() && GetAvailableRedSpawnSlots() <= 0 )
+ {
+ // try to make room by removing an unseen wanderer
+ for( int i=0; i<defenseTeam->GetNumPlayers(); ++i )
+ {
+ if ( !defenseTeam->GetPlayer(i)->IsBot() )
+ continue;
+
+ CTFBot *defender = (CTFBot *)defenseTeam->GetPlayer(i);
+
+ if ( !defender->IsAlive() )
+ continue;
+
+ // only cull Scouts
+ if ( !defender->IsPlayerClass( TF_CLASS_SCOUT ) )
+ continue;
+
+ // don't cull mob rushers
+ if ( defender->HasAttribute( CTFBot::AGGRESSIVE ) )
+ continue;
+
+ // try to open up a slot
+ if ( Unspawn( defender ) )
+ {
+ if ( defender->GetLastKnownArea() )
+ {
+ defender->GetLastKnownArea()->AddToWanderCount( 1 );
+ }
+ break;
+ }
+ }
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------
+class CShowEscapeRoute : public ISearchSurroundingAreasFunctor
+{
+public:
+ virtual bool operator() ( CNavArea *baseArea, CNavArea *priorArea, float travelDistanceSoFar )
+ {
+ CTFNavArea *area = (CTFNavArea *)baseArea;
+
+ if ( area->HasAttributeTF( TF_NAV_ESCAPE_ROUTE ) )
+ {
+ area->DrawFilled( 100, 255, 255, 255, NDEBUG_PERSIST_TILL_NEXT_SERVER, false );
+ }
+ else if ( area->HasAttributeTF( TF_NAV_ESCAPE_ROUTE_VISIBLE ) )
+ {
+ area->DrawFilled( 100, 200, 100, 255, NDEBUG_PERSIST_TILL_NEXT_SERVER, false );
+ }
+
+ return true;
+ }
+
+ virtual bool ShouldSearch( CNavArea *adjArea, CNavArea *currentArea, float travelDistanceSoFar )
+ {
+ return travelDistanceSoFar < 3000.0f;
+ }
+};
+
+
+//--------------------------------------------------------------------------------------------------------
+void CRaidLogic::DrawDebugDisplay( float deltaT )
+{
+ // avoid Warning() spam from UTIL_GetListenServerHost when on a dedicated server
+ if ( engine->IsDedicatedServer() )
+ return;
+
+ CBasePlayer *player = UTIL_GetListenServerHost();
+ if ( player == NULL )
+ return;
+
+ if ( tf_raid_debug_director.GetBool() )
+ {
+ NDebugOverlay::ScreenText( 0.01f, 0.5f, CFmtStr( "Mob timer: %3.2f", m_mobSpawnTimer.GetRemainingTime() ), 255, 255, 0, 255, 0.33f );
+ NDebugOverlay::ScreenText( 0.01f, 0.51f, CFmtStr( "Wanderers: %d", m_wandererCount ), 255, 255, 0, 255, 0.33f );
+ NDebugOverlay::ScreenText( 0.01f, 0.52f, CFmtStr( "Engineers: %d", m_engineerCount ), 255, 255, 0, 255, 0.33f );
+ NDebugOverlay::ScreenText( 0.01f, 0.53f, CFmtStr( "Demomen: %d", m_demomanCount ), 255, 255, 0, 255, 0.33f );
+ NDebugOverlay::ScreenText( 0.01f, 0.54f, CFmtStr( "Heavies: %d", m_heavyCount ), 255, 255, 0, 255, 0.33f );
+ NDebugOverlay::ScreenText( 0.01f, 0.55f, CFmtStr( "Soldiers: %d", m_soldierCount ), 255, 255, 0, 255, 0.33f );
+ NDebugOverlay::ScreenText( 0.01f, 0.56f, CFmtStr( "Pyros: %d", m_pyroCount ), 255, 255, 0, 255, 0.33f );
+ NDebugOverlay::ScreenText( 0.01f, 0.57f, CFmtStr( "Spies: %d", m_spyCount ), 255, 255, 0, 255, 0.33f );
+ NDebugOverlay::ScreenText( 0.01f, 0.58f, CFmtStr( "Snipers: %d", m_sniperCount ), 255, 255, 0, 255, 0.33f );
+ NDebugOverlay::ScreenText( 0.01f, 0.59f, CFmtStr( "Squads: %d", m_squadCount ), 255, 255, 0, 255, 0.33f );
+ NDebugOverlay::ScreenText( 0.01f, 0.60f, CFmtStr( "Raider max inc range: %3.2f", GetMaximumRaiderIncursionDistance() ), 255, 255, 0, 255, 0.33f );
+ }
+
+ if ( tf_raid_show_escape_route.GetInt() == 2 )
+ {
+ if ( m_escapeRouteVector.Count() >= 2 )
+ {
+ int anchor=0;
+ float closeRangeSq = FLT_MAX;
+ for( int i=0; i<m_escapeRouteVector.Count(); ++i )
+ {
+ float rangeSq = ( m_escapeRouteVector[i]->GetCenter() - player->GetAbsOrigin() ).LengthSqr();
+ if ( rangeSq < closeRangeSq )
+ {
+ closeRangeSq = rangeSq;
+ anchor = i;
+ }
+ }
+
+ int lo = MAX( 0, anchor-20 );
+ int hi = MIN( m_escapeRouteVector.Count(), anchor+20 );
+
+ for( int i=lo; i<hi-1; ++i )
+ {
+ NDebugOverlay::HorzArrow( m_escapeRouteVector[i]->GetCenter(), m_escapeRouteVector[i+1]->GetCenter(), 5.0f, 255, 0, 0, 255, true, deltaT );
+ NDebugOverlay::Text( m_escapeRouteVector[i]->GetCenter(), CFmtStr( "%d", i ), true, deltaT );
+ }
+ }
+ }
+ else if ( tf_raid_show_escape_route.GetInt() == 1 )
+ {
+ CShowEscapeRoute show;
+ SearchSurroundingAreas( player->GetLastKnownArea(), show );
+ }
+
+ if ( tf_raid_debug_sentry_placement.GetBool() )
+ {
+ for( int i=0; i<m_actualSentrySpotVector.Count(); ++i )
+ {
+ m_actualSentrySpotVector[i]->DrawFilled( 255, 155, 0, 255, deltaT, true );
+ }
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------
+void CRaidLogic::Update( void )
+{
+ VPROF_BUDGET( "CRaidLogic::Update", "Game" );
+
+ const float deltaT = 0.33f;
+
+ SetNextThink( gpGlobals->curtime + deltaT );
+
+ if ( !TFGameRules()->IsRaidMode() )
+ return;
+
+ DrawDebugDisplay( deltaT );
+
+ CTeam *raidingTeam = GetGlobalTeam( TF_TEAM_BLUE );
+
+ // total hack for testing mini-bosses
+ if ( m_miniBossIndex == 0 && m_miniBossHomeVector.Count() > 0 )
+ {
+ CTFNavArea *bossHomeArea = m_miniBossHomeVector[0];
+
+ CBaseEntity *miniBoss = CreateEntityByName( "bot_boss_mini_rockets" );
+ if ( miniBoss )
+ {
+ miniBoss->SetAbsOrigin( bossHomeArea->GetCenter() );
+ miniBoss->SetOwnerEntity( NULL );
+
+ DispatchSpawn( miniBoss );
+ }
+
+ ++m_miniBossIndex;
+ }
+ else if ( m_miniBossIndex == 1 && m_miniBossHomeVector.Count() > 1 )
+ {
+ if ( gEntList.FindEntityByClassname( NULL, "bot_boss_mini_rockets" ) == NULL )
+ {
+ CTFNavArea *bossHomeArea = m_miniBossHomeVector[1];
+
+ CBaseEntity *miniBoss = CreateEntityByName( "bot_boss_mini_nuker" );
+ if ( miniBoss )
+ {
+ miniBoss->SetAbsOrigin( bossHomeArea->GetCenter() );
+ miniBoss->SetOwnerEntity( NULL );
+
+ DispatchSpawn( miniBoss );
+ }
+
+ ++m_miniBossIndex;
+ }
+ }
+
+
+ if ( IsWaitingForRaidersToLeaveSafeRoom() )
+ {
+ // has anyone left?
+ for( int i=0; i<raidingTeam->GetNumPlayers(); ++i )
+ {
+ CTFPlayer *player = (CTFPlayer *)raidingTeam->GetPlayer(i);
+
+ if ( !player->IsAlive() || !player->GetLastKnownArea() )
+ continue;
+
+ // don't start until a HUMAN leaves the safe room
+ if ( player->IsBot() )
+ continue;
+
+ CTFNavArea *area = (CTFNavArea *)player->GetLastKnownArea();
+
+ if ( !area->HasAttributeTF( TF_NAV_SPAWN_ROOM_BLUE ) )
+ {
+ // this Raider has left the spawn room - game on!
+ m_isWaitingForRaidersToLeaveSpawnRoom = false;
+
+ StartMobTimer( RandomFloat( 0.5f * tf_raid_mob_spawn_min_interval.GetFloat(), tf_raid_mob_spawn_max_interval.GetFloat() ) );
+ m_specialSpawnTimer.Start( RandomFloat( 0.0f, tf_raid_special_spawn_min_interval.GetFloat() ) );
+
+ DevMsg( "RAID: %3.2f: Raiders left the spawn room!\n", gpGlobals->curtime );
+ }
+ }
+ }
+ else
+ {
+ int aliveCount = 0;
+ for( int i=0; i<raidingTeam->GetNumPlayers(); ++i )
+ {
+ CTFPlayer *player = ToTFPlayer( raidingTeam->GetPlayer(i) );
+
+ CTFBot *bot = ToTFBot( player );
+ if ( bot && bot->HasAttribute( CTFBot::IS_NPC ) )
+ continue;
+
+ if ( player->IsAlive() )
+ {
+ CTFNavArea *area = (CTFNavArea *)player->GetLastKnownArea();
+ if ( !area || !area->HasAttributeTF( TF_NAV_RESCUE_CLOSET ) )
+ {
+ // only count raiders not in a rescue closet
+ ++aliveCount;
+ }
+ }
+ }
+
+ if ( m_priorRaiderAliveCount < 0 )
+ {
+ // just left the safe room
+ if ( m_didFailLastTime )
+ {
+ TFGameRules()->BroadcastSound( 255, "Announcer.DontFailAgain" );
+ }
+ else
+ {
+ TFGameRules()->BroadcastSound( 255, "Announcer.AM_GameStarting04" ); // "Let the games begin!"
+ }
+ }
+ else
+ {
+ if ( m_priorRaiderAliveCount > aliveCount )
+ {
+ // somebody died, warn the team
+ switch( aliveCount )
+ {
+ case 3: TFGameRules()->BroadcastSound( 255, "Announcer.RoundEnds3seconds" ); break;
+ case 2: TFGameRules()->BroadcastSound( 255, "Announcer.RoundEnds2seconds" ); break;
+ case 1: TFGameRules()->BroadcastSound( 255, "Announcer.AM_LastManAlive01" ); break;
+ }
+ }
+ else if ( m_priorRaiderAliveCount < aliveCount )
+ {
+ // someone has joined/respawned, let the team know
+ switch( aliveCount )
+ {
+ case 4: TFGameRules()->BroadcastSound( 255, "Announcer.RoundEnds4seconds" ); break;
+ case 3: TFGameRules()->BroadcastSound( 255, "Announcer.RoundEnds3seconds" ); break;
+ case 2: TFGameRules()->BroadcastSound( 255, "Announcer.RoundEnds2seconds" ); break;
+ }
+ }
+ }
+
+ m_priorRaiderAliveCount = aliveCount;
+
+
+ if ( TFGameRules() && !TFGameRules()->IsInWaitingForPlayers() )
+ {
+ // if all of the raiders die, they lose
+ if ( aliveCount == 0 )
+ {
+ CTeamplayRoundBasedRules *pRules = dynamic_cast<CTeamplayRoundBasedRules*>( GameRules() );
+ if ( pRules )
+ {
+ pRules->SetWinningTeam( TF_TEAM_RED, WINREASON_OPPONENTS_DEAD );
+ }
+ }
+ }
+ }
+
+
+ // have the raiders begun capturing the next point?
+ CTeamControlPoint *ctrlPoint = GetContestedPoint();
+ bool isCapturingPoint = ( ctrlPoint && ctrlPoint->GetTeamCapPercentage( TF_TEAM_BLUE ) );
+
+ // find maximum incursion distance
+ if ( m_incursionDistanceAtEnd < 0.0f )
+ {
+ for( int i=0; i<TheNavAreas.Count(); ++i )
+ {
+ CTFNavArea *area = (CTFNavArea *)TheNavAreas[ i ];
+
+ if ( area->GetIncursionDistance( TF_TEAM_BLUE ) > m_incursionDistanceAtEnd )
+ {
+ m_incursionDistanceAtEnd = area->GetIncursionDistance( TF_TEAM_BLUE );
+ }
+ }
+ }
+
+ // find incursion range that surrounds raider team
+ float minIncursion = FLT_MAX, maxIncursion = -FLT_MAX;
+
+ m_farthestAlongRaider = NULL;
+ CTFPlayer *capturer = NULL;
+ int i;
+
+ for( i=0; i<raidingTeam->GetNumPlayers(); ++i )
+ {
+ CTFPlayer *player = (CTFPlayer *)raidingTeam->GetPlayer(i);
+
+ if ( !player->IsAlive() || !player->GetLastKnownArea() )
+ continue;
+
+ CTFBot *bot = ToTFBot( player );
+ if ( bot && bot->HasAttribute( CTFBot::IS_NPC ) )
+ continue;
+
+ if ( player->IsCapturingPoint() )
+ {
+ capturer = player;
+ }
+
+ CTFNavArea *area = (CTFNavArea *)player->GetLastKnownArea();
+
+ float myIncursion = area->GetIncursionDistance( TF_TEAM_BLUE );
+
+ if ( maxIncursion < myIncursion )
+ {
+ maxIncursion = myIncursion;
+ m_farthestAlongRaider = player;
+ }
+
+ if ( minIncursion > myIncursion )
+ {
+ minIncursion = myIncursion;
+ }
+ }
+
+ m_farthestAlongEscapeRouteArea = NULL;
+
+ if ( !m_farthestAlongRaider )
+ return;
+
+ // watch for point capture events
+ if ( !m_wasCapturingPoint && isCapturingPoint )
+ {
+ DevMsg( "RAID: %3.2f: Point capture STARTED!\n", gpGlobals->curtime );
+
+ // UTIL_CenterPrintAll( CFmtStr( "*** %s started capturing the point! ***", capturer->GetPlayerName() ) );
+
+ // spawn a mob immediately
+ StartMobTimer( 0.1f );
+ }
+ else if ( m_wasCapturingPoint && !isCapturingPoint )
+ {
+ DevMsg( "RAID: %3.2f: Point capture STOPPED!\n", gpGlobals->curtime );
+ }
+ m_wasCapturingPoint = isCapturingPoint;
+
+ // track escape route area nearest leader
+ for( i=0; i<m_escapeRouteVector.Count(); ++i )
+ {
+ CTFNavArea *area = m_escapeRouteVector[i];
+
+ if ( area->GetIncursionDistance( TF_TEAM_BLUE ) <= GetMaximumRaiderIncursionDistance() )
+ {
+ if ( m_farthestAlongEscapeRouteArea )
+ {
+ if ( m_farthestAlongEscapeRouteArea->GetIncursionDistance( TF_TEAM_BLUE ) < area->GetIncursionDistance( TF_TEAM_BLUE ) )
+ {
+ m_farthestAlongEscapeRouteArea = area;
+ }
+ }
+ else
+ {
+ m_farthestAlongEscapeRouteArea = area;
+ }
+ }
+ }
+
+ if ( tf_raid_debug_escape_route.GetBool() && m_farthestAlongEscapeRouteArea )
+ {
+ m_farthestAlongEscapeRouteArea->DrawFilled( 255, 100, 0, 255, NDEBUG_PERSIST_TILL_NEXT_SERVER );
+ }
+
+ CullObsoleteEnemies( minIncursion, maxIncursion );
+
+ // count defensive types
+ CTeam *defenseTeam = GetGlobalTeam( TF_TEAM_RED );
+ m_wandererCount = 0;
+ m_engineerCount = 0;
+ m_demomanCount = 0;
+ m_heavyCount = 0;
+ m_soldierCount = 0;
+ m_pyroCount = 0;
+ m_spyCount = 0;
+ m_sniperCount = 0;
+ m_squadCount = 0;
+
+ CUtlVector< CTFBotSquad * > m_squadVector;
+
+ for( i=0; i<defenseTeam->GetNumPlayers(); ++i )
+ {
+ if ( !defenseTeam->GetPlayer(i)->IsBot() )
+ continue;
+
+ CTFBot *defender = (CTFBot *)defenseTeam->GetPlayer(i);
+
+ if ( !defender->IsAlive() )
+ continue;
+
+ if ( defender->GetSquad() )
+ {
+ if ( m_squadVector.Find( defender->GetSquad() ) == m_squadVector.InvalidIndex() )
+ {
+ m_squadVector.AddToTail( defender->GetSquad() );
+ ++m_squadCount;
+ }
+ continue;
+ }
+
+ if ( defender->IsPlayerClass( TF_CLASS_ENGINEER ) )
+ {
+ ++m_engineerCount;
+ }
+ else if ( defender->IsPlayerClass( TF_CLASS_DEMOMAN ) )
+ {
+ ++m_demomanCount;
+ }
+ else if ( defender->IsPlayerClass( TF_CLASS_HEAVYWEAPONS ) )
+ {
+ ++m_heavyCount;
+ }
+ else if ( defender->IsPlayerClass( TF_CLASS_SOLDIER ) )
+ {
+ ++m_soldierCount;
+ }
+ else if ( defender->IsPlayerClass( TF_CLASS_PYRO ) )
+ {
+ ++m_pyroCount;
+ }
+ else if ( defender->IsPlayerClass( TF_CLASS_SPY ) )
+ {
+ ++m_spyCount;
+ }
+ else if ( defender->IsPlayerClass( TF_CLASS_SNIPER ) )
+ {
+ ++m_sniperCount;
+ }
+ else if ( defender->IsPlayerClass( TF_CLASS_SCOUT ) )
+ {
+ if ( defender->HasAttribute( CTFBot::AGGRESSIVE ) )
+ continue;
+
+ ++m_wandererCount;
+ }
+ }
+
+
+ if ( tf_raid_spawn_enable.GetBool() == false )
+ return;
+
+#if 0
+ // populate wanderers
+ CPopulator populator( maxIncursion, tf_raid_max_wanderers.GetInt() - m_wandererCount );
+ SearchSurroundingAreas( m_farthestAlongRaider->GetLastKnownArea(), populator );
+
+ // if raiders are capturing a point, spawn mobs at a faster rate
+ if ( isCapturingPoint && m_mobSpawnTimer.GetRemainingTime() > tf_raid_capture_mob_interval.GetFloat() )
+ {
+ StartMobTimer( tf_raid_capture_mob_interval.GetFloat() - 0.1f );
+ }
+
+ SpawnMobs( &populator.m_hiddenAreaVector );
+ SpawnSpecials( &populator.m_hiddenAreaAheadVector, &populator.m_hiddenAreaVector );
+ SpawnEngineers();
+
+
+ // emit mob
+ if ( IsMobSpawning() && m_mobCountRemaining && m_mobArea && tf_raid_spawn_mobs.GetBool() )
+ {
+ if ( m_mobArea->HasAttributeTF( TF_NAV_NO_SPAWNING ) )
+ {
+ // the mob is trying to spawn from an area that doesn't have room to spawn in bots, pick another
+ DevMsg( "RAID: %3.2f: Mob attempting to spawn where there isn't room - retrying\n", gpGlobals->curtime );
+ m_mobArea = SelectMobSpawn( &populator.m_hiddenAreaVector, BEHIND );
+ if ( !m_mobArea )
+ {
+ DevMsg( "RAID: %3.2f: Can't find a mob spawn area!\n", gpGlobals->curtime );
+
+ // try again soon
+ StartMobTimer( 2.0f );
+ }
+ }
+
+ if ( m_mobArea )
+ {
+ if ( SpawnRedTFBot( m_mobClass, m_mobArea->GetCenter() + Vector( 0, 0, StepHeight ), IS_MOB_RUSHER ) )
+ {
+ --m_mobCountRemaining;
+ DevMsg( "RAID: %3.2f: Spawned mob member, %d to go\n", gpGlobals->curtime, m_mobCountRemaining );
+ }
+ }
+ }
+#endif // 0
+
+ // block/unblock capture point gate doors
+ // TODO: Do this more efficiently
+ for( i=0; i<m_gateVector.Count(); ++i )
+ {
+ CBaseDoor *door = m_gateVector[i];
+
+ Extent doorExtent;
+ doorExtent.Init( door );
+
+ NavAreaCollector overlapAreas;
+ TheNavMesh->ForAllAreasOverlappingExtent( overlapAreas, doorExtent );
+
+ for( int b=0; b<overlapAreas.m_area.Count(); ++b )
+ {
+ CTFNavArea *area = (CTFNavArea *)overlapAreas.m_area[b];
+ if ( door->m_toggle_state == TS_AT_TOP )
+ {
+ // open, not blocked
+ area->ClearAttributeTF( TF_NAV_BLOCKED );
+ }
+ else
+ {
+ // not open, blocked
+ area->SetAttributeTF( TF_NAV_BLOCKED );
+ }
+ }
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------
+float CRaidLogic::GetMaximumRaiderIncursionDistance( void ) const
+{
+ if ( m_farthestAlongRaider == NULL )
+ return 0.0f;
+
+ CTFNavArea *area = (CTFNavArea *)m_farthestAlongRaider->GetLastKnownArea();
+
+ return area ? area->GetIncursionDistance( TF_TEAM_BLUE ) : 0.0f;
+}
+
+
+//--------------------------------------------------------------------------------------------------------
+/**
+ * Given a viewing area, return the earliest escape route area near the team that is visible
+ */
+CTFNavArea *CRaidLogic::FindEarliestVisibleEscapeRouteAreaNearTeam( CTFNavArea *viewArea ) const
+{
+ if ( viewArea == NULL || m_farthestAlongEscapeRouteArea == NULL )
+ return NULL;
+
+ const float nearIncursionRange = 1000.0f;
+ const float minIncursion = GetMaximumRaiderIncursionDistance() - nearIncursionRange;
+ const float maxIncursion = GetMaximumRaiderIncursionDistance() + nearIncursionRange;
+
+ NavAreaCollector collector;
+ viewArea->ForAllCompletelyVisibleAreas( collector );
+
+ CTFNavArea *firstArea = NULL;
+ float firstAreaIncursion = FLT_MAX;
+
+ for( int i=0; i<collector.m_area.Count(); ++i )
+ {
+ CTFNavArea *area = (CTFNavArea *)collector.m_area[i];
+ float areaIncursion = area->GetIncursionDistance( TF_TEAM_BLUE );
+
+ if ( area->HasAttributeTF( TF_NAV_ESCAPE_ROUTE ) && areaIncursion > minIncursion && areaIncursion < maxIncursion )
+ {
+ if ( tf_debug_sniper_spots.GetBool() )
+ {
+ area->DrawFilled( 255, 0, 255, 255, 120.0f );
+ }
+
+/*
+ float rangeSq = ( viewArea->GetCenter() - area->GetCenter() ).LengthSqr();
+
+ if ( firstAreaIncursion > rangeSq )
+ {
+ // closest
+ firstAreaIncursion = rangeSq;
+ firstArea = area;
+ }
+*/
+
+ if ( firstAreaIncursion > areaIncursion )
+ {
+ firstArea = area;
+ firstAreaIncursion = areaIncursion;
+ }
+ }
+ }
+
+ return firstArea;
+}
+
+
+//--------------------------------------------------------------------------------------------------------
+/**
+ * Return area where sniper should take up a vantage point. That area's parent will be
+ * the actual spawn-in area not currently visible to the team.
+ */
+CTFNavArea *CRaidLogic::FindSniperSpawn( void )
+{
+ CUtlVector< CTFNavArea * > validAreas;
+
+ float aheadLimit = GetMaximumRaiderIncursionDistance() + tf_raid_sniper_spawn_ahead_incursion.GetFloat();
+ float behindLimit = GetMaximumRaiderIncursionDistance() - tf_raid_sniper_spawn_behind_incursion.GetFloat();
+
+ float tooCloseLimitSq = tf_raid_sniper_spawn_min_range.GetFloat();
+ tooCloseLimitSq *= tooCloseLimitSq;
+
+ for( int i=0; i<m_sniperSpotVector.Count(); ++i )
+ {
+ CTFNavArea *sniperArea = m_sniperSpotVector[i];
+
+ if ( sniperArea->GetIncursionDistance( TF_TEAM_BLUE ) < 0.0f )
+ continue;
+
+ if ( sniperArea->GetIncursionDistance( TF_TEAM_BLUE ) > aheadLimit )
+ continue;
+
+ if ( sniperArea->GetIncursionDistance( TF_TEAM_BLUE ) < behindLimit )
+ continue;
+
+ // make sure no Raider is too close
+ CClosestTFPlayer close( sniperArea->GetCenter(), TF_TEAM_BLUE );
+ ForEachPlayer( close );
+
+ if ( close.m_closePlayer && close.m_closeRangeSq < tooCloseLimitSq )
+ continue;
+
+ // this is often too restrictive - we really want "potentially visible to region near team"
+ if ( !sniperArea->IsPotentiallyVisibleToTeam( TF_TEAM_BLUE ) )
+ {
+ if ( tf_debug_sniper_spots.GetBool() )
+ sniperArea->DrawFilled( 100, 100, 100, 255, 99999.9f );
+
+ continue;
+ }
+
+ validAreas.AddToTail( sniperArea );
+ }
+
+ if ( validAreas.Count() )
+ {
+ // choose a specific sniper spot
+ CTFNavArea *sniperArea = validAreas[ RandomInt( 0, validAreas.Count()-1 ) ];
+
+ // find a nearby non-visible spawn spot
+ CNearbyHiddenScan hide;
+ const float hideRange = 1000.0f;
+ SearchSurroundingAreas( sniperArea, hide, hideRange );
+
+ if ( hide.m_hiddenArea )
+ {
+ sniperArea->SetParent( hide.m_hiddenArea );
+
+ if ( tf_debug_sniper_spots.GetBool() )
+ {
+ const float deltaT = 999999.9f;
+ hide.m_hiddenArea->DrawFilled( 0, 0, 255, 255, deltaT );
+ NDebugOverlay::HorzArrow( hide.m_hiddenArea->GetCenter() + Vector( 0, 0, 10.0f ), sniperArea->GetCenter() + Vector( 0, 0, 10.0f ), 5.0f, 255, 255, 0, 255, true, deltaT );
+ sniperArea->DrawFilled( 255, 0, 0, 255, deltaT );
+
+ for( int i=0; i<validAreas.Count(); ++i )
+ {
+ if ( validAreas[i] != sniperArea && validAreas[i] != hide.m_hiddenArea )
+ validAreas[i]->DrawFilled( 0, 255, 0, 255, 99999.9f );
+ }
+ }
+
+ return sniperArea;
+ }
+ }
+
+ return NULL;
+}
+
+
+//--------------------------------------------------------------------------------------------------------
+/**
+ * Return a good area for a Sentry Gun, and return a hidden area for the engineer
+ * to spawn as the parent of that area.
+ */
+CTFNavArea *CRaidLogic::FindSentryArea( void )
+{
+ CUtlVector< CTFNavArea * > validAreas;
+
+ float aheadLimit = GetMaximumRaiderIncursionDistance() + tf_raid_sentry_build_ahead_incursion.GetFloat();
+ float behindLimit = GetMaximumRaiderIncursionDistance() - tf_raid_sentry_build_behind_incursion.GetFloat();
+
+ for( int i=0; i<m_sentrySpotVector.Count(); ++i )
+ {
+ CTFNavArea *sentryArea = m_sentrySpotVector[i];
+
+ if ( sentryArea->GetIncursionDistance( TF_TEAM_BLUE ) < 0.0f )
+ continue;
+
+ if ( sentryArea->GetIncursionDistance( TF_TEAM_BLUE ) > aheadLimit )
+ continue;
+
+ if ( sentryArea->GetIncursionDistance( TF_TEAM_BLUE ) < behindLimit )
+ continue;
+
+ // don't use this area if it already has a sentry in it
+ if ( TheTFNavMesh()->IsSentryGunHere( sentryArea ) )
+ continue;
+
+ validAreas.AddToTail( sentryArea );
+ }
+
+ if ( validAreas.Count() )
+ {
+ // choose a specific sentry spot
+ CTFNavArea *sentryArea = validAreas[ RandomInt( 0, validAreas.Count()-1 ) ];
+
+ // find a nearby non-visible spawn spot for the engineer
+ CNearbyHiddenScan hide;
+ const float hideRange = 1000.0f;
+ SearchSurroundingAreas( sentryArea, hide, hideRange );
+
+ if ( hide.m_hiddenArea )
+ {
+ sentryArea->SetParent( hide.m_hiddenArea );
+ return sentryArea;
+ }
+ }
+
+ return NULL;
+}
+
+
+//---------------------------------------------------------------------------------------------
+// Pick a member of the raiding (blue) team for a red defender to attack
+CTFPlayer *CRaidLogic::SelectRaiderToAttack( void )
+{
+ CTeam *invaderTeam = GetGlobalTeam( TF_TEAM_BLUE );
+
+ // attack point cappers first
+ CUtlVector< CTFPlayer * > victimVector;
+ int i;
+ for( i=0; i<invaderTeam->GetNumPlayers(); ++i )
+ {
+ CTFPlayer *player = (CTFPlayer *)invaderTeam->GetPlayer(i);
+
+ if ( player->IsAlive() && player->IsCapturingPoint() )
+ victimVector.AddToTail( player );
+ }
+
+ if ( victimVector.Count() == 0 )
+ {
+ // pick a random living Raider
+ for( i=0; i<invaderTeam->GetNumPlayers(); ++i )
+ {
+ CTFPlayer *player = (CTFPlayer *)invaderTeam->GetPlayer(i);
+
+ CTFBot *bot = ToTFBot( player );
+ if ( bot && bot->HasAttribute( CTFBot::IS_NPC ) )
+ continue;
+
+ if ( player->IsAlive() )
+ victimVector.AddToTail( player );
+ }
+ }
+
+ if ( victimVector.Count() )
+ {
+ return victimVector[ RandomInt( 0, victimVector.Count()-1 ) ];
+ }
+
+ return NULL;
+}
+
+
+//--------------------------------------------------------------------------------------------------------
+// Return entity positioned within next valid rescue closet area for to respawn players in
+CBaseEntity *CRaidLogic::GetRescueRespawn( void ) const
+{
+ if ( g_internalSpawnPoint == NULL )
+ {
+ g_internalSpawnPoint = (CPopulatorInternalSpawnPoint *)CreateEntityByName( "populator_internal_spawn_point" );
+ g_internalSpawnPoint->Spawn();
+ }
+
+ float limit = GetMaximumRaiderIncursionDistance() - 500.0f;
+
+ CTFNavArea *rescueArea = NULL;
+ float rescueFlow = FLT_MAX;
+
+ for( int i=0; i<m_rescueClosetVector.Count(); ++i )
+ {
+ float flow = m_rescueClosetVector[i]->GetIncursionDistance( TF_TEAM_BLUE );
+ if ( flow > limit && flow < rescueFlow )
+ {
+ rescueArea = m_rescueClosetVector[i];
+ rescueFlow = flow;
+ }
+ }
+
+ if ( rescueArea )
+ {
+ g_internalSpawnPoint->SetAbsOrigin( rescueArea->GetCenter() );
+ g_internalSpawnPoint->SetLocalAngles( vec3_angle );
+
+ return g_internalSpawnPoint;
+ }
+
+ return NULL;
+}
+
+
+//--------------------------------------------------------------------------------------------------------
+class CMarkEscapeRoute
+{
+public:
+ CMarkEscapeRoute( CUtlVector< CTFNavArea * > *escapeRouteVector )
+ {
+ m_escapeRouteVector = escapeRouteVector;
+ }
+
+ void operator() ( CNavArea *baseArea )
+ {
+ CTFNavArea *area = (CTFNavArea *)baseArea;
+
+ if ( !m_escapeRouteVector->HasElement( area ) )
+ {
+ area->SetAttributeTF( TF_NAV_ESCAPE_ROUTE );
+ m_escapeRouteVector->AddToTail( area );
+ }
+ }
+
+ CUtlVector< CTFNavArea * > *m_escapeRouteVector;
+};
+
+
+//--------------------------------------------------------------------------------------------------------
+class CMarkEscapeRouteVisible
+{
+public:
+ bool operator() ( CNavArea *baseArea )
+ {
+ CTFNavArea *area = (CTFNavArea *)baseArea;
+
+ area->SetAttributeTF( TF_NAV_ESCAPE_ROUTE_VISIBLE );
+
+ return true;
+ }
+};
+
+
+//---------------------------------------------------------------------------------------------
+// Locate and store escape route
+void CRaidLogic::BuildEscapeRoute( void )
+{
+ // find blue spawn room
+ CBaseEntity *entity = NULL;
+ for ( int i=0; i<IFuncRespawnRoomAutoList::AutoList().Count(); ++i )
+ {
+ CFuncRespawnRoom *respawnRoom = static_cast< CFuncRespawnRoom* >( IFuncRespawnRoomAutoList::AutoList()[i] );
+ entity = respawnRoom;
+ if ( respawnRoom->GetActive() && respawnRoom->GetTeamNumber() == TF_TEAM_BLUE )
+ {
+ break;
+ }
+ }
+
+ if ( entity )
+ {
+ // respawn room absorigin is 0,0,0 - have to use extent
+ Extent extent;
+ extent.Init( entity );
+ Vector safeRoomPos = ( extent.lo + extent.hi ) / 2;
+
+ DevMsg( "RAID: Blue spawn room at (%g, %g, %g)\n", safeRoomPos.x, safeRoomPos.y, safeRoomPos.z );
+
+
+ // assume path_track nearest the blue spawn is the start of the escape route
+ // according to the mappers, previous pointers do not contain useful data
+ CPathTrack *pathNode = NULL;
+ CPathTrack *firstPathNode = NULL;
+ float closeRangeSq = FLT_MAX;
+
+ for( pathNode = dynamic_cast< CPathTrack * >( gEntList.FindEntityByClassname( pathNode, "path_track" ) );
+ pathNode;
+ pathNode = dynamic_cast< CPathTrack * >( gEntList.FindEntityByClassname( pathNode, "path_track" ) ) )
+ {
+ float rangeSq = ( pathNode->GetAbsOrigin() - safeRoomPos ).LengthSqr();
+
+ if ( rangeSq < closeRangeSq )
+ {
+ closeRangeSq = rangeSq;
+ firstPathNode = pathNode;
+ }
+ }
+
+ if ( firstPathNode )
+ {
+ CUtlVector< CTFNavArea * > pathTrackAreaVector;
+
+ for( pathNode = firstPathNode; pathNode; pathNode = pathNode->GetNext() )
+ {
+ CTFNavArea *pathArea = (CTFNavArea *)TheNavMesh->GetNavArea( pathNode->GetAbsOrigin() );
+ if ( pathArea )
+ {
+ if ( !pathTrackAreaVector.HasElement( pathArea ) )
+ {
+ pathTrackAreaVector.AddToTail( pathArea );
+ }
+ }
+ }
+
+ if ( pathTrackAreaVector.Count() > 1 )
+ {
+ // build contiguous escape route area vector
+ m_escapeRouteVector.RemoveAll();
+ CMarkEscapeRoute markAsEscapeRoute( &m_escapeRouteVector );
+ for( int i=1; i<pathTrackAreaVector.Count(); ++i )
+ {
+ // mark all areas between as on the escape route
+ TheNavMesh->ForAllAreasAlongLine( markAsEscapeRoute, pathTrackAreaVector[i-1], pathTrackAreaVector[i] );
+ }
+
+ // flag all areas that can see the escape route as escape route 'visible'
+ for( int i=0; i<m_escapeRouteVector.Count(); ++i )
+ {
+ CTFNavArea *escapeArea = m_escapeRouteVector[i];
+
+ CMarkEscapeRouteVisible markAsEscapeRouteVisible;
+ escapeArea->ForAllCompletelyVisibleAreas( markAsEscapeRouteVisible );
+ }
+ }
+ }
+ }
+}
+
+
+//---------------------------------------------------------------------------------------------
+//
+// Return the next control point that can be captured
+//
+CTeamControlPoint *CRaidLogic::GetContestedPoint( void ) const
+{
+ CTeamControlPointMaster *pMaster = g_hControlPointMasters.Count() ? g_hControlPointMasters[0] : NULL;
+ if ( pMaster )
+ {
+ for( int i=0; i<pMaster->GetNumPoints(); ++i )
+ {
+ CTeamControlPoint *point = pMaster->GetControlPoint( i );
+ if ( point && pMaster->IsInRound( point ) )
+ {
+ if ( ObjectiveResource()->GetOwningTeam( point->GetPointIndex() ) == TF_TEAM_BLUE )
+ continue;
+
+ // blue are the invaders
+ if ( !TeamplayGameRules()->TeamMayCapturePoint( TF_TEAM_BLUE, point->GetPointIndex() ) )
+ continue;
+
+ return point;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+
+#endif // TF_RAID_MODE