PageRenderTime 87ms CodeModel.GetById 16ms RepoModel.GetById 1ms app.codeStats 1ms

/ghost/game_base.cpp

http://ghostcb.googlecode.com/
C++ | 5237 lines | 3908 code | 874 blank | 455 comment | 1032 complexity | cf655cf2ef0b8f397572278fd51a8654 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. // Next line enables database-stats in UI (Stats, DotA/DB)
  15. //#define ENABLE_UI_DB_STATS
  16. #include "ghost.h"
  17. #include "util.h"
  18. #include "config.h"
  19. #include "language.h"
  20. #include "socket.h"
  21. #include "ghostdb.h"
  22. #include "bnet.h"
  23. #include "map.h"
  24. #include "packed.h"
  25. #include "savegame.h"
  26. #include "replay.h"
  27. #include "gameplayer.h"
  28. #include "gameprotocol.h"
  29. #include "game_base.h"
  30. #include "ui/forward.h"
  31. #include <cmath>
  32. #include <string.h>
  33. #include <time.h>
  34. #include "next_combination.h"
  35. //
  36. // CBaseGame
  37. //
  38. static uint32_t nextID = 0;
  39. CBaseGame :: CBaseGame( CGHost *nGHost, CMap *nMap, CSaveGame *nSaveGame, uint16_t nHostPort, unsigned char nGameState, string nGameName, string nOwnerName, string nCreatorName, string nCreatorServer )
  40. {
  41. m_GameID = nextID++;
  42. m_GHost = nGHost;
  43. m_Socket = new CTCPServer( );
  44. m_Protocol = new CGameProtocol( m_GHost );
  45. m_Map = new CMap( *nMap );
  46. m_SaveGame = nSaveGame;
  47. if( m_GHost->m_SaveReplays && !m_SaveGame )
  48. m_Replay = new CReplay( );
  49. else
  50. m_Replay = NULL;
  51. m_Exiting = false;
  52. m_Saving = false;
  53. m_HostPort = nHostPort;
  54. m_GameState = nGameState;
  55. m_VirtualHostPID = 255;
  56. m_FakePlayerPID = 255;
  57. // wait time of 1 minute = 0 empty actions required
  58. // wait time of 2 minutes = 1 empty action required
  59. // etc...
  60. if( m_GHost->m_ReconnectWaitTime == 0 )
  61. m_GProxyEmptyActions = 0;
  62. else
  63. {
  64. m_GProxyEmptyActions = m_GHost->m_ReconnectWaitTime - 1;
  65. // clamp to 9 empty actions (10 minutes)
  66. if( m_GProxyEmptyActions > 9 )
  67. m_GProxyEmptyActions = 9;
  68. }
  69. m_GameName = nGameName;
  70. m_LastGameName = nGameName;
  71. m_VirtualHostName = m_GHost->m_VirtualHostName;
  72. m_OwnerName = nOwnerName;
  73. m_CreatorName = nCreatorName;
  74. m_CreatorServer = nCreatorServer;
  75. m_HCLCommandString = m_Map->GetMapDefaultHCL( );
  76. m_RandomSeed = GetTicks( );
  77. m_HostCounter = m_GHost->m_HostCounter++;
  78. m_EntryKey = rand( );
  79. m_Latency = m_GHost->m_Latency;
  80. m_SyncLimit = m_GHost->m_SyncLimit;
  81. m_SyncCounter = 0;
  82. m_GameTicks = 0;
  83. m_CreationTime = GetTime( );
  84. m_LastPingTime = GetTime( );
  85. m_LastRefreshTime = GetTime( );
  86. m_LastDownloadTicks = GetTime( );
  87. m_DownloadCounter = 0;
  88. m_LastDownloadCounterResetTicks = GetTicks( );
  89. m_LastAnnounceTime = 0;
  90. m_AnnounceInterval = 0;
  91. m_LastAutoStartTime = GetTime( );
  92. m_AutoStartPlayers = 0;
  93. m_LastCountDownTicks = 0;
  94. m_CountDownCounter = 0;
  95. m_StartedLoadingTicks = 0;
  96. m_StartPlayers = 0;
  97. m_LastLagScreenResetTime = 0;
  98. m_LastActionSentTicks = 0;
  99. m_LastActionLateBy = 0;
  100. m_StartedLaggingTime = 0;
  101. m_LastLagScreenTime = 0;
  102. m_LastReservedSeen = GetTime( );
  103. m_StartedKickVoteTime = 0;
  104. m_GameOverTime = 0;
  105. m_LastPlayerLeaveTicks = 0;
  106. m_MinimumScore = 0.0;
  107. m_MaximumScore = 0.0;
  108. m_SlotInfoChanged = false;
  109. m_Locked = false;
  110. m_RefreshMessages = m_GHost->m_RefreshMessages;
  111. m_RefreshError = false;
  112. m_RefreshRehosted = false;
  113. m_MuteAll = false;
  114. m_MuteLobby = false;
  115. m_CountDownStarted = false;
  116. m_GameLoading = false;
  117. m_GameLoaded = false;
  118. m_LoadInGame = m_Map->GetMapLoadInGame( );
  119. m_Lagging = false;
  120. m_AutoSave = m_GHost->m_AutoSave;
  121. m_MatchMaking = false;
  122. m_LocalAdminMessages = m_GHost->m_LocalAdminMessages;
  123. m_UsingStart = false;
  124. m_LastUiTime = 0;
  125. m_LastUiSlotsTime = 0;
  126. if( m_SaveGame )
  127. {
  128. m_EnforceSlots = m_SaveGame->GetSlots( );
  129. m_Slots = m_SaveGame->GetSlots( );
  130. // the savegame slots contain player entries
  131. // we really just want the open/closed/computer entries
  132. // so open all the player slots
  133. for( vector<CGameSlot> :: iterator i = m_Slots.begin( ); i != m_Slots.end( ); ++i )
  134. {
  135. if( (*i).GetSlotStatus( ) == SLOTSTATUS_OCCUPIED && (*i).GetComputer( ) == 0 )
  136. {
  137. (*i).SetPID( 0 );
  138. (*i).SetDownloadStatus( 255 );
  139. (*i).SetSlotStatus( SLOTSTATUS_OPEN );
  140. }
  141. }
  142. }
  143. else
  144. m_Slots = m_Map->GetSlots( );
  145. if( !m_GHost->m_IPBlackListFile.empty( ) )
  146. {
  147. ifstream in;
  148. in.open( m_GHost->m_IPBlackListFile.c_str( ) );
  149. if( in.fail( ) )
  150. CONSOLE_Print( "[GAME: " + m_GameName + "] error loading IP blacklist file [" + m_GHost->m_IPBlackListFile + "]" );
  151. else
  152. {
  153. CONSOLE_Print( "[GAME: " + m_GameName + "] loading IP blacklist file [" + m_GHost->m_IPBlackListFile + "]" );
  154. string Line;
  155. while( !in.eof( ) )
  156. {
  157. getline( in, Line );
  158. // ignore blank lines and comments
  159. if( Line.empty( ) || Line[0] == '#' )
  160. continue;
  161. // remove newlines and partial newlines to help fix issues with Windows formatted files on Linux systems
  162. Line.erase( remove( Line.begin( ), Line.end( ), ' ' ), Line.end( ) );
  163. Line.erase( remove( Line.begin( ), Line.end( ), '\r' ), Line.end( ) );
  164. Line.erase( remove( Line.begin( ), Line.end( ), '\n' ), Line.end( ) );
  165. // ignore lines that don't look like IP addresses
  166. if( Line.find_first_not_of( "1234567890." ) != string :: npos )
  167. continue;
  168. m_IPBlackList.insert( Line );
  169. }
  170. in.close( );
  171. CONSOLE_Print( "[GAME: " + m_GameName + "] loaded " + UTIL_ToString( m_IPBlackList.size( ) ) + " lines from IP blacklist file" );
  172. }
  173. }
  174. // start listening for connections
  175. if( !m_GHost->m_BindAddress.empty( ) )
  176. CONSOLE_Print( "[GAME: " + m_GameName + "] attempting to bind to address [" + m_GHost->m_BindAddress + "]" );
  177. else
  178. CONSOLE_Print( "[GAME: " + m_GameName + "] attempting to bind to all available addresses" );
  179. if( m_Socket->Listen( m_GHost->m_BindAddress, m_HostPort ) )
  180. CONSOLE_Print( "[GAME: " + m_GameName + "] listening on port " + UTIL_ToString( m_HostPort ) );
  181. else
  182. {
  183. CONSOLE_Print( "[GAME: " + m_GameName + "] error listening on port " + UTIL_ToString( m_HostPort ) );
  184. m_Exiting = true;
  185. }
  186. forward( new CFwdData( FWD_GAME_ADD, m_GameName, m_GameID ) );
  187. vector<string> MapData;
  188. MapData.push_back( "Local Path" ); MapData.push_back( m_Map->GetMapLocalPath( ) );
  189. forward( new CFwdData( FWD_GAME_MAP_INFO_ADD, MapData, m_GameID ) );
  190. MapData.clear( );
  191. MapData.push_back( "Players" ); MapData.push_back( UTIL_ToString( GetNumHumanPlayers( ) ) + "/" + UTIL_ToString( m_GameLoading || m_GameLoaded ? m_StartPlayers : m_Slots.size( ) ) );
  192. forward( new CFwdData( FWD_GAME_MAP_INFO_ADD, MapData, m_GameID ) );
  193. MapData.clear( );
  194. MapData.push_back( "Teams" ); MapData.push_back( UTIL_ToString( m_Map->GetMapNumTeams( ) ) );
  195. forward( new CFwdData( FWD_GAME_MAP_INFO_ADD, MapData, m_GameID ) );
  196. MapData.clear( );
  197. MapData.push_back( "Visibility" ); MapData.push_back( m_Map->GetMapVisibility( ) == 4 ? "Default" :
  198. m_Map->GetMapVisibility( ) == 3 ? "Always visible" :
  199. m_Map->GetMapVisibility( ) == 2 ? "Explored" : "Hide Terrain" );
  200. forward( new CFwdData( FWD_GAME_MAP_INFO_ADD, MapData, m_GameID ) );
  201. MapData.clear( );
  202. MapData.push_back( "Speed" ); MapData.push_back( m_Map->GetMapSpeed( ) == 3 ? "Fast" :
  203. m_Map->GetMapSpeed( ) == 2 ? "Normal" : "Slow" );
  204. forward( new CFwdData( FWD_GAME_MAP_INFO_ADD, MapData, m_GameID ) );
  205. MapData.clear( );
  206. MapData.push_back( "HCL" ); MapData.push_back( m_HCLCommandString );
  207. forward( new CFwdData( FWD_GAME_MAP_INFO_ADD, MapData, m_GameID ) );
  208. MapData.clear( );
  209. MapData.push_back( "Owner" ); MapData.push_back( m_OwnerName );
  210. forward( new CFwdData( FWD_GAME_MAP_INFO_ADD, MapData, m_GameID ) );
  211. MapData.clear( );
  212. MapData.push_back( "Time" );
  213. if( m_GameLoading || m_GameLoaded )
  214. MapData.push_back( UTIL_ToString( ( m_GameTicks / 1000 ) / 60 ) + " minutes" );
  215. else
  216. MapData.push_back( UTIL_ToString( ( GetTime( ) - m_CreationTime ) / 60 ) + " minutes" );
  217. forward( new CFwdData( FWD_GAME_MAP_INFO_ADD, MapData, m_GameID ) );
  218. }
  219. CBaseGame :: ~CBaseGame( )
  220. {
  221. forward( new CFwdData( FWD_GAME_REMOVE, m_GameID ) );
  222. // save replay
  223. // todotodo: put this in a thread
  224. if( m_Replay && ( m_GameLoading || m_GameLoaded ) )
  225. {
  226. time_t Now = time( NULL );
  227. char Time[17];
  228. memset( Time, 0, sizeof( char ) * 17 );
  229. strftime( Time, sizeof( char ) * 17, "%Y-%m-%d %H-%M", localtime( &Now ) );
  230. string MinString = UTIL_ToString( ( m_GameTicks / 1000 ) / 60 );
  231. string SecString = UTIL_ToString( ( m_GameTicks / 1000 ) % 60 );
  232. if( MinString.size( ) == 1 )
  233. MinString.insert( 0, "0" );
  234. if( SecString.size( ) == 1 )
  235. SecString.insert( 0, "0" );
  236. m_Replay->BuildReplay( m_GameName, m_StatString, m_GHost->m_ReplayWar3Version, m_GHost->m_ReplayBuildNumber );
  237. m_Replay->Save( m_GHost->m_TFT, m_GHost->m_ReplayPath + UTIL_FileSafeName( "GHost++ " + string( Time ) + " " + m_GameName + " (" + MinString + "m" + SecString + "s).w3g" ) );
  238. }
  239. delete m_Socket;
  240. delete m_Protocol;
  241. delete m_Map;
  242. delete m_Replay;
  243. for( vector<CPotentialPlayer *> :: iterator i = m_Potentials.begin( ); i != m_Potentials.end( ); ++i )
  244. delete *i;
  245. for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); ++i )
  246. delete *i;
  247. for( vector<CCallableScoreCheck *> :: iterator i = m_ScoreChecks.begin( ); i != m_ScoreChecks.end( ); ++i )
  248. m_GHost->m_Callables.push_back( *i );
  249. while( !m_Actions.empty( ) )
  250. {
  251. delete m_Actions.front( );
  252. m_Actions.pop( );
  253. }
  254. }
  255. uint32_t CBaseGame :: GetNextTimedActionTicks( )
  256. {
  257. // return the number of ticks (ms) until the next "timed action", which for our purposes is the next game update
  258. // the main GHost++ loop will make sure the next loop update happens at or before this value
  259. // note: there's no reason this function couldn't take into account the game's other timers too but they're far less critical
  260. // warning: this function must take into account when actions are not being sent (e.g. during loading or lagging)
  261. if( !m_GameLoaded || m_Lagging )
  262. return 50;
  263. uint32_t TicksSinceLastUpdate = GetTicks( ) - m_LastActionSentTicks;
  264. if( TicksSinceLastUpdate > m_Latency - m_LastActionLateBy )
  265. return 0;
  266. else
  267. return m_Latency - m_LastActionLateBy - TicksSinceLastUpdate;
  268. }
  269. uint32_t CBaseGame :: GetSlotsOccupied( )
  270. {
  271. uint32_t NumSlotsOccupied = 0;
  272. for( vector<CGameSlot> :: iterator i = m_Slots.begin( ); i != m_Slots.end( ); ++i )
  273. {
  274. if( (*i).GetSlotStatus( ) == SLOTSTATUS_OCCUPIED )
  275. ++NumSlotsOccupied;
  276. }
  277. return NumSlotsOccupied;
  278. }
  279. uint32_t CBaseGame :: GetSlotsOpen( )
  280. {
  281. uint32_t NumSlotsOpen = 0;
  282. for( vector<CGameSlot> :: iterator i = m_Slots.begin( ); i != m_Slots.end( ); ++i )
  283. {
  284. if( (*i).GetSlotStatus( ) == SLOTSTATUS_OPEN )
  285. ++NumSlotsOpen;
  286. }
  287. return NumSlotsOpen;
  288. }
  289. uint32_t CBaseGame :: GetNumPlayers( )
  290. {
  291. uint32_t NumPlayers = GetNumHumanPlayers( );
  292. if( m_FakePlayerPID != 255 )
  293. ++NumPlayers;
  294. return NumPlayers;
  295. }
  296. uint32_t CBaseGame :: GetNumHumanPlayers( )
  297. {
  298. uint32_t NumHumanPlayers = 0;
  299. for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); ++i )
  300. {
  301. if( !(*i)->GetLeftMessageSent( ) )
  302. ++NumHumanPlayers;
  303. }
  304. return NumHumanPlayers;
  305. }
  306. string CBaseGame :: GetDescription( )
  307. {
  308. string Description = m_GameName + " : " + m_OwnerName + " : " + UTIL_ToString( GetNumHumanPlayers( ) ) + "/" + UTIL_ToString( m_GameLoading || m_GameLoaded ? m_StartPlayers : m_Slots.size( ) );
  309. if( m_GameLoading || m_GameLoaded )
  310. Description += " : " + UTIL_ToString( ( m_GameTicks / 1000 ) / 60 ) + "m";
  311. else
  312. Description += " : " + UTIL_ToString( ( GetTime( ) - m_CreationTime ) / 60 ) + "m";
  313. return Description;
  314. }
  315. void CBaseGame :: SetAnnounce( uint32_t interval, string message )
  316. {
  317. m_AnnounceInterval = interval;
  318. m_AnnounceMessage = message;
  319. m_LastAnnounceTime = GetTime( );
  320. }
  321. unsigned int CBaseGame :: SetFD( void *fd, void *send_fd, int *nfds )
  322. {
  323. unsigned int NumFDs = 0;
  324. if( m_Socket )
  325. {
  326. m_Socket->SetFD( (fd_set *)fd, (fd_set *)send_fd, nfds );
  327. ++NumFDs;
  328. }
  329. for( vector<CPotentialPlayer *> :: iterator i = m_Potentials.begin( ); i != m_Potentials.end( ); ++i )
  330. {
  331. if( (*i)->GetSocket( ) )
  332. {
  333. (*i)->GetSocket( )->SetFD( (fd_set *)fd, (fd_set *)send_fd, nfds );
  334. ++NumFDs;
  335. }
  336. }
  337. for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); ++i )
  338. {
  339. if( (*i)->GetSocket( ) )
  340. {
  341. (*i)->GetSocket( )->SetFD( (fd_set *)fd, (fd_set *)send_fd, nfds );
  342. ++NumFDs;
  343. }
  344. }
  345. return NumFDs;
  346. }
  347. bool CBaseGame :: Update( void *fd, void *send_fd )
  348. {
  349. // update callables
  350. for( vector<CCallableScoreCheck *> :: iterator i = m_ScoreChecks.begin( ); i != m_ScoreChecks.end( ); )
  351. {
  352. if( (*i)->GetReady( ) )
  353. {
  354. double Score = (*i)->GetResult( );
  355. for( vector<CPotentialPlayer *> :: iterator j = m_Potentials.begin( ); j != m_Potentials.end( ); ++j )
  356. {
  357. if( (*j)->GetJoinPlayer( ) && (*j)->GetJoinPlayer( )->GetName( ) == (*i)->GetName( ) )
  358. EventPlayerJoinedWithScore( *j, (*j)->GetJoinPlayer( ), Score );
  359. }
  360. m_GHost->m_DB->RecoverCallable( *i );
  361. delete *i;
  362. i = m_ScoreChecks.erase( i );
  363. }
  364. else
  365. ++i;
  366. }
  367. // update players
  368. for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); )
  369. {
  370. if( (*i)->Update( fd ) )
  371. {
  372. EventPlayerDeleted( *i );
  373. delete *i;
  374. i = m_Players.erase( i );
  375. }
  376. else
  377. ++i;
  378. }
  379. for( vector<CPotentialPlayer *> :: iterator i = m_Potentials.begin( ); i != m_Potentials.end( ); )
  380. {
  381. if( (*i)->Update( fd ) )
  382. {
  383. // flush the socket (e.g. in case a rejection message is queued)
  384. if( (*i)->GetSocket( ) )
  385. (*i)->GetSocket( )->DoSend( (fd_set *)send_fd );
  386. delete *i;
  387. i = m_Potentials.erase( i );
  388. }
  389. else
  390. ++i;
  391. }
  392. // create the virtual host player
  393. if( !m_GameLoading && !m_GameLoaded && GetNumPlayers( ) < 12 )
  394. CreateVirtualHost( );
  395. // unlock the game
  396. if( m_Locked && !GetPlayerFromName( m_OwnerName, false ) )
  397. {
  398. SendAllChat( m_GHost->m_Language->GameUnlocked( ) );
  399. m_Locked = false;
  400. }
  401. // ping every 5 seconds
  402. // changed this to ping during game loading as well to hopefully fix some problems with people disconnecting during loading
  403. // changed this to ping during the game as well
  404. if( GetTime( ) - m_LastPingTime >= 5 )
  405. {
  406. // note: we must send pings to players who are downloading the map because Warcraft III disconnects from the lobby if it doesn't receive a ping every ~90 seconds
  407. // so if the player takes longer than 90 seconds to download the map they would be disconnected unless we keep sending pings
  408. // todotodo: ignore pings received from players who have recently finished downloading the map
  409. SendAll( m_Protocol->SEND_W3GS_PING_FROM_HOST( ) );
  410. // we also broadcast the game to the local network every 5 seconds so we hijack this timer for our nefarious purposes
  411. // however we only want to broadcast if the countdown hasn't started
  412. // see the !sendlan code later in this file for some more information about how this works
  413. // todotodo: should we send a game cancel message somewhere? we'll need to implement a host counter for it to work
  414. if( !m_CountDownStarted )
  415. {
  416. // construct a fixed host counter which will be used to identify players from this "realm" (i.e. LAN)
  417. // the fixed host counter's 4 most significant bits will contain a 4 bit ID (0-15)
  418. // the rest of the fixed host counter will contain the 28 least significant bits of the actual host counter
  419. // since we're destroying 4 bits of information here the actual host counter should not be greater than 2^28 which is a reasonable assumption
  420. // when a player joins a game we can obtain the ID from the received host counter
  421. // note: LAN broadcasts use an ID of 0, battle.net refreshes use an ID of 1-10, the rest are unused
  422. uint32_t FixedHostCounter = m_HostCounter & 0x0FFFFFFF;
  423. // we send 12 for SlotsTotal because this determines how many PID's Warcraft 3 allocates
  424. // we need to make sure Warcraft 3 allocates at least SlotsTotal + 1 but at most 12 PID's
  425. // this is because we need an extra PID for the virtual host player (but we always delete the virtual host player when the 12th person joins)
  426. // however, we can't send 13 for SlotsTotal because this causes Warcraft 3 to crash when sharing control of units
  427. // nor can we send SlotsTotal because then Warcraft 3 crashes when playing maps with less than 12 PID's (because of the virtual host player taking an extra PID)
  428. // we also send 12 for SlotsOpen because Warcraft 3 assumes there's always at least one player in the game (the host)
  429. // so if we try to send accurate numbers it'll always be off by one and results in Warcraft 3 assuming the game is full when it still needs one more player
  430. // the easiest solution is to simply send 12 for both so the game will always show up as (1/12) players
  431. if( m_SaveGame )
  432. {
  433. // note: the PrivateGame flag is not set when broadcasting to LAN (as you might expect)
  434. uint32_t MapGameType = MAPGAMETYPE_SAVEDGAME;
  435. BYTEARRAY MapWidth;
  436. BYTEARRAY MapHeight;
  437. if ( m_GHost->m_Reconnect )
  438. {
  439. MapWidth.push_back( 192 );
  440. MapWidth.push_back( 7 );
  441. MapHeight.push_back( 192 );
  442. MapHeight.push_back( 7 );
  443. }
  444. else
  445. {
  446. MapWidth.push_back( 0 );
  447. MapWidth.push_back( 0 );
  448. MapHeight.push_back( 0 );
  449. MapHeight.push_back( 0 );
  450. }
  451. BYTEARRAY data = m_Protocol->SEND_W3GS_GAMEINFO( m_GHost->m_TFT, m_GHost->m_LANWar3Version, UTIL_CreateByteArray( MapGameType, false ), m_Map->GetMapGameFlags( ), MapWidth, MapHeight, m_GameName, "Varlock", GetTime( ) - m_CreationTime, "Save\\Multiplayer\\" + m_SaveGame->GetFileNameNoPath( ), m_SaveGame->GetMagicNumber( ), 12, 12, m_HostPort, FixedHostCounter, m_EntryKey );
  452. m_GHost->m_UDPSocket->Broadcast( 6112, data );
  453. for(vector<CTCPSocket * >::iterator i = m_GHost->m_GameBroadcasters.begin( ); i!= m_GHost->m_GameBroadcasters.end( ); i++ )
  454. {
  455. (*i)->PutBytes( data );
  456. }
  457. }
  458. else
  459. {
  460. // note: the PrivateGame flag is not set when broadcasting to LAN (as you might expect)
  461. // note: we do not use m_Map->GetMapGameType because none of the filters are set when broadcasting to LAN (also as you might expect)
  462. uint32_t MapGameType = MAPGAMETYPE_UNKNOWN0;
  463. BYTEARRAY MapWidth;
  464. BYTEARRAY MapHeight;
  465. if ( m_GHost->m_Reconnect )
  466. {
  467. MapWidth.push_back( 192 );
  468. MapWidth.push_back( 7 );
  469. MapHeight.push_back( 192 );
  470. MapHeight.push_back( 7 );
  471. }
  472. else
  473. {
  474. MapWidth = m_Map->GetMapWidth( );
  475. MapHeight = m_Map->GetMapHeight( );
  476. }
  477. BYTEARRAY data = m_Protocol->SEND_W3GS_GAMEINFO( m_GHost->m_TFT, m_GHost->m_LANWar3Version, UTIL_CreateByteArray( MapGameType, false ), m_Map->GetMapGameFlags( ), MapWidth, MapHeight, m_GameName, "Varlock", GetTime( ) - m_CreationTime, m_Map->GetMapPath( ), m_Map->GetMapCRC( ), 12, 12, m_HostPort, FixedHostCounter, m_EntryKey );
  478. m_GHost->m_UDPSocket->Broadcast( 6112, data );
  479. for(vector<CTCPSocket * >::iterator i = m_GHost->m_GameBroadcasters.begin( ); i!= m_GHost->m_GameBroadcasters.end( ); i++ )
  480. {
  481. (*i)->PutBytes( data );
  482. }
  483. }
  484. }
  485. m_LastPingTime = GetTime( );
  486. }
  487. // auto rehost if there was a refresh error in autohosted games
  488. if( m_RefreshError && !m_CountDownStarted && m_GameState == GAME_PUBLIC && !m_GHost->m_AutoHostGameName.empty( ) && m_GHost->m_AutoHostMaximumGames != 0 && m_GHost->m_AutoHostAutoStartPlayers != 0 && m_AutoStartPlayers != 0 )
  489. {
  490. // there's a slim chance that this isn't actually an autohosted game since there is no explicit autohost flag
  491. // however, if autohosting is enabled and this game is public and this game is set to autostart, it's probably autohosted
  492. // so rehost it using the current autohost game name
  493. string GameName = m_GHost->m_AutoHostGameName + " #" + UTIL_ToString( m_GHost->m_HostCounter );
  494. CONSOLE_Print( "[GAME: " + m_GameName + "] automatically trying to rehost as public game [" + GameName + "] due to refresh failure" );
  495. m_LastGameName = m_GameName;
  496. m_GameName = GameName;
  497. m_HostCounter = m_GHost->m_HostCounter++;
  498. m_RefreshError = false;
  499. for( vector<CBNET *> :: iterator i = m_GHost->m_BNETs.begin( ); i != m_GHost->m_BNETs.end( ); ++i )
  500. {
  501. (*i)->QueueGameUncreate( );
  502. (*i)->QueueEnterChat( );
  503. // the game creation message will be sent on the next refresh
  504. }
  505. m_CreationTime = GetTime( );
  506. m_LastRefreshTime = GetTime( );
  507. }
  508. // refresh every 3 seconds
  509. if( !m_RefreshError && !m_CountDownStarted && m_GameState == GAME_PUBLIC && GetSlotsOpen( ) > 0 && GetTime( ) - m_LastRefreshTime >= 3 )
  510. {
  511. // send a game refresh packet to each battle.net connection
  512. bool Refreshed = false;
  513. for( vector<CBNET *> :: iterator i = m_GHost->m_BNETs.begin( ); i != m_GHost->m_BNETs.end( ); ++i )
  514. {
  515. // don't queue a game refresh message if the queue contains more than 1 packet because they're very low priority
  516. if( (*i)->GetOutPacketsQueued( ) <= 1 )
  517. {
  518. /*
  519. Varlock's original refresh
  520. (*i)->QueueGameRefresh( m_GameState, m_GameName, string( ), m_Map, m_SaveGame, GetTime( ) - m_CreationTime, m_HostCounter );
  521. */
  522. // Refresh code from Fire86 (http://codelain.com/forum/index.php?topic=11373.msg88767#msg88767)
  523. (*i)->QueueGameRefresh( m_GameState, m_GameName, string( ), m_Map, m_SaveGame, 0, m_HostCounter );
  524. Refreshed = true;
  525. }
  526. }
  527. // only print the "game refreshed" message if we actually refreshed on at least one battle.net server
  528. if( m_RefreshMessages && Refreshed )
  529. SendAllChat( m_GHost->m_Language->GameRefreshed( ) );
  530. m_LastRefreshTime = GetTime( );
  531. }
  532. // send more map data
  533. if( !m_GameLoading && !m_GameLoaded && GetTicks( ) - m_LastDownloadCounterResetTicks >= 1000 )
  534. {
  535. // hackhack: another timer hijack is in progress here
  536. // since the download counter is reset once per second it's a great place to update the slot info if necessary
  537. if( m_SlotInfoChanged )
  538. SendAllSlotInfo( );
  539. m_DownloadCounter = 0;
  540. m_LastDownloadCounterResetTicks = GetTicks( );
  541. }
  542. if( !m_GameLoading && !m_GameLoaded && GetTicks( ) - m_LastDownloadTicks >= 100 )
  543. {
  544. uint32_t Downloaders = 0;
  545. for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); ++i )
  546. {
  547. if( (*i)->GetDownloadStarted( ) && !(*i)->GetDownloadFinished( ) )
  548. {
  549. ++Downloaders;
  550. if( m_GHost->m_MaxDownloaders > 0 && Downloaders > m_GHost->m_MaxDownloaders )
  551. break;
  552. // send up to 100 pieces of the map at once so that the download goes faster
  553. // if we wait for each MAPPART packet to be acknowledged by the client it'll take a long time to download
  554. // this is because we would have to wait the round trip time (the ping time) between sending every 1442 bytes of map data
  555. // doing it this way allows us to send at least 140 KB in each round trip interval which is much more reasonable
  556. // the theoretical throughput is [140 KB * 1000 / ping] in KB/sec so someone with 100 ping (round trip ping, not LC ping) could download at 1400 KB/sec
  557. // note: this creates a queue of map data which clogs up the connection when the client is on a slower connection (e.g. dialup)
  558. // in this case any changes to the lobby are delayed by the amount of time it takes to send the queued data (i.e. 140 KB, which could be 30 seconds or more)
  559. // for example, players joining and leaving, slot changes, chat messages would all appear to happen much later for the low bandwidth player
  560. // note: the throughput is also limited by the number of times this code is executed each second
  561. // e.g. if we send the maximum amount (140 KB) 10 times per second the theoretical throughput is 1400 KB/sec
  562. // therefore the maximum throughput is 1400 KB/sec regardless of ping and this value slowly diminishes as the player's ping increases
  563. // in addition to this, the throughput is limited by the configuration value bot_maxdownloadspeed
  564. // in summary: the actual throughput is MIN( 140 * 1000 / ping, 1400, bot_maxdownloadspeed ) in KB/sec assuming only one player is downloading the map
  565. uint32_t MapSize = UTIL_ByteArrayToUInt32( m_Map->GetMapSize( ), false );
  566. while( (*i)->GetLastMapPartSent( ) < (*i)->GetLastMapPartAcked( ) + 1442 * 100 && (*i)->GetLastMapPartSent( ) < MapSize )
  567. {
  568. if( (*i)->GetLastMapPartSent( ) == 0 )
  569. {
  570. // overwrite the "started download ticks" since this is the first time we've sent any map data to the player
  571. // prior to this we've only determined if the player needs to download the map but it's possible we could have delayed sending any data due to download limits
  572. (*i)->SetStartedDownloadingTicks( GetTicks( ) );
  573. }
  574. // limit the download speed if we're sending too much data
  575. // the download counter is the # of map bytes downloaded in the last second (it's reset once per second)
  576. if( m_GHost->m_MaxDownloadSpeed > 0 && m_DownloadCounter > m_GHost->m_MaxDownloadSpeed * 1024 )
  577. break;
  578. Send( *i, m_Protocol->SEND_W3GS_MAPPART( GetHostPID( ), (*i)->GetPID( ), (*i)->GetLastMapPartSent( ), m_Map->GetMapData( ) ) );
  579. (*i)->SetLastMapPartSent( (*i)->GetLastMapPartSent( ) + 1442 );
  580. m_DownloadCounter += 1442;
  581. }
  582. }
  583. }
  584. m_LastDownloadTicks = GetTicks( );
  585. }
  586. // announce every m_AnnounceInterval seconds
  587. if( !m_AnnounceMessage.empty( ) && !m_CountDownStarted && GetTime( ) - m_LastAnnounceTime >= m_AnnounceInterval )
  588. {
  589. SendAllChat( m_AnnounceMessage );
  590. m_LastAnnounceTime = GetTime( );
  591. }
  592. // kick players who don't spoof check within 20 seconds when spoof checks are required and the game is autohosted
  593. if( !m_CountDownStarted && m_GHost->m_RequireSpoofChecks && m_GameState == GAME_PUBLIC && !m_GHost->m_AutoHostGameName.empty( ) && m_GHost->m_AutoHostMaximumGames != 0 && m_GHost->m_AutoHostAutoStartPlayers != 0 && m_AutoStartPlayers != 0 )
  594. {
  595. for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); ++i )
  596. {
  597. if( !(*i)->GetSpoofed( ) && GetTime( ) - (*i)->GetJoinTime( ) >= 20 )
  598. {
  599. (*i)->SetDeleteMe( true );
  600. (*i)->SetLeftReason( m_GHost->m_Language->WasKickedForNotSpoofChecking( ) );
  601. (*i)->SetLeftCode( PLAYERLEAVE_LOBBY );
  602. OpenSlot( GetSIDFromPID( (*i)->GetPID( ) ), false );
  603. }
  604. }
  605. }
  606. // try to auto start every 5 sec (!start) or 10 sec (!autostart)
  607. if( m_UsingStart == true )
  608. {
  609. if( !m_CountDownStarted && m_AutoStartPlayers != 0 && GetTime( ) - m_LastAutoStartTime >= 5 )
  610. {
  611. StartCountDownAuto( m_GHost->m_RequireSpoofChecks );
  612. m_LastAutoStartTime = GetTime( );
  613. }
  614. }
  615. else
  616. if( !m_CountDownStarted && m_AutoStartPlayers != 0 && GetTime( ) - m_LastAutoStartTime >= 10 )
  617. {
  618. StartCountDownAuto( m_GHost->m_RequireSpoofChecks );
  619. m_LastAutoStartTime = GetTime( );
  620. }
  621. // normal countdown if active every 1200 ms (because we need an extra sec for 0 )
  622. if ( m_GHost->m_UseNormalCountDown )
  623. {
  624. if( m_CountDownStarted && GetTicks( ) >= m_LastCountDownTicks + 1200 )
  625. {
  626. if( m_CountDownCounter > 0 )
  627. {
  628. --m_CountDownCounter;
  629. }
  630. else if( !m_GameLoading && !m_GameLoaded )
  631. {
  632. EventGameStarted( );
  633. }
  634. m_LastCountDownTicks = GetTicks( );
  635. }
  636. }
  637. else
  638. {
  639. // countdown (ghost style) every 500 ms
  640. if( m_CountDownStarted && GetTicks( ) >= m_LastCountDownTicks + 500 )
  641. {
  642. if( m_CountDownCounter > 0 )
  643. {
  644. // we use a countdown counter rather than a "finish countdown time" here because it might alternately round up or down the count
  645. // this sometimes resulted in a countdown of e.g. "6 5 3 2 1" during my testing which looks pretty dumb
  646. // doing it this way ensures it's always "5 4 3 2 1" but each interval might not be *exactly* the same length
  647. SendAllChat( UTIL_ToString( m_CountDownCounter ) + ". . ." );
  648. m_CountDownCounter--;
  649. }
  650. else if( !m_GameLoading && !m_GameLoaded )
  651. EventGameStarted( );
  652. m_LastCountDownTicks = GetTicks( );
  653. }
  654. }
  655. // check if the lobby is "abandoned" and needs to be closed since it will never start
  656. if( !m_GameLoading && !m_GameLoaded && m_AutoStartPlayers == 0 && m_GHost->m_LobbyTimeLimit > 0 )
  657. {
  658. // check if there's a player with reserved status in the game
  659. for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); ++i )
  660. {
  661. if( (*i)->GetReserved( ) )
  662. m_LastReservedSeen = GetTime( );
  663. }
  664. // check if we've hit the time limit
  665. if( GetTime( ) - m_LastReservedSeen >= m_GHost->m_LobbyTimeLimit * 60 )
  666. {
  667. CONSOLE_Print( "[GAME: " + m_GameName + "] is over (lobby time limit hit)" );
  668. return true;
  669. }
  670. }
  671. // check if the game is loaded
  672. if( m_GameLoading )
  673. {
  674. bool FinishedLoading = true;
  675. for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); ++i )
  676. {
  677. FinishedLoading = (*i)->GetFinishedLoading( );
  678. if( !FinishedLoading )
  679. break;
  680. }
  681. if( FinishedLoading )
  682. {
  683. m_LastActionSentTicks = GetTicks( );
  684. m_GameLoading = false;
  685. m_GameLoaded = true;
  686. EventGameLoaded( );
  687. }
  688. else
  689. {
  690. // reset the "lag" screen (the load-in-game screen) every 30 seconds
  691. if( m_LoadInGame && GetTime( ) - m_LastLagScreenResetTime >= 30 )
  692. {
  693. bool UsingGProxy = false;
  694. for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); ++i )
  695. {
  696. if( (*i)->GetGProxy( ) )
  697. UsingGProxy = true;
  698. }
  699. for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); ++i )
  700. {
  701. if( (*i)->GetFinishedLoading( ) )
  702. {
  703. // stop the lag screen
  704. for( vector<CGamePlayer *> :: iterator j = m_Players.begin( ); j != m_Players.end( ); ++j )
  705. {
  706. if( !(*j)->GetFinishedLoading( ) )
  707. Send( *i, m_Protocol->SEND_W3GS_STOP_LAG( *j, true ) );
  708. }
  709. // send an empty update
  710. // this resets the lag screen timer but creates a rather annoying problem
  711. // in order to prevent a desync we must make sure every player receives the exact same "desyncable game data" (updates and player leaves) in the exact same order
  712. // unfortunately we cannot send updates to players who are still loading the map, so we buffer the updates to those players (see the else clause a few lines down for the code)
  713. // in addition to this we must ensure any player leave messages are sent in the exact same position relative to these updates so those must be buffered too
  714. if( UsingGProxy && !(*i)->GetGProxy( ) )
  715. {
  716. // we must send empty actions to non-GProxy++ players
  717. // GProxy++ will insert these itself so we don't need to send them to GProxy++ players
  718. // empty actions are used to extend the time a player can use when reconnecting
  719. for( unsigned char j = 0; j < m_GProxyEmptyActions; ++j )
  720. Send( *i, m_Protocol->SEND_W3GS_INCOMING_ACTION( queue<CIncomingAction *>( ), 0 ) );
  721. }
  722. Send( *i, m_Protocol->SEND_W3GS_INCOMING_ACTION( queue<CIncomingAction *>( ), 0 ) );
  723. // start the lag screen
  724. Send( *i, m_Protocol->SEND_W3GS_START_LAG( m_Players, true ) );
  725. }
  726. else
  727. {
  728. // buffer the empty update since the player is still loading the map
  729. if( UsingGProxy && !(*i)->GetGProxy( ) )
  730. {
  731. // we must send empty actions to non-GProxy++ players
  732. // GProxy++ will insert these itself so we don't need to send them to GProxy++ players
  733. // empty actions are used to extend the time a player can use when reconnecting
  734. for( unsigned char j = 0; j < m_GProxyEmptyActions; ++j )
  735. (*i)->AddLoadInGameData( m_Protocol->SEND_W3GS_INCOMING_ACTION( queue<CIncomingAction *>( ), 0 ) );
  736. }
  737. (*i)->AddLoadInGameData( m_Protocol->SEND_W3GS_INCOMING_ACTION( queue<CIncomingAction *>( ), 0 ) );
  738. }
  739. }
  740. // add actions to replay
  741. if( m_Replay )
  742. {
  743. if( UsingGProxy )
  744. {
  745. for( unsigned char i = 0; i < m_GProxyEmptyActions; ++i )
  746. m_Replay->AddTimeSlot( 0, queue<CIncomingAction *>( ) );
  747. }
  748. m_Replay->AddTimeSlot( 0, queue<CIncomingAction *>( ) );
  749. }
  750. // Warcraft III doesn't seem to respond to empty actions
  751. /* if( UsingGProxy )
  752. m_SyncCounter += m_GProxyEmptyActions;
  753. m_SyncCounter++; */
  754. m_LastLagScreenResetTime = GetTime( );
  755. }
  756. }
  757. }
  758. // keep track of the largest sync counter (the number of keepalive packets received by each player)
  759. // if anyone falls behind by more than m_SyncLimit keepalives we start the lag screen
  760. if( m_GameLoaded )
  761. {
  762. // check if anyone has started lagging
  763. // we consider a player to have started lagging if they're more than m_SyncLimit keepalives behind
  764. if( !m_Lagging )
  765. {
  766. string LaggingString;
  767. for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); ++i )
  768. {
  769. if( m_SyncCounter - (*i)->GetSyncCounter( ) > m_SyncLimit )
  770. {
  771. (*i)->SetLagging( true );
  772. (*i)->SetStartedLaggingTicks( GetTicks( ) );
  773. m_Lagging = true;
  774. m_StartedLaggingTime = GetTime( );
  775. if( LaggingString.empty( ) )
  776. LaggingString = (*i)->GetName( );
  777. else
  778. LaggingString += ", " + (*i)->GetName( );
  779. }
  780. }
  781. if( m_Lagging )
  782. {
  783. // start the lag screen
  784. CONSOLE_Print( "[GAME: " + m_GameName + "] started lagging on [" + LaggingString + "]" );
  785. SendAll( m_Protocol->SEND_W3GS_START_LAG( m_Players ) );
  786. // reset everyone's drop vote
  787. for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); ++i )
  788. (*i)->SetDropVote( false );
  789. m_LastLagScreenResetTime = GetTime( );
  790. }
  791. }
  792. if( m_Lagging )
  793. {
  794. bool UsingGProxy = false;
  795. for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); ++i )
  796. {
  797. if( (*i)->GetGProxy( ) )
  798. UsingGProxy = true;
  799. }
  800. uint32_t WaitTime = 60;
  801. if( UsingGProxy )
  802. WaitTime = ( m_GProxyEmptyActions + 1 ) * 60;
  803. if( GetTime( ) - m_StartedLaggingTime >= WaitTime )
  804. StopLaggers( m_GHost->m_Language->WasAutomaticallyDroppedAfterSeconds( UTIL_ToString( WaitTime ) ) );
  805. // we cannot allow the lag screen to stay up for more than ~65 seconds because Warcraft III disconnects if it doesn't receive an action packet at least this often
  806. // one (easy) solution is to simply drop all the laggers if they lag for more than 60 seconds
  807. // another solution is to reset the lag screen the same way we reset it when using load-in-game
  808. // this is required in order to give GProxy++ clients more time to reconnect
  809. if( GetTime( ) - m_LastLagScreenResetTime >= 60 )
  810. {
  811. for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); ++i )
  812. {
  813. // stop the lag screen
  814. for( vector<CGamePlayer *> :: iterator j = m_Players.begin( ); j != m_Players.end( ); ++j )
  815. {
  816. if( (*j)->GetLagging( ) )
  817. Send( *i, m_Protocol->SEND_W3GS_STOP_LAG( *j ) );
  818. }
  819. // send an empty update
  820. // this resets the lag screen timer
  821. if( UsingGProxy && !(*i)->GetGProxy( ) )
  822. {
  823. // we must send additional empty actions to non-GProxy++ players
  824. // GProxy++ will insert these itself so we don't need to send them to GProxy++ players
  825. // empty actions are used to extend the time a player can use when reconnecting
  826. for( unsigned char j = 0; j < m_GProxyEmptyActions; ++j )
  827. Send( *i, m_Protocol->SEND_W3GS_INCOMING_ACTION( queue<CIncomingAction *>( ), 0 ) );
  828. }
  829. Send( *i, m_Protocol->SEND_W3GS_INCOMING_ACTION( queue<CIncomingAction *>( ), 0 ) );
  830. // start the lag screen
  831. Send( *i, m_Protocol->SEND_W3GS_START_LAG( m_Players ) );
  832. }
  833. // add actions to replay
  834. if( m_Replay )
  835. {
  836. if( UsingGProxy )
  837. {
  838. for( unsigned char i = 0; i < m_GProxyEmptyActions; ++i )
  839. m_Replay->AddTimeSlot( 0, queue<CIncomingAction *>( ) );
  840. }
  841. m_Replay->AddTimeSlot( 0, queue<CIncomingAction *>( ) );
  842. }
  843. // Warcraft III doesn't seem to respond to empty actions
  844. /* if( UsingGProxy )
  845. m_SyncCounter += m_GProxyEmptyActions;
  846. m_SyncCounter++; */
  847. m_LastLagScreenResetTime = GetTime( );
  848. }
  849. // check if anyone has stopped lagging normally
  850. // we consider a player to have stopped lagging if they're less than half m_SyncLimit keepalives behind
  851. for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); ++i )
  852. {
  853. if( (*i)->GetLagging( ) && m_SyncCounter - (*i)->GetSyncCounter( ) < m_SyncLimit / 2 )
  854. {
  855. // stop the lag screen for this player
  856. CONSOLE_Print( "[GAME: " + m_GameName + "] stopped lagging on [" + (*i)->GetName( ) + "]" );
  857. SendAll( m_Protocol->SEND_W3GS_STOP_LAG( *i ) );
  858. (*i)->SetLagging( false );
  859. (*i)->SetStartedLaggingTicks( 0 );
  860. }
  861. }
  862. // check if everyone has stopped lagging
  863. bool Lagging = false;
  864. for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); ++i )
  865. {
  866. if( (*i)->GetLagging( ) )
  867. Lagging = true;
  868. }
  869. m_Lagging = Lagging;
  870. // reset m_LastActionSentTicks because we want the game to stop running while the lag screen is up
  871. m_LastActionSentTicks = GetTicks( );
  872. // keep track of the last lag screen time so we can avoid timing out players
  873. m_LastLagScreenTime = GetTime( );
  874. }
  875. }
  876. // send actions every m_Latency milliseconds
  877. // actions are at the heart of every Warcraft 3 game but luckily we don't need to know their contents to relay them
  878. // we queue player actions in EventPlayerAction then just resend them in batches to all players here
  879. if( m_GameLoaded && !m_Lagging && GetTicks( ) - m_LastActionSentTicks >= m_Latency - m_LastActionLateBy )
  880. SendAllActions( );
  881. // expire the votekick
  882. if( !m_KickVotePlayer.empty( ) && GetTime( ) - m_StartedKickVoteTime >= 60 )
  883. {
  884. CONSOLE_Print( "[GAME: " + m_GameName + "] votekick against player [" + m_KickVotePlayer + "] expired" );
  885. SendAllChat( m_GHost->m_Language->VoteKickExpired( m_KickVotePlayer ) );
  886. m_KickVotePlayer.clear( );
  887. m_StartedKickVoteTime = 0;
  888. }
  889. // start the gameover timer if there's only one player left
  890. if( m_Players.size( ) == 1 && m_FakePlayerPID == 255 && m_GameOverTime == 0 && ( m_GameLoading || m_GameLoaded ) )
  891. {
  892. CONSOLE_Print( "[GAME: " + m_GameName + "] gameover timer started (one player left)" );
  893. m_GameOverTime = GetTime( );
  894. }
  895. // finish the gameover timer
  896. if( m_GameOverTime != 0 && GetTime( ) - m_GameOverTime >= 60 )
  897. {
  898. bool AlreadyStopped = true;
  899. for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); ++i )
  900. {
  901. if( !(*i)->GetDeleteMe( ) )
  902. {
  903. AlreadyStopped = false;
  904. break;
  905. }
  906. }
  907. if( !AlreadyStopped )
  908. {
  909. CONSOLE_Print( "[GAME: " + m_GameName + "] is over (gameover timer finished)" );
  910. StopPlayers( "was disconnected (gameover timer finished)" );
  911. }
  912. }
  913. // end the game if there aren't any players left
  914. if( m_Players.empty( ) && ( m_GameLoading || m_GameLoaded ) )
  915. {
  916. if( !m_Saving )
  917. {
  918. CONSOLE_Print( "[GAME: " + m_GameName + "] is over (no players left)" );
  919. SaveGameData( );
  920. m_Saving = true;
  921. }
  922. else if( IsGameDataSaved( ) )
  923. return true;
  924. }
  925. // accept new connections
  926. if( m_Socket )
  927. {
  928. CTCPSocket *NewSocket = m_Socket->Accept( (fd_set *)fd );
  929. if( NewSocket )
  930. {
  931. // check the IP blacklist
  932. if( m_IPBlackList.find( NewSocket->GetIPString( ) ) == m_IPBlackList.end( ) )
  933. {
  934. if( m_GHost->m_TCPNoDelay )
  935. NewSocket->SetNoDelay( true );
  936. m_Potentials.push_back( new CPotentialPlayer( m_Protocol, this, NewSocket ) );
  937. }
  938. else
  939. {
  940. CONSOLE_Print( "[GAME: " + m_GameName + "] rejected connection from [" + NewSocket->GetIPString( ) + "] due to blacklist" );
  941. delete NewSocket;
  942. }
  943. }
  944. if( m_Socket->HasError( ) )
  945. return true;
  946. }
  947. // update time in ui
  948. if( GetTime( ) > m_LastUiTime )
  949. {
  950. vector<string> MapData;
  951. MapData.push_back( "Time" );
  952. if( m_GameLoading || m_GameLoaded )
  953. MapData.push_back( UTIL_ToString( ( m_GameTicks / 1000 ) / 60 ) + " minutes" );
  954. else
  955. MapData.push_back( UTIL_ToString( ( GetTime( ) - m_CreationTime ) / 60 ) + " minutes" );
  956. forward( new CFwdData( FWD_GAME_MAP_INFO_UPDATE, MapData, m_GameID ) );
  957. m_LastUiTime = GetTime( ) + 60;
  958. }
  959. // update players in ui
  960. if( GetTime( ) > m_LastUiSlotsTime )
  961. {
  962. vector<string> playerData;
  963. for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); ++i )
  964. {
  965. playerData.clear( );
  966. playerData.push_back( (*i)->GetName( ) ); // name
  967. playerData.push_back( UTIL_ToString( GetSIDFromPID( (*i)->GetPID( ) ) + 1 ) ); // slot
  968. playerData.push_back( m_GHost->m_DBLocal->FromCheck( UTIL_ByteArrayToUInt32( (*i)->GetExternalIP( ), true ) ) ); // from
  969. playerData.push_back( UTIL_ToString( (*i)->GetPing(true) ) + " ms" ); // ping
  970. playerData.push_back( m_Slots[ GetSIDFromPID( (*i)->GetPID( ) ) ].GetRace( ) == 1 ? "HU" :
  971. m_Slots[ GetSIDFromPID( (*i)->GetPID( ) ) ].GetRace( ) == 2 ? "ORC" :
  972. m_Slots[ GetSIDFromPID( (*i)->GetPID( ) ) ].GetRace( ) == 4 ? "NE" :
  973. m_Slots[ GetSIDFromPID( (*i)->GetPID( ) ) ].GetRace( ) == 8 ? "UD" : "??" ); // race
  974. playerData.push_back( UTIL_ToString( m_Slots[GetSIDFromPID( (*i)->GetPID( ) )].GetTeam( ) + 1 ) ); // team
  975. playerData.push_back( ColourValueToString( m_Slots[GetSIDFromPID( (*i)->GetPID( ) )].GetColour( ) ) ); // color
  976. playerData.push_back( UTIL_ToString( m_Slots[GetSIDFromPID( (*i)->GetPID( ) )].GetHandicap( ) ) ); // handicap
  977. playerData.push_back( (*i)->GetGProxy( ) ? "on" : "off" ); // gproxy++
  978. forward( new CFwdData( FWD_GAME_SLOT_UPDATE, playerData, m_GameID ) );
  979. }
  980. m_LastUiSlotsTime = GetTime( ) + 10;
  981. }
  982. return m_Exiting;
  983. }
  984. void CBaseGame :: UpdatePost( void *send_fd )
  985. {
  986. // we need to manually call DoSend on each player now because CGamePlayer :: Update doesn't do it
  987. // this is in case player 2 generates a packet for player 1 during the update but it doesn't get sent because player 1 already finished updating
  988. // in reality since we're queueing actions it might not make a big difference but oh well
  989. for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); ++i )
  990. {
  991. if( (*i)->GetSocket( ) )
  992. (*i)->GetSocket( )->DoSend( (fd_set *)send_fd );
  993. }
  994. for( vector<CPotentialPlayer *> :: iterator i = m_Potentials.begin( ); i != m_Potentials.end( ); ++i )
  995. {
  996. if( (*i)->GetSocket( ) )
  997. (*i)->GetSocket( )->DoSend( (fd_set *)send_fd );
  998. }
  999. }
  1000. void CBaseGame :: Send( CGamePlayer *player, BYTEARRAY data )
  1001. {
  1002. if( player )
  1003. player->Send( data );
  1004. }
  1005. void CBaseGame :: Send( unsigned char PID, BYTEARRAY data )
  1006. {
  1007. Send( GetPlayerFromPID( PID ), data );
  1008. }
  1009. void CBaseGame :: Send( BYTEARRAY PIDs, BYTEARRAY data )
  1010. {
  1011. for( unsigned int i = 0; i < PIDs.size( ); ++i )
  1012. Send( PIDs[i], data );
  1013. }
  1014. void CBaseGame :: SendAll( BYTEARRAY data )
  1015. {
  1016. for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); ++i )
  1017. (*i)->Send( data );
  1018. }
  1019. void CBaseGame :: SendChat( unsigned char fromPID, CGamePlayer *player, string message )
  1020. {
  1021. // send a private message to one player - it'll be marked [Private] in Warcraft 3
  1022. if( player )
  1023. {
  1024. if( !m_GameLoading && !m_GameLoaded )
  1025. {
  1026. if( message.size( ) > 254 )
  1027. message = message.substr( 0, 254 );
  1028. Send( player, m_Protocol->SEND_W3GS_CHAT_FROM_HOST( fromPID, UTIL_CreateByteArray( player->GetPID( ) ), 16, BYTEARRAY( ), message ) );
  1029. }
  1030. else
  1031. {
  1032. unsigned char ExtraFlags[] = { 3, 0, 0, 0 };
  1033. // based on my limited testing it seems that the extra flags' first byte contains 3 plus the recipient's colour to denote a private message
  1034. unsigned char SID = GetSIDFromPID( player->GetPID( ) );
  1035. if( SID < m_Slots.size( ) )
  1036. ExtraFlags[0] = 3 + m_Slots[SID].GetColour( );
  1037. if( message.size( ) > 127 )
  1038. message = message.substr( 0, 127 );
  1039. Send( player, m_Protocol->SEND_W3GS_CHAT_FROM_HOST( fromPID, UTIL_CreateByteArray( player->GetPID( ) ), 32, UTIL_CreateByteArray( ExtraFlags, 4 ), message ) );
  1040. }
  1041. }
  1042. }
  1043. void CBaseGame :: SendChat( unsigned char fromPID, unsigned char toPID, string message )
  1044. {
  1045. SendChat( fromPID, GetPlayerFromPID( toPID ), message );
  1046. }
  1047. void CBaseGame :: SendChat( CGamePlayer *player, string message )
  1048. {
  1049. SendChat( GetHostPID( ), player, message );
  1050. }
  1051. void CBaseGame :: SendChat( unsigned char toPID, string message )
  1052. {
  1053. SendChat( GetHostPID( ), toPID, message );
  1054. }
  1055. void CBaseGame :: SendAllChat( unsigned char fromPID, string message )
  1056. {
  1057. // bot command
  1058. if( !message.empty( ) && message[0] == m_GHost->m_CommandTrigger )
  1059. {
  1060. // extract the command trigger, the command, and the payload
  1061. // e.g. "!say hello world" -> command: "say", payload: "hello world"
  1062. string Command;
  1063. string Payload;
  1064. string :: size_type PayloadStart = message.find( " " );
  1065. if( PayloadStart != string :: npos )
  1066. {
  1067. Command = message.substr( 1, PayloadStart - 1 );
  1068. Payload = message.substr( PayloadStart + 1 );
  1069. }
  1070. else
  1071. Command = message.substr( 1 );
  1072. transform( Command.begin( ), Command.end( ), Command.begin( ), (int(*)(int))tolower );
  1073. BYTEARRAY temp;
  1074. CGamePlayer *Player = new CGamePlayer(0, 0, 0, fromPID, m_CreatorServer, m_CreatorName, temp, false);
  1075. Player->SetSpoofed(true);
  1076. Player->SetSpoofedRealm(m_CreatorServer);
  1077. EventPlayerBotCommand( Player, Command, Payload );
  1078. delete Player;
  1079. return; // Don't show the message.
  1080. }
  1081. forward( new CFwdData( FWD_GAME_CHAT, "ADMIN:\3", 1, m_GameID ) );
  1082. forward( new CFwdData( FWD_GAME_CHAT, message, 0, m_GameID ) );
  1083. // send a public message to all players - it'll be marked [All] in Warcraft 3
  1084. if( GetNumHumanPlayers( ) > 0 )
  1085. {
  1086. CONSOLE_Print( "[GAME: " + m_GameName + "] [Local]: " + message );
  1087. if( !m_GameLoading && !m_GameLoaded )
  1088. {
  1089. if( message.size( ) > 254 )
  1090. message = message.substr( 0, 254 );
  1091. SendAll( m_Protocol->SEND_W3GS_CHAT_FROM_HOST( fromPID, GetPIDs( ), 16, BYTEARRAY( ), message ) );
  1092. }
  1093. else
  1094. {
  1095. if( message.size( ) > 127 )
  1096. message = message.substr( 0, 127 );
  1097. SendAll( m_Protocol->SEND_W3GS_CHAT_FROM_HOST( fromPID, GetPIDs( ), 32, UTIL_CreateByteArray( (uint32_t)0, false ), message ) );
  1098. if( m_Replay )
  1099. m_Replay->AddChatMessage( fromPID, 32, 0, message );
  1100. }
  1101. }
  1102. }
  1103. void CBaseGame :: SendAllChat( string message )
  1104. {
  1105. SendAllChat( GetHostPID( ), message );
  1106. }
  1107. void CBaseGame :: SendLocalAdminChat( string message )
  1108. {
  1109. if( !m_LocalAdminMessages )
  1110. return;
  1111. // send a message to LAN/local players who are admins
  1112. // at the time of this writing it is only possible for the game owner to meet this criteria because being an admin requires spoof checking
  1113. // this is mainly used for relaying battle.net whispers, chat messages, and emotes to these players
  1114. for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); ++i )
  1115. {
  1116. if( (*i)->GetSpoofed( ) && IsOwner( (*i)->GetName( ) ) && ( UTIL_IsLanIP( (*i)->GetExternalIP( ) ) || UTIL_IsLocalIP( (*i)->GetExternalIP( ), m_GHost->m_LocalAddresses ) ) )
  1117. {
  1118. if( m_VirtualHostPID != 255 )
  1119. SendChat( m_VirtualHostPID, *i, message );
  1120. else
  1121. {
  1122. // make the chat message originate from the recipient since it's not going to be logged to the replay
  1123. SendChat( (*i)->GetPID( ), *i, message );
  1124. }
  1125. }
  1126. }
  1127. }
  1128. void CBaseGame :: SendAllSlotInfo( )
  1129. {
  1130. if( !m_GameLoading && !m_GameLoaded )
  1131. {
  1132. SendAll( m_Protocol->SEND_W3GS_SLOTINFO( m_Slots, m_RandomSeed, m_Map->GetMapLayoutStyle( ), m_Map->GetMapNumPlayers( ) ) );
  1133. m_SlotInfoChanged = false;
  1134. }
  1135. }
  1136. void CBaseGame :: SendVirtualHostPlayerInfo( CGamePlayer *player )
  1137. {
  1138. if( m_VirtualHostPID == 255 )
  1139. return;
  1140. BYTEARRAY IP;
  1141. IP.push_back( 0 );
  1142. IP.push_back( 0 );
  1143. IP.push_back( 0 );
  1144. IP.push_back( 0 );
  1145. Send( player, m_Protocol->SEND_W3GS_PLAYERINFO( m_VirtualHostPID, m_VirtualHostName, IP, IP ) );
  1146. }
  1147. void CBaseGame :: SendFakePlayerInfo( CGamePlayer *player )
  1148. {
  1149. if( m_FakePlayerPID == 255 )
  1150. return;
  1151. BYTEARRAY IP;
  1152. IP.push_back( 0 );
  1153. IP.push_back( 0 );
  1154. IP.push_back( 0 );
  1155. IP.push_back( 0 );
  1156. Send( player, m_Protocol->SEND_W3GS_PLAYERINFO( m_FakePlayerPID, "FakePlayer", IP, IP ) );
  1157. }
  1158. void CBaseGame :: SendAllActions( )
  1159. {
  1160. bool UsingGProxy = false;
  1161. for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); ++i )
  1162. {
  1163. if( (*i)->GetGProxy( ) )
  1164. UsingGProxy = true;
  1165. }
  1166. m_GameTicks += m_Latency;
  1167. if( UsingGProxy )
  1168. {
  1169. // we must send empty actions to non-GProxy++ players
  1170. // GProxy++ will insert these itself so we don't need to send them to GProxy++ players
  1171. // empty actions are used to extend the time a player can use when reconnecting
  1172. for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); ++i )
  1173. {
  1174. if( !(*i)->GetGProxy( ) )
  1175. {
  1176. for( unsigned char j = 0; j < m_GProxyEmptyActions; ++j )
  1177. Send( *i, m_Protocol->SEND_W3GS_INCOMING_ACTION( queue<CIncomingAction *>( ), 0 ) );
  1178. }
  1179. }
  1180. if( m_Replay )
  1181. {
  1182. for( unsigned char i = 0; i < m_GProxyEmptyActions; ++i )
  1183. m_Replay->AddTimeSlot( 0, queue<CIncomingAction *>( ) );
  1184. }
  1185. }
  1186. // Warcraft III doesn't seem to respond to empty actions
  1187. /* if( UsingGProxy )
  1188. m_SyncCounter += m_GProxyEmptyActions; */
  1189. ++m_SyncCounter;
  1190. // we aren't allowed to send more than 1460 bytes in a single packet but it's possible we might have more than that many bytes waiting in the queue
  1191. if( !m_Actions.empty( ) )
  1192. {
  1193. // we use a "sub actions queue" which we keep adding actions to until we reach the size limit
  1194. // start by adding one action to the sub actions queue
  1195. queue<CIncomingAction *> SubActions;
  1196. CIncomingAction *Action = m_Actions.front( );
  1197. m_Actions.pop( );
  1198. SubActions.push( Action );
  1199. uint32_t SubActionsLength = Action->GetLength( );
  1200. while( !m_Actions.empty( ) )
  1201. {
  1202. Action = m_Actions.front( );
  1203. m_Actions.pop( );
  1204. // check if adding the next action to the sub actions queue would put us over the limit (1452 because the INCOMING_ACTION and INCOMING_ACTION2 packets use an extra 8 bytes)
  1205. if( SubActionsLength + Action->GetLength( ) > 1452 )
  1206. {
  1207. // we'd be over the limit if we added the next action to the sub actions queue
  1208. // so send everything already in the queue and then clear it out
  1209. // the W3GS_INCOMING_ACTION2 packet handles the overflow but it must be sent *before* the corresponding W3GS_INCOMING_ACTION packet
  1210. SendAll( m_Protocol->SEND_W3GS_INCOMING_ACTION2( SubActions ) );
  1211. if( m_Replay )
  1212. m_Replay->AddTimeSlot2( SubActions );
  1213. while( !SubActions.empty( ) )
  1214. {
  1215. delete SubActions.front( );
  1216. SubActions.pop( );
  1217. }
  1218. SubActionsLength = 0;
  1219. }
  1220. SubActions.push( Action );
  1221. SubActionsLength += Action->GetLength( );
  1222. }
  1223. SendAll( m_Protocol->SEND_W3GS_INCOMING_ACTION( SubActions, m_Latency ) );
  1224. if( m_Replay )
  1225. m_Replay->AddTimeSlot( m_Latency, SubActions );
  1226. while( !SubActions.empty( ) )
  1227. {
  1228. delete SubActions.front( );
  1229. SubActions.pop( );
  1230. }
  1231. }
  1232. else
  1233. {
  1234. SendAll( m_Protocol->SEND_W3GS_INCOMING_ACTION( m_Actions, m_Latency ) );
  1235. if( m_Replay )
  1236. m_Replay->AddTimeSlot( m_Latency, m_Actions );
  1237. }
  1238. uint32_t ActualSendInterval = GetTicks( ) - m_LastActionSentTicks;
  1239. uint32_t ExpectedSendInterval = m_Latency - m_LastActionLateBy;
  1240. m_LastActionLateBy = ActualSendInterval - ExpectedSendInterval;
  1241. if( m_LastActionLateBy > m_Latency )
  1242. {
  1243. // something is going terribly wrong - GHost++ is probably starved of resources
  1244. // print a message because even though this will take more resources it should provide some information to the administrator for future reference
  1245. // other solutions - dynamically modify the latency, request higher priority, terminate other games, ???
  1246. CONSOLE_Print( "[GAME: " + m_GameName + "] warning - the latency is " + UTIL_ToString( m_Latency ) + "ms but the last update was late by " + UTIL_ToString( m_LastActionLateBy ) + "ms" );
  1247. m_LastActionLateBy = m_Latency;
  1248. }
  1249. m_LastActionSentTicks = GetTicks( );
  1250. }
  1251. void CBaseGame :: SendWelcomeMessage( CGamePlayer *player )
  1252. {
  1253. // read from motd.txt if available (thanks to zeeg for this addition)
  1254. ifstream in;
  1255. in.open( m_GHost->m_MOTDFile.c_str( ) );
  1256. if( in.fail( ) )
  1257. {
  1258. // default welcome message
  1259. if( m_HCLCommandString.empty( ) )
  1260. SendChat( player, " " );
  1261. SendChat( player, " " );
  1262. SendChat( player, " Welcome to game " + m_GameName );
  1263. SendChat( player, " " );
  1264. SendChat( player, " This game is hosted with Ghost++ v" + m_GHost->m_Version ); http://forum.codelain.com/" );
  1265. SendChat( player, "GHost++ http://www.codelain.com/" );
  1266. SendChat( player, "-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-" );
  1267. SendChat( player, " The Owner is " + m_OwnerName );
  1268. if( !m_HCLCommandString.empty( ) )
  1269. SendChat( player, "Game Mode will be -" + m_HCLCommandString );
  1270. else
  1271. SendChat( player, " " );
  1272. }
  1273. else
  1274. {
  1275. // custom welcome message
  1276. // don't print more than 8 lines
  1277. uint32_t Count = 0;
  1278. string Line;
  1279. while( !in.eof( ) && Count < 8 )
  1280. {
  1281. getline( in, Line );
  1282. // Shade0o - added in custom message codes
  1283. UTIL_Replace( Line, "$OWNERNAME$", m_OwnerName );
  1284. UTIL_Replace( Line, "$GAMENAME$", m_GameName );
  1285. UTIL_Replace( Line, "$HCL$", m_HCLCommandString );
  1286. UTIL_Replace( Line, "$VERSION$", m_GHost->m_Version );
  1287. UTIL_Replace( Line, "$USER$", player->GetName( ) );
  1288. UTIL_Replace( Line, "$BOTNAME$", m_VirtualHostName );
  1289. if( Line.empty( ) )
  1290. {
  1291. if( !in.eof( ) )
  1292. SendChat( player, " " );
  1293. }
  1294. else
  1295. SendChat( player, Line );
  1296. ++Count;
  1297. }
  1298. in.close( );
  1299. }
  1300. }
  1301. void CBaseGame :: SendEndMessage( )
  1302. {
  1303. // read from gameover.txt if available
  1304. ifstream in;
  1305. in.open( m_GHost->m_GameOverFile.c_str( ) );
  1306. if( !in.fail( ) )
  1307. {
  1308. // don't print more than 8 lines
  1309. uint32_t Count = 0;
  1310. string Line;
  1311. while( !in.eof( ) && Count < 8 )
  1312. {
  1313. getline( in, Line );
  1314. if( Line.empty( ) )
  1315. {
  1316. if( !in.eof( ) )
  1317. SendAllChat( " " );
  1318. }
  1319. else
  1320. SendAllChat( Line );
  1321. ++Count;
  1322. }
  1323. in.close( );
  1324. }
  1325. }
  1326. void CBaseGame :: EventPlayerDeleted( CGamePlayer *player )
  1327. {
  1328. CONSOLE_Print( "[GAME: " + m_GameName + "] deleting player [" + player->GetName( ) + "]: " + player->GetLeftReason( ) );
  1329. forward( new CFwdData( FWD_GAME_SLOT_REMOVE, player->GetName( ), m_GameID ) );
  1330. forward( new CFwdData( FWD_GAME_STATS_REMOVE, player->GetName( ), m_GameID ) );
  1331. forward( new CFwdData( FWD_GAME_DOTA_DB_REMOVE, player->GetName( ), m_GameID ) );
  1332. // remove any queued spoofcheck messages for this player
  1333. if( player->GetWhoisSent( ) && !player->GetJoinedRealm( ).empty( ) && player->GetSpoofedRealm( ).empty( ) )
  1334. {
  1335. for( vector<CBNET *> :: iterator i = m_GHost->m_BNETs.begin( ); i != m_GHost->m_BNETs.end( ); ++i )
  1336. {
  1337. if( (*i)->GetServer( ) == player->GetJoinedRealm( ) )
  1338. {
  1339. // hackhack: there must be a better way to do this
  1340. if( (*i)->GetPasswordHashType( ) == "pvpgn" )
  1341. (*i)->UnqueueChatCommand( "/whereis " + player->GetName( ) );
  1342. else
  1343. (*i)->UnqueueChatCommand( "/whois " + player->GetName( ) );
  1344. (*i)->UnqueueChatCommand( "/w " + player->GetName( ) + " " + m_GHost->m_Language->SpoofCheckByReplying( ) );
  1345. }
  1346. }
  1347. }
  1348. m_LastPlayerLeaveTicks = GetTicks( );
  1349. // in some cases we're forced to send the left message early so don't send it again
  1350. if( player->GetLeftMessageSent( ) )
  1351. return;
  1352. if( m_GameLoaded )
  1353. SendAllChat( player->GetName( ) + " " + player->GetLeftReason( ) + "." );
  1354. if( player->GetLagging( ) )
  1355. SendAll( m_Protocol->SEND_W3GS_STOP_LAG( player ) );
  1356. // autosave
  1357. if( m_GameLoaded && player->GetLeftCode( ) == PLAYERLEAVE_DISCONNECT && m_AutoSave )
  1358. {
  1359. string SaveGameName = UTIL_FileSafeName( "GHost++ AutoSave " + m_GameName + " (" + player->GetName( ) + ").w3z" );
  1360. CONSOLE_Print( "[GAME: " + m_GameName + "] auto saving [" + SaveGameName + "] before player drop, shortened send interval = " + UTIL_ToString( GetTicks( ) - m_LastActionSentTicks ) );
  1361. BYTEARRAY CRC;
  1362. BYTEARRAY Action;
  1363. Action.push_back( 6 );
  1364. UTIL_AppendByteArray( Action, SaveGameName );
  1365. m_Actions.push( new CIncomingAction( player->GetPID( ), CRC, Action ) );
  1366. // todotodo: with the new latency system there needs to be a way to send a 0-time action
  1367. SendAllActions( );
  1368. }
  1369. if( m_GameLoading && m_LoadInGame )
  1370. {
  1371. // we must buffer player leave messages when using "load in game" to prevent desyncs
  1372. // this ensures the player leave messages are correctly interleaved with the empty updates sent to each player
  1373. for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); ++i )
  1374. {
  1375. if( (*i)->GetFinishedLoading( ) )
  1376. {
  1377. if( !player->GetFinishedLoading( ) )
  1378. Send( *i, m_Protocol->SEND_W3GS_STOP_LAG( player ) );
  1379. Send( *i, m_Protocol->SEND_W3GS_PLAYERLEAVE_OTHERS( player->GetPID( ), player->GetLeftCode( ) ) );
  1380. }
  1381. else
  1382. (*i)->AddLoadInGameData( m_Protocol->SEND_W3GS_PLAYERLEAVE_OTHERS( player->GetPID( ), player->GetLeftCode( ) ) );
  1383. }
  1384. }
  1385. else
  1386. {
  1387. // tell everyone about the player leaving
  1388. SendAll( m_Protocol->SEND_W3GS_PLAYERLEAVE_OTHERS( player->GetPID( ), player->GetLeftCode( ) ) );
  1389. }
  1390. // set the replay's host PID and name to the last player to leave the game
  1391. // this will get overwritten as each player leaves the game so it will eventually be set to the last player
  1392. if( m_Replay && ( m_GameLoading || m_GameLoaded ) )
  1393. {
  1394. m_Replay->SetHostPID( player->GetPID( ) );
  1395. m_Replay->SetHostName( player->GetName( ) );
  1396. // add leave message to replay
  1397. if( m_GameLoading && !m_LoadInGame )
  1398. m_Replay->AddLeaveGameDuringLoading( 1, player->GetPID( ), player->GetLeftCode( ) );
  1399. else
  1400. m_Replay->AddLeaveGame( 1, player->GetPID( ), player->GetLeftCode( ) );
  1401. }
  1402. // abort the countdown if there was one in progress and normal countdown is off
  1403. if ( !m_GHost->m_UseNormalCountDown && !m_UsingStart )
  1404. {
  1405. if( m_CountDownStarted && !m_GameLoading && !m_GameLoaded )
  1406. {
  1407. SendAllChat( m_GHost->m_Language->CountDownAborted( ) );
  1408. m_CountDownStarted = false;
  1409. }
  1410. }
  1411. // abort the votekick
  1412. if( !m_KickVotePlayer.empty( ) )
  1413. SendAllChat( m_GHost->m_Language->VoteKickCancelled( m_KickVotePlayer ) );
  1414. m_KickVotePlayer.clear( );
  1415. m_StartedKickVoteTime = 0;
  1416. vector<string> MapData;
  1417. MapData.push_back( "Players" ); MapData.push_back( UTIL_ToString( GetNumHumanPlayers( ) - 1 ) + "/" + UTIL_ToString( m_GameLoading || m_GameLoaded ? m_StartPlayers : m_Slots.size( ) ) );
  1418. forward( new CFwdData( FWD_GAME_MAP_INFO_UPDATE, MapData, m_GameID ) );
  1419. }
  1420. void CBaseGame :: EventPlayerDisconnectTimedOut( CGamePlayer *player )
  1421. {
  1422. if( player->GetGProxy( ) && m_GameLoaded )
  1423. {
  1424. if( !player->GetGProxyDisconnectNoticeSent( ) )
  1425. {
  1426. SendAllChat( player->GetName( ) + " " + m_GHost->m_Language->HasLostConnectionTimedOutGProxy( ) + "." );
  1427. player->SetGProxyDisconnectNoticeSent( true );
  1428. }
  1429. if( GetTime( ) - player->GetLastGProxyWaitNoticeSentTime( ) >= 20 )
  1430. {
  1431. uint32_t TimeRemaining = ( m_GProxyEmptyActions + 1 ) * 60 - ( GetTime( ) - m_StartedLaggingTime );
  1432. if( TimeRemaining > ( (uint32_t)m_GProxyEmptyActions + 1 ) * 60 )
  1433. TimeRemaining = ( m_GProxyEmptyActions + 1 ) * 60;
  1434. SendAllChat( player->GetPID( ), m_GHost->m_Language->WaitForReconnectSecondsRemain( UTIL_ToString( TimeRemaining ) ) );
  1435. player->SetLastGProxyWaitNoticeSentTime( GetTime( ) );
  1436. }
  1437. return;
  1438. }
  1439. // not only do we not do any timeouts if the game is lagging, we allow for an additional grace period of 10 seconds
  1440. // this is because Warcraft 3 stops sending packets during the lag screen
  1441. // so when the lag screen finishes we would immediately disconnect everyone if we didn't give them some extra time
  1442. if( GetTime( ) - m_LastLagScreenTime >= 10 )
  1443. {
  1444. player->SetDeleteMe( true );
  1445. player->SetLeftReason( m_GHost->m_Language->HasLostConnectionTimedOut( ) );
  1446. player->SetLeftCode( PLAYERLEAVE_DISCONNECT );
  1447. if( !m_GameLoading && !m_GameLoaded )
  1448. OpenSlot( GetSIDFromPID( player->GetPID( ) ), false );
  1449. }
  1450. }
  1451. void CBaseGame :: EventPlayerDisconnectPlayerError( CGamePlayer *player )
  1452. {
  1453. // at the time of this comment there's only one player error and that's when we receive a bad packet from the player
  1454. // since TCP has checks and balances for data corruption the chances of this are pretty slim
  1455. player->SetDeleteMe( true );
  1456. player->SetLeftReason( m_GHost->m_Language->HasLostConnectionPlayerError( player->GetErrorString( ) ) );
  1457. player->SetLeftCode( PLAYERLEAVE_DISCONNECT );
  1458. if( !m_GameLoading && !m_GameLoaded )
  1459. OpenSlot( GetSIDFromPID( player->GetPID( ) ), false );
  1460. }
  1461. void CBaseGame :: EventPlayerDisconnectSocketError( CGamePlayer *player )
  1462. {
  1463. if( player->GetGProxy( ) && m_GameLoaded )
  1464. {
  1465. if( !player->GetGProxyDisconnectNoticeSent( ) )
  1466. {
  1467. SendAllChat( player->GetName( ) + " " + m_GHost->m_Language->HasLostConnectionSocketErrorGProxy( player->GetSocket( )->GetErrorString( ) ) + "." );
  1468. player->SetGProxyDisconnectNoticeSent( true );
  1469. }
  1470. if( GetTime( ) - player->GetLastGProxyWaitNoticeSentTime( ) >= 20 )
  1471. {
  1472. uint32_t TimeRemaining = ( m_GProxyEmptyActions + 1 ) * 60 - ( GetTime( ) - m_StartedLaggingTime );
  1473. if( TimeRemaining > ( (uint32_t)m_GProxyEmptyActions + 1 ) * 60 )
  1474. TimeRemaining = ( m_GProxyEmptyActions + 1 ) * 60;
  1475. SendAllChat( player->GetPID( ), m_GHost->m_Language->WaitForReconnectSecondsRemain( UTIL_ToString( TimeRemaining ) ) );
  1476. player->SetLastGProxyWaitNoticeSentTime( GetTime( ) );
  1477. }
  1478. return;
  1479. }
  1480. player->SetDeleteMe( true );
  1481. player->SetLeftReason( m_GHost->m_Language->HasLostConnectionSocketError( player->GetSocket( )->GetErrorString( ) ) );
  1482. player->SetLeftCode( PLAYERLEAVE_DISCONNECT );
  1483. if( !m_GameLoading && !m_GameLoaded )
  1484. OpenSlot( GetSIDFromPID( player->GetPID( ) ), false );
  1485. }
  1486. void CBaseGame :: EventPlayerDisconnectConnectionClosed( CGamePlayer *player )
  1487. {
  1488. if( player->GetGProxy( ) && m_GameLoaded )
  1489. {
  1490. if( !player->GetGProxyDisconnectNoticeSent( ) )
  1491. {
  1492. SendAllChat( player->GetName( ) + " " + m_GHost->m_Language->HasLostConnectionClosedByRemoteHostGProxy( ) + "." );
  1493. player->SetGProxyDisconnectNoticeSent( true );
  1494. }
  1495. if( GetTime( ) - player->GetLastGProxyWaitNoticeSentTime( ) >= 20 )
  1496. {
  1497. uint32_t TimeRemaining = ( m_GProxyEmptyActions + 1 ) * 60 - ( GetTime( ) - m_StartedLaggingTime );
  1498. if( TimeRemaining > ( (uint32_t)m_GProxyEmptyActions + 1 ) * 60 )
  1499. TimeRemaining = ( m_GProxyEmptyActions + 1 ) * 60;
  1500. SendAllChat( player->GetPID( ), m_GHost->m_Language->WaitForReconnectSecondsRemain( UTIL_ToString( TimeRemaining ) ) );
  1501. player->SetLastGProxyWaitNoticeSentTime( GetTime( ) );
  1502. }
  1503. return;
  1504. }
  1505. player->SetDeleteMe( true );
  1506. player->SetLeftReason( m_GHost->m_Language->HasLostConnectionClosedByRemoteHost( ) );
  1507. player->SetLeftCode( PLAYERLEAVE_DISCONNECT );
  1508. if( !m_GameLoading && !m_GameLoaded )
  1509. OpenSlot( GetSIDFromPID( player->GetPID( ) ), false );
  1510. }
  1511. void CBaseGame :: EventPlayerJoined( CPotentialPlayer *potential, CIncomingJoinPlayer *joinPlayer )
  1512. {
  1513. if ( m_GHost->m_UseNormalCountDown && m_CountDownStarted )
  1514. {
  1515. potential->GetSocket( )->PutBytes( m_Protocol->SEND_W3GS_REJECTJOIN( REJECTJOIN_FULL ) );
  1516. potential->SetDeleteMe( true );
  1517. return;
  1518. }
  1519. // check if the new player's name is empty or too long
  1520. if( joinPlayer->GetName( ).empty( ) || joinPlayer->GetName( ).size( ) > 15 )
  1521. {
  1522. CONSOLE_Print( "[GAME: " + m_GameName + "] player [" + joinPlayer->GetName( ) + "|" + potential->GetExternalIPString( ) + "] is trying to join the game with an invalid name of length " + UTIL_ToString( joinPlayer->GetName( ).size( ) ) );
  1523. potential->Send( m_Protocol->SEND_W3GS_REJECTJOIN( REJECTJOIN_FULL ) );
  1524. potential->SetDeleteMe( true );
  1525. return;
  1526. }
  1527. // check if the new player's name is the same as the virtual host name
  1528. if( joinPlayer->GetName( ) == m_VirtualHostName )
  1529. {
  1530. CONSOLE_Print( "[GAME: " + m_GameName + "] player [" + joinPlayer->GetName( ) + "|" + potential->GetExternalIPString( ) + "] is trying to join the game with the virtual host name" );
  1531. potential->Send( m_Protocol->SEND_W3GS_REJECTJOIN( REJECTJOIN_FULL ) );
  1532. potential->SetDeleteMe( true );
  1533. return;
  1534. }
  1535. // check if the new player's name is already taken
  1536. if( GetPlayerFromName( joinPlayer->GetName( ), false ) )
  1537. {
  1538. CONSOLE_Print( "[GAME: " + m_GameName + "] player [" + joinPlayer->GetName( ) + "|" + potential->GetExternalIPString( ) + "] is trying to join the game but that name is already taken" );
  1539. // SendAllChat( m_GHost->m_Language->TryingToJoinTheGameButTaken( joinPlayer->GetName( ) ) );
  1540. potential->Send( m_Protocol->SEND_W3GS_REJECTJOIN( REJECTJOIN_FULL ) );
  1541. potential->SetDeleteMe( true );
  1542. return;
  1543. }
  1544. // identify their joined realm
  1545. // this is only possible because when we send a game refresh via LAN or battle.net we encode an ID value in the 4 most significant bits of the host counter
  1546. // the client sends the host counter when it joins so we can extract the ID value here
  1547. // note: this is not a replacement for spoof checking since it doesn't verify the player's name and it can be spoofed anyway
  1548. uint32_t HostCounterID = joinPlayer->GetHostCounter( ) >> 28;
  1549. string JoinedRealm;
  1550. // we use an ID value of 0 to denote joining via LAN
  1551. if( HostCounterID == 0 )
  1552. {
  1553. // the player is pretending to join via LAN, which they might or might not be (i.e. it could be spoofed)
  1554. // however, we've been broadcasting a random entry key to the LAN
  1555. // if the player is really on the LAN they'll know the entry key, otherwise they won't
  1556. // or they're very lucky since it's a 32 bit number
  1557. if( m_GHost->m_EntryKeyRequired == 1 && joinPlayer->GetEntryKey( ) != m_EntryKey )
  1558. {
  1559. // oops!
  1560. CONSOLE_Print( "[GAME: " + m_GameName + "] player [" + joinPlayer->GetName( ) + "|" + potential->GetExternalIPString( ) + "] is trying to join the game over LAN but used an incorrect entry key" );
  1561. potential->Send( m_Protocol->SEND_W3GS_REJECTJOIN( REJECTJOIN_WRONGPASSWORD ) );
  1562. potential->SetDeleteMe( true );
  1563. return;
  1564. }
  1565. }
  1566. else
  1567. {
  1568. for( vector<CBNET *> :: iterator i = m_GHost->m_BNETs.begin( ); i != m_GHost->m_BNETs.end( ); ++i )
  1569. {
  1570. if( (*i)->GetHostCounterID( ) == HostCounterID )
  1571. JoinedRealm = (*i)->GetServer( );
  1572. }
  1573. }
  1574. // check if the new player's name is banned but only if bot_banmethod is not 0
  1575. // this is because if bot_banmethod is 0 and we announce the ban here it's possible for the player to be rejected later because the game is full
  1576. // this would allow the player to spam the chat by attempting to join the game multiple times in a row
  1577. if( m_GHost->m_BanMethod != 0 )
  1578. {
  1579. for( vector<CBNET *> :: iterator i = m_GHost->m_BNETs.begin( ); i != m_GHost->m_BNETs.end( ); ++i )
  1580. {
  1581. if( (*i)->GetServer( ) == JoinedRealm )
  1582. {
  1583. CDBBan *Ban = (*i)->IsBannedName( joinPlayer->GetName( ) );
  1584. if( Ban )
  1585. {
  1586. if( m_GHost->m_BanMethod == 1 || m_GHost->m_BanMethod == 3 )
  1587. {
  1588. CONSOLE_Print( "[GAME: " + m_GameName + "] player [" + joinPlayer->GetName( ) + "|" + potential->GetExternalIPString( ) + "] is trying to join the game but is banned by name" );
  1589. if( m_IgnoredNames.find( joinPlayer->GetName( ) ) == m_IgnoredNames.end( ) )
  1590. {
  1591. SendAllChat( m_GHost->m_Language->TryingToJoinTheGameButBannedByName( joinPlayer->GetName( ) ) );
  1592. SendAllChat( m_GHost->m_Language->UserWasBannedOnByBecause( Ban->GetServer( ), Ban->GetName( ), Ban->GetDate( ), Ban->GetAdmin( ), Ban->GetReason( ) ) );
  1593. m_IgnoredNames.insert( joinPlayer->GetName( ) );
  1594. }
  1595. // let banned players "join" the game with an arbitrary PID then immediately close the connection
  1596. // this causes them to be kicked back to the chat channel on battle.net
  1597. vector<CGameSlot> Slots = m_Map->GetSlots( );
  1598. potential->Send( m_Protocol->SEND_W3GS_SLOTINFOJOIN( 1, potential->GetSocket( )->GetPort( ), potential->GetExternalIP( ), Slots, 0, m_Map->GetMapLayoutStyle( ), m_Map->GetMapNumPlayers( ) ) );
  1599. potential->SetDeleteMe( true );
  1600. return;
  1601. }
  1602. break;
  1603. }
  1604. }
  1605. CDBBan *Ban = (*i)->IsBannedIP( potential->GetExternalIPString( ) );
  1606. if( Ban )
  1607. {
  1608. if( m_GHost->m_BanMethod == 2 || m_GHost->m_BanMethod == 3 )
  1609. {
  1610. CONSOLE_Print( "[GAME: " + m_GameName + "] player [" + joinPlayer->GetName( ) + "|" + potential->GetExternalIPString( ) + "] is trying to join the game but is banned by IP address" );
  1611. if( m_IgnoredNames.find( joinPlayer->GetName( ) ) == m_IgnoredNames.end( ) )
  1612. {
  1613. SendAllChat( m_GHost->m_Language->TryingToJoinTheGameButBannedByIP( joinPlayer->GetName( ), potential->GetExternalIPString( ), Ban->GetName( ) ) );
  1614. SendAllChat( m_GHost->m_Language->UserWasBannedOnByBecause( Ban->GetServer( ), Ban->GetName( ), Ban->GetDate( ), Ban->GetAdmin( ), Ban->GetReason( ) ) );
  1615. m_IgnoredNames.insert( joinPlayer->GetName( ) );
  1616. }
  1617. // let banned players "join" the game with an arbitrary PID then immediately close the connection
  1618. // this causes them to be kicked back to the chat channel on battle.net
  1619. vector<CGameSlot> Slots = m_Map->GetSlots( );
  1620. potential->Send( m_Protocol->SEND_W3GS_SLOTINFOJOIN( 1, potential->GetSocket( )->GetPort( ), potential->GetExternalIP( ), Slots, 0, m_Map->GetMapLayoutStyle( ), m_Map->GetMapNumPlayers( ) ) );
  1621. potential->SetDeleteMe( true );
  1622. return;
  1623. }
  1624. break;
  1625. }
  1626. }
  1627. }
  1628. if( m_MatchMaking && m_AutoStartPlayers != 0 && !m_Map->GetMapMatchMakingCategory( ).empty( ) && m_Map->GetMapOptions( ) & MAPOPT_FIXEDPLAYERSETTINGS )
  1629. {
  1630. // matchmaking is enabled
  1631. // start a database query to determine the player's score
  1632. // when the query is complete we will call EventPlayerJoinedWithScore
  1633. m_ScoreChecks.push_back( m_GHost->m_DB->ThreadedScoreCheck( m_Map->GetMapMatchMakingCategory( ), joinPlayer->GetName( ), JoinedRealm ) );
  1634. return;
  1635. }
  1636. // [FROMENFORCER]
  1637. //Make sure from checking is enabled and config values are clean
  1638. if(!m_GHost->m_ApprovedCountries.empty( ) || m_GHost->m_ApprovedCountries.length() % 2 != 0)
  1639. {
  1640. int num;
  1641. vector<string> ApprovedLocations;
  1642. string PlayerLocation;
  1643. bool playerIsApproved;
  1644. if(m_GHost->m_ApprovedCountries.length() == 2)
  1645. num = 1;
  1646. else
  1647. num = m_GHost->m_ApprovedCountries.length() / 2;
  1648. //Loop through approved countries and construct an array
  1649. for(int i = 0; i < m_GHost->m_ApprovedCountries.length(); i += 2)
  1650. ApprovedLocations.push_back(m_GHost->m_ApprovedCountries.substr(i,2));
  1651. //Get their location
  1652. PlayerLocation = m_GHost->m_DBLocal->FromCheck( UTIL_ByteArrayToUInt32( potential->GetExternalIP( ), true ));
  1653. //Kick if not from an allowed location, ignore if their location is approved or cannot be found "??"
  1654. playerIsApproved = false;
  1655. //Try to make a match
  1656. for(int x = 0; x < num; x++)
  1657. {
  1658. //Approve the player if their country is approved
  1659. if(PlayerLocation == ApprovedLocations[x])
  1660. playerIsApproved = true;
  1661. }
  1662. if(!playerIsApproved && PlayerLocation != "??")
  1663. {
  1664. //Player location has been found and is invalid, deny them entry
  1665. CONSOLE_Print("[FROMENFORCER] Player [" + joinPlayer->GetName() + "] tried to join the game but is not from an approved location (" + PlayerLocation + ")");
  1666. SendAllChat("[" + joinPlayer->GetName() + "] tried to join the game but is not from an approved location (" + PlayerLocation + ")");
  1667. potential->SetDeleteMe(true);
  1668. return;
  1669. }
  1670. }
  1671. // check if the player is an admin or root admin on any connected realm for determining reserved status
  1672. // we can't just use the spoof checked realm like in EventPlayerBotCommand because the player hasn't spoof checked yet
  1673. bool AnyAdminCheck = false;
  1674. for( vector<CBNET *> :: iterator i = m_GHost->m_BNETs.begin( ); i != m_GHost->m_BNETs.end( ); ++i )
  1675. {
  1676. if( (*i)->IsAdmin( joinPlayer->GetName( ) ) || (*i)->IsRootAdmin( joinPlayer->GetName( ) ) || ( (*i)->IsLANRootAdmin( joinPlayer->GetName( ) ) && m_GHost->m_GetLANRootAdmins == 1 ) )
  1677. {
  1678. AnyAdminCheck = true;
  1679. break;
  1680. }
  1681. }
  1682. bool Reserved = IsReserved( joinPlayer->GetName( ) ) || ( m_GHost->m_ReserveAdmins && AnyAdminCheck ) || IsOwner( joinPlayer->GetName( ) );
  1683. // try to find a slot
  1684. unsigned char SID = 255;
  1685. unsigned char EnforcePID = 255;
  1686. unsigned char EnforceSID = 0;
  1687. CGameSlot EnforceSlot( 255, 0, 0, 0, 0, 0, 0 );
  1688. if( m_SaveGame )
  1689. {
  1690. // in a saved game we enforce the player layout and the slot layout
  1691. // unfortunately we don't know how to extract the player layout from the saved game so we use the data from a replay instead
  1692. // the !enforcesg command defines the player layout by parsing a replay
  1693. for( vector<PIDPlayer> :: iterator i = m_EnforcePlayers.begin( ); i != m_EnforcePlayers.end( ); ++i )
  1694. {
  1695. if( (*i).second == joinPlayer->GetName( ) )
  1696. EnforcePID = (*i).first;
  1697. }
  1698. for( vector<CGameSlot> :: iterator i = m_EnforceSlots.begin( ); i != m_EnforceSlots.end( ); ++i )
  1699. {
  1700. if( (*i).GetPID( ) == EnforcePID )
  1701. {
  1702. EnforceSlot = *i;
  1703. break;
  1704. }
  1705. EnforceSID++;
  1706. }
  1707. if( EnforcePID == 255 || EnforceSlot.GetPID( ) == 255 || EnforceSID >= m_Slots.size( ) )
  1708. {
  1709. CONSOLE_Print( "[GAME: " + m_GameName + "] player [" + joinPlayer->GetName( ) + "|" + potential->GetExternalIPString( ) + "] is trying to join the game but isn't in the enforced list" );
  1710. potential->Send( m_Protocol->SEND_W3GS_REJECTJOIN( REJECTJOIN_FULL ) );
  1711. potential->SetDeleteMe( true );
  1712. return;
  1713. }
  1714. SID = EnforceSID;
  1715. }
  1716. else
  1717. {
  1718. // try to find an empty slot
  1719. SID = GetEmptySlot( false );
  1720. if( SID == 255 && Reserved )
  1721. {
  1722. // a reserved player is trying to join the game but it's full, try to find a reserved slot
  1723. SID = GetEmptySlot( true );
  1724. if( SID != 255 )
  1725. {
  1726. CGamePlayer *KickedPlayer = GetPlayerFromSID( SID );
  1727. if( KickedPlayer )
  1728. {
  1729. KickedPlayer->SetDeleteMe( true );
  1730. KickedPlayer->SetLeftReason( m_GHost->m_Language->WasKickedForReservedPlayer( joinPlayer->GetName( ) ) );
  1731. KickedPlayer->SetLeftCode( PLAYERLEAVE_LOBBY );
  1732. // send a playerleave message immediately since it won't normally get sent until the player is deleted which is after we send a playerjoin message
  1733. // we don't need to call OpenSlot here because we're about to overwrite the slot data anyway
  1734. SendAll( m_Protocol->SEND_W3GS_PLAYERLEAVE_OTHERS( KickedPlayer->GetPID( ), KickedPlayer->GetLeftCode( ) ) );
  1735. KickedPlayer->SetLeftMessageSent( true );
  1736. }
  1737. }
  1738. }
  1739. if( SID == 255 && IsOwner( joinPlayer->GetName( ) ) )
  1740. {
  1741. // the owner player is trying to join the game but it's full and we couldn't even find a reserved slot, kick the player in the lowest numbered slot
  1742. // updated this to try to find a player slot so that we don't end up kicking a computer
  1743. SID = 0;
  1744. for( unsigned char i = 0; i < m_Slots.size( ); ++i )
  1745. {
  1746. if( m_Slots[i].GetSlotStatus( ) == SLOTSTATUS_OCCUPIED && m_Slots[i].GetComputer( ) == 0 )
  1747. {
  1748. SID = i;
  1749. break;
  1750. }
  1751. }
  1752. CGamePlayer *KickedPlayer = GetPlayerFromSID( SID );
  1753. if( KickedPlayer )
  1754. {
  1755. KickedPlayer->SetDeleteMe( true );
  1756. KickedPlayer->SetLeftReason( m_GHost->m_Language->WasKickedForOwnerPlayer( joinPlayer->GetName( ) ) );
  1757. KickedPlayer->SetLeftCode( PLAYERLEAVE_LOBBY );
  1758. // send a playerleave message immediately since it won't normally get sent until the player is deleted which is after we send a playerjoin message
  1759. // we don't need to call OpenSlot here because we're about to overwrite the slot data anyway
  1760. SendAll( m_Protocol->SEND_W3GS_PLAYERLEAVE_OTHERS( KickedPlayer->GetPID( ), KickedPlayer->GetLeftCode( ) ) );
  1761. KickedPlayer->SetLeftMessageSent( true );
  1762. }
  1763. }
  1764. }
  1765. if( SID >= m_Slots.size( ) )
  1766. {
  1767. potential->Send( m_Protocol->SEND_W3GS_REJECTJOIN( REJECTJOIN_FULL ) );
  1768. potential->SetDeleteMe( true );
  1769. return;
  1770. }
  1771. // check if the new player's name is banned but only if bot_banmethod is 0
  1772. // this is because if bot_banmethod is 0 we need to wait to announce the ban until now because they could have been rejected because the game was full
  1773. // this would have allowed the player to spam the chat by attempting to join the game multiple times in a row
  1774. if( m_GHost->m_BanMethod == 0 || m_GHost->m_BanMethod == 1 )
  1775. {
  1776. for( vector<CBNET *> :: iterator i = m_GHost->m_BNETs.begin( ); i != m_GHost->m_BNETs.end( ); ++i )
  1777. {
  1778. if( (*i)->GetServer( ) == JoinedRealm && m_GHost->m_BanMethod == 0 )
  1779. {
  1780. CDBBan *Ban = (*i)->IsBannedName( joinPlayer->GetName( ) );
  1781. if( Ban )
  1782. {
  1783. CONSOLE_Print( "[GAME: " + m_GameName + "] player [" + joinPlayer->GetName( ) + "|" + potential->GetExternalIPString( ) + "] is using a banned name" );
  1784. SendAllChat( m_GHost->m_Language->HasBannedName( joinPlayer->GetName( ) ) );
  1785. SendAllChat( m_GHost->m_Language->UserWasBannedOnByBecause( Ban->GetServer( ), Ban->GetName( ), Ban->GetDate( ), Ban->GetAdmin( ), Ban->GetReason( ) ) );
  1786. break;
  1787. }
  1788. }
  1789. CDBBan *Ban = (*i)->IsBannedIP( potential->GetExternalIPString( ) );
  1790. if( Ban )
  1791. {
  1792. CONSOLE_Print( "[GAME: " + m_GameName + "] player [" + joinPlayer->GetName( ) + "|" + potential->GetExternalIPString( ) + "] is using a banned IP address" );
  1793. SendAllChat( m_GHost->m_Language->HasBannedIP( joinPlayer->GetName( ), potential->GetExternalIPString( ), Ban->GetName( ) ) );
  1794. SendAllChat( m_GHost->m_Language->UserWasBannedOnByBecause( Ban->GetServer( ), Ban->GetName( ), Ban->GetDate( ), Ban->GetAdmin( ), Ban->GetReason( ) ) );
  1795. break;
  1796. }
  1797. }
  1798. }
  1799. // we have a slot for the new player
  1800. // make room for them by deleting the virtual host player if we have to
  1801. if( GetNumPlayers( ) >= 11 || EnforcePID == m_VirtualHostPID )
  1802. DeleteVirtualHost( );
  1803. // turning the CPotentialPlayer into a CGamePlayer is a bit of a pain because we have to be careful not to close the socket
  1804. // this problem is solved by setting the socket to NULL before deletion and handling the NULL case in the destructor
  1805. // we also have to be careful to not modify the m_Potentials vector since we're currently looping through it
  1806. CONSOLE_Print( "[GAME: " + m_GameName + "] player [" + joinPlayer->GetName( ) + "|" + potential->GetExternalIPString( ) + "] joined the game" );
  1807. CGamePlayer *Player = new CGamePlayer( potential, m_SaveGame ? EnforcePID : GetNewPID( ), JoinedRealm, joinPlayer->GetName( ), joinPlayer->GetInternalIP( ), Reserved );
  1808. // consider LAN players to have already spoof checked since they can't
  1809. // since so many people have trouble with this feature we now use the JoinedRealm to determine LAN status
  1810. if( JoinedRealm.empty( ) )
  1811. Player->SetSpoofed( true );
  1812. Player->SetWhoisShouldBeSent( m_GHost->m_SpoofChecks == 1 || ( m_GHost->m_SpoofChecks == 2 && AnyAdminCheck ) );
  1813. m_Players.push_back( Player );
  1814. potential->SetSocket( NULL );
  1815. potential->SetDeleteMe( true );
  1816. if( m_SaveGame )
  1817. m_Slots[SID] = EnforceSlot;
  1818. else
  1819. {
  1820. if( m_Map->GetMapOptions( ) & MAPOPT_CUSTOMFORCES )
  1821. m_Slots[SID] = CGameSlot( Player->GetPID( ), 255, SLOTSTATUS_OCCUPIED, 0, m_Slots[SID].GetTeam( ), m_Slots[SID].GetColour( ), m_Slots[SID].GetRace( ) );
  1822. else
  1823. {
  1824. if( m_Map->GetMapFlags( ) & MAPFLAG_RANDOMRACES )
  1825. m_Slots[SID] = CGameSlot( Player->GetPID( ), 255, SLOTSTATUS_OCCUPIED, 0, 12, 12, SLOTRACE_RANDOM );
  1826. else
  1827. m_Slots[SID] = CGameSlot( Player->GetPID( ), 255, SLOTSTATUS_OCCUPIED, 0, 12, 12, SLOTRACE_RANDOM | SLOTRACE_SELECTABLE );
  1828. // try to pick a team and colour
  1829. // make sure there aren't too many other players already
  1830. unsigned char NumOtherPlayers = 0;
  1831. for( unsigned char i = 0; i < m_Slots.size( ); ++i )
  1832. {
  1833. if( m_Slots[i].GetSlotStatus( ) == SLOTSTATUS_OCCUPIED && m_Slots[i].GetTeam( ) != 12 )
  1834. NumOtherPlayers++;
  1835. }
  1836. if( NumOtherPlayers < m_Map->GetMapNumPlayers( ) )
  1837. {
  1838. if( SID < m_Map->GetMapNumPlayers( ) )
  1839. m_Slots[SID].SetTeam( SID );
  1840. else
  1841. m_Slots[SID].SetTeam( 0 );
  1842. m_Slots[SID].SetColour( GetNewColour( ) );
  1843. }
  1844. }
  1845. }
  1846. // send slot info to the new player
  1847. // the SLOTINFOJOIN packet also tells the client their assigned PID and that the join was successful
  1848. Player->Send( m_Protocol->SEND_W3GS_SLOTINFOJOIN( Player->GetPID( ), Player->GetSocket( )->GetPort( ), Player->GetExternalIP( ), m_Slots, m_RandomSeed, m_Map->GetMapLayoutStyle( ), m_Map->GetMapNumPlayers( ) ) );
  1849. // send virtual host info and fake player info (if present) to the new player
  1850. SendVirtualHostPlayerInfo( Player );
  1851. SendFakePlayerInfo( Player );
  1852. BYTEARRAY BlankIP;
  1853. BlankIP.push_back( 0 );
  1854. BlankIP.push_back( 0 );
  1855. BlankIP.push_back( 0 );
  1856. BlankIP.push_back( 0 );
  1857. for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); ++i )
  1858. {
  1859. if( !(*i)->GetLeftMessageSent( ) && *i != Player )
  1860. {
  1861. // send info about the new player to every other player
  1862. if( (*i)->GetSocket( ) )
  1863. {
  1864. if( m_GHost->m_HideIPAddresses )
  1865. (*i)->Send( m_Protocol->SEND_W3GS_PLAYERINFO( Player->GetPID( ), Player->GetName( ), BlankIP, BlankIP ) );
  1866. else
  1867. (*i)->Send( m_Protocol->SEND_W3GS_PLAYERINFO( Player->GetPID( ), Player->GetName( ), Player->GetExternalIP( ), Player->GetInternalIP( ) ) );
  1868. }
  1869. // send info about every other player to the new player
  1870. if( m_GHost->m_HideIPAddresses )
  1871. Player->Send( m_Protocol->SEND_W3GS_PLAYERINFO( (*i)->GetPID( ), (*i)->GetName( ), BlankIP, BlankIP ) );
  1872. else
  1873. Player->Send( m_Protocol->SEND_W3GS_PLAYERINFO( (*i)->GetPID( ), (*i)->GetName( ), (*i)->GetExternalIP( ), (*i)->GetInternalIP( ) ) );
  1874. }
  1875. }
  1876. // send a map check packet to the new player
  1877. Player->Send( m_Protocol->SEND_W3GS_MAPCHECK( m_Map->GetMapPath( ), m_Map->GetMapSize( ), m_Map->GetMapInfo( ), m_Map->GetMapCRC( ), m_Map->GetMapSHA1( ) ) );
  1878. // send slot info to everyone, so the new player gets this info twice but everyone else still needs to know the new slot layout
  1879. SendAllSlotInfo( );
  1880. // send a welcome message
  1881. SendWelcomeMessage( Player );
  1882. // if spoof checks are required and we won't automatically spoof check this player then tell them how to spoof check
  1883. // e.g. if automatic spoof checks are disabled, or if automatic spoof checks are done on admins only and this player isn't an admin
  1884. if( m_GHost->m_RequireSpoofChecks && !Player->GetWhoisShouldBeSent( ) )
  1885. {
  1886. for( vector<CBNET *> :: iterator i = m_GHost->m_BNETs.begin( ); i != m_GHost->m_BNETs.end( ); ++i )
  1887. {
  1888. // note: the following (commented out) line of code will crash because calling GetUniqueName( ) twice will result in two different return values
  1889. // and unfortunately iterators are not valid if compared against different containers
  1890. // this comment shall serve as warning to not make this mistake again since it has now been made twice before in GHost++
  1891. // string( (*i)->GetUniqueName( ).begin( ), (*i)->GetUniqueName( ).end( ) )
  1892. BYTEARRAY UniqueName = (*i)->GetUniqueName( );
  1893. if( (*i)->GetServer( ) == JoinedRealm )
  1894. SendChat( Player, m_GHost->m_Language->SpoofCheckByWhispering( string( UniqueName.begin( ), UniqueName.end( ) ) ) );
  1895. }
  1896. }
  1897. // check for multiple IP usage
  1898. if( m_GHost->m_CheckMultipleIPUsage )
  1899. {
  1900. string Others;
  1901. for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); ++i )
  1902. {
  1903. if( Player != *i && Player->GetExternalIPString( ) == (*i)->GetExternalIPString( ) )
  1904. {
  1905. if( Others.empty( ) )
  1906. Others = (*i)->GetName( );
  1907. else
  1908. Others += ", " + (*i)->GetName( );
  1909. }
  1910. }
  1911. if( !Others.empty( ) )
  1912. SendAllChat( m_GHost->m_Language->MultipleIPAddressUsageDetected( joinPlayer->GetName( ), Others ) );
  1913. }
  1914. // abort the countdown if there was one in progress and normal countdown is off
  1915. if ( !m_GHost->m_UseNormalCountDown )
  1916. {
  1917. if( m_CountDownStarted && !m_GameLoading && !m_GameLoaded )
  1918. {
  1919. SendAllChat( m_GHost->m_Language->CountDownAborted( ) );
  1920. m_CountDownStarted = false;
  1921. }
  1922. }
  1923. // auto lock the game
  1924. if( m_GHost->m_AutoLock && !m_Locked && IsOwner( joinPlayer->GetName( ) ) )
  1925. {
  1926. SendAllChat( m_GHost->m_Language->GameLocked( ) );
  1927. m_Locked = true;
  1928. }
  1929. vector<string> playerData;
  1930. playerData.push_back( Player->GetName( ) ); // name
  1931. playerData.push_back( UTIL_ToString( SID + 1 ) ); // slot
  1932. playerData.push_back( m_GHost->m_DBLocal->FromCheck( UTIL_ByteArrayToUInt32( Player->GetExternalIP( ), true ) ) ); // from
  1933. playerData.push_back( UTIL_ToString( Player->GetPing(true) ) + " ms" ); // ping
  1934. playerData.push_back( m_Slots[SID].GetRace( ) == 1 ? "HU" :
  1935. m_Slots[SID].GetRace( ) == 2 ? "ORC" :
  1936. m_Slots[SID].GetRace( ) == 4 ? "NE" :
  1937. m_Slots[SID].GetRace( ) == 8 ? "UD" : "??" ); // race
  1938. playerData.push_back( UTIL_ToString( m_Slots[SID].GetTeam( ) + 1 ) ); // team
  1939. playerData.push_back( ColourValueToString( m_Slots[SID].GetColour( ) ) ); // color
  1940. playerData.push_back( UTIL_ToString( m_Slots[SID].GetHandicap( ) ) ); // handicap
  1941. playerData.push_back( Player->GetGProxy( ) ? "on" : "off" ); // gproxy++
  1942. forward( new CFwdData( FWD_GAME_SLOT_ADD, playerData, m_GameID ) );
  1943. #ifdef ENABLE_UI_DB_STATS
  1944. CCallableGamePlayerSummaryCheck *stats = m_GHost->m_DB->ThreadedGamePlayerSummaryCheck( Player->GetName( ) );
  1945. if(stats->GetResult())
  1946. {
  1947. playerData.clear( );
  1948. playerData.push_back( Player->GetName( ) ); // name
  1949. playerData.push_back( UTIL_ToString( stats->GetResult( )->GetTotalGames( ) ) ); // total games
  1950. playerData.push_back( stats->GetResult( )->GetLastGameDateTime( ) ); // last gamedate
  1951. playerData.push_back( UTIL_ToString( stats->GetResult( )->GetAvgLeftPercent( ) ) ); // avg left%
  1952. playerData.push_back( UTIL_ToString( stats->GetResult( )->GetAvgDuration( ) ) + " s" ); // avg duration
  1953. playerData.push_back( UTIL_ToString( (float)stats->GetResult( )->GetAvgLoadingTime( ) / 1000, 2 ) + " s" ); // avg loading time
  1954. forward( new CFwdData( FWD_GAME_STATS_ADD, playerData, m_GameID ) );
  1955. }
  1956. delete stats;
  1957. CCallableDotAPlayerSummaryCheck *dotadb = m_GHost->m_DB->ThreadedDotAPlayerSummaryCheck( Player->GetName( ) );
  1958. if(dotadb->GetResult())
  1959. {
  1960. playerData.clear( );
  1961. playerData.push_back( Player->GetName( ) ); // Name
  1962. playerData.push_back( UTIL_ToString( dotadb->GetResult( )->GetTotalWins( ) ) + "/" +
  1963. UTIL_ToString( dotadb->GetResult( )->GetTotalLosses( ) ) ); // Wins/Losses
  1964. playerData.push_back( UTIL_ToString( dotadb->GetResult( )->GetTotalKills( ) ) + "/" +
  1965. UTIL_ToString( dotadb->GetResult( )->GetTotalDeaths( ) ) + "/" +
  1966. UTIL_ToString( dotadb->GetResult( )->GetTotalAssists( ) ) ); // K/D/A
  1967. playerData.push_back( UTIL_ToString( dotadb->GetResult( )->GetAvgKills( ), 2 ) + "/" +
  1968. UTIL_ToString( dotadb->GetResult( )->GetAvgDeaths( ), 2 ) + "/" +
  1969. UTIL_ToString( dotadb->GetResult( )->GetAvgAssists( ), 2 ) ); // AVG K/D/A
  1970. playerData.push_back( UTIL_ToString( dotadb->GetResult( )->GetTotalCreepKills( ) ) + "/" +
  1971. UTIL_ToString( dotadb->GetResult( )->GetTotalCreepDenies( ) ) + "/" +
  1972. UTIL_ToString( dotadb->GetResult( )->GetTotalNeutralKills( ) ) ); // CS K/D/N
  1973. playerData.push_back( UTIL_ToString( dotadb->GetResult( )->GetAvgCreepKills( ), 2 ) + "/" +
  1974. UTIL_ToString( dotadb->GetResult( )->GetAvgCreepDenies( ), 2 ) + "/" +
  1975. UTIL_ToString( dotadb->GetResult( )->GetAvgNeutralKills( ), 2 ) ); // AVG CS K/D/N
  1976. playerData.push_back( UTIL_ToString( dotadb->GetResult( )->GetTotalTowerKills( ) ) + "/" +
  1977. UTIL_ToString( dotadb->GetResult( )->GetTotalRaxKills( ) ) + "/" +
  1978. UTIL_ToString( dotadb->GetResult( )->GetTotalCourierKills( ) ) ); // T/R/C
  1979. forward( new CFwdData( FWD_GAME_DOTA_DB_ADD, playerData, m_GameID ) );
  1980. }
  1981. delete dotadb;
  1982. #endif
  1983. playerData.clear( );
  1984. playerData.push_back( "Players" ); playerData.push_back( UTIL_ToString( GetNumHumanPlayers( ) ) + "/" + UTIL_ToString( m_GameLoading || m_GameLoaded ? m_StartPlayers : m_Slots.size( ) ) );
  1985. forward( new CFwdData( FWD_GAME_MAP_INFO_UPDATE, playerData, m_GameID ) );
  1986. }
  1987. void CBaseGame :: EventPlayerJoinedWithScore( CPotentialPlayer *potential, CIncomingJoinPlayer *joinPlayer, double score )
  1988. {
  1989. if ( m_GHost->m_UseNormalCountDown && m_CountDownStarted )
  1990. {
  1991. potential->GetSocket( )->PutBytes( m_Protocol->SEND_W3GS_REJECTJOIN( REJECTJOIN_FULL ) );
  1992. potential->SetDeleteMe( true );
  1993. return;
  1994. }
  1995. // this function is only called when matchmaking is enabled
  1996. // EventPlayerJoined will be called first in all cases
  1997. // if matchmaking is enabled EventPlayerJoined will start a database query to retrieve the player's score and keep the connection open while we wait
  1998. // when the database query is complete EventPlayerJoinedWithScore will be called
  1999. // check if the new player's name is the same as the virtual host name
  2000. if( joinPlayer->GetName( ) == m_VirtualHostName )
  2001. {
  2002. CONSOLE_Print( "[GAME: " + m_GameName + "] player [" + joinPlayer->GetName( ) + "|" + potential->GetExternalIPString( ) + "] is trying to join the game with the virtual host name" );
  2003. potential->Send( m_Protocol->SEND_W3GS_REJECTJOIN( REJECTJOIN_FULL ) );
  2004. potential->SetDeleteMe( true );
  2005. return;
  2006. }
  2007. // check if the new player's name is already taken
  2008. if( GetPlayerFromName( joinPlayer->GetName( ), false ) )
  2009. {
  2010. CONSOLE_Print( "[GAME: " + m_GameName + "] player [" + joinPlayer->GetName( ) + "|" + potential->GetExternalIPString( ) + "] is trying to join the game but that name is already taken" );
  2011. // SendAllChat( m_GHost->m_Language->TryingToJoinTheGameButTaken( joinPlayer->GetName( ) ) );
  2012. potential->Send( m_Protocol->SEND_W3GS_REJECTJOIN( REJECTJOIN_FULL ) );
  2013. potential->SetDeleteMe( true );
  2014. return;
  2015. }
  2016. // check if the new player's score is within the limits
  2017. if( score > -99999.0 && ( score < m_MinimumScore || score > m_MaximumScore ) )
  2018. {
  2019. CONSOLE_Print( "[GAME: " + m_GameName + "] player [" + joinPlayer->GetName( ) + "|" + potential->GetExternalIPString( ) + "] is trying to join the game but has a rating [" + UTIL_ToString( score, 2 ) + "] outside the limits [" + UTIL_ToString( m_MinimumScore, 2 ) + "] to [" + UTIL_ToString( m_MaximumScore, 2 ) + "]" );
  2020. potential->Send( m_Protocol->SEND_W3GS_REJECTJOIN( REJECTJOIN_FULL ) );
  2021. potential->SetDeleteMe( true );
  2022. return;
  2023. }
  2024. // try to find an empty slot
  2025. unsigned char SID = GetEmptySlot( false );
  2026. // check if the player is an admin or root admin on any connected realm for determining reserved status
  2027. // we can't just use the spoof checked realm like in EventPlayerBotCommand because the player hasn't spoof checked yet
  2028. bool AnyAdminCheck = false;
  2029. for( vector<CBNET *> :: iterator i = m_GHost->m_BNETs.begin( ); i != m_GHost->m_BNETs.end( ); ++i )
  2030. {
  2031. if( (*i)->IsAdmin( joinPlayer->GetName( ) ) || (*i)->IsRootAdmin( joinPlayer->GetName( ) ) )
  2032. {
  2033. AnyAdminCheck = true;
  2034. break;
  2035. }
  2036. }
  2037. if( SID == 255 )
  2038. {
  2039. // no empty slot found, time to do some matchmaking!
  2040. // note: the database code uses a score of -100000 to denote "no score"
  2041. if( m_GHost->m_MatchMakingMethod == 0 )
  2042. {
  2043. // method 0: don't do any matchmaking
  2044. // that was easy!
  2045. }
  2046. else if( m_GHost->m_MatchMakingMethod == 1 )
  2047. {
  2048. // method 1: furthest score method
  2049. // calculate the average score of all players in the game
  2050. // then kick the player with the score furthest from that average (or a player without a score)
  2051. // this ensures that the players' scores will tend to converge as players join the game
  2052. double AverageScore = 0.0;
  2053. uint32_t PlayersScored = 0;
  2054. if( score > -99999.0 )
  2055. {
  2056. AverageScore = score;
  2057. PlayersScored = 1;
  2058. }
  2059. for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); ++i )
  2060. {
  2061. if( (*i)->GetScore( ) > -99999.0 )
  2062. {
  2063. AverageScore += (*i)->GetScore( );
  2064. PlayersScored++;
  2065. }
  2066. }
  2067. if( PlayersScored > 0 )
  2068. AverageScore /= PlayersScored;
  2069. // calculate the furthest player from the average
  2070. CGamePlayer *FurthestPlayer = NULL;
  2071. for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); ++i )
  2072. {
  2073. if( !FurthestPlayer || (*i)->GetScore( ) < -99999.0 || abs( (*i)->GetScore( ) - AverageScore ) > abs( FurthestPlayer->GetScore( ) - AverageScore ) )
  2074. FurthestPlayer = *i;
  2075. }
  2076. if( !FurthestPlayer )
  2077. {
  2078. // this should be impossible
  2079. CONSOLE_Print( "[GAME: " + m_GameName + "] player [" + joinPlayer->GetName( ) + "|" + potential->GetExternalIPString( ) + "] is trying to join the game but no furthest player was found (this should be impossible)" );
  2080. potential->Send( m_Protocol->SEND_W3GS_REJECTJOIN( REJECTJOIN_FULL ) );
  2081. potential->SetDeleteMe( true );
  2082. return;
  2083. }
  2084. // kick the new player if they have the furthest score
  2085. if( score < -99999.0 || abs( score - AverageScore ) > abs( FurthestPlayer->GetScore( ) - AverageScore ) )
  2086. {
  2087. if( score < -99999.0 )
  2088. CONSOLE_Print( "[GAME: " + m_GameName + "] player [" + joinPlayer->GetName( ) + "|" + potential->GetExternalIPString( ) + "] is trying to join the game but has the furthest rating [N/A] from the average [" + UTIL_ToString( AverageScore, 2 ) + "]" );
  2089. else
  2090. CONSOLE_Print( "[GAME: " + m_GameName + "] player [" + joinPlayer->GetName( ) + "|" + potential->GetExternalIPString( ) + "] is trying to join the game but has the furthest rating [" + UTIL_ToString( score, 2 ) + "] from the average [" + UTIL_ToString( AverageScore, 2 ) + "]" );
  2091. potential->Send( m_Protocol->SEND_W3GS_REJECTJOIN( REJECTJOIN_FULL ) );
  2092. potential->SetDeleteMe( true );
  2093. return;
  2094. }
  2095. // kick the furthest player
  2096. SID = GetSIDFromPID( FurthestPlayer->GetPID( ) );
  2097. FurthestPlayer->SetDeleteMe( true );
  2098. if( FurthestPlayer->GetScore( ) < -99999.0 )
  2099. FurthestPlayer->SetLeftReason( m_GHost->m_Language->WasKickedForHavingFurthestScore( "N/A", UTIL_ToString( AverageScore, 2 ) ) );
  2100. else
  2101. FurthestPlayer->SetLeftReason( m_GHost->m_Language->WasKickedForHavingFurthestScore( UTIL_ToString( FurthestPlayer->GetScore( ), 2 ), UTIL_ToString( AverageScore, 2 ) ) );
  2102. FurthestPlayer->SetLeftCode( PLAYERLEAVE_LOBBY );
  2103. // send a playerleave message immediately since it won't normally get sent until the player is deleted which is after we send a playerjoin message
  2104. // we don't need to call OpenSlot here because we're about to overwrite the slot data anyway
  2105. SendAll( m_Protocol->SEND_W3GS_PLAYERLEAVE_OTHERS( FurthestPlayer->GetPID( ), FurthestPlayer->GetLeftCode( ) ) );
  2106. FurthestPlayer->SetLeftMessageSent( true );
  2107. if( FurthestPlayer->GetScore( ) < -99999.0 )
  2108. SendAllChat( m_GHost->m_Language->PlayerWasKickedForFurthestScore( FurthestPlayer->GetName( ), "N/A", UTIL_ToString( AverageScore, 2 ) ) );
  2109. else
  2110. SendAllChat( m_GHost->m_Language->PlayerWasKickedForFurthestScore( FurthestPlayer->GetName( ), UTIL_ToString( FurthestPlayer->GetScore( ), 2 ), UTIL_ToString( AverageScore, 2 ) ) );
  2111. }
  2112. else if( m_GHost->m_MatchMakingMethod == 2 )
  2113. {
  2114. // method 2: lowest score method
  2115. // kick the player with the lowest score (or a player without a score)
  2116. CGamePlayer *LowestPlayer = NULL;
  2117. for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); ++i )
  2118. {
  2119. if( !LowestPlayer || (*i)->GetScore( ) < -99999.0 || (*i)->GetScore( ) < LowestPlayer->GetScore( ) )
  2120. LowestPlayer = *i;
  2121. }
  2122. if( !LowestPlayer )
  2123. {
  2124. // this should be impossible
  2125. CONSOLE_Print( "[GAME: " + m_GameName + "] player [" + joinPlayer->GetName( ) + "|" + potential->GetExternalIPString( ) + "] is trying to join the game but no lowest player was found (this should be impossible)" );
  2126. potential->Send( m_Protocol->SEND_W3GS_REJECTJOIN( REJECTJOIN_FULL ) );
  2127. potential->SetDeleteMe( true );
  2128. return;
  2129. }
  2130. // kick the new player if they have the lowest score
  2131. if( score < -99999.0 || score < LowestPlayer->GetScore( ) )
  2132. {
  2133. if( score < -99999.0 )
  2134. CONSOLE_Print( "[GAME: " + m_GameName + "] player [" + joinPlayer->GetName( ) + "|" + potential->GetExternalIPString( ) + "] is trying to join the game but has the lowest rating [N/A]" );
  2135. else
  2136. CONSOLE_Print( "[GAME: " + m_GameName + "] player [" + joinPlayer->GetName( ) + "|" + potential->GetExternalIPString( ) + "] is trying to join the game but has the lowest rating [" + UTIL_ToString( score, 2 ) + "]" );
  2137. potential->Send( m_Protocol->SEND_W3GS_REJECTJOIN( REJECTJOIN_FULL ) );
  2138. potential->SetDeleteMe( true );
  2139. return;
  2140. }
  2141. // kick the lowest player
  2142. SID = GetSIDFromPID( LowestPlayer->GetPID( ) );
  2143. LowestPlayer->SetDeleteMe( true );
  2144. if( LowestPlayer->GetScore( ) < -99999.0 )
  2145. LowestPlayer->SetLeftReason( m_GHost->m_Language->WasKickedForHavingLowestScore( "N/A" ) );
  2146. else
  2147. LowestPlayer->SetLeftReason( m_GHost->m_Language->WasKickedForHavingLowestScore( UTIL_ToString( LowestPlayer->GetScore( ), 2 ) ) );
  2148. LowestPlayer->SetLeftCode( PLAYERLEAVE_LOBBY );
  2149. // send a playerleave message immediately since it won't normally get sent until the player is deleted which is after we send a playerjoin message
  2150. // we don't need to call OpenSlot here because we're about to overwrite the slot data anyway
  2151. SendAll( m_Protocol->SEND_W3GS_PLAYERLEAVE_OTHERS( LowestPlayer->GetPID( ), LowestPlayer->GetLeftCode( ) ) );
  2152. LowestPlayer->SetLeftMessageSent( true );
  2153. if( LowestPlayer->GetScore( ) < -99999.0 )
  2154. SendAllChat( m_GHost->m_Language->PlayerWasKickedForLowestScore( LowestPlayer->GetName( ), "N/A" ) );
  2155. else
  2156. SendAllChat( m_GHost->m_Language->PlayerWasKickedForLowestScore( LowestPlayer->GetName( ), UTIL_ToString( LowestPlayer->GetScore( ), 2 ) ) );
  2157. }
  2158. }
  2159. if( SID >= m_Slots.size( ) )
  2160. {
  2161. potential->Send( m_Protocol->SEND_W3GS_REJECTJOIN( REJECTJOIN_FULL ) );
  2162. potential->SetDeleteMe( true );
  2163. return;
  2164. }
  2165. // we have a slot for the new player
  2166. // make room for them by deleting the virtual host player if we have to
  2167. if( GetNumPlayers( ) >= 11 )
  2168. DeleteVirtualHost( );
  2169. // identify their joined realm
  2170. // this is only possible because when we send a game refresh via LAN or battle.net we encode an ID value in the 4 most significant bits of the host counter
  2171. // the client sends the host counter when it joins so we can extract the ID value here
  2172. // note: this is not a replacement for spoof checking since it doesn't verify the player's name and it can be spoofed anyway
  2173. uint32_t HostCounterID = joinPlayer->GetHostCounter( ) >> 28;
  2174. string JoinedRealm;
  2175. // we use an ID value of 0 to denote joining via LAN
  2176. if( HostCounterID != 0 )
  2177. {
  2178. for( vector<CBNET *> :: iterator i = m_GHost->m_BNETs.begin( ); i != m_GHost->m_BNETs.end( ); ++i )
  2179. {
  2180. if( (*i)->GetHostCounterID( ) == HostCounterID )
  2181. JoinedRealm = (*i)->GetServer( );
  2182. }
  2183. }
  2184. // turning the CPotentialPlayer into a CGamePlayer is a bit of a pain because we have to be careful not to close the socket
  2185. // this problem is solved by setting the socket to NULL before deletion and handling the NULL case in the destructor
  2186. // we also have to be careful to not modify the m_Potentials vector since we're currently looping through it
  2187. CONSOLE_Print( "[GAME: " + m_GameName + "] player [" + joinPlayer->GetName( ) + "|" + potential->GetExternalIPString( ) + "] joined the game" );
  2188. CGamePlayer *Player = new CGamePlayer( potential, GetNewPID( ), JoinedRealm, joinPlayer->GetName( ), joinPlayer->GetInternalIP( ), false );
  2189. // consider LAN players to have already spoof checked since they can't
  2190. // since so many people have trouble with this feature we now use the JoinedRealm to determine LAN status
  2191. if( JoinedRealm.empty( ) )
  2192. Player->SetSpoofed( true );
  2193. Player->SetWhoisShouldBeSent( m_GHost->m_SpoofChecks == 1 || ( m_GHost->m_SpoofChecks == 2 && AnyAdminCheck ) );
  2194. Player->SetScore( score );
  2195. m_Players.push_back( Player );
  2196. potential->SetSocket( NULL );
  2197. potential->SetDeleteMe( true );
  2198. m_Slots[SID] = CGameSlot( Player->GetPID( ), 255, SLOTSTATUS_OCCUPIED, 0, m_Slots[SID].GetTeam( ), m_Slots[SID].GetColour( ), m_Slots[SID].GetRace( ) );
  2199. // send slot info to the new player
  2200. // the SLOTINFOJOIN packet also tells the client their assigned PID and that the join was successful
  2201. Player->Send( m_Protocol->SEND_W3GS_SLOTINFOJOIN( Player->GetPID( ), Player->GetSocket( )->GetPort( ), Player->GetExternalIP( ), m_Slots, m_RandomSeed, m_Map->GetMapLayoutStyle( ), m_Map->GetMapNumPlayers( ) ) );
  2202. // send virtual host info and fake player info (if present) to the new player
  2203. SendVirtualHostPlayerInfo( Player );
  2204. SendFakePlayerInfo( Player );
  2205. BYTEARRAY BlankIP;
  2206. BlankIP.push_back( 0 );
  2207. BlankIP.push_back( 0 );
  2208. BlankIP.push_back( 0 );
  2209. BlankIP.push_back( 0 );
  2210. for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); ++i )
  2211. {
  2212. if( !(*i)->GetLeftMessageSent( ) && *i != Player )
  2213. {
  2214. // send info about the new player to every other player
  2215. if( (*i)->GetSocket( ) )
  2216. {
  2217. if( m_GHost->m_HideIPAddresses )
  2218. (*i)->Send( m_Protocol->SEND_W3GS_PLAYERINFO( Player->GetPID( ), Player->GetName( ), BlankIP, BlankIP ) );
  2219. else
  2220. (*i)->Send( m_Protocol->SEND_W3GS_PLAYERINFO( Player->GetPID( ), Player->GetName( ), Player->GetExternalIP( ), Player->GetInternalIP( ) ) );
  2221. }
  2222. // send info about every other player to the new player
  2223. if( m_GHost->m_HideIPAddresses )
  2224. Player->Send( m_Protocol->SEND_W3GS_PLAYERINFO( (*i)->GetPID( ), (*i)->GetName( ), BlankIP, BlankIP ) );
  2225. else
  2226. Player->Send( m_Protocol->SEND_W3GS_PLAYERINFO( (*i)->GetPID( ), (*i)->GetName( ), (*i)->GetExternalIP( ), (*i)->GetInternalIP( ) ) );
  2227. }
  2228. }
  2229. // send a map check packet to the new player
  2230. Player->Send( m_Protocol->SEND_W3GS_MAPCHECK( m_Map->GetMapPath( ), m_Map->GetMapSize( ), m_Map->GetMapInfo( ), m_Map->GetMapCRC( ), m_Map->GetMapSHA1( ) ) );
  2231. // send slot info to everyone, so the new player gets this info twice but everyone else still needs to know the new slot layout
  2232. SendAllSlotInfo( );
  2233. // send a welcome message
  2234. SendWelcomeMessage( Player );
  2235. // if spoof checks are required and we won't automatically spoof check this player then tell them how to spoof check
  2236. // e.g. if automatic spoof checks are disabled, or if automatic spoof checks are done on admins only and this player isn't an admin
  2237. if( m_GHost->m_RequireSpoofChecks && !Player->GetWhoisShouldBeSent( ) )
  2238. {
  2239. for( vector<CBNET *> :: iterator i = m_GHost->m_BNETs.begin( ); i != m_GHost->m_BNETs.end( ); ++i )
  2240. {
  2241. // note: the following (commented out) line of code will crash because calling GetUniqueName( ) twice will result in two different return values
  2242. // and unfortunately iterators are not valid if compared against different containers
  2243. // this comment shall serve as warning to not make this mistake again since it has now been made twice before in GHost++
  2244. // string( (*i)->GetUniqueName( ).begin( ), (*i)->GetUniqueName( ).end( ) )
  2245. BYTEARRAY UniqueName = (*i)->GetUniqueName( );
  2246. if( (*i)->GetServer( ) == JoinedRealm )
  2247. SendChat( Player, m_GHost->m_Language->SpoofCheckByWhispering( string( UniqueName.begin( ), UniqueName.end( ) ) ) );
  2248. }
  2249. }
  2250. if( score < -99999.0 )
  2251. SendAllChat( m_GHost->m_Language->PlayerHasScore( joinPlayer->GetName( ), "N/A" ) );
  2252. else
  2253. SendAllChat( m_GHost->m_Language->PlayerHasScore( joinPlayer->GetName( ), UTIL_ToString( score, 2 ) ) );
  2254. uint32_t PlayersScored = 0;
  2255. uint32_t PlayersNotScored = 0;
  2256. double AverageScore = 0.0;
  2257. double MinScore = 0.0;
  2258. double MaxScore = 0.0;
  2259. bool Found = false;
  2260. for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); ++i )
  2261. {
  2262. if( !(*i)->GetLeftMessageSent( ) )
  2263. {
  2264. if( (*i)->GetScore( ) < -99999.0 )
  2265. PlayersNotScored++;
  2266. else
  2267. {
  2268. PlayersScored++;
  2269. AverageScore += (*i)->GetScore( );
  2270. if( !Found || (*i)->GetScore( ) < MinScore )
  2271. MinScore = (*i)->GetScore( );
  2272. if( !Found || (*i)->GetScore( ) > MaxScore )
  2273. MaxScore = (*i)->GetScore( );
  2274. Found = true;
  2275. }
  2276. }
  2277. }
  2278. double Spread = MaxScore - MinScore;
  2279. SendAllChat( m_GHost->m_Language->RatedPlayersSpread( UTIL_ToString( PlayersScored ), UTIL_ToString( PlayersScored + PlayersNotScored ), UTIL_ToString( (uint32_t)Spread ) ) );
  2280. // check for multiple IP usage
  2281. if( m_GHost->m_CheckMultipleIPUsage )
  2282. {
  2283. string Others;
  2284. for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); ++i )
  2285. {
  2286. if( Player != *i && Player->GetExternalIPString( ) == (*i)->GetExternalIPString( ) )
  2287. {
  2288. if( Others.empty( ) )
  2289. Others = (*i)->GetName( );
  2290. else
  2291. Others += ", " + (*i)->GetName( );
  2292. }
  2293. }
  2294. if( !Others.empty( ) )
  2295. SendAllChat( m_GHost->m_Language->MultipleIPAddressUsageDetected( joinPlayer->GetName( ), Others ) );
  2296. }
  2297. // check for past username change
  2298. if( m_GHost->m_CheckMultipleIPUsage )
  2299. {
  2300. string Others;
  2301. for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); ++i )
  2302. {
  2303. if( Player != *i && Player->GetExternalIPString( ) == (*i)->GetExternalIPString( ) )
  2304. {
  2305. if( Others.empty( ) )
  2306. Others = (*i)->GetName( );
  2307. else
  2308. Others += ", " + (*i)->GetName( );
  2309. }
  2310. }
  2311. if( !Others.empty( ) )
  2312. SendAllChat( m_GHost->m_Language->MultipleIPAddressUsageDetected( joinPlayer->GetName( ), Others ) );
  2313. }
  2314. // abort the countdown if there was one in progress and normal countdown is off
  2315. if ( !m_GHost->m_UseNormalCountDown )
  2316. {
  2317. if( m_CountDownStarted && !m_GameLoading && !m_GameLoaded )
  2318. {
  2319. SendAllChat( m_GHost->m_Language->CountDownAborted( ) );
  2320. m_CountDownStarted = false;
  2321. }
  2322. }
  2323. // auto lock the game
  2324. if( m_GHost->m_AutoLock && !m_Locked && IsOwner( joinPlayer->GetName( ) ) )
  2325. {
  2326. SendAllChat( m_GHost->m_Language->GameLocked( ) );
  2327. m_Locked = true;
  2328. }
  2329. // balance the slots
  2330. if( m_AutoStartPlayers != 0 && GetNumHumanPlayers( ) == m_AutoStartPlayers )
  2331. BalanceSlots( );
  2332. vector<string> playerData;
  2333. playerData.push_back( Player->GetName( ) ); // name
  2334. playerData.push_back( UTIL_ToString( SID + 1 ) ); // slot
  2335. playerData.push_back( m_GHost->m_DBLocal->FromCheck( UTIL_ByteArrayToUInt32( Player->GetExternalIP( ), true ) ) ); // from
  2336. playerData.push_back( UTIL_ToString( Player->GetPing(true) ) + " ms" ); // ping
  2337. playerData.push_back( m_Slots[SID].GetRace( ) == 1 ? "HU" :
  2338. m_Slots[SID].GetRace( ) == 2 ? "ORC" :
  2339. m_Slots[SID].GetRace( ) == 4 ? "NE" :
  2340. m_Slots[SID].GetRace( ) == 8 ? "UD" : "??" ); // race
  2341. playerData.push_back( UTIL_ToString( m_Slots[SID].GetTeam( ) + 1 ) ); // team
  2342. playerData.push_back( ColourValueToString( m_Slots[SID].GetColour( ) ) ); // color
  2343. playerData.push_back( UTIL_ToString( m_Slots[SID].GetHandicap( ) ) ); // handicap
  2344. playerData.push_back( Player->GetGProxy( ) ? "on" : "off" ); // gproxy++
  2345. forward( new CFwdData( FWD_GAME_SLOT_ADD, playerData, m_GameID ) );
  2346. CCallableGamePlayerSummaryCheck *stats = m_GHost->m_DB->ThreadedGamePlayerSummaryCheck( Player->GetName( ) );
  2347. if(stats->GetResult())
  2348. {
  2349. playerData.clear( );
  2350. playerData.push_back( Player->GetName( ) ); // name
  2351. playerData.push_back( UTIL_ToString( stats->GetResult( )->GetTotalGames( ) ) ); // total games
  2352. playerData.push_back( stats->GetResult( )->GetLastGameDateTime( ) ); // last gamedate
  2353. playerData.push_back( UTIL_ToString( stats->GetResult( )->GetAvgLeftPercent( ) ) ); // avg left%
  2354. playerData.push_back( UTIL_ToString( stats->GetResult( )->GetAvgDuration( ) ) + " s" ); // avg duration
  2355. playerData.push_back( UTIL_ToString( (float)stats->GetResult( )->GetAvgLoadingTime( ) / 1000, 2 ) + " s" ); // avg loading time
  2356. forward( new CFwdData( FWD_GAME_STATS_ADD, playerData, m_GameID ) );
  2357. }
  2358. delete stats;
  2359. CCallableDotAPlayerSummaryCheck *dotadb = m_GHost->m_DB->ThreadedDotAPlayerSummaryCheck( Player->GetName( ) );
  2360. if(dotadb->GetResult())
  2361. {
  2362. playerData.clear( );
  2363. playerData.push_back( Player->GetName( ) ); // Name
  2364. playerData.push_back( UTIL_ToString( dotadb->GetResult( )->GetTotalWins( ) ) + "/" +
  2365. UTIL_ToString( dotadb->GetResult( )->GetTotalLosses( ) ) ); // Wins/Losses
  2366. playerData.push_back( UTIL_ToString( dotadb->GetResult( )->GetTotalKills( ) ) + "/" +
  2367. UTIL_ToString( dotadb->GetResult( )->GetTotalDeaths( ) ) + "/" +
  2368. UTIL_ToString( dotadb->GetResult( )->GetTotalAssists( ) ) ); // K/D/A
  2369. playerData.push_back( UTIL_ToString( dotadb->GetResult( )->GetAvgKills( ), 2 ) + "/" +
  2370. UTIL_ToString( dotadb->GetResult( )->GetAvgDeaths( ), 2 ) + "/" +
  2371. UTIL_ToString( dotadb->GetResult( )->GetAvgAssists( ), 2 ) ); // AVG K/D/A
  2372. playerData.push_back( UTIL_ToString( dotadb->GetResult( )->GetTotalCreepKills( ) ) + "/" +
  2373. UTIL_ToString( dotadb->GetResult( )->GetTotalCreepDenies( ) ) + "/" +
  2374. UTIL_ToString( dotadb->GetResult( )->GetTotalNeutralKills( ) ) ); // CS K/D/N
  2375. playerData.push_back( UTIL_ToString( dotadb->GetResult( )->GetAvgCreepKills( ), 2 ) + "/" +
  2376. UTIL_ToString( dotadb->GetResult( )->GetAvgCreepDenies( ), 2 ) + "/" +
  2377. UTIL_ToString( dotadb->GetResult( )->GetAvgNeutralKills( ), 2 ) ); // AVG CS K/D/N
  2378. playerData.push_back( UTIL_ToString( dotadb->GetResult( )->GetTotalTowerKills( ) ) + "/" +
  2379. UTIL_ToString( dotadb->GetResult( )->GetTotalRaxKills( ) ) + "/" +
  2380. UTIL_ToString( dotadb->GetResult( )->GetTotalCourierKills( ) ) ); // T/R/C
  2381. forward( new CFwdData( FWD_GAME_DOTA_DB_ADD, playerData, m_GameID ) );
  2382. }
  2383. delete dotadb;
  2384. playerData.clear( );
  2385. playerData.push_back( "Players" ); playerData.push_back( UTIL_ToString( GetNumHumanPlayers( ) ) + "/" + UTIL_ToString( m_GameLoading || m_GameLoaded ? m_StartPlayers : m_Slots.size( ) ) );
  2386. forward( new CFwdData( FWD_GAME_MAP_INFO_UPDATE, playerData, m_GameID ) );
  2387. }
  2388. void CBaseGame :: EventPlayerLeft( CGamePlayer *player, uint32_t reason )
  2389. {
  2390. // this function is only called when a player leave packet is received, not when there's a socket error, kick, etc...
  2391. player->SetDeleteMe( true );
  2392. if( reason == PLAYERLEAVE_GPROXY )
  2393. player->SetLeftReason( m_GHost->m_Language->WasUnrecoverablyDroppedFromGProxy( ) );
  2394. else
  2395. player->SetLeftReason( m_GHost->m_Language->HasLeftVoluntarily( ) );
  2396. player->SetLeftCode( PLAYERLEAVE_LOST );
  2397. if( !m_GameLoading && !m_GameLoaded )
  2398. OpenSlot( GetSIDFromPID( player->GetPID( ) ), false );
  2399. }
  2400. void CBaseGame :: EventPlayerLoaded( CGamePlayer *player )
  2401. {
  2402. CONSOLE_Print( "[GAME: " + m_GameName + "] player [" + player->GetName( ) + "] finished loading in " + UTIL_ToString( (float)( player->GetFinishedLoadingTicks( ) - m_StartedLoadingTicks ) / 1000, 2 ) + " seconds" );
  2403. if( m_LoadInGame )
  2404. {
  2405. // send any buffered data to the player now
  2406. // see the Update function for more information about why we do this
  2407. // this includes player loaded messages, game updates, and player leave messages
  2408. queue<BYTEARRAY> *LoadInGameData = player->GetLoadInGameData( );
  2409. while( !LoadInGameData->empty( ) )
  2410. {
  2411. Send( player, LoadInGameData->front( ) );
  2412. LoadInGameData->pop( );
  2413. }
  2414. // start the lag screen for the new player
  2415. bool FinishedLoading = true;
  2416. for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); ++i )
  2417. {
  2418. FinishedLoading = (*i)->GetFinishedLoading( );
  2419. if( !FinishedLoading )
  2420. break;
  2421. }
  2422. if( !FinishedLoading )
  2423. Send( player, m_Protocol->SEND_W3GS_START_LAG( m_Players, true ) );
  2424. // remove the new player from previously loaded players' lag screens
  2425. for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); ++i )
  2426. {
  2427. if( *i != player && (*i)->GetFinishedLoading( ) )
  2428. Send( *i, m_Protocol->SEND_W3GS_STOP_LAG( player ) );
  2429. }
  2430. // send a chat message to previously loaded players
  2431. for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); ++i )
  2432. {
  2433. if( *i != player && (*i)->GetFinishedLoading( ) )
  2434. SendChat( *i, m_GHost->m_Language->PlayerFinishedLoading( player->GetName( ) ) );
  2435. }
  2436. if( !FinishedLoading )
  2437. SendChat( player, m_GHost->m_Language->PleaseWaitPlayersStillLoading( ) );
  2438. }
  2439. else
  2440. SendAll( m_Protocol->SEND_W3GS_GAMELOADED_OTHERS( player->GetPID( ) ) );
  2441. }
  2442. void CBaseGame :: EventPlayerAction( CGamePlayer *player, CIncomingAction *action )
  2443. {
  2444. m_Actions.push( action );
  2445. // check for players saving the game and notify everyone
  2446. if( !action->GetAction( )->empty( ) && (*action->GetAction( ))[0] == 6 )
  2447. {
  2448. CONSOLE_Print( "[GAME: " + m_GameName + "] player [" + player->GetName( ) + "] is saving the game" );
  2449. SendAllChat( m_GHost->m_Language->PlayerIsSavingTheGame( player->GetName( ) ) );
  2450. }
  2451. }
  2452. void CBaseGame :: EventPlayerKeepAlive( CGamePlayer *player, uint32_t checkSum )
  2453. {
  2454. // check for desyncs
  2455. // however, it's possible that not every player has sent a checksum for this frame yet
  2456. // first we verify that we have enough checksums to work with otherwise we won't know exactly who desynced
  2457. for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); ++i )
  2458. {
  2459. if( !(*i)->GetDeleteMe( ) && (*i)->GetCheckSums( )->empty( ) )
  2460. return;
  2461. }
  2462. // now we check for desyncs since we know that every player has at least one checksum waiting
  2463. bool FoundPlayer = false;
  2464. uint32_t FirstCheckSum = 0;
  2465. for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); ++i )
  2466. {
  2467. if( !(*i)->GetDeleteMe( ) )
  2468. {
  2469. FoundPlayer = true;
  2470. FirstCheckSum = (*i)->GetCheckSums( )->front( );
  2471. break;
  2472. }
  2473. }
  2474. if( !FoundPlayer )
  2475. return;
  2476. bool AddToReplay = true;
  2477. for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); ++i )
  2478. {
  2479. if( !(*i)->GetDeleteMe( ) && (*i)->GetCheckSums( )->front( ) != FirstCheckSum )
  2480. {
  2481. CONSOLE_Print( "[GAME: " + m_GameName + "] desync detected" );
  2482. SendAllChat( m_GHost->m_Language->DesyncDetected( ) );
  2483. // try to figure out who desynced
  2484. // this is complicated by the fact that we don't know what the correct game state is so we let the players vote
  2485. // put the players into bins based on their game state
  2486. map<uint32_t, vector<unsigned char> > Bins;
  2487. for( vector<CGamePlayer *> :: iterator j = m_Players.begin( ); j != m_Players.end( ); ++j )
  2488. {
  2489. if( !(*j)->GetDeleteMe( ) )
  2490. Bins[(*j)->GetCheckSums( )->front( )].push_back( (*j)->GetPID( ) );
  2491. }
  2492. uint32_t StateNumber = 1;
  2493. map<uint32_t, vector<unsigned char> > :: iterator LargestBin = Bins.begin( );
  2494. bool Tied = false;
  2495. for( map<uint32_t, vector<unsigned char> > :: iterator j = Bins.begin( ); j != Bins.end( ); ++j )
  2496. {
  2497. if( (*j).second.size( ) > (*LargestBin).second.size( ) )
  2498. {
  2499. LargestBin = j;
  2500. Tied = false;
  2501. }
  2502. else if( j != LargestBin && (*j).second.size( ) == (*LargestBin).second.size( ) )
  2503. Tied = true;
  2504. string Players;
  2505. for( vector<unsigned char> :: iterator k = (*j).second.begin( ); k != (*j).second.end( ); ++k )
  2506. {
  2507. CGamePlayer *Player = GetPlayerFromPID( *k );
  2508. if( Player )
  2509. {
  2510. if( Players.empty( ) )
  2511. Players = Player->GetName( );
  2512. else
  2513. Players += ", " + Player->GetName( );
  2514. }
  2515. }
  2516. SendAllChat( m_GHost->m_Language->PlayersInGameState( UTIL_ToString( StateNumber ), Players ) );
  2517. ++StateNumber;
  2518. }
  2519. FirstCheckSum = (*LargestBin).first;
  2520. if( Tied )
  2521. {
  2522. // there is a tie, which is unfortunate
  2523. // the most common way for this to happen is with a desync in a 1v1 situation
  2524. // this is not really unsolvable since the game shouldn't continue anyway so we just kick both players
  2525. // in a 2v2 or higher the chance of this happening is very slim
  2526. // however, we still kick every player because it's not fair to pick one or another group
  2527. // todotodo: it would be possible to split the game at this point and create a "new" game for each game state
  2528. CONSOLE_Print( "[GAME: " + m_GameName + "] can't kick desynced players because there is a tie, kicking all players instead" );
  2529. StopPlayers( m_GHost->m_Language->WasDroppedDesync( ) );
  2530. AddToReplay = false;
  2531. }
  2532. else
  2533. {
  2534. CONSOLE_Print( "[GAME: " + m_GameName + "] kicking desynced players" );
  2535. for( map<uint32_t, vector<unsigned char> > :: iterator j = Bins.begin( ); j != Bins.end( ); ++j )
  2536. {
  2537. // kick players who are NOT in the largest bin
  2538. // examples: suppose there are 10 players
  2539. // the most common case will be 9v1 (e.g. one player desynced and the others were unaffected) and this will kick the single outlier
  2540. // another (very unlikely) possibility is 8v1v1 or 8v2 and this will kick both of the outliers, regardless of whether their game states match
  2541. if( (*j).first != (*LargestBin).first )
  2542. {
  2543. for( vector<unsigned char> :: iterator k = (*j).second.begin( ); k != (*j).second.end( ); ++k )
  2544. {
  2545. CGamePlayer *Player = GetPlayerFromPID( *k );
  2546. if( Player )
  2547. {
  2548. Player->SetDeleteMe( true );
  2549. Player->SetLeftReason( m_GHost->m_Language->WasDroppedDesync( ) );
  2550. Player->SetLeftCode( PLAYERLEAVE_LOST );
  2551. }
  2552. }
  2553. }
  2554. }
  2555. }
  2556. // don't continue looking for desyncs, we already found one!
  2557. break;
  2558. }
  2559. }
  2560. for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); ++i )
  2561. {
  2562. if( !(*i)->GetDeleteMe( ) )
  2563. (*i)->GetCheckSums( )->pop( );
  2564. }
  2565. // add checksum to replay
  2566. /* if( m_Replay && AddToReplay )
  2567. m_Replay->AddCheckSum( FirstCheckSum ); */
  2568. }
  2569. void CBaseGame :: EventPlayerChatToHost( CGamePlayer *player, CIncomingChatPlayer *chatPlayer )
  2570. {
  2571. if( chatPlayer->GetFromPID( ) == player->GetPID( ) )
  2572. {
  2573. if( chatPlayer->GetType( ) == CIncomingChatPlayer :: CTH_MESSAGE || chatPlayer->GetType( ) == CIncomingChatPlayer :: CTH_MESSAGEEXTRA )
  2574. {
  2575. // relay the chat message to other players
  2576. bool Relay = !player->GetMuted( );
  2577. BYTEARRAY ExtraFlags = chatPlayer->GetExtraFlags( );
  2578. // calculate timestamp
  2579. string MinString = UTIL_ToString( ( m_GameTicks / 1000 ) / 60 );
  2580. string SecString = UTIL_ToString( ( m_GameTicks / 1000 ) % 60 );
  2581. if( MinString.size( ) == 1 )
  2582. MinString.insert( 0, "0" );
  2583. if( SecString.size( ) == 1 )
  2584. SecString.insert( 0, "0" );
  2585. if( !ExtraFlags.empty( ) )
  2586. {
  2587. if( ExtraFlags[0] == 0 )
  2588. {
  2589. // this is an ingame [All] message, print it to the console
  2590. CONSOLE_Print( "[GAME: " + m_GameName + "] (" + MinString + ":" + SecString + ") [All] [" + player->GetName( ) + "]: " + chatPlayer->GetMessage( ) );
  2591. forward( new CFwdData( FWD_GAME_CHAT, "(" + MinString + ":" + SecString + ") (All) " + player->GetName( ) + ": " + chatPlayer->GetMessage( ), m_GameID ) );
  2592. // don't relay ingame messages targeted for all players if we're currently muting all
  2593. // note that commands will still be processed even when muting all because we only stop relaying the messages, the rest of the function is unaffected
  2594. if( m_MuteAll )
  2595. Relay = false;
  2596. }
  2597. else if( ExtraFlags[0] == 2 )
  2598. {
  2599. // this is an ingame [Obs/Ref] message, print it to the console
  2600. CONSOLE_Print( "[GAME: " + m_GameName + "] (" + MinString + ":" + SecString + ") [Obs/Ref] [" + player->GetName( ) + "]: " + chatPlayer->GetMessage( ) );
  2601. forward( new CFwdData( FWD_GAME_CHAT, "(" + MinString + ":" + SecString + ") (Obs/Ref) " + player->GetName( ) + ": " + chatPlayer->GetMessage( ), m_GameID ) );
  2602. }
  2603. if( Relay )
  2604. {
  2605. // add chat message to replay
  2606. // this includes allied chat and private chat from both teams as long as it was relayed
  2607. if( m_Replay )
  2608. m_Replay->AddChatMessage( chatPlayer->GetFromPID( ), chatPlayer->GetFlag( ), UTIL_ByteArrayToUInt32( chatPlayer->GetExtraFlags( ), false ), chatPlayer->GetMessage( ) );
  2609. }
  2610. }
  2611. else
  2612. {
  2613. // this is a lobby message, print it to the console
  2614. CONSOLE_Print( "[GAME: " + m_GameName + "] [Lobby] [" + player->GetName( ) + "]: " + chatPlayer->GetMessage( ) );
  2615. forward( new CFwdData( FWD_GAME_CHAT, player->GetName( ) + ":\3", 1, m_GameID ) );
  2616. forward( new CFwdData( FWD_GAME_CHAT, chatPlayer->GetMessage( ), 0, m_GameID ) );
  2617. if( m_MuteLobby )
  2618. Relay = false;
  2619. }
  2620. // handle bot commands
  2621. string Message = chatPlayer->GetMessage( );
  2622. if( Message == "?trigger" )
  2623. SendChat( player, m_GHost->m_Language->CommandTrigger( string( 1, m_GHost->m_CommandTrigger ) ) );
  2624. else if( !Message.empty( ) && Message[0] == m_GHost->m_CommandTrigger )
  2625. {
  2626. // extract the command trigger, the command, and the payload
  2627. // e.g. "!say hello world" -> command: "say", payload: "hello world"
  2628. string Command;
  2629. string Payload;
  2630. string :: size_type PayloadStart = Message.find( " " );
  2631. if( PayloadStart != string :: npos )
  2632. {
  2633. Command = Message.substr( 1, PayloadStart - 1 );
  2634. Payload = Message.substr( PayloadStart + 1 );
  2635. }
  2636. else
  2637. Command = Message.substr( 1 );
  2638. transform( Command.begin( ), Command.end( ), Command.begin( ), (int(*)(int))tolower );
  2639. // don't allow EventPlayerBotCommand to veto a previous instruction to set Relay to false
  2640. // so if Relay is already false (e.g. because the player is muted) then it cannot be forced back to true here
  2641. if( EventPlayerBotCommand( player, Command, Payload ) )
  2642. Relay = false;
  2643. }
  2644. if( Relay )
  2645. Send( chatPlayer->GetToPIDs( ), m_Protocol->SEND_W3GS_CHAT_FROM_HOST( chatPlayer->GetFromPID( ), chatPlayer->GetToPIDs( ), chatPlayer->GetFlag( ), chatPlayer->GetExtraFlags( ), chatPlayer->GetMessage( ) ) );
  2646. }
  2647. else if( chatPlayer->GetType( ) == CIncomingChatPlayer :: CTH_TEAMCHANGE && !m_CountDownStarted )
  2648. EventPlayerChangeTeam( player, chatPlayer->GetByte( ) );
  2649. else if( chatPlayer->GetType( ) == CIncomingChatPlayer :: CTH_COLOURCHANGE && !m_CountDownStarted )
  2650. EventPlayerChangeColour( player, chatPlayer->GetByte( ) );
  2651. else if( chatPlayer->GetType( ) == CIncomingChatPlayer :: CTH_RACECHANGE && !m_CountDownStarted )
  2652. EventPlayerChangeRace( player, chatPlayer->GetByte( ) );
  2653. else if( chatPlayer->GetType( ) == CIncomingChatPlayer :: CTH_HANDICAPCHANGE && !m_CountDownStarted )
  2654. EventPlayerChangeHandicap( player, chatPlayer->GetByte( ) );
  2655. }
  2656. }
  2657. bool CBaseGame :: EventPlayerBotCommand( CGamePlayer *player, string command, string payload )
  2658. {
  2659. // return true if the command itself should be hidden from other players
  2660. return false;
  2661. }
  2662. void CBaseGame :: EventPlayerChangeTeam( CGamePlayer *player, unsigned char team )
  2663. {
  2664. // player is requesting a team change
  2665. if( m_SaveGame )
  2666. return;
  2667. if( m_Map->GetMapOptions( ) & MAPOPT_CUSTOMFORCES )
  2668. {
  2669. unsigned char oldSID = GetSIDFromPID( player->GetPID( ) );
  2670. unsigned char newSID = GetEmptySlot( team, player->GetPID( ) );
  2671. SwapSlots( oldSID, newSID );
  2672. }
  2673. else
  2674. {
  2675. if( team > 12 )
  2676. return;
  2677. if( team == 12 )
  2678. {
  2679. if( m_Map->GetMapObservers( ) != MAPOBS_ALLOWED && m_Map->GetMapObservers( ) != MAPOBS_REFEREES )
  2680. return;
  2681. }
  2682. else
  2683. {
  2684. if( team >= m_Map->GetMapNumPlayers( ) )
  2685. return;
  2686. // make sure there aren't too many other players already
  2687. unsigned char NumOtherPlayers = 0;
  2688. for( unsigned char i = 0; i < m_Slots.size( ); ++i )
  2689. {
  2690. if( m_Slots[i].GetSlotStatus( ) == SLOTSTATUS_OCCUPIED && m_Slots[i].GetTeam( ) != 12 && m_Slots[i].GetPID( ) != player->GetPID( ) )
  2691. ++NumOtherPlayers;
  2692. }
  2693. if( NumOtherPlayers >= m_Map->GetMapNumPlayers( ) )
  2694. return;
  2695. }
  2696. unsigned char SID = GetSIDFromPID( player->GetPID( ) );
  2697. if( SID < m_Slots.size( ) )
  2698. {
  2699. m_Slots[SID].SetTeam( team );
  2700. if( team == 12 )
  2701. {
  2702. // if they're joining the observer team give them the observer colour
  2703. m_Slots[SID].SetColour( 12 );
  2704. }
  2705. else if( m_Slots[SID].GetColour( ) == 12 )
  2706. {
  2707. // if they're joining a regular team give them an unused colour
  2708. m_Slots[SID].SetColour( GetNewColour( ) );
  2709. }
  2710. SendAllSlotInfo( );
  2711. }
  2712. }
  2713. }
  2714. void CBaseGame :: EventPlayerChangeColour( CGamePlayer *player, unsigned char colour )
  2715. {
  2716. // player is requesting a colour change
  2717. if( m_SaveGame )
  2718. return;
  2719. if( m_Map->GetMapOptions( ) & MAPOPT_FIXEDPLAYERSETTINGS )
  2720. return;
  2721. if( colour > 11 )
  2722. return;
  2723. unsigned char SID = GetSIDFromPID( player->GetPID( ) );
  2724. if( SID < m_Slots.size( ) )
  2725. {
  2726. // make sure the player isn't an observer
  2727. if( m_Slots[SID].GetTeam( ) == 12 )
  2728. return;
  2729. ColourSlot( SID, colour );
  2730. }
  2731. }
  2732. void CBaseGame :: EventPlayerChangeRace( CGamePlayer *player, unsigned char race )
  2733. {
  2734. // player is requesting a race change
  2735. if( m_SaveGame )
  2736. return;
  2737. if( m_Map->GetMapOptions( ) & MAPOPT_FIXEDPLAYERSETTINGS )
  2738. return;
  2739. if( m_Map->GetMapFlags( ) & MAPFLAG_RANDOMRACES )
  2740. return;
  2741. if( race != SLOTRACE_HUMAN && race != SLOTRACE_ORC && race != SLOTRACE_NIGHTELF && race != SLOTRACE_UNDEAD && race != SLOTRACE_RANDOM )
  2742. return;
  2743. unsigned char SID = GetSIDFromPID( player->GetPID( ) );
  2744. if( SID < m_Slots.size( ) )
  2745. {
  2746. m_Slots[SID].SetRace( race | SLOTRACE_SELECTABLE );
  2747. SendAllSlotInfo( );
  2748. }
  2749. }
  2750. void CBaseGame :: EventPlayerChangeHandicap( CGamePlayer *player, unsigned char handicap )
  2751. {
  2752. // player is requesting a handicap change
  2753. if( m_SaveGame )
  2754. return;
  2755. if( m_Map->GetMapOptions( ) & MAPOPT_FIXEDPLAYERSETTINGS )
  2756. return;
  2757. if( handicap != 50 && handicap != 60 && handicap != 70 && handicap != 80 && handicap != 90 && handicap != 100 )
  2758. return;
  2759. unsigned char SID = GetSIDFromPID( player->GetPID( ) );
  2760. if( SID < m_Slots.size( ) )
  2761. {
  2762. m_Slots[SID].SetHandicap( handicap );
  2763. SendAllSlotInfo( );
  2764. }
  2765. }
  2766. void CBaseGame :: EventPlayerDropRequest( CGamePlayer *player )
  2767. {
  2768. // todotodo: check that we've waited the full 45 seconds
  2769. if( m_Lagging )
  2770. {
  2771. CONSOLE_Print( "[GAME: " + m_GameName + "] player [" + player->GetName( ) + "] voted to drop laggers" );
  2772. SendAllChat( m_GHost->m_Language->PlayerVotedToDropLaggers( player->GetName( ) ) );
  2773. // check if at least half the players voted to drop
  2774. uint32_t Votes = 0;
  2775. for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); ++i )
  2776. {
  2777. if( (*i)->GetDropVote( ) )
  2778. ++Votes;
  2779. }
  2780. if( (float)Votes / m_Players.size( ) > 0.49 )
  2781. StopLaggers( m_GHost->m_Language->LaggedOutDroppedByVote( ) );
  2782. }
  2783. }
  2784. void CBaseGame :: EventPlayerMapSize( CGamePlayer *player, CIncomingMapSize *mapSize )
  2785. {
  2786. if( m_GameLoading || m_GameLoaded )
  2787. return;
  2788. // todotodo: the variable names here are confusing due to extremely poor design on my part
  2789. uint32_t MapSize = UTIL_ByteArrayToUInt32( m_Map->GetMapSize( ), false );
  2790. if( mapSize->GetSizeFlag( ) != 1 || mapSize->GetMapSize( ) != MapSize )
  2791. {
  2792. // the player doesn't have the map
  2793. if( m_GHost->m_AllowDownloads != 0 )
  2794. {
  2795. string *MapData = m_Map->GetMapData( );
  2796. if( !MapData->empty( ) )
  2797. {
  2798. if( m_GHost->m_AllowDownloads == 1 || ( m_GHost->m_AllowDownloads == 2 && player->GetDownloadAllowed( ) ) )
  2799. {
  2800. if( !player->GetDownloadStarted( ) && mapSize->GetSizeFlag( ) == 1 )
  2801. {
  2802. // inform the client that we are willing to send the map
  2803. CONSOLE_Print( "[GAME: " + m_GameName + "] map download started for player [" + player->GetName( ) + "]" );
  2804. Send( player, m_Protocol->SEND_W3GS_STARTDOWNLOAD( GetHostPID( ) ) );
  2805. player->SetDownloadStarted( true );
  2806. player->SetStartedDownloadingTicks( GetTicks( ) );
  2807. }
  2808. else
  2809. player->SetLastMapPartAcked( mapSize->GetMapSize( ) );
  2810. }
  2811. }
  2812. else
  2813. {
  2814. player->SetDeleteMe( true );
  2815. player->SetLeftReason( "doesn't have the map and there is no local copy of the map to send" );
  2816. player->SetLeftCode( PLAYERLEAVE_LOBBY );
  2817. OpenSlot( GetSIDFromPID( player->GetPID( ) ), false );
  2818. }
  2819. }
  2820. else
  2821. {
  2822. player->SetDeleteMe( true );
  2823. player->SetLeftReason( "doesn't have the map and map downloads are disabled" );
  2824. player->SetLeftCode( PLAYERLEAVE_LOBBY );
  2825. OpenSlot( GetSIDFromPID( player->GetPID( ) ), false );
  2826. }
  2827. }
  2828. else
  2829. {
  2830. if( player->GetDownloadStarted( ) )
  2831. {
  2832. // calculate download rate
  2833. float Seconds = (float)( GetTicks( ) - player->GetStartedDownloadingTicks( ) ) / 1000;
  2834. float Rate = (float)MapSize / 1024 / Seconds;
  2835. CONSOLE_Print( "[GAME: " + m_GameName + "] map download finished for player [" + player->GetName( ) + "] in " + UTIL_ToString( Seconds, 1 ) + " seconds" );
  2836. SendAllChat( m_GHost->m_Language->PlayerDownloadedTheMap( player->GetName( ), UTIL_ToString( Seconds, 1 ), UTIL_ToString( Rate, 1 ) ) );
  2837. player->SetDownloadFinished( true );
  2838. player->SetFinishedDownloadingTime( GetTime( ) );
  2839. // add to database
  2840. m_GHost->m_Callables.push_back( m_GHost->m_DB->ThreadedDownloadAdd( m_Map->GetMapPath( ), MapSize, player->GetName( ), player->GetExternalIPString( ), player->GetSpoofed( ) ? 1 : 0, player->GetSpoofedRealm( ), GetTicks( ) - player->GetStartedDownloadingTicks( ) ) );
  2841. }
  2842. }
  2843. unsigned char NewDownloadStatus = (unsigned char)( (float)mapSize->GetMapSize( ) / MapSize * 100 );
  2844. unsigned char SID = GetSIDFromPID( player->GetPID( ) );
  2845. if( NewDownloadStatus > 100 )
  2846. NewDownloadStatus = 100;
  2847. if( SID < m_Slots.size( ) )
  2848. {
  2849. // only send the slot info if the download status changed
  2850. if( m_Slots[SID].GetDownloadStatus( ) != NewDownloadStatus )
  2851. {
  2852. m_Slots[SID].SetDownloadStatus( NewDownloadStatus );
  2853. // we don't actually send the new slot info here
  2854. // this is an optimization because it's possible for a player to download a map very quickly
  2855. // if we send a new slot update for every percentage change in their download status it adds up to a lot of data
  2856. // instead, we mark the slot info as "out of date" and update it only once in awhile (once per second when this comment was made)
  2857. m_SlotInfoChanged = true;
  2858. }
  2859. }
  2860. }
  2861. void CBaseGame :: EventPlayerPongToHost( CGamePlayer *player, uint32_t pong )
  2862. {
  2863. // autokick players with excessive pings but only if they're not reserved and we've received at least 3 pings from them
  2864. // also don't kick anyone if the game is loading or loaded - this could happen because we send pings during loading but we stop sending them after the game is loaded
  2865. // see the Update function for where we send pings
  2866. if( !m_GameLoading && !m_GameLoaded && !player->GetDeleteMe( ) && !player->GetReserved( ) && player->GetNumPings( ) >= 3 && player->GetPing( m_GHost->m_LCPings ) > m_GHost->m_AutoKickPing )
  2867. {
  2868. // send a chat message because we don't normally do so when a player leaves the lobby
  2869. SendAllChat( m_GHost->m_Language->AutokickingPlayerForExcessivePing( player->GetName( ), UTIL_ToString( player->GetPing( m_GHost->m_LCPings ) ) ) );
  2870. player->SetDeleteMe( true );
  2871. player->SetLeftReason( "was autokicked for excessive ping of " + UTIL_ToString( player->GetPing( m_GHost->m_LCPings ) ) );
  2872. player->SetLeftCode( PLAYERLEAVE_LOBBY );
  2873. OpenSlot( GetSIDFromPID( player->GetPID( ) ), false );
  2874. }
  2875. }
  2876. void CBaseGame :: EventGameRefreshed( string server )
  2877. {
  2878. if( m_RefreshRehosted )
  2879. {
  2880. // we're not actually guaranteed this refresh was for the rehosted game and not the previous one
  2881. // but since we unqueue game refreshes when rehosting, the only way this can happen is due to network delay
  2882. // it's a risk we're willing to take but can result in a false positive here
  2883. SendAllChat( m_GHost->m_Language->RehostWasSuccessful( ) );
  2884. m_RefreshRehosted = false;
  2885. forward( new CFwdData( FWD_GAME_UPDATE, m_GameName, m_GameID ) );
  2886. }
  2887. }
  2888. void CBaseGame :: AutoSetHCL ( )
  2889. {
  2890. // auto set HCL if map_defaulthcl is not empty
  2891. string gameName = m_GameName;
  2892. transform( gameName.begin( ), gameName.end( ), gameName.begin( ), (int(*)(int))tolower );
  2893. string m_Mode = string();
  2894. string m_Modes = string();
  2895. string m_Modes2 = string();
  2896. if ( m_GHost->m_HCLCommandFromGameName )
  2897. if (gameName.find("-")!= string::npos)
  2898. {
  2899. uint32_t j = 0;
  2900. uint32_t k = 0;
  2901. uint32_t i = 0;
  2902. string mode = string();
  2903. while (j<gameName.length()-1 && (gameName.find("-",j)!= string::npos))
  2904. {
  2905. k = gameName.find("-",j);
  2906. i = gameName.find(" ",k);
  2907. if (i==0)
  2908. i = gameName.length() - k + 1;
  2909. else
  2910. i = i - k;
  2911. mode = gameName.substr(k, i);
  2912. // keep the first mode separately to be used in HCL
  2913. if (m_Mode.empty())
  2914. m_Mode = gameName.substr(k+1, i-1);
  2915. m_Modes += " "+mode;
  2916. if (m_Modes2.length()>0)
  2917. m_Modes2+=" ";
  2918. m_Modes2+= mode;
  2919. j = k + i;
  2920. }
  2921. if (!m_Mode.empty())
  2922. {
  2923. CONSOLE_Print( "[GHOST] autosetting HCL to [" + m_Mode + "]" );
  2924. if( m_Mode.size( ) <= m_Slots.size( ) )
  2925. {
  2926. string HCLChars = "abcdefghijklmnopqrstuvwxyz0123456789 -=,.";
  2927. if( m_Mode.find_first_not_of( HCLChars ) == string :: npos )
  2928. {
  2929. SetHCL(m_Mode);
  2930. if (m_Mode == m_HCLCommandString)
  2931. SendAllChat( m_GHost->m_Language->SettingHCL( GetHCL() ) );
  2932. }
  2933. else
  2934. SendAllChat( m_GHost->m_Language->UnableToSetHCLInvalid( ) );
  2935. }
  2936. else
  2937. SendAllChat( m_GHost->m_Language->UnableToSetHCLTooLong( ) );
  2938. }
  2939. } else
  2940. // no gamemode detected from gamename, disable map_defaulthcl
  2941. {
  2942. SetHCL(string());
  2943. }
  2944. }
  2945. void CBaseGame :: EventGameStarted( )
  2946. {
  2947. CONSOLE_Print( "[GAME: " + m_GameName + "] started loading with " + UTIL_ToString( GetNumHumanPlayers( ) ) + " players" );
  2948. if ( m_GHost->m_ResetDownloads )
  2949. m_GHost->m_AllowDownloads = m_GHost->m_AllowDownloads2;
  2950. // @disturbed_oc
  2951. // Get HCL Command from gamename
  2952. if ( m_Map->GetMapHCLFromGameName() || !m_HCLOverride )
  2953. {
  2954. //CONSOLE_Print( "[GAME: " + m_GameName + "] Trying to get Game Mode for HCL from gamename. Valid Modes [" + m_Map->GetMapHCLValidModes() + "]" );
  2955. vector<string> ValidModes = UTIL_Tokenize(m_Map->GetMapHCLValidModes(), ' ');
  2956. string::size_type loc;
  2957. for( vector<string> :: iterator i = ValidModes.begin( ); i != ValidModes.end( ); ++i )
  2958. {
  2959. loc = m_GameName.find( (*i) );
  2960. if ( loc != string::npos )
  2961. {
  2962. CONSOLE_Print( "[GAME: " + m_GameName + "] Found Game Mode [" + (*i).c_str() + "] HCL Command [" + (*i).substr(1) + "]" );
  2963. m_HCLCommandString = (*i).substr(1);
  2964. }
  2965. }
  2966. }
  2967. // @end
  2968. // encode the HCL command string in the slot handicaps
  2969. // here's how it works:
  2970. // the user inputs a command string to be sent to the map
  2971. // it is almost impossible to send a message from the bot to the map so we encode the command string in the slot handicaps
  2972. // this works because there are only 6 valid handicaps but Warcraft III allows the bot to set up to 256 handicaps
  2973. // we encode the original (unmodified) handicaps in the new handicaps and use the remaining space to store a short message
  2974. // only occupied slots deliver their handicaps to the map and we can send one character (from a list) per handicap
  2975. // when the map finishes loading, assuming it's designed to use the HCL system, it checks if anyone has an invalid handicap
  2976. // if so, it decodes the message from the handicaps and restores the original handicaps using the encoded values
  2977. // the meaning of the message is specific to each map and the bot doesn't need to understand it
  2978. // e.g. you could send game modes, # of rounds, level to start on, anything you want as long as it fits in the limited space available
  2979. // note: if you attempt to use the HCL system on a map that does not support HCL the bot will drastically modify the handicaps
  2980. // since the map won't automatically restore the original handicaps in this case your game will be ruined
  2981. if( !m_HCLCommandString.empty( ) )
  2982. {
  2983. if( m_HCLCommandString.size( ) <= GetSlotsOccupied( ) )
  2984. {
  2985. string HCLChars = "abcdefghijklmnopqrstuvwxyz0123456789 -=,.";
  2986. if( m_HCLCommandString.find_first_not_of( HCLChars ) == string :: npos )
  2987. {
  2988. unsigned char EncodingMap[256];
  2989. unsigned char j = 0;
  2990. for( uint32_t i = 0; i < 256; i++ )
  2991. {
  2992. // the following 7 handicap values are forbidden
  2993. if( j == 0 || j == 50 || j == 60 || j == 70 || j == 80 || j == 90 || j == 100 )
  2994. j++;
  2995. EncodingMap[i] = j++;
  2996. }
  2997. unsigned char CurrentSlot = 0;
  2998. for( string :: iterator si = m_HCLCommandString.begin( ); si != m_HCLCommandString.end( ); si++ )
  2999. {
  3000. while( m_Slots[CurrentSlot].GetSlotStatus( ) != SLOTSTATUS_OCCUPIED )
  3001. CurrentSlot++;
  3002. unsigned char HandicapIndex = ( m_Slots[CurrentSlot].GetHandicap( ) - 50 ) / 10;
  3003. unsigned char CharIndex = HCLChars.find( *si );
  3004. m_Slots[CurrentSlot++].SetHandicap( EncodingMap[HandicapIndex + CharIndex * 6] );
  3005. }
  3006. SendAllSlotInfo( );
  3007. CONSOLE_Print( "[GAME: " + m_GameName + "] successfully encoded HCL command string [" + m_HCLCommandString + "]" );
  3008. }
  3009. else
  3010. CONSOLE_Print( "[GAME: " + m_GameName + "] encoding HCL command string [" + m_HCLCommandString + "] failed because it contains invalid characters" );
  3011. }
  3012. else
  3013. CONSOLE_Print( "[GAME: " + m_GameName + "] encoding HCL command string [" + m_HCLCommandString + "] failed because there aren't enough occupied slots" );
  3014. }
  3015. // send a final slot info update if necessary
  3016. // this typically won't happen because we prevent the !start command from completing while someone is downloading the map
  3017. // however, if someone uses !start force while a player is downloading the map this could trigger
  3018. // this is because we only permit slot info updates to be flagged when it's just a change in download status, all others are sent immediately
  3019. // it might not be necessary but let's clean up the mess anyway
  3020. if( m_SlotInfoChanged )
  3021. SendAllSlotInfo( );
  3022. m_StartedLoadingTicks = GetTicks( );
  3023. m_LastLagScreenResetTime = GetTime( );
  3024. m_GameLoading = true;
  3025. if ( !m_GHost->m_UseNormalCountDown )
  3026. {
  3027. // since we use a fake countdown to deal with leavers during countdown the COUNTDOWN_START and COUNTDOWN_END packets are sent in quick succession
  3028. // send a start countdown packet
  3029. SendAll( m_Protocol->SEND_W3GS_COUNTDOWN_START( ) );
  3030. }
  3031. // remove the virtual host player
  3032. DeleteVirtualHost( );
  3033. // send an end countdown packet
  3034. SendAll( m_Protocol->SEND_W3GS_COUNTDOWN_END( ) );
  3035. // send a game loaded packet for the fake player (if present)
  3036. if( m_FakePlayerPID != 255 )
  3037. SendAll( m_Protocol->SEND_W3GS_GAMELOADED_OTHERS( m_FakePlayerPID ) );
  3038. // record the starting number of players
  3039. m_StartPlayers = GetNumHumanPlayers( );
  3040. // close the listening socket
  3041. delete m_Socket;
  3042. m_Socket = NULL;
  3043. // delete any potential players that are still hanging around
  3044. for( vector<CPotentialPlayer *> :: iterator i = m_Potentials.begin( ); i != m_Potentials.end( ); ++i )
  3045. delete *i;
  3046. m_Potentials.clear( );
  3047. // set initial values for replay
  3048. if( m_Replay )
  3049. {
  3050. for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); ++i )
  3051. m_Replay->AddPlayer( (*i)->GetPID( ), (*i)->GetName( ) );
  3052. if( m_FakePlayerPID != 255 )
  3053. m_Replay->AddPlayer( m_FakePlayerPID, "FakePlayer" );
  3054. m_Replay->SetSlots( m_Slots );
  3055. m_Replay->SetRandomSeed( m_RandomSeed );
  3056. m_Replay->SetSelectMode( m_Map->GetMapLayoutStyle( ) );
  3057. m_Replay->SetStartSpotCount( m_Map->GetMapNumPlayers( ) );
  3058. if( m_SaveGame )
  3059. {
  3060. uint32_t MapGameType = MAPGAMETYPE_SAVEDGAME;
  3061. if( m_GameState == GAME_PRIVATE )
  3062. MapGameType |= MAPGAMETYPE_PRIVATEGAME;
  3063. m_Replay->SetMapGameType( MapGameType );
  3064. }
  3065. else
  3066. {
  3067. uint32_t MapGameType = m_Map->GetMapGameType( );
  3068. MapGameType |= MAPGAMETYPE_UNKNOWN0;
  3069. if( m_GameState == GAME_PRIVATE )
  3070. MapGameType |= MAPGAMETYPE_PRIVATEGAME;
  3071. m_Replay->SetMapGameType( MapGameType );
  3072. }
  3073. if( !m_Players.empty( ) )
  3074. {
  3075. // this might not be necessary since we're going to overwrite the replay's host PID and name everytime a player leaves
  3076. m_Replay->SetHostPID( m_Players[0]->GetPID( ) );
  3077. m_Replay->SetHostName( m_Players[0]->GetName( ) );
  3078. }
  3079. }
  3080. // build a stat string for use when saving the replay
  3081. // we have to build this now because the map data is going to be deleted
  3082. BYTEARRAY StatString;
  3083. UTIL_AppendByteArray( StatString, m_Map->GetMapGameFlags( ) );
  3084. StatString.push_back( 0 );
  3085. UTIL_AppendByteArray( StatString, m_Map->GetMapWidth( ) );
  3086. UTIL_AppendByteArray( StatString, m_Map->GetMapHeight( ) );
  3087. UTIL_AppendByteArray( StatString, m_Map->GetMapCRC( ) );
  3088. UTIL_AppendByteArray( StatString, m_Map->GetMapPath( ) );
  3089. UTIL_AppendByteArray( StatString, "GHost++" );
  3090. StatString.push_back( 0 );
  3091. UTIL_AppendByteArray( StatString, m_Map->GetMapSHA1( ) ); // note: in replays generated by Warcraft III it stores 20 zeros for the SHA1 instead of the real thing
  3092. StatString = UTIL_EncodeStatString( StatString );
  3093. m_StatString = string( StatString.begin( ), StatString.end( ) );
  3094. // delete the map data
  3095. delete m_Map;
  3096. m_Map = NULL;
  3097. if( m_LoadInGame )
  3098. {
  3099. // buffer all the player loaded messages
  3100. // this ensures that every player receives the same set of player loaded messages in the same order, even if someone leaves during loading
  3101. // if someone leaves during loading we buffer the leave message to ensure it gets sent in the correct position but the player loaded message wouldn't get sent if we didn't buffer it now
  3102. for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); ++i )
  3103. {
  3104. for( vector<CGamePlayer *> :: iterator j = m_Players.begin( ); j != m_Players.end( ); ++j )
  3105. (*j)->AddLoadInGameData( m_Protocol->SEND_W3GS_GAMELOADED_OTHERS( (*i)->GetPID( ) ) );
  3106. }
  3107. }
  3108. // move the game to the games in progress vector
  3109. m_GHost->m_CurrentGame = NULL;
  3110. m_GHost->m_Games.push_back( this );
  3111. // and finally reenter battle.net chat
  3112. for( vector<CBNET *> :: iterator i = m_GHost->m_BNETs.begin( ); i != m_GHost->m_BNETs.end( ); ++i )
  3113. {
  3114. (*i)->QueueGameUncreate( );
  3115. (*i)->QueueEnterChat( );
  3116. }
  3117. }
  3118. void CBaseGame :: EventGameLoaded( )
  3119. {
  3120. CONSOLE_Print( "[GAME: " + m_GameName + "] finished loading with " + UTIL_ToString( GetNumHumanPlayers( ) ) + " players" );
  3121. // send shortest, longest, and personal load times to each player
  3122. CGamePlayer *Shortest = NULL;
  3123. CGamePlayer *Longest = NULL;
  3124. for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); ++i )
  3125. {
  3126. if( !Shortest || (*i)->GetFinishedLoadingTicks( ) < Shortest->GetFinishedLoadingTicks( ) )
  3127. Shortest = *i;
  3128. if( !Longest || (*i)->GetFinishedLoadingTicks( ) > Longest->GetFinishedLoadingTicks( ) )
  3129. Longest = *i;
  3130. }
  3131. if( Shortest && Longest )
  3132. {
  3133. SendAllChat( m_GHost->m_Language->ShortestLoadByPlayer( Shortest->GetName( ), UTIL_ToString( (float)( Shortest->GetFinishedLoadingTicks( ) - m_StartedLoadingTicks ) / 1000, 2 ) ) );
  3134. SendAllChat( m_GHost->m_Language->LongestLoadByPlayer( Longest->GetName( ), UTIL_ToString( (float)( Longest->GetFinishedLoadingTicks( ) - m_StartedLoadingTicks ) / 1000, 2 ) ) );
  3135. }
  3136. for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); ++i )
  3137. SendChat( *i, m_GHost->m_Language->YourLoadingTimeWas( UTIL_ToString( (float)( (*i)->GetFinishedLoadingTicks( ) - m_StartedLoadingTicks ) / 1000, 2 ) ) );
  3138. // read from gameloaded.txt if available
  3139. ifstream in;
  3140. in.open( m_GHost->m_GameLoadedFile.c_str( ) );
  3141. if( !in.fail( ) )
  3142. {
  3143. // don't print more than 8 lines
  3144. uint32_t Count = 0;
  3145. string Line;
  3146. while( !in.eof( ) && Count < 8 )
  3147. {
  3148. getline( in, Line );
  3149. if( Line.empty( ) )
  3150. {
  3151. if( !in.eof( ) )
  3152. SendAllChat( " " );
  3153. }
  3154. else
  3155. SendAllChat( Line );
  3156. ++Count;
  3157. }
  3158. in.close( );
  3159. }
  3160. }
  3161. unsigned char CBaseGame :: GetSIDFromPID( unsigned char PID )
  3162. {
  3163. if( m_Slots.size( ) > 255 )
  3164. return 255;
  3165. for( unsigned char i = 0; i < m_Slots.size( ); ++i )
  3166. {
  3167. if( m_Slots[i].GetPID( ) == PID )
  3168. return i;
  3169. }
  3170. return 255;
  3171. }
  3172. CGamePlayer *CBaseGame :: GetPlayerFromPID( unsigned char PID )
  3173. {
  3174. for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); ++i )
  3175. {
  3176. if( !(*i)->GetLeftMessageSent( ) && (*i)->GetPID( ) == PID )
  3177. return *i;
  3178. }
  3179. return NULL;
  3180. }
  3181. CGamePlayer *CBaseGame :: GetPlayerFromSID( unsigned char SID )
  3182. {
  3183. if( SID < m_Slots.size( ) )
  3184. return GetPlayerFromPID( m_Slots[SID].GetPID( ) );
  3185. return NULL;
  3186. }
  3187. CGamePlayer *CBaseGame :: GetPlayerFromName( string name, bool sensitive )
  3188. {
  3189. if( !sensitive )
  3190. transform( name.begin( ), name.end( ), name.begin( ), (int(*)(int))tolower );
  3191. for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); ++i )
  3192. {
  3193. if( !(*i)->GetLeftMessageSent( ) )
  3194. {
  3195. string TestName = (*i)->GetName( );
  3196. if( !sensitive )
  3197. transform( TestName.begin( ), TestName.end( ), TestName.begin( ), (int(*)(int))tolower );
  3198. if( TestName == name )
  3199. return *i;
  3200. }
  3201. }
  3202. return NULL;
  3203. }
  3204. uint32_t CBaseGame :: GetPlayerFromNamePartial( string name, CGamePlayer **player )
  3205. {
  3206. transform( name.begin( ), name.end( ), name.begin( ), (int(*)(int))tolower );
  3207. uint32_t Matches = 0;
  3208. *player = NULL;
  3209. // try to match each player with the passed string (e.g. "Varlock" would be matched with "lock")
  3210. for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); ++i )
  3211. {
  3212. if( !(*i)->GetLeftMessageSent( ) )
  3213. {
  3214. string TestName = (*i)->GetName( );
  3215. transform( TestName.begin( ), TestName.end( ), TestName.begin( ), (int(*)(int))tolower );
  3216. if( TestName.find( name ) != string :: npos )
  3217. {
  3218. ++Matches;
  3219. *player = *i;
  3220. // if the name matches exactly stop any further matching
  3221. if( TestName == name )
  3222. {
  3223. Matches = 1;
  3224. break;
  3225. }
  3226. }
  3227. }
  3228. }
  3229. return Matches;
  3230. }
  3231. CGamePlayer *CBaseGame :: GetPlayerFromColour( unsigned char colour )
  3232. {
  3233. for( unsigned char i = 0; i < m_Slots.size( ); ++i )
  3234. {
  3235. if( m_Slots[i].GetColour( ) == colour )
  3236. return GetPlayerFromSID( i );
  3237. }
  3238. return NULL;
  3239. }
  3240. unsigned char CBaseGame :: GetNewPID( )
  3241. {
  3242. // find an unused PID for a new player to use
  3243. for( unsigned char TestPID = 1; TestPID < 255; ++TestPID )
  3244. {
  3245. if( TestPID == m_VirtualHostPID || TestPID == m_FakePlayerPID )
  3246. continue;
  3247. bool InUse = false;
  3248. for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); ++i )
  3249. {
  3250. if( !(*i)->GetLeftMessageSent( ) && (*i)->GetPID( ) == TestPID )
  3251. {
  3252. InUse = true;
  3253. break;
  3254. }
  3255. }
  3256. if( !InUse )
  3257. return TestPID;
  3258. }
  3259. // this should never happen
  3260. return 255;
  3261. }
  3262. unsigned char CBaseGame :: GetNewColour( )
  3263. {
  3264. // find an unused colour for a player to use
  3265. for( unsigned char TestColour = 0; TestColour < 12; ++TestColour )
  3266. {
  3267. bool InUse = false;
  3268. for( unsigned char i = 0; i < m_Slots.size( ); ++i )
  3269. {
  3270. if( m_Slots[i].GetColour( ) == TestColour )
  3271. {
  3272. InUse = true;
  3273. break;
  3274. }
  3275. }
  3276. if( !InUse )
  3277. return TestColour;
  3278. }
  3279. // this should never happen
  3280. return 12;
  3281. }
  3282. BYTEARRAY CBaseGame :: GetPIDs( )
  3283. {
  3284. BYTEARRAY result;
  3285. for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); ++i )
  3286. {
  3287. if( !(*i)->GetLeftMessageSent( ) )
  3288. result.push_back( (*i)->GetPID( ) );
  3289. }
  3290. return result;
  3291. }
  3292. BYTEARRAY CBaseGame :: GetPIDs( unsigned char excludePID )
  3293. {
  3294. BYTEARRAY result;
  3295. for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); ++i )
  3296. {
  3297. if( !(*i)->GetLeftMessageSent( ) && (*i)->GetPID( ) != excludePID )
  3298. result.push_back( (*i)->GetPID( ) );
  3299. }
  3300. return result;
  3301. }
  3302. unsigned char CBaseGame :: GetHostPID( )
  3303. {
  3304. // return the player to be considered the host (it can be any player) - mainly used for sending text messages from the bot
  3305. // try to find the virtual host player first
  3306. if( m_VirtualHostPID != 255 )
  3307. return m_VirtualHostPID;
  3308. // try to find the fakeplayer next
  3309. if( m_FakePlayerPID != 255 )
  3310. return m_FakePlayerPID;
  3311. // try to find the owner player next
  3312. for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); ++i )
  3313. {
  3314. if( !(*i)->GetLeftMessageSent( ) && IsOwner( (*i)->GetName( ) ) )
  3315. return (*i)->GetPID( );
  3316. }
  3317. // okay then, just use the first available player
  3318. for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); ++i )
  3319. {
  3320. if( !(*i)->GetLeftMessageSent( ) )
  3321. return (*i)->GetPID( );
  3322. }
  3323. return 255;
  3324. }
  3325. unsigned char CBaseGame :: GetEmptySlot( bool reserved )
  3326. {
  3327. if( m_Slots.size( ) > 255 )
  3328. return 255;
  3329. if( m_SaveGame )
  3330. {
  3331. // unfortunately we don't know which slot each player was assigned in the savegame
  3332. // but we do know which slots were occupied and which weren't so let's at least force players to use previously occupied slots
  3333. vector<CGameSlot> SaveGameSlots = m_SaveGame->GetSlots( );
  3334. for( unsigned char i = 0; i < m_Slots.size( ); ++i )
  3335. {
  3336. if( m_Slots[i].GetSlotStatus( ) == SLOTSTATUS_OPEN && SaveGameSlots[i].GetSlotStatus( ) == SLOTSTATUS_OCCUPIED && SaveGameSlots[i].GetComputer( ) == 0 )
  3337. return i;
  3338. }
  3339. // don't bother with reserved slots in savegames
  3340. }
  3341. else
  3342. {
  3343. // look for an empty slot for a new player to occupy
  3344. // if reserved is true then we're willing to use closed or occupied slots as long as it wouldn't displace a player with a reserved slot
  3345. for( unsigned char i = 0; i < m_Slots.size( ); ++i )
  3346. {
  3347. if( m_Slots[i].GetSlotStatus( ) == SLOTSTATUS_OPEN )
  3348. return i;
  3349. }
  3350. if( reserved )
  3351. {
  3352. // no empty slots, but since player is reserved give them a closed slot
  3353. for( unsigned char i = 0; i < m_Slots.size( ); ++i )
  3354. {
  3355. if( m_Slots[i].GetSlotStatus( ) == SLOTSTATUS_CLOSED )
  3356. return i;
  3357. }
  3358. // no closed slots either, give them an occupied slot but not one occupied by another reserved player
  3359. // first look for a player who is downloading the map and has the least amount downloaded so far
  3360. unsigned char LeastDownloaded = 100;
  3361. unsigned char LeastSID = 255;
  3362. for( unsigned char i = 0; i < m_Slots.size( ); ++i )
  3363. {
  3364. CGamePlayer *Player = GetPlayerFromSID( i );
  3365. if( Player && !Player->GetReserved( ) && m_Slots[i].GetDownloadStatus( ) < LeastDownloaded )
  3366. {
  3367. LeastDownloaded = m_Slots[i].GetDownloadStatus( );
  3368. LeastSID = i;
  3369. }
  3370. }
  3371. if( LeastSID != 255 )
  3372. return LeastSID;
  3373. // nobody who isn't reserved is downloading the map, just choose the first player who isn't reserved
  3374. for( unsigned char i = 0; i < m_Slots.size( ); ++i )
  3375. {
  3376. CGamePlayer *Player = GetPlayerFromSID( i );
  3377. if( Player && !Player->GetReserved( ) )
  3378. return i;
  3379. }
  3380. }
  3381. }
  3382. return 255;
  3383. }
  3384. unsigned char CBaseGame :: GetEmptySlot( unsigned char team, unsigned char PID )
  3385. {
  3386. if( m_Slots.size( ) > 255 )
  3387. return 255;
  3388. // find an empty slot based on player's current slot
  3389. unsigned char StartSlot = GetSIDFromPID( PID );
  3390. if( StartSlot < m_Slots.size( ) )
  3391. {
  3392. if( m_Slots[StartSlot].GetTeam( ) != team )
  3393. {
  3394. // player is trying to move to another team so start looking from the first slot on that team
  3395. // we actually just start looking from the very first slot since the next few loops will check the team for us
  3396. StartSlot = 0;
  3397. }
  3398. if( m_SaveGame )
  3399. {
  3400. vector<CGameSlot> SaveGameSlots = m_SaveGame->GetSlots( );
  3401. for( unsigned char i = StartSlot; i < m_Slots.size( ); ++i )
  3402. {
  3403. if( m_Slots[i].GetSlotStatus( ) == SLOTSTATUS_OPEN && m_Slots[i].GetTeam( ) == team && SaveGameSlots[i].GetSlotStatus( ) == SLOTSTATUS_OCCUPIED && SaveGameSlots[i].GetComputer( ) == 0 )
  3404. return i;
  3405. }
  3406. for( unsigned char i = 0; i < StartSlot; ++i )
  3407. {
  3408. if( m_Slots[i].GetSlotStatus( ) == SLOTSTATUS_OPEN && m_Slots[i].GetTeam( ) == team && SaveGameSlots[i].GetSlotStatus( ) == SLOTSTATUS_OCCUPIED && SaveGameSlots[i].GetComputer( ) == 0 )
  3409. return i;
  3410. }
  3411. }
  3412. else
  3413. {
  3414. // find an empty slot on the correct team starting from StartSlot
  3415. for( unsigned char i = StartSlot; i < m_Slots.size( ); ++i )
  3416. {
  3417. if( m_Slots[i].GetSlotStatus( ) == SLOTSTATUS_OPEN && m_Slots[i].GetTeam( ) == team )
  3418. return i;
  3419. }
  3420. // didn't find an empty slot, but we could have missed one with SID < StartSlot
  3421. // e.g. in the DotA case where I am in slot 4 (yellow), slot 5 (orange) is occupied, and slot 1 (blue) is open and I am trying to move to another slot
  3422. for( unsigned char i = 0; i < StartSlot; ++i )
  3423. {
  3424. if( m_Slots[i].GetSlotStatus( ) == SLOTSTATUS_OPEN && m_Slots[i].GetTeam( ) == team )
  3425. return i;
  3426. }
  3427. }
  3428. }
  3429. return 255;
  3430. }
  3431. void CBaseGame :: SwapSlots( unsigned char SID1, unsigned char SID2 )
  3432. {
  3433. if( SID1 < m_Slots.size( ) && SID2 < m_Slots.size( ) && SID1 != SID2 )
  3434. {
  3435. CGameSlot Slot1 = m_Slots[SID1];
  3436. CGameSlot Slot2 = m_Slots[SID2];
  3437. if( m_Map->GetMapOptions( ) & MAPOPT_FIXEDPLAYERSETTINGS )
  3438. {
  3439. // don't swap the team, colour, or race
  3440. m_Slots[SID1] = CGameSlot( Slot2.GetPID( ), Slot2.GetDownloadStatus( ), Slot2.GetSlotStatus( ), Slot2.GetComputer( ), Slot1.GetTeam( ), Slot1.GetColour( ), Slot1.GetRace( ), Slot2.GetComputerType( ), Slot2.GetHandicap( ) );
  3441. m_Slots[SID2] = CGameSlot( Slot1.GetPID( ), Slot1.GetDownloadStatus( ), Slot1.GetSlotStatus( ), Slot1.GetComputer( ), Slot2.GetTeam( ), Slot2.GetColour( ), Slot2.GetRace( ), Slot1.GetComputerType( ), Slot1.GetHandicap( ) );
  3442. }
  3443. else
  3444. {
  3445. // swap everything
  3446. if( m_Map->GetMapOptions( ) & MAPOPT_CUSTOMFORCES )
  3447. {
  3448. // except if custom forces is set, then we don't swap teams...
  3449. Slot1.SetTeam( m_Slots[SID2].GetTeam( ) );
  3450. Slot2.SetTeam( m_Slots[SID1].GetTeam( ) );
  3451. }
  3452. m_Slots[SID1] = Slot2;
  3453. m_Slots[SID2] = Slot1;
  3454. }
  3455. SendAllSlotInfo( );
  3456. }
  3457. }
  3458. void CBaseGame :: OpenSlot( unsigned char SID, bool kick )
  3459. {
  3460. if( SID < m_Slots.size( ) )
  3461. {
  3462. if( kick )
  3463. {
  3464. CGamePlayer *Player = GetPlayerFromSID( SID );
  3465. if( Player )
  3466. {
  3467. Player->SetDeleteMe( true );
  3468. Player->SetLeftReason( "was kicked when opening a slot" );
  3469. Player->SetLeftCode( PLAYERLEAVE_LOBBY );
  3470. }
  3471. }
  3472. CGameSlot Slot = m_Slots[SID];
  3473. m_Slots[SID] = CGameSlot( 0, 255, SLOTSTATUS_OPEN, 0, Slot.GetTeam( ), Slot.GetColour( ), Slot.GetRace( ) );
  3474. SendAllSlotInfo( );
  3475. }
  3476. }
  3477. void CBaseGame :: CloseSlot( unsigned char SID, bool kick )
  3478. {
  3479. if( SID < m_Slots.size( ) )
  3480. {
  3481. if( kick )
  3482. {
  3483. CGamePlayer *Player = GetPlayerFromSID( SID );
  3484. if( Player )
  3485. {
  3486. Player->SetDeleteMe( true );
  3487. Player->SetLeftReason( "was kicked when closing a slot" );
  3488. Player->SetLeftCode( PLAYERLEAVE_LOBBY );
  3489. }
  3490. }
  3491. CGameSlot Slot = m_Slots[SID];
  3492. m_Slots[SID] = CGameSlot( 0, 255, SLOTSTATUS_CLOSED, 0, Slot.GetTeam( ), Slot.GetColour( ), Slot.GetRace( ) );
  3493. SendAllSlotInfo( );
  3494. }
  3495. }
  3496. void CBaseGame :: ComputerSlot( unsigned char SID, unsigned char skill, bool kick )
  3497. {
  3498. if( SID < m_Slots.size( ) && skill < 3 )
  3499. {
  3500. if( kick )
  3501. {
  3502. CGamePlayer *Player = GetPlayerFromSID( SID );
  3503. if( Player )
  3504. {
  3505. Player->SetDeleteMe( true );
  3506. Player->SetLeftReason( "was kicked when creating a computer in a slot" );
  3507. Player->SetLeftCode( PLAYERLEAVE_LOBBY );
  3508. }
  3509. }
  3510. CGameSlot Slot = m_Slots[SID];
  3511. m_Slots[SID] = CGameSlot( 0, 100, SLOTSTATUS_OCCUPIED, 1, Slot.GetTeam( ), Slot.GetColour( ), Slot.GetRace( ), skill );
  3512. SendAllSlotInfo( );
  3513. }
  3514. }
  3515. void CBaseGame :: ColourSlot( unsigned char SID, unsigned char colour )
  3516. {
  3517. if( SID < m_Slots.size( ) && colour < 12 )
  3518. {
  3519. // make sure the requested colour isn't already taken
  3520. bool Taken = false;
  3521. unsigned char TakenSID = 0;
  3522. for( unsigned char i = 0; i < m_Slots.size( ); ++i )
  3523. {
  3524. if( m_Slots[i].GetColour( ) == colour )
  3525. {
  3526. TakenSID = i;
  3527. Taken = true;
  3528. }
  3529. }
  3530. if( Taken && m_Slots[TakenSID].GetSlotStatus( ) != SLOTSTATUS_OCCUPIED )
  3531. {
  3532. // the requested colour is currently "taken" by an unused (open or closed) slot
  3533. // but we allow the colour to persist within a slot so if we only update the existing player's colour the unused slot will have the same colour
  3534. // this isn't really a problem except that if someone then joins the game they'll receive the unused slot's colour resulting in a duplicate
  3535. // one way to solve this (which we do here) is to swap the player's current colour into the unused slot
  3536. m_Slots[TakenSID].SetColour( m_Slots[SID].GetColour( ) );
  3537. m_Slots[SID].SetColour( colour );
  3538. SendAllSlotInfo( );
  3539. }
  3540. else if( !Taken )
  3541. {
  3542. // the requested colour isn't used by ANY slot
  3543. m_Slots[SID].SetColour( colour );
  3544. SendAllSlotInfo( );
  3545. }
  3546. }
  3547. }
  3548. void CBaseGame :: OpenAllSlots( )
  3549. {
  3550. bool Changed = false;
  3551. for( vector<CGameSlot> :: iterator i = m_Slots.begin( ); i != m_Slots.end( ); ++i )
  3552. {
  3553. if( (*i).GetSlotStatus( ) == SLOTSTATUS_CLOSED )
  3554. {
  3555. (*i).SetSlotStatus( SLOTSTATUS_OPEN );
  3556. Changed = true;
  3557. }
  3558. }
  3559. if( Changed )
  3560. SendAllSlotInfo( );
  3561. }
  3562. void CBaseGame :: CloseAllSlots( )
  3563. {
  3564. bool Changed = false;
  3565. for( vector<CGameSlot> :: iterator i = m_Slots.begin( ); i != m_Slots.end( ); ++i )
  3566. {
  3567. if( (*i).GetSlotStatus( ) == SLOTSTATUS_OPEN )
  3568. {
  3569. (*i).SetSlotStatus( SLOTSTATUS_CLOSED );
  3570. Changed = true;
  3571. }
  3572. }
  3573. if( Changed )
  3574. SendAllSlotInfo( );
  3575. }
  3576. void CBaseGame :: ShuffleSlots( )
  3577. {
  3578. // we only want to shuffle the player slots
  3579. // that means we need to prevent this function from shuffling the open/closed/computer slots too
  3580. // so we start by copying the player slots to a temporary vector
  3581. vector<CGameSlot> PlayerSlots;
  3582. for( vector<CGameSlot> :: iterator i = m_Slots.begin( ); i != m_Slots.end( ); ++i )
  3583. {
  3584. if( (*i).GetSlotStatus( ) == SLOTSTATUS_OCCUPIED && (*i).GetComputer( ) == 0 && (*i).GetTeam( ) != 12 )
  3585. PlayerSlots.push_back( *i );
  3586. }
  3587. // now we shuffle PlayerSlots
  3588. if( m_Map->GetMapOptions( ) & MAPOPT_CUSTOMFORCES )
  3589. {
  3590. // rather than rolling our own probably broken shuffle algorithm we use random_shuffle because it's guaranteed to do it properly
  3591. // so in order to let random_shuffle do all the work we need a vector to operate on
  3592. // unfortunately we can't just use PlayerSlots because the team/colour/race shouldn't be modified
  3593. // so make a vector we can use
  3594. vector<unsigned char> SIDs;
  3595. for( unsigned char i = 0; i < PlayerSlots.size( ); ++i )
  3596. SIDs.push_back( i );
  3597. random_shuffle( SIDs.begin( ), SIDs.end( ) );
  3598. // now put the PlayerSlots vector in the same order as the SIDs vector
  3599. vector<CGameSlot> Slots;
  3600. // as usual don't modify the team/colour/race
  3601. for( unsigned char i = 0; i < SIDs.size( ); ++i )
  3602. Slots.push_back( CGameSlot( PlayerSlots[SIDs[i]].GetPID( ), PlayerSlots[SIDs[i]].GetDownloadStatus( ), PlayerSlots[SIDs[i]].GetSlotStatus( ), PlayerSlots[SIDs[i]].GetComputer( ), PlayerSlots[i].GetTeam( ), PlayerSlots[i].GetColour( ), PlayerSlots[i].GetRace( ) ) );
  3603. PlayerSlots = Slots;
  3604. }
  3605. else
  3606. {
  3607. // regular game
  3608. // it's easy when we're allowed to swap the team/colour/race!
  3609. random_shuffle( PlayerSlots.begin( ), PlayerSlots.end( ) );
  3610. }
  3611. // now we put m_Slots back together again
  3612. vector<CGameSlot> :: iterator CurrentPlayer = PlayerSlots.begin( );
  3613. vector<CGameSlot> Slots;
  3614. for( vector<CGameSlot> :: iterator i = m_Slots.begin( ); i != m_Slots.end( ); ++i )
  3615. {
  3616. if( (*i).GetSlotStatus( ) == SLOTSTATUS_OCCUPIED && (*i).GetComputer( ) == 0 && (*i).GetTeam( ) != 12 )
  3617. {
  3618. Slots.push_back( *CurrentPlayer );
  3619. ++CurrentPlayer;
  3620. }
  3621. else
  3622. Slots.push_back( *i );
  3623. }
  3624. m_Slots = Slots;
  3625. // and finally tell everyone about the new slot configuration
  3626. SendAllSlotInfo( );
  3627. }
  3628. vector<unsigned char> CBaseGame :: BalanceSlotsRecursive( vector<unsigned char> PlayerIDs, unsigned char *TeamSizes, double *PlayerScores, unsigned char StartTeam )
  3629. {
  3630. // take a brute force approach to finding the best balance by iterating through every possible combination of players
  3631. // 1.) since the number of teams is arbitrary this algorithm must be recursive
  3632. // 2.) on the first recursion step every possible combination of players into two "teams" is checked, where the first team is the correct size and the second team contains everyone else
  3633. // 3.) on the next recursion step every possible combination of the remaining players into two more "teams" is checked, continuing until all the actual teams are accounted for
  3634. // 4.) for every possible combination, check the largest difference in total scores between any two actual teams
  3635. // 5.) minimize this value by choosing the combination of players with the smallest difference
  3636. vector<unsigned char> BestOrdering = PlayerIDs;
  3637. double BestDifference = -1.0;
  3638. for( unsigned char i = StartTeam; i < 12; ++i )
  3639. {
  3640. if( TeamSizes[i] > 0 )
  3641. {
  3642. unsigned char Mid = TeamSizes[i];
  3643. // the base case where only one actual team worth of players was passed to this function is handled by the behaviour of next_combination
  3644. // in this case PlayerIDs.begin( ) + Mid will actually be equal to PlayerIDs.end( ) and next_combination will return false
  3645. while( next_combination( PlayerIDs.begin( ), PlayerIDs.begin( ) + Mid, PlayerIDs.end( ) ) )
  3646. {
  3647. // we're splitting the players into every possible combination of two "teams" based on the midpoint Mid
  3648. // the first (left) team contains the correct number of players but the second (right) "team" might or might not
  3649. // for example, it could contain one, two, or more actual teams worth of players
  3650. // so recurse using the second "team" as the full set of players to perform the balancing on
  3651. vector<unsigned char> BestSubOrdering = BalanceSlotsRecursive( vector<unsigned char>( PlayerIDs.begin( ) + Mid, PlayerIDs.end( ) ), TeamSizes, PlayerScores, i + 1 );
  3652. // BestSubOrdering now contains the best ordering of all the remaining players (the "right team") given this particular combination of players into two "teams"
  3653. // in order to calculate the largest difference in total scores we need to recombine the subordering with the first team
  3654. vector<unsigned char> TestOrdering = vector<unsigned char>( PlayerIDs.begin( ), PlayerIDs.begin( ) + Mid );
  3655. TestOrdering.insert( TestOrdering.end( ), BestSubOrdering.begin( ), BestSubOrdering.end( ) );
  3656. // now calculate the team scores for all the teams that we know about (e.g. on subsequent recursion steps this will NOT be every possible team)
  3657. vector<unsigned char> :: iterator CurrentPID = TestOrdering.begin( );
  3658. double TeamScores[12];
  3659. for( unsigned char j = StartTeam; j < 12; ++j )
  3660. {
  3661. TeamScores[j] = 0.0;
  3662. for( unsigned char k = 0; k < TeamSizes[j]; ++k )
  3663. {
  3664. TeamScores[j] += PlayerScores[*CurrentPID];
  3665. ++CurrentPID;
  3666. }
  3667. }
  3668. // find the largest difference in total scores between any two teams
  3669. double LargestDifference = 0.0;
  3670. for( unsigned char j = StartTeam; j < 12; ++j )
  3671. {
  3672. if( TeamSizes[j] > 0 )
  3673. {
  3674. for( unsigned char k = j + 1; k < 12; ++k )
  3675. {
  3676. if( TeamSizes[k] > 0 )
  3677. {
  3678. double Difference = abs( TeamScores[j] - TeamScores[k] );
  3679. if( Difference > LargestDifference )
  3680. LargestDifference = Difference;
  3681. }
  3682. }
  3683. }
  3684. }
  3685. // and minimize it
  3686. if( BestDifference < 0.0 || LargestDifference < BestDifference )
  3687. {
  3688. BestOrdering = TestOrdering;
  3689. BestDifference = LargestDifference;
  3690. }
  3691. }
  3692. }
  3693. }
  3694. return BestOrdering;
  3695. }
  3696. void CBaseGame :: BalanceSlots( )
  3697. {
  3698. if( !( m_Map->GetMapOptions( ) & MAPOPT_FIXEDPLAYERSETTINGS ) )
  3699. {
  3700. CONSOLE_Print( "[GAME: " + m_GameName + "] error balancing slots - can't balance slots without fixed player settings" );
  3701. return;
  3702. }
  3703. // setup the necessary variables for the balancing algorithm
  3704. // use an array of 13 elements for 12 players because GHost++ allocates PID's from 1-12 (i.e. excluding 0) and we use the PID to index the array
  3705. vector<unsigned char> PlayerIDs;
  3706. unsigned char TeamSizes[12];
  3707. double PlayerScores[13];
  3708. memset( TeamSizes, 0, sizeof( unsigned char ) * 12 );
  3709. for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); ++i )
  3710. {
  3711. unsigned char PID = (*i)->GetPID( );
  3712. if( PID < 13 )
  3713. {
  3714. unsigned char SID = GetSIDFromPID( PID );
  3715. if( SID < m_Slots.size( ) )
  3716. {
  3717. unsigned char Team = m_Slots[SID].GetTeam( );
  3718. if( Team < 12 )
  3719. {
  3720. // we are forced to use a default score because there's no way to balance the teams otherwise
  3721. double Score = (*i)->GetScore( );
  3722. if( Score < -99999.0 )
  3723. Score = m_Map->GetMapDefaultPlayerScore( );
  3724. PlayerIDs.push_back( PID );
  3725. TeamSizes[Team]++;
  3726. PlayerScores[PID] = Score;
  3727. }
  3728. }
  3729. }
  3730. }
  3731. sort( PlayerIDs.begin( ), PlayerIDs.end( ) );
  3732. // balancing the teams is a variation of the bin packing problem which is NP
  3733. // we can have up to 12 players and/or teams so the scope of the problem is sometimes small enough to process quickly
  3734. // let's try to figure out roughly how much work this is going to take
  3735. // examples:
  3736. // 2 teams of 4 = 70 ~ 5ms *** ok
  3737. // 2 teams of 5 = 252 ~ 5ms *** ok
  3738. // 2 teams of 6 = 924 ~ 20ms *** ok
  3739. // 3 teams of 2 = 90 ~ 5ms *** ok
  3740. // 3 teams of 3 = 1680 ~ 25ms *** ok
  3741. // 3 teams of 4 = 34650 ~ 250ms *** will cause a lag spike
  3742. // 4 teams of 2 = 2520 ~ 30ms *** ok
  3743. // 4 teams of 3 = 369600 ~ 3500ms *** unacceptable
  3744. uint32_t AlgorithmCost = 0;
  3745. uint32_t PlayersLeft = PlayerIDs.size( );
  3746. for( unsigned char i = 0; i < 12; ++i )
  3747. {
  3748. if( TeamSizes[i] > 0 )
  3749. {
  3750. if( AlgorithmCost == 0 )
  3751. AlgorithmCost = nCr( PlayersLeft, TeamSizes[i] );
  3752. else
  3753. AlgorithmCost *= nCr( PlayersLeft, TeamSizes[i] );
  3754. PlayersLeft -= TeamSizes[i];
  3755. }
  3756. }
  3757. if( AlgorithmCost > 40000 )
  3758. {
  3759. // the cost is too high, don't run the algorithm
  3760. // a possible alternative: stop after enough iterations and/or time has passed
  3761. CONSOLE_Print( "[GAME: " + m_GameName + "] shuffling slots instead of balancing - the algorithm is too slow (with a cost of " + UTIL_ToString( AlgorithmCost ) + ") for this team configuration" );
  3762. SendAllChat( m_GHost->m_Language->ShufflingPlayers( ) );
  3763. ShuffleSlots( );
  3764. return;
  3765. }
  3766. uint32_t StartTicks = GetTicks( );
  3767. vector<unsigned char> BestOrdering = BalanceSlotsRecursive( PlayerIDs, TeamSizes, PlayerScores, 0 );
  3768. uint32_t EndTicks = GetTicks( );
  3769. // the BestOrdering assumes the teams are in slot order although this may not be the case
  3770. // so put the players on the correct teams regardless of slot order
  3771. vector<unsigned char> :: iterator CurrentPID = BestOrdering.begin( );
  3772. for( unsigned char i = 0; i < 12; ++i )
  3773. {
  3774. unsigned char CurrentSlot = 0;
  3775. for( unsigned char j = 0; j < TeamSizes[i]; ++j )
  3776. {
  3777. while( CurrentSlot < m_Slots.size( ) && m_Slots[CurrentSlot].GetTeam( ) != i )
  3778. ++CurrentSlot;
  3779. // put the CurrentPID player on team i by swapping them into CurrentSlot
  3780. unsigned char SID1 = CurrentSlot;
  3781. unsigned char SID2 = GetSIDFromPID( *CurrentPID );
  3782. if( SID1 < m_Slots.size( ) && SID2 < m_Slots.size( ) )
  3783. {
  3784. CGameSlot Slot1 = m_Slots[SID1];
  3785. CGameSlot Slot2 = m_Slots[SID2];
  3786. m_Slots[SID1] = CGameSlot( Slot2.GetPID( ), Slot2.GetDownloadStatus( ), Slot2.GetSlotStatus( ), Slot2.GetComputer( ), Slot1.GetTeam( ), Slot1.GetColour( ), Slot1.GetRace( ) );
  3787. m_Slots[SID2] = CGameSlot( Slot1.GetPID( ), Slot1.GetDownloadStatus( ), Slot1.GetSlotStatus( ), Slot1.GetComputer( ), Slot2.GetTeam( ), Slot2.GetColour( ), Slot2.GetRace( ) );
  3788. }
  3789. else
  3790. {
  3791. CONSOLE_Print( "[GAME: " + m_GameName + "] shuffling slots instead of balancing - the balancing algorithm tried to do an invalid swap (this shouldn't happen)" );
  3792. SendAllChat( m_GHost->m_Language->ShufflingPlayers( ) );
  3793. ShuffleSlots( );
  3794. return;
  3795. }
  3796. ++CurrentPID;
  3797. ++CurrentSlot;
  3798. }
  3799. }
  3800. CONSOLE_Print( "[GAME: " + m_GameName + "] balancing slots completed in " + UTIL_ToString( EndTicks - StartTicks ) + "ms (with a cost of " + UTIL_ToString( AlgorithmCost ) + ")" );
  3801. SendAllChat( m_GHost->m_Language->BalancingSlotsCompleted( ) );
  3802. SendAllSlotInfo( );
  3803. for( unsigned char i = 0; i < 12; ++i )
  3804. {
  3805. bool TeamHasPlayers = false;
  3806. double TeamScore = 0.0;
  3807. for( vector<CGamePlayer *> :: iterator j = m_Players.begin( ); j != m_Players.end( ); ++j )
  3808. {
  3809. unsigned char SID = GetSIDFromPID( (*j)->GetPID( ) );
  3810. if( SID < m_Slots.size( ) && m_Slots[SID].GetTeam( ) == i )
  3811. {
  3812. TeamHasPlayers = true;
  3813. double Score = (*j)->GetScore( );
  3814. if( Score < -99999.0 )
  3815. Score = m_Map->GetMapDefaultPlayerScore( );
  3816. TeamScore += Score;
  3817. }
  3818. }
  3819. if( TeamHasPlayers )
  3820. SendAllChat( m_GHost->m_Language->TeamCombinedScore( UTIL_ToString( i + 1 ), UTIL_ToString( TeamScore, 2 ) ) );
  3821. }
  3822. }
  3823. void CBaseGame :: AddToSpoofed( string server, string name, bool sendMessage )
  3824. {
  3825. CGamePlayer *Player = GetPlayerFromName( name, true );
  3826. if( Player )
  3827. {
  3828. Player->SetSpoofedRealm( server );
  3829. Player->SetSpoofed( true );
  3830. if( sendMessage )
  3831. SendAllChat( m_GHost->m_Language->SpoofCheckAcceptedFor( server, name ) );
  3832. }
  3833. }
  3834. void CBaseGame :: AddToReserved( string name )
  3835. {
  3836. transform( name.begin( ), name.end( ), name.begin( ), (int(*)(int))tolower );
  3837. // check that the user is not already reserved
  3838. for( vector<string> :: iterator i = m_Reserved.begin( ); i != m_Reserved.end( ); ++i )
  3839. {
  3840. if( *i == name )
  3841. return;
  3842. }
  3843. m_Reserved.push_back( name );
  3844. // upgrade the user if they're already in the game
  3845. for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); ++i )
  3846. {
  3847. string NameLower = (*i)->GetName( );
  3848. transform( NameLower.begin( ), NameLower.end( ), NameLower.begin( ), (int(*)(int))tolower );
  3849. if( NameLower == name )
  3850. (*i)->SetReserved( true );
  3851. }
  3852. }
  3853. bool CBaseGame :: IsOwner( string name )
  3854. {
  3855. string OwnerLower = m_OwnerName;
  3856. transform( name.begin( ), name.end( ), name.begin( ), (int(*)(int))tolower );
  3857. transform( OwnerLower.begin( ), OwnerLower.end( ), OwnerLower.begin( ), (int(*)(int))tolower );
  3858. return name == OwnerLower;
  3859. }
  3860. bool CBaseGame :: IsReserved( string name )
  3861. {
  3862. transform( name.begin( ), name.end( ), name.begin( ), (int(*)(int))tolower );
  3863. for( vector<string> :: iterator i = m_Reserved.begin( ); i != m_Reserved.end( ); ++i )
  3864. {
  3865. if( *i == name )
  3866. return true;
  3867. }
  3868. return false;
  3869. }
  3870. bool CBaseGame :: IsDownloading( )
  3871. {
  3872. // returns true if at least one player is downloading the map
  3873. for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); ++i )
  3874. {
  3875. if( (*i)->GetDownloadStarted( ) && !(*i)->GetDownloadFinished( ) )
  3876. return true;
  3877. }
  3878. return false;
  3879. }
  3880. bool CBaseGame :: IsGameDataSaved( )
  3881. {
  3882. return true;
  3883. }
  3884. void CBaseGame :: SaveGameData( )
  3885. {
  3886. }
  3887. void CBaseGame :: StartCountDown( bool force )
  3888. {
  3889. if( !m_CountDownStarted )
  3890. {
  3891. if( force )
  3892. {
  3893. m_CountDownStarted = true;
  3894. m_CountDownCounter = 5;
  3895. if ( m_GHost->m_UseNormalCountDown )
  3896. SendAll( m_Protocol->SEND_W3GS_COUNTDOWN_START( ) );
  3897. }
  3898. else
  3899. {
  3900. // check if the HCL command string is short enough
  3901. if( m_HCLCommandString.size( ) > GetSlotsOccupied( ) )
  3902. {
  3903. SendAllChat( m_GHost->m_Language->TheHCLIsTooLongUseForceToStart( ) );
  3904. return;
  3905. }
  3906. // check if everyone has the map
  3907. string StillDownloading;
  3908. for( vector<CGameSlot> :: iterator i = m_Slots.begin( ); i != m_Slots.end( ); ++i )
  3909. {
  3910. if( (*i).GetSlotStatus( ) == SLOTSTATUS_OCCUPIED && (*i).GetComputer( ) == 0 && (*i).GetDownloadStatus( ) != 100 )
  3911. {
  3912. CGamePlayer *Player = GetPlayerFromPID( (*i).GetPID( ) );
  3913. if( Player )
  3914. {
  3915. if( StillDownloading.empty( ) )
  3916. StillDownloading = Player->GetName( );
  3917. else
  3918. StillDownloading += ", " + Player->GetName( );
  3919. }
  3920. }
  3921. }
  3922. if( !StillDownloading.empty( ) )
  3923. SendAllChat( m_GHost->m_Language->PlayersStillDownloading( StillDownloading ) );
  3924. // check if everyone is spoof checked
  3925. string NotSpoofChecked;
  3926. if( m_GHost->m_RequireSpoofChecks )
  3927. {
  3928. for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); ++i )
  3929. {
  3930. if( !(*i)->GetSpoofed( ) )
  3931. {
  3932. if( NotSpoofChecked.empty( ) )
  3933. NotSpoofChecked = (*i)->GetName( );
  3934. else
  3935. NotSpoofChecked += ", " + (*i)->GetName( );
  3936. }
  3937. }
  3938. if( !NotSpoofChecked.empty( ) )
  3939. SendAllChat( m_GHost->m_Language->PlayersNotYetSpoofChecked( NotSpoofChecked ) );
  3940. }
  3941. // check if everyone has been pinged enough (3 times) that the autokicker would have kicked them by now
  3942. // see function EventPlayerPongToHost for the autokicker code
  3943. string NotPinged;
  3944. for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); ++i )
  3945. {
  3946. if( !(*i)->GetReserved( ) && (*i)->GetNumPings( ) < 3 )
  3947. {
  3948. if( NotPinged.empty( ) )
  3949. NotPinged = (*i)->GetName( );
  3950. else
  3951. NotPinged += ", " + (*i)->GetName( );
  3952. }
  3953. }
  3954. if( !NotPinged.empty( ) )
  3955. SendAllChat( m_GHost->m_Language->PlayersNotYetPinged( NotPinged ) );
  3956. // if no problems found start the game
  3957. if( StillDownloading.empty( ) && NotSpoofChecked.empty( ) && NotPinged.empty( ) )
  3958. {
  3959. m_CountDownStarted = true;
  3960. m_CountDownCounter = 5;
  3961. if ( m_GHost->m_UseNormalCountDown )
  3962. SendAll( m_Protocol->SEND_W3GS_COUNTDOWN_START( ) );
  3963. }
  3964. }
  3965. }
  3966. }
  3967. void CBaseGame :: StartCountDownAuto( bool requireSpoofChecks )
  3968. {
  3969. if( !m_CountDownStarted )
  3970. {
  3971. // check if enough players are present
  3972. if( !m_UsingStart && GetNumHumanPlayers( ) < m_AutoStartPlayers )
  3973. {
  3974. SendAllChat( m_GHost->m_Language->WaitingForPlayersBeforeAutoStart( UTIL_ToString( m_AutoStartPlayers ), UTIL_ToString( m_AutoStartPlayers - GetNumHumanPlayers( ) ) ) );
  3975. return;
  3976. }
  3977. // check if everyone has the map
  3978. string StillDownloading;
  3979. for( vector<CGameSlot> :: iterator i = m_Slots.begin( ); i != m_Slots.end( ); ++i )
  3980. {
  3981. if( (*i).GetSlotStatus( ) == SLOTSTATUS_OCCUPIED && (*i).GetComputer( ) == 0 && (*i).GetDownloadStatus( ) != 100 )
  3982. {
  3983. CGamePlayer *Player = GetPlayerFromPID( (*i).GetPID( ) );
  3984. if( Player )
  3985. {
  3986. if( StillDownloading.empty( ) )
  3987. StillDownloading = Player->GetName( );
  3988. else
  3989. StillDownloading += ", " + Player->GetName( );
  3990. }
  3991. }
  3992. }
  3993. if( !StillDownloading.empty( ) )
  3994. {
  3995. SendAllChat( m_GHost->m_Language->PlayersStillDownloading( StillDownloading ) );
  3996. return;
  3997. }
  3998. // check if everyone is spoof checked
  3999. string NotSpoofChecked;
  4000. if( requireSpoofChecks )
  4001. {
  4002. for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); ++i )
  4003. {
  4004. if( !(*i)->GetSpoofed( ) )
  4005. {
  4006. if( NotSpoofChecked.empty( ) )
  4007. NotSpoofChecked = (*i)->GetName( );
  4008. else
  4009. NotSpoofChecked += ", " + (*i)->GetName( );
  4010. }
  4011. }
  4012. if( !NotSpoofChecked.empty( ) )
  4013. SendAllChat( m_GHost->m_Language->PlayersNotYetSpoofChecked( NotSpoofChecked ) );
  4014. }
  4015. // check if everyone has been pinged enough (3 times) that the autokicker would have kicked them by now
  4016. // see function EventPlayerPongToHost for the autokicker code
  4017. string NotPinged;
  4018. for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); ++i )
  4019. {
  4020. if( !(*i)->GetReserved( ) && (*i)->GetNumPings( ) < 3 )
  4021. {
  4022. if( NotPinged.empty( ) )
  4023. NotPinged = (*i)->GetName( );
  4024. else
  4025. NotPinged += ", " + (*i)->GetName( );
  4026. }
  4027. }
  4028. if( !NotPinged.empty( ) )
  4029. {
  4030. SendAllChat( m_GHost->m_Language->PlayersNotYetPingedAutoStart( NotPinged ) );
  4031. return;
  4032. }
  4033. // if no problems found start the game
  4034. if( StillDownloading.empty( ) && NotSpoofChecked.empty( ) && NotPinged.empty( ) )
  4035. {
  4036. m_CountDownStarted = true;
  4037. m_CountDownCounter = 5;
  4038. if ( m_GHost->m_UseNormalCountDown )
  4039. SendAll( m_Protocol->SEND_W3GS_COUNTDOWN_START( ) );
  4040. }
  4041. }
  4042. }
  4043. void CBaseGame :: StopPlayers( string reason )
  4044. {
  4045. // disconnect every player and set their left reason to the passed string
  4046. // we use this function when we want the code in the Update function to run before the destructor (e.g. saving players to the database)
  4047. // therefore calling this function when m_GameLoading || m_GameLoaded is roughly equivalent to setting m_Exiting = true
  4048. // the only difference is whether the code in the Update function is executed or not
  4049. for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); ++i )
  4050. {
  4051. (*i)->SetDeleteMe( true );
  4052. (*i)->SetLeftReason( reason );
  4053. (*i)->SetLeftCode( PLAYERLEAVE_LOST );
  4054. }
  4055. }
  4056. void CBaseGame :: StopLaggers( string reason )
  4057. {
  4058. for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); ++i )
  4059. {
  4060. if( (*i)->GetLagging( ) )
  4061. {
  4062. (*i)->SetDeleteMe( true );
  4063. (*i)->SetLeftReason( reason );
  4064. (*i)->SetLeftCode( PLAYERLEAVE_DISCONNECT );
  4065. }
  4066. }
  4067. }
  4068. void CBaseGame :: CreateVirtualHost( )
  4069. {
  4070. if( m_VirtualHostPID != 255 )
  4071. return;
  4072. m_VirtualHostPID = GetNewPID( );
  4073. BYTEARRAY IP;
  4074. IP.push_back( 0 );
  4075. IP.push_back( 0 );
  4076. IP.push_back( 0 );
  4077. IP.push_back( 0 );
  4078. SendAll( m_Protocol->SEND_W3GS_PLAYERINFO( m_VirtualHostPID, m_VirtualHostName, IP, IP ) );
  4079. }
  4080. void CBaseGame :: DeleteVirtualHost( )
  4081. {
  4082. if( m_VirtualHostPID == 255 )
  4083. return;
  4084. SendAll( m_Protocol->SEND_W3GS_PLAYERLEAVE_OTHERS( m_VirtualHostPID, PLAYERLEAVE_LOBBY ) );
  4085. m_VirtualHostPID = 255;
  4086. }
  4087. void CBaseGame :: CreateFakePlayer( )
  4088. {
  4089. if( m_FakePlayerPID != 255 )
  4090. return;
  4091. unsigned char SID = GetEmptySlot( false );
  4092. if( SID < m_Slots.size( ) )
  4093. {
  4094. if( GetNumPlayers( ) >= 11 )
  4095. DeleteVirtualHost( );
  4096. m_FakePlayerPID = GetNewPID( );
  4097. BYTEARRAY IP;
  4098. IP.push_back( 0 );
  4099. IP.push_back( 0 );
  4100. IP.push_back( 0 );
  4101. IP.push_back( 0 );
  4102. SendAll( m_Protocol->SEND_W3GS_PLAYERINFO( m_FakePlayerPID, "FakePlayer", IP, IP ) );
  4103. m_Slots[SID] = CGameSlot( m_FakePlayerPID, 100, SLOTSTATUS_OCCUPIED, 0, m_Slots[SID].GetTeam( ), m_Slots[SID].GetColour( ), m_Slots[SID].GetRace( ) );
  4104. SendAllSlotInfo( );
  4105. }
  4106. }
  4107. void CBaseGame :: DeleteFakePlayer( )
  4108. {
  4109. if( m_FakePlayerPID == 255 )
  4110. return;
  4111. for( unsigned char i = 0; i < m_Slots.size( ); ++i )
  4112. {
  4113. if( m_Slots[i].GetPID( ) == m_FakePlayerPID )
  4114. m_Slots[i] = CGameSlot( 0, 255, SLOTSTATUS_OPEN, 0, m_Slots[i].GetTeam( ), m_Slots[i].GetColour( ), m_Slots[i].GetRace( ) );
  4115. }
  4116. SendAll( m_Protocol->SEND_W3GS_PLAYERLEAVE_OTHERS( m_FakePlayerPID, PLAYERLEAVE_LOBBY ) );
  4117. SendAllSlotInfo( );
  4118. m_FakePlayerPID = 255;
  4119. }
  4120. string CBaseGame :: ColourValueToString( unsigned char value )
  4121. {
  4122. switch( value )
  4123. {
  4124. case 0: return "Red";
  4125. case 1: return "Blue";
  4126. case 2: return "Teal";
  4127. case 3: return "Purple";
  4128. case 4: return "Yellow";
  4129. case 5: return "Orange";
  4130. case 6: return "Green";
  4131. case 7: return "Pink";
  4132. case 8: return "Gray";
  4133. case 9: return "Light Blue";
  4134. case 10: return "Dark Green";
  4135. case 11: return "Brown";
  4136. default: return "---";
  4137. }
  4138. }