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.

1823 lines
51 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //=============================================================================
  6. #include "cbase.h"
  7. #ifdef CLIENT_DLL
  8. #include "achievementmgr.h"
  9. #include "baseachievement.h"
  10. #include "tf_hud_statpanel.h"
  11. #include "c_tf_team.h"
  12. #include "c_tf_player.h"
  13. #include "c_tf_playerresource.h"
  14. #include "tf_gamerules.h"
  15. #include "achievements_tf.h"
  16. //======================================================================================================================================
  17. // MEDIC ACHIEVEMENT PACK
  18. //======================================================================================================================================
  19. class CAchievementTFMedic_TopScoreboard : public CAchievementTopScoreboard
  20. {
  21. DECLARE_CLASS( CAchievementTFMedic_TopScoreboard, CAchievementTopScoreboard );
  22. void Init()
  23. {
  24. SetFlags( ACH_SAVE_GLOBAL | ACH_FILTER_FULL_ROUND_ONLY | ACH_LISTEN_PLAYER_KILL_ENEMY_EVENTS );
  25. m_bKilledAnyone = false;
  26. SetGoal(1);
  27. }
  28. void FireGameEvent_Internal( IGameEvent *event )
  29. {
  30. if ( Q_strcmp( event->GetName(), "teamplay_round_active" ) == 0 )
  31. {
  32. m_bKilledAnyone = false;
  33. }
  34. else
  35. {
  36. BaseClass::FireGameEvent_Internal( event );
  37. }
  38. }
  39. virtual void Event_OnRoundComplete( float flRoundTime, IGameEvent *event )
  40. {
  41. if ( !m_bKilledAnyone )
  42. {
  43. BaseClass::Event_OnRoundComplete( flRoundTime, event );
  44. }
  45. }
  46. virtual void Event_EntityKilled( CBaseEntity *pVictim, CBaseEntity *pAttacker, CBaseEntity *pInflictor, IGameEvent *event )
  47. {
  48. if ( pAttacker == C_TFPlayer::GetLocalTFPlayer() )
  49. {
  50. m_bKilledAnyone = true;
  51. }
  52. }
  53. private:
  54. bool m_bKilledAnyone;
  55. };
  56. DECLARE_ACHIEVEMENT( CAchievementTFMedic_TopScoreboard, ACHIEVEMENT_TF_MEDIC_TOP_SCOREBOARD, "TF_MEDIC_TOP_SCOREBOARD", 5 );
  57. //----------------------------------------------------------------------------------------------------------------
  58. class CAchievementTFMedic_ChargeBySetupEnd : public CBaseTFAchievement
  59. {
  60. void Init()
  61. {
  62. SetFlags( ACH_SAVE_GLOBAL );
  63. SetGoal( 1 );
  64. }
  65. virtual void ListenForEvents()
  66. {
  67. ListenForGameEvent( "teamplay_setup_finished" );
  68. }
  69. void FireGameEvent_Internal( IGameEvent *event )
  70. {
  71. if ( Q_strcmp( event->GetName(), "teamplay_setup_finished" ) == 0 )
  72. {
  73. // If we're a medic, and we have a charge, we've got the achievement.
  74. if ( IsLocalTFPlayerClass( TF_CLASS_MEDIC ) )
  75. {
  76. C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
  77. if ( pLocalPlayer->MedicGetChargeLevel() >= 1.0 )
  78. {
  79. IncrementCount();
  80. }
  81. }
  82. }
  83. }
  84. };
  85. DECLARE_ACHIEVEMENT( CAchievementTFMedic_ChargeBySetupEnd, ACHIEVEMENT_TF_MEDIC_SETUP_CHARGE, "TF_MEDIC_SETUP_CHARGE", 1 );
  86. //----------------------------------------------------------------------------------------------------------------
  87. class CAchievementTFMedic_HealTargetUnderFire : public CBaseTFAchievement
  88. {
  89. void Init()
  90. {
  91. SetFlags( ACH_SAVE_GLOBAL );
  92. SetGoal( 1 );
  93. }
  94. // server awards this achievement, no other code within achievement necessary
  95. };
  96. DECLARE_ACHIEVEMENT( CAchievementTFMedic_HealTargetUnderFire, ACHIEVEMENT_TF_MEDIC_HEAL_UNDER_FIRE, "TF_MEDIC_HEAL_UNDER_FIRE", 5 );
  97. //----------------------------------------------------------------------------------------------------------------
  98. class CAchievementTFMedic_SimultaneousCharges : public CBaseTFAchievement
  99. {
  100. void Init()
  101. {
  102. SetFlags( ACH_SAVE_GLOBAL );
  103. SetGoal( 1 );
  104. }
  105. // server awards this achievement, no other code within achievement necessary
  106. };
  107. DECLARE_ACHIEVEMENT( CAchievementTFMedic_SimultaneousCharges, ACHIEVEMENT_TF_MEDIC_SIMUL_CHARGE, "TF_MEDIC_SIMUL_CHARGE", 5 );
  108. //----------------------------------------------------------------------------------------------------------------
  109. class CAchievementTFMedic_RapidUbercharges : public CBaseTFAchievement
  110. {
  111. void Init()
  112. {
  113. SetFlags( ACH_SAVE_GLOBAL | ACH_LISTEN_KILL_EVENTS );
  114. SetGoal( 1 );
  115. }
  116. virtual void ListenForEvents()
  117. {
  118. ListenForGameEvent( "player_chargedeployed" );
  119. // Clear times on level init
  120. m_vecPreviousCharges.Purge();
  121. }
  122. void FireGameEvent_Internal( IGameEvent *event )
  123. {
  124. if ( Q_strcmp( event->GetName(), "player_chargedeployed" ) == 0 )
  125. {
  126. int iMedic = engine->GetPlayerForUserID( event->GetInt( "userid" ) );
  127. CBaseEntity *pMedic = UTIL_PlayerByIndex( iMedic );
  128. if ( pMedic && pMedic == C_TFPlayer::GetLocalTFPlayer() )
  129. {
  130. m_vecPreviousCharges.AddToTail( gpGlobals->curtime );
  131. if ( m_vecPreviousCharges.Count() > 3 )
  132. {
  133. m_vecPreviousCharges.Remove(0);
  134. }
  135. CheckForSuccess();
  136. }
  137. }
  138. }
  139. virtual void Event_EntityKilled( CBaseEntity *pVictim, CBaseEntity *pAttacker, CBaseEntity *pInflictor, IGameEvent *event )
  140. {
  141. int iAssisterIndex = engine->GetPlayerForUserID( event->GetInt( "assister" ) );
  142. if ( iAssisterIndex > 0 )
  143. {
  144. CBaseEntity *pAssister = UTIL_PlayerByIndex( iAssisterIndex );
  145. if ( pAssister && ( pAssister == C_TFPlayer::GetLocalTFPlayer() ) )
  146. {
  147. m_vecPreviousAssists.AddToTail( gpGlobals->curtime );
  148. if ( m_vecPreviousAssists.Count() > 5 )
  149. {
  150. m_vecPreviousAssists.Remove(0);
  151. }
  152. CheckForSuccess();
  153. }
  154. }
  155. }
  156. void CheckForSuccess( void )
  157. {
  158. if ( m_vecPreviousCharges.Count() >= 3 )
  159. {
  160. // Have we done the last 3 charges in 5 minutes?
  161. float flTimeRequirement = (5 * 60);
  162. if ( gpGlobals->curtime - m_vecPreviousCharges[0] < flTimeRequirement )
  163. {
  164. // Now check to make sure we've assisted in 5 kills in that time too.
  165. if ( m_vecPreviousAssists.Count() >= 5 )
  166. {
  167. if ( gpGlobals->curtime - m_vecPreviousAssists[0] < flTimeRequirement )
  168. {
  169. IncrementCount();
  170. }
  171. }
  172. }
  173. }
  174. }
  175. private:
  176. CUtlVector<float> m_vecPreviousCharges;
  177. CUtlVector<float> m_vecPreviousAssists;
  178. };
  179. DECLARE_ACHIEVEMENT( CAchievementTFMedic_RapidUbercharges, ACHIEVEMENT_TF_MEDIC_RAPID_CHARGE, "TF_MEDIC_RAPID_CHARGE", 1 );
  180. //----------------------------------------------------------------------------------------------------------------
  181. class CAchievementTFMedic_CounterUbercharges : public CBaseTFAchievement
  182. {
  183. void Init()
  184. {
  185. SetFlags( ACH_SAVE_GLOBAL );
  186. SetGoal( 1 );
  187. m_flPreviousEnemyCharge = 0;
  188. m_hEnemyMedic = NULL;
  189. }
  190. virtual void ListenForEvents()
  191. {
  192. ListenForGameEvent( "player_chargedeployed" );
  193. // Clear enemy medic stats on level init
  194. m_flPreviousEnemyCharge = 0;
  195. m_hEnemyMedic = NULL;
  196. }
  197. void FireGameEvent_Internal( IGameEvent *event )
  198. {
  199. if ( Q_strcmp( event->GetName(), "player_chargedeployed" ) == 0 )
  200. {
  201. int iMedic = engine->GetPlayerForUserID( event->GetInt( "userid" ) );
  202. CBaseEntity *pMedic = UTIL_PlayerByIndex( iMedic );
  203. C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
  204. if ( pMedic && pLocalPlayer )
  205. {
  206. // If we're the medic, get the achievement if an enemy medic in our PVS has deployed in the last 8 seconds
  207. if ( pMedic == C_TFPlayer::GetLocalTFPlayer() )
  208. {
  209. if ( gpGlobals->curtime - m_flPreviousEnemyCharge < 8.0 && m_hEnemyMedic && !m_hEnemyMedic->IsDormant() )
  210. {
  211. IncrementCount();
  212. }
  213. }
  214. else if ( pMedic->GetTeamNumber() != pLocalPlayer->GetTeamNumber() )
  215. {
  216. // Only track enemy medics deploying charge if they're in our PVS
  217. if ( !pMedic->IsDormant() )
  218. {
  219. m_flPreviousEnemyCharge = gpGlobals->curtime;
  220. m_hEnemyMedic = pMedic;
  221. }
  222. }
  223. }
  224. }
  225. }
  226. private:
  227. float m_flPreviousEnemyCharge;
  228. EHANDLE m_hEnemyMedic;
  229. };
  230. DECLARE_ACHIEVEMENT( CAchievementTFMedic_CounterUbercharges, ACHIEVEMENT_TF_MEDIC_COUNTER_CHARGE, "TF_MEDIC_COUNTER_CHARGE", 1 );
  231. //----------------------------------------------------------------------------------------------------------------
  232. class CAchievementTFMedic_SwitchToMedic : public CBaseTFAchievementSimple
  233. {
  234. void Init()
  235. {
  236. SetFlags( ACH_SAVE_GLOBAL );
  237. SetGoal( 1 );
  238. m_flPreviousMedicCall = 0;
  239. m_bTrackingHealth = false;
  240. m_iPrevHealPoints = 0;
  241. m_iTrackedHealPoints = 0;
  242. }
  243. virtual void ListenForEvents()
  244. {
  245. ListenForGameEvent( "player_calledformedic" );
  246. ListenForGameEvent( "localplayer_changeclass" );
  247. ListenForGameEvent( "localplayer_becameobserver" );
  248. // Clear data on level init
  249. m_flPreviousMedicCall = 0;
  250. m_bTrackingHealth = false;
  251. m_iPrevHealPoints = 0;
  252. m_iTrackedHealPoints = 0;
  253. }
  254. void FireGameEvent_Internal( IGameEvent *event )
  255. {
  256. // no progress in endround
  257. if ( !GameRulesAllowsAchievements() )
  258. return;
  259. C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
  260. if ( !pLocalPlayer )
  261. return;
  262. if ( Q_strcmp( event->GetName(), "player_calledformedic" ) == 0 )
  263. {
  264. // Only track these if we're not a medic
  265. if ( !IsLocalTFPlayerClass( TF_CLASS_MEDIC ) )
  266. {
  267. int iCaller = engine->GetPlayerForUserID( event->GetInt( "userid" ) );
  268. CBaseEntity *pCaller = UTIL_PlayerByIndex( iCaller );
  269. if ( pCaller && pCaller->InSameTeam( pLocalPlayer ) )
  270. {
  271. m_flPreviousMedicCall = gpGlobals->curtime;
  272. }
  273. }
  274. }
  275. else if ( Q_strcmp( event->GetName(), "localplayer_changeclass" ) == 0 || Q_strcmp( event->GetName(), "localplayer_becameobserver" ) == 0 )
  276. {
  277. // See if we've switched to Medic right after someone called for medic.
  278. // We trigger off the localplayer_becameobserver as well, to catch the case where they've suicided to change class, but don't
  279. // respawn within the 10 second window (because we only get localplayer_changeclass on the respawn as the new class).
  280. bool bWantsMedic = (IsLocalTFPlayerClass( TF_CLASS_MEDIC ) || pLocalPlayer->m_Shared.GetDesiredPlayerClassIndex() == TF_CLASS_MEDIC );
  281. if ( m_flPreviousMedicCall && (gpGlobals->curtime - m_flPreviousMedicCall < 10.0) && bWantsMedic )
  282. {
  283. // Ensure we're the only medic on the team
  284. if ( g_TF_PR )
  285. {
  286. bool bOnlyMedic = true;
  287. int iLocalTeam = C_TFPlayer::GetLocalTFPlayer()->GetTeamNumber();
  288. for( int playerIndex = 1; playerIndex <= MAX_PLAYERS; playerIndex++ )
  289. {
  290. if ( !g_PR->IsConnected( playerIndex ) || g_PR->IsLocalPlayer( playerIndex ) )
  291. continue;
  292. if ( g_PR->GetTeam(playerIndex) != iLocalTeam )
  293. continue;
  294. if ( g_TF_PR->GetPlayerClass( playerIndex ) == TF_CLASS_MEDIC )
  295. {
  296. bOnlyMedic = false;
  297. break;
  298. }
  299. }
  300. if ( bOnlyMedic )
  301. {
  302. m_bTrackingHealth = true;
  303. m_iTrackedHealPoints = 0;
  304. ClassStats_t &classStats = CTFStatPanel::GetClassStats( TF_CLASS_MEDIC );
  305. m_iPrevHealPoints = classStats.accumulated.m_iStat[TFSTAT_HEALING];
  306. }
  307. }
  308. }
  309. else if ( !bWantsMedic )
  310. {
  311. m_bTrackingHealth = false;
  312. }
  313. }
  314. }
  315. void OnPlayerStatsUpdate()
  316. {
  317. if ( !m_bTrackingHealth || !IsLocalTFPlayerClass( TF_CLASS_MEDIC ) )
  318. return;
  319. ClassStats_t &classStats = CTFStatPanel::GetClassStats( TF_CLASS_MEDIC );
  320. int iCount = classStats.accumulated.m_iStat[TFSTAT_HEALING];
  321. if ( iCount > m_iPrevHealPoints )
  322. {
  323. m_iTrackedHealPoints += (iCount - m_iPrevHealPoints);
  324. m_iPrevHealPoints = iCount;
  325. if ( m_iTrackedHealPoints > 500 )
  326. {
  327. IncrementCount();
  328. }
  329. }
  330. }
  331. private:
  332. float m_flPreviousMedicCall;
  333. bool m_bTrackingHealth;
  334. int m_iPrevHealPoints;
  335. int m_iTrackedHealPoints;
  336. };
  337. DECLARE_ACHIEVEMENT( CAchievementTFMedic_SwitchToMedic, ACHIEVEMENT_TF_MEDIC_SWITCH_TO_MEDIC, "TF_MEDIC_SWITCH_TO_MEDIC", 1 );
  338. //----------------------------------------------------------------------------------------------------------------
  339. class CAchievementTFMedic_SaveTeammate : public CBaseTFAchievement
  340. {
  341. void Init()
  342. {
  343. SetFlags( ACH_SAVE_GLOBAL );
  344. SetGoal( 1 );
  345. }
  346. // server awards this achievement, no other code within achievement necessary
  347. };
  348. DECLARE_ACHIEVEMENT( CAchievementTFMedic_SaveTeammate, ACHIEVEMENT_TF_MEDIC_SAVE_TEAMMATE, "TF_MEDIC_SAVE_TEAMMATE", 5 );
  349. //----------------------------------------------------------------------------------------------------------------
  350. class CAchievementTFMedic_ChargeBlocker : public CBaseTFAchievement
  351. {
  352. void Init()
  353. {
  354. SetFlags( ACH_SAVE_GLOBAL );
  355. SetGoal( 1 );
  356. }
  357. // server awards this achievement, no other code within achievement necessary
  358. };
  359. DECLARE_ACHIEVEMENT( CAchievementTFMedic_ChargeBlocker, ACHIEVEMENT_TF_MEDIC_CHARGE_BLOCKER, "TF_MEDIC_CHARGE_BLOCKER", 5 );
  360. //----------------------------------------------------------------------------------------------------------------
  361. class CAchievementTFMedic_AssistMedic : public CBaseTFAchievement
  362. {
  363. void Init()
  364. {
  365. SetFlags( ACH_SAVE_GLOBAL | ACH_LISTEN_KILL_EVENTS | ACH_FILTER_VICTIM_IS_PLAYER_ENEMY );
  366. SetGoal( 1 );
  367. m_iAssists = 0;
  368. }
  369. virtual void ListenForEvents()
  370. {
  371. ListenForGameEvent( "localplayer_respawn" );
  372. }
  373. void FireGameEvent_Internal( IGameEvent *event )
  374. {
  375. const char *pszEventName = event->GetName();
  376. if ( FStrEq( pszEventName, "localplayer_respawn" ) )
  377. {
  378. m_iAssists = 0;
  379. }
  380. }
  381. // Assist a fellow Medic in killing 5 enemies in a single life.
  382. virtual void Event_EntityKilled( CBaseEntity *pVictim, CBaseEntity *pAttacker, CBaseEntity *pInflictor, IGameEvent *event )
  383. {
  384. C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer();
  385. if ( pLocalPlayer )
  386. {
  387. int iAssisterIndex = engine->GetPlayerForUserID( event->GetInt( "assister" ) );
  388. if ( iAssisterIndex > 0 )
  389. {
  390. C_TFPlayer *pAtkPlayer = ToTFPlayer( pAttacker );
  391. C_TFPlayer *pAssister = ToTFPlayer( UTIL_PlayerByIndex( iAssisterIndex ) );
  392. if ( pAssister == pLocalPlayer || pAttacker == pLocalPlayer )
  393. {
  394. if ( pAssister && pAssister->IsPlayerClass(TF_CLASS_MEDIC) && pAtkPlayer && pAtkPlayer->IsPlayerClass(TF_CLASS_MEDIC) )
  395. {
  396. m_iAssists++;
  397. if ( m_iAssists >= 3 )
  398. {
  399. IncrementCount();
  400. }
  401. }
  402. }
  403. }
  404. }
  405. }
  406. private:
  407. int m_iAssists;
  408. };
  409. DECLARE_ACHIEVEMENT( CAchievementTFMedic_AssistMedic, ACHIEVEMENT_TF_MEDIC_ASSIST_MEDIC, "TF_MEDIC_ASSIST_MEDIC", 1 );
  410. //----------------------------------------------------------------------------------------------------------------
  411. class CAchievementTFMedic_KillScoutsWithSyringe : public CBaseTFAchievement
  412. {
  413. void Init()
  414. {
  415. SetFlags( ACH_SAVE_GLOBAL | ACH_LISTEN_KILL_EVENTS );
  416. SetGoal( 50 );
  417. }
  418. virtual void Event_EntityKilled( CBaseEntity *pVictim, CBaseEntity *pAttacker, CBaseEntity *pInflictor, IGameEvent *event )
  419. {
  420. C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer();
  421. if ( pAttacker == pLocalPlayer )
  422. {
  423. C_TFPlayer *pTFVictim = ToTFPlayer( pVictim );
  424. if ( pTFVictim && pTFVictim->IsPlayerClass(TF_CLASS_SCOUT) && event->GetInt( "weaponid" ) == TF_WEAPON_SYRINGEGUN_MEDIC )
  425. {
  426. IncrementCount();
  427. }
  428. }
  429. }
  430. private:
  431. int m_iAssists;
  432. };
  433. DECLARE_ACHIEVEMENT( CAchievementTFMedic_KillScoutsWithSyringe, ACHIEVEMENT_TF_MEDIC_SYRINGE_SCOUTS, "TF_MEDIC_SYRINGE_SCOUTS", 1 );
  434. //----------------------------------------------------------------------------------------------------------------
  435. class CAchievementTFMedic_KillMedicsWithBonesaw : public CBaseTFAchievement
  436. {
  437. void Init()
  438. {
  439. SetFlags( ACH_SAVE_GLOBAL | ACH_LISTEN_KILL_EVENTS );
  440. SetGoal( 10 );
  441. }
  442. virtual void Event_EntityKilled( CBaseEntity *pVictim, CBaseEntity *pAttacker, CBaseEntity *pInflictor, IGameEvent *event )
  443. {
  444. C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer();
  445. if ( pAttacker == pLocalPlayer )
  446. {
  447. C_TFPlayer *pTFVictim = ToTFPlayer( pVictim );
  448. if ( pTFVictim && pTFVictim->IsPlayerClass(TF_CLASS_MEDIC) && event->GetInt( "weaponid" ) == TF_WEAPON_BONESAW )
  449. {
  450. IncrementCount();
  451. }
  452. }
  453. }
  454. private:
  455. int m_iAssists;
  456. };
  457. DECLARE_ACHIEVEMENT( CAchievementTFMedic_KillMedicsWithBonesaw, ACHIEVEMENT_TF_MEDIC_BONESAW_MEDICS, "TF_MEDIC_BONESAW_MEDICS", 1 );
  458. //----------------------------------------------------------------------------------------------------------------
  459. #define MAX_PARTNERS 12
  460. class CAchievementTFMedic_AssistHeavyLongStreak : public CBaseTFAchievement
  461. {
  462. void Init()
  463. {
  464. SetFlags( ACH_SAVE_GLOBAL | ACH_LISTEN_KILL_EVENTS );
  465. SetGoal( 1 );
  466. }
  467. virtual void ListenForEvents()
  468. {
  469. ListenForGameEvent( "teamplay_round_active" );
  470. ListenForGameEvent( "localplayer_respawn" );
  471. }
  472. void FireGameEvent_Internal( IGameEvent *event )
  473. {
  474. if ( FStrEq( event->GetName(), "teamplay_round_active" ) )
  475. {
  476. m_Partners.Purge();
  477. }
  478. else if ( FStrEq( event->GetName(), "localplayer_respawn" ) )
  479. {
  480. m_Partners.Purge();
  481. }
  482. }
  483. int GetPartnerIndex( CBaseEntity *pPlayer )
  484. {
  485. for ( int i = 0; i < m_Partners.Count(); i++ )
  486. {
  487. if ( m_Partners[i].hPartner == pPlayer )
  488. return i;
  489. }
  490. return -1;
  491. }
  492. virtual void Event_EntityKilled( CBaseEntity *pVictim, CBaseEntity *pAttacker, CBaseEntity *pInflictor, IGameEvent *event )
  493. {
  494. C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer();
  495. if ( pLocalPlayer )
  496. {
  497. if ( pVictim->GetTeamNumber() != pLocalPlayer->GetTeamNumber() )
  498. {
  499. int iAssisterIndex = engine->GetPlayerForUserID( event->GetInt( "assister" ) );
  500. if ( iAssisterIndex > 0 )
  501. {
  502. if ( UTIL_PlayerByIndex( iAssisterIndex ) == pLocalPlayer )
  503. {
  504. C_TFPlayer *pAtkPlayer = ToTFPlayer( pAttacker );
  505. if ( pAtkPlayer && pAtkPlayer->IsPlayerClass( TF_CLASS_HEAVYWEAPONS ) )
  506. {
  507. int index = GetPartnerIndex( pAtkPlayer );
  508. if ( index == -1 )
  509. {
  510. if ( m_Partners.Count() >= MAX_PARTNERS )
  511. {
  512. // Remove the one with the least assists
  513. int iLowest = 999;
  514. int iLowestIndex = -1;
  515. for ( int i = 0; i < m_Partners.Count(); i++ )
  516. {
  517. if ( !m_Partners[i].hPartner )
  518. {
  519. // Player is gone. Lets remove that one.
  520. iLowestIndex = i;
  521. break;
  522. }
  523. if ( m_Partners[i].iAssists < iLowest )
  524. {
  525. iLowestIndex = i;
  526. iLowest = m_Partners[i].iAssists;
  527. }
  528. }
  529. if ( iLowestIndex >= 0 )
  530. {
  531. //Msg("FULL Removed %d (%s)\n", iLowestIndex, g_PR->GetPlayerName(m_Partners[iLowestIndex].hPartner->entindex()) );
  532. m_Partners.Remove(iLowestIndex);
  533. }
  534. }
  535. int iNewIndex = m_Partners.AddToTail();
  536. m_Partners[iNewIndex].hPartner = pAtkPlayer;
  537. m_Partners[iNewIndex].iAssists = 1;
  538. //Msg("Inserted %s into %d\n", g_PR->GetPlayerName(pAtkPlayer->entindex()), iNewIndex );
  539. }
  540. else
  541. {
  542. m_Partners[index].iAssists++;
  543. //Msg("Incremented %s in %d to %d\n", g_PR->GetPlayerName(m_Partners[index].hPartner->entindex()), index, m_Partners[index].iAssists );
  544. if ( m_Partners[index].iAssists >= 10 )
  545. {
  546. IncrementCount();
  547. }
  548. }
  549. }
  550. else
  551. {
  552. // Ensure this guy isn't in our list. We can have non-heavies in our list if we
  553. // earn an assist with them, and then they switch classes in the respawn room.
  554. int index = GetPartnerIndex( pAtkPlayer );
  555. if ( index != -1 )
  556. {
  557. m_Partners.Remove(index);
  558. }
  559. }
  560. }
  561. }
  562. }
  563. // See if it's one of our partners
  564. int index = GetPartnerIndex( pVictim );
  565. if ( index != -1 )
  566. {
  567. //Msg("DEATH: Removed %d (%s)\n", index, g_PR->GetPlayerName(m_Partners[index].hPartner->entindex()) );
  568. m_Partners.Remove(index);
  569. }
  570. /*
  571. Msg("State:\n");
  572. for ( int i = 0; i < m_Partners.Count(); i++ )
  573. {
  574. if ( m_Partners[i].hPartner )
  575. {
  576. Msg(" %d: %s with %d\n", i, g_PR->GetPlayerName(m_Partners[i].hPartner->entindex()), m_Partners[i].iAssists );
  577. }
  578. else
  579. {
  580. Msg(" %d: EMPTY\n", i );
  581. }
  582. }
  583. */
  584. }
  585. }
  586. private:
  587. struct partners_t
  588. {
  589. EHANDLE hPartner;
  590. int iAssists;
  591. };
  592. CUtlVector<partners_t> m_Partners;
  593. };
  594. DECLARE_ACHIEVEMENT( CAchievementTFMedic_AssistHeavyLongStreak, ACHIEVEMENT_TF_MEDIC_ASSIST_HEAVY_LONG, "TF_MEDIC_ASSIST_HEAVY_LONG", 1 );
  595. //----------------------------------------------------------------------------------------------------------------
  596. // Base helper for achievements that want to track assists while the medic is deploying his uber charge
  597. class CAchievementTFMedic_BaseAssistWhileCharged : public CBaseTFAchievement
  598. {
  599. public:
  600. void Init()
  601. {
  602. SetFlags( ACH_SAVE_GLOBAL | ACH_LISTEN_KILL_EVENTS );
  603. SetGoal( 1 );
  604. }
  605. virtual void ListenForEvents()
  606. {
  607. ListenForGameEvent( "player_chargedeployed" );
  608. }
  609. void FireGameEvent_Internal( IGameEvent *event )
  610. {
  611. if ( Q_strcmp( event->GetName(), "player_chargedeployed" ) == 0 )
  612. {
  613. int iMedic = engine->GetPlayerForUserID( event->GetInt( "userid" ) );
  614. CBaseEntity *pMedic = UTIL_PlayerByIndex( iMedic );
  615. if ( pMedic == C_TFPlayer::GetLocalTFPlayer() )
  616. {
  617. DeployedCharge();
  618. }
  619. }
  620. }
  621. virtual void Event_EntityKilled( CBaseEntity *pVictim, CBaseEntity *pAttacker, CBaseEntity *pInflictor, IGameEvent *event )
  622. {
  623. int iAssisterIndex = engine->GetPlayerForUserID( event->GetInt( "assister" ) );
  624. if ( iAssisterIndex > 0 )
  625. {
  626. CBaseEntity *pAssister = UTIL_PlayerByIndex( iAssisterIndex );
  627. C_TFPlayer *pTFPlayer = C_TFPlayer::GetLocalTFPlayer();
  628. if ( pAssister == pTFPlayer && pTFPlayer && pTFPlayer->MedicIsReleasingCharge() )
  629. {
  630. AssistedWhileCharged( pVictim, pAttacker, pInflictor, event );
  631. }
  632. }
  633. }
  634. virtual void DeployedCharge( void ) = 0;
  635. virtual void AssistedWhileCharged( CBaseEntity *pVictim, CBaseEntity *pAttacker, CBaseEntity *pInflictor, IGameEvent *event ) = 0;
  636. };
  637. //----------------------------------------------------------------------------------------------------------------
  638. class CAchievementTFMedic_AssistChargeScout : public CAchievementTFMedic_BaseAssistWhileCharged
  639. {
  640. DECLARE_CLASS( CAchievementTFMedic_AssistChargeScout, CAchievementTFMedic_BaseAssistWhileCharged );
  641. void Init()
  642. {
  643. BaseClass::Init();
  644. m_iAssists = 0;
  645. }
  646. // Assist in killing 4 enemies with a single Uber-Charge on a Scout
  647. virtual void DeployedCharge( void )
  648. {
  649. m_iAssists = 0;
  650. }
  651. virtual void AssistedWhileCharged( CBaseEntity *pVictim, CBaseEntity *pAttacker, CBaseEntity *pInflictor, IGameEvent *event )
  652. {
  653. C_TFPlayer *pAtkPlayer = ToTFPlayer( pAttacker );
  654. if ( pAtkPlayer && pAtkPlayer->IsPlayerClass(TF_CLASS_SCOUT) )
  655. {
  656. m_iAssists++;
  657. if ( m_iAssists >= 3 )
  658. {
  659. IncrementCount();
  660. }
  661. }
  662. }
  663. private:
  664. int m_iAssists;
  665. };
  666. DECLARE_ACHIEVEMENT( CAchievementTFMedic_AssistChargeScout, ACHIEVEMENT_TF_MEDIC_ASSIST_SCOUT, "TF_MEDIC_ASSIST_SCOUT", 1 );
  667. //----------------------------------------------------------------------------------------------------------------
  668. class CAchievementTFMedic_AssistChargeSoldier : public CAchievementTFMedic_BaseAssistWhileCharged
  669. {
  670. DECLARE_CLASS( CAchievementTFMedic_AssistChargeSoldier, CAchievementTFMedic_BaseAssistWhileCharged );
  671. void Init()
  672. {
  673. BaseClass::Init();
  674. m_iAssists = 0;
  675. }
  676. // Assist in exploding 5 enemies with a single Uber-Charge on a Soldier
  677. virtual void DeployedCharge( void )
  678. {
  679. m_iAssists = 0;
  680. }
  681. virtual void AssistedWhileCharged( CBaseEntity *pVictim, CBaseEntity *pAttacker, CBaseEntity *pInflictor, IGameEvent *event )
  682. {
  683. C_TFPlayer *pAtkPlayer = ToTFPlayer( pAttacker );
  684. if ( pAtkPlayer && pAtkPlayer->IsPlayerClass(TF_CLASS_SOLDIER) )
  685. {
  686. if ( event->GetInt( "weaponid" ) == TF_WEAPON_ROCKETLAUNCHER )
  687. {
  688. m_iAssists++;
  689. if ( m_iAssists >= 5 )
  690. {
  691. IncrementCount();
  692. }
  693. }
  694. }
  695. }
  696. private:
  697. int m_iAssists;
  698. };
  699. DECLARE_ACHIEVEMENT( CAchievementTFMedic_AssistChargeSoldier, ACHIEVEMENT_TF_MEDIC_ASSIST_SOLDIER, "TF_MEDIC_ASSIST_SOLDIER", 1 );
  700. //----------------------------------------------------------------------------------------------------------------
  701. class CAchievementTFMedic_AssistChargeDemoman : public CAchievementTFMedic_BaseAssistWhileCharged
  702. {
  703. DECLARE_CLASS( CAchievementTFMedic_AssistChargeDemoman, CAchievementTFMedic_BaseAssistWhileCharged );
  704. void Init()
  705. {
  706. SetFlags( ACH_SAVE_GLOBAL );
  707. SetGoal( 1 );
  708. m_iAssists = 0;
  709. }
  710. virtual void ListenForEvents()
  711. {
  712. BaseClass::ListenForEvents();
  713. ListenForGameEvent( "object_destroyed" );
  714. }
  715. virtual void DeployedCharge( void )
  716. {
  717. m_iAssists = 0;
  718. }
  719. void FireGameEvent_Internal( IGameEvent *event )
  720. {
  721. if ( Q_strcmp( event->GetName(), "object_destroyed" ) == 0 )
  722. {
  723. int iAssisterIndex = engine->GetPlayerForUserID( event->GetInt( "assister" ) );
  724. if ( iAssisterIndex > 0 )
  725. {
  726. CBaseEntity *pAssister = UTIL_PlayerByIndex( iAssisterIndex );
  727. C_TFPlayer *pTFPlayer = C_TFPlayer::GetLocalTFPlayer();
  728. if ( pAssister == pTFPlayer && pTFPlayer && pTFPlayer->MedicIsReleasingCharge() )
  729. {
  730. CBaseEntity *pAttacker = ClientEntityList().GetEnt( engine->GetPlayerForUserID( event->GetInt("attacker") ) );
  731. AssistedWhileCharged( NULL, pAttacker, NULL, event );
  732. }
  733. }
  734. }
  735. else
  736. {
  737. BaseClass::FireGameEvent_Internal( event );
  738. }
  739. }
  740. virtual void Event_EntityKilled( CBaseEntity *pVictim, CBaseEntity *pAttacker, CBaseEntity *pInflictor, IGameEvent *event )
  741. {
  742. // don't count player kills in our assists
  743. }
  744. virtual void AssistedWhileCharged( CBaseEntity *pVictim, CBaseEntity *pAttacker, CBaseEntity *pInflictor, IGameEvent *event )
  745. {
  746. C_TFPlayer *pAtkPlayer = ToTFPlayer( pAttacker );
  747. if ( pAtkPlayer && pAtkPlayer->IsPlayerClass(TF_CLASS_DEMOMAN) )
  748. {
  749. m_iAssists++;
  750. if ( m_iAssists >= 5 )
  751. {
  752. IncrementCount();
  753. }
  754. }
  755. }
  756. private:
  757. int m_iAssists;
  758. };
  759. DECLARE_ACHIEVEMENT( CAchievementTFMedic_AssistChargeDemoman, ACHIEVEMENT_TF_MEDIC_ASSIST_DEMOMAN, "TF_MEDIC_ASSIST_DEMOMAN", 1 );
  760. //----------------------------------------------------------------------------------------------------------------
  761. class CAchievementTFMedic_HealEngineer : public CBaseTFAchievement
  762. {
  763. void Init()
  764. {
  765. SetFlags( ACH_SAVE_GLOBAL );
  766. SetGoal( 1 );
  767. }
  768. // server awards this achievement, no other code within achievement necessary
  769. };
  770. DECLARE_ACHIEVEMENT( CAchievementTFMedic_HealEngineer, ACHIEVEMENT_TF_MEDIC_HEAL_ENGINEER, "TF_MEDIC_HEAL_ENGINEER", 5 );
  771. //----------------------------------------------------------------------------------------------------------------
  772. class CAchievementTFMedic_AssistPyro : public CBaseTFAchievement
  773. {
  774. void Init()
  775. {
  776. SetFlags( ACH_SAVE_GLOBAL );
  777. SetGoal( 1 );
  778. m_iPlayersIgnited = 0;
  779. }
  780. // Assist in burning 8 enemies with a single Uber-Charge on a Pyro.
  781. virtual void ListenForEvents()
  782. {
  783. ListenForGameEvent( "player_chargedeployed" );
  784. ListenForGameEvent( "player_ignited_inv" );
  785. }
  786. virtual void FireGameEvent_Internal( IGameEvent *event )
  787. {
  788. if ( !IsLocalTFPlayerClass( TF_CLASS_MEDIC ) )
  789. return;
  790. C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
  791. if ( FStrEq( event->GetName(), "player_chargedeployed" ) )
  792. {
  793. int iMedic = engine->GetPlayerForUserID( event->GetInt( "userid" ) );
  794. CBaseEntity *pMedic = UTIL_PlayerByIndex( iMedic );
  795. if ( pMedic == pLocalPlayer )
  796. {
  797. m_iPlayersIgnited = 0;
  798. }
  799. }
  800. else if ( FStrEq( event->GetName(), "player_ignited_inv" ) )
  801. {
  802. int iMedic = event->GetInt( "medic_entindex" );
  803. CBaseEntity *pMedic = UTIL_PlayerByIndex( iMedic );
  804. if ( pMedic == pLocalPlayer )
  805. {
  806. if ( ++m_iPlayersIgnited >= 5 )
  807. {
  808. IncrementCount();
  809. }
  810. }
  811. }
  812. }
  813. private:
  814. int m_iPlayersIgnited;
  815. };
  816. DECLARE_ACHIEVEMENT( CAchievementTFMedic_AssistPyro, ACHIEVEMENT_TF_MEDIC_ASSIST_PYRO, "TF_MEDIC_ASSIST_PYRO", 5 );
  817. //----------------------------------------------------------------------------------------------------------------
  818. class CAchievementTFMedic_AssistHeavy : public CBaseTFAchievement
  819. {
  820. void Init()
  821. {
  822. SetFlags( ACH_SAVE_GLOBAL );
  823. SetGoal( 1 );
  824. }
  825. // server awards this achievement, no other code within achievement necessary
  826. private:
  827. };
  828. DECLARE_ACHIEVEMENT( CAchievementTFMedic_AssistHeavy, ACHIEVEMENT_TF_MEDIC_ASSIST_HEAVY, "TF_MEDIC_ASSIST_HEAVY", 5 );
  829. //----------------------------------------------------------------------------------------------------------------
  830. class CAchievementTFMedic_AssistCapturer : public CBaseTFAchievement
  831. {
  832. void Init()
  833. {
  834. SetFlags( ACH_SAVE_GLOBAL );
  835. SetGoal( 1 );
  836. }
  837. // Assist in killing 3 enemies on an enemy control point, in a single life.
  838. // server awards this achievement, no other code within achievement necessary
  839. };
  840. DECLARE_ACHIEVEMENT( CAchievementTFMedic_AssistCapturer, ACHIEVEMENT_TF_MEDIC_ASSIST_CAPTURER, "TF_MEDIC_ASSIST_CAPTURER", 5 );
  841. //----------------------------------------------------------------------------------------------------------------
  842. class CAchievementTFMedic_HealCallers : public CBaseTFAchievement
  843. {
  844. void Init()
  845. {
  846. SetFlags( ACH_SAVE_GLOBAL );
  847. SetGoal( 200 );
  848. m_flLastHealTime = -1;
  849. m_iLastHealee = -1;
  850. }
  851. // Heal 200 teammates after they've called for "Medic!".
  852. virtual void ListenForEvents()
  853. {
  854. ListenForGameEvent( "player_healedmediccall" );
  855. // Clear data on level init
  856. m_flLastHealTime = -1;
  857. m_iLastHealee = -1;
  858. }
  859. void FireGameEvent_Internal( IGameEvent *event )
  860. {
  861. C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
  862. if ( !pLocalPlayer )
  863. return;
  864. if ( Q_strcmp( event->GetName(), "player_healedmediccall" ) == 0 )
  865. {
  866. int iCaller = engine->GetPlayerForUserID( event->GetInt( "userid" ) );
  867. CBaseEntity *pCaller = UTIL_PlayerByIndex( iCaller );
  868. if ( pCaller && pCaller->InSameTeam( pLocalPlayer ) )
  869. {
  870. // don't count multiple heals on the same player unless they retrigger the saveme
  871. if ( iCaller == m_iLastHealee )
  872. {
  873. if ( gpGlobals->curtime - m_flLastHealTime < 10.0 )
  874. return;
  875. }
  876. IncrementCount();
  877. m_iLastHealee = iCaller;
  878. m_flLastHealTime = gpGlobals->curtime;
  879. }
  880. }
  881. }
  882. private:
  883. float m_iLastHealee;
  884. float m_flLastHealTime;
  885. };
  886. DECLARE_ACHIEVEMENT( CAchievementTFMedic_HealCallers, ACHIEVEMENT_TF_MEDIC_HEAL_CALLERS, "TF_MEDIC_HEAL_CALLERS", 5 );
  887. //----------------------------------------------------------------------------------------------------------------
  888. class CAchievementTFMedic_ExtinguishTeammates : public CBaseTFAchievement
  889. {
  890. void Init()
  891. {
  892. SetFlags( ACH_SAVE_GLOBAL );
  893. SetGoal( 100 );
  894. }
  895. // Extinguish 100 burning teammates.
  896. virtual void ListenForEvents( void )
  897. {
  898. ListenForGameEvent( "player_extinguished" );
  899. }
  900. void FireGameEvent_Internal( IGameEvent *event )
  901. {
  902. if ( FStrEq( event->GetName(), "player_extinguished" ) )
  903. {
  904. CTFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
  905. if ( !pLocalPlayer )
  906. return;
  907. // userid of player that was extinguished by a medic
  908. // if local player is healing them, we get credit
  909. CTFPlayer *pTarget = ToTFPlayer( UTIL_PlayerByIndex( event->GetInt( "victim" ) ) );
  910. if ( pTarget && pTarget == pLocalPlayer->MedicGetHealTarget() )
  911. {
  912. IncrementCount();
  913. }
  914. }
  915. }
  916. };
  917. DECLARE_ACHIEVEMENT( CAchievementTFMedic_ExtinguishTeammates, ACHIEVEMENT_TF_MEDIC_EXTINGUISH_TEAMMATES, "TF_MEDIC_EXTINGUISH_TEAMMATES", 5 );
  918. //----------------------------------------------------------------------------------------------------------------
  919. class CAchievementTFMedic_AssistVsNemeses : public CBaseTFAchievement
  920. {
  921. void Init()
  922. {
  923. SetFlags( ACH_SAVE_GLOBAL | ACH_LISTEN_KILL_EVENTS );
  924. SetGoal( 20 );
  925. }
  926. // Assist in killing 20 nemeses.
  927. virtual void Event_EntityKilled( CBaseEntity *pVictim, CBaseEntity *pAttacker, CBaseEntity *pInflictor, IGameEvent *event )
  928. {
  929. C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
  930. if ( !pLocalPlayer )
  931. return;
  932. // We count 2 ways to get this.
  933. // 1) Medic is healing someone who gets revenge - "revenge" is valid
  934. // 2) Medic heals a guy who kills the medic's nemesis - "assister_revenge" is valid
  935. // In both cases, "assister" is the medic.
  936. int iAssisterID = event->GetInt( "assister" );
  937. if ( iAssisterID > 0 && iAssisterID == pLocalPlayer->GetUserID() )
  938. {
  939. if ( event->GetInt( "death_flags" ) & TF_DEATH_REVENGE || event->GetInt( "death_flags" ) & TF_DEATH_ASSISTER_REVENGE )
  940. {
  941. IncrementCount();
  942. }
  943. }
  944. }
  945. };
  946. DECLARE_ACHIEVEMENT( CAchievementTFMedic_AssistVsNemeses, ACHIEVEMENT_TF_MEDIC_ASSIST_VS_NEMESES, "TF_MEDIC_ASSIST_VS_NEMESES", 5 );
  947. //----------------------------------------------------------------------------------------------------------------
  948. class CAchievementTFMedic_KillWhileCharged : public CBaseTFAchievement
  949. {
  950. void Init()
  951. {
  952. SetFlags( ACH_SAVE_GLOBAL | ACH_LISTEN_PLAYER_KILL_ENEMY_EVENTS );
  953. SetGoal( 1 );
  954. m_bPlayerHasCharge = false;
  955. m_iKillCount = 0;
  956. }
  957. // Kill 5 enemies in a single life, while having your Uber-Charge ready, but undeployed.
  958. virtual void ListenForEvents()
  959. {
  960. ListenForGameEvent( "localplayer_chargeready" );
  961. ListenForGameEvent( "player_chargedeployed" );
  962. ListenForGameEvent( "localplayer_respawn" );
  963. }
  964. void FireGameEvent_Internal( IGameEvent *event )
  965. {
  966. const char *pszEventName = event->GetName();
  967. // sent from client, assumes that player with charge is the local player.
  968. if ( FStrEq( pszEventName, "localplayer_chargeready" ) )
  969. {
  970. m_bPlayerHasCharge = true;
  971. m_iKillCount = 0;
  972. }
  973. else if ( FStrEq( pszEventName, "player_chargedeployed" ) )
  974. {
  975. int iMedic = engine->GetPlayerForUserID( event->GetInt( "userid" ) );
  976. if ( UTIL_PlayerByIndex( iMedic ) == C_TFPlayer::GetLocalTFPlayer() )
  977. {
  978. m_bPlayerHasCharge = false;
  979. m_iKillCount = 0;
  980. }
  981. }
  982. else if ( FStrEq( pszEventName, "localplayer_respawn" ) )
  983. {
  984. m_iKillCount = 0;
  985. m_bPlayerHasCharge = false;
  986. }
  987. }
  988. virtual void Event_EntityKilled( CBaseEntity *pVictim, CBaseEntity *pAttacker, CBaseEntity *pInflictor, IGameEvent *event )
  989. {
  990. Assert( pAttacker == C_TFPlayer::GetLocalTFPlayer() );
  991. if ( m_bPlayerHasCharge )
  992. {
  993. m_iKillCount++;
  994. if ( m_iKillCount >= 2 )
  995. {
  996. IncrementCount();
  997. }
  998. }
  999. else
  1000. {
  1001. m_iKillCount = 0;
  1002. }
  1003. }
  1004. private:
  1005. int m_iKillCount;
  1006. bool m_bPlayerHasCharge;
  1007. };
  1008. DECLARE_ACHIEVEMENT( CAchievementTFMedic_KillWhileCharged, ACHIEVEMENT_TF_MEDIC_KILL_WHILE_CHARGED, "TF_MEDIC_KILL_WHILE_CHARGED", 5 );
  1009. //----------------------------------------------------------------------------------------------------------------
  1010. class CAchievementTFMedic_BonesawNoMisses : public CBaseTFAchievement
  1011. {
  1012. void Init()
  1013. {
  1014. SetFlags( ACH_SAVE_GLOBAL );
  1015. SetGoal( 1 );
  1016. }
  1017. // server awards this achievement, no other code within achievement necessary
  1018. // Hit enemies with your bonesaw 5 times in a row without dying or missing.
  1019. };
  1020. DECLARE_ACHIEVEMENT( CAchievementTFMedic_BonesawNoMisses, ACHIEVEMENT_TF_MEDIC_BONESAW_NOMISSES, "TF_MEDIC_BONESAW_NOMISSES", 5 );
  1021. //----------------------------------------------------------------------------------------------------------------
  1022. class CAchievementTFMedic_HealLarge : public CBaseTFAchievement
  1023. {
  1024. void Init()
  1025. {
  1026. SetFlags( ACH_SAVE_GLOBAL );
  1027. SetGoal( 7000 );
  1028. }
  1029. // Accumulate 7000 heal points health in a single life.
  1030. void OnPlayerStatsUpdate()
  1031. {
  1032. ClassStats_t &classStats = CTFStatPanel::GetClassStats( TF_CLASS_MEDIC );
  1033. int iOldCount = m_iCount;
  1034. m_iCount = classStats.max.m_iStat[TFSTAT_HEALING];
  1035. if ( m_iCount != iOldCount )
  1036. {
  1037. m_pAchievementMgr->SetDirty( true );
  1038. }
  1039. if ( IsLocalTFPlayerClass( TF_CLASS_MEDIC ) )
  1040. {
  1041. EvaluateNewAchievement();
  1042. }
  1043. }
  1044. };
  1045. DECLARE_ACHIEVEMENT( CAchievementTFMedic_HealLarge, ACHIEVEMENT_TF_MEDIC_HEAL_LARGE, "TF_MEDIC_HEAL_LARGE", 5 );
  1046. //----------------------------------------------------------------------------------------------------------------
  1047. class CAchievementTFMedic_HealHuge : public CBaseTFAchievement
  1048. {
  1049. void Init()
  1050. {
  1051. SetFlags( ACH_SAVE_GLOBAL );
  1052. SetGoal( 10000 );
  1053. }
  1054. // Accumulate 10000 heal points health in a single life.
  1055. void OnPlayerStatsUpdate()
  1056. {
  1057. ClassStats_t &classStats = CTFStatPanel::GetClassStats( TF_CLASS_MEDIC );
  1058. int iOldCount = m_iCount;
  1059. m_iCount = classStats.max.m_iStat[TFSTAT_HEALING];
  1060. if ( m_iCount != iOldCount )
  1061. {
  1062. m_pAchievementMgr->SetDirty( true );
  1063. }
  1064. if ( IsLocalTFPlayerClass( TF_CLASS_MEDIC ) )
  1065. {
  1066. EvaluateNewAchievement();
  1067. }
  1068. }
  1069. };
  1070. DECLARE_ACHIEVEMENT( CAchievementTFMedic_HealHuge, ACHIEVEMENT_TF_MEDIC_HEAL_HUGE, "TF_MEDIC_HEAL_HUGE", 5 );
  1071. //----------------------------------------------------------------------------------------------------------------
  1072. class CAchievementTFMedic_HealGrind : public CBaseTFAchievement
  1073. {
  1074. void Init()
  1075. {
  1076. SetFlags( ACH_SAVE_GLOBAL );
  1077. SetGoal( 1000000 );
  1078. }
  1079. // Accumulate 1 million total heal points.
  1080. void OnPlayerStatsUpdate()
  1081. {
  1082. ClassStats_t &classStats = CTFStatPanel::GetClassStats( TF_CLASS_MEDIC );
  1083. int iOldCount = m_iCount;
  1084. m_iCount = classStats.accumulated.m_iStat[TFSTAT_HEALING];
  1085. if ( m_iCount != iOldCount )
  1086. {
  1087. m_pAchievementMgr->SetDirty( true );
  1088. }
  1089. if ( IsLocalTFPlayerClass( TF_CLASS_MEDIC ) )
  1090. {
  1091. EvaluateNewAchievement();
  1092. }
  1093. }
  1094. };
  1095. DECLARE_ACHIEVEMENT( CAchievementTFMedic_HealGrind, ACHIEVEMENT_TF_MEDIC_HEAL_GRIND, "TF_MEDIC_HEAL_GRIND", 5 );
  1096. //----------------------------------------------------------------------------------------------------------------
  1097. class CAchievementTFMedic_KillHealedSpy : public CBaseTFAchievement
  1098. {
  1099. void Init()
  1100. {
  1101. SetFlags( ACH_SAVE_GLOBAL );
  1102. SetGoal( 1 );
  1103. }
  1104. // server awards this achievement, no other code within achievement necessary
  1105. };
  1106. DECLARE_ACHIEVEMENT( CAchievementTFMedic_KillHealedSpy, ACHIEVEMENT_TF_MEDIC_KILL_HEALED_SPY, "TF_MEDIC_KILL_HEALED_SPY", 5 );
  1107. //----------------------------------------------------------------------------------------------------------------
  1108. class CAchievementTFMedic_SaveFallingTeammate : public CBaseTFAchievement
  1109. {
  1110. void Init()
  1111. {
  1112. SetFlags( ACH_SAVE_GLOBAL );
  1113. SetGoal( 1 );
  1114. }
  1115. // server awards this achievement, no other code within achievement necessary
  1116. };
  1117. DECLARE_ACHIEVEMENT( CAchievementTFMedic_SaveFallingTeammate, ACHIEVEMENT_TF_MEDIC_SAVE_FALLING_TEAMMATE, "TF_MEDIC_SAVE_FALLING_TEAMMATE", 5 );
  1118. //----------------------------------------------------------------------------------------------------------------
  1119. class CAchievementTFMedic_ChargeJuggle : public CBaseTFAchievement
  1120. {
  1121. void Init()
  1122. {
  1123. SetFlags( ACH_SAVE_GLOBAL );
  1124. SetGoal( 1 );
  1125. ResetJuggle();
  1126. }
  1127. ~CAchievementTFMedic_ChargeJuggle()
  1128. {
  1129. ResetJuggle();
  1130. }
  1131. virtual void ListenForEvents( void )
  1132. {
  1133. ListenForGameEvent( "player_invulned" );
  1134. // Clear data on level init
  1135. ResetJuggle();
  1136. }
  1137. void FireGameEvent_Internal( IGameEvent *event )
  1138. {
  1139. if ( FStrEq( event->GetName(), "player_invulned" ) )
  1140. {
  1141. C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer();
  1142. if ( !pLocalPlayer )
  1143. return;
  1144. // Ignore local player getting invuln
  1145. int iUserID = event->GetInt( "userid" );
  1146. if ( iUserID == pLocalPlayer->GetUserID() )
  1147. return;
  1148. if ( event->GetInt( "medic_userid" ) == pLocalPlayer->GetUserID() && iUserID > 0 )
  1149. {
  1150. int index = m_InvulnHistory.AddToHead( new target_history_t );
  1151. m_InvulnHistory[index]->m_iUserID = iUserID;
  1152. m_InvulnHistory[index]->m_flTime = gpGlobals->curtime;
  1153. EvaluateInvulnJuggle();
  1154. }
  1155. }
  1156. }
  1157. void EvaluateInvulnJuggle( void )
  1158. {
  1159. // first clear and entries that are older than max time
  1160. float flTimeCutoff = 5.0f;
  1161. int iCount = m_InvulnHistory.Count();
  1162. for ( int i=iCount-1;i>= 0;i-- )
  1163. {
  1164. if ( ( gpGlobals->curtime - m_InvulnHistory.Element(i)->m_flTime ) > flTimeCutoff )
  1165. {
  1166. m_InvulnHistory.Remove( i );
  1167. }
  1168. }
  1169. // If 1 or 0 targets, reset the count, we wont get a match later.
  1170. if ( m_InvulnHistory.Count() < 2 )
  1171. {
  1172. m_iNumJuggles = 0;
  1173. return;
  1174. }
  1175. // the first item is the guy we just invulned
  1176. // see if the second guy still has TF_COND_INVULNERABLE_WEARINGOFF
  1177. target_history_t *first = m_InvulnHistory.Element(0);
  1178. target_history_t *second = m_InvulnHistory.Element(1);
  1179. if ( !first || !second )
  1180. {
  1181. ResetJuggle();
  1182. return;
  1183. }
  1184. if ( first->m_iUserID == second->m_iUserID )
  1185. {
  1186. ResetJuggle();
  1187. return;
  1188. }
  1189. // so we have two different players now.
  1190. // make sure second has TF_COND_INVULNERABLE_WEARINGOFF
  1191. int iTargetIndex = engine->GetPlayerForUserID( second->m_iUserID );
  1192. if ( iTargetIndex > 0 )
  1193. {
  1194. C_TFPlayer *pTarget = ToTFPlayer( UTIL_PlayerByIndex( iTargetIndex ) );
  1195. if ( pTarget )
  1196. {
  1197. if ( pTarget->m_Shared.InCond( TF_COND_INVULNERABLE_WEARINGOFF ) || pTarget->m_Shared.InCond( TF_COND_INVULNERABLE ) )
  1198. {
  1199. // assume it was us that gave him the invuln
  1200. m_iNumJuggles++;
  1201. }
  1202. else
  1203. {
  1204. ResetJuggle();
  1205. }
  1206. }
  1207. }
  1208. if ( m_iNumJuggles >= 4 )
  1209. {
  1210. IncrementCount();
  1211. }
  1212. }
  1213. void ResetJuggle( void )
  1214. {
  1215. m_iNumJuggles = 0;
  1216. m_InvulnHistory.PurgeAndDeleteElements();
  1217. }
  1218. private:
  1219. int m_iNumJuggles;
  1220. typedef struct
  1221. {
  1222. int m_iUserID;
  1223. float m_flTime;
  1224. } target_history_t;
  1225. // array of userid's/times that we have recently invulned
  1226. CUtlVector< target_history_t *> m_InvulnHistory;
  1227. };
  1228. DECLARE_ACHIEVEMENT( CAchievementTFMedic_ChargeJuggle, ACHIEVEMENT_TF_MEDIC_CHARGE_JUGGLE, "TF_MEDIC_CHARGE_JUGGLE", 5 );
  1229. //----------------------------------------------------------------------------------------------------------------
  1230. class CAchievementTFMedic_FreezecamRagdoll : public CBaseTFAchievement
  1231. {
  1232. void Init()
  1233. {
  1234. SetFlags( ACH_SAVE_GLOBAL );
  1235. SetGoal( 1 );
  1236. }
  1237. };
  1238. DECLARE_ACHIEVEMENT( CAchievementTFMedic_FreezecamRagdoll, ACHIEVEMENT_TF_MEDIC_FREEZECAM_RAGDOLL, "TF_MEDIC_FREEZECAM_RAGDOLL", 5 );
  1239. //----------------------------------------------------------------------------------------------------------------
  1240. class CAchievementTFMedic_BonesawSpyCallers : public CBaseTFAchievement
  1241. {
  1242. void Init()
  1243. {
  1244. SetFlags( ACH_SAVE_GLOBAL | ACH_LISTEN_PLAYER_KILL_ENEMY_EVENTS );
  1245. SetGoal( 1 );
  1246. }
  1247. // Use your bonesaw to kill an enemy spy who has been calling for "Medic!".
  1248. virtual void Event_EntityKilled( CBaseEntity *pVictim, CBaseEntity *pAttacker, CBaseEntity *pInflictor, IGameEvent *event )
  1249. {
  1250. Assert( pAttacker == C_TFPlayer::GetLocalTFPlayer() );
  1251. if ( event->GetInt( "weaponid" ) != TF_WEAPON_BONESAW )
  1252. return;
  1253. // victim is a spy and has called for medic recently
  1254. // we will only have the saveme counter if we were able to see the
  1255. // saveme effect, so he must have been disguised at that point
  1256. C_TFPlayer *pTFVictim = ToTFPlayer( pVictim );
  1257. if ( pTFVictim && pTFVictim->IsPlayerClass( TF_CLASS_SPY ) )
  1258. {
  1259. if ( pTFVictim->m_flSaveMeExpireTime > gpGlobals->curtime )
  1260. {
  1261. IncrementCount();
  1262. }
  1263. }
  1264. }
  1265. };
  1266. DECLARE_ACHIEVEMENT( CAchievementTFMedic_BonesawSpyCallers, ACHIEVEMENT_TF_MEDIC_BONESAW_SPY_CALLERS, "TF_MEDIC_BONESAW_SPY_CALLERS", 5 );
  1267. //----------------------------------------------------------------------------------------------------------------
  1268. ConVar tf_chargedfriends( "tf_chargedfriends", "", FCVAR_ARCHIVE );
  1269. class CAchievementTFMedic_ChargeFriends : public CBaseTFAchievement
  1270. {
  1271. void Init()
  1272. {
  1273. SetFlags( ACH_SAVE_GLOBAL );
  1274. SetGoal( 5 );
  1275. }
  1276. // Uber-charge ten of your Steam Community Friends
  1277. virtual void ListenForEvents( void )
  1278. {
  1279. ListenForGameEvent( "player_invulned" );
  1280. }
  1281. void FireGameEvent_Internal( IGameEvent *event )
  1282. {
  1283. if ( FStrEq( event->GetName(), "player_invulned" ) )
  1284. {
  1285. C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer();
  1286. if ( !pLocalPlayer )
  1287. return;
  1288. int iTargetIndex = engine->GetPlayerForUserID( event->GetInt( "userid" ) );
  1289. if ( event->GetInt( "medic_userid" ) == pLocalPlayer->GetUserID() && iTargetIndex > 0 )
  1290. {
  1291. CBasePlayer *pTarget = UTIL_PlayerByIndex( iTargetIndex );
  1292. if ( !steamapicontext->SteamFriends() || !steamapicontext->SteamUtils() || !g_pGameRules->IsMultiplayer() )
  1293. return;
  1294. player_info_t pi;
  1295. if ( !engine->GetPlayerInfo( pTarget->entindex(), &pi ) )
  1296. return;
  1297. if ( !pi.friendsID )
  1298. return;
  1299. // check and see if they're on the local player's friends list
  1300. CSteamID steamID( pi.friendsID, 1, GetUniverse(), k_EAccountTypeIndividual );
  1301. if ( steamapicontext->SteamFriends()->HasFriend( steamID, k_EFriendFlagImmediate ) )
  1302. {
  1303. // get the friendsID
  1304. //
  1305. // parse tf_chargedfriends into an array
  1306. CUtlStringList vecChargedFriends;
  1307. V_SplitString( tf_chargedfriends.GetString(), ":", vecChargedFriends );
  1308. char szTargetFriendsID[16];
  1309. Q_snprintf( szTargetFriendsID, sizeof(szTargetFriendsID), "%d", pi.friendsID );
  1310. bool bFound = false;
  1311. for ( int i=0;i<vecChargedFriends.Count();i++ )
  1312. {
  1313. if ( (uint32)atoi(vecChargedFriends[i]) == pi.friendsID )
  1314. {
  1315. bFound = true;
  1316. break;
  1317. }
  1318. }
  1319. if ( !bFound )
  1320. {
  1321. // If this would get us the achievement, validate the friends ids that we have in our list
  1322. if ( ( vecChargedFriends.Count() + 1 ) > m_iCount )
  1323. {
  1324. EUniverse universe = GetUniverse();
  1325. // validate the friends
  1326. for ( int i=vecChargedFriends.Count()-1;i>=0;i-- )
  1327. {
  1328. uint32 iFriendID = (uint32)atoi(vecChargedFriends[i]);
  1329. CSteamID steamIDFriend( iFriendID, 1, universe, k_EAccountTypeIndividual );
  1330. if ( !steamapicontext->SteamFriends()->HasFriend( steamIDFriend, k_EFriendFlagImmediate ) )
  1331. {
  1332. // remove this person, not a friend anymore, or trying to cheat
  1333. vecChargedFriends.Remove(i);
  1334. }
  1335. }
  1336. }
  1337. // If we still have more valid targets, increment for real
  1338. if ( ( vecChargedFriends.Count() + 1 ) > m_iCount )
  1339. {
  1340. IncrementCount();
  1341. }
  1342. // write friends back to the convar
  1343. char buf[512]; // what is max size of 10 * steam ids?
  1344. Q_snprintf( buf, sizeof(buf), "%d", pi.friendsID );
  1345. for ( int i=0;i<vecChargedFriends.Count();i++ )
  1346. {
  1347. Q_strncat( buf, VarArgs( ":%d", (uint32)atoi(vecChargedFriends[i]) ), sizeof(buf), COPY_ALL_CHARACTERS );
  1348. }
  1349. tf_chargedfriends.SetValue( buf );
  1350. }
  1351. }
  1352. }
  1353. }
  1354. }
  1355. };
  1356. DECLARE_ACHIEVEMENT( CAchievementTFMedic_ChargeFriends, ACHIEVEMENT_TF_MEDIC_CHARGE_FRIENDS, "TF_MEDIC_CHARGE_FRIENDS", 5 );
  1357. //----------------------------------------------------------------------------------------------------------------
  1358. class CAchievementTFMedic_InviteJoinCharge : public CBaseTFAchievement
  1359. {
  1360. void Init()
  1361. {
  1362. SetFlags( ACH_SAVE_GLOBAL );
  1363. SetGoal( 1 );
  1364. }
  1365. // Join a game that one of your friends is in and then deploy an Uber-Charge on him.
  1366. // when we first select a class, it has to be medic. Then we make a list of all our connected friends
  1367. // the first
  1368. // called every changelevel - doh
  1369. virtual void ListenForEvents( void )
  1370. {
  1371. ListenForGameEvent( "localplayer_changeclass" );
  1372. ListenForGameEvent( "player_invulned" );
  1373. m_iConnectedFriends.Purge();
  1374. }
  1375. virtual void FireGameEvent_Internal( IGameEvent *event )
  1376. {
  1377. C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
  1378. if ( !pLocalPlayer )
  1379. return;
  1380. const char *pszName = event->GetName();
  1381. if ( FStrEq( pszName, "localplayer_changeclass" ) )
  1382. {
  1383. // if the server has been on this map for less than some amount of time
  1384. // then this is probably a changelevel, and doesn't count.
  1385. if ( gpGlobals->curtime < ( 4 * 60 ) )
  1386. {
  1387. StopListeningForAllEvents();
  1388. return;
  1389. }
  1390. // if this is not medic, stop listening
  1391. bool bWantsMedic = (IsLocalTFPlayerClass( TF_CLASS_MEDIC ) ||
  1392. pLocalPlayer->m_Shared.GetDesiredPlayerClassIndex() == TF_CLASS_MEDIC );
  1393. if ( bWantsMedic )
  1394. {
  1395. CountConnectedFriends();
  1396. }
  1397. else
  1398. {
  1399. StopListeningForAllEvents();
  1400. }
  1401. }
  1402. else if ( FStrEq( pszName, "player_invulned" ) )
  1403. {
  1404. if ( event->GetInt( "medic_userid" ) == pLocalPlayer->GetUserID() )
  1405. {
  1406. // is this person a friend who was on when we joined?
  1407. int iTargetIndex = engine->GetPlayerForUserID( event->GetInt( "userid" ) );
  1408. if ( IsInvulnTargetInFriendsList(iTargetIndex) )
  1409. {
  1410. IncrementCount();
  1411. }
  1412. else
  1413. {
  1414. StopListeningForAllEvents();
  1415. }
  1416. }
  1417. }
  1418. }
  1419. void CountConnectedFriends( void )
  1420. {
  1421. if ( !g_PR )
  1422. return;
  1423. Assert( g_pGameRules->IsMultiplayer() );
  1424. // determine local player team
  1425. int iLocalPlayerIndex = GetLocalPlayerIndex();
  1426. if ( !steamapicontext->SteamFriends() || !steamapicontext->SteamUtils() || !g_pGameRules->IsMultiplayer() )
  1427. return;
  1428. // store connected friends' steam ids
  1429. for( int iPlayerIndex = 1 ; iPlayerIndex <= MAX_PLAYERS; iPlayerIndex++ )
  1430. {
  1431. if( ( iPlayerIndex != iLocalPlayerIndex ) && ( g_PR->IsConnected( iPlayerIndex ) ) )
  1432. {
  1433. player_info_t pi;
  1434. if ( !engine->GetPlayerInfo( iPlayerIndex, &pi ) )
  1435. continue;
  1436. if ( !pi.friendsID )
  1437. continue;
  1438. // check and see if they're on the local player's friends list
  1439. CSteamID steamID( pi.friendsID, 1, GetUniverse(), k_EAccountTypeIndividual );
  1440. if ( !steamapicontext->SteamFriends()->HasFriend( steamID, k_EFriendFlagImmediate ) )
  1441. continue;
  1442. m_iConnectedFriends.AddToTail( steamID );
  1443. }
  1444. }
  1445. }
  1446. bool IsInvulnTargetInFriendsList( int iTargetIndex )
  1447. {
  1448. player_info_t pi;
  1449. if ( !engine->GetPlayerInfo( iTargetIndex, &pi ) )
  1450. return false;
  1451. if ( !pi.friendsID )
  1452. return false;
  1453. if ( !steamapicontext->SteamUtils() )
  1454. return false;
  1455. CSteamID steamID( pi.friendsID, 1, GetUniverse(), k_EAccountTypeIndividual );
  1456. return ( m_iConnectedFriends.Find( steamID ) != m_iConnectedFriends.InvalidIndex() );
  1457. }
  1458. private:
  1459. CUtlVector< CSteamID > m_iConnectedFriends;
  1460. };
  1461. DECLARE_ACHIEVEMENT( CAchievementTFMedic_InviteJoinCharge, ACHIEVEMENT_TF_MEDIC_INVITE_JOIN_CHARGE, "TF_MEDIC_INVITE_JOIN_CHARGE", 5 );
  1462. //----------------------------------------------------------------------------------------------------------------
  1463. class CAchievementTFMedic_HealAchiever : public CBaseTFAchievement
  1464. {
  1465. void Init()
  1466. {
  1467. SetFlags( ACH_SAVE_GLOBAL );
  1468. SetGoal( 1 );
  1469. }
  1470. // Be healing a teammate as he achieves an achievement of his own.
  1471. virtual void ListenForEvents( void )
  1472. {
  1473. ListenForGameEvent( "achievement_earned" );
  1474. }
  1475. virtual void FireGameEvent_Internal( IGameEvent *event )
  1476. {
  1477. C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
  1478. if ( !pLocalPlayer )
  1479. return;
  1480. const char *pszName = event->GetName();
  1481. if ( FStrEq( pszName, "achievement_earned" ) )
  1482. {
  1483. int iPlayerIndex = event->GetInt( "player" );
  1484. C_BasePlayer *pPlayer = UTIL_PlayerByIndex( iPlayerIndex );
  1485. if ( pPlayer && !pPlayer->IsDormant() )
  1486. {
  1487. // if he is our heal target
  1488. if ( pLocalPlayer->MedicGetHealTarget() == pPlayer )
  1489. {
  1490. IncrementCount();
  1491. }
  1492. }
  1493. }
  1494. }
  1495. };
  1496. DECLARE_ACHIEVEMENT( CAchievementTFMedic_HealAchiever, ACHIEVEMENT_TF_MEDIC_HEAL_ACHIEVER, "TF_MEDIC_HEAL_ACHIEVER", 5 );
  1497. //----------------------------------------------------------------------------------------------------------------
  1498. class CAchievementTFMedic_AchieveProgress1 : public CAchievement_AchievedCount
  1499. {
  1500. public:
  1501. DECLARE_CLASS( CAchievementTFMedic_AchieveProgress1, CAchievement_AchievedCount );
  1502. void Init()
  1503. {
  1504. BaseClass::Init();
  1505. SetAchievementsRequired( 10, ACHIEVEMENT_TF_MEDIC_START_RANGE, ACHIEVEMENT_TF_MEDIC_END_RANGE );
  1506. }
  1507. // Earn 10 of the 36 Medic Pack Achievements ( 3 achievement count achievements not included )
  1508. };
  1509. DECLARE_ACHIEVEMENT( CAchievementTFMedic_AchieveProgress1, ACHIEVEMENT_TF_MEDIC_ACHIEVE_PROGRESS1, "TF_MEDIC_ACHIEVE_PROGRESS1", 5 );
  1510. //----------------------------------------------------------------------------------------------------------------
  1511. class CAchievementTFMedic_AchieveProgress2 : public CAchievement_AchievedCount
  1512. {
  1513. public:
  1514. DECLARE_CLASS( CAchievementTFMedic_AchieveProgress2, CAchievement_AchievedCount );
  1515. void Init()
  1516. {
  1517. BaseClass::Init();
  1518. SetAchievementsRequired( 16, ACHIEVEMENT_TF_MEDIC_START_RANGE, ACHIEVEMENT_TF_MEDIC_END_RANGE );
  1519. }
  1520. // Earn 15 of the 36 Medic Pack Achievements ( 3 achievement count achievements not included )
  1521. };
  1522. DECLARE_ACHIEVEMENT( CAchievementTFMedic_AchieveProgress2, ACHIEVEMENT_TF_MEDIC_ACHIEVE_PROGRESS2, "TF_MEDIC_ACHIEVE_PROGRESS2", 5 );
  1523. //----------------------------------------------------------------------------------------------------------------
  1524. class CAchievementTFMedic_AchieveProgress3 : public CAchievement_AchievedCount
  1525. {
  1526. public:
  1527. DECLARE_CLASS( CAchievementTFMedic_AchieveProgress3, CAchievement_AchievedCount );
  1528. void Init()
  1529. {
  1530. BaseClass::Init();
  1531. SetAchievementsRequired( 22, ACHIEVEMENT_TF_MEDIC_START_RANGE, ACHIEVEMENT_TF_MEDIC_END_RANGE );
  1532. }
  1533. // Earn 20 of the 36 Medic Pack Achievements ( 3 achievement count achievements not included )
  1534. };
  1535. DECLARE_ACHIEVEMENT( CAchievementTFMedic_AchieveProgress3, ACHIEVEMENT_TF_MEDIC_ACHIEVE_PROGRESS3, "TF_MEDIC_ACHIEVE_PROGRESS3", 5 );
  1536. #endif // CLIENT_DLL