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.

722 lines
15 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. // Parse Halloween 2011 server logs and mine Eyeball Boss (Monoculus) data
  3. #define _CRT_SECURE_NO_WARNINGS // STFU
  4. #include <tchar.h>
  5. #include "stdio.h"
  6. #include <math.h>
  7. #include <vector>
  8. #define MAX_BUFFER_SIZE 256
  9. #define MAX_NAME_SIZE 64
  10. enum EventType
  11. {
  12. EYEBALL_SPAWN, // eyeball_spawn (max_health 11200) (player_count 18)
  13. EYEBALL_ESCAPE, // eyeball_escaped (max_dps 220.00) (health 3071)
  14. EYEBALL_DEATH, // eyeball_death (max_dps 467.63) (max_health 12000) (player_count 23)
  15. EYEBALL_STUN, // "Aque0us<174><STEAM_0:0:212532><Red>" eyeball_stunned with "NoWeapon" (attacker_position "-1033 872 143")
  16. PURGATORY_ENTER, // "I_DONT_KNOW<181><STEAM_0:1:34210475><Red>" purgatory_teleport "spawn_purgatory"
  17. PURGATORY_ESCAPE, // "I_DONT_KNOW<181><STEAM_0:1:34210475><Red>" purgatory_escaped
  18. LOOT_ISLAND_ENTER, // "I_DONT_KNOW<181><STEAM_0:1:34210475><Red>" purgatory_teleport "spawn_loot"
  19. EYEBALL_KILLER, // "Spaghetti Western<192><STEAM_0:1:41812531><Red>" eyeball_killer with "liberty_launcher" (attacker_position "-1629 -293 213")
  20. EVENT_COUNT
  21. };
  22. #define TEAM_NONE 0
  23. #define TEAM_RED 1
  24. #define TEAM_BLUE 2
  25. struct EventInfo
  26. {
  27. EventType m_type;
  28. char m_date[ MAX_NAME_SIZE ];
  29. int m_timestamp; // seconds since log start
  30. int m_health;
  31. int m_maxHealth;
  32. int m_playerCount;
  33. int m_maxDPS;
  34. int m_attackerPosX;
  35. int m_attackerPosY;
  36. int m_attackerPosZ;
  37. char m_weaponName[ MAX_NAME_SIZE ];
  38. char m_playerName[ MAX_NAME_SIZE ];
  39. int m_playerID;
  40. int m_team;
  41. };
  42. std::vector< EventInfo > TheData;
  43. //----------------------------------------------------------------------------------
  44. bool ParseEvent( FILE *fp, EventInfo *event )
  45. {
  46. return false;
  47. }
  48. //----------------------------------------------------------------------------------
  49. char *ParsePlayer( EventInfo *event, char *data )
  50. {
  51. // "TheSauce<17><STEAM_0:1:19333174><Red>"
  52. // ARGH! Player names can contain '<' and '>'
  53. // skip the "
  54. ++data;
  55. char *restOfData = NULL;
  56. char *c = &data[ strlen(data)-1 ];
  57. // backup until we find '>'
  58. while( c != data )
  59. {
  60. --c;
  61. if ( *c == '>' )
  62. {
  63. restOfData = c+2;
  64. // terminate the team name
  65. *c = '\000';
  66. break;
  67. }
  68. }
  69. // backup until we find '<'
  70. char *teamName = NULL;
  71. while( c != data )
  72. {
  73. --c;
  74. if ( *c == '<' )
  75. {
  76. teamName = c+1;
  77. // back up and terminate the Steam ID
  78. --c;
  79. *c = '\000';
  80. break;
  81. }
  82. }
  83. if ( !teamName )
  84. {
  85. return NULL;
  86. }
  87. if ( !strcmp( "Red", teamName ) )
  88. {
  89. event->m_team = TEAM_RED;
  90. }
  91. else if ( !strcmp( "Blue", teamName ) )
  92. {
  93. event->m_team = TEAM_BLUE;
  94. }
  95. else
  96. {
  97. return NULL;
  98. }
  99. // "TheSauce<17><STEAM_0:1:19333174><Red>"
  100. // backup until we find '<'
  101. char *steamID = NULL;
  102. while( c != data )
  103. {
  104. --c;
  105. if ( *c == '<' )
  106. {
  107. steamID = c+1;
  108. // back up and terminate the player ID
  109. --c;
  110. *c = '\000';
  111. break;
  112. }
  113. }
  114. if ( !steamID )
  115. {
  116. return NULL;
  117. }
  118. // backup until we find '<'
  119. char *playerID = NULL;
  120. while( c != data )
  121. {
  122. --c;
  123. if ( *c == '<' )
  124. {
  125. playerID = c+1;
  126. // terminate the player name
  127. *c = '\000';
  128. break;
  129. }
  130. }
  131. if ( !playerID )
  132. {
  133. return NULL;
  134. }
  135. event->m_playerID = atoi( playerID );
  136. strcpy( event->m_playerName, data );
  137. return restOfData;
  138. #ifdef OLDWAY
  139. // skip the "
  140. ++data;
  141. char *token = strtok( data, "<" );
  142. if ( !token )
  143. return NULL;
  144. strcpy( event->m_playerName, token );
  145. token = strtok( NULL, ">" );
  146. if ( !token )
  147. return NULL;
  148. event->m_playerID = atoi( token );
  149. // steam ID
  150. token = strtok( NULL, "<>" );
  151. if ( !token )
  152. return NULL;
  153. // team
  154. token = strtok( NULL, "<>" );
  155. if ( !token )
  156. return NULL;
  157. if ( !strcmp( "Red", token ) )
  158. {
  159. event->m_team = TEAM_RED;
  160. }
  161. else if ( !strcmp( "Blue", token ) )
  162. {
  163. event->m_team = TEAM_BLUE;
  164. }
  165. // get rest of line after closing quote
  166. token = strtok( NULL, "" );
  167. token += 2;
  168. return token;
  169. #endif
  170. }
  171. //----------------------------------------------------------------------------------
  172. // Parse and return int value from "(name value)"
  173. bool parse_strtok_int( const char *name, int *value )
  174. {
  175. char *token = strtok( NULL, "( " );
  176. if ( !token )
  177. return false;
  178. if ( _stricmp( name, token ) )
  179. return false;
  180. // get value
  181. token = strtok( NULL, ") " );
  182. if ( !token )
  183. return false;
  184. *value = atoi( token );
  185. return true;
  186. }
  187. //----------------------------------------------------------------------------------
  188. bool ProcessData( const char *filename )
  189. {
  190. FILE *fp = fopen( filename, "r" );
  191. if ( !fp )
  192. {
  193. printf( "ERROR: Cannot access file '%s'\n", filename );
  194. return false;
  195. }
  196. FILE *errorFP = fopen( "cooked_stats_error.txt", "w" );
  197. if ( !errorFP )
  198. {
  199. printf( "ERROR: Can't open output file\n" );
  200. return false;
  201. }
  202. char buffer[ MAX_BUFFER_SIZE ];
  203. bool isFirstLine = true;
  204. int initialTimestamp = 0;
  205. int line = 0;
  206. EventInfo info;
  207. while( true )
  208. {
  209. memset( &info, 0, sizeof( EventInfo ) );
  210. fgets( buffer, MAX_BUFFER_SIZE, fp );
  211. ++line;
  212. // eat filename
  213. char *token = buffer;
  214. while( *token != 'L' )
  215. {
  216. ++token;
  217. }
  218. if ( !token )
  219. break;
  220. // read date
  221. token = strtok( token, "L " );
  222. if ( !token )
  223. break;
  224. strcpy( info.m_date, token );
  225. // read time
  226. token = strtok( NULL, "- " );
  227. if ( !token )
  228. break;
  229. token[2] = '\000';
  230. int hour = atoi( token );
  231. token[5] = '\000';
  232. int minute = atoi( &token[3] );
  233. token[8] = '\000';
  234. int second = atoi( &token[6] );
  235. int timestamp = hour * 3600 + minute * 60 + second;
  236. if ( isFirstLine )
  237. {
  238. isFirstLine = false;
  239. initialTimestamp = timestamp;
  240. }
  241. info.m_timestamp = timestamp - initialTimestamp;
  242. int fullDay = 24 * 3600;
  243. if ( info.m_timestamp > fullDay )
  244. {
  245. // wrapped past midnight
  246. info.m_timestamp -= fullDay;
  247. }
  248. // eat "HALLOWEEN"
  249. token = strtok( NULL, ": " );
  250. if ( !token )
  251. break;
  252. // get the rest of the line
  253. token = strtok( NULL, "" );
  254. if ( !token )
  255. break;
  256. // skip trailing space
  257. ++token;
  258. if ( !strncmp( "eyeball_spawn", token, 13 ) )
  259. {
  260. // eyeball_spawn (max_health 8000) (player_count 10)
  261. info.m_type = EYEBALL_SPAWN;
  262. // eat 'eyeball_spawn'
  263. token = strtok( token, " " );
  264. if ( !token )
  265. break;
  266. if ( !parse_strtok_int( "max_health", &info.m_maxHealth ) )
  267. break;
  268. if ( !parse_strtok_int( "player_count", &info.m_playerCount ) )
  269. break;
  270. }
  271. else if ( !strncmp( "eyeball_escaped", token, 15 ) )
  272. {
  273. // eyeball_escaped (max_dps 143.64) (health 1525)
  274. info.m_type = EYEBALL_ESCAPE;
  275. // eat 'eyeball_escape'
  276. token = strtok( token, " " );
  277. if ( !token )
  278. break;
  279. if ( !parse_strtok_int( "max_dps", &info.m_maxDPS ) )
  280. break;
  281. if ( !parse_strtok_int( "health", &info.m_health ) )
  282. break;
  283. }
  284. else if ( !strncmp( "eyeball_death", token, 13 ) )
  285. {
  286. // eyeball_death (max_dps 285.54) (max_health 13200) (player_count 24)
  287. info.m_type = EYEBALL_DEATH;
  288. // eat 'eyeball_death'
  289. token = strtok( token, " " );
  290. if ( !token )
  291. break;
  292. if ( !parse_strtok_int( "max_dps", &info.m_maxDPS ) )
  293. break;
  294. if ( !parse_strtok_int( "max_health", &info.m_maxHealth ) )
  295. break;
  296. if ( !parse_strtok_int( "player_count", &info.m_playerCount ) )
  297. break;
  298. }
  299. else if ( token[0] == '"' )
  300. {
  301. char *data = ParsePlayer( &info, token );
  302. if ( data )
  303. {
  304. token = strtok( data, " " );
  305. if ( !token )
  306. continue;
  307. if ( !strncmp( "purgatory_escaped", token, 17 ) )
  308. {
  309. info.m_type = PURGATORY_ESCAPE;
  310. }
  311. else if ( !strcmp( "purgatory_teleport", token ) )
  312. {
  313. token = strtok( NULL, "\" " );
  314. if ( !token )
  315. continue;
  316. if ( !strcmp( "spawn_purgatory", token ) )
  317. {
  318. info.m_type = PURGATORY_ENTER;
  319. }
  320. else if ( !strcmp( "spawn_loot", token ) )
  321. {
  322. info.m_type = LOOT_ISLAND_ENTER;
  323. }
  324. else
  325. {
  326. fprintf( errorFP, "ERROR @ Line %d: Unknown purgatory teleport '%s'\n", line, token );
  327. continue;
  328. }
  329. }
  330. else if ( !strcmp( "eyeball_stunned", token ) )
  331. {
  332. // eyeball_stunned with "short_stop" (attacker_position "-1951 -166 256")
  333. info.m_type = EYEBALL_STUN;
  334. // eat 'with'
  335. token = strtok( NULL, " " );
  336. token = strtok( NULL, "\" " );
  337. if ( !token )
  338. continue;
  339. strcpy( info.m_weaponName, token );
  340. // eat (attacker_position
  341. token = strtok( NULL, " " );
  342. token = strtok( NULL, "\" " );
  343. if ( !token )
  344. continue;
  345. info.m_attackerPosX = atoi( token );
  346. token = strtok( NULL, "\" " );
  347. if ( !token )
  348. continue;
  349. info.m_attackerPosY = atoi( token );
  350. token = strtok( NULL, "\" " );
  351. if ( !token )
  352. continue;
  353. info.m_attackerPosZ = atoi( token );
  354. }
  355. else if ( !strcmp( "eyeball_killer", token ) )
  356. {
  357. // eyeball_killer with "short_stop" (attacker_position "-1213 271 236")
  358. info.m_type = EYEBALL_KILLER;
  359. // eat 'with'
  360. token = strtok( NULL, " " );
  361. token = strtok( NULL, "\" " );
  362. if ( !token )
  363. continue;
  364. strcpy( info.m_weaponName, token );
  365. // eat (attacker_position
  366. token = strtok( NULL, " " );
  367. token = strtok( NULL, "\" " );
  368. if ( !token )
  369. continue;
  370. info.m_attackerPosX = atoi( token );
  371. token = strtok( NULL, "\" " );
  372. if ( !token )
  373. continue;
  374. info.m_attackerPosY = atoi( token );
  375. token = strtok( NULL, "\" " );
  376. if ( !token )
  377. continue;
  378. info.m_attackerPosZ = atoi( token );
  379. }
  380. else
  381. {
  382. fprintf( errorFP, "ERROR at Line %d: Unknown player event '%s'\n", line, token );
  383. }
  384. }
  385. }
  386. else
  387. {
  388. fprintf( errorFP, "ERROR at Line %d: Unknown data '%s'\n", line, token );
  389. continue;
  390. }
  391. TheData.push_back( info );
  392. }
  393. fclose( fp );
  394. //----------------------------------------
  395. unsigned int bossSpawnCount = 0;
  396. unsigned int bossEscapedCount = 0;
  397. unsigned int bossDeathCount = 0;
  398. unsigned int bossSpawnTimestamp = 0;
  399. unsigned int bossDroppedCount = 0;
  400. std::vector< int > bossLifetime;
  401. std::vector< int > bossEscapeLifetime;
  402. std::vector< int > bossDeathLifetime;
  403. std::vector< int > bossStunCount;
  404. bool isBossAlive = false;
  405. for( unsigned int i=0; i<TheData.size(); ++i )
  406. {
  407. switch( TheData[i].m_type )
  408. {
  409. case EYEBALL_SPAWN:
  410. if ( isBossAlive )
  411. {
  412. // we didn't get an ESCAPE or DEATH - map change?
  413. // timestamp can go backwards on map change, so just don't count these lifetimes
  414. bossLifetime.pop_back();
  415. bossEscapeLifetime.pop_back();
  416. bossDeathLifetime.pop_back();
  417. bossStunCount.pop_back();
  418. --bossSpawnCount;
  419. ++bossDroppedCount;
  420. }
  421. isBossAlive = true;
  422. bossSpawnTimestamp = TheData[i].m_timestamp;
  423. ++bossSpawnCount;
  424. bossLifetime.push_back(0);
  425. bossEscapeLifetime.push_back(0);
  426. bossDeathLifetime.push_back(0);
  427. bossStunCount.push_back(0);
  428. break;
  429. case EYEBALL_ESCAPE:
  430. if ( !isBossAlive )
  431. {
  432. fprintf( errorFP, "%d: Got ESCAPE when not spawned\n", i );
  433. }
  434. else
  435. {
  436. isBossAlive = false;
  437. bossLifetime[ bossSpawnCount-1 ] = TheData[i].m_timestamp - bossSpawnTimestamp;
  438. bossEscapeLifetime[ bossSpawnCount-1 ] = TheData[i].m_timestamp - bossSpawnTimestamp;
  439. ++bossEscapedCount;
  440. }
  441. break;
  442. case EYEBALL_DEATH:
  443. if ( !isBossAlive )
  444. {
  445. int lifetime = TheData[i].m_timestamp - bossSpawnTimestamp;
  446. // using large time delta to account for stuns while escaping followed by a death
  447. if ( lifetime - bossEscapeLifetime[ bossSpawnCount-1 ] < 60.0f )
  448. {
  449. // killed while escaping - he didn't actually escape!
  450. bossEscapeLifetime[ bossSpawnCount-1 ] = 0;
  451. --bossEscapedCount;
  452. }
  453. else
  454. {
  455. fprintf( errorFP, "%d: Got DEATH when not spawned\n", i );
  456. }
  457. }
  458. else
  459. {
  460. isBossAlive = false;
  461. bossLifetime[ bossSpawnCount-1 ] = TheData[i].m_timestamp - bossSpawnTimestamp;
  462. bossDeathLifetime[ bossSpawnCount-1 ] = TheData[i].m_timestamp - bossSpawnTimestamp;
  463. ++bossDeathCount;
  464. }
  465. break;
  466. case EYEBALL_STUN:
  467. // skip alive check to collect stuns that happen just after escape starts
  468. bossStunCount[ bossSpawnCount-1 ] = bossStunCount[ bossSpawnCount-1 ] + 1;
  469. break;
  470. case PURGATORY_ENTER:
  471. break;
  472. case PURGATORY_ESCAPE:
  473. break;
  474. case LOOT_ISLAND_ENTER:
  475. break;
  476. case EYEBALL_KILLER:
  477. break;
  478. }
  479. }
  480. fclose( errorFP );
  481. //----------------------------------------
  482. sprintf( buffer, "%s_cooked.txt", filename );
  483. fp = fopen( buffer, "w" );
  484. if ( !fp )
  485. {
  486. printf( "ERROR: Can't open output file '%s'\n", buffer );
  487. return false;
  488. }
  489. if ( bossSpawnCount )
  490. {
  491. int minTime = 99999, maxTime = 0, avgTime = 0;
  492. #define USE_COLUMNS
  493. #ifdef USE_COLUMNS
  494. fprintf( fp, "Lifetime, EscapeLifetime, DeathLifetime, StunCount\n" );
  495. for( unsigned int i=0; i<bossSpawnCount; ++i )
  496. {
  497. fprintf( fp, "%d, %d, %d, %d\n", bossLifetime[i], bossEscapeLifetime[i], bossDeathLifetime[i], bossStunCount[i] );
  498. if ( bossLifetime[i] < minTime )
  499. {
  500. minTime = bossLifetime[i];
  501. }
  502. if ( bossLifetime[i] > maxTime )
  503. {
  504. maxTime = bossLifetime[i];
  505. }
  506. avgTime += bossLifetime[i];
  507. }
  508. fprintf( fp, "\n" );
  509. #else
  510. fprintf( fp, "Lifetime, " );
  511. for( unsigned int i=0; i<bossSpawnCount; ++i )
  512. {
  513. fprintf( fp, "%d, ", bossLifetime[i] );
  514. if ( bossLifetime[i] < minTime )
  515. {
  516. minTime = bossLifetime[i];
  517. }
  518. if ( bossLifetime[i] > maxTime )
  519. {
  520. maxTime = bossLifetime[i];
  521. }
  522. avgTime += bossLifetime[i];
  523. }
  524. fprintf( fp, "\n" );
  525. fprintf( fp, "EscapeLifetime, " );
  526. for( unsigned int i=0; i<bossSpawnCount; ++i )
  527. {
  528. if ( bossEscapeLifetime[i] > 0 )
  529. fprintf( fp, "%d, ", bossEscapeLifetime[i] );
  530. }
  531. fprintf( fp, "\n" );
  532. fprintf( fp, "DeathLifetime, " );
  533. for( unsigned int i=0; i<bossSpawnCount; ++i )
  534. {
  535. if ( bossDeathLifetime[i] > 0 )
  536. fprintf( fp, "%d, ", bossDeathLifetime[i] );
  537. }
  538. fprintf( fp, "\n" );
  539. fprintf( fp, "StunCount, " );
  540. for( unsigned int i=0; i<bossSpawnCount; ++i )
  541. {
  542. fprintf( fp, "%d, ", bossStunCount[i] );
  543. }
  544. fprintf( fp, "\n\n" );
  545. #endif
  546. fprintf( fp, "Boss spawn count = %d\n", bossSpawnCount );
  547. fprintf( fp, "Boss escape count = %d\n", bossEscapedCount );
  548. fprintf( fp, "Boss escape ratio = %d%%\n", 100 * bossEscapedCount / bossSpawnCount );
  549. fprintf( fp, "Boss killed count = %d\n", bossDeathCount );
  550. fprintf( fp, "Boss killed ratio = %d%%\n", 100 * bossDeathCount / bossSpawnCount );
  551. fprintf( fp, "Boss dropped count = %d\n", bossDroppedCount );
  552. fprintf( fp, "Boss dropped ratio = %d%%\n", 100 * bossDroppedCount / bossSpawnCount );
  553. fprintf( fp, "Boss MinLifetime %d, AvgLifeTime %d, MaxLifetime %d\n", minTime, avgTime / bossSpawnCount, maxTime );
  554. }
  555. else
  556. {
  557. fprintf( fp, "No Halloween data found.\n" );
  558. }
  559. fclose( fp );
  560. return true;
  561. }
  562. //----------------------------------------------------------------------------------
  563. int _tmain(int argc, _TCHAR* argv[])
  564. {
  565. if ( argc < 2 )
  566. {
  567. printf( "USAGE: %s <server log .txt file>\n", argv[0] );
  568. return -1;
  569. }
  570. for( int i=1; i<argc; ++i )
  571. {
  572. ProcessData( argv[i] );
  573. }
  574. return 0;
  575. }