Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1614 lines
50 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: Entities for use in the Robot Destruction TF2 game mode.
  4. //
  5. //=========================================================================//
  6. #include "cbase.h"
  7. #include "tf_logic_robot_destruction.h"
  8. #include "tf_shareddefs.h"
  9. #include "tf_gamerules.h"
  10. #ifdef GAME_DLL
  11. #include "tf_objective_resource.h"
  12. #include "entity_bonuspack.h"
  13. #include "pathtrack.h"
  14. #include "tf_gamestats.h"
  15. #endif
  16. #ifdef GAME_DLL
  17. void cc_tf_rd_max_points_override( IConVar *pConVar, const char *pOldString, float flOldValue )
  18. {
  19. ConVarRef var( pConVar );
  20. if ( CTFRobotDestructionLogic::GetRobotDestructionLogic() )
  21. CTFRobotDestructionLogic::GetRobotDestructionLogic()->DBG_SetMaxPoints( var.GetInt() );
  22. }
  23. ConVar tf_rd_max_points_override( "tf_rd_max_points_override", "0", FCVAR_GAMEDLL, "When changed, overrides the current max points", cc_tf_rd_max_points_override );
  24. #if defined( STAGING_ONLY ) || defined( DEBUG )
  25. void cc_tf_rd_score_blue_points( const CCommand &args )
  26. {
  27. int nPoints = args.ArgC() > 1 ? atoi(args[1]) : 0;
  28. if ( CTFRobotDestructionLogic::GetRobotDestructionLogic() )
  29. CTFRobotDestructionLogic::GetRobotDestructionLogic()->ScorePoints( TF_TEAM_BLUE
  30. , nPoints
  31. , SCORE_CORES_COLLECTED
  32. , NULL );
  33. }
  34. ConCommand tf_rd_score_blue_points( "tf_rd_score_blue_points", cc_tf_rd_score_blue_points, "Give blue points.", FCVAR_CHEAT );
  35. void cc_tf_rd_score_red_points( const CCommand &args )
  36. {
  37. int nPoints = args.ArgC() > 1 ? atoi(args[1]) : 0;
  38. if ( CTFRobotDestructionLogic::GetRobotDestructionLogic() )
  39. CTFRobotDestructionLogic::GetRobotDestructionLogic()->ScorePoints( TF_TEAM_RED
  40. , nPoints
  41. , SCORE_CORES_COLLECTED
  42. , NULL );
  43. }
  44. ConCommand tf_rd_score_red_points( "tf_rd_score_red_points", cc_tf_rd_score_red_points, "Give red points.", FCVAR_CHEAT );
  45. #endif // STAGING_ONLY
  46. #endif
  47. ConVar tf_rd_robot_attack_notification_cooldown( "tf_rd_robot_attack_notification_cooldown", "10", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY );
  48. ConVar tf_rd_steal_rate( "tf_rd_steal_rate", "0.5", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY );
  49. ConVar tf_rd_points_per_steal( "tf_rd_points_per_steal", "5", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY );
  50. ConVar tf_rd_points_approach_interval( "tf_rd_points_approach_interval", "0.1f", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY );
  51. ConVar tf_rd_points_per_approach( "tf_rd_points_per_approach", "5", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY );
  52. ConVar tf_rd_min_points_to_steal( "tf_rd_min_points_to_steal", "25", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY );
  53. #ifdef CLIENT_DLL
  54. ConVar tf_rd_finale_beep_time( "tf_rd_finale_beep_time", "10", FCVAR_ARCHIVE );
  55. #endif
  56. extern RobotData_t* g_RobotData[ NUM_ROBOT_TYPES ];
  57. #define GROUP_RESPAWN_CONTEXT "group_respawn_context"
  58. #define ADD_POINTS_CONTEXT "add_points_context"
  59. #define UPDATE_STOLEN_POINTS_THINK "stolen_points_think"
  60. #define APPROACH_POINTS_THINK "approach_points_think"
  61. IMPLEMENT_NETWORKCLASS_ALIASED( TFRobotDestruction_RobotSpawn, DT_TFRobotDestructionRobotSpawn )
  62. BEGIN_NETWORK_TABLE_NOBASE( CTFRobotDestruction_RobotSpawn, DT_TFRobotDestructionRobotSpawn )
  63. END_NETWORK_TABLE()
  64. LINK_ENTITY_TO_CLASS( tf_robot_destruction_robot_spawn, CTFRobotDestruction_RobotSpawn );
  65. BEGIN_DATADESC( CTFRobotDestruction_RobotSpawn )
  66. #ifdef GAME_DLL
  67. DEFINE_INPUTFUNC( FIELD_VOID, "SpawnRobot", InputSpawnRobot ),
  68. // Keyfields
  69. DEFINE_KEYFIELD( m_spawnData.m_eType, FIELD_INTEGER, "type" ),
  70. DEFINE_KEYFIELD( m_spawnData.m_nRobotHealth, FIELD_INTEGER, "health" ),
  71. DEFINE_KEYFIELD( m_spawnData.m_nPoints, FIELD_INTEGER, "points" ),
  72. DEFINE_KEYFIELD( m_spawnData.m_pszGroupName, FIELD_STRING, "spawngroup" ),
  73. DEFINE_KEYFIELD( m_spawnData.m_nNumGibs, FIELD_INTEGER, "gibs" ),
  74. DEFINE_KEYFIELD( m_spawnData.m_pszPathName, FIELD_STRING, "startpath" ),
  75. DEFINE_OUTPUT( m_OnRobotKilled, "OnRobotKilled" ),
  76. #endif
  77. END_DATADESC()
  78. CTFRobotDestruction_RobotSpawn::CTFRobotDestruction_RobotSpawn()
  79. {}
  80. void CTFRobotDestruction_RobotSpawn::Spawn()
  81. {
  82. BaseClass::Spawn();
  83. #ifdef GAME_DLL
  84. SetSolid( SOLID_NONE );
  85. Precache();
  86. #endif
  87. }
  88. void CTFRobotDestruction_RobotSpawn::Activate()
  89. {
  90. BaseClass::Activate();
  91. #ifdef GAME_DLL
  92. if ( !m_spawnData.m_pszGroupName || !m_spawnData.m_pszGroupName[0] )
  93. {
  94. Assert(0);
  95. Warning( "%s has no spawn group defined!", STRING(GetEntityName()) );
  96. return;
  97. }
  98. // Make sure the group exists
  99. CBaseEntity *pEnt = gEntList.FindEntityByName( NULL, m_spawnData.m_pszGroupName );
  100. CTFRobotDestruction_RobotGroup *pGroup = dynamic_cast<CTFRobotDestruction_RobotGroup*>( pEnt );
  101. if ( pEnt != pGroup )
  102. {
  103. const char *pszMsg = CFmtStr( "%s specified '%s' as its group, but %s is a %s"
  104. , STRING( GetEntityName() )
  105. , m_spawnData.m_pszGroupName
  106. , m_spawnData.m_pszGroupName
  107. , pEnt->GetClassname() );
  108. AssertMsg( false, "%s", pszMsg );
  109. Warning( "%s", pszMsg );
  110. }
  111. if ( pGroup )
  112. {
  113. // Make sure there's not two with the same name
  114. Assert( gEntList.FindEntityByName( pGroup, m_spawnData.m_pszGroupName ) == NULL );
  115. pGroup->AddToGroup( this );
  116. }
  117. else
  118. {
  119. Assert(0);
  120. Warning( "Couldn't find robot destruction spawn group named '%s'!\n", m_spawnData.m_pszGroupName );
  121. }
  122. // Make sure the path exists
  123. pEnt = gEntList.FindEntityByName( NULL, m_spawnData.m_pszPathName );
  124. CPathTrack *pPath = dynamic_cast< CPathTrack * >( pEnt );
  125. if ( pPath != pEnt )
  126. {
  127. const char *pszMsg = CFmtStr( "%s specified '%s' as its first path, but %s is a %s"
  128. , STRING( GetEntityName() )
  129. , m_spawnData.m_pszPathName
  130. , m_spawnData.m_pszPathName
  131. , pEnt->GetClassname() );
  132. AssertMsg( 0, "%s", pszMsg );
  133. Warning( "%s", pszMsg );
  134. }
  135. else if ( pEnt == NULL )
  136. {
  137. const char *pszMsg = CFmtStr( "%s specified '%s' as its first path, but %s doesn't exist"
  138. , STRING( GetEntityName() )
  139. , m_spawnData.m_pszPathName
  140. , m_spawnData.m_pszPathName );
  141. AssertMsg( 0, "%s", pszMsg );
  142. Warning( "%s", pszMsg );
  143. }
  144. #endif
  145. }
  146. #ifdef GAME_DLL
  147. void CTFRobotDestruction_RobotSpawn::SpawnRobot()
  148. {
  149. if ( m_hGroup.Get() == NULL )
  150. {
  151. Assert(0);
  152. Warning( "Spawnpoint '%s' tried to spawn a robot, but group name '%s' didnt find any groups!\n", STRING(GetEntityName()), m_spawnData.m_pszGroupName );
  153. return;
  154. }
  155. if ( m_hRobot == NULL )
  156. {
  157. m_hRobot = assert_cast< CTFRobotDestruction_Robot* >( CreateEntityByName( "tf_robot_destruction_robot" ) );
  158. m_hRobot->SetModel( g_RobotData[ m_spawnData.m_eType ]->GetStringData( RobotData_t::MODEL_KEY ) );
  159. m_hRobot->ChangeTeam( m_hGroup->GetTeamNumber() );
  160. m_hRobot->SetHealth( m_spawnData.m_nRobotHealth );
  161. m_hRobot->SetMaxHealth( m_spawnData.m_nRobotHealth );
  162. m_hRobot->SetGroup( m_hGroup.Get() );
  163. m_hRobot->SetSpawn( this );
  164. m_hRobot->SetRobotSpawnData( m_spawnData );
  165. m_hRobot->SetName( AllocPooledString(CFmtStr( "%s_robot", STRING(GetEntityName())) ) );
  166. DispatchSpawn( m_hRobot );
  167. m_hRobot->SetAbsOrigin( GetAbsOrigin() );
  168. m_hRobot->SetAbsAngles( GetAbsAngles() );
  169. }
  170. }
  171. void CTFRobotDestruction_RobotSpawn::InputSpawnRobot( inputdata_t &inputdata )
  172. {
  173. SpawnRobot();
  174. }
  175. void CTFRobotDestruction_RobotSpawn::OnRobotKilled()
  176. {
  177. Assert( m_hRobot.Get() );
  178. ClearRobot();
  179. m_OnRobotKilled.FireOutput( this, this );
  180. }
  181. void CTFRobotDestruction_RobotSpawn::ClearRobot()
  182. {
  183. m_hRobot = NULL;
  184. }
  185. void CTFRobotDestruction_RobotSpawn::Precache()
  186. {
  187. BaseClass::Precache();
  188. CTFRobotDestruction_Robot::StaticPrecache();
  189. PrecacheModel( g_RobotData[ m_spawnData.m_eType ]->GetStringData( RobotData_t::MODEL_KEY ) );
  190. PrecacheModel( g_RobotData[ m_spawnData.m_eType ]->GetStringData( RobotData_t::DAMAGED_MODEL_KEY ) );
  191. }
  192. bool CTFRobotDestruction_RobotSpawn::ShouldCollide( int collisionGroup, int contentsMask ) const
  193. {
  194. if ( collisionGroup == COLLISION_GROUP_PLAYER_MOVEMENT )
  195. {
  196. return false;
  197. }
  198. return BaseClass::ShouldCollide( collisionGroup, contentsMask );
  199. }
  200. #endif
  201. IMPLEMENT_AUTO_LIST( IRobotDestructionGroupAutoList );
  202. BEGIN_DATADESC( CTFRobotDestruction_RobotGroup )
  203. #ifdef GAME_DLL
  204. DEFINE_KEYFIELD( m_iszHudIcon, FIELD_STRING, "hud_icon" ),
  205. DEFINE_KEYFIELD( m_flRespawnTime, FIELD_FLOAT, "respawn_time" ),
  206. DEFINE_KEYFIELD( m_nGroupNumber, FIELD_INTEGER, "group_number" ),
  207. DEFINE_KEYFIELD( m_nTeamNumber, FIELD_INTEGER, "team_number" ),
  208. DEFINE_KEYFIELD( m_flTeamRespawnReductionScale, FIELD_FLOAT, "respawn_reduction_scale" ),
  209. DEFINE_OUTPUT( m_OnRobotsRespawn, "OnRobotsRespawn" ),
  210. DEFINE_OUTPUT( m_OnAllRobotsDead, "OnAllRobotsDead" ),
  211. #endif
  212. END_DATADESC()
  213. LINK_ENTITY_TO_CLASS( tf_robot_destruction_spawn_group, CTFRobotDestruction_RobotGroup );
  214. IMPLEMENT_NETWORKCLASS_ALIASED( TFRobotDestruction_RobotGroup, DT_TFRobotDestruction_RobotGroup )
  215. BEGIN_NETWORK_TABLE_NOBASE( CTFRobotDestruction_RobotGroup, DT_TFRobotDestruction_RobotGroup )
  216. #ifdef CLIENT_DLL
  217. RecvPropString( RECVINFO( m_pszHudIcon ) ),
  218. RecvPropInt( RECVINFO( m_iTeamNum ) ),
  219. RecvPropInt( RECVINFO( m_nGroupNumber ) ),
  220. RecvPropInt( RECVINFO( m_nState ) ),
  221. RecvPropFloat( RECVINFO( m_flRespawnStartTime ) ),
  222. RecvPropFloat( RECVINFO( m_flRespawnEndTime ) ),
  223. RecvPropFloat( RECVINFO( m_flLastAttackedTime ) ),
  224. #else
  225. SendPropString( SENDINFO( m_pszHudIcon ) ),
  226. SendPropInt( SENDINFO( m_iTeamNum ), -1, SPROP_VARINT | SPROP_UNSIGNED ),
  227. SendPropInt( SENDINFO( m_nGroupNumber ), -1, SPROP_VARINT | SPROP_UNSIGNED ),
  228. SendPropInt( SENDINFO( m_nState ), -1, SPROP_VARINT | SPROP_UNSIGNED ),
  229. SendPropFloat( SENDINFO( m_flRespawnStartTime ), -1, SPROP_NOSCALE ),
  230. SendPropFloat( SENDINFO( m_flRespawnEndTime ), -1, SPROP_NOSCALE ),
  231. SendPropFloat( SENDINFO( m_flLastAttackedTime ), -1, SPROP_NOSCALE ),
  232. #endif
  233. END_NETWORK_TABLE()
  234. CTFRobotDestruction_RobotGroup::~CTFRobotDestruction_RobotGroup()
  235. {
  236. #ifdef CLIENT_DLL
  237. IGameEvent *event = gameeventmanager->CreateEvent( "rd_rules_state_changed" );
  238. if ( event )
  239. {
  240. gameeventmanager->FireEventClientSide( event );
  241. }
  242. #endif
  243. }
  244. #ifdef GAME_DLL
  245. float CTFRobotDestruction_RobotGroup::m_sflNextAllowedAttackAlertTime[TF_TEAM_COUNT] = { 0.f, 0.f, 0.f, 0.f };
  246. CTFRobotDestruction_RobotGroup::CTFRobotDestruction_RobotGroup()
  247. : m_flRespawnTime( 0.f )
  248. , m_nTeamNumber( 0 )
  249. {
  250. m_nState.Set( ROBOT_STATE_DEAD );
  251. m_nGroupNumber.Set( 0 );
  252. m_flRespawnStartTime.Set( 0.f );
  253. m_flRespawnEndTime.Set( 1.f );
  254. }
  255. void CTFRobotDestruction_RobotGroup::Spawn()
  256. {
  257. V_strncpy( m_pszHudIcon.GetForModify(), STRING( m_iszHudIcon ), MAX_PATH );
  258. }
  259. void CTFRobotDestruction_RobotGroup::Activate()
  260. {
  261. BaseClass::Activate();
  262. ChangeTeam( m_nTeamNumber );
  263. memset( m_sflNextAllowedAttackAlertTime, 0.f, sizeof( m_sflNextAllowedAttackAlertTime ) );
  264. if ( CTFRobotDestructionLogic::GetRobotDestructionLogic() )
  265. {
  266. CTFRobotDestructionLogic::GetRobotDestructionLogic()->AddRobotGroup( this );
  267. }
  268. }
  269. void CTFRobotDestruction_RobotGroup::AddToGroup( CTFRobotDestruction_RobotSpawn * pSpawn )
  270. {
  271. Assert( m_vecSpawns.Find( pSpawn ) == m_vecSpawns.InvalidIndex() );
  272. pSpawn->SetGroup( this );
  273. m_vecSpawns.AddToTail( pSpawn );
  274. }
  275. void CTFRobotDestruction_RobotGroup::RemoveFromGroup( CTFRobotDestruction_RobotSpawn * pSpawn )
  276. {
  277. Assert( m_vecSpawns.Find( pSpawn ) != m_vecSpawns.InvalidIndex() );
  278. pSpawn->SetGroup( NULL );
  279. m_vecSpawns.FindAndRemove( pSpawn );
  280. }
  281. void CTFRobotDestruction_RobotGroup::UpdateState()
  282. {
  283. bool bShielded = false;
  284. int nAlive = 0;
  285. FOR_EACH_VEC( m_vecSpawns, i )
  286. {
  287. CTFRobotDestruction_Robot* pRobot = m_vecSpawns[ i ]->GetRobot();
  288. if ( !pRobot )
  289. continue;
  290. if ( pRobot->m_lifeState != LIFE_DEAD )
  291. {
  292. ++nAlive;
  293. bShielded |= m_vecSpawns[ i ]->GetRobot()->GetShieldedState();
  294. }
  295. }
  296. eRobotUIState eState = ROBOT_STATE_INACIVE;
  297. if ( bShielded )
  298. {
  299. eState = ROBOT_STATE_SHIELDED;
  300. }
  301. else if ( nAlive > 0 )
  302. {
  303. eState = ROBOT_STATE_ACTIVE;
  304. }
  305. else
  306. {
  307. eState = ROBOT_STATE_DEAD;
  308. }
  309. m_nState.Set( (int)eState );
  310. m_flRespawnEndTime = GetNextThink( GROUP_RESPAWN_CONTEXT );
  311. }
  312. void CTFRobotDestruction_RobotGroup::OnRobotAttacked()
  313. {
  314. float& flNextAlertTime = m_sflNextAllowedAttackAlertTime[ GetTeamNumber() ];
  315. if ( gpGlobals->curtime >= flNextAlertTime )
  316. {
  317. flNextAlertTime = gpGlobals->curtime + tf_rd_robot_attack_notification_cooldown.GetFloat();
  318. CTeamRecipientFilter filter( GetTeamNumber(), true );
  319. TFGameRules()->SendHudNotification( filter, HUD_NOTIFY_RD_ROBOT_UNDER_ATTACK );
  320. }
  321. m_flLastAttackedTime = gpGlobals->curtime;
  322. }
  323. void CTFRobotDestruction_RobotGroup::OnRobotKilled()
  324. {
  325. UpdateState();
  326. if ( CTFRobotDestructionLogic::GetRobotDestructionLogic() )
  327. {
  328. CTFRobotDestructionLogic::GetRobotDestructionLogic()->ManageGameState();
  329. }
  330. // If all our robots are dead, fire the corresponding output
  331. if ( GetNumAliveBots() == 0 )
  332. {
  333. m_OnAllRobotsDead.FireOutput( this, this );
  334. }
  335. }
  336. void CTFRobotDestruction_RobotGroup::OnRobotSpawned()
  337. {
  338. UpdateState();
  339. }
  340. void CTFRobotDestruction_RobotGroup::RespawnRobots()
  341. {
  342. // Clear out our think
  343. StopRespawnTimer();
  344. FOR_EACH_VEC( m_vecSpawns, i )
  345. {
  346. m_vecSpawns[ i ]->SpawnRobot();
  347. }
  348. if ( CTFRobotDestructionLogic::GetRobotDestructionLogic() )
  349. {
  350. CTFRobotDestructionLogic::GetRobotDestructionLogic()->ManageGameState();
  351. }
  352. m_OnRobotsRespawn.FireOutput( this, this );
  353. }
  354. int CTFRobotDestruction_RobotGroup::GetNumAliveBots() const
  355. {
  356. int nNumAlive = 0;
  357. FOR_EACH_VEC( m_vecSpawns, i )
  358. {
  359. CTFRobotDestruction_RobotSpawn* pSpawn = m_vecSpawns[i];
  360. CTFRobotDestruction_Robot *pRobot = pSpawn->GetRobot();
  361. if ( pRobot && pRobot->m_lifeState != LIFE_DEAD )
  362. {
  363. ++nNumAlive;
  364. }
  365. }
  366. return nNumAlive;
  367. }
  368. void CTFRobotDestruction_RobotGroup::StopRespawnTimer()
  369. {
  370. SetContextThink( NULL, TICK_NEVER_THINK, GROUP_RESPAWN_CONTEXT );
  371. }
  372. void CTFRobotDestruction_RobotGroup::StartRespawnTimerIfNeeded( CTFRobotDestruction_RobotGroup *pMasterGroup )
  373. {
  374. bool bIsMaster = pMasterGroup == this || pMasterGroup == NULL;
  375. // We're already thinking and we're the master
  376. if ( GetNextThink( GROUP_RESPAWN_CONTEXT ) != TICK_NEVER_THINK && bIsMaster )
  377. {
  378. return;
  379. }
  380. // We dont have dead bots
  381. if ( GetNumAliveBots() != 0 )
  382. {
  383. return;
  384. }
  385. // Use the master's time if one got passed in
  386. float flRespawnTime = bIsMaster ? gpGlobals->curtime + m_flRespawnTime : pMasterGroup->GetNextThink( GROUP_RESPAWN_CONTEXT );
  387. // If this respawn time is different, then mark this time as the respawn start time. This can
  388. // get multiple times with the same value, and we dont want to update every time if we dont have to.
  389. if ( !AlmostEqual( flRespawnTime, GetNextThink( GROUP_RESPAWN_CONTEXT ) ) )
  390. {
  391. // Mark this time
  392. m_flRespawnStartTime = gpGlobals->curtime;
  393. }
  394. SetContextThink( &CTFRobotDestruction_RobotGroup::RespawnCountdownFinish, flRespawnTime, GROUP_RESPAWN_CONTEXT );
  395. m_flRespawnEndTime = flRespawnTime;
  396. }
  397. void CTFRobotDestruction_RobotGroup::RespawnCountdownFinish()
  398. {
  399. RespawnRobots();
  400. // Do other stuff?
  401. }
  402. void CTFRobotDestruction_RobotGroup::EnableUberForGroup()
  403. {
  404. FOR_EACH_VEC( m_vecSpawns, i )
  405. {
  406. CTFRobotDestruction_Robot *pRobot = m_vecSpawns[ i ]->GetRobot();
  407. if ( pRobot )
  408. {
  409. pRobot->EnableUber();
  410. }
  411. }
  412. }
  413. void CTFRobotDestruction_RobotGroup::DisableUberForGroup()
  414. {
  415. FOR_EACH_VEC( m_vecSpawns, i )
  416. {
  417. CTFRobotDestruction_Robot *pRobot = m_vecSpawns[ i ]->GetRobot();
  418. if ( pRobot )
  419. {
  420. pRobot->DisableUber();
  421. }
  422. }
  423. }
  424. #else //GAME_DLL
  425. void CTFRobotDestruction_RobotGroup::PostDataUpdate( DataUpdateType_t updateType )
  426. {
  427. BaseClass::PostDataUpdate( updateType );
  428. if ( updateType == DATA_UPDATE_CREATED )
  429. {
  430. IGameEvent *event = gameeventmanager->CreateEvent( "rd_rules_state_changed" );
  431. if ( event )
  432. {
  433. gameeventmanager->FireEventClientSide( event );
  434. }
  435. }
  436. }
  437. void CTFRobotDestruction_RobotGroup::SetDormant( bool bDormant )
  438. {
  439. BaseClass::SetDormant( bDormant );
  440. IGameEvent *event = gameeventmanager->CreateEvent( "rd_rules_state_changed" );
  441. if ( event )
  442. {
  443. gameeventmanager->FireEventClientSide( event );
  444. }
  445. }
  446. #endif
  447. #ifdef GAME_DLL
  448. static CTFRobotDestruction_RobotGroup * GetLowestAlive( const CUtlVector < CTFRobotDestruction_RobotGroup * >& vecGroups )
  449. {
  450. CTFRobotDestruction_RobotGroup *pLowest = NULL;
  451. FOR_EACH_VEC( vecGroups, i )
  452. {
  453. CTFRobotDestruction_RobotGroup *pGroup = vecGroups[i];
  454. // Must have some bots alive
  455. if ( pGroup->GetNumAliveBots() == 0 )
  456. continue;
  457. if ( pLowest == NULL || pGroup->GetGroupNumber() < pLowest->GetGroupNumber() )
  458. {
  459. pLowest = pGroup;
  460. }
  461. }
  462. return pLowest;
  463. }
  464. static CTFRobotDestruction_RobotGroup * GetHighestDead( const CUtlVector < CTFRobotDestruction_RobotGroup * >& vecGroups )
  465. {
  466. CTFRobotDestruction_RobotGroup *pHighest = NULL;
  467. FOR_EACH_VEC( vecGroups, i )
  468. {
  469. CTFRobotDestruction_RobotGroup *pGroup = vecGroups[i];
  470. // Must not have any alive bots
  471. if ( pGroup->GetNumAliveBots() > 0 )
  472. continue;
  473. if ( pHighest == NULL || pGroup->GetGroupNumber() > pHighest->GetGroupNumber() )
  474. {
  475. pHighest = pGroup;
  476. }
  477. }
  478. return pHighest;
  479. }
  480. #endif
  481. //-----------------------------------------------------------------------------
  482. // Purpose:
  483. //-----------------------------------------------------------------------------
  484. CTFRobotDestructionLogic::CTFRobotDestructionLogic()
  485. {
  486. Assert( m_sCTFRobotDestructionLogic == NULL );
  487. m_sCTFRobotDestructionLogic = this;
  488. #ifdef GAME_DLL
  489. m_nBlueTargetPoints = 0.f;
  490. m_nRedTargetPoints = 0.f;
  491. m_flBlueFinaleEndTime = FLT_MAX;
  492. m_flRedFinaleEndTime = FLT_MAX;
  493. m_flNextRedRobotAttackedAlertTime = 0.f;
  494. m_flNextBlueRobotAttackedAlertTime = 0.f;
  495. memset( m_nNumFlagsOut, 0, sizeof( m_nNumFlagsOut ) );
  496. m_iszResFile = MAKE_STRING( "resource/UI/HudObjectiveRobotDestruction.res" ); // Can get overridden from the map
  497. ListenForGameEvent( "teamplay_pre_round_time_left" );
  498. ListenForGameEvent( "player_spawn" );
  499. m_mapRateLimitedSounds.SetLessFunc( StringLessThan );
  500. m_mapRateLimitedSounds.Insert( "RD.TeamScoreCore", new RateLimitedSound_t( 0.001f ) );
  501. m_mapRateLimitedSounds.Insert( "RD.EnemyScoreCore", new RateLimitedSound_t( 0.001f ) );
  502. m_mapRateLimitedSounds.Insert( "RD.EnemyStealingPoints", new RateLimitedSound_t( 0.45f ) );
  503. m_mapRateLimitedSounds.Insert( "MVM.PlayerUpgraded", new RateLimitedSound_t( 0.2f ) );
  504. m_AnnouncerProgressSound = { "Announcer.OurTeamCloseToWinning", "Announcer.EnemyTeamCloseToWinning" };
  505. for ( int i = 0 ; i < TF_TEAM_COUNT ; i++ )
  506. {
  507. m_eWinningMethod.Set( i, SCORE_UNDEFINED );
  508. }
  509. #else
  510. m_flLastTickSoundTime = 0.f;
  511. #endif
  512. }
  513. CTFRobotDestructionLogic::~CTFRobotDestructionLogic()
  514. {
  515. Assert( m_sCTFRobotDestructionLogic == this );
  516. if ( m_sCTFRobotDestructionLogic == this )
  517. m_sCTFRobotDestructionLogic = NULL;
  518. #ifdef GAME_DLL
  519. m_mapRateLimitedSounds.PurgeAndDeleteElements();
  520. #endif
  521. }
  522. void CTFRobotDestructionLogic::Spawn()
  523. {
  524. BaseClass::Spawn();
  525. Precache();
  526. #ifdef GAME_DLL
  527. V_strncpy( m_szResFile.GetForModify(), STRING( m_iszResFile ), MAX_PATH );
  528. #endif
  529. }
  530. void CTFRobotDestructionLogic::Precache()
  531. {
  532. BaseClass::Precache();
  533. PrecacheScriptSound( "Announcer.HowToPlayRD" );
  534. PrecacheScriptSound( "RD.TeamScoreCore" );
  535. PrecacheScriptSound( "RD.EnemyScoreCore" );
  536. PrecacheScriptSound( "RD.EnemyStealingPoints" );
  537. PrecacheScriptSound( "RD.FlagReturn" );
  538. PrecacheScriptSound( "RD.FinaleMusic" );
  539. #ifdef GAME_DLL
  540. PrecacheScriptSound( m_AnnouncerProgressSound.m_pszTheirTeam );
  541. PrecacheScriptSound( m_AnnouncerProgressSound.m_pszYourTeam );
  542. #endif
  543. }
  544. //-----------------------------------------------------------------------------
  545. // Purpose:
  546. //-----------------------------------------------------------------------------
  547. float CTFRobotDestructionLogic::GetRespawnScaleForTeam( int nTeam ) const
  548. {
  549. if ( nTeam == TF_TEAM_RED )
  550. {
  551. return m_flRedTeamRespawnScale;
  552. }
  553. else
  554. {
  555. return m_flBlueTeamRespawnScale;
  556. }
  557. }
  558. //-----------------------------------------------------------------------------
  559. // Purpose: Return the score for a team
  560. //-----------------------------------------------------------------------------
  561. int CTFRobotDestructionLogic::GetScore( int nTeam ) const
  562. {
  563. Assert( nTeam == TF_TEAM_RED || nTeam == TF_TEAM_BLUE );
  564. return nTeam == TF_TEAM_RED ? m_nRedScore.Get() : m_nBlueScore.Get();
  565. }
  566. //-----------------------------------------------------------------------------
  567. // Purpose: Return the target score that their real score will approach
  568. //-----------------------------------------------------------------------------
  569. int CTFRobotDestructionLogic::GetTargetScore( int nTeam ) const
  570. {
  571. Assert( nTeam == TF_TEAM_RED || nTeam == TF_TEAM_BLUE );
  572. return nTeam == TF_TEAM_RED ? m_nRedTargetPoints.Get() : m_nBlueTargetPoints.Get();
  573. }
  574. float CTFRobotDestructionLogic::GetFinaleWinTime( int nTeam ) const
  575. {
  576. Assert( nTeam == TF_TEAM_RED || nTeam == TF_TEAM_BLUE );
  577. return nTeam == TF_TEAM_RED ? m_flRedFinaleEndTime.Get() : m_flBlueFinaleEndTime.Get();
  578. }
  579. #ifdef GAME_DLL
  580. //-----------------------------------------------------------------------------
  581. // Purpose: Have scores approach target score
  582. //-----------------------------------------------------------------------------
  583. void CTFRobotDestructionLogic::ApproachTargetScoresThink()
  584. {
  585. // If the round is not in play, dont do anything with points
  586. if ( !TFGameRules()->FlagsMayBeCapped() )
  587. return;
  588. // Approach
  589. int nOldRedScore = m_nRedScore;
  590. m_nRedScore.Set( ApproachTeamTargetScore( TF_TEAM_RED, m_nRedTargetPoints, m_nRedScore.Get() ) );
  591. if ( nOldRedScore != m_nRedScore )
  592. {
  593. OnRedScoreChanged();
  594. }
  595. int m_nOldBlueScore = m_nBlueScore;
  596. m_nBlueScore.Set( ApproachTeamTargetScore( TF_TEAM_BLUE, m_nBlueTargetPoints, m_nBlueScore.Get() ) );
  597. if ( m_nOldBlueScore != m_nBlueScore )
  598. {
  599. OnBlueScoreChanged();
  600. }
  601. // Re-think if something is still off
  602. if ( m_nBlueTargetPoints != m_nBlueScore.Get() || m_nRedTargetPoints != m_nRedScore.Get() )
  603. {
  604. SetContextThink( &CTFRobotDestructionLogic::ApproachTargetScoresThink, gpGlobals->curtime + tf_rd_points_approach_interval.GetFloat(), APPROACH_POINTS_THINK );
  605. }
  606. }
  607. //-----------------------------------------------------------------------------
  608. // Purpose: Have score approach target score. Fire events regarding score.
  609. //-----------------------------------------------------------------------------
  610. int CTFRobotDestructionLogic::ApproachTeamTargetScore( int nTeam, int nApproachScore, int nCurrentScore )
  611. {
  612. if ( nApproachScore != nCurrentScore )
  613. {
  614. // Figure out which events we need
  615. COutputEvent& eventHitZeroPoints = nTeam == TF_TEAM_RED ? m_OnRedHitZeroPoints : m_OnBlueHitZeroPoints;
  616. COutputEvent& eventHasPoints = nTeam == TF_TEAM_RED ? m_OnRedHasPoints : m_OnBlueHasPoints;
  617. // Approach by 1 per interval
  618. int nDelta = clamp( nApproachScore - nCurrentScore, -tf_rd_points_per_approach.GetInt(), tf_rd_points_per_approach.GetInt() );
  619. int nNewScore = nCurrentScore + nDelta;
  620. // Enable the appropriate team flag if their score went from below to above min to steal
  621. if ( nCurrentScore < tf_rd_min_points_to_steal.GetInt() && nNewScore >= tf_rd_min_points_to_steal.GetInt() )
  622. {
  623. for ( int i=0; i<ICaptureFlagAutoList::AutoList().Count(); ++i )
  624. {
  625. CCaptureFlag *pFlag = static_cast< CCaptureFlag* >( ICaptureFlagAutoList::AutoList()[i] );
  626. if ( pFlag->GetTeamNumber() == nTeam )
  627. {
  628. pFlag->SetDisabled( false );
  629. }
  630. }
  631. }
  632. if ( nNewScore == m_nMaxPoints )
  633. {
  634. if ( nTeam == TF_TEAM_RED )
  635. {
  636. m_OnRedHitMaxPoints.FireOutput( this, this );
  637. m_flRedFinaleEndTime = gpGlobals->curtime + m_flFinaleLength;
  638. SetContextThink( &CTFRobotDestructionLogic::RedTeamWin, m_flRedFinaleEndTime, "RedWin" );
  639. if ( m_flBlueFinaleEndTime == FLT_MAX && GetType() == TYPE_ROBOT_DESTRUCTION )
  640. {
  641. // Announce the state change
  642. TFGameRules()->BroadcastSound( 255, "RD.FinaleMusic" );
  643. }
  644. }
  645. else
  646. {
  647. m_OnBlueHitMaxPoints.FireOutput( this, this );
  648. m_flBlueFinaleEndTime = gpGlobals->curtime + m_flFinaleLength;
  649. SetContextThink( &CTFRobotDestructionLogic::BlueTeamWin, m_flBlueFinaleEndTime, "BlueWin" );
  650. if ( m_flRedFinaleEndTime == FLT_MAX && GetType() == TYPE_ROBOT_DESTRUCTION )
  651. {
  652. // Announce the state change
  653. TFGameRules()->BroadcastSound( 255, "RD.FinaleMusic" );
  654. }
  655. }
  656. }
  657. else if ( nCurrentScore == m_nMaxPoints && nNewScore < m_nMaxPoints )
  658. {
  659. if ( nTeam == TF_TEAM_RED )
  660. {
  661. m_OnRedLeaveMaxPoints.FireOutput( this, this );
  662. m_flRedFinaleEndTime = FLT_MAX;
  663. SetContextThink( NULL, 0.f, "RedWin" );
  664. if ( m_flBlueFinaleEndTime == FLT_MAX )
  665. {
  666. CUtlVector< CTFPlayer* > vecAllPlayers;
  667. CollectHumanPlayers( &vecAllPlayers );
  668. FOR_EACH_VEC( vecAllPlayers, i )
  669. {
  670. CTFPlayer *pPlayer = vecAllPlayers[i];
  671. pPlayer->StopSound( "RD.FinaleMusic" );
  672. }
  673. }
  674. }
  675. else
  676. {
  677. m_OnBlueLeaveMaxPoints.FireOutput( this, this );
  678. m_flBlueFinaleEndTime = FLT_MAX;
  679. SetContextThink( NULL, 0.f, "BlueWin" );
  680. if ( m_flRedFinaleEndTime == FLT_MAX )
  681. {
  682. CUtlVector< CTFPlayer* > vecAllPlayers;
  683. CollectHumanPlayers( &vecAllPlayers );
  684. FOR_EACH_VEC( vecAllPlayers, i )
  685. {
  686. CTFPlayer *pPlayer = vecAllPlayers[i];
  687. pPlayer->StopSound( "RD.FinaleMusic" );
  688. }
  689. }
  690. }
  691. }
  692. else if ( nNewScore == 0 )
  693. {
  694. eventHitZeroPoints.FireOutput( this, this );
  695. }
  696. else if ( nCurrentScore == 0 && nNewScore > 0 )
  697. {
  698. eventHasPoints.FireOutput( this, this );
  699. }
  700. return nNewScore;
  701. }
  702. return nCurrentScore;
  703. }
  704. //-----------------------------------------------------------------------------
  705. // Purpose: Score nPoints for nTeam. Check for a victory.
  706. //-----------------------------------------------------------------------------
  707. void CTFRobotDestructionLogic::ScorePoints( int nTeam, int nPoints, RDScoreMethod_t eMethod, CTFPlayer *pPlayer )
  708. {
  709. // If the round is not in play, dont do anything with points
  710. if ( !TFGameRules()->FlagsMayBeCapped() )
  711. return;
  712. if ( nPoints == 0 )
  713. return;
  714. Assert( nTeam == TF_TEAM_RED || nTeam == TF_TEAM_BLUE );
  715. // Set the target score
  716. int nTargetScore = 0;
  717. if ( nTeam == TF_TEAM_RED )
  718. {
  719. nTargetScore = m_nRedTargetPoints = clamp ( m_nRedTargetPoints + nPoints, 0, m_nMaxPoints.Get() );
  720. }
  721. else
  722. {
  723. nTargetScore = m_nBlueTargetPoints = clamp ( m_nBlueTargetPoints + nPoints, 0, m_nMaxPoints.Get() );
  724. }
  725. if ( GetNextThink( APPROACH_POINTS_THINK ) == TICK_NEVER_THINK )
  726. {
  727. SetContextThink( &CTFRobotDestructionLogic::ApproachTargetScoresThink, gpGlobals->curtime + tf_rd_points_approach_interval.GetFloat(), APPROACH_POINTS_THINK );
  728. }
  729. int nOldScore = nTeam == TF_TEAM_RED ? m_nRedScore.Get() : m_nBlueScore.Get();
  730. // Can't do anything if we're already at max and adding points
  731. if ( nOldScore == m_nMaxPoints && nPoints > 0 )
  732. {
  733. return;
  734. }
  735. // Or if at 0 and substracting points
  736. if ( nOldScore == 0 && nPoints < 0 )
  737. {
  738. return;
  739. }
  740. // is this going to cause a win? store the method.
  741. if ( nOldScore != nTargetScore )
  742. {
  743. m_eWinningMethod.Set( nTeam, eMethod );
  744. }
  745. int nNewScore = Clamp( nOldScore + nPoints, 0, m_nMaxPoints.Get() );
  746. // We want to play different sounds based on the player's team
  747. CUtlVector< CTFPlayer* > vecAllPlayers;
  748. CollectHumanPlayers( &vecAllPlayers );
  749. FOR_EACH_VEC( vecAllPlayers, i )
  750. {
  751. CTFPlayer* pSoundPlayer = vecAllPlayers[i];
  752. bool bPositive = ( pSoundPlayer->GetTeamNumber() == nTeam && nPoints > 0 ) || ( pSoundPlayer->GetTeamNumber() != nTeam && nPoints < 0 );
  753. PlaySoundInfoForScoreEvent( pSoundPlayer, bPositive, nPoints, nTeam, eMethod );
  754. }
  755. // Earn 1 score point for every 10 bonus points
  756. if ( pPlayer && nPoints > 0 )
  757. {
  758. CTF_GameStats.Event_PlayerAwardBonusPoints( pPlayer, NULL, ( nPoints ) );
  759. }
  760. // Possibly have the announcer speak about how close the team is to winning if the
  761. // score was made by picking up a power core
  762. const int nCloseToWinningThreshold = (5.f / 6.f) * m_nMaxPoints;
  763. if ( eMethod == SCORE_CORES_COLLECTED && ( nOldScore < nCloseToWinningThreshold ) && ( nNewScore >= nCloseToWinningThreshold ) && GetType() == TYPE_ROBOT_DESTRUCTION )
  764. {
  765. TFGameRules()->BroadcastSound( nTeam, m_AnnouncerProgressSound.m_pszYourTeam );
  766. TFGameRules()->BroadcastSound( GetEnemyTeam( nTeam ), m_AnnouncerProgressSound.m_pszTheirTeam );
  767. }
  768. short nDelta = nNewScore - nOldScore;
  769. if ( nDelta != 0 )
  770. {
  771. const char *pszEventName = "RDTeamPointsChanged";
  772. CBroadcastRecipientFilter filter;
  773. filter.MakeReliable();
  774. UserMessageBegin( filter, pszEventName );
  775. WRITE_SHORT( nDelta );
  776. WRITE_BYTE( nTeam );
  777. WRITE_BYTE( (int)eMethod );
  778. MessageEnd();
  779. if ( pPlayer )
  780. {
  781. IGameEvent *pScoreEvent = gameeventmanager->CreateEvent( "rd_player_score_points" );
  782. if ( pScoreEvent )
  783. {
  784. pScoreEvent->SetInt( "player", pPlayer->GetUserID() );
  785. pScoreEvent->SetInt( "method", (int)eMethod );
  786. pScoreEvent->SetInt( "amount", nDelta );
  787. gameeventmanager->FireEvent( pScoreEvent );
  788. }
  789. }
  790. }
  791. }
  792. //-----------------------------------------------------------------------------
  793. // Purpose:
  794. //-----------------------------------------------------------------------------
  795. void CTFRobotDestructionLogic::InputRoundActivate( inputdata_t &/*inputdata*/ )
  796. {
  797. FOR_EACH_VEC( m_vecSpawnGroups, i )
  798. {
  799. m_vecSpawnGroups[ i ]->RespawnRobots();
  800. }
  801. }
  802. #endif
  803. //-----------------------------------------------------------------------------
  804. // Purpose: Give us the One True Robot Destruction Llgic
  805. //-----------------------------------------------------------------------------
  806. CTFRobotDestructionLogic* CTFRobotDestructionLogic::GetRobotDestructionLogic()
  807. {
  808. return m_sCTFRobotDestructionLogic;
  809. }
  810. CTFRobotDestructionLogic* CTFRobotDestructionLogic::m_sCTFRobotDestructionLogic = NULL;
  811. void CTFRobotDestructionLogic::PlaySoundInfoForScoreEvent( CTFPlayer* pPlayer, bool bPositive, int nNewScore, int nTeam, RDScoreMethod_t eMethod )
  812. {
  813. if ( !pPlayer )
  814. return;
  815. eMethod = eMethod == SCORE_UNDEFINED ? (RDScoreMethod_t)m_eWinningMethod[ nTeam ] : eMethod;
  816. EmitSound_t params;
  817. float soundlen = 0;
  818. params.m_flSoundTime = 0;
  819. params.m_pSoundName = NULL;
  820. params.m_pflSoundDuration = &soundlen;
  821. switch ( eMethod )
  822. {
  823. case SCORE_CORES_COLLECTED:
  824. {
  825. params.m_pSoundName = bPositive ? "RD.TeamScoreCore" : "RD.EnemyScoreCore";
  826. params.m_nPitch = RemapValClamped( nNewScore, m_nMaxPoints * 0.75, m_nMaxPoints, 100, 120 );
  827. params.m_nFlags |= SND_CHANGE_PITCH;
  828. params.m_flVolume = 0.25f;
  829. params.m_nFlags |= SND_CHANGE_VOL;
  830. break;
  831. }
  832. case SCORE_REACTOR_CAPTURED:
  833. case SCORE_REACTOR_RETURNED:
  834. {
  835. params.m_pSoundName = "RD.FlagReturn";
  836. break;
  837. }
  838. case SCORE_REACTOR_STEAL:
  839. {
  840. params.m_pSoundName = bPositive ? "MVM.PlayerUpgraded" : "RD.EnemyStealingPoints";
  841. break;
  842. }
  843. default:
  844. {
  845. // By default nothing
  846. }
  847. }
  848. if ( params.m_pSoundName )
  849. {
  850. #ifdef GAME_DLL
  851. PlaySoundInPlayersEars( pPlayer, params );
  852. #else
  853. pPlayer->StopSound( params.m_pSoundName );
  854. CBroadcastRecipientFilter filter;
  855. pPlayer->EmitSound( filter, pPlayer->entindex(), params );
  856. #endif
  857. }
  858. }
  859. #ifdef CLIENT_DLL
  860. void CTFRobotDestructionLogic::OnDataChanged( DataUpdateType_t type )
  861. {
  862. BaseClass::OnDataChanged( type );
  863. float flSoonestFinale = Min( m_flBlueFinaleEndTime.Get(), m_flRedFinaleEndTime.Get() ) - gpGlobals->curtime;
  864. if ( flSoonestFinale <= m_flFinaleLength && m_flLastTickSoundTime == 0.f )
  865. {
  866. float flFirstBeepTime = flSoonestFinale - tf_rd_finale_beep_time.GetFloat();
  867. SetNextClientThink( gpGlobals->curtime + flFirstBeepTime );
  868. }
  869. }
  870. void CTFRobotDestructionLogic::ClientThink()
  871. {
  872. float flSoonestFinale = Min( m_flBlueFinaleEndTime.Get(), m_flRedFinaleEndTime.Get() ) - gpGlobals->curtime;
  873. if ( flSoonestFinale <= tf_rd_finale_beep_time.GetFloat() && flSoonestFinale > 0.f)
  874. {
  875. SetNextClientThink( gpGlobals->curtime + 1.f );
  876. // Play a beeping sound that gets louder the closer we get to finishing
  877. C_BasePlayer* pPlayer = C_BasePlayer::GetLocalPlayer();
  878. if ( pPlayer )
  879. {
  880. bool bLastTick = flSoonestFinale <= 1.f;
  881. float flExcitementScale = RemapValClamped( Bias( 1.f - ( flSoonestFinale / tf_rd_finale_beep_time.GetFloat() ), 0.2f ), 0.f, 1.f, 0.3f, 1.f );
  882. float soundlen = 0;
  883. EmitSound_t params;
  884. params.m_flSoundTime = 0;
  885. params.m_pSoundName = bLastTick ? "Weapon_Grenade_Det_Pack.Timer" : "RD.FinaleBeep";
  886. params.m_pflSoundDuration = &soundlen;
  887. params.m_flVolume = flExcitementScale;
  888. params.m_nPitch = bLastTick ? PITCH_NORM : PITCH_NORM * ( 1.f + flExcitementScale );
  889. params.m_nFlags |= SND_CHANGE_VOL | SND_CHANGE_PITCH;
  890. CBroadcastRecipientFilter filter;
  891. pPlayer->EmitSound( filter, pPlayer->entindex(), params );
  892. }
  893. }
  894. }
  895. #endif
  896. #ifdef GAME_DLL
  897. void CTFRobotDestructionLogic::Activate()
  898. {
  899. BaseClass::Activate();
  900. IGameEvent *event = gameeventmanager->CreateEvent( "rd_rules_state_changed" );
  901. if ( event )
  902. {
  903. gameeventmanager->FireEventClientSide( event );
  904. }
  905. }
  906. void CTFRobotDestructionLogic::FireGameEvent( IGameEvent * event )
  907. {
  908. const char *pszName = event->GetName();
  909. if( FStrEq( pszName, "teamplay_pre_round_time_left" ) )
  910. {
  911. int nTimeLeft = event->GetInt( "time" );
  912. // The round has started. After this point, when players connect and spawn we want to play the sound
  913. if ( nTimeLeft == 0 )
  914. {
  915. m_bEducateNewConnectors = true;
  916. }
  917. // At the 20 second mark we want to play a sound for all the players
  918. else if ( nTimeLeft == 20 )
  919. {
  920. CUtlVector< CTFPlayer* > vecAllPlayers;
  921. CollectHumanPlayers( &vecAllPlayers );
  922. FOR_EACH_VEC( vecAllPlayers, i )
  923. {
  924. CTFPlayer *pPlayer = vecAllPlayers[i];
  925. // Ony play the sound for players that are alive
  926. if ( !pPlayer->IsAlive() )
  927. {
  928. continue;
  929. }
  930. // Only play the sound for players who havent heard it
  931. if ( m_vecEducatedPlayers.Find( pPlayer ) == m_vecEducatedPlayers.InvalidIndex() )
  932. {
  933. // Remember who has heard the sound
  934. m_vecEducatedPlayers.AddToTail( pPlayer );
  935. float soundlen = 0;
  936. EmitSound_t params;
  937. params.m_flSoundTime = 0;
  938. params.m_pSoundName = "Announcer.HowToPlayRD";
  939. params.m_pflSoundDuration = &soundlen;
  940. PlaySoundInPlayersEars( pPlayer, params );
  941. }
  942. }
  943. }
  944. }
  945. else if ( FStrEq( pszName, "player_spawn" ) )
  946. {
  947. // If we're not telling players yet, then skip
  948. if ( !m_bEducateNewConnectors )
  949. return;
  950. const int nUserID = event->GetInt( "userid" );
  951. CTFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByUserId( nUserID ) );
  952. // If the just spawned and havent heard the sound, play the sound
  953. if ( pPlayer && pPlayer->IsAlive() && m_vecEducatedPlayers.Find( pPlayer ) == m_vecEducatedPlayers.InvalidIndex() )
  954. {
  955. // Remember who heard the sound
  956. m_vecEducatedPlayers.AddToTail( pPlayer );
  957. float soundlen = 0;
  958. EmitSound_t params;
  959. params.m_flSoundTime = 0;
  960. params.m_pSoundName = "Announcer.HowToPlayRD";
  961. params.m_pflSoundDuration = &soundlen;
  962. PlaySoundInPlayersEars( pPlayer, params );
  963. }
  964. }
  965. }
  966. //-----------------------------------------------------------------------------
  967. // Purpose: Givin a pointer to a robot, give the next in the list. If NULL,
  968. // return the first in the list.
  969. //-----------------------------------------------------------------------------
  970. CTFRobotDestruction_Robot* CTFRobotDestructionLogic::IterateRobots( CTFRobotDestruction_Robot* pRobot ) const
  971. {
  972. int nIndex = m_vecRobots.Find( pRobot );
  973. // Not found? Return the head
  974. if ( nIndex == -1 && m_vecRobots.Count() )
  975. return m_vecRobots.Head();
  976. // Found, but at the end? Return NULL
  977. if ( (nIndex + 1) >= m_vecRobots.Count() )
  978. return NULL;
  979. // Return the next
  980. return m_vecRobots[ nIndex + 1 ];
  981. }
  982. void CTFRobotDestructionLogic::AddRobotGroup( CTFRobotDestruction_RobotGroup* pGroup )
  983. {
  984. Assert( m_vecSpawnGroups.Find( pGroup ) == m_vecSpawnGroups.InvalidIndex() );
  985. FOR_EACH_VEC( m_vecSpawnGroups, i )
  986. {
  987. Assert( m_vecSpawnGroups[i]->GetGroupNumber() != pGroup->GetGroupNumber()
  988. || m_vecSpawnGroups[i]->GetTeamNumber() != pGroup->GetTeamNumber() );
  989. }
  990. m_vecSpawnGroups.AddToTail( pGroup );
  991. IGameEvent *event = gameeventmanager->CreateEvent( "rd_rules_state_changed" );
  992. if ( event )
  993. {
  994. gameeventmanager->FireEventClientSide( event );
  995. }
  996. }
  997. void CTFRobotDestructionLogic::ManageGameState()
  998. {
  999. // Put all the groups into team-based vectors
  1000. CUtlVector< CTFRobotDestruction_RobotGroup * > vecTeamGroups[ TF_TEAM_COUNT ];
  1001. FOR_EACH_VEC( m_vecSpawnGroups, i )
  1002. {
  1003. vecTeamGroups[ m_vecSpawnGroups[i]->GetTeamNumber() ].AddToTail( m_vecSpawnGroups[i] );
  1004. }
  1005. CTFRobotDestruction_RobotGroup *pLowestAlive[ TF_TEAM_COUNT ];
  1006. CTFRobotDestruction_RobotGroup *pHighestDead[ TF_TEAM_COUNT ];
  1007. // Find the highest group-numbered group with no living bots, and the lowest group-numbered group
  1008. // with any alive bots
  1009. for( int i = 0; i < TF_TEAM_COUNT; ++i )
  1010. {
  1011. pLowestAlive[ i ] = GetLowestAlive( vecTeamGroups[ i ] );
  1012. pHighestDead[ i ] = GetHighestDead( vecTeamGroups[ i ] );
  1013. }
  1014. // Reset respawn bonus times to 0. They'll get updated below
  1015. m_flRedTeamRespawnScale = m_flBlueTeamRespawnScale = 0.f;
  1016. // Go through and change the state of the bots
  1017. for( int nTeam = 0; nTeam < TF_TEAM_COUNT; ++nTeam )
  1018. {
  1019. // Skip empty groups
  1020. if ( vecTeamGroups[ nTeam ].Count() == 0 )
  1021. continue;
  1022. CTFRobotDestruction_RobotGroup *pLowest = pLowestAlive[ nTeam ];
  1023. CTFRobotDestruction_RobotGroup *pHighest = pHighestDead[ nTeam ];
  1024. bool bHighestAlreadyRespawning = false;
  1025. // The highest dead group is the master respawning group
  1026. if ( pHighest )
  1027. {
  1028. bHighestAlreadyRespawning = pHighest->GetNextThink( GROUP_RESPAWN_CONTEXT ) != TICK_NEVER_THINK;
  1029. pHighest->StartRespawnTimerIfNeeded( pHighest );
  1030. if ( nTeam == TF_TEAM_RED )
  1031. {
  1032. m_flRedTeamRespawnScale = pHighest->GetTeamRespawnScale();
  1033. }
  1034. else
  1035. {
  1036. m_flBlueTeamRespawnScale = pHighest->GetTeamRespawnScale();
  1037. }
  1038. }
  1039. // The lowest alive group is the only non-uber group
  1040. if ( pLowest )
  1041. {
  1042. pLowest->DisableUberForGroup();
  1043. }
  1044. bool bAllDead = true;
  1045. FOR_EACH_VEC( vecTeamGroups[ nTeam ], i )
  1046. {
  1047. CTFRobotDestruction_RobotGroup *pGroup = vecTeamGroups[ nTeam ][ i ];
  1048. bAllDead &= pGroup->GetNumAliveBots() == 0;
  1049. // The non-lowest alive groups are ubered
  1050. if ( pGroup != pLowest )
  1051. {
  1052. pGroup->EnableUberForGroup();
  1053. }
  1054. // The non-highest dead groups respawn when the highest-dead group respawns
  1055. if ( pGroup != pHighest )
  1056. {
  1057. pGroup->StartRespawnTimerIfNeeded( pHighest );
  1058. }
  1059. }
  1060. }
  1061. }
  1062. //-----------------------------------------------------------------------------
  1063. // Purpose: Plays a sound in a player's ears
  1064. //-----------------------------------------------------------------------------
  1065. void CTFRobotDestructionLogic::PlaySoundInPlayersEars( CTFPlayer* pPlayer, const EmitSound_t& params ) const
  1066. {
  1067. int nIndex = m_mapRateLimitedSounds.Find( params.m_pSoundName );
  1068. if ( nIndex != m_mapRateLimitedSounds.InvalidIndex() )
  1069. {
  1070. RateLimitedSound_t* pSound = m_mapRateLimitedSounds[ nIndex ];
  1071. int nPlayerIndex = pSound->m_mapNextAllowedTime.Find( pPlayer );
  1072. if ( nPlayerIndex == pSound->m_mapNextAllowedTime.InvalidIndex() )
  1073. {
  1074. nPlayerIndex = pSound->m_mapNextAllowedTime.Insert( pPlayer );
  1075. pSound->m_mapNextAllowedTime[ nPlayerIndex ] = 0.f;
  1076. }
  1077. float& flNextAllowedTime = pSound->m_mapNextAllowedTime[ nPlayerIndex ];
  1078. // If we're not allowed to play, then return
  1079. if ( flNextAllowedTime > gpGlobals->curtime )
  1080. {
  1081. return;
  1082. }
  1083. // Mark the next time we're allowed to play
  1084. flNextAllowedTime = gpGlobals->curtime + m_mapRateLimitedSounds[ nIndex ]->m_flPause;
  1085. }
  1086. // Play in the player's ears
  1087. CSingleUserRecipientFilter filter( pPlayer );
  1088. filter.MakeReliable();
  1089. if ( params.m_nFlags & SND_CHANGE_PITCH )
  1090. {
  1091. pPlayer->StopSound( params.m_pSoundName );
  1092. }
  1093. pPlayer->EmitSound( filter, pPlayer->entindex(), params );
  1094. }
  1095. //-----------------------------------------------------------------------------
  1096. // Purpose:
  1097. //-----------------------------------------------------------------------------
  1098. void CTFRobotDestructionLogic::RedTeamWin()
  1099. {
  1100. TeamWin( TF_TEAM_RED );
  1101. }
  1102. //-----------------------------------------------------------------------------
  1103. // Purpose:
  1104. //-----------------------------------------------------------------------------
  1105. void CTFRobotDestructionLogic::BlueTeamWin()
  1106. {
  1107. TeamWin( TF_TEAM_BLUE );
  1108. }
  1109. //-----------------------------------------------------------------------------
  1110. // Purpose:
  1111. //-----------------------------------------------------------------------------
  1112. void CTFRobotDestructionLogic::TeamWin( int nTeam )
  1113. {
  1114. RDScoreMethod_t eMethod = (RDScoreMethod_t)m_eWinningMethod.Get( nTeam );
  1115. if ( TFGameRules() )
  1116. {
  1117. TFGameRules()->SetWinningTeam( nTeam, ( eMethod == SCORE_REACTOR_CAPTURED ) ? WINREASON_RD_REACTOR_CAPTURED : ( ( eMethod == SCORE_CORES_COLLECTED ) ? WINREASON_RD_CORES_COLLECTED : WINREASON_RD_REACTOR_RETURNED ) );
  1118. }
  1119. }
  1120. //-----------------------------------------------------------------------------
  1121. // Purpose:
  1122. //-----------------------------------------------------------------------------
  1123. void CTFRobotDestructionLogic::FlagCreated( int nTeam )
  1124. {
  1125. if ( nTeam == TF_TEAM_RED )
  1126. {
  1127. m_OnRedFlagStolen.FireOutput( this, this );
  1128. if ( m_nNumFlagsOut[ nTeam ] == 0 )
  1129. {
  1130. m_OnRedFirstFlagStolen.FireOutput( this, this );
  1131. }
  1132. }
  1133. else
  1134. {
  1135. m_OnBlueFlagStolen.FireOutput( this, this );
  1136. if ( m_nNumFlagsOut[ nTeam ] == 0 )
  1137. {
  1138. m_OnBlueFirstFlagStolen.FireOutput( this, this );
  1139. }
  1140. }
  1141. ++m_nNumFlagsOut[ nTeam ];
  1142. }
  1143. //-----------------------------------------------------------------------------
  1144. // Purpose:
  1145. //-----------------------------------------------------------------------------
  1146. void CTFRobotDestructionLogic::FlagDestroyed( int nTeam )
  1147. {
  1148. if ( m_nNumFlagsOut[ nTeam ] == 1 )
  1149. {
  1150. if ( nTeam == TF_TEAM_RED )
  1151. {
  1152. m_OnRedLastFlagReturned.FireOutput( this, this );
  1153. }
  1154. else
  1155. {
  1156. m_OnBlueLastFlagReturned.FireOutput( this, this );
  1157. }
  1158. }
  1159. --m_nNumFlagsOut[ nTeam ];
  1160. }
  1161. //-----------------------------------------------------------------------------
  1162. // Purpose: Add a given robot to our list of robots. Increment our count of
  1163. // robots for the team that the robot is on
  1164. //-----------------------------------------------------------------------------
  1165. void CTFRobotDestructionLogic::RobotCreated( CTFRobotDestruction_Robot *pRobot )
  1166. {
  1167. m_vecRobots.AddToTail( pRobot );
  1168. }
  1169. //-----------------------------------------------------------------------------
  1170. // Purpose: Remove a robot from our list. Decrement our count of robots for
  1171. // the team that the robot was on
  1172. //-----------------------------------------------------------------------------
  1173. void CTFRobotDestructionLogic::RobotRemoved( CTFRobotDestruction_Robot *pRobot )
  1174. {
  1175. m_vecRobots.FindAndRemove( pRobot );
  1176. }
  1177. //-----------------------------------------------------------------------------
  1178. // Purpose: Perform alerts when a robot is attacked
  1179. //-----------------------------------------------------------------------------
  1180. void CTFRobotDestructionLogic::RobotAttacked( CTFRobotDestruction_Robot *pRobot )
  1181. {
  1182. float& flNextAlertTime = ( pRobot->GetTeamNumber() == TF_TEAM_RED ) ? m_flNextRedRobotAttackedAlertTime
  1183. : m_flNextBlueRobotAttackedAlertTime;
  1184. if ( gpGlobals->curtime >= flNextAlertTime )
  1185. {
  1186. flNextAlertTime = gpGlobals->curtime + tf_rd_robot_attack_notification_cooldown.GetFloat();
  1187. CTeamRecipientFilter filter( pRobot->GetTeamNumber(), true );
  1188. TFGameRules()->SendHudNotification( filter, HUD_NOTIFY_RD_ROBOT_UNDER_ATTACK );
  1189. }
  1190. }
  1191. BEGIN_DATADESC( CTFRobotDestructionLogic )
  1192. DEFINE_INPUTFUNC( FIELD_VOID, "RoundActivate", InputRoundActivate ),
  1193. DEFINE_OUTPUT( m_OnRedHitZeroPoints, "OnRedHitZeroPoints" ),
  1194. DEFINE_OUTPUT( m_OnRedHasPoints, "OnRedHasPoints" ),
  1195. DEFINE_OUTPUT( m_OnRedFinalePeriodEnd, "OnRedFinalePeriodEnd" ),
  1196. DEFINE_OUTPUT( m_OnBlueHitZeroPoints, "OnBlueHitZeroPoints" ),
  1197. DEFINE_OUTPUT( m_OnBlueHasPoints, "OnBlueHasPoints" ),
  1198. DEFINE_OUTPUT( m_OnBlueFinalePeriodEnd, "OnBlueFinalePeriodEnd" ),
  1199. DEFINE_OUTPUT( m_OnRedFirstFlagStolen, "OnRedFirstFlagStolen" ),
  1200. DEFINE_OUTPUT( m_OnRedFlagStolen, "OnRedFlagStolen" ),
  1201. DEFINE_OUTPUT( m_OnRedLastFlagReturned, "OnRedLastFlagReturned" ),
  1202. DEFINE_OUTPUT( m_OnBlueFirstFlagStolen, "OnBlueFirstFlagStolen" ),
  1203. DEFINE_OUTPUT( m_OnBlueFlagStolen, "OnBlueFlagStolen" ),
  1204. DEFINE_OUTPUT( m_OnBlueLastFlagReturned, "OnBlueLastFlagReturned" ),
  1205. DEFINE_OUTPUT( m_OnBlueLeaveMaxPoints, "OnBlueLeaveMaxPoints" ),
  1206. DEFINE_OUTPUT( m_OnRedLeaveMaxPoints, "OnRedLeaveMaxPoints" ),
  1207. DEFINE_OUTPUT( m_OnBlueHitMaxPoints, "OnBlueHitMaxPoints" ),
  1208. DEFINE_OUTPUT( m_OnRedHitMaxPoints, "OnRedHitMaxPoints" ),
  1209. DEFINE_KEYFIELD( m_flRobotScoreInterval, FIELD_FLOAT, "score_interval" ),
  1210. DEFINE_KEYFIELD( m_flLoserRespawnBonusPerBot, FIELD_FLOAT, "loser_respawn_bonus_per_bot" ),
  1211. DEFINE_KEYFIELD( m_nMaxPoints, FIELD_INTEGER, "max_points" ),
  1212. DEFINE_KEYFIELD( m_flFinaleLength, FIELD_FLOAT, "finale_length" ),
  1213. DEFINE_KEYFIELD( m_iszResFile, FIELD_STRING, "res_file" ),
  1214. END_DATADESC()
  1215. #endif
  1216. LINK_ENTITY_TO_CLASS( tf_logic_robot_destruction, CTFRobotDestructionLogic );
  1217. IMPLEMENT_NETWORKCLASS_ALIASED( TFRobotDestructionLogic, DT_TFRobotDestructionLogic )
  1218. BEGIN_NETWORK_TABLE_NOBASE( CTFRobotDestructionLogic, DT_TFRobotDestructionLogic )
  1219. #ifdef CLIENT_DLL
  1220. RecvPropInt( RECVINFO( m_nMaxPoints ) ),
  1221. RecvPropInt( RECVINFO( m_nBlueScore ) ),
  1222. RecvPropInt( RECVINFO( m_nRedScore ) ),
  1223. RecvPropInt( RECVINFO( m_nBlueTargetPoints ) ),
  1224. RecvPropInt( RECVINFO( m_nRedTargetPoints ) ),
  1225. RecvPropFloat( RECVINFO( m_flBlueTeamRespawnScale ) ),
  1226. RecvPropFloat( RECVINFO( m_flRedTeamRespawnScale ) ),
  1227. RecvPropFloat( RECVINFO( m_flBlueFinaleEndTime ) ),
  1228. RecvPropFloat( RECVINFO( m_flRedFinaleEndTime ) ),
  1229. RecvPropFloat( RECVINFO( m_flFinaleLength ) ),
  1230. RecvPropString( RECVINFO( m_szResFile ) ),
  1231. RecvPropArray3( RECVINFO_ARRAY( m_eWinningMethod ), RecvPropInt( RECVINFO( m_eWinningMethod[0] ) ) ),
  1232. RecvPropFloat( RECVINFO( m_flCountdownEndTime ) ),
  1233. #else
  1234. SendPropInt( SENDINFO( m_nMaxPoints ), -1, SPROP_VARINT | SPROP_UNSIGNED ),
  1235. SendPropInt( SENDINFO( m_nBlueScore ), -1, SPROP_VARINT | SPROP_UNSIGNED ),
  1236. SendPropInt( SENDINFO( m_nRedScore ), -1, SPROP_VARINT | SPROP_UNSIGNED ),
  1237. SendPropInt( SENDINFO( m_nBlueTargetPoints ), -1, SPROP_VARINT | SPROP_UNSIGNED ),
  1238. SendPropInt( SENDINFO( m_nRedTargetPoints ), -1, SPROP_VARINT | SPROP_UNSIGNED ),
  1239. SendPropFloat( SENDINFO( m_flBlueTeamRespawnScale ), -1, SPROP_NOSCALE ),
  1240. SendPropFloat( SENDINFO( m_flRedTeamRespawnScale ), -1, SPROP_NOSCALE ),
  1241. SendPropFloat( SENDINFO( m_flBlueFinaleEndTime ), -1, SPROP_NOSCALE ),
  1242. SendPropFloat( SENDINFO( m_flRedFinaleEndTime ), -1, SPROP_NOSCALE ),
  1243. SendPropFloat( SENDINFO( m_flFinaleLength ), -1, SPROP_NOSCALE ),
  1244. SendPropString( SENDINFO( m_szResFile ) ),
  1245. SendPropArray3( SENDINFO_ARRAY3( m_eWinningMethod ), SendPropInt( SENDINFO_ARRAY( m_eWinningMethod ), -1, SPROP_UNSIGNED | SPROP_VARINT ) ),
  1246. SendPropFloat( SENDINFO( m_flCountdownEndTime ), -1, SPROP_NOSCALE ),
  1247. #endif
  1248. END_NETWORK_TABLE()
  1249. #ifdef GAME_DLL
  1250. LINK_ENTITY_TO_CLASS( trigger_rd_vault_trigger, CRobotDestructionVaultTrigger );
  1251. BEGIN_DATADESC( CRobotDestructionVaultTrigger )
  1252. DEFINE_OUTPUT( m_OnPointsStolen, "OnPointsStolen" ),
  1253. DEFINE_OUTPUT( m_OnPointsStartStealing, "OnPointsStartStealing" ),
  1254. DEFINE_OUTPUT( m_OnPointsEndStealing, "OnPointsEndStealing" ),
  1255. END_DATADESC()
  1256. CRobotDestructionVaultTrigger::CRobotDestructionVaultTrigger()
  1257. : m_bIsStealing( false )
  1258. {}
  1259. void CRobotDestructionVaultTrigger::Spawn()
  1260. {
  1261. BaseClass::Spawn();
  1262. InitTrigger();
  1263. }
  1264. void CRobotDestructionVaultTrigger::Precache()
  1265. {
  1266. BaseClass::Precache();
  1267. PrecacheScriptSound( "Cart.WarningSingle" );
  1268. }
  1269. bool CRobotDestructionVaultTrigger::PassesTriggerFilters( CBaseEntity *pOther )
  1270. {
  1271. if ( pOther->GetTeamNumber() == GetTeamNumber() )
  1272. return false;
  1273. // Only allow these entities
  1274. if ( !pOther->ClassMatches( "player" ) )
  1275. return false;
  1276. return true;
  1277. }
  1278. void CRobotDestructionVaultTrigger::StartTouch(CBaseEntity *pOther)
  1279. {
  1280. if ( !PassesTriggerFilters( pOther ) )
  1281. return;
  1282. BaseClass::StartTouch( pOther );
  1283. // This is the first guy to touch us. Start thinking
  1284. if ( m_hTouchingEntities.Count() == 1 )
  1285. {
  1286. SetContextThink( &CRobotDestructionVaultTrigger::StealPointsThink, gpGlobals->curtime, ADD_POINTS_CONTEXT );
  1287. }
  1288. }
  1289. void CRobotDestructionVaultTrigger::EndTouch(CBaseEntity *pOther)
  1290. {
  1291. BaseClass::EndTouch( pOther );
  1292. // Last guy stopped touching us. Stop thinking
  1293. if ( m_hTouchingEntities.Count() == 0 )
  1294. {
  1295. SetContextThink( NULL, 0, ADD_POINTS_CONTEXT );
  1296. }
  1297. // Force the stealing player to drop the flag if they didnt steal enough points
  1298. CTFPlayer *pPlayer = dynamic_cast< CTFPlayer * >( pOther );
  1299. if ( pPlayer )
  1300. {
  1301. CCaptureFlag *pFlag = dynamic_cast< CCaptureFlag * >( pPlayer->GetItem() );
  1302. if ( pFlag )
  1303. {
  1304. if ( pFlag->GetPointValue() < tf_rd_min_points_to_steal.GetInt() )
  1305. {
  1306. pFlag->Drop( pPlayer, true, true );
  1307. pFlag->ResetFlag();
  1308. // TODO: Play negative sound in player's ears
  1309. }
  1310. if ( m_bIsStealing )
  1311. {
  1312. // If the flag carrier is leaving us, we're done stealing
  1313. m_bIsStealing = false;
  1314. m_OnPointsEndStealing.FireOutput( this, this );
  1315. }
  1316. }
  1317. }
  1318. }
  1319. void CRobotDestructionVaultTrigger::StealPointsThink()
  1320. {
  1321. // Do it again!
  1322. SetContextThink( &CRobotDestructionVaultTrigger::StealPointsThink, gpGlobals->curtime + tf_rd_steal_rate.GetFloat(), ADD_POINTS_CONTEXT );
  1323. int nNumStolen = 0;
  1324. FOR_EACH_VEC( m_hTouchingEntities, i )
  1325. {
  1326. CTFPlayer *pPlayer = static_cast< CTFPlayer * >( m_hTouchingEntities[i].Get() );
  1327. if ( pPlayer )
  1328. {
  1329. CCaptureFlag *pFlag = dynamic_cast< CCaptureFlag * >( pPlayer->GetItem() );
  1330. if ( pFlag )
  1331. {
  1332. nNumStolen = StealPoints( pPlayer );
  1333. }
  1334. }
  1335. }
  1336. // Check to fire the stealing outputs
  1337. if ( nNumStolen > 0 )
  1338. {
  1339. m_OnPointsStolen.FireOutput( this, this );
  1340. }
  1341. if ( nNumStolen && !m_bIsStealing )
  1342. {
  1343. m_OnPointsStartStealing.FireOutput( this, this );
  1344. }
  1345. else if ( !nNumStolen && m_bIsStealing )
  1346. {
  1347. m_OnPointsEndStealing.FireOutput( this, this );
  1348. }
  1349. m_bIsStealing = nNumStolen != 0;
  1350. }
  1351. int CRobotDestructionVaultTrigger::StealPoints( CTFPlayer *pPlayer )
  1352. {
  1353. CCaptureFlag *pFlag = dynamic_cast<CCaptureFlag*>( pPlayer->GetItem() );
  1354. if ( pFlag && CTFRobotDestructionLogic::GetRobotDestructionLogic() )
  1355. {
  1356. int nEnemyTeamNumber = GetEnemyTeam( pPlayer->GetTeamNumber() );
  1357. int nEnemyPoints = CTFRobotDestructionLogic::GetRobotDestructionLogic()->GetTargetScore( nEnemyTeamNumber );
  1358. if ( nEnemyPoints )
  1359. {
  1360. int nPointsToSteal = Min( nEnemyPoints, tf_rd_points_per_steal.GetInt() );
  1361. pFlag->AddPointValue( nPointsToSteal );
  1362. CTFRobotDestructionLogic::GetRobotDestructionLogic()->ScorePoints( nEnemyTeamNumber
  1363. , -nPointsToSteal
  1364. , SCORE_REACTOR_STEAL
  1365. , pPlayer );
  1366. SetContextThink( &CRobotDestructionVaultTrigger::StealPointsThink, gpGlobals->curtime + tf_rd_steal_rate.GetFloat(), ADD_POINTS_CONTEXT );
  1367. return nPointsToSteal;
  1368. }
  1369. }
  1370. return 0;
  1371. }
  1372. #endif