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.

417 lines
16 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: TF's custom CPlayerResource
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================//
  7. #include "cbase.h"
  8. #include "tf_player.h"
  9. #include "player_resource.h"
  10. #include "tf_player_resource.h"
  11. #include "tf_gamestats.h"
  12. #include "tf_gamerules.h"
  13. #include <coordsize.h>
  14. #include "tf_matchmaking_shared.h"
  15. #include "tf_mann_vs_machine_stats.h"
  16. #include "player_vs_environment/tf_population_manager.h"
  17. #include "tf_gc_server.h"
  18. #define STATS_SEND_FREQUENCY 1.f
  19. // Datatable
  20. IMPLEMENT_SERVERCLASS_ST( CTFPlayerResource, DT_TFPlayerResource )
  21. SendPropArray3( SENDINFO_ARRAY3( m_iTotalScore ), SendPropInt( SENDINFO_ARRAY( m_iTotalScore ), -1, SPROP_UNSIGNED | SPROP_VARINT ) ),
  22. SendPropArray3( SENDINFO_ARRAY3( m_iMaxHealth ), SendPropInt( SENDINFO_ARRAY( m_iMaxHealth ), -1, SPROP_UNSIGNED | SPROP_VARINT ) ),
  23. SendPropArray3( SENDINFO_ARRAY3( m_iMaxBuffedHealth ), SendPropInt( SENDINFO_ARRAY( m_iMaxBuffedHealth ), -1, SPROP_UNSIGNED | SPROP_VARINT ) ),
  24. SendPropArray3( SENDINFO_ARRAY3( m_iPlayerClass ), SendPropInt( SENDINFO_ARRAY( m_iPlayerClass ), 5, SPROP_UNSIGNED ) ),
  25. SendPropArray3( SENDINFO_ARRAY3( m_bArenaSpectator ), SendPropBool( SENDINFO_ARRAY( m_bArenaSpectator ) ) ),
  26. SendPropArray3( SENDINFO_ARRAY3( m_iActiveDominations ), SendPropInt( SENDINFO_ARRAY( m_iActiveDominations ), 6, SPROP_UNSIGNED ) ),
  27. SendPropArray3( SENDINFO_ARRAY3( m_flNextRespawnTime ), SendPropTime( SENDINFO_ARRAY( m_flNextRespawnTime ) ) ),
  28. SendPropArray3( SENDINFO_ARRAY3( m_iChargeLevel ), SendPropInt( SENDINFO_ARRAY( m_iChargeLevel ), 8, SPROP_UNSIGNED ) ),
  29. SendPropArray3( SENDINFO_ARRAY3( m_iDamage ), SendPropInt( SENDINFO_ARRAY( m_iDamage ), -1, SPROP_UNSIGNED | SPROP_VARINT ) ),
  30. SendPropArray3( SENDINFO_ARRAY3( m_iDamageAssist ), SendPropInt( SENDINFO_ARRAY( m_iDamageAssist ), -1, SPROP_UNSIGNED | SPROP_VARINT ) ),
  31. SendPropArray3( SENDINFO_ARRAY3( m_iDamageBoss ), SendPropInt( SENDINFO_ARRAY( m_iDamageBoss ), -1, SPROP_UNSIGNED | SPROP_VARINT ) ),
  32. SendPropArray3( SENDINFO_ARRAY3( m_iHealing ), SendPropInt( SENDINFO_ARRAY( m_iHealing ), -1, SPROP_UNSIGNED | SPROP_VARINT ) ),
  33. SendPropArray3( SENDINFO_ARRAY3( m_iHealingAssist ), SendPropInt( SENDINFO_ARRAY( m_iHealingAssist ), -1, SPROP_UNSIGNED | SPROP_VARINT ) ),
  34. SendPropArray3( SENDINFO_ARRAY3( m_iDamageBlocked ), SendPropInt( SENDINFO_ARRAY( m_iDamageBlocked ), -1, SPROP_UNSIGNED | SPROP_VARINT ) ),
  35. SendPropArray3( SENDINFO_ARRAY3( m_iCurrencyCollected ), SendPropInt( SENDINFO_ARRAY( m_iCurrencyCollected ), -1, SPROP_UNSIGNED | SPROP_VARINT ) ),
  36. SendPropArray3( SENDINFO_ARRAY3( m_iBonusPoints ), SendPropInt( SENDINFO_ARRAY( m_iBonusPoints ), -1, SPROP_UNSIGNED | SPROP_VARINT ) ),
  37. SendPropArray3( SENDINFO_ARRAY3( m_iPlayerLevel ), SendPropInt( SENDINFO_ARRAY( m_iPlayerLevel ), -1, SPROP_UNSIGNED | SPROP_VARINT ) ),
  38. SendPropArray3( SENDINFO_ARRAY3( m_iStreaks ), SendPropInt( SENDINFO_ARRAY( m_iStreaks ), -1, SPROP_UNSIGNED | SPROP_VARINT ) ),
  39. SendPropArray3( SENDINFO_ARRAY3( m_iUpgradeRefundCredits ), SendPropInt( SENDINFO_ARRAY( m_iUpgradeRefundCredits ), -1, SPROP_UNSIGNED | SPROP_VARINT ) ),
  40. SendPropArray3( SENDINFO_ARRAY3( m_iBuybackCredits ), SendPropInt( SENDINFO_ARRAY( m_iBuybackCredits ), -1, SPROP_UNSIGNED | SPROP_VARINT ) ),
  41. SendPropInt( SENDINFO( m_iPartyLeaderRedTeamIndex ), -1, SPROP_UNSIGNED | SPROP_VARINT ),
  42. SendPropInt( SENDINFO( m_iPartyLeaderBlueTeamIndex ), -1, SPROP_UNSIGNED | SPROP_VARINT ),
  43. SendPropInt( SENDINFO( m_iEventTeamStatus ), -1, SPROP_UNSIGNED | SPROP_VARINT ),
  44. SendPropArray3( SENDINFO_ARRAY3( m_iPlayerClassWhenKilled ), SendPropInt( SENDINFO_ARRAY( m_iPlayerClassWhenKilled ), 5, SPROP_UNSIGNED ) ),
  45. SendPropArray3( SENDINFO_ARRAY3( m_iConnectionState ), SendPropInt( SENDINFO_ARRAY( m_iConnectionState ), 3, SPROP_UNSIGNED ) ),
  46. END_SEND_TABLE()
  47. LINK_ENTITY_TO_CLASS( tf_player_manager, CTFPlayerResource );
  48. //-----------------------------------------------------------------------------
  49. // Purpose:
  50. //-----------------------------------------------------------------------------
  51. CTFPlayerResource::CTFPlayerResource( void )
  52. {
  53. ListenForGameEvent( "mvm_wave_complete" );
  54. m_flNextDamageAndHealingSend = 0.f;
  55. m_iPartyLeaderRedTeamIndex = 0;
  56. m_iPartyLeaderBlueTeamIndex = 0;
  57. m_iEventTeamStatus = 0;
  58. }
  59. //-----------------------------------------------------------------------------
  60. // Purpose:
  61. //-----------------------------------------------------------------------------
  62. void CTFPlayerResource::FireGameEvent( IGameEvent * event )
  63. {
  64. const char *pszEvent = event->GetName();
  65. if ( !Q_strcmp( pszEvent, "mvm_wave_complete" ) )
  66. {
  67. // Force a re-send on wave complete
  68. m_flNextDamageAndHealingSend = 0.f;
  69. UpdatePlayerData();
  70. }
  71. }
  72. //-----------------------------------------------------------------------------
  73. // Purpose:
  74. //-----------------------------------------------------------------------------
  75. void CTFPlayerResource::SetPartyLeaderIndex( int iTeam, int iIndex )
  76. {
  77. Assert( iIndex >= 0 && iIndex <= MAX_PLAYERS );
  78. switch( iTeam )
  79. {
  80. case TF_TEAM_RED:
  81. m_iPartyLeaderRedTeamIndex = iIndex;
  82. break;
  83. case TF_TEAM_BLUE:
  84. m_iPartyLeaderBlueTeamIndex = iIndex;
  85. break;
  86. default:
  87. break;
  88. }
  89. }
  90. //-----------------------------------------------------------------------------
  91. // Purpose:
  92. //-----------------------------------------------------------------------------
  93. int CTFPlayerResource::GetPartyLeaderIndex( int iTeam )
  94. {
  95. if ( iTeam == TF_TEAM_RED )
  96. return m_iPartyLeaderRedTeamIndex;
  97. else if ( iTeam == TF_TEAM_BLUE )
  98. return m_iPartyLeaderBlueTeamIndex;
  99. return 0;
  100. }
  101. //-----------------------------------------------------------------------------
  102. // Purpose:
  103. //-----------------------------------------------------------------------------
  104. void CTFPlayerResource::UpdatePlayerData( void )
  105. {
  106. m_vecRedPlayers.RemoveAll();
  107. m_vecBluePlayers.RemoveAll();
  108. m_vecFreeSlots.RemoveAll();
  109. BaseClass::UpdatePlayerData();
  110. // check if player is still part of the match
  111. CMatchInfo *pMatch = GTFGCClientSystem()->GetMatch();
  112. if ( pMatch && TFGameRules() )
  113. {
  114. for ( int i=0; i<pMatch->GetNumTotalMatchPlayers(); ++i )
  115. {
  116. AssertMsg( m_vecFreeSlots.Count() > 0, "There should always be free slots for player to join" );
  117. CMatchInfo::PlayerMatchData_t *pData = pMatch->GetMatchDataForPlayer( i );
  118. uint32 unAccountID = pData->steamID.GetAccountID();
  119. int iTeam = TFGameRules()->GetGameTeamForGCTeam( pData->eGCTeam );
  120. CUtlVector< uint32 >* pVecPlayers = iTeam == TF_TEAM_RED ? &m_vecRedPlayers : &m_vecBluePlayers;
  121. // add players that are not yet connected to the server
  122. if ( !pData->bDropped && pVecPlayers->Find( unAccountID ) == pVecPlayers->InvalidIndex() && m_vecFreeSlots.Count() > 0 )
  123. {
  124. int iIndex = m_vecFreeSlots[0];
  125. m_vecFreeSlots.Remove( 0 );
  126. AssertMsg( m_iAccountID[iIndex] == 0, "No account should be assigned to this slot" );
  127. Init( iIndex );
  128. m_iAccountID.Set( iIndex, unAccountID );
  129. m_bValid.Set( iIndex, 1 );
  130. m_iTeam.Set( iIndex, iTeam );
  131. m_iConnectionState.Set( iIndex, pData->GetConnectionState() );
  132. }
  133. }
  134. // do we need to set m_bValid on these?
  135. if ( GTFGCClientSystem()->BLateJoinEligible() )
  136. {
  137. int iTeamSize = pMatch->GetCanonicalMatchSize() / 2;
  138. int iRedWaiting = iTeamSize-m_vecRedPlayers.Count();
  139. for ( int i=0; i<iRedWaiting && m_vecFreeSlots.Count() > 0; ++i )
  140. {
  141. int iIndex = m_vecFreeSlots[0];
  142. m_vecFreeSlots.Remove( 0 );
  143. AssertMsg( m_iAccountID[iIndex] == 0, "No account should be assigned to this slot" );
  144. m_iTeam.Set( iIndex, TF_TEAM_RED );
  145. m_iConnectionState.Set( iIndex, MM_WAITING_FOR_PLAYER );
  146. }
  147. int iBlueWaiting = iTeamSize-m_vecBluePlayers.Count();
  148. for ( int i=0; i<iBlueWaiting && m_vecFreeSlots.Count() > 0; ++i )
  149. {
  150. int iIndex = m_vecFreeSlots[0];
  151. m_vecFreeSlots.Remove( 0 );
  152. AssertMsg( m_iAccountID[iIndex] == 0, "No account should be assigned to this slot" );
  153. m_iTeam.Set( iIndex, TF_TEAM_BLUE );
  154. m_iConnectionState.Set( iIndex, MM_WAITING_FOR_PLAYER );
  155. }
  156. }
  157. }
  158. if ( gpGlobals->curtime > m_flNextDamageAndHealingSend )
  159. {
  160. m_flNextDamageAndHealingSend = gpGlobals->curtime + STATS_SEND_FREQUENCY;
  161. }
  162. if ( pMatch && m_iEventTeamStatus != (int)pMatch->m_unEventTeamStatus )
  163. {
  164. m_iEventTeamStatus = pMatch->m_unEventTeamStatus;
  165. }
  166. }
  167. //-----------------------------------------------------------------------------
  168. // Purpose:
  169. //-----------------------------------------------------------------------------
  170. void CTFPlayerResource::UpdateConnectedPlayer( int iIndex, CBasePlayer *pPlayer )
  171. {
  172. BaseClass::UpdateConnectedPlayer( iIndex, pPlayer );
  173. CTFPlayer *pTFPlayer = ToTFPlayer( pPlayer );
  174. PlayerStats_t *pTFPlayerStats = CTF_GameStats.FindPlayerStats( pTFPlayer );
  175. if ( pTFPlayerStats )
  176. {
  177. // Update accumulated and per-round stats
  178. localplayerscoring_t *pData = pTFPlayer->m_Shared.GetScoringData();
  179. pData->UpdateStats( pTFPlayerStats->statsAccumulated, pTFPlayer, false );
  180. pData = pTFPlayer->m_Shared.GetRoundScoringData();
  181. pData->UpdateStats( pTFPlayerStats->statsCurrentRound, pTFPlayer, true );
  182. // Send every STATS_SEND_FREQUENCY (1.f)
  183. if ( gpGlobals->curtime > m_flNextDamageAndHealingSend )
  184. {
  185. m_iDamage.Set( iIndex, pTFPlayerStats->statsCurrentRound.m_iStat[TFSTAT_DAMAGE] );
  186. m_iDamageAssist.Set( iIndex, pTFPlayerStats->statsCurrentRound.m_iStat[TFSTAT_DAMAGE_ASSIST] );
  187. m_iDamageBoss.Set( iIndex, pTFPlayerStats->statsCurrentRound.m_iStat[TFSTAT_DAMAGE_BOSS] );
  188. m_iHealing.Set( iIndex, pTFPlayerStats->statsCurrentRound.m_iStat[TFSTAT_HEALING] );
  189. m_iHealingAssist.Set( iIndex, pTFPlayerStats->statsCurrentRound.m_iStat[TFSTAT_HEALING_ASSIST] );
  190. m_iDamageBlocked.Set( iIndex, pTFPlayerStats->statsCurrentRound.m_iStat[TFSTAT_DAMAGE_BLOCKED] );
  191. m_iCurrencyCollected.Set( iIndex, pTFPlayerStats->statsCurrentRound.m_iStat[TFSTAT_CURRENCY_COLLECTED] );
  192. m_iBonusPoints.Set( iIndex, pTFPlayerStats->statsCurrentRound.m_iStat[TFSTAT_BONUS_POINTS] );
  193. m_iPlayerLevel.Set( iIndex, pTFPlayer->GetExperienceLevel() );
  194. }
  195. }
  196. m_iMaxHealth.Set( iIndex, pTFPlayer->GetMaxHealth() );
  197. // m_iMaxBuffedHealth is misnamed -- it should be m_iMaxHealthForBuffing, but we don't want to change it now due to demos.
  198. m_iMaxBuffedHealth.Set( iIndex, pTFPlayer->GetMaxHealthForBuffing() );
  199. m_iPlayerClass.Set( iIndex, pTFPlayer->GetPlayerClass()->GetClassIndex() );
  200. m_iActiveDominations.Set( iIndex, pTFPlayer->GetNumberofDominations() );
  201. int iTotalScore = CTFGameRules::CalcPlayerScore( &pTFPlayerStats->statsAccumulated, pTFPlayer );
  202. if ( m_iTotalScore.Get( iIndex ) != iTotalScore )
  203. {
  204. int nDelta = iTotalScore - m_iTotalScore.Get( iIndex );
  205. if ( TFGameRules()->IsMannVsMachineMode() )
  206. {
  207. MannVsMachineStats_PlayerEvent_PointsChanged( pTFPlayer, nDelta );
  208. }
  209. else
  210. {
  211. // Kill eater points-scored tracking. Increment all equipped items with this kill eater type.
  212. // We only do this when we're NOT in MvM
  213. HatAndMiscEconEntities_OnOwnerKillEaterEventNoParter( pTFPlayer, kKillEaterEvent_PointsScored, nDelta );
  214. }
  215. }
  216. m_iTotalScore.Set( iIndex, iTotalScore );
  217. m_bArenaSpectator.Set( iIndex, pTFPlayer->IsArenaSpectator() );
  218. if ( TFGameRules()->IsInTournamentMode() )
  219. {
  220. float flCharge = pTFPlayer->MedicGetChargeLevel();
  221. m_iChargeLevel.Set( iIndex, (int)(flCharge * 100) );
  222. }
  223. else
  224. {
  225. m_iChargeLevel.Set( iIndex, 0 );
  226. }
  227. float flRespawnTime = pTFPlayer->IsAlive() ? 0 : TFGameRules()->GetNextRespawnWave( pTFPlayer->GetTeamNumber(), pTFPlayer );
  228. if ( pTFPlayer->GetRespawnTimeOverride() != -1.f )
  229. {
  230. flRespawnTime = pTFPlayer->GetDeathTime() + pTFPlayer->GetRespawnTimeOverride();
  231. }
  232. m_flNextRespawnTime.Set( iIndex, flRespawnTime );
  233. for ( int streak_type = 0; streak_type < CTFPlayerShared::kTFStreak_COUNT; streak_type++ )
  234. {
  235. m_iStreaks.Set( iIndex * CTFPlayerShared::kTFStreak_COUNT + streak_type, pTFPlayer->m_Shared.GetStreak( (CTFPlayerShared::ETFStreak)streak_type ) );
  236. }
  237. if ( g_pPopulationManager )
  238. {
  239. // Only update when we have new data
  240. int nRespecs = g_pPopulationManager->GetNumRespecsAvailableForPlayer( pTFPlayer );
  241. m_iUpgradeRefundCredits.Set( iIndex, nRespecs );
  242. int nBuybacks = g_pPopulationManager->GetNumBuybackCreditsForPlayer( pTFPlayer );
  243. m_iBuybackCredits.Set( iIndex, nBuybacks );
  244. }
  245. CSteamID steamID;
  246. pTFPlayer->GetSteamID( &steamID );
  247. int iTeam = pPlayer->GetTeamNumber();
  248. CMatchInfo *pMatch = GTFGCClientSystem()->GetMatch();
  249. if ( pMatch )
  250. {
  251. CMatchInfo::PlayerMatchData_t *pData = pMatch->GetMatchDataForPlayer( steamID );
  252. if ( pData )
  253. {
  254. iTeam = TFGameRules()->GetGameTeamForGCTeam( pData->eGCTeam );
  255. }
  256. }
  257. CUtlVector< uint32 >* pVecPlayers = ( iTeam == TF_TEAM_RED ) ? &m_vecRedPlayers : ( ( iTeam == TF_TEAM_BLUE ) ? &m_vecBluePlayers : NULL );
  258. if ( pVecPlayers )
  259. {
  260. if ( pVecPlayers->Find( steamID.GetAccountID() ) == pVecPlayers->InvalidIndex() )
  261. {
  262. pVecPlayers->AddToTail( steamID.GetAccountID() );
  263. }
  264. }
  265. m_iConnectionState.Set( iIndex, MM_CONNECTED );
  266. }
  267. //-----------------------------------------------------------------------------
  268. // Purpose:
  269. //-----------------------------------------------------------------------------
  270. void CTFPlayerResource::UpdateDisconnectedPlayer( int iIndex )
  271. {
  272. // cache accountID to see if we should preserve this account
  273. uint32 unAccountID = m_iAccountID[iIndex];
  274. BaseClass::UpdateDisconnectedPlayer( iIndex );
  275. // preserve if player is still in part of the match, and still gone
  276. CMatchInfo *pMatch = GTFGCClientSystem()->GetMatch();
  277. if ( pMatch )
  278. {
  279. CSteamID steamID( unAccountID, GetUniverse(), k_EAccountTypeIndividual );
  280. if ( steamID.IsValid() )
  281. {
  282. CBasePlayer *pPlayer = (CBasePlayer*)UTIL_PlayerBySteamID( steamID );
  283. CMatchInfo::PlayerMatchData_t *pData = pMatch->GetMatchDataForPlayer( steamID );
  284. // Skip if they're connected
  285. if ( pData && ( !pPlayer || !pPlayer->IsConnected() ) )
  286. {
  287. if ( !pData->bDropped )
  288. {
  289. int iTeam = TFGameRules()->GetGameTeamForGCTeam( pData->eGCTeam );
  290. m_iConnectionState.Set( iIndex, pData->GetConnectionState() );
  291. // re-apply the accountID to keep the data
  292. m_iAccountID.Set( iIndex, unAccountID );
  293. m_iTeam.Set( iIndex, iTeam );
  294. m_bValid.Set( iIndex, 1 );
  295. CUtlVector< uint32 >* pVecPlayers = iTeam == TF_TEAM_RED ? &m_vecRedPlayers : &m_vecBluePlayers;
  296. pVecPlayers->AddToTail( unAccountID );
  297. return;
  298. }
  299. }
  300. }
  301. }
  302. // free up the slot if we're not preserving it
  303. m_iConnectionState.Set( iIndex, MM_DISCONNECTED );
  304. m_vecFreeSlots.AddToTail( iIndex );
  305. }
  306. //-----------------------------------------------------------------------------
  307. // Purpose:
  308. //-----------------------------------------------------------------------------
  309. void CTFPlayerResource::Spawn( void )
  310. {
  311. BaseClass::Spawn();
  312. }
  313. //-----------------------------------------------------------------------------
  314. // Purpose:
  315. //-----------------------------------------------------------------------------
  316. void CTFPlayerResource::Init( int iIndex )
  317. {
  318. BaseClass::Init( iIndex );
  319. m_iTotalScore.Set( iIndex, 0 );
  320. m_iMaxHealth.Set( iIndex, TF_HEALTH_UNDEFINED );
  321. m_iMaxBuffedHealth.Set( iIndex, TF_HEALTH_UNDEFINED );
  322. m_iPlayerClass.Set( iIndex, TF_CLASS_UNDEFINED );
  323. m_iActiveDominations.Set( iIndex, 0 );
  324. m_iPlayerClassWhenKilled.Set( iIndex, TF_CLASS_UNDEFINED );
  325. m_iConnectionState.Set( iIndex, MM_DISCONNECTED );
  326. m_bValid.Set( iIndex, 0 );
  327. }
  328. //-----------------------------------------------------------------------------
  329. // Purpose: Gets a value from an array member
  330. //-----------------------------------------------------------------------------
  331. int CTFPlayerResource::GetTotalScore( int iIndex )
  332. {
  333. Assert( iIndex >= 0 && iIndex <= MAX_PLAYERS );
  334. CTFPlayer *pPlayer = (CTFPlayer*)UTIL_PlayerByIndex( iIndex );
  335. if ( pPlayer && pPlayer->IsConnected() )
  336. {
  337. return m_iTotalScore[iIndex];
  338. }
  339. return 0;
  340. }
  341. //-----------------------------------------------------------------------------
  342. // Purpose:
  343. //-----------------------------------------------------------------------------
  344. void CTFPlayerResource::SetPlayerClassWhenKilled( int iIndex, int iClass )
  345. {
  346. Assert( iIndex >= 0 && iIndex <= MAX_PLAYERS );
  347. m_iPlayerClassWhenKilled.Set( iIndex, iClass );
  348. }