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.

683 lines
22 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //=============================================================================
  6. #include "cbase.h"
  7. #include "portal_gamestats.h"
  8. #include "tier1/utlbuffer.h"
  9. #include "portal_player.h"
  10. #define PORTALSTATS_TRIMEVENT( varName, varType )\
  11. if( varName->Count() > varType::TRIMSIZE )\
  12. varName->RemoveMultiple( 0, (varName->Count() - varType::TRIMSIZE) );
  13. #define PORTALSTATS_PREPCHUNK( structType, bufName, SizePositionVarNameToUse )\
  14. SaveBuffer.PutUnsignedShort( structType::CHUNKID );\
  15. int SizePositionVarNameToUse = bufName.TellPut();\
  16. bufName.PutUnsignedInt( 0 );
  17. #define PORTALSTATS_WRITECHUNKSIZE( bufName, SizePositionVariable ) \
  18. {\
  19. int SizePositionVariable ## _askdjbhas = bufName.TellPut() - SizePositionVariable;\
  20. bufName.SeekPut( CUtlBuffer::SEEK_HEAD, SizePositionVariable );\
  21. bufName.PutUnsignedInt( SizePositionVariable ## _askdjbhas );\
  22. bufName.SeekPut( CUtlBuffer::SEEK_TAIL, 0 );\
  23. }
  24. static Portal_Gamestats_LevelStats_t s_DummyStats;
  25. CPortalGameStats g_PortalGameStats;
  26. class CPortalGameStatsSingleton //used to remove the constructor destructor from the general class
  27. {
  28. public:
  29. CPortalGameStatsSingleton( void )
  30. {
  31. gamestats = (CBaseGameStats *)&g_PortalGameStats;
  32. CreateLevelStatPointers( &s_DummyStats );
  33. }
  34. ~CPortalGameStatsSingleton( void )
  35. {
  36. AssertMsg( (CBaseGameStats::StatTrackingAllowed() == false) ||
  37. ((s_DummyStats.m_pDeaths->Count() == 0) &&
  38. (s_DummyStats.m_pPlacements->Count() == 0) &&
  39. (s_DummyStats.m_pUseEvents->Count() == 0) &&
  40. (s_DummyStats.m_pStuckSpots->Count() == 0) &&
  41. (s_DummyStats.m_pJumps->Count() == 0) &&
  42. (s_DummyStats.m_pTimeSpentInVisLeafs->Count() == 0)),
  43. "Some stats were deferred to the dummy entry." );
  44. DestroyLevelStatPointers( &s_DummyStats );
  45. }
  46. };
  47. CPortalGameStatsSingleton s_CPGSS_ThisJustSitsInMemory;
  48. CPortalGameStats::CPortalGameStats( void )
  49. : m_pCurrentMapStats( &s_DummyStats )
  50. {
  51. }
  52. CPortalGameStats::~CPortalGameStats( void )
  53. {
  54. Clear();
  55. }
  56. void Portal_Gamestats_LevelStats_t::AppendSubChunksToBuffer( CUtlBuffer &SaveBuffer )
  57. {
  58. if( m_pDeaths->Count() != 0 )
  59. {
  60. PORTALSTATS_TRIMEVENT( m_pDeaths, PlayerDeaths_t );
  61. PORTALSTATS_PREPCHUNK( PlayerDeaths_t, SaveBuffer, iSubChunkSizePosition );
  62. int iDeathStatsCount = m_pDeaths->Count();
  63. SaveBuffer.PutUnsignedInt( iDeathStatsCount );
  64. for( int i = 0; i != iDeathStatsCount; ++i )
  65. {
  66. PlayerDeaths_t &DeathStat = m_pDeaths->Element( i );
  67. SaveBuffer.PutFloat( DeathStat.ptPositionOfDeath.x );
  68. SaveBuffer.PutFloat( DeathStat.ptPositionOfDeath.y );
  69. SaveBuffer.PutFloat( DeathStat.ptPositionOfDeath.z );
  70. SaveBuffer.PutInt( DeathStat.iDamageType );
  71. SaveBuffer.PutString( DeathStat.szAttackerClassName );
  72. }
  73. PORTALSTATS_WRITECHUNKSIZE( SaveBuffer, iSubChunkSizePosition );
  74. }
  75. if( m_pPlacements->Count() != 0 )
  76. {
  77. PORTALSTATS_TRIMEVENT( m_pPlacements, PortalPlacement_t );
  78. PORTALSTATS_PREPCHUNK( PortalPlacement_t, SaveBuffer, iSubChunkSizePosition );
  79. int iPlacementStatsCount = m_pPlacements->Count();
  80. SaveBuffer.PutUnsignedInt( iPlacementStatsCount );
  81. for( int i = 0; i != iPlacementStatsCount; ++i )
  82. {
  83. PortalPlacement_t &PortalPlacementStat = m_pPlacements->Element( i );
  84. SaveBuffer.PutFloat( PortalPlacementStat.ptPlayerFiredFrom.x );
  85. SaveBuffer.PutFloat( PortalPlacementStat.ptPlayerFiredFrom.y );
  86. SaveBuffer.PutFloat( PortalPlacementStat.ptPlayerFiredFrom.z );
  87. SaveBuffer.PutFloat( PortalPlacementStat.ptPlacementPosition.x );
  88. SaveBuffer.PutFloat( PortalPlacementStat.ptPlacementPosition.y );
  89. SaveBuffer.PutFloat( PortalPlacementStat.ptPlacementPosition.z );
  90. SaveBuffer.PutChar( PortalPlacementStat.iSuccessCode );
  91. }
  92. PORTALSTATS_WRITECHUNKSIZE( SaveBuffer, iSubChunkSizePosition );
  93. }
  94. if( m_pUseEvents->Count() != 0 )
  95. {
  96. PORTALSTATS_TRIMEVENT( m_pUseEvents, PlayerUse_t );
  97. PORTALSTATS_PREPCHUNK( PlayerUse_t, SaveBuffer, iSubChunkSizePosition );
  98. int iUseEventCount = m_pUseEvents->Count();
  99. SaveBuffer.PutUnsignedInt( iUseEventCount );
  100. for( int i = 0; i != iUseEventCount; ++i )
  101. {
  102. PlayerUse_t &UseEvent = m_pUseEvents->Element( i );
  103. SaveBuffer.PutFloat( UseEvent.ptTraceStart.x );
  104. SaveBuffer.PutFloat( UseEvent.ptTraceStart.y );
  105. SaveBuffer.PutFloat( UseEvent.ptTraceStart.z );
  106. SaveBuffer.PutFloat( UseEvent.vTraceDelta.x );
  107. SaveBuffer.PutFloat( UseEvent.vTraceDelta.y );
  108. SaveBuffer.PutFloat( UseEvent.vTraceDelta.z );
  109. SaveBuffer.PutString( UseEvent.szUseEntityClassName );
  110. }
  111. PORTALSTATS_WRITECHUNKSIZE( SaveBuffer, iSubChunkSizePosition );
  112. }
  113. if( m_pStuckSpots->Count() != 0 )
  114. {
  115. PORTALSTATS_TRIMEVENT( m_pStuckSpots, StuckEvent_t );
  116. PORTALSTATS_PREPCHUNK( StuckEvent_t, SaveBuffer, iSubChunkSizePosition );
  117. int iStuckStatsCount = m_pStuckSpots->Count();
  118. SaveBuffer.PutUnsignedInt( iStuckStatsCount );
  119. for( int i = 0; i != iStuckStatsCount; ++i )
  120. {
  121. StuckEvent_t &StuckStat = m_pStuckSpots->Element( i );
  122. SaveBuffer.PutFloat( StuckStat.ptPlayerPosition.x );
  123. SaveBuffer.PutFloat( StuckStat.ptPlayerPosition.y );
  124. SaveBuffer.PutFloat( StuckStat.ptPlayerPosition.z );
  125. SaveBuffer.PutFloat( StuckStat.qPlayerAngles.x );
  126. SaveBuffer.PutFloat( StuckStat.qPlayerAngles.y );
  127. SaveBuffer.PutFloat( StuckStat.qPlayerAngles.z );
  128. unsigned char bitFlags = 0;
  129. if( StuckStat.bNearPortal )
  130. bitFlags |= (1 << 0);
  131. if( StuckStat.bDucking )
  132. bitFlags |= (1 << 1);
  133. SaveBuffer.PutUnsignedChar( bitFlags );
  134. }
  135. PORTALSTATS_WRITECHUNKSIZE( SaveBuffer, iSubChunkSizePosition );
  136. }
  137. if( m_pJumps->Count() != 0 )
  138. {
  139. PORTALSTATS_TRIMEVENT( m_pJumps, JumpEvent_t );
  140. PORTALSTATS_PREPCHUNK( JumpEvent_t, SaveBuffer, iSubChunkSizePosition );
  141. int iJumpStatsCount = m_pJumps->Count();
  142. SaveBuffer.PutUnsignedInt( iJumpStatsCount );
  143. for( int i = 0; i != iJumpStatsCount; ++i )
  144. {
  145. JumpEvent_t &JumpStat = m_pJumps->Element( i );
  146. SaveBuffer.PutFloat( JumpStat.ptPlayerPositionAtJumpStart.x );
  147. SaveBuffer.PutFloat( JumpStat.ptPlayerPositionAtJumpStart.y );
  148. SaveBuffer.PutFloat( JumpStat.ptPlayerPositionAtJumpStart.z );
  149. SaveBuffer.PutFloat( JumpStat.vPlayerVelocityAtJumpStart.x );
  150. SaveBuffer.PutFloat( JumpStat.vPlayerVelocityAtJumpStart.y );
  151. SaveBuffer.PutFloat( JumpStat.vPlayerVelocityAtJumpStart.z );
  152. }
  153. PORTALSTATS_WRITECHUNKSIZE( SaveBuffer, iSubChunkSizePosition );
  154. }
  155. if( m_pTimeSpentInVisLeafs->Count() != 0 )
  156. {
  157. PORTALSTATS_TRIMEVENT( m_pTimeSpentInVisLeafs, LeafTimes_t );
  158. PORTALSTATS_PREPCHUNK( LeafTimes_t, SaveBuffer, iSubChunkSizePosition );
  159. int iLeafTimeStatsCount = m_pTimeSpentInVisLeafs->Count();
  160. SaveBuffer.PutUnsignedInt( iLeafTimeStatsCount );
  161. for( int i = 0; i != iLeafTimeStatsCount; ++i )
  162. {
  163. LeafTimes_t &LeafTimeStat = m_pTimeSpentInVisLeafs->Element( i );
  164. SaveBuffer.PutFloat( LeafTimeStat.fTimeSpentInVisLeaf ); //assumes visleafs will be the same when this data is loaded again, or that there will be a way to invalidate the data
  165. }
  166. PORTALSTATS_WRITECHUNKSIZE( SaveBuffer, iSubChunkSizePosition );
  167. }
  168. }
  169. void Portal_Gamestats_LevelStats_t::LoadSubChunksFromBuffer( CUtlBuffer &LoadBuffer, unsigned int iChunkEndPosition )
  170. {
  171. Clear();
  172. while( ((unsigned int)LoadBuffer.TellGet()) != iChunkEndPosition )
  173. {
  174. Assert( (iChunkEndPosition - LoadBuffer.TellGet()) > (sizeof( unsigned short ) + sizeof( unsigned int )) ); //at least an empty chunk left
  175. unsigned short iChunkID = LoadBuffer.GetUnsignedShort();
  176. unsigned int iChunkSize = LoadBuffer.GetUnsignedInt() - sizeof( unsigned int ); //chunk size includes the chunk size data itself
  177. #ifdef _DEBUG
  178. unsigned int iChunkEndPosition = LoadBuffer.TellGet() + iChunkSize; //used in an assert later
  179. #endif
  180. switch( iChunkID )
  181. {
  182. #ifdef PORTAL_GAMESTATS_VERBOSE //don't bother loading verbose chunks if we're not going to save them back out
  183. case PlayerDeaths_t::CHUNKID:
  184. {
  185. unsigned int iDeathStatCount = LoadBuffer.GetUnsignedInt();
  186. for( unsigned int i = 0; i != iDeathStatCount; ++i )
  187. {
  188. int index = m_pDeaths->AddToTail();
  189. PlayerDeaths_t &DeathStat = m_pDeaths->Element( index );
  190. DeathStat.ptPositionOfDeath.x = LoadBuffer.GetFloat();
  191. DeathStat.ptPositionOfDeath.y = LoadBuffer.GetFloat();
  192. DeathStat.ptPositionOfDeath.z = LoadBuffer.GetFloat();
  193. DeathStat.iDamageType = LoadBuffer.GetInt();
  194. LoadBuffer.GetString( DeathStat.szAttackerClassName );
  195. }
  196. break;
  197. }
  198. case PortalPlacement_t::CHUNKID:
  199. {
  200. unsigned int iPlacementStatCount = LoadBuffer.GetUnsignedInt();
  201. for( unsigned int i = 0; i != iPlacementStatCount; ++i )
  202. {
  203. int index = m_pPlacements->AddToTail();
  204. PortalPlacement_t &PlacementStat = m_pPlacements->Element( index );
  205. PlacementStat.ptPlayerFiredFrom.x = LoadBuffer.GetFloat();
  206. PlacementStat.ptPlayerFiredFrom.y = LoadBuffer.GetFloat();
  207. PlacementStat.ptPlayerFiredFrom.z = LoadBuffer.GetFloat();
  208. PlacementStat.ptPlacementPosition.x = LoadBuffer.GetFloat();
  209. PlacementStat.ptPlacementPosition.y = LoadBuffer.GetFloat();
  210. PlacementStat.ptPlacementPosition.z = LoadBuffer.GetFloat();
  211. PlacementStat.iSuccessCode = LoadBuffer.GetChar();
  212. }
  213. break;
  214. }
  215. case PlayerUse_t::CHUNKID:
  216. {
  217. int iUseEventCount = LoadBuffer.GetUnsignedInt();
  218. for( int i = 0; i != iUseEventCount; ++i )
  219. {
  220. int index = m_pUseEvents->AddToTail();
  221. PlayerUse_t &UseEvent = m_pUseEvents->Element( index );
  222. UseEvent.ptTraceStart.x = LoadBuffer.GetFloat();
  223. UseEvent.ptTraceStart.y = LoadBuffer.GetFloat();
  224. UseEvent.ptTraceStart.z = LoadBuffer.GetFloat();
  225. UseEvent.vTraceDelta.x = LoadBuffer.GetFloat();
  226. UseEvent.vTraceDelta.y = LoadBuffer.GetFloat();
  227. UseEvent.vTraceDelta.z = LoadBuffer.GetFloat();
  228. LoadBuffer.GetString( UseEvent.szUseEntityClassName );
  229. }
  230. break;
  231. }
  232. case StuckEvent_t::CHUNKID:
  233. {
  234. unsigned int iStuckEventCount = LoadBuffer.GetUnsignedInt();
  235. for( unsigned int i = 0; i != iStuckEventCount; ++i )
  236. {
  237. int index = m_pStuckSpots->AddToTail();
  238. StuckEvent_t &StuckEvent = m_pStuckSpots->Element( index );
  239. StuckEvent.ptPlayerPosition.x = LoadBuffer.GetFloat();
  240. StuckEvent.ptPlayerPosition.y = LoadBuffer.GetFloat();
  241. StuckEvent.ptPlayerPosition.z = LoadBuffer.GetFloat();
  242. StuckEvent.qPlayerAngles.x = LoadBuffer.GetFloat();
  243. StuckEvent.qPlayerAngles.y = LoadBuffer.GetFloat();
  244. StuckEvent.qPlayerAngles.z = LoadBuffer.GetFloat();
  245. unsigned char bitFlags = LoadBuffer.GetUnsignedChar();
  246. StuckEvent.bNearPortal = ( (bitFlags & (1 << 0)) != 0 );
  247. StuckEvent.bDucking = ( (bitFlags & (1 << 1)) != 0 );
  248. }
  249. break;
  250. }
  251. case JumpEvent_t::CHUNKID:
  252. {
  253. unsigned int iJumpEventCount = LoadBuffer.GetUnsignedInt();
  254. for( unsigned int i = 0; i != iJumpEventCount; ++i )
  255. {
  256. int index = m_pJumps->AddToTail();
  257. JumpEvent_t &JumpEvent = m_pJumps->Element( index );
  258. JumpEvent.ptPlayerPositionAtJumpStart.x = LoadBuffer.GetFloat();
  259. JumpEvent.ptPlayerPositionAtJumpStart.y = LoadBuffer.GetFloat();
  260. JumpEvent.ptPlayerPositionAtJumpStart.z = LoadBuffer.GetFloat();
  261. JumpEvent.vPlayerVelocityAtJumpStart.x = LoadBuffer.GetFloat();
  262. JumpEvent.vPlayerVelocityAtJumpStart.y = LoadBuffer.GetFloat();
  263. JumpEvent.vPlayerVelocityAtJumpStart.z = LoadBuffer.GetFloat();
  264. }
  265. break;
  266. }
  267. case LeafTimes_t::CHUNKID:
  268. {
  269. //IMPORTANT TODO
  270. //TODO: Detect if the leaves have changed and invalidate these counts
  271. unsigned int iLeafCount = LoadBuffer.GetUnsignedInt();
  272. for( unsigned int i = 0; i != iLeafCount; ++i )
  273. {
  274. int index = m_pTimeSpentInVisLeafs->AddToTail();
  275. LeafTimes_t &LeafTime = m_pTimeSpentInVisLeafs->Element( index );
  276. LeafTime.fTimeSpentInVisLeaf = LoadBuffer.GetFloat();
  277. }
  278. break;
  279. }
  280. #else
  281. case PlayerDeaths_t::CHUNKID: //warning workaround
  282. #endif
  283. default:
  284. {
  285. //an unknown chunk, skip it
  286. LoadBuffer.SeekGet( CUtlBuffer::SEEK_CURRENT, iChunkSize );
  287. }
  288. };
  289. Assert( ((unsigned int)LoadBuffer.TellGet()) == iChunkEndPosition );
  290. };
  291. }
  292. void Portal_Gamestats_LevelStats_t::Clear( void )
  293. {
  294. m_pDeaths->RemoveAll();
  295. m_pPlacements->RemoveAll();
  296. m_pStuckSpots->RemoveAll();
  297. m_pJumps->RemoveAll();
  298. m_pTimeSpentInVisLeafs->RemoveAll();
  299. }
  300. void CPortalGameStats::AppendCustomDataToSaveBuffer( CUtlBuffer &SaveBuffer )
  301. {
  302. #ifdef PORTAL_GAMESTATS_VERBOSE //we only have verbose chunks for now, so only write custom data if we're verbosely tracking
  303. SaveBuffer.PutUnsignedShort( PORTAL_GAMESTATS_VERSION );
  304. //no headers allowed, chunks were chosen for their flexibility in loading even when parts of the data are unknown
  305. //you can simulate a header by enclosing the entirety of the custom data in a chunk that starts with a header, but you'll kill loading in old versions
  306. if( m_CustomMapStats.Count() != 0 ) //we have some map stats
  307. {
  308. //put out a map chunk for each map
  309. for ( int i = m_CustomMapStats.First(); i != m_CustomMapStats.InvalidIndex(); i = m_CustomMapStats.Next( i ) )
  310. {
  311. SaveBuffer.PutShort( Portal_Gamestats_LevelStats_t::CHUNKID );
  312. //we can trivially find the chunk size after the chunk is written, but chunk size needs to be at the beginning, reserve the space for chunk size now
  313. int iChunkSizePosition = SaveBuffer.TellPut();
  314. SaveBuffer.PutUnsignedInt( 0 );
  315. char const *szMapName = m_CustomMapStats.GetElementName( i );
  316. Portal_Gamestats_LevelStats_t &mapStats = m_CustomMapStats[ i ];
  317. SaveBuffer.PutString( szMapName );
  318. mapStats.AppendSubChunksToBuffer( SaveBuffer );
  319. //write out the map stats chunk size
  320. {
  321. int iChunkSize = SaveBuffer.TellPut() - iChunkSizePosition;
  322. Assert( iChunkSize >= sizeof( int ) ); //minimum sizeof( int )
  323. #ifdef _DEBUG
  324. int iOldTellPut = SaveBuffer.TellPut(); //needed for an assert below
  325. #endif
  326. SaveBuffer.SeekPut( CUtlBuffer::SEEK_HEAD, iChunkSizePosition );
  327. SaveBuffer.PutUnsignedInt( iChunkSize );
  328. SaveBuffer.SeekPut( CUtlBuffer::SEEK_TAIL, 0 );
  329. Assert( iOldTellPut == SaveBuffer.TellPut() ); //writing the chunk size should have overwritten, not inserted
  330. }
  331. }
  332. }
  333. #endif //#ifdef PORTAL_GAMESTATS_VERBOSE
  334. }
  335. void CPortalGameStats::LoadCustomDataFromBuffer( CUtlBuffer &LoadBuffer )
  336. {
  337. #ifdef _DEBUG
  338. unsigned short iSaveStatsVersion = LoadBuffer.GetUnsignedShort();
  339. AssertOnce( iSaveStatsVersion <= PORTAL_GAMESTATS_VERSION ); //useful to know, but shouldn't be a failure case
  340. #else
  341. LoadBuffer.GetUnsignedShort(); //don't really need the version
  342. #endif
  343. int iEndPosition = LoadBuffer.TellPut();
  344. while( LoadBuffer.TellGet() != iEndPosition )
  345. {
  346. Assert( (iEndPosition - LoadBuffer.TellGet()) > (sizeof( unsigned short ) + sizeof( unsigned int )) ); //at least an empty chunk left
  347. unsigned short iChunkID = LoadBuffer.GetUnsignedShort();
  348. unsigned int iChunkSize = LoadBuffer.GetUnsignedInt() - sizeof( unsigned int ); //chunk size includes the chunk size data itself
  349. #ifdef PORTAL_GAMESTATS_VERBOSE
  350. unsigned int iChunkEndPosition = LoadBuffer.TellGet() + iChunkSize; //used in an assert later
  351. #endif
  352. switch( iChunkID )
  353. {
  354. #ifdef PORTAL_GAMESTATS_VERBOSE //levelstats only have verbose data for the time being, so only bother to load verboseness if we're still tracking it
  355. case Portal_Gamestats_LevelStats_t::CHUNKID:
  356. {
  357. //map chunk
  358. char szMapName[256];
  359. LoadBuffer.GetString( szMapName );
  360. Portal_Gamestats_LevelStats_t *mapStats = FindOrAddMapStats( szMapName );
  361. mapStats->LoadSubChunksFromBuffer( LoadBuffer, iChunkEndPosition );
  362. break;
  363. }
  364. #else
  365. case Portal_Gamestats_LevelStats_t::CHUNKID: //warning workaround
  366. #endif
  367. default:
  368. {
  369. //an unknown chunk, skip it
  370. LoadBuffer.SeekGet( CUtlBuffer::SEEK_CURRENT, iChunkSize );
  371. }
  372. };
  373. #ifdef PORTAL_GAMESTATS_VERBOSE
  374. Assert( ((unsigned int)LoadBuffer.TellGet()) == iChunkEndPosition );
  375. #endif
  376. }
  377. }
  378. void CPortalGameStats::Event_PlayerKilled( CBasePlayer *pPlayer, const CTakeDamageInfo &info )
  379. {
  380. #ifdef PORTAL_GAMESTATS_VERBOSE
  381. if( CBaseGameStats::StatTrackingAllowed() == false )
  382. return;
  383. int index = m_pCurrentMapStats->m_pDeaths->AddToTail();
  384. Portal_Gamestats_LevelStats_t::PlayerDeaths_t &DeathStat = m_pCurrentMapStats->m_pDeaths->Element( index );
  385. DeathStat.ptPositionOfDeath = pPlayer->GetAbsOrigin();
  386. DeathStat.iDamageType = info.GetDamageType();
  387. DeathStat.szAttackerClassName[0] = '\0';
  388. CBaseEntity *pInflictor = info.GetInflictor();
  389. if( pInflictor )
  390. Q_strncpy( DeathStat.szAttackerClassName, pInflictor->GetClassname(), sizeof( DeathStat.szAttackerClassName ) );
  391. #endif
  392. }
  393. void CPortalGameStats::Event_PlayerJump( const Vector &ptStartPosition, const Vector &vStartVelocity )
  394. {
  395. #ifdef PORTAL_GAMESTATS_VERBOSE
  396. if( CBaseGameStats::StatTrackingAllowed() == false )
  397. return;
  398. int index = m_pCurrentMapStats->m_pJumps->AddToTail();
  399. Portal_Gamestats_LevelStats_t::JumpEvent_t &JumpStat = m_pCurrentMapStats->m_pJumps->Element( index );
  400. JumpStat.ptPlayerPositionAtJumpStart = ptStartPosition;
  401. JumpStat.vPlayerVelocityAtJumpStart = vStartVelocity;
  402. #endif
  403. }
  404. void CPortalGameStats::Event_PortalPlacement( const Vector &ptPlayerFiredFrom, const Vector &ptAttemptedPosition, char iSuccessCode )
  405. {
  406. #ifdef PORTAL_GAMESTATS_VERBOSE
  407. if( CBaseGameStats::StatTrackingAllowed() == false )
  408. return;
  409. int index = m_pCurrentMapStats->m_pPlacements->AddToTail();
  410. Portal_Gamestats_LevelStats_t::PortalPlacement_t &PlacementStat = m_pCurrentMapStats->m_pPlacements->Element( index );
  411. PlacementStat.ptPlacementPosition = ptAttemptedPosition;
  412. PlacementStat.ptPlayerFiredFrom = ptPlayerFiredFrom;
  413. PlacementStat.iSuccessCode = iSuccessCode;
  414. #endif
  415. }
  416. void CPortalGameStats::Event_PlayerUsed( const Vector &ptTraceStart, const Vector &vTraceDelta, CBaseEntity *pUsedEntity )
  417. {
  418. #ifdef PORTAL_GAMESTATS_VERBOSE
  419. if( CBaseGameStats::StatTrackingAllowed() == false )
  420. return;
  421. static float fLastUseTime = 0.0f;
  422. if( fLastUseTime > gpGlobals->curtime ) //I'm not positive, but I think curtime resets between levels, cheap to do this
  423. fLastUseTime = 0.0f;
  424. if( (gpGlobals->curtime - fLastUseTime) < 0.25f ) //use events cluster
  425. return;
  426. fLastUseTime = gpGlobals->curtime;
  427. int index = m_pCurrentMapStats->m_pUseEvents->AddToTail();
  428. Portal_Gamestats_LevelStats_t::PlayerUse_t &UseEvent = m_pCurrentMapStats->m_pUseEvents->Element( index );
  429. UseEvent.ptTraceStart = ptTraceStart;
  430. UseEvent.vTraceDelta = vTraceDelta;
  431. UseEvent.szUseEntityClassName[0] = '\0';
  432. if( pUsedEntity )
  433. Q_strncpy( UseEvent.szUseEntityClassName, pUsedEntity->GetClassname(), sizeof( UseEvent.szUseEntityClassName ) );
  434. #endif
  435. }
  436. void CPortalGameStats::Event_PlayerStuck( CPortal_Player *pPlayer )
  437. {
  438. #ifdef PORTAL_GAMESTATS_VERBOSE
  439. if( CBaseGameStats::StatTrackingAllowed() == false )
  440. return;
  441. static float fLastStuckTime = 0.0f;
  442. if( fLastStuckTime > gpGlobals->curtime ) //I'm not positive, but I think curtime resets between levels, cheap to do this
  443. fLastStuckTime = 0.0f;
  444. if( (gpGlobals->curtime - fLastStuckTime) < 10.0f ) //only log one stuck spot per 10 second interval (in case it oscillates)
  445. return;
  446. fLastStuckTime = gpGlobals->curtime;
  447. int index = m_pCurrentMapStats->m_pStuckSpots->AddToTail();
  448. Portal_Gamestats_LevelStats_t::StuckEvent_t &StuckSpot = m_pCurrentMapStats->m_pStuckSpots->Element( index );
  449. StuckSpot.ptPlayerPosition = pPlayer->GetAbsOrigin();
  450. StuckSpot.qPlayerAngles = pPlayer->GetAbsAngles();
  451. StuckSpot.bNearPortal = (pPlayer->m_hPortalEnvironment.Get() != NULL);
  452. StuckSpot.bDucking = ((pPlayer->m_nButtons & IN_DUCK) != 0);
  453. #endif
  454. }
  455. void CPortalGameStats::Event_LevelInit( void )
  456. {
  457. BaseClass::Event_LevelInit();
  458. m_pCurrentMapStats = FindOrAddMapStats( STRING( gpGlobals->mapname ) );
  459. }
  460. void CPortalGameStats::Event_MapChange( const char *szOldMapName, const char *szNewMapName )
  461. {
  462. BaseClass::Event_MapChange( szOldMapName, szNewMapName );
  463. m_pCurrentMapStats = FindOrAddMapStats( szNewMapName );
  464. }
  465. void CPortalGameStats::Clear( void )
  466. {
  467. for( int i = m_CustomMapStats.First(); i != m_CustomMapStats.InvalidIndex(); i = m_CustomMapStats.Next( i ) )
  468. {
  469. DestroyLevelStatPointers( &m_CustomMapStats[i] );
  470. }
  471. m_CustomMapStats.RemoveAll();
  472. }
  473. Portal_Gamestats_LevelStats_t *CPortalGameStats::FindOrAddMapStats( const char *szMapName )
  474. {
  475. int idx = m_CustomMapStats.Find( szMapName );
  476. if( idx == m_CustomMapStats.InvalidIndex() )
  477. {
  478. idx = m_CustomMapStats.Insert( szMapName );
  479. CreateLevelStatPointers( &m_CustomMapStats[idx] );
  480. }
  481. return &m_CustomMapStats[ idx ];
  482. }
  483. void CreateLevelStatPointers( Portal_Gamestats_LevelStats_t *pFillIn )
  484. {
  485. pFillIn->m_pDeaths = new CUtlVector<Portal_Gamestats_LevelStats_t::PlayerDeaths_t>;
  486. pFillIn->m_pPlacements = new CUtlVector<Portal_Gamestats_LevelStats_t::PortalPlacement_t>;
  487. pFillIn->m_pUseEvents = new CUtlVector<Portal_Gamestats_LevelStats_t::PlayerUse_t>;
  488. pFillIn->m_pStuckSpots = new CUtlVector<Portal_Gamestats_LevelStats_t::StuckEvent_t>;
  489. pFillIn->m_pJumps = new CUtlVector<Portal_Gamestats_LevelStats_t::JumpEvent_t>;
  490. pFillIn->m_pTimeSpentInVisLeafs = new CUtlVector<Portal_Gamestats_LevelStats_t::LeafTimes_t>;
  491. }
  492. void DestroyLevelStatPointers( Portal_Gamestats_LevelStats_t *pDestroyFrom )
  493. {
  494. delete pDestroyFrom->m_pDeaths;
  495. delete pDestroyFrom->m_pPlacements;
  496. delete pDestroyFrom->m_pUseEvents;
  497. delete pDestroyFrom->m_pStuckSpots;
  498. delete pDestroyFrom->m_pJumps;
  499. delete pDestroyFrom->m_pTimeSpentInVisLeafs;
  500. }
  501. static char const *portalMaps[] =
  502. {
  503. "testchmb_a_00",
  504. "testchmb_a_01",
  505. "testchmb_a_02",
  506. "testchmb_a_03",
  507. "testchmb_a_04",
  508. "testchmb_a_05",
  509. "testchmb_a_06",
  510. "testchmb_a_07",
  511. "testchmb_a_08",
  512. "testchmb_a_09",
  513. "testchmb_a_10",
  514. "testchmb_a_11",
  515. //"testchmb_a_12", //12 got deleted/skipped
  516. "testchmb_a_13",
  517. "testchmb_a_14",
  518. "testchmb_a_15",
  519. "escape_00",
  520. "escape_01",
  521. "escape_02"
  522. };
  523. bool CPortalGameStats::UserPlayedAllTheMaps( void )
  524. {
  525. int c = ARRAYSIZE( portalMaps );
  526. for ( int i = 0; i < c; ++i )
  527. {
  528. int idx = m_BasicStats.m_MapTotals.Find( portalMaps[ i ] );
  529. if( idx == m_BasicStats.m_MapTotals.InvalidIndex() )
  530. return false;
  531. }
  532. return true;
  533. }