PageRenderTime 40ms CodeModel.GetById 1ms app.highlight 34ms RepoModel.GetById 1ms app.codeStats 0ms

/ghost/statsdota.cpp

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