/ghost/statsdota.cpp

http://ghostcb.googlecode.com/ · C++ · 439 lines · 308 code · 59 blank · 72 comment · 213 complexity · 955817a0478d674c4f0deaa21efbc015 MD5 · raw file

  1. /*
  2. Copyright [2008] [Trevor Hogan]
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. CODE PORTED FROM THE ORIGINAL GHOST PROJECT: http://ghost.pwner.org/
  13. */
  14. #include "ghost.h"
  15. #include "util.h"
  16. #include "ghostdb.h"
  17. #include "gameplayer.h"
  18. #include "gameprotocol.h"
  19. #include "game_base.h"
  20. #include "stats.h"
  21. #include "statsdota.h"
  22. //
  23. // CStatsDOTA
  24. //
  25. CStatsDOTA :: CStatsDOTA( CBaseGame *nGame ) : CStats( nGame ), m_Winner( 0 ), m_Min( 0 ), m_Sec( 0 )
  26. {
  27. CONSOLE_Print( "[STATSDOTA] using dota stats" );
  28. for( unsigned int i = 0; i < 12; ++i )
  29. m_Players[i] = NULL;
  30. }
  31. CStatsDOTA :: ~CStatsDOTA( )
  32. {
  33. for( unsigned int i = 0; i < 12; ++i )
  34. {
  35. if( m_Players[i] )
  36. delete m_Players[i];
  37. }
  38. }
  39. bool CStatsDOTA :: ProcessAction( CIncomingAction *Action )
  40. {
  41. unsigned int i = 0;
  42. BYTEARRAY *ActionData = Action->GetAction( );
  43. BYTEARRAY Data;
  44. BYTEARRAY Key;
  45. BYTEARRAY Value;
  46. // dota actions with real time replay data start with 0x6b then the null terminated string "dr.x"
  47. // unfortunately more than one action can be sent in a single packet and the length of each action isn't explicitly represented in the packet
  48. // so we have to either parse all the actions and calculate the length based on the type or we can search for an identifying sequence
  49. // parsing the actions would be more correct but would be a lot more difficult to write for relatively little gain
  50. // so we take the easy route (which isn't always guaranteed to work) and search the data for the sequence "6b 64 72 2e 78 00" and hope it identifies an action
  51. while( ActionData->size( ) >= i + 6 )
  52. {
  53. if( (*ActionData)[i] == 0x6b && (*ActionData)[i + 1] == 0x64 && (*ActionData)[i + 2] == 0x72 && (*ActionData)[i + 3] == 0x2e && (*ActionData)[i + 4] == 0x78 && (*ActionData)[i + 5] == 0x00 )
  54. {
  55. // we think we've found an action with real time replay data (but we can't be 100% sure)
  56. // next we parse out two null terminated strings and a 4 byte integer
  57. if( ActionData->size( ) >= i + 7 )
  58. {
  59. // the first null terminated string should either be the strings "Data" or "Global" or a player id in ASCII representation, e.g. "1" or "2"
  60. Data = UTIL_ExtractCString( *ActionData, i + 6 );
  61. if( ActionData->size( ) >= i + 8 + Data.size( ) )
  62. {
  63. // the second null terminated string should be the key
  64. Key = UTIL_ExtractCString( *ActionData, i + 7 + Data.size( ) );
  65. if( ActionData->size( ) >= i + 12 + Data.size( ) + Key.size( ) )
  66. {
  67. // the 4 byte integer should be the value
  68. Value = BYTEARRAY( ActionData->begin( ) + i + 8 + Data.size( ) + Key.size( ), ActionData->begin( ) + i + 12 + Data.size( ) + Key.size( ) );
  69. string DataString = string( Data.begin( ), Data.end( ) );
  70. string KeyString = string( Key.begin( ), Key.end( ) );
  71. uint32_t ValueInt = UTIL_ByteArrayToUInt32( Value, false );
  72. // CONSOLE_Print( "[STATS] " + DataString + ", " + KeyString + ", " + UTIL_ToString( ValueInt ) );
  73. if( DataString == "Data" )
  74. {
  75. // these are received during the game
  76. // you could use these to calculate killing sprees and double or triple kills (you'd have to make up your own time restrictions though)
  77. // you could also build a table of "who killed who" data
  78. if( KeyString.size( ) >= 5 && KeyString.substr( 0, 4 ) == "Hero" )
  79. {
  80. // a hero died
  81. string VictimColourString = KeyString.substr( 4 );
  82. uint32_t VictimColour = UTIL_ToUInt32( VictimColourString );
  83. CGamePlayer *Killer = m_Game->GetPlayerFromColour( ValueInt );
  84. CGamePlayer *Victim = m_Game->GetPlayerFromColour( VictimColour );
  85. if( Killer && Victim )
  86. CONSOLE_Print( "[STATSDOTA: " + m_Game->GetGameName( ) + "] player [" + Killer->GetName( ) + "] killed player [" + Victim->GetName( ) + "]" );
  87. else if( Victim )
  88. {
  89. if( ValueInt == 0 )
  90. CONSOLE_Print( "[STATSDOTA: " + m_Game->GetGameName( ) + "] the Sentinel killed player [" + Victim->GetName( ) + "]" );
  91. else if( ValueInt == 6 )
  92. CONSOLE_Print( "[STATSDOTA: " + m_Game->GetGameName( ) + "] the Scourge killed player [" + Victim->GetName( ) + "]" );
  93. }
  94. }
  95. else if( KeyString.size( ) >= 8 && KeyString.substr( 0, 7 ) == "Courier" )
  96. {
  97. // a courier died
  98. if( ( ValueInt >= 1 && ValueInt <= 5 ) || ( ValueInt >= 7 && ValueInt <= 11 ) )
  99. {
  100. if( !m_Players[ValueInt] )
  101. m_Players[ValueInt] = new CDBDotAPlayer( );
  102. m_Players[ValueInt]->SetCourierKills( m_Players[ValueInt]->GetCourierKills( ) + 1 );
  103. }
  104. string VictimColourString = KeyString.substr( 7 );
  105. uint32_t VictimColour = UTIL_ToUInt32( VictimColourString );
  106. CGamePlayer *Killer = m_Game->GetPlayerFromColour( ValueInt );
  107. CGamePlayer *Victim = m_Game->GetPlayerFromColour( VictimColour );
  108. if( Killer && Victim )
  109. CONSOLE_Print( "[STATSDOTA: " + m_Game->GetGameName( ) + "] player [" + Killer->GetName( ) + "] killed a courier owned by player [" + Victim->GetName( ) + "]" );
  110. else if( Victim )
  111. {
  112. if( ValueInt == 0 )
  113. CONSOLE_Print( "[STATSDOTA: " + m_Game->GetGameName( ) + "] the Sentinel killed a courier owned by player [" + Victim->GetName( ) + "]" );
  114. else if( ValueInt == 6 )
  115. CONSOLE_Print( "[STATSDOTA: " + m_Game->GetGameName( ) + "] the Scourge killed a courier owned by player [" + Victim->GetName( ) + "]" );
  116. }
  117. }
  118. else if( KeyString.size( ) >= 8 && KeyString.substr( 0, 5 ) == "Tower" )
  119. {
  120. // a tower died
  121. if( ( ValueInt >= 1 && ValueInt <= 5 ) || ( ValueInt >= 7 && ValueInt <= 11 ) )
  122. {
  123. if( !m_Players[ValueInt] )
  124. m_Players[ValueInt] = new CDBDotAPlayer( );
  125. m_Players[ValueInt]->SetTowerKills( m_Players[ValueInt]->GetTowerKills( ) + 1 );
  126. }
  127. string Alliance = KeyString.substr( 5, 1 );
  128. string Level = KeyString.substr( 6, 1 );
  129. string Side = KeyString.substr( 7, 1 );
  130. CGamePlayer *Killer = m_Game->GetPlayerFromColour( ValueInt );
  131. string AllianceString;
  132. string SideString;
  133. if( Alliance == "0" )
  134. AllianceString = "Sentinel";
  135. else if( Alliance == "1" )
  136. AllianceString = "Scourge";
  137. else
  138. AllianceString = "unknown";
  139. if( Side == "0" )
  140. SideString = "top";
  141. else if( Side == "1" )
  142. SideString = "mid";
  143. else if( Side == "2" )
  144. SideString = "bottom";
  145. else
  146. SideString = "unknown";
  147. if( Killer )
  148. CONSOLE_Print( "[STATSDOTA: " + m_Game->GetGameName( ) + "] player [" + Killer->GetName( ) + "] destroyed a level [" + Level + "] " + AllianceString + " tower (" + SideString + ")" );
  149. else
  150. {
  151. if( ValueInt == 0 )
  152. CONSOLE_Print( "[STATSDOTA: " + m_Game->GetGameName( ) + "] the Sentinel destroyed a level [" + Level + "] " + AllianceString + " tower (" + SideString + ")" );
  153. else if( ValueInt == 6 )
  154. CONSOLE_Print( "[STATSDOTA: " + m_Game->GetGameName( ) + "] the Scourge destroyed a level [" + Level + "] " + AllianceString + " tower (" + SideString + ")" );
  155. }
  156. }
  157. else if( KeyString.size( ) >= 6 && KeyString.substr( 0, 3 ) == "Rax" )
  158. {
  159. // a rax died
  160. if( ( ValueInt >= 1 && ValueInt <= 5 ) || ( ValueInt >= 7 && ValueInt <= 11 ) )
  161. {
  162. if( !m_Players[ValueInt] )
  163. m_Players[ValueInt] = new CDBDotAPlayer( );
  164. m_Players[ValueInt]->SetRaxKills( m_Players[ValueInt]->GetRaxKills( ) + 1 );
  165. }
  166. string Alliance = KeyString.substr( 3, 1 );
  167. string Side = KeyString.substr( 4, 1 );
  168. string Type = KeyString.substr( 5, 1 );
  169. CGamePlayer *Killer = m_Game->GetPlayerFromColour( ValueInt );
  170. string AllianceString;
  171. string SideString;
  172. string TypeString;
  173. if( Alliance == "0" )
  174. AllianceString = "Sentinel";
  175. else if( Alliance == "1" )
  176. AllianceString = "Scourge";
  177. else
  178. AllianceString = "unknown";
  179. if( Side == "0" )
  180. SideString = "top";
  181. else if( Side == "1" )
  182. SideString = "mid";
  183. else if( Side == "2" )
  184. SideString = "bottom";
  185. else
  186. SideString = "unknown";
  187. if( Type == "0" )
  188. TypeString = "melee";
  189. else if( Type == "1" )
  190. TypeString = "ranged";
  191. else
  192. TypeString = "unknown";
  193. if( Killer )
  194. CONSOLE_Print( "[STATSDOTA: " + m_Game->GetGameName( ) + "] player [" + Killer->GetName( ) + "] destroyed a " + TypeString + " " + AllianceString + " rax (" + SideString + ")" );
  195. else
  196. {
  197. if( ValueInt == 0 )
  198. CONSOLE_Print( "[STATSDOTA: " + m_Game->GetGameName( ) + "] the Sentinel destroyed a " + TypeString + " " + AllianceString + " rax (" + SideString + ")" );
  199. else if( ValueInt == 6 )
  200. CONSOLE_Print( "[STATSDOTA: " + m_Game->GetGameName( ) + "] the Scourge destroyed a " + TypeString + " " + AllianceString + " rax (" + SideString + ")" );
  201. }
  202. }
  203. else if( KeyString.size( ) >= 6 && KeyString.substr( 0, 6 ) == "Throne" )
  204. {
  205. // the frozen throne got hurt
  206. CONSOLE_Print( "[STATSDOTA: " + m_Game->GetGameName( ) + "] the Frozen Throne is now at " + UTIL_ToString( ValueInt ) + "% HP" );
  207. }
  208. else if( KeyString.size( ) >= 4 && KeyString.substr( 0, 4 ) == "Tree" )
  209. {
  210. // the world tree got hurt
  211. CONSOLE_Print( "[STATSDOTA: " + m_Game->GetGameName( ) + "] the World Tree is now at " + UTIL_ToString( ValueInt ) + "% HP" );
  212. }
  213. else if( KeyString.size( ) >= 2 && KeyString.substr( 0, 2 ) == "CK" )
  214. {
  215. // a player disconnected
  216. }
  217. else if( KeyString.size( ) >= 9 && KeyString.substr( 0, 9 ) == "GameStart" )
  218. {
  219. // Store the starttime of the game
  220. m_Game->SetGameStartTime ( m_Game->GetGameTicks() );
  221. }
  222. }
  223. else if( DataString == "Global" )
  224. {
  225. // these are only received at the end of the game
  226. if( KeyString == "Winner" )
  227. {
  228. // Value 1 -> sentinel
  229. // Value 2 -> scourge
  230. m_Winner = ValueInt;
  231. if( m_Winner == 1 )
  232. CONSOLE_Print( "[STATSDOTA: " + m_Game->GetGameName( ) + "] detected winner: Sentinel" );
  233. else if( m_Winner == 2 )
  234. CONSOLE_Print( "[STATSDOTA: " + m_Game->GetGameName( ) + "] detected winner: Scourge" );
  235. else
  236. CONSOLE_Print( "[STATSDOTA: " + m_Game->GetGameName( ) + "] detected winner: " + UTIL_ToString( ValueInt ) );
  237. }
  238. else if( KeyString == "m" )
  239. m_Min = ValueInt;
  240. else if( KeyString == "s" )
  241. m_Sec = ValueInt;
  242. }
  243. else if( DataString.size( ) <= 2 && DataString.find_first_not_of( "1234567890" ) == string :: npos )
  244. {
  245. // these are only received at the end of the game
  246. uint32_t ID = UTIL_ToUInt32( DataString );
  247. if( ( ID >= 1 && ID <= 5 ) || ( ID >= 7 && ID <= 11 ) )
  248. {
  249. if( !m_Players[ID] )
  250. {
  251. m_Players[ID] = new CDBDotAPlayer( );
  252. m_Players[ID]->SetColour( ID );
  253. }
  254. // Key "1" -> Kills
  255. // Key "2" -> Deaths
  256. // Key "3" -> Creep Kills
  257. // Key "4" -> Creep Denies
  258. // Key "5" -> Assists
  259. // Key "6" -> Current Gold
  260. // Key "7" -> Neutral Kills
  261. // Key "8_0" -> Item 1
  262. // Key "8_1" -> Item 2
  263. // Key "8_2" -> Item 3
  264. // Key "8_3" -> Item 4
  265. // Key "8_4" -> Item 5
  266. // Key "8_5" -> Item 6
  267. // Key "id" -> ID (1-5 for sentinel, 6-10 for scourge, accurate after using -sp and/or -switch)
  268. if( KeyString == "1" )
  269. m_Players[ID]->SetKills( ValueInt );
  270. else if( KeyString == "2" )
  271. m_Players[ID]->SetDeaths( ValueInt );
  272. else if( KeyString == "3" )
  273. m_Players[ID]->SetCreepKills( ValueInt );
  274. else if( KeyString == "4" )
  275. m_Players[ID]->SetCreepDenies( ValueInt );
  276. else if( KeyString == "5" )
  277. m_Players[ID]->SetAssists( ValueInt );
  278. else if( KeyString == "6" )
  279. m_Players[ID]->SetGold( ValueInt );
  280. else if( KeyString == "7" )
  281. m_Players[ID]->SetNeutralKills( ValueInt );
  282. else if( KeyString == "8_0" )
  283. m_Players[ID]->SetItem( 0, string( Value.rbegin( ), Value.rend( ) ) );
  284. else if( KeyString == "8_1" )
  285. m_Players[ID]->SetItem( 1, string( Value.rbegin( ), Value.rend( ) ) );
  286. else if( KeyString == "8_2" )
  287. m_Players[ID]->SetItem( 2, string( Value.rbegin( ), Value.rend( ) ) );
  288. else if( KeyString == "8_3" )
  289. m_Players[ID]->SetItem( 3, string( Value.rbegin( ), Value.rend( ) ) );
  290. else if( KeyString == "8_4" )
  291. m_Players[ID]->SetItem( 4, string( Value.rbegin( ), Value.rend( ) ) );
  292. else if( KeyString == "8_5" )
  293. m_Players[ID]->SetItem( 5, string( Value.rbegin( ), Value.rend( ) ) );
  294. else if( KeyString == "9" )
  295. m_Players[ID]->SetHero( string( Value.rbegin( ), Value.rend( ) ) );
  296. else if( KeyString == "id" )
  297. {
  298. // DotA sends id values from 1-10 with 1-5 being sentinel players and 6-10 being scourge players
  299. // unfortunately the actual player colours are from 1-5 and from 7-11 so we need to deal with this case here
  300. if( ValueInt >= 6 )
  301. m_Players[ID]->SetNewColour( ValueInt + 1 );
  302. else
  303. m_Players[ID]->SetNewColour( ValueInt );
  304. }
  305. }
  306. }
  307. i += 12 + Data.size( ) + Key.size( );
  308. }
  309. else
  310. ++i;
  311. }
  312. else
  313. ++i;
  314. }
  315. else
  316. ++i;
  317. }
  318. else
  319. ++i;
  320. }
  321. return m_Winner != 0;
  322. }
  323. void CStatsDOTA :: Save( CGHost *GHost, CGHostDB *DB, uint32_t GameID )
  324. {
  325. if( DB->Begin( ) )
  326. {
  327. // since we only record the end game information it's possible we haven't recorded anything yet if the game didn't end with a tree/throne death
  328. // this will happen if all the players leave before properly finishing the game
  329. // the dotagame stats are always saved (with winner = 0 if the game didn't properly finish)
  330. // the dotaplayer stats are only saved if the game is properly finished
  331. unsigned int Players = 0;
  332. // save the dotagame
  333. GHost->m_Callables.push_back( DB->ThreadedDotAGameAdd( GameID, m_Winner, m_Min, m_Sec ) );
  334. // check for invalid colours and duplicates
  335. // this can only happen if DotA sends us garbage in the "id" value but we should check anyway
  336. for( unsigned int i = 0; i < 12; ++i )
  337. {
  338. if( m_Players[i] )
  339. {
  340. uint32_t Colour = m_Players[i]->GetNewColour( );
  341. if( !( ( Colour >= 1 && Colour <= 5 ) || ( Colour >= 7 && Colour <= 11 ) ) )
  342. {
  343. CONSOLE_Print( "[STATSDOTA: " + m_Game->GetGameName( ) + "] discarding player data, invalid colour found" );
  344. DB->Commit( );
  345. return;
  346. }
  347. for( unsigned int j = i + 1; j < 12; ++j )
  348. {
  349. if( m_Players[j] && Colour == m_Players[j]->GetNewColour( ) )
  350. {
  351. CONSOLE_Print( "[STATSDOTA: " + m_Game->GetGameName( ) + "] discarding player data, duplicate colour found" );
  352. DB->Commit( );
  353. return;
  354. }
  355. }
  356. }
  357. }
  358. // save the dotaplayers
  359. for( unsigned int i = 0; i < 12; ++i )
  360. {
  361. if( m_Players[i] )
  362. {
  363. GHost->m_Callables.push_back( DB->ThreadedDotAPlayerAdd( GameID, m_Players[i]->GetColour( ), m_Players[i]->GetKills( ), m_Players[i]->GetDeaths( ), m_Players[i]->GetCreepKills( ), m_Players[i]->GetCreepDenies( ), m_Players[i]->GetAssists( ), m_Players[i]->GetGold( ), m_Players[i]->GetNeutralKills( ), m_Players[i]->GetItem( 0 ), m_Players[i]->GetItem( 1 ), m_Players[i]->GetItem( 2 ), m_Players[i]->GetItem( 3 ), m_Players[i]->GetItem( 4 ), m_Players[i]->GetItem( 5 ), m_Players[i]->GetHero( ), m_Players[i]->GetNewColour( ), m_Players[i]->GetTowerKills( ), m_Players[i]->GetRaxKills( ), m_Players[i]->GetCourierKills( ) ) );
  364. ++Players;
  365. }
  366. }
  367. if( DB->Commit( ) )
  368. CONSOLE_Print( "[STATSDOTA: " + m_Game->GetGameName( ) + "] saving " + UTIL_ToString( Players ) + " players" );
  369. else
  370. CONSOLE_Print( "[STATSDOTA: " + m_Game->GetGameName( ) + "] unable to commit database transaction, data not saved" );
  371. }
  372. else
  373. CONSOLE_Print( "[STATSDOTA: " + m_Game->GetGameName( ) + "] unable to begin database transaction, data not saved" );
  374. }