rendered paste body/* Copyright [2008] [Trevor Hogan] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. CODE PORTED FROM THE ORIGINAL GHOST PROJECT: http://ghost.pwner.org/*/#include "ghost.h"#include "util.h"#include "config.h"#include "language.h"#include "socket.h"#include "ghostdb.h"#include "bnet.h"#include "map.h"#include "packed.h"#include "savegame.h"#include "replay.h"#include "gameplayer.h"#include "gameprotocol.h"#include "game.h"#include "stats.h"#include "statsdota.h"#include <time.h>//// CBaseGame//CBaseGame :: CBaseGame( CGHost *nGHost, CMap *nMap, CSaveGame *nSaveGame, uint16_t nHostPort, unsigned char nGameState, string nGameName, string nOwnerName, string nCreatorName, string nCreatorServer ){ m_GHost = nGHost; m_Socket = new CTCPServer( ); m_Protocol = new CGameProtocol( m_GHost ); m_Map = nMap; m_SaveGame = nSaveGame; if( m_GHost->m_SaveReplays && !m_SaveGame ) m_Replay = new CReplay( m_GHost ); else m_Replay = NULL; m_Exiting = false; m_HostPort = nHostPort; m_GameState = nGameState; m_VirtualHostPID = 255; m_GameName = nGameName; m_VirtualHostName = "|cffFF0000Red|rHhost"; m_OwnerName = nOwnerName; m_CreatorName = nCreatorName; m_CreatorServer = nCreatorServer; m_RandomSeed = GetTicks( ); m_HostCounter = m_GHost->m_HostCounter++; m_Latency = m_GHost->m_Latency; m_SyncLimit = m_GHost->m_SyncLimit; m_MaxSyncCounter = 0; m_GameTicks = 0; m_CreationTime = GetTime( ); m_LastPingTime = GetTime( ); m_LastRefreshTime = GetTime( ); m_LastDLCounterTicks = GetTime( ); m_DLCounter = 0; m_LastAnnounceTime = 0; m_AnnounceInterval = 0; m_LastAutoStartTime = GetTime( ); m_AutoStartPlayers = 0; m_LastCountDownTicks = 0; m_CountDownCounter = 0; m_StartedLoadingTicks = 0; m_StartedLoadingTime = 0; m_StartPlayers = 0; m_LastActionSentTicks = 0; m_LastLagScreenTime = 0; m_Locked = false; m_RefreshMessages = m_GHost->m_RefreshMessages; m_MuteAll = false; m_MuteLobby = false; m_CountDownStarted = false; m_GameLoading = false; m_GameLoaded = false; m_Desynced = false; m_Lagging = false; m_AutoSave = m_GHost->m_AutoSave; if( m_SaveGame ) { m_Slots = m_SaveGame->GetSlots( ); // the savegame slots contain player entries // we really just want the open/closed/computer entries // so open all the player slots for( vector<CGameSlot> :: iterator i = m_Slots.begin( ); i != m_Slots.end( ); i++ ) { if( (*i).GetSlotStatus( ) == SLOTSTATUS_OCCUPIED && (*i).GetComputer( ) == 0 ) { (*i).SetPID( 0 ); (*i).SetDownloadStatus( 255 ); (*i).SetSlotStatus( SLOTSTATUS_OPEN ); } } } else m_Slots = m_Map->GetSlots( ); // start listening for connections if( m_Socket->Listen( string( ), m_HostPort ) ) CONSOLE_Print( "[GAME: " + m_GameName + "] listening on port " + UTIL_ToString( m_HostPort ) ); else { CONSOLE_Print( "[GAME: " + m_GameName + "] error listening on port " + UTIL_ToString( m_HostPort ) ); m_Exiting = true; }}CBaseGame :: ~CBaseGame( ){ // save replay if( m_Replay && ( m_GameLoading || m_GameLoaded ) ) { time_t Now = time( NULL ); char Time[17]; memset( Time, 0, sizeof( char ) * 17 ); strftime( Time, sizeof( char ) * 17, "%Y-%m-%d %H-%M", localtime( &Now ) ); string MinString = UTIL_ToString( ( m_GameTicks / 1000 ) / 60 ); string SecString = UTIL_ToString( ( m_GameTicks / 1000 ) % 60 ); if( MinString.size( ) == 1 ) MinString.insert( 0, "0" ); if( SecString.size( ) == 1 ) SecString.insert( 0, "0" ); m_Replay->BuildReplay( m_GameName, m_StatString ); m_Replay->Save( m_GHost->m_ReplayPath + UTIL_FileSafeName( "GHost++ " + string( Time ) + " " + m_GameName + " (" + MinString + "m" + SecString + "s).w3g" ) ); } delete m_Socket; delete m_Protocol; delete m_Replay; for( vector<CPotentialPlayer *> :: iterator i = m_Potentials.begin( ); i != m_Potentials.end( ); i++ ) delete *i; for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) delete *i; while( !m_Actions.empty( ) ) { delete m_Actions.front( ); m_Actions.pop( ); }}uint32_t CBaseGame :: GetSlotsOpen( ){ uint32_t NumSlotsOpen = 0; for( vector<CGameSlot> :: iterator i = m_Slots.begin( ); i != m_Slots.end( ); i++ ) { if( (*i).GetSlotStatus( ) == SLOTSTATUS_OPEN ) NumSlotsOpen++; } return NumSlotsOpen;}uint32_t CBaseGame :: GetNumPlayers( ){ uint32_t NumPlayers = 0; for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) { if( !(*i)->GetLeftMessageSent( ) ) NumPlayers++; } return NumPlayers;}string CBaseGame :: GetDescription( ){ string Description = m_GameName + " : " + m_OwnerName + " : " + UTIL_ToString( GetNumPlayers( ) ) + "/" + UTIL_ToString( m_GameLoading || m_GameLoaded ? m_StartPlayers : m_Slots.size( ) ); if( m_GameLoading || m_GameLoaded ) Description += " : " + UTIL_ToString( ( m_GameTicks / 1000 ) / 60 ) + "m"; else Description += " : " + UTIL_ToString( ( GetTime( ) - m_CreationTime ) / 60 ) + "m"; return Description;}void CBaseGame :: SetAnnounce( uint32_t interval, string message ){ m_AnnounceInterval = interval; m_AnnounceMessage = message; m_LastAnnounceTime = GetTime( );}unsigned int CBaseGame :: SetFD( void *fd, int *nfds ){ unsigned int NumFDs = 0; if( m_Socket ) { m_Socket->SetFD( (fd_set *)fd, nfds ); NumFDs++; } for( vector<CPotentialPlayer *> :: iterator i = m_Potentials.begin( ); i != m_Potentials.end( ); i++ ) { if( (*i)->GetSocket( ) ) { (*i)->GetSocket( )->SetFD( (fd_set *)fd, nfds ); NumFDs++; } } for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) { if( (*i)->GetSocket( ) ) { (*i)->GetSocket( )->SetFD( (fd_set *)fd, nfds ); NumFDs++; } } return NumFDs;}bool CBaseGame :: Update( void *fd ){ // update players for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); ) { if( (*i)->Update( fd ) ) { EventPlayerDeleted( *i ); delete *i; i = m_Players.erase( i ); } else i++; } for( vector<CPotentialPlayer *> :: iterator i = m_Potentials.begin( ); i != m_Potentials.end( ); ) { if( (*i)->Update( fd ) ) { delete *i; i = m_Potentials.erase( i ); } else i++; } // create the virtual host player if( !m_GameLoading && !m_GameLoaded && GetNumPlayers( ) < 12 ) CreateVirtualHost( ); // unlock the game if( m_Locked && !GetPlayerFromName( m_OwnerName, false ) ) { SendAllChat( m_GHost->m_Language->GameUnlocked( ) ); m_Locked = false; } // ping every 5 seconds // changed this to ping during game loading as well to hopefully fix some problems with people disconnecting during loading // changed this to ping during the game as well if( GetTime( ) >= m_LastPingTime + 5 ) { // don't send pings to players who are downloading for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) { if( !(*i)->GetDownloadStarted( ) || (*i)->GetDownloadFinished( ) ) Send( (*i), m_Protocol->SEND_W3GS_PING_FROM_HOST( ) ); } // we also broadcast the game to the local network every 5 seconds so we hijack this timer for our nefarious purposes // however we only want to broadcast if the countdown hasn't started // see the !sendlan code later in this file for some more information about how this works // todotodo: should we send a game cancel message somewhere? we'll need to implement a host counter for it to work if( !m_CountDownStarted ) { BYTEARRAY MapGameType; // construct the correct W3GS_GAMEINFO packet if( m_SaveGame ) { MapGameType.push_back( 0 ); MapGameType.push_back( 2 ); MapGameType.push_back( 0 ); MapGameType.push_back( 0 ); BYTEARRAY MapWidth; MapWidth.push_back( 0 ); MapWidth.push_back( 0 ); BYTEARRAY MapHeight; MapHeight.push_back( 0 ); MapHeight.push_back( 0 ); m_GHost->m_UDPSocket->Broadcast( 6112, m_Protocol->SEND_W3GS_GAMEINFO( MapGameType, m_Map->GetMapGameFlags( ), MapWidth, MapHeight, m_GameName, "Varlock", GetTime( ) - m_CreationTime, "Save\\Multiplayer\\" + m_SaveGame->GetFileNameNoPath( ), m_SaveGame->GetMagicNumber( ), 12, 12, m_HostPort, m_HostCounter ) ); } else { MapGameType.push_back( m_Map->GetMapGameType( ) ); MapGameType.push_back( 0 ); MapGameType.push_back( 0 ); MapGameType.push_back( 0 ); m_GHost->m_UDPSocket->Broadcast( 6112, m_Protocol->SEND_W3GS_GAMEINFO( MapGameType, m_Map->GetMapGameFlags( ), m_Map->GetMapWidth( ), m_Map->GetMapHeight( ), m_GameName, "Varlock", GetTime( ) - m_CreationTime, m_Map->GetMapPath( ), m_Map->GetMapCRC( ), 12, 12, m_HostPort, m_HostCounter ) ); } } m_LastPingTime = GetTime( ); } // refresh every 5 seconds if( !m_CountDownStarted && m_GameState == GAME_PUBLIC && GetSlotsOpen( ) > 0 && GetTime( ) >= m_LastRefreshTime + 5 ) { // send a game refresh packet to each battle.net connection for( vector<CBNET *> :: iterator i = m_GHost->m_BNETs.begin( ); i != m_GHost->m_BNETs.end( ); i++ ) (*i)->SendGameRefresh( m_GameState, m_GameName, string( ), m_Map, m_SaveGame, GetTime( ) - m_CreationTime ); if( m_RefreshMessages ) SendAllChat( m_GHost->m_Language->GameRefreshed( ) ); m_LastRefreshTime = GetTime( ); } // announce every m_AnnounceInterval seconds if( !m_AnnounceMessage.empty( ) && !m_CountDownStarted && GetTime( ) >= m_LastAnnounceTime + m_AnnounceInterval ) { SendAllChat( m_AnnounceMessage ); m_LastAnnounceTime = GetTime( ); } // try to auto start every 10 seconds if( m_AutoStartPlayers != 0 && GetTime( ) >= m_LastAutoStartTime + 10 ) { StartCountDownAuto( ); m_LastAutoStartTime = GetTime( ); } // countdown every 500 ms if( m_CountDownStarted && GetTicks( ) >= m_LastCountDownTicks + 500 ) { if( m_CountDownCounter > 0 ) { // we use a countdown counter rather than a "finish countdown time" here because it might alternately round up or down the count // this sometimes resulted in a countdown of e.g. "6 5 3 2 1" during my testing which looks pretty dumb // doing it this way ensures it's always "5 4 3 2 1" but each interval might not be *exactly* the same length SendAllChat( UTIL_ToString( m_CountDownCounter ) + ". . ." ); m_CountDownCounter--; } else if( !m_GameLoading && !m_GameLoaded ) { m_StartedLoadingTicks = GetTicks( ); m_StartedLoadingTime = GetTime( ); m_GameLoading = true; EventGameStarted( ); } m_LastCountDownTicks = GetTicks( ); } // check if the game is loaded if( m_GameLoading ) { bool FinishedLoading = true; for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) { FinishedLoading = (*i)->GetFinishedLoading( ); if( !FinishedLoading ) break; } if( FinishedLoading ) { m_LastActionSentTicks = GetTicks( ); m_GameLoading = false; m_GameLoaded = true; EventGameLoaded( ); } } // keep track of the largest sync counter (the number of keepalive packets received by each player) // if anyone falls behind by more than m_SyncLimit keepalives we start the lag screen if( m_GameLoaded ) { // calculate the largest sync counter for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) { if( (*i)->GetSyncCounter( ) > m_MaxSyncCounter ) m_MaxSyncCounter = (*i)->GetSyncCounter( ); } // check if anyone has started lagging // we consider a player to have started lagging if they're more than m_SyncLimit keepalives behind if( !m_Lagging ) { string LaggingString; for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) { if( m_MaxSyncCounter - (*i)->GetSyncCounter( ) > m_SyncLimit ) { (*i)->SetLagging( true ); (*i)->SetStartedLaggingTicks( GetTicks( ) ); m_Lagging = true; if( LaggingString.empty( ) ) LaggingString = (*i)->GetName( ); else LaggingString += ", " + (*i)->GetName( ); } } if( m_Lagging ) { // start the lag screen CONSOLE_Print( "[GAME: " + m_GameName + "] started lagging on [" + LaggingString + "]" ); SendAll( m_Protocol->SEND_W3GS_START_LAG( m_Players ) ); // reset everyone's drop vote for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) (*i)->SetDropVote( false ); } } // check if anyone has stopped lagging // we consider a player to have stopped lagging if they're less than half m_SyncLimit keepalives behind if( m_Lagging ) { for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) { if( (*i)->GetLagging( ) && m_MaxSyncCounter - (*i)->GetSyncCounter( ) < m_SyncLimit / 2 ) { // stop the lag screen for this player CONSOLE_Print( "[GAME: " + m_GameName + "] stopped lagging on [" + (*i)->GetName( ) + "]" ); SendAll( m_Protocol->SEND_W3GS_STOP_LAG( *i ) ); (*i)->SetLagging( false ); (*i)->SetStartedLaggingTicks( 0 ); } } // check if everyone has stopped lagging bool Lagging = false; for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) { if( (*i)->GetLagging( ) ) Lagging = true; } m_Lagging = Lagging; // reset m_LastActionSentTicks because we want the game to stop running while the lag screen is up m_LastActionSentTicks = GetTicks( ); // keep track of the last lag screen time so we can avoid timing out players m_LastLagScreenTime = GetTime( ); } } // send actions every m_Latency milliseconds // actions are at the heart of every Warcraft 3 game but luckily we don't need to know their contents to relay them // we queue player actions in EventPlayerAction then just resend them in batches to all players here if( m_GameLoaded && !m_Lagging && GetTicks( ) >= m_LastActionSentTicks + m_Latency ) SendAllActions( ); // if there aren't any players left and the game is loading/loaded then it's over and we can mark ourselves for deletion if( m_Players.empty( ) && ( m_GameLoading || m_GameLoaded ) ) { CONSOLE_Print( "[GAME: " + m_GameName + "] is over (no players left)" ); return true; } // accept new connections if( m_Socket ) { CTCPSocket *NewSocket = m_Socket->Accept( (fd_set *)fd ); if( NewSocket ) m_Potentials.push_back( new CPotentialPlayer( m_Protocol, this, NewSocket ) ); if( m_Socket->HasError( ) ) return true; } return m_Exiting;}void CBaseGame :: UpdatePost( ){ // we need to manually call DoSend on each player now because CGamePlayer :: Update doesn't do it // 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 // in reality since we're queueing actions it might not make a big difference but oh well for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) { if( (*i)->GetSocket( ) ) (*i)->GetSocket( )->DoSend( ); } for( vector<CPotentialPlayer *> :: iterator i = m_Potentials.begin( ); i != m_Potentials.end( ); i++ ) { if( (*i)->GetSocket( ) ) (*i)->GetSocket( )->DoSend( ); }}void CBaseGame :: Send( CGamePlayer *player, BYTEARRAY data ){ if( player && player->GetSocket( ) ) player->GetSocket( )->PutBytes( data );}void CBaseGame :: Send( unsigned char PID, BYTEARRAY data ){ Send( GetPlayerFromPID( PID ), data );}void CBaseGame :: Send( BYTEARRAY PIDs, BYTEARRAY data ){ for( unsigned int i = 0; i < PIDs.size( ); i++ ) Send( PIDs[i], data );}void CBaseGame :: SendAll( BYTEARRAY data ){ for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) { if( (*i)->GetSocket( ) ) (*i)->GetSocket( )->PutBytes( data ); }}void CBaseGame :: SendChat( unsigned char fromPID, CGamePlayer *player, string message ){ // send a private message to one player - it'll be marked [Private] in Warcraft 3 if( player ) { if( !m_GameLoaded ) { if( message.size( ) > 220 ) message = message.substr( 0, 220 ); Send( player, m_Protocol->SEND_W3GS_CHAT_FROM_HOST( fromPID, UTIL_CreateByteArray( player->GetPID( ) ), 16, BYTEARRAY( ), message ) ); } else { unsigned char ExtraFlags[] = { 3, 0, 0, 0 }; // 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 unsigned char SID = GetSIDFromPID( player->GetPID( ) ); if( SID < m_Slots.size( ) ) ExtraFlags[0] = 3 + m_Slots[SID].GetColour( ); if( message.size( ) > 120 ) message = message.substr( 0, 120 ); Send( player, m_Protocol->SEND_W3GS_CHAT_FROM_HOST( fromPID, UTIL_CreateByteArray( player->GetPID( ) ), 32, UTIL_CreateByteArray( ExtraFlags, 4 ), message ) ); } }}void CBaseGame :: SendChat( unsigned char fromPID, unsigned char toPID, string message ){ SendChat( fromPID, GetPlayerFromPID( toPID ), message );}void CBaseGame :: SendChat( CGamePlayer *player, string message ){ SendChat( GetHostPID( ), player, message );}void CBaseGame :: SendChat( unsigned char toPID, string message ){ SendChat( GetHostPID( ), toPID, message );}void CBaseGame :: SendAllChat( unsigned char fromPID, string message ){ // send a public message to all players - it'll be marked [All] in Warcraft 3 if( GetNumPlayers( ) > 0 ) { if( !m_GameLoaded ) { if( message.size( ) > 220 ) message = message.substr( 0, 220 ); // this is a lobby ghost chat message SendAll( m_Protocol->SEND_W3GS_CHAT_FROM_HOST( fromPID, GetPIDs( ), 16, BYTEARRAY( ), message ) ); } else { if( message.size( ) > 120 ) message = message.substr( 0, 120 ); // this is an ingame ghost chat message, print it to the console CONSOLE_Print( "[GAME: " + m_GameName + "] [Local]: " + message ); SendAll( m_Protocol->SEND_W3GS_CHAT_FROM_HOST( fromPID, GetPIDs( ), 32, UTIL_CreateByteArray( (uint32_t)0, false ), message ) ); if( m_Replay ) m_Replay->AddChatMessage( fromPID, 32, 0, message ); } }}void CBaseGame :: SendAllChat( string message ){ SendAllChat( GetHostPID( ), message );}void CBaseGame :: SendAllSlotInfo( ){ if( !m_GameLoading && !m_GameLoaded ) SendAll( m_Protocol->SEND_W3GS_SLOTINFO( m_Slots, m_RandomSeed, m_Map->GetMapGameType( ) == GAMETYPE_CUSTOM ? 3 : 0, m_Map->GetMapNumPlayers( ) ) );}void CBaseGame :: SendVirtualHostPlayerInfo( CGamePlayer *player ){ if( m_VirtualHostPID == 255 ) return; BYTEARRAY IP; IP.push_back( 0 ); IP.push_back( 0 ); IP.push_back( 0 ); IP.push_back( 0 ); Send( player, m_Protocol->SEND_W3GS_PLAYERINFO( m_VirtualHostPID, m_VirtualHostName, IP, IP ) );}void CBaseGame :: SendAllActions( ){ uint16_t SendInterval = GetTicks( ) - m_LastActionSentTicks; m_GameTicks += SendInterval; // add actions to replay if( m_Replay ) m_Replay->AddTimeSlot( SendInterval, m_Actions ); // 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 if( !m_Actions.empty( ) ) { // we use a "sub actions queue" which we keep adding actions to until we reach the size limit // start by adding one action to the sub actions queue queue<CIncomingAction *> SubActions; CIncomingAction *Action = m_Actions.front( ); m_Actions.pop( ); SubActions.push( Action ); uint32_t SubActionsLength = Action->GetLength( ); while( !m_Actions.empty( ) ) { Action = m_Actions.front( ); m_Actions.pop( ); // 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) if( SubActionsLength + Action->GetLength( ) > 1452 ) { // we'd be over the limit if we added the next action to the sub actions queue // so send everything already in the queue and then clear it out // the W3GS_INCOMING_ACTION2 packet handles the overflow but it must be sent *before* the corresponding W3GS_INCOMING_ACTION packet SendAll( m_Protocol->SEND_W3GS_INCOMING_ACTION2( SubActions ) ); while( !SubActions.empty( ) ) { delete SubActions.front( ); SubActions.pop( ); } SubActionsLength = 0; } SubActions.push( Action ); SubActionsLength += Action->GetLength( ); } SendAll( m_Protocol->SEND_W3GS_INCOMING_ACTION( SubActions, SendInterval ) ); while( !SubActions.empty( ) ) { delete SubActions.front( ); SubActions.pop( ); } } else SendAll( m_Protocol->SEND_W3GS_INCOMING_ACTION( m_Actions, SendInterval ) ); m_LastActionSentTicks = GetTicks( );}void CBaseGame :: SendWelcomeMessage( CGamePlayer *player ){ SendChat( player, " " ); SendChat( player, " " ); SendChat( player, " " ); SendChat( player, "Welcome to Hacksaw Host, hosting Age of Myths 24/7!" ); SendChat( player, "HHost++ http://forum.codelain.com/" ); SendChat( player, "-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-" ); SendChat( player, " Game Name: " + m_GameName ); SendChat( player, " Game Admis: Hacksaw and ThE_WhIz" ); SendChat( player, " Please respect the Admins!" );}void CBaseGame :: SendEndMessage( ){}void CBaseGame :: EventPlayerDeleted( CGamePlayer *player ){ CONSOLE_Print( "[GAME: " + m_GameName + "] deleting player [" + player->GetName( ) + "]: " + player->GetLeftReason( ) ); // in some cases we're forced to send the left message early so don't send it again if( player->GetLeftMessageSent( ) ) return; if( m_GameLoaded ) SendAllChat( player->GetName( ) + " " + player->GetLeftReason( ) + "." ); if( player->GetLagging( ) ) SendAll( m_Protocol->SEND_W3GS_STOP_LAG( player ) ); // autosave if( m_AutoSave && m_GameLoaded && player->GetLeftCode( ) == PLAYERLEAVE_DISCONNECT ) { string SaveGameName = UTIL_FileSafeName( "GHost++ AutoSave " + m_GameName + " (" + player->GetName( ) + ").w3z" ); CONSOLE_Print( "[GAME: " + m_GameName + "] auto saving [" + SaveGameName + "] before player drop, shortened send interval = " + UTIL_ToString( GetTicks( ) - m_LastActionSentTicks ) ); BYTEARRAY CRC; BYTEARRAY Action; Action.push_back( 6 ); UTIL_AppendByteArray( Action, SaveGameName ); m_Actions.push( new CIncomingAction( player->GetPID( ), CRC, Action ) ); SendAllActions( ); } // tell everyone about the player leaving SendAll( m_Protocol->SEND_W3GS_PLAYERLEAVE_OTHERS( player->GetPID( ), player->GetLeftCode( ) ) ); // set the replay's host PID and name to the last player to leave the game // this will get overwritten as each player leaves the game so it will eventually be set to the last player if( m_Replay && ( m_GameLoading || m_GameLoaded ) ) { m_Replay->SetHostPID( player->GetPID( ) ); m_Replay->SetHostName( player->GetName( ) ); // add leave message to replay m_Replay->AddLeaveGame( 1, player->GetPID( ), player->GetLeftCode( ) ); } // abort the countdown if there was one in progress if( m_CountDownStarted && !m_GameLoading && !m_GameLoaded ) { SendAllChat( m_GHost->m_Language->CountDownAborted( ) ); m_CountDownStarted = false; }}void CBaseGame :: EventPlayerDisconnectTimedOut( CGamePlayer *player ){ // not only do we not do any timeouts if the game is lagging, we allow for an additional grace period of 10 seconds // this is because Warcraft 3 stops sending packets during the lag screen // so when the lag screen finishes we would immediately disconnect everyone if we didn't give them some extra time if( GetTime( ) > m_LastLagScreenTime + 10 ) { player->SetDeleteMe( true ); player->SetLeftReason( m_GHost->m_Language->HasLostConnectionTimedOut( ) ); player->SetLeftCode( PLAYERLEAVE_DISCONNECT ); if( !m_GameLoading && !m_GameLoaded ) OpenSlot( GetSIDFromPID( player->GetPID( ) ), false ); }}void CBaseGame :: EventPlayerDisconnectPlayerError( CGamePlayer *player ){ // at the time of this comment there's only one player error and that's when we receive a bad packet from the player // since TCP has checks and balances for data corruption the chances of this are pretty slim player->SetDeleteMe( true ); player->SetLeftReason( m_GHost->m_Language->HasLostConnectionPlayerError( player->GetErrorString( ) ) ); player->SetLeftCode( PLAYERLEAVE_DISCONNECT ); if( !m_GameLoading && !m_GameLoaded ) OpenSlot( GetSIDFromPID( player->GetPID( ) ), false );}void CBaseGame :: EventPlayerDisconnectSocketError( CGamePlayer *player ){ player->SetDeleteMe( true ); player->SetLeftReason( m_GHost->m_Language->HasLostConnectionSocketError( player->GetSocket( )->GetErrorString( ) ) ); player->SetLeftCode( PLAYERLEAVE_DISCONNECT ); if( !m_GameLoading && !m_GameLoaded ) OpenSlot( GetSIDFromPID( player->GetPID( ) ), false );}void CBaseGame :: EventPlayerDisconnectConnectionClosed( CGamePlayer *player ){ player->SetDeleteMe( true ); player->SetLeftReason( m_GHost->m_Language->HasLostConnectionClosedByRemoteHost( ) ); player->SetLeftCode( PLAYERLEAVE_DISCONNECT ); if( !m_GameLoading && !m_GameLoaded ) OpenSlot( GetSIDFromPID( player->GetPID( ) ), false );}void CBaseGame :: EventPlayerJoined( CPotentialPlayer *potential, CIncomingJoinPlayer *joinPlayer ){ // check if the new player's name is empty or too long if( joinPlayer->GetName( ).empty( ) || joinPlayer->GetName( ).size( ) > 15 ) { CONSOLE_Print( "[GAME: " + m_GameName + "] player [" + joinPlayer->GetName( ) + "] is trying to join the game with an invalid name of length " + UTIL_ToString( joinPlayer->GetName( ).size( ) ) ); potential->SetDeleteMe( true ); return; } // check if the new player's name is the same as the virtual host name if( joinPlayer->GetName( ) == m_VirtualHostName ) { CONSOLE_Print( "[GAME: " + m_GameName + "] player [" + joinPlayer->GetName( ) + "] is trying to join the game with the virtual host name" ); potential->SetDeleteMe( true ); return; } // check if the new player's name is already taken if( GetPlayerFromName( joinPlayer->GetName( ), false ) ) { CONSOLE_Print( "[GAME: " + m_GameName + "] player [" + joinPlayer->GetName( ) + "] is trying to join the game but that name is already taken" ); // SendAllChat( m_GHost->m_Language->TryingToJoinTheGameButTaken( joinPlayer->GetName( ) ) ); potential->SetDeleteMe( true ); return; } // check if the new player's name is banned for( vector<CBNET *> :: iterator i = m_GHost->m_BNETs.begin( ); i != m_GHost->m_BNETs.end( ); i++ ) { CDBBan *Ban = m_GHost->m_DB->BanCheck( (*i)->GetServer( ), joinPlayer->GetName( ) ); if( Ban ) { delete Ban; CONSOLE_Print( "[GAME: " + m_GameName + "] player [" + joinPlayer->GetName( ) + "] is trying to join the game but is banned" ); SendAllChat( m_GHost->m_Language->TryingToJoinTheGameButBanned( joinPlayer->GetName( ) ) ); potential->SetDeleteMe( true ); return; } } // try to find an empty slot unsigned char SID = GetEmptySlot( false ); // check if the player is an admin or root admin on any connected realm for determining reserved status // we can't just use the spoof checked realm like in EventPlayerBotCommand because the player hasn't spoof checked yet bool AdminCheck = false; for( vector<CBNET *> :: iterator i = m_GHost->m_BNETs.begin( ); i != m_GHost->m_BNETs.end( ); i++ ) { if( m_GHost->m_DB->AdminCheck( (*i)->GetServer( ), joinPlayer->GetName( ) ) || (*i)->IsRootAdmin( joinPlayer->GetName( ) ) ) { AdminCheck = true; break; } } bool Reserved = IsReserved( joinPlayer->GetName( ) ) || AdminCheck || IsOwner( joinPlayer->GetName( ) ); if( SID == 255 && Reserved ) { // a reserved player is trying to join the game but it's full, try to find a reserved slot SID = GetEmptySlot( true ); if( SID != 255 ) { CGamePlayer *KickedPlayer = GetPlayerFromSID( SID ); if( KickedPlayer ) { KickedPlayer->SetDeleteMe( true ); KickedPlayer->SetLeftReason( m_GHost->m_Language->WasKickedForReservedPlayer( joinPlayer->GetName( ) ) ); KickedPlayer->SetLeftCode( PLAYERLEAVE_LOBBY ); // 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 // we don't need to call OpenSlot here because we're about to overwrite the slot data anyway SendAll( m_Protocol->SEND_W3GS_PLAYERLEAVE_OTHERS( KickedPlayer->GetPID( ), KickedPlayer->GetLeftCode( ) ) ); KickedPlayer->SetLeftMessageSent( true ); } } } if( SID == 255 && IsOwner( joinPlayer->GetName( ) ) ) { // 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 // updated this to try to find a player slot so that we don't end up kicking a computer SID = 0; for( unsigned char i = 0; i < m_Slots.size( ); i++ ) { if( m_Slots[i].GetSlotStatus( ) == SLOTSTATUS_OCCUPIED && m_Slots[i].GetComputer( ) == 0 ) { SID = i; break; } } CGamePlayer *KickedPlayer = GetPlayerFromSID( SID ); if( KickedPlayer ) { KickedPlayer->SetDeleteMe( true ); KickedPlayer->SetLeftReason( m_GHost->m_Language->WasKickedForOwnerPlayer( joinPlayer->GetName( ) ) ); KickedPlayer->SetLeftCode( PLAYERLEAVE_LOBBY ); // 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 // we don't need to call OpenSlot here because we're about to overwrite the slot data anyway SendAll( m_Protocol->SEND_W3GS_PLAYERLEAVE_OTHERS( KickedPlayer->GetPID( ), KickedPlayer->GetLeftCode( ) ) ); KickedPlayer->SetLeftMessageSent( true ); } } if( SID >= m_Slots.size( ) ) { potential->SetDeleteMe( true ); return; } // we have a slot for the new player // make room for them by deleting the virtual host player if we have to if( GetNumPlayers( ) >= 11 ) DeleteVirtualHost( ); // turning the CPotentialPlayer into a CGamePlayer is a bit of a pain because we have to be careful not to close the socket // this problem is solved by setting the socket to NULL before deletion and handling the NULL case in the destructor // we also have to be careful to not modify the m_Potentials vector since we're currently looping through it CONSOLE_Print( "[GAME: " + m_GameName + "] player [" + joinPlayer->GetName( ) + "] joined the game" ); CGamePlayer *Player = new CGamePlayer( potential, GetNewPID( ), joinPlayer->GetName( ), joinPlayer->GetInternalIP( ), Reserved ); // consider the owner player to have already spoof checked // we will still attempt to spoof check them if it's enabled but this allows owners connecting over LAN to access admin commands if( IsOwner( joinPlayer->GetName( ) ) ) Player->SetSpoofed( true ); m_Players.push_back( Player ); potential->SetSocket( NULL ); potential->SetDeleteMe( true ); if( m_Map->GetMapGameType( ) == GAMETYPE_CUSTOM ) m_Slots[SID] = CGameSlot( Player->GetPID( ), 255, SLOTSTATUS_OCCUPIED, 0, m_Slots[SID].GetTeam( ), m_Slots[SID].GetColour( ), m_Slots[SID].GetRace( ) ); else { m_Slots[SID] = CGameSlot( Player->GetPID( ), 255, SLOTSTATUS_OCCUPIED, 0, 12, 12, SLOTRACE_RANDOM ); // try to pick a team and colour // make sure there aren't too many other players already unsigned char NumOtherPlayers = 0; for( unsigned char i = 0; i < m_Slots.size( ); i++ ) { if( m_Slots[i].GetSlotStatus( ) == SLOTSTATUS_OCCUPIED && m_Slots[i].GetTeam( ) != 12 ) NumOtherPlayers++; } if( NumOtherPlayers < m_Map->GetMapNumPlayers( ) ) { m_Slots[SID].SetTeam( 0 ); m_Slots[SID].SetColour( GetNewColour( ) ); } } // send slot info to the new player // the SLOTINFOJOIN packet also tells the client their assigned PID and that the join was successful Player->GetSocket( )->PutBytes( m_Protocol->SEND_W3GS_SLOTINFOJOIN( Player->GetPID( ), Player->GetExternalIP( ), m_Slots, m_RandomSeed, m_Map->GetMapGameType( ) == GAMETYPE_CUSTOM ? 3 : 0, m_Map->GetMapNumPlayers( ) ) ); // send virtual host info to the new player SendVirtualHostPlayerInfo( Player ); for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) { if( !(*i)->GetLeftMessageSent( ) && *i != Player ) { // send info about the new player to every other player if( (*i)->GetSocket( ) ) (*i)->GetSocket( )->PutBytes( m_Protocol->SEND_W3GS_PLAYERINFO( Player->GetPID( ), Player->GetName( ), Player->GetExternalIP( ), Player->GetInternalIP( ) ) ); // send info about every other player to the new player Player->GetSocket( )->PutBytes( m_Protocol->SEND_W3GS_PLAYERINFO( (*i)->GetPID( ), (*i)->GetName( ), (*i)->GetExternalIP( ), (*i)->GetInternalIP( ) ) ); } } // send a map check packet to the new player Player->GetSocket( )->PutBytes( m_Protocol->SEND_W3GS_MAPCHECK( m_Map->GetMapPath( ), m_Map->GetMapSize( ), m_Map->GetMapInfo( ), m_Map->GetMapCRC( ) ) ); // send slot info to everyone, so the new player gets this info twice but everyone else still needs to know the new slot layout SendAllSlotInfo( ); // send a welcome message SendWelcomeMessage( Player ); // abort the countdown if there was one in progress if( m_CountDownStarted && !m_GameLoading && !m_GameLoaded ) { SendAllChat( m_GHost->m_Language->CountDownAborted( ) ); m_CountDownStarted = false; } // auto lock the game if( m_GHost->m_AutoLock && !m_Locked && IsOwner( joinPlayer->GetName( ) ) ) { SendAllChat( m_GHost->m_Language->GameLocked( ) ); m_Locked = true; }}void CBaseGame :: EventPlayerLeft( CGamePlayer *player ){ // this function is only called when a player leave packet is received, not when there's a socket error, kick, etc... player->SetDeleteMe( true ); player->SetLeftReason( m_GHost->m_Language->HasLeftVoluntarily( ) ); player->SetLeftCode( PLAYERLEAVE_LOST ); if( !m_GameLoading && !m_GameLoaded ) OpenSlot( GetSIDFromPID( player->GetPID( ) ), false );}void CBaseGame :: EventPlayerLoaded( CGamePlayer *player ){ SendAll( m_Protocol->SEND_W3GS_GAMELOADED_OTHERS( player->GetPID( ) ) );}void CBaseGame :: EventPlayerAction( CGamePlayer *player, CIncomingAction *action ){ m_Actions.push( action );}void CBaseGame :: EventPlayerKeepAlive( CGamePlayer *player, uint32_t checkSum ){ // check for desyncs uint32_t FirstCheckSum = player->GetCheckSums( )->front( ); for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) { if( (*i)->GetCheckSums( )->empty( ) ) return; if( !m_Desynced && (*i)->GetCheckSums( )->front( ) != FirstCheckSum ) { m_Desynced = true; CONSOLE_Print( "[GAME: " + m_GameName + "] desync detected" ); SendAllChat( m_GHost->m_Language->DesyncDetected( ) ); SendAllChat( m_GHost->m_Language->DesyncDetected( ) ); SendAllChat( m_GHost->m_Language->DesyncDetected( ) ); } } for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) (*i)->GetCheckSums( )->pop( ); // add checksum to replay but only if we're not desynced if( m_Replay && !m_Desynced ) m_Replay->AddCheckSum( FirstCheckSum );}void CBaseGame :: EventPlayerChatToHost( CGamePlayer *player, CIncomingChatPlayer *chatPlayer ){ if( chatPlayer->GetFromPID( ) == player->GetPID( ) ) { if( chatPlayer->GetType( ) == CIncomingChatPlayer :: CTH_MESSAGE || chatPlayer->GetType( ) == CIncomingChatPlayer :: CTH_MESSAGEEXTRA ) { // relay the chat message to other players if( !m_GameLoading ) { bool Relay = true; BYTEARRAY ExtraFlags = chatPlayer->GetExtraFlags( ); // calculate timestamp string MinString = UTIL_ToString( ( m_GameTicks / 1000 ) / 60 ); string SecString = UTIL_ToString( ( m_GameTicks / 1000 ) % 60 ); if( MinString.size( ) == 1 ) MinString.insert( 0, "0" ); if( SecString.size( ) == 1 ) SecString.insert( 0, "0" ); if( !ExtraFlags.empty( ) ) { if( ExtraFlags[0] == 0 ) { // this is an ingame [All] message, print it to the console CONSOLE_Print( "[GAME: " + m_GameName + "] (" + MinString + ":" + SecString + ") [All] [" + player->GetName( ) + "]: " + chatPlayer->GetMessage( ) ); // don't relay ingame messages targeted for all players if we're currently muting all // 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 if( m_MuteAll ) Relay = false; } if( Relay ) { // add chat message to replay // this includes allied chat and private chat from both teams as long as it was relayed if( m_Replay ) m_Replay->AddChatMessage( chatPlayer->GetFromPID( ), chatPlayer->GetFlag( ), UTIL_ByteArrayToUInt32( chatPlayer->GetExtraFlags( ), false ), chatPlayer->GetMessage( ) ); } } else { // this is a lobby message, print it to the console CONSOLE_Print( "[GAME: " + m_GameName + "] [Lobby] [" + player->GetName( ) + "]: " + chatPlayer->GetMessage( ) ); if( m_MuteLobby ) Relay = false; } if( Relay ) Send( chatPlayer->GetToPIDs( ), m_Protocol->SEND_W3GS_CHAT_FROM_HOST( chatPlayer->GetFromPID( ), chatPlayer->GetToPIDs( ), chatPlayer->GetFlag( ), chatPlayer->GetExtraFlags( ), chatPlayer->GetMessage( ) ) ); } // handle bot commands string Message = chatPlayer->GetMessage( ); if( !Message.empty( ) && Message[0] == m_GHost->m_CommandTrigger ) { // extract the command trigger, the command, and the payload // e.g. "!say hello world" -> command: "say", payload: "hello world" string Command; string Payload; string :: size_type PayloadStart = Message.find( " " ); if( PayloadStart != string :: npos ) { Command = Message.substr( 1, PayloadStart - 1 ); Payload = Message.substr( PayloadStart + 1 ); } else Command = Message.substr( 1 ); transform( Command.begin( ), Command.end( ), Command.begin( ), (int(*)(int))tolower ); EventPlayerBotCommand( player, Command, Payload ); } } else if( chatPlayer->GetType( ) == CIncomingChatPlayer :: CTH_TEAMCHANGE && !m_CountDownStarted ) EventPlayerChangeTeam( player, chatPlayer->GetByte( ) ); else if( chatPlayer->GetType( ) == CIncomingChatPlayer :: CTH_COLOURCHANGE && !m_CountDownStarted ) EventPlayerChangeColour( player, chatPlayer->GetByte( ) ); else if( chatPlayer->GetType( ) == CIncomingChatPlayer :: CTH_RACECHANGE && !m_CountDownStarted ) EventPlayerChangeRace( player, chatPlayer->GetByte( ) ); else if( chatPlayer->GetType( ) == CIncomingChatPlayer :: CTH_HANDICAPCHANGE && !m_CountDownStarted ) EventPlayerChangeHandicap( player, chatPlayer->GetByte( ) ); }}void CBaseGame :: EventPlayerBotCommand( CGamePlayer *player, string command, string payload ){}void CBaseGame :: EventPlayerChangeTeam( CGamePlayer *player, unsigned char team ){ // player is requesting a team change if( m_Map->GetMapGameType( ) == GAMETYPE_CUSTOM ) { unsigned char oldSID = GetSIDFromPID( player->GetPID( ) ); unsigned char newSID = GetEmptySlot( team, player->GetPID( ) ); SwapSlots( oldSID, newSID ); } else { if( team > 12 ) return; if( team == 12 ) { if( m_Map->GetMapObservers( ) != MAPOBS_ALLOWED && m_Map->GetMapObservers( ) != MAPOBS_REFEREES ) return; } else { if( team >= m_Map->GetMapNumTeams( ) ) return; // make sure there aren't too many other players already unsigned char NumOtherPlayers = 0; for( unsigned char i = 0; i < m_Slots.size( ); i++ ) { if( m_Slots[i].GetSlotStatus( ) == SLOTSTATUS_OCCUPIED && m_Slots[i].GetTeam( ) != 12 && m_Slots[i].GetPID( ) != player->GetPID( ) ) NumOtherPlayers++; } if( NumOtherPlayers >= m_Map->GetMapNumPlayers( ) ) return; } unsigned char SID = GetSIDFromPID( player->GetPID( ) ); if( SID < m_Slots.size( ) ) { m_Slots[SID].SetTeam( team ); if( team == 12 ) { // if they're joining the observer team give them the observer colour m_Slots[SID].SetColour( 12 ); } else if( m_Slots[SID].GetColour( ) == 12 ) { // if they're joining a regular team give them an unused colour m_Slots[SID].SetColour( GetNewColour( ) ); } SendAllSlotInfo( ); } }}void CBaseGame :: EventPlayerChangeColour( CGamePlayer *player, unsigned char colour ){ // player is requesting a colour change if( m_Map->GetMapGameType( ) == GAMETYPE_CUSTOM ) return; if( colour > 11 ) return; unsigned char SID = GetSIDFromPID( player->GetPID( ) ); if( SID < m_Slots.size( ) ) { // make sure the player isn't an observer if( m_Slots[SID].GetTeam( ) == 12 ) return; ColourSlot( SID, colour ); }}void CBaseGame :: EventPlayerChangeRace( CGamePlayer *player, unsigned char race ){ // player is requesting a race change if( m_Map->GetMapGameType( ) == GAMETYPE_CUSTOM ) return; if( m_Map->GetMapFlags( ) & MAPFLAG_RANDOMRACES ) return; if( race != SLOTRACE_HUMAN && race != SLOTRACE_ORC && race != SLOTRACE_NIGHTELF && race != SLOTRACE_UNDEAD && race != SLOTRACE_RANDOM ) return; unsigned char SID = GetSIDFromPID( player->GetPID( ) ); if( SID < m_Slots.size( ) ) { m_Slots[SID].SetRace( race ); SendAllSlotInfo( ); }}void CBaseGame :: EventPlayerChangeHandicap( CGamePlayer *player, unsigned char handicap ){ // player is requesting a handicap change if( m_Map->GetMapGameType( ) == GAMETYPE_CUSTOM ) return; if( handicap != 50 && handicap != 60 && handicap != 70 && handicap != 80 && handicap != 90 && handicap != 100 ) return; unsigned char SID = GetSIDFromPID( player->GetPID( ) ); if( SID < m_Slots.size( ) ) { m_Slots[SID].SetHandicap( handicap ); SendAllSlotInfo( ); }}void CBaseGame :: EventPlayerDropRequest( CGamePlayer *player ){ // todotodo: check that we've waited the full 45 seconds if( m_Lagging ) { CONSOLE_Print( "[GAME: " + m_GameName + "] player [" + player->GetName( ) + "] voted to drop laggers" ); SendAllChat( m_GHost->m_Language->PlayerVotedToDropLaggers( player->GetName( ) ) ); // check if at least half the players voted to drop uint32_t Votes = 0; for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) { if( (*i)->GetDropVote( ) ) Votes++; } if( (float)Votes / m_Players.size( ) > 0.49 ) StopLaggers( m_GHost->m_Language->LaggedOutDroppedByVote( ) ); }}void CBaseGame :: EventPlayerMapSize( CGamePlayer *player, CIncomingMapSize *mapSize ){ if( m_GameLoading || m_GameLoaded ) return; // todotodo: the variable names here are confusing due to extremely poor design on my part uint32_t MapSize = UTIL_ByteArrayToUInt32( m_Map->GetMapSize( ), false ); if( mapSize->GetSizeFlag( ) != 1 || mapSize->GetMapSize( ) != MapSize ) { // the player doesn't have the map if( m_GHost->m_AllowDownloads != 0 ) { string *MapData = m_Map->GetMapData( ); if( !MapData->empty( ) ) { if( m_GHost->m_AllowDownloads == 1 || ( m_GHost->m_AllowDownloads == 2 && player->GetDownloadAllowed( ) ) ) { if( !player->GetDownloadStarted( ) && mapSize->GetSizeFlag( ) == 1 ) { // inform the client that we are willing to send the map CONSOLE_Print( "[GAME: " + m_GameName + "] map download started for player [" + player->GetName( ) + "]" ); Send( player, m_Protocol->SEND_W3GS_STARTDOWNLOAD( GetHostPID( ) ) ); player->SetDownloadStarted( true ); player->SetStartedDownloadingTicks( GetTicks( ) ); } else { // send up to 50 pieces of the map at once so that the download goes faster // if we wait for each MAPPART packet to be acknowledged by the client it'll take a long time to download // this is because we would have to wait the round trip time (the ping time) between sending every 1442 bytes of map data // doing it this way allows us to send at least 70 KB in each round trip interval which is much more reasonable // the theoretical throughput is [70 KB * 1000 / ping] in KB/sec so someone with 100 ping (round trip ping, not LC ping) could download at 700 KB/sec while( player->GetLastMapPartSent( ) < mapSize->GetMapSize( ) + 1442 * 50 && player->GetLastMapPartSent( ) < MapSize ) { Send( player, m_Protocol->SEND_W3GS_MAPPART( GetHostPID( ), player->GetPID( ), player->GetLastMapPartSent( ), MapData ) ); player->SetLastMapPartSent( player->GetLastMapPartSent( ) + 1442 ); } } } } else { player->SetDeleteMe( true ); player->SetLeftReason( "doesn't have the map and there is no local copy of the map to send" ); player->SetLeftCode( PLAYERLEAVE_LOBBY ); OpenSlot( GetSIDFromPID( player->GetPID( ) ), false ); } } else { player->SetDeleteMe( true ); player->SetLeftReason( "doesn't have the map and map downloads are disabled" ); player->SetLeftCode( PLAYERLEAVE_LOBBY ); OpenSlot( GetSIDFromPID( player->GetPID( ) ), false ); } } else { if( player->GetDownloadStarted( ) ) { // calculate download rate float Seconds = (float)( GetTicks( ) - player->GetStartedDownloadingTicks( ) ) / 1000; float Rate = (float)MapSize / 1024 / Seconds; CONSOLE_Print( "[GAME: " + m_GameName + "] map download finished for player [" + player->GetName( ) + "] in " + UTIL_ToString( Seconds, 1 ) + " seconds" ); SendAllChat( m_GHost->m_Language->PlayerDownloadedTheMap( player->GetName( ), UTIL_ToString( Seconds, 1 ), UTIL_ToString( Rate, 1 ) ) ); player->SetDownloadFinished( true ); // add to database m_GHost->m_DB->DownloadAdd( m_Map->GetMapPath( ), MapSize, player->GetName( ), player->GetExternalIPString( ), player->GetSpoofed( ) ? 1 : 0, player->GetSpoofedRealm( ), GetTicks( ) - player->GetStartedDownloadingTicks( ) ); } } unsigned char NewDownloadStatus = (unsigned char)( (float)mapSize->GetMapSize( ) / MapSize * 100 ); unsigned char SID = GetSIDFromPID( player->GetPID( ) ); if( NewDownloadStatus > 100 ) NewDownloadStatus = 100; if( SID < m_Slots.size( ) ) { // only send the slot info if the download status changed if( m_Slots[SID].GetDownloadStatus( ) != NewDownloadStatus ) { m_Slots[SID].SetDownloadStatus( NewDownloadStatus ); SendAllSlotInfo( ); } }}void CBaseGame :: EventPlayerPongToHost( CGamePlayer *player, uint32_t pong ){ // autokick players with excessive pings but only if they're not reserved and we've received at least 3 pings from them // 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 // see the Update function for where we send pings if( !m_GameLoading && !m_GameLoaded && !player->GetReserved( ) && player->GetNumPings( ) >= 3 && player->GetPing( m_GHost->m_LCPings ) > m_GHost->m_AutoKickPing ) { // send a chat message because we don't normally do so when a player leaves the lobby SendAllChat( m_GHost->m_Language->AutokickingPlayerForExcessivePing( player->GetName( ), UTIL_ToString( player->GetPing( m_GHost->m_LCPings ) ) ) ); player->SetDeleteMe( true ); player->SetLeftReason( "was autokicked for excessive ping of " + UTIL_ToString( player->GetPing( m_GHost->m_LCPings ) ) ); player->SetLeftCode( PLAYERLEAVE_LOBBY ); OpenSlot( GetSIDFromPID( player->GetPID( ) ), false ); }}void CBaseGame :: EventGameStarted( ){ CONSOLE_Print( "[GAME: " + m_GameName + "] started loading with " + UTIL_ToString( GetNumPlayers( ) ) + " players" ); // since we use a fake countdown to deal with leavers during countdown the COUNTDOWN_START and COUNTDOWN_END packets are sent in quick succession // send a start countdown packet SendAll( m_Protocol->SEND_W3GS_COUNTDOWN_START( ) ); // remove the virtual host player DeleteVirtualHost( ); // send an end countdown packet SendAll( m_Protocol->SEND_W3GS_COUNTDOWN_END( ) ); // record the starting number of players m_StartPlayers = GetNumPlayers( ); // close the listening socket delete m_Socket; m_Socket = NULL; // delete any potential players that are still hanging around for( vector<CPotentialPlayer *> :: iterator i = m_Potentials.begin( ); i != m_Potentials.end( ); i++ ) delete *i; m_Potentials.clear( ); // set initial values for replay if( m_Replay ) { for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) m_Replay->AddPlayer( (*i)->GetPID( ), (*i)->GetName( ) ); m_Replay->SetSlots( m_Slots ); m_Replay->SetRandomSeed( m_RandomSeed ); m_Replay->SetSelectMode( m_Map->GetMapGameType( ) == GAMETYPE_CUSTOM ? 3 : 0 ); m_Replay->SetStartSpotCount( m_Map->GetMapNumPlayers( ) ); if( !m_Players.empty( ) ) { // this might not be necessary since we're going to overwrite the replay's host PID and name everytime a player leaves m_Replay->SetHostPID( m_Players[0]->GetPID( ) ); m_Replay->SetHostName( m_Players[0]->GetName( ) ); } } // build a stat string for use when saving the replay // we have to build this now because the map data could change now that the game has started BYTEARRAY StatString; UTIL_AppendByteArray( StatString, m_Map->GetMapGameFlags( ) ); StatString.push_back( 0 ); UTIL_AppendByteArray( StatString, m_Map->GetMapWidth( ) ); UTIL_AppendByteArray( StatString, m_Map->GetMapHeight( ) ); UTIL_AppendByteArray( StatString, m_Map->GetMapCRC( ) ); UTIL_AppendByteArray( StatString, m_Map->GetMapPath( ) ); UTIL_AppendByteArray( StatString, "GHost++" ); StatString.push_back( 0 ); StatString = UTIL_EncodeStatString( StatString ); m_StatString = string( StatString.begin( ), StatString.end( ) ); // move the game to the games in progress vector m_GHost->m_CurrentGame = NULL; m_GHost->m_Games.push_back( this ); // and finally reenter battle.net chat for( vector<CBNET *> :: iterator i = m_GHost->m_BNETs.begin( ); i != m_GHost->m_BNETs.end( ); i++ ) { (*i)->SendGameUncreate( ); (*i)->SendEnterChat( ); }}void CBaseGame :: EventGameLoaded( ){ CONSOLE_Print( "[GAME: " + m_GameName + "] finished loading with " + UTIL_ToString( GetNumPlayers( ) ) + " players" ); // send shortest, longest, and personal load times to each player CGamePlayer *Shortest = NULL; CGamePlayer *Longest = NULL; for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) { if( !Shortest || (*i)->GetFinishedLoadingTicks( ) < Shortest->GetFinishedLoadingTicks( ) ) Shortest = *i; if( !Longest || (*i)->GetFinishedLoadingTicks( ) > Longest->GetFinishedLoadingTicks( ) ) Longest = *i; } if( Shortest && Longest ) { SendAllChat( m_GHost->m_Language->ShortestLoadByPlayer( Shortest->GetName( ), UTIL_ToString( (float)( Shortest->GetFinishedLoadingTicks( ) - m_StartedLoadingTicks ) / 1000, 2 ) ) ); SendAllChat( m_GHost->m_Language->LongestLoadByPlayer( Longest->GetName( ), UTIL_ToString( (float)( Longest->GetFinishedLoadingTicks( ) - m_StartedLoadingTicks ) / 1000, 2 ) ) ); } for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) SendChat( *i, m_GHost->m_Language->YourLoadingTimeWas( UTIL_ToString( (float)( (*i)->GetFinishedLoadingTicks( ) - m_StartedLoadingTicks ) / 1000, 2 ) ) );}unsigned char CBaseGame :: GetSIDFromPID( unsigned char PID ){ if( m_Slots.size( ) > 255 ) return 255; for( unsigned char i = 0; i < m_Slots.size( ); i++ ) { if( m_Slots[i].GetPID( ) == PID ) return i; } return 255;}CGamePlayer *CBaseGame :: GetPlayerFromPID( unsigned char PID ){ for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) { if( !(*i)->GetLeftMessageSent( ) && (*i)->GetPID( ) == PID ) return *i; } return NULL;}CGamePlayer *CBaseGame :: GetPlayerFromSID( unsigned char SID ){ if( SID < m_Slots.size( ) ) return GetPlayerFromPID( m_Slots[SID].GetPID( ) ); return NULL;}CGamePlayer *CBaseGame :: GetPlayerFromName( string name, bool sensitive ){ if( !sensitive ) transform( name.begin( ), name.end( ), name.begin( ), (int(*)(int))tolower ); for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) { if( !(*i)->GetLeftMessageSent( ) ) { string TestName = (*i)->GetName( ); if( !sensitive ) transform( TestName.begin( ), TestName.end( ), TestName.begin( ), (int(*)(int))tolower ); if( TestName == name ) return *i; } } return NULL;}uint32_t CBaseGame :: GetPlayerFromNamePartial( string name, CGamePlayer **player ){ transform( name.begin( ), name.end( ), name.begin( ), (int(*)(int))tolower ); uint32_t Matches = 0; *player = NULL; // try to match each player with the passed string (e.g. "Varlock" would be matched with "lock") for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) { if( !(*i)->GetLeftMessageSent( ) ) { string TestName = (*i)->GetName( ); transform( TestName.begin( ), TestName.end( ), TestName.begin( ), (int(*)(int))tolower ); if( TestName.find( name ) != string :: npos ) { Matches++; *player = *i; } } } return Matches;}CGamePlayer *CBaseGame :: GetPlayerFromColour( unsigned char colour ){ for( unsigned char i = 0; i < m_Slots.size( ); i++ ) { if( m_Slots[i].GetColour( ) == colour ) return GetPlayerFromSID( i ); } return NULL;}unsigned char CBaseGame :: GetNewPID( ){ // find an unused PID for a new player to use for( unsigned char TestPID = 1; TestPID < 255; TestPID++ ) { if( TestPID == m_VirtualHostPID ) continue; bool InUse = false; for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) { if( !(*i)->GetLeftMessageSent( ) && (*i)->GetPID( ) == TestPID ) { InUse = true; break; } } if( !InUse ) return TestPID; } // this should never happen return 255;}unsigned char CBaseGame :: GetNewColour( ){ // find an unused colour for a player to use for( unsigned char TestColour = 0; TestColour < 12; TestColour++ ) { bool InUse = false; for( unsigned char i = 0; i < m_Slots.size( ); i++ ) { if( m_Slots[i].GetColour( ) == TestColour ) { InUse = true; break; } } if( !InUse ) return TestColour; } // this should never happen return 12;}BYTEARRAY CBaseGame :: GetPIDs( ){ BYTEARRAY result; for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) { if( !(*i)->GetLeftMessageSent( ) ) result.push_back( (*i)->GetPID( ) ); } return result;}BYTEARRAY CBaseGame :: GetPIDs( unsigned char excludePID ){ BYTEARRAY result; for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) { if( !(*i)->GetLeftMessageSent( ) && (*i)->GetPID( ) != excludePID ) result.push_back( (*i)->GetPID( ) ); } return result;}unsigned char CBaseGame :: GetHostPID( ){ // return the player to be considered the host (it can be any player) - mainly used for sending text messages from the bot // try to find the virtual host player first if( m_VirtualHostPID != 255 ) return m_VirtualHostPID; // try to find the owner player next for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) { if( !(*i)->GetLeftMessageSent( ) && (*i)->GetName( ) == m_OwnerName ) return (*i)->GetPID( ); } // okay then, just use the first available player for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) { if( !(*i)->GetLeftMessageSent( ) ) return (*i)->GetPID( ); } return 255;}unsigned char CBaseGame :: GetEmptySlot( bool reserved ){ if( m_Slots.size( ) > 255 ) return 255; if( m_SaveGame ) { // unfortunately we don't know which slot each player was assigned in the savegame // but we do know which slots were occupied and which weren't so let's at least force players to use previously occupied slots vector<CGameSlot> SaveGameSlots = m_SaveGame->GetSlots( ); for( unsigned char i = 0; i < m_Slots.size( ); i++ ) { if( m_Slots[i].GetSlotStatus( ) == SLOTSTATUS_OPEN && SaveGameSlots[i].GetSlotStatus( ) == SLOTSTATUS_OCCUPIED && SaveGameSlots[i].GetComputer( ) == 0 ) return i; } // don't bother with reserved slots in savegames } else { // look for an empty slot for a new player to occupy // 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 for( unsigned char i = 0; i < m_Slots.size( ); i++ ) { if( m_Slots[i].GetSlotStatus( ) == SLOTSTATUS_OPEN ) return i; } if( reserved ) { // no empty slots, but since player is reserved give them a closed slot for( unsigned char i = 0; i < m_Slots.size( ); i++ ) { if( m_Slots[i].GetSlotStatus( ) == SLOTSTATUS_CLOSED ) return i; } // no closed slots either, give them an occupied slot but not one occupied by another reserved player for( unsigned char i = 0; i < m_Slots.size( ); i++ ) { CGamePlayer *Player = GetPlayerFromSID( i ); if( Player && !Player->GetReserved( ) ) return i; } } } return 255;}unsigned char CBaseGame :: GetEmptySlot( unsigned char team, unsigned char PID ){ if( m_Slots.size( ) > 255 ) return 255; // find an empty slot based on player's current slot unsigned char StartSlot = GetSIDFromPID( PID ); if( StartSlot < m_Slots.size( ) ) { if( m_Slots[StartSlot].GetTeam( ) != team ) { // player is trying to move to another team so start looking from the first slot on that team // we actually just start looking from the very first slot since the next few loops will check the team for us StartSlot = 0; } if( m_SaveGame ) { vector<CGameSlot> SaveGameSlots = m_SaveGame->GetSlots( ); for( unsigned char i = StartSlot; i < m_Slots.size( ); i++ ) { if( m_Slots[i].GetSlotStatus( ) == SLOTSTATUS_OPEN && m_Slots[i].GetTeam( ) == team && SaveGameSlots[i].GetSlotStatus( ) == SLOTSTATUS_OCCUPIED && SaveGameSlots[i].GetComputer( ) == 0 ) return i; } for( unsigned char i = 0; i < StartSlot; i++ ) { if( m_Slots[i].GetSlotStatus( ) == SLOTSTATUS_OPEN && m_Slots[i].GetTeam( ) == team && SaveGameSlots[i].GetSlotStatus( ) == SLOTSTATUS_OCCUPIED && SaveGameSlots[i].GetComputer( ) == 0 ) return i; } } else { // find an empty slot on the correct team starting from StartSlot for( unsigned char i = StartSlot; i < m_Slots.size( ); i++ ) { if( m_Slots[i].GetSlotStatus( ) == SLOTSTATUS_OPEN && m_Slots[i].GetTeam( ) == team ) return i; } // didn't find an empty slot, but we could have missed one with SID < StartSlot // 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 for( unsigned char i = 0; i < StartSlot; i++ ) { if( m_Slots[i].GetSlotStatus( ) == SLOTSTATUS_OPEN && m_Slots[i].GetTeam( ) == team ) return i; } } } return 255;}void CBaseGame :: SwapSlots( unsigned char SID1, unsigned char SID2 ){ if( SID1 < m_Slots.size( ) && SID2 < m_Slots.size( ) && SID1 != SID2 ) { CGameSlot Slot1 = m_Slots[SID1]; CGameSlot Slot2 = m_Slots[SID2]; if( m_Map->GetMapGameType( ) != GAMETYPE_CUSTOM ) { // regular game - swap everything m_Slots[SID1] = Slot2; m_Slots[SID2] = Slot1; } else { // custom game - don't swap the team, colour, or race m_Slots[SID1] = CGameSlot( Slot2.GetPID( ), Slot2.GetDownloadStatus( ), Slot2.GetSlotStatus( ), Slot2.GetComputer( ), Slot1.GetTeam( ), Slot1.GetColour( ), Slot1.GetRace( ) ); m_Slots[SID2] = CGameSlot( Slot1.GetPID( ), Slot1.GetDownloadStatus( ), Slot1.GetSlotStatus( ), Slot1.GetComputer( ), Slot2.GetTeam( ), Slot2.GetColour( ), Slot2.GetRace( ) ); } SendAllSlotInfo( ); }}void CBaseGame :: OpenSlot( unsigned char SID, bool kick ){ if( kick ) { CGamePlayer *Player = GetPlayerFromSID( SID ); if( Player ) { Player->SetDeleteMe( true ); Player->SetLeftReason( "was kicked when opening a slot" ); Player->SetLeftCode( PLAYERLEAVE_LOBBY ); } } if( SID < m_Slots.size( ) ) { CGameSlot Slot = m_Slots[SID]; m_Slots[SID] = CGameSlot( 0, 255, SLOTSTATUS_OPEN, 0, Slot.GetTeam( ), Slot.GetColour( ), Slot.GetRace( ) ); SendAllSlotInfo( ); }}void CBaseGame :: CloseSlot( unsigned char SID, bool kick ){ if( kick ) { CGamePlayer *Player = GetPlayerFromSID( SID ); if( Player ) { Player->SetDeleteMe( true ); Player->SetLeftReason( "was kicked when closing a slot" ); Player->SetLeftCode( PLAYERLEAVE_LOBBY ); } } if( SID < m_Slots.size( ) ) { CGameSlot Slot = m_Slots[SID]; m_Slots[SID] = CGameSlot( 0, 255, SLOTSTATUS_CLOSED, 0, Slot.GetTeam( ), Slot.GetColour( ), Slot.GetRace( ) ); SendAllSlotInfo( ); }}void CBaseGame :: ComputerSlot( unsigned char SID, unsigned char skill, bool kick ){ if( kick ) { CGamePlayer *Player = GetPlayerFromSID( SID ); if( Player ) { Player->SetDeleteMe( true ); Player->SetLeftReason( "was kicked when creating a computer in a slot" ); Player->SetLeftCode( PLAYERLEAVE_LOBBY ); } } if( SID < m_Slots.size( ) && skill < 3 ) { CGameSlot Slot = m_Slots[SID]; m_Slots[SID] = CGameSlot( 0, 100, SLOTSTATUS_OCCUPIED, 1, Slot.GetTeam( ), Slot.GetColour( ), Slot.GetRace( ), skill ); SendAllSlotInfo( ); }}void CBaseGame :: ColourSlot( unsigned char SID, unsigned char colour ){ if( SID < m_Slots.size( ) && colour < 12 ) { // make sure the requested colour isn't already taken bool Taken = false; unsigned char TakenSID = 0; for( unsigned char i = 0; i < m_Slots.size( ); i++ ) { if( m_Slots[i].GetColour( ) == colour ) { TakenSID = i; Taken = true; } } if( Taken && m_Slots[TakenSID].GetSlotStatus( ) != SLOTSTATUS_OCCUPIED ) { // the requested colour is currently "taken" by an unused (open or closed) slot // 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 // 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 // one way to solve this (which we do here) is to swap the player's current colour into the unused slot m_Slots[TakenSID].SetColour( m_Slots[SID].GetColour( ) ); m_Slots[SID].SetColour( colour ); SendAllSlotInfo( ); } else if( !Taken ) { // the requested colour isn't used by ANY slot m_Slots[SID].SetColour( colour ); SendAllSlotInfo( ); } }}void CBaseGame :: OpenAllSlots( ){ bool Changed = false; for( vector<CGameSlot> :: iterator i = m_Slots.begin( ); i != m_Slots.end( ); i++ ) { if( (*i).GetSlotStatus( ) == SLOTSTATUS_CLOSED ) { (*i).SetSlotStatus( SLOTSTATUS_OPEN ); Changed = true; } } if( Changed ) SendAllSlotInfo( );}void CBaseGame :: CloseAllSlots( ){ bool Changed = false; for( vector<CGameSlot> :: iterator i = m_Slots.begin( ); i != m_Slots.end( ); i++ ) { if( (*i).GetSlotStatus( ) == SLOTSTATUS_OPEN ) { (*i).SetSlotStatus( SLOTSTATUS_CLOSED ); Changed = true; } } if( Changed ) SendAllSlotInfo( );}void CBaseGame :: ShuffleSlots( ){ // we only want to shuffle the player slots // that means we need to prevent this function from shuffling the open/closed/computer slots too // so we start by copying the player slots to a temporary vector vector<CGameSlot> PlayerSlots; for( vector<CGameSlot> :: iterator i = m_Slots.begin( ); i != m_Slots.end( ); i++ ) { if( (*i).GetSlotStatus( ) == SLOTSTATUS_OCCUPIED && (*i).GetComputer( ) == 0 ) PlayerSlots.push_back( *i ); } // now we shuffle PlayerSlots if( m_Map->GetMapGameType( ) == GAMETYPE_CUSTOM ) { // custom game // rather than rolling our own probably broken shuffle algorithm we use random_shuffle because it's guaranteed to do it properly // so in order to let random_shuffle do all the work we need a vector to operate on // unfortunately we can't just use PlayerSlots because the team/colour/race shouldn't be modified // so make a vector we can use vector<unsigned char> SIDs; for( unsigned char i = 0; i < PlayerSlots.size( ); i++ ) SIDs.push_back( i ); random_shuffle( SIDs.begin( ), SIDs.end( ) ); // now put the PlayerSlots vector in the same order as the SIDs vector vector<CGameSlot> Slots; // as usual don't modify the team/colour/race for( unsigned char i = 0; i < SIDs.size( ); i++ ) 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( ) ) ); PlayerSlots = Slots; } else { // regular game // it's easy when we're allowed to swap the team/colour/race! random_shuffle( PlayerSlots.begin( ), PlayerSlots.end( ) ); } // now we put m_Slots back together again vector<CGameSlot> :: iterator CurrentPlayer = PlayerSlots.begin( ); vector<CGameSlot> Slots; for( vector<CGameSlot> :: iterator i = m_Slots.begin( ); i != m_Slots.end( ); i++ ) { if( (*i).GetSlotStatus( ) == SLOTSTATUS_OCCUPIED && (*i).GetComputer( ) == 0 ) { Slots.push_back( *CurrentPlayer ); CurrentPlayer++; } else Slots.push_back( *i ); } m_Slots = Slots; // and finally tell everyone about the new slot configuration SendAllSlotInfo( );}void CBaseGame :: AddToSpoofed( string server, string name, bool sendMessage ){ CGamePlayer *Player = GetPlayerFromName( name, true ); if( Player ) { Player->SetSpoofedRealm( server ); Player->SetSpoofed( true ); if( sendMessage ) SendAllChat( m_GHost->m_Language->SpoofCheckAcceptedFor( server, name ) ); }}void CBaseGame :: AddToReserved( string name ){ transform( name.begin( ), name.end( ), name.begin( ), (int(*)(int))tolower ); // check that the user is not already reserved for( vector<string> :: iterator i = m_Reserved.begin( ); i != m_Reserved.end( ); i++ ) { if( *i == name ) return; } m_Reserved.push_back( name ); // upgrade the user if they're already in the game for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) { string NameLower = (*i)->GetName( ); transform( NameLower.begin( ), NameLower.end( ), NameLower.begin( ), (int(*)(int))tolower ); if( NameLower == name ) (*i)->SetReserved( true ); }}bool CBaseGame :: IsOwner( string name ){ string OwnerLower = m_OwnerName; transform( name.begin( ), name.end( ), name.begin( ), (int(*)(int))tolower ); transform( OwnerLower.begin( ), OwnerLower.end( ), OwnerLower.begin( ), (int(*)(int))tolower ); return name == OwnerLower;}bool CBaseGame :: IsReserved( string name ){ transform( name.begin( ), name.end( ), name.begin( ), (int(*)(int))tolower ); for( vector<string> :: iterator i = m_Reserved.begin( ); i != m_Reserved.end( ); i++ ) { if( *i == name ) return true; } return false;}bool CBaseGame :: IsDownloading( ){ // returns true if at least one player is downloading the map for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) { if( (*i)->GetDownloadStarted( ) && !(*i)->GetDownloadFinished( ) ) return true; } return false;}void CBaseGame :: StartCountDown( bool force ){ if( !m_CountDownStarted ) { if( force ) { m_CountDownStarted = true; m_CountDownCounter = 5; } else { // check if everyone has the map string StillDownloading; for( vector<CGameSlot> :: iterator i = m_Slots.begin( ); i != m_Slots.end( ); i++ ) { if( (*i).GetSlotStatus( ) == SLOTSTATUS_OCCUPIED && (*i).GetComputer( ) == 0 && (*i).GetDownloadStatus( ) != 100 ) { CGamePlayer *Player = GetPlayerFromPID( (*i).GetPID( ) ); if( Player ) { if( StillDownloading.empty( ) ) StillDownloading = Player->GetName( ); else StillDownloading += ", " + Player->GetName( ); } } } if( !StillDownloading.empty( ) ) SendAllChat( m_GHost->m_Language->PlayersStillDownloading( StillDownloading ) ); // check if everyone is spoof checked string NotSpoofChecked; if( m_GHost->m_SpoofChecks ) { for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) { if( !(*i)->GetSpoofed( ) ) { if( NotSpoofChecked.empty( ) ) NotSpoofChecked = (*i)->GetName( ); else NotSpoofChecked += ", " + (*i)->GetName( ); } } if( !NotSpoofChecked.empty( ) ) { SendAllChat( m_GHost->m_Language->PlayersNotYetSpoofChecked( NotSpoofChecked ) ); if( m_GHost->m_BNETs.size( ) == 1 ) { BYTEARRAY UniqueName = m_GHost->m_BNETs[0]->GetUniqueName( ); if( m_GameState == GAME_PUBLIC ) SendAllChat( m_GHost->m_Language->ManuallySpoofCheckByWhispering( string( UniqueName.begin( ), UniqueName.end( ) ) ) ); else if( m_GameState == GAME_PRIVATE ) SendAllChat( m_GHost->m_Language->SpoofCheckByWhispering( string( UniqueName.begin( ), UniqueName.end( ) ) ) ); } // todotodo: figure something out with multiple realms here } } // check if everyone has been pinged enough (3 times) that the autokicker would have kicked them by now // see function EventPlayerPongToHost for the autokicker code string NotPinged; for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) { if( (*i)->GetNumPings( ) < 3 ) { if( NotPinged.empty( ) ) NotPinged = (*i)->GetName( ); else NotPinged += ", " + (*i)->GetName( ); } } if( !NotPinged.empty( ) ) SendAllChat( m_GHost->m_Language->PlayersNotYetPinged( NotPinged ) ); // if no problems found start the game if( StillDownloading.empty( ) && NotSpoofChecked.empty( ) && NotPinged.empty( ) ) { m_CountDownStarted = true; m_CountDownCounter = 5; } } }}void CBaseGame :: StartCountDownAuto( ){ if( !m_CountDownStarted ) { // check if enough players are present if( GetNumPlayers( ) < m_AutoStartPlayers ) { SendAllChat( m_GHost->m_Language->WaitingForPlayersBeforeAutoStart( UTIL_ToString( m_AutoStartPlayers ) ) ); return; } // check if everyone has the map string StillDownloading; for( vector<CGameSlot> :: iterator i = m_Slots.begin( ); i != m_Slots.end( ); i++ ) { if( (*i).GetSlotStatus( ) == SLOTSTATUS_OCCUPIED && (*i).GetComputer( ) == 0 && (*i).GetDownloadStatus( ) != 100 ) { CGamePlayer *Player = GetPlayerFromPID( (*i).GetPID( ) ); if( Player ) { if( StillDownloading.empty( ) ) StillDownloading = Player->GetName( ); else StillDownloading += ", " + Player->GetName( ); } } } if( !StillDownloading.empty( ) ) { SendAllChat( m_GHost->m_Language->PlayersStillDownloading( StillDownloading ) ); return; } // check if everyone has been pinged enough (3 times) that the autokicker would have kicked them by now // see function EventPlayerPongToHost for the autokicker code string NotPinged; for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) { if( (*i)->GetNumPings( ) < 3 ) { if( NotPinged.empty( ) ) NotPinged = (*i)->GetName( ); else NotPinged += ", " + (*i)->GetName( ); } } if( !NotPinged.empty( ) ) { SendAllChat( m_GHost->m_Language->PlayersNotYetPinged( NotPinged ) ); return; } // if no problems found start the game if( StillDownloading.empty( ) && NotPinged.empty( ) ) { m_CountDownStarted = true; m_CountDownCounter = 10; } }}void CBaseGame :: StopPlayers( string reason ){ // disconnect every player and set their left reason to the passed string // 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) // therefore calling this function when m_GameLoading || m_GameLoaded is roughly equivalent to setting m_Exiting = true // the only difference is whether the code in the Update function is executed or not for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) { (*i)->SetDeleteMe( true ); (*i)->SetLeftReason( reason ); (*i)->SetLeftCode( PLAYERLEAVE_LOST ); }}void CBaseGame :: StopLaggers( string reason ){ for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) { if( (*i)->GetLagging( ) ) { (*i)->SetDeleteMe( true ); (*i)->SetLeftReason( reason ); (*i)->SetLeftCode( PLAYERLEAVE_DISCONNECT ); } }}void CBaseGame :: CreateVirtualHost( ){ if( m_VirtualHostPID != 255 ) return; m_VirtualHostPID = GetNewPID( ); BYTEARRAY IP; IP.push_back( 0 ); IP.push_back( 0 ); IP.push_back( 0 ); IP.push_back( 0 ); SendAll( m_Protocol->SEND_W3GS_PLAYERINFO( m_VirtualHostPID, m_VirtualHostName, IP, IP ) );}void CBaseGame :: DeleteVirtualHost( ){ if( m_VirtualHostPID == 255 ) return; SendAll( m_Protocol->SEND_W3GS_PLAYERLEAVE_OTHERS( m_VirtualHostPID, PLAYERLEAVE_LOBBY ) ); m_VirtualHostPID = 255;}//// sorting classes//class CGamePlayerSortAscByPing{public: bool operator( ) ( CGamePlayer *Player1, CGamePlayer *Player2 ) const { return Player1->GetPing( false ) < Player2->GetPing( false ); }};class CGamePlayerSortDescByPing{public: bool operator( ) ( CGamePlayer *Player1, CGamePlayer *Player2 ) const { return Player1->GetPing( false ) > Player2->GetPing( false ); }};//// CGame//CGame :: CGame( CGHost *nGHost, CMap *nMap, CSaveGame *nSaveGame, uint16_t nHostPort, unsigned char nGameState, string nGameName, string nOwnerName, string nCreatorName, string nCreatorServer ) : CBaseGame( nGHost, nMap, nSaveGame, nHostPort, nGameState, nGameName, nOwnerName, nCreatorName, nCreatorServer ){ m_DBBanLast = NULL; m_DBGame = new CDBGame( 0, string( ), m_Map->GetMapPath( ), string( ), string( ), string( ), 0 ); if( m_Map->GetMapType( ) == "dota" ) m_Stats = new CStatsDOTA( this ); else m_Stats = NULL; m_GameOverTime = 0;}CGame :: ~CGame( ){ if( m_GameLoading || m_GameLoaded ) { // store the CDBGame in the database uint32_t GameID = m_GHost->m_DB->GameAdd( m_GHost->m_BNETs.size( ) == 1 ? m_GHost->m_BNETs[0]->GetServer( ) : string( ), m_DBGame->GetMap( ), m_GameName, m_OwnerName, GetTime( ) - m_StartedLoadingTime, m_GameState, m_CreatorName, m_CreatorServer ); if( GameID > 0 ) { // store the CDBGamePlayers in the database for( vector<CDBGamePlayer *> :: iterator i = m_DBGamePlayers.begin( ); i != m_DBGamePlayers.end( ); i++ ) m_GHost->m_DB->GamePlayerAdd( GameID, (*i)->GetName( ), (*i)->GetIP( ), (*i)->GetSpoofed( ), (*i)->GetSpoofedRealm( ), (*i)->GetReserved( ), (*i)->GetLoadingTime( ), (*i)->GetLeft( ), (*i)->GetLeftReason( ), (*i)->GetTeam( ), (*i)->GetColour( ) ); // store the stats in the database if( m_Stats ) m_Stats->Save( m_GHost->m_DB, GameID ); } } for( vector<CDBBan *> :: iterator i = m_DBBans.begin( ); i != m_DBBans.end( ); i++ ) delete *i; delete m_DBGame; for( vector<CDBGamePlayer *> :: iterator i = m_DBGamePlayers.begin( ); i != m_DBGamePlayers.end( ); i++ ) delete *i; delete m_Stats;}void CGame :: EventPlayerDeleted( CGamePlayer *player ){ CBaseGame :: EventPlayerDeleted( player ); // record everything we need to know about the player for storing in the database later // since we haven't stored the game yet (it's not over yet!) we can't link the gameplayer to the game // see the destructor for where these CDBGamePlayers are stored in the database // we could have inserted an incomplete record on creation and updated it later but this makes for a cleaner interface if( m_GameLoading || m_GameLoaded ) { // todotodo: since we store players that crash during loading it's possible that the stats classes could have no information on them // that could result in a DBGamePlayer without a corresponding DBDotAPlayer - just be aware of the possibility unsigned char SID = GetSIDFromPID( player->GetPID( ) ); unsigned char Team = 255; unsigned char Colour = 255; if( SID < m_Slots.size( ) ) { Team = m_Slots[SID].GetTeam( ); Colour = m_Slots[SID].GetColour( ); } m_DBGamePlayers.push_back( new CDBGamePlayer( 0, 0, player->GetName( ), player->GetExternalIPString( ), player->GetSpoofed( ) ? 1 : 0, player->GetSpoofedRealm( ), player->GetReserved( ) ? 1 : 0, player->GetFinishedLoading( ) ? player->GetFinishedLoadingTicks( ) - m_StartedLoadingTicks : 0, GetTime( ) - m_StartedLoadingTime, player->GetLeftReason( ), Team, Colour ) ); // also keep track of the last player to leave for the !banlast command for( vector<CDBBan *> :: iterator i = m_DBBans.begin( ); i != m_DBBans.end( ); i++ ) { if( (*i)->GetName( ) == player->GetName( ) ) m_DBBanLast = *i; } }}void CGame :: EventPlayerAction( CGamePlayer *player, CIncomingAction *action ){ CBaseGame :: EventPlayerAction( player, action ); // give the stats class a chance to process the action if( m_Stats && m_Stats->ProcessAction( action ) && m_GameOverTime == 0 ) { CONSOLE_Print( "[GAME: " + m_GameName + "] stats class reported game over" ); SendEndMessage( ); m_GameOverTime = GetTime( ); }}void CGame :: EventPlayerBotCommand( CGamePlayer *player, string command, string payload ){ CBaseGame :: EventPlayerBotCommand( player, command, payload ); // todotodo: don't be lazy string User = player->GetName( ); string Command = command; string Payload = payload; bool RootAdminCheck = false; for( vector<CBNET *> :: iterator i = m_GHost->m_BNETs.begin( ); i != m_GHost->m_BNETs.end( ); i++ ) { if( (*i)->GetServer( ) == player->GetSpoofedRealm( ) && (*i)->IsRootAdmin( User ) ) { RootAdminCheck = true; break; } } if( player->GetSpoofed( ) && ( m_GHost->m_DB->AdminCheck( player->GetSpoofedRealm( ), User ) || RootAdminCheck || IsOwner( User ) ) ) { CONSOLE_Print( "[GAME: " + m_GameName + "] admin [" + User + "] sent command [" + Command + "] with payload [" + Payload + "]" ); if( !m_Locked || IsOwner( User ) ) { /***************** * ADMIN COMMANDS * ******************/ // // !ABORT (abort countdown) // !A // // we use "!a" as an alias for abort because you don't have much time to abort the countdown so it's useful for the abort command to be easy to type if( ( Command == "abort" || Command == "a" ) && m_CountDownStarted && !m_GameLoading && !m_GameLoaded ) { SendAllChat( m_GHost->m_Language->CountDownAborted( ) ); m_CountDownStarted = false; } // // !ADDBAN // !BAN // if( ( Command == "addban" || Command == "ban" ) && !Payload.empty( ) && !m_GHost->m_BNETs.empty( ) ) { // extract the victim and the reason // e.g. "Varlock leaver after dying" -> victim: "Varlock", reason: "leaver after dying" string Victim; string Reason; stringstream SS; SS << Payload; SS >> Victim; if( !SS.eof( ) ) { getline( SS, Reason ); string :: size_type Start = Reason.find_first_not_of( " " ); if( Start != string :: npos ) Reason = Reason.substr( Start ); } if( m_GameLoaded ) { string VictimLower = Victim; transform( VictimLower.begin( ), VictimLower.end( ), VictimLower.begin( ), (int(*)(int))tolower ); uint32_t Matches = 0; CDBBan *LastMatch = NULL; // try to match each player with the passed string (e.g. "Varlock" would be matched with "lock") // we use the m_DBBans vector for this in case the player already left and thus isn't in the m_Players vector anymore for( vector<CDBBan *> :: iterator i = m_DBBans.begin( ); i != m_DBBans.end( ); i++ ) { string TestName = (*i)->GetName( ); transform( TestName.begin( ), TestName.end( ), TestName.begin( ), (int(*)(int))tolower ); if( TestName.find( VictimLower ) != string :: npos ) { Matches++; LastMatch = *i; } } if( Matches == 0 ) SendAllChat( m_GHost->m_Language->UnableToBanNoMatchesFound( Victim ) ); else if( Matches == 1 ) { if( !LastMatch->GetServer( ).empty( ) ) { // the user was spoof checked, ban only on the spoofed realm m_GHost->m_DB->BanAdd( LastMatch->GetServer( ), LastMatch->GetName( ), LastMatch->GetIP( ), m_GameName, User, Reason ); } else { // the user wasn't spoof checked, ban on every realm for( vector<CBNET *> :: iterator i = m_GHost->m_BNETs.begin( ); i != m_GHost->m_BNETs.end( ); i++ ) m_GHost->m_DB->BanAdd( (*i)->GetServer( ), LastMatch->GetName( ), LastMatch->GetIP( ), m_GameName, User, Reason ); } CONSOLE_Print( "[GAME: " + m_GameName + "] player [" + LastMatch->GetName( ) + "] was banned by player [" + User + "]" ); SendAllChat( m_GHost->m_Language->PlayerWasBannedByPlayer( LastMatch->GetName( ), User ) ); } else SendAllChat( m_GHost->m_Language->UnableToBanFoundMoreThanOneMatch( Victim ) ); } else { CGamePlayer *LastMatch = NULL; uint32_t Matches = GetPlayerFromNamePartial( Victim, &LastMatch ); if( Matches == 0 ) SendAllChat( m_GHost->m_Language->UnableToBanNoMatchesFound( Victim ) ); else if( Matches == 1 ) { if( !LastMatch->GetSpoofedRealm( ).empty( ) ) { // the user was spoof checked, ban only on the spoofed realm m_GHost->m_DB->BanAdd( LastMatch->GetSpoofedRealm( ), LastMatch->GetName( ), LastMatch->GetExternalIPString( ), m_GameName, User, Reason ); } else { // the user wasn't spoof checked, ban on every realm for( vector<CBNET *> :: iterator i = m_GHost->m_BNETs.begin( ); i != m_GHost->m_BNETs.end( ); i++ ) m_GHost->m_DB->BanAdd( (*i)->GetServer( ), LastMatch->GetName( ), LastMatch->GetExternalIPString( ), m_GameName, User, Reason ); } CONSOLE_Print( "[GAME: " + m_GameName + "] player [" + LastMatch->GetName( ) + "] was banned by player [" + User + "]" ); SendAllChat( m_GHost->m_Language->PlayerWasBannedByPlayer( LastMatch->GetName( ), User ) ); } else SendAllChat( m_GHost->m_Language->UnableToBanFoundMoreThanOneMatch( Victim ) ); } } // // !ANNOUNCE // if( Command == "announce" && !m_CountDownStarted ) { if( Payload.empty( ) || Payload == "off" ) { SendAllChat( m_GHost->m_Language->AnnounceMessageDisabled( ) ); SetAnnounce( 0, string( ) ); } else { // extract the interval and the message // e.g. "30 hello everyone" -> interval: "30", message: "hello everyone" uint32_t Interval; string Message; stringstream SS; SS << Payload; SS >> Interval; if( SS.fail( ) || Interval == 0 ) CONSOLE_Print( "[GAME: " + m_GameName + "] bad input #1 to announce command" ); else { if( SS.eof( ) ) CONSOLE_Print( "[GAME: " + m_GameName + "] missing input #2 to announce command" ); else { getline( SS, Message ); string :: size_type Start = Message.find_first_not_of( " " ); if( Start != string :: npos ) Message = Message.substr( Start ); SendAllChat( m_GHost->m_Language->AnnounceMessageEnabled( ) ); SetAnnounce( Interval, Message ); } } } } // // !AUTOSAVE // if( Command == "autosave" ) { if( Payload == "on" ) { SendAllChat( m_GHost->m_Language->AutoSaveEnabled( ) ); m_AutoSave = true; } else if( Payload == "off" ) { SendAllChat( m_GHost->m_Language->AutoSaveDisabled( ) ); m_AutoSave = false; } } // // !AUTOSTART // if( Command == "autostart" && !m_CountDownStarted ) { if( Payload.empty( ) || Payload == "off" ) { SendAllChat( m_GHost->m_Language->AutoStartDisabled( ) ); m_AutoStartPlayers = 0; } else { uint32_t AutoStartPlayers = UTIL_ToUInt32( Payload ); if( AutoStartPlayers != 0 ) { SendAllChat( m_GHost->m_Language->AutoStartEnabled( UTIL_ToString( AutoStartPlayers ) ) ); m_AutoStartPlayers = AutoStartPlayers; } } } // // !BANLAST // if( Command == "banlast" && m_GameLoaded && !m_GHost->m_BNETs.empty( ) && m_DBBanLast ) { if( !m_DBBanLast->GetServer( ).empty( ) ) { // the user was spoof checked, ban only on the spoofed realm m_GHost->m_DB->BanAdd( m_DBBanLast->GetServer( ), m_DBBanLast->GetName( ), m_DBBanLast->GetIP( ), m_GameName, User, Payload ); } else { // the user wasn't spoof checked, ban on every realm for( vector<CBNET *> :: iterator i = m_GHost->m_BNETs.begin( ); i != m_GHost->m_BNETs.end( ); i++ ) m_GHost->m_DB->BanAdd( (*i)->GetServer( ), m_DBBanLast->GetName( ), m_DBBanLast->GetIP( ), m_GameName, User, Payload ); } CONSOLE_Print( "[GAME: " + m_GameName + "] player [" + m_DBBanLast->GetName( ) + "] was banned by player [" + User + "]" ); SendAllChat( m_GHost->m_Language->PlayerWasBannedByPlayer( m_DBBanLast->GetName( ), User ) ); } // // !CHECK // if( Command == "check" ) { if( !Payload.empty( ) ) { CGamePlayer *LastMatch = NULL; uint32_t Matches = GetPlayerFromNamePartial( Payload, &LastMatch ); if( Matches == 0 ) SendAllChat( m_GHost->m_Language->UnableToCheckPlayerNoMatchesFound( Payload ) ); else if( Matches == 1 ) SendAllChat( m_GHost->m_Language->CheckedPlayer( LastMatch->GetName( ), LastMatch->GetNumPings( ) > 0 ? UTIL_ToString( LastMatch->GetPing( m_GHost->m_LCPings ) ) + "ms" : "N/A", m_GHost->m_DB->FromCheck( UTIL_ByteArrayToUInt32( LastMatch->GetExternalIP( ), true ) ), m_GHost->m_DB->AdminCheck( LastMatch->GetSpoofedRealm( ), LastMatch->GetName( ) ) || RootAdminCheck ? "Yes" : "No", IsOwner( LastMatch->GetName( ) ) ? "Yes" : "No", LastMatch->GetSpoofed( ) ? "Yes" : "No", LastMatch->GetSpoofedRealm( ).empty( ) ? "N/A" : LastMatch->GetSpoofedRealm( ), LastMatch->GetReserved( ) ? "Yes" : "No" ) ); else SendAllChat( m_GHost->m_Language->UnableToCheckPlayerFoundMoreThanOneMatch( Payload ) ); } else SendAllChat( m_GHost->m_Language->CheckedPlayer( User, player->GetNumPings( ) > 0 ? UTIL_ToString( player->GetPing( m_GHost->m_LCPings ) ) + "ms" : "N/A", m_GHost->m_DB->FromCheck( UTIL_ByteArrayToUInt32( player->GetExternalIP( ), true ) ), m_GHost->m_DB->AdminCheck( player->GetSpoofedRealm( ), User ) || RootAdminCheck ? "Yes" : "No", IsOwner( User ) ? "Yes" : "No", player->GetSpoofed( ) ? "Yes" : "No", player->GetSpoofedRealm( ).empty( ) ? "N/A" : player->GetSpoofedRealm( ), player->GetReserved( ) ? "Yes" : "No" ) ); } // // !CHECKBAN // if( Command == "checkban" && !Payload.empty( ) && !m_GHost->m_BNETs.empty( ) ) { for( vector<CBNET *> :: iterator i = m_GHost->m_BNETs.begin( ); i != m_GHost->m_BNETs.end( ); i++ ) { CDBBan *Ban = m_GHost->m_DB->BanCheck( (*i)->GetServer( ), Payload ); if( Ban ) { SendAllChat( m_GHost->m_Language->UserWasBannedOnByBecause( (*i)->GetServer( ), Payload, Ban->GetDate( ), Ban->GetAdmin( ), Ban->GetReason( ) ) ); delete Ban; Ban = NULL; } else SendAllChat( m_GHost->m_Language->UserIsNotBanned( (*i)->GetServer( ), Payload ) ); } } // // !CLOSE (close slot) // if( Command == "close" && !Payload.empty( ) && !m_GameLoading && !m_GameLoaded ) { // close as many slots as specified, e.g. "5 10" closes slots 5 and 10 stringstream SS; SS << Payload; while( !SS.eof( ) ) { uint32_t SID; SS >> SID; if( SS.fail( ) ) { CONSOLE_Print( "[GAME: " + m_GameName + "] bad input to close command" ); break; } else CloseSlot( (unsigned char)( SID - 1 ), true ); } } // // !CLOSEALL // if( Command == "closeall" && !m_GameLoading && !m_GameLoaded ) CloseAllSlots( ); // // !COMP (computer slot) // if( Command == "comp" && !Payload.empty( ) && !m_GameLoading && !m_GameLoaded && !m_SaveGame ) { // extract the slot and the skill // e.g. "1 2" -> slot: "1", skill: "2" uint32_t Slot; uint32_t Skill = 1; stringstream SS; SS << Payload; SS >> Slot; if( SS.fail( ) ) CONSOLE_Print( "[GAME: " + m_GameName + "] bad input #1 to comp command" ); else { if( !SS.eof( ) ) SS >> Skill; if( SS.fail( ) ) CONSOLE_Print( "[GAME: " + m_GameName + "] bad input #2 to comp command" ); else ComputerSlot( (unsigned char)( Slot - 1 ), (unsigned char)Skill, true ); } } // // !COMPCOLOUR (computer colour change) // if( Command == "compcolour" && !Payload.empty( ) && !m_GameLoading && !m_GameLoaded && !m_SaveGame ) { // extract the slot and the colour // e.g. "1 2" -> slot: "1", colour: "2" uint32_t Slot; uint32_t Colour; stringstream SS; SS << Payload; SS >> Slot; if( SS.fail( ) ) CONSOLE_Print( "[GAME: " + m_GameName + "] bad input #1 to compcolour command" ); else { if( SS.eof( ) ) CONSOLE_Print( "[GAME: " + m_GameName + "] missing input #2 to compcolour command" ); else { SS >> Colour; if( SS.fail( ) ) CONSOLE_Print( "[GAME: " + m_GameName + "] bad input #2 to compcolour command" ); else { unsigned char SID = (unsigned char)( Slot - 1 ); if( m_Map->GetMapGameType( ) != GAMETYPE_CUSTOM && Colour < 12 && SID < m_Slots.size( ) ) { if( m_Slots[SID].GetSlotStatus( ) == SLOTSTATUS_OCCUPIED && m_Slots[SID].GetComputer( ) == 1 ) ColourSlot( SID, Colour ); } } } } } // // !COMPHANDICAP (computer handicap change) // if( Command == "comphandicap" && !Payload.empty( ) && !m_GameLoading && !m_GameLoaded && !m_SaveGame ) { // extract the slot and the handicap // e.g. "1 50" -> slot: "1", handicap: "50" uint32_t Slot; uint32_t Handicap; stringstream SS; SS << Payload; SS >> Slot; if( SS.fail( ) ) CONSOLE_Print( "[GAME: " + m_GameName + "] bad input #1 to comphandicap command" ); else { if( SS.eof( ) ) CONSOLE_Print( "[GAME: " + m_GameName + "] missing input #2 to comphandicap command" ); else { SS >> Handicap; if( SS.fail( ) ) CONSOLE_Print( "[GAME: " + m_GameName + "] bad input #2 to comphandicap command" ); else { unsigned char SID = (unsigned char)( Slot - 1 ); if( m_Map->GetMapGameType( ) != GAMETYPE_CUSTOM && ( Handicap == 50 || Handicap == 60 || Handicap == 70 || Handicap == 80 || Handicap == 90 || Handicap == 100 ) && SID < m_Slots.size( ) ) { if( m_Slots[SID].GetSlotStatus( ) == SLOTSTATUS_OCCUPIED && m_Slots[SID].GetComputer( ) == 1 ) { m_Slots[SID].SetHandicap( (unsigned char)Handicap ); SendAllSlotInfo( ); } } } } } } // // !COMPRACE (computer race change) // if( Command == "comprace" && !Payload.empty( ) && !m_GameLoading && !m_GameLoaded && !m_SaveGame ) { // extract the slot and the race // e.g. "1 human" -> slot: "1", race: "human" uint32_t Slot; string Race; stringstream SS; SS << Payload; SS >> Slot; if( SS.fail( ) ) CONSOLE_Print( "[GAME: " + m_GameName + "] bad input #1 to comprace command" ); else { if( SS.eof( ) ) CONSOLE_Print( "[GAME: " + m_GameName + "] missing input #2 to comprace command" ); else { getline( SS, Race ); string :: size_type Start = Race.find_first_not_of( " " ); if( Start != string :: npos ) Race = Race.substr( Start ); transform( Race.begin( ), Race.end( ), Race.begin( ), (int(*)(int))tolower ); unsigned char SID = (unsigned char)( Slot - 1 ); if( m_Map->GetMapGameType( ) != GAMETYPE_CUSTOM && !( m_Map->GetMapFlags( ) & MAPFLAG_RANDOMRACES ) && SID < m_Slots.size( ) ) { if( m_Slots[SID].GetSlotStatus( ) == SLOTSTATUS_OCCUPIED && m_Slots[SID].GetComputer( ) == 1 ) { if( Race == "human" ) { m_Slots[SID].SetRace( SLOTRACE_HUMAN ); SendAllSlotInfo( ); } else if( Race == "orc" ) { m_Slots[SID].SetRace( SLOTRACE_ORC ); SendAllSlotInfo( ); } else if( Race == "night elf" ) { m_Slots[SID].SetRace( SLOTRACE_NIGHTELF ); SendAllSlotInfo( ); } else if( Race == "undead" ) { m_Slots[SID].SetRace( SLOTRACE_UNDEAD ); SendAllSlotInfo( ); } else if( Race == "random" ) { m_Slots[SID].SetRace( SLOTRACE_RANDOM ); SendAllSlotInfo( ); } else CONSOLE_Print( "[GAME: " + m_GameName + "] unknown race [" + Race + "] sent to comprace command" ); } } } } } // // !COMPTEAM (computer team change) // if( Command == "compteam" && !Payload.empty( ) && !m_GameLoading && !m_GameLoaded && !m_SaveGame ) { // extract the slot and the team // e.g. "1 2" -> slot: "1", team: "2" uint32_t Slot; uint32_t Team; stringstream SS; SS << Payload; SS >> Slot; if( SS.fail( ) ) CONSOLE_Print( "[GAME: " + m_GameName + "] bad input #1 to compteam command" ); else { if( SS.eof( ) ) CONSOLE_Print( "[GAME: " + m_GameName + "] missing input #2 to compteam command" ); else { SS >> Team; if( SS.fail( ) ) CONSOLE_Print( "[GAME: " + m_GameName + "] bad input #2 to compteam command" ); else { unsigned char SID = (unsigned char)( Slot - 1 ); if( m_Map->GetMapGameType( ) != GAMETYPE_CUSTOM && Team < 12 && SID < m_Slots.size( ) ) { if( m_Slots[SID].GetSlotStatus( ) == SLOTSTATUS_OCCUPIED && m_Slots[SID].GetComputer( ) == 1 ) { m_Slots[SID].SetTeam( (unsigned char)( Team - 1 ) ); SendAllSlotInfo( ); } } } } } } // // !DOWNLOAD // !DL // if( ( Command == "download" || Command == "dl" ) && !Payload.empty( ) && !m_GameLoading && !m_GameLoaded ) { CGamePlayer *LastMatch = NULL; uint32_t Matches = GetPlayerFromNamePartial( Payload, &LastMatch ); if( Matches == 0 ) SendAllChat( m_GHost->m_Language->UnableToStartDownloadNoMatchesFound( Payload ) ); else if( Matches == 1 ) { if( !LastMatch->GetDownloadStarted( ) && !LastMatch->GetDownloadFinished( ) ) { unsigned char SID = GetSIDFromPID( LastMatch->GetPID( ) ); if( SID < m_Slots.size( ) && m_Slots[SID].GetDownloadStatus( ) != 100 ) { // inform the client that we are willing to send the map CONSOLE_Print( "[GAME: " + m_GameName + "] map download started for player [" + LastMatch->GetName( ) + "]" ); Send( LastMatch, m_Protocol->SEND_W3GS_STARTDOWNLOAD( GetHostPID( ) ) ); LastMatch->SetDownloadAllowed( true ); LastMatch->SetDownloadStarted( true ); LastMatch->SetStartedDownloadingTicks( GetTicks( ) ); } } } else SendAllChat( m_GHost->m_Language->UnableToStartDownloadFoundMoreThanOneMatch( Payload ) ); } // // !DROP // if( Command == "drop" && m_GameLoaded ) StopLaggers( "lagged out (dropped by admin)" ); // // !END // if( Command == "end" && m_GameLoaded ) { CONSOLE_Print( "[GAME: " + m_GameName + "] is over (admin ended game)" ); StopPlayers( "was disconnected (admin ended game)" ); } // // !FROM // if( Command == "from" ) { string Froms; for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) { // we reverse the byte order on the IP because it's stored in network byte order Froms += (*i)->GetName( ); Froms += ": ("; Froms += m_GHost->m_DB->FromCheck( UTIL_ByteArrayToUInt32( (*i)->GetExternalIP( ), true ) ); Froms += ")"; if( i != m_Players.end( ) - 1 ) Froms += ", "; } SendAllChat( Froms ); } // // !HOLD (hold a slot for someone) // if( Command == "hold" && !Payload.empty( ) && !m_GameLoading && !m_GameLoaded ) { // hold as many players as specified, e.g. "Varlock Kilranin" holds players "Varlock" and "Kilranin" stringstream SS; SS << Payload; while( !SS.eof( ) ) { string HoldName; SS >> HoldName; if( SS.fail( ) ) { CONSOLE_Print( "[GAME: " + m_GameName + "] bad input to hold command" ); break; } else { SendAllChat( m_GHost->m_Language->AddedPlayerToTheHoldList( HoldName ) ); AddToReserved( HoldName ); } } } // // !KICK (kick a player) // if( Command == "kick" && !Payload.empty( ) ) { CGamePlayer *LastMatch = NULL; uint32_t Matches = GetPlayerFromNamePartial( Payload, &LastMatch ); if( Matches == 0 ) SendAllChat( m_GHost->m_Language->UnableToKickNoMatchesFound( Payload ) ); else if( Matches == 1 ) { LastMatch->SetDeleteMe( true ); LastMatch->SetLeftReason( m_GHost->m_Language->WasKickedByPlayer( User ) ); if( !m_GameLoading && !m_GameLoaded ) LastMatch->SetLeftCode( PLAYERLEAVE_LOBBY ); else LastMatch->SetLeftCode( PLAYERLEAVE_LOST ); if( !m_GameLoading && !m_GameLoaded ) OpenSlot( GetSIDFromPID( LastMatch->GetPID( ) ), false ); } else SendAllChat( m_GHost->m_Language->UnableToKickFoundMoreThanOneMatch( Payload ) ); } // // !LATENCY (set game latency) // if( Command == "latency" ) { if( Payload.empty( ) ) SendAllChat( m_GHost->m_Language->LatencyIs( UTIL_ToString( m_Latency ) ) ); else { m_Latency = UTIL_ToUInt32( Payload ); if( m_Latency <= 50 ) { m_Latency = 50; SendAllChat( m_GHost->m_Language->SettingLatencyToMinimum( "50" ) ); } else if( m_Latency >= 500 ) { m_Latency = 500; SendAllChat( m_GHost->m_Language->SettingLatencyToMaximum( "500" ) ); } else SendAllChat( m_GHost->m_Language->SettingLatencyTo( UTIL_ToString( m_Latency ) ) ); } } // // !LOCK // if( Command == "lock" && IsOwner( User ) ) { SendAllChat( m_GHost->m_Language->GameLocked( ) ); m_Locked = true; } // // !MUTEALL // if( Command == "muteall" && m_GameLoaded ) { SendAllChat( m_GHost->m_Language->GlobalChatMuted( ) ); m_MuteAll = true; } // // !OPEN (open slot) // if( Command == "open" && !Payload.empty( ) && !m_GameLoading && !m_GameLoaded ) { // open as many slots as specified, e.g. "5 10" opens slots 5 and 10 stringstream SS; SS << Payload; while( !SS.eof( ) ) { uint32_t SID; SS >> SID; if( SS.fail( ) ) { CONSOLE_Print( "[GAME: " + m_GameName + "] bad input to open command" ); break; } else OpenSlot( (unsigned char)( SID - 1 ), true ); } } // // !OPENALL // if( Command == "openall" && !m_GameLoading && !m_GameLoaded ) OpenAllSlots( ); // // !OWNER (set game owner) // if( Command == "owner" ) { if( IsOwner( User ) || !GetPlayerFromName( m_OwnerName, false ) ) { if( !Payload.empty( ) ) { SendAllChat( m_GHost->m_Language->SettingGameOwnerTo( Payload ) ); m_OwnerName = Payload; } else { SendAllChat( m_GHost->m_Language->SettingGameOwnerTo( User ) ); m_OwnerName = User; } } else SendAllChat( m_GHost->m_Language->UnableToSetGameOwner( m_OwnerName ) ); } // // !PING // if( Command == "ping" ) { // kick players with ping higher than payload if payload isn't empty // we only do this if the game hasn't started since we don't want to kick players from a game in progress uint32_t Kicked = 0; uint32_t KickPing = 0; if( !m_GameLoading && !m_GameLoaded && !Payload.empty( ) ) KickPing = UTIL_ToUInt32( Payload ); // copy the m_Players vector so we can sort by descending ping so it's easier to find players with high pings vector<CGamePlayer *> SortedPlayers = m_Players; sort( SortedPlayers.begin( ), SortedPlayers.end( ), CGamePlayerSortDescByPing( ) ); string Pings; for( vector<CGamePlayer *> :: iterator i = SortedPlayers.begin( ); i != SortedPlayers.end( ); i++ ) { Pings += (*i)->GetName( ); Pings += ": "; if( (*i)->GetNumPings( ) > 0 ) { Pings += UTIL_ToString( (*i)->GetPing( m_GHost->m_LCPings ) ); if( !m_GameLoading && !m_GameLoaded && !(*i)->GetReserved( ) && !Payload.empty( ) && (*i)->GetPing( m_GHost->m_LCPings ) > KickPing ) { (*i)->SetDeleteMe( true ); (*i)->SetLeftReason( "was kicked for excessive ping " + UTIL_ToString( (*i)->GetPing( m_GHost->m_LCPings ) ) + " > " + UTIL_ToString( KickPing ) ); (*i)->SetLeftCode( PLAYERLEAVE_LOBBY ); OpenSlot( GetSIDFromPID( (*i)->GetPID( ) ), false ); Kicked++; } Pings += "ms"; } else Pings += "N/A"; if( i != SortedPlayers.end( ) - 1 ) Pings += ", "; } SendAllChat( Pings ); if( Kicked > 0 ) SendAllChat( m_GHost->m_Language->KickingPlayersWithPingsGreaterThan( UTIL_ToString( Kicked ), UTIL_ToString( KickPing ) ) ); } // // !PRIV (rehost as private game) // if( Command == "priv" && !Payload.empty( ) && !m_CountDownStarted && !m_SaveGame ) { CONSOLE_Print( "[GAME: " + m_GameName + "] trying to rehost as private game [" + Payload + "]" ); m_GameState = GAME_PRIVATE; m_GameName = Payload; for( vector<CBNET *> :: iterator i = m_GHost->m_BNETs.begin( ); i != m_GHost->m_BNETs.end( ); i++ ) { (*i)->SendGameUncreate( ); (*i)->SendGameCreate( m_GameState, m_GameName, string( ), m_Map, NULL ); } m_CreationTime = GetTime( ); m_LastRefreshTime = GetTime( ); } // // !PUB (rehost as public game) // if( Command == "pub" && !Payload.empty( ) && !m_CountDownStarted && !m_SaveGame ) { CONSOLE_Print( "[GAME: " + m_GameName + "] trying to rehost as public game [" + Payload + "]" ); m_GameState = GAME_PUBLIC; m_GameName = Payload; for( vector<CBNET *> :: iterator i = m_GHost->m_BNETs.begin( ); i != m_GHost->m_BNETs.end( ); i++ ) { (*i)->SendGameUncreate( ); (*i)->SendGameCreate( m_GameState, m_GameName, string( ), m_Map, NULL ); } m_CreationTime = GetTime( ); m_LastRefreshTime = GetTime( ); } // // !REFRESH (turn on or off refresh messages) // if( Command == "refresh" && !m_CountDownStarted ) { if( Payload == "on" ) { SendAllChat( m_GHost->m_Language->RefreshMessagesEnabled( ) ); m_RefreshMessages = true; } else if( Payload == "off" ) { SendAllChat( m_GHost->m_Language->RefreshMessagesDisabled( ) ); m_RefreshMessages = false; } } // // !SENDLAN // if( Command == "sendlan" && !Payload.empty( ) && !m_CountDownStarted ) { // extract the ip and the port // e.g. "1.2.3.4 6112" -> ip: "1.2.3.4", port: "6112" string IP; uint32_t Port = 6112; stringstream SS; SS << Payload; SS >> IP; if( !SS.eof( ) ) SS >> Port; if( SS.fail( ) ) CONSOLE_Print( "[GAME: " + m_GameName + "] bad inputs to sendlan command" ); else { // we send 12 for SlotsTotal because this determines how many PID's Warcraft 3 allocates // we need to make sure Warcraft 3 allocates at least SlotsTotal + 1 but at most 12 PID's // 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) // however, we can't send 13 for SlotsTotal because this causes Warcraft 3 to crash when sharing control of units // 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) // we also send 12 for SlotsOpen because Warcraft 3 assumes there's always at least one player in the game (the host) // 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 // the easiest solution is to simply send 12 for both so the game will always show up as (1/12) players BYTEARRAY MapGameType; // construct the correct W3GS_GAMEINFO packet if( m_SaveGame ) { MapGameType.push_back( 0 ); MapGameType.push_back( 2 ); MapGameType.push_back( 0 ); MapGameType.push_back( 0 ); BYTEARRAY MapWidth; MapWidth.push_back( 0 ); MapWidth.push_back( 0 ); BYTEARRAY MapHeight; MapHeight.push_back( 0 ); MapHeight.push_back( 0 ); m_GHost->m_UDPSocket->SendTo( IP, Port, m_Protocol->SEND_W3GS_GAMEINFO( MapGameType, m_Map->GetMapGameFlags( ), MapWidth, MapHeight, m_GameName, "Varlock", GetTime( ) - m_CreationTime, "Save\\Multiplayer\\" + m_SaveGame->GetFileNameNoPath( ), m_SaveGame->GetMagicNumber( ), 12, 12, m_HostPort, m_HostCounter ) ); } else { MapGameType.push_back( m_Map->GetMapGameType( ) ); MapGameType.push_back( 0 ); MapGameType.push_back( 0 ); MapGameType.push_back( 0 ); m_GHost->m_UDPSocket->SendTo( IP, Port, m_Protocol->SEND_W3GS_GAMEINFO( MapGameType, m_Map->GetMapGameFlags( ), m_Map->GetMapWidth( ), m_Map->GetMapHeight( ), m_GameName, "Varlock", GetTime( ) - m_CreationTime, m_Map->GetMapPath( ), m_Map->GetMapCRC( ), 12, 12, m_HostPort, m_HostCounter ) ); } } } // // !SP // if( Command == "sp" && !m_CountDownStarted ) { SendAllChat( m_GHost->m_Language->ShufflingPlayers( ) ); ShuffleSlots( ); } // // !START // if( Command == "start" && !m_CountDownStarted ) { // if the player sent "!start force" skip the checks and start the countdown // otherwise check that the game is ready to start if( Payload == "force" ) StartCountDown( true ); else StartCountDown( false ); } // // !SWAP (swap slots) // if( Command == "swap" && !Payload.empty( ) && !m_GameLoading && !m_GameLoaded ) { uint32_t SID1; uint32_t SID2; stringstream SS; SS << Payload; SS >> SID1; if( SS.fail( ) ) CONSOLE_Print( "[GAME: " + m_GameName + "] bad input #1 to swap command" ); else { if( SS.eof( ) ) CONSOLE_Print( "[GAME: " + m_GameName + "] missing input #2 to swap command" ); else { SS >> SID2; if( SS.fail( ) ) CONSOLE_Print( "[GAME: " + m_GameName + "] bad input #2 to swap command" ); else SwapSlots( (unsigned char)( SID1 - 1 ), (unsigned char)( SID2 - 1 ) ); } } } // // !SYNCLIMIT // if( Command == "synclimit" ) { if( Payload.empty( ) ) SendAllChat( m_GHost->m_Language->SyncLimitIs( UTIL_ToString( m_SyncLimit ) ) ); else { m_SyncLimit = UTIL_ToUInt32( Payload ); if( m_SyncLimit <= 10 ) { m_SyncLimit = 10; SendAllChat( m_GHost->m_Language->SettingSyncLimitToMinimum( "10" ) ); } else if( m_SyncLimit >= 10000 ) { m_SyncLimit = 10000; SendAllChat( m_GHost->m_Language->SettingSyncLimitToMaximum( "10000" ) ); } else SendAllChat( m_GHost->m_Language->SettingSyncLimitTo( UTIL_ToString( m_SyncLimit ) ) ); } } // // !UNHOST // if( Command == "unhost" && !m_CountDownStarted ) m_Exiting = true; // // !UNLOCK // if( Command == "unlock" && IsOwner( User ) ) { SendAllChat( m_GHost->m_Language->GameUnlocked( ) ); m_Locked = false; } // // !UNMUTEALL // if( Command == "unmuteall" && m_GameLoaded ) { SendAllChat( m_GHost->m_Language->GlobalChatUnmuted( ) ); m_MuteAll = false; } } else { CONSOLE_Print( "[GAME: " + m_GameName + "] admin command ignored, the game is locked" ); SendChat( player, m_GHost->m_Language->TheGameIsLocked( ) ); } } else CONSOLE_Print( "[GAME: " + m_GameName + "] user [" + User + "] sent command [" + Command + "] with payload [" + Payload + "]" ); /********************* * NON ADMIN COMMANDS * *********************/ // // !CHECKME // if( Command == "checkme" ) SendChat( player, m_GHost->m_Language->CheckedPlayer( User, player->GetNumPings( ) > 0 ? UTIL_ToString( player->GetPing( m_GHost->m_LCPings ) ) + "ms" : "N/A", m_GHost->m_DB->FromCheck( UTIL_ByteArrayToUInt32( player->GetExternalIP( ), true ) ), m_GHost->m_DB->AdminCheck( player->GetSpoofedRealm( ), User ) || RootAdminCheck ? "Yes" : "No", IsOwner( User ) ? "Yes" : "No", player->GetSpoofed( ) ? "Yes" : "No", player->GetSpoofedRealm( ).empty( ) ? "N/A" : player->GetSpoofedRealm( ), player->GetReserved( ) ? "Yes" : "No" ) ); // // !STATS // if( Command == "stats" && GetTime( ) >= player->GetStatsSentTime( ) + 5 ) { string StatsUser = User; if( !Payload.empty( ) ) StatsUser = Payload; CDBGamePlayerSummary *GamePlayerSummary = m_GHost->m_DB->GamePlayerSummaryCheck( StatsUser ); if( GamePlayerSummary ) { SendAllChat( m_GHost->m_Language->HasPlayedGamesWithThisBot( StatsUser, GamePlayerSummary->GetFirstGameDateTime( ), GamePlayerSummary->GetLastGameDateTime( ), UTIL_ToString( GamePlayerSummary->GetTotalGames( ) ), UTIL_ToString( (float)GamePlayerSummary->GetAvgLoadingTime( ) / 1000, 2 ), UTIL_ToString( GamePlayerSummary->GetAvgLeftPercent( ) ) ) ); delete GamePlayerSummary; GamePlayerSummary = NULL; } else SendAllChat( m_GHost->m_Language->HasntPlayedGamesWithThisBot( StatsUser ) ); player->SetStatsSentTime( GetTime( ) ); } // // !STATSDOTA // if( Command == "statsdota" && GetTime( ) >= player->GetStatsDotASentTime( ) + 5 ) { string StatsUser = User; if( !Payload.empty( ) ) StatsUser = Payload; CDBDotAPlayerSummary *DotAPlayerSummary = m_GHost->m_DB->DotAPlayerSummaryCheck( StatsUser ); if( DotAPlayerSummary ) { SendAllChat( m_GHost->m_Language->HasPlayedDotAGamesWithThisBot( StatsUser, UTIL_ToString( DotAPlayerSummary->GetTotalGames( ) ), UTIL_ToString( DotAPlayerSummary->GetTotalWins( ) ), UTIL_ToString( DotAPlayerSummary->GetTotalLosses( ) ), UTIL_ToString( DotAPlayerSummary->GetTotalKills( ) ), UTIL_ToString( DotAPlayerSummary->GetTotalDeaths( ) ), UTIL_ToString( DotAPlayerSummary->GetTotalCreepKills( ) ), UTIL_ToString( DotAPlayerSummary->GetTotalCreepDenies( ) ), UTIL_ToString( DotAPlayerSummary->GetTotalAssists( ) ), UTIL_ToString( DotAPlayerSummary->GetTotalNeutralKills( ) ) ) ); delete DotAPlayerSummary; DotAPlayerSummary = NULL; } else SendAllChat( m_GHost->m_Language->HasntPlayedDotAGamesWithThisBot( StatsUser ) ); player->SetStatsDotASentTime( GetTime( ) ); } // // !VERSION // if( Command == "version" ) { if( player->GetSpoofed( ) && ( m_GHost->m_DB->AdminCheck( player->GetSpoofedRealm( ), User ) || RootAdminCheck || IsOwner( User ) ) ) SendAllChat( m_GHost->m_Language->VersionAdmin( m_GHost->m_Version ) ); else SendAllChat( m_GHost->m_Language->VersionNotAdmin( m_GHost->m_Version ) ); }}void CGame :: EventGameStarted( ){ CBaseGame :: EventGameStarted( ); // record everything we need to ban each player in case we decide to do so later // this is because when a player leaves the game an admin might want to ban that player // but since the player has already left the game we don't have access to their information anymore // so we create a "potential ban" for each player and only store it in the database if requested to by an admin for( vector<CGamePlayer *> :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) m_DBBans.push_back( new CDBBan( (*i)->GetSpoofedRealm( ), (*i)->GetName( ), (*i)->GetExternalIPString( ), string( ), string( ), string( ), string( ) ) );}//// CAdminGame//CAdminGame :: CAdminGame( CGHost *nGHost, CMap *nMap, CSaveGame *nSaveGame, uint16_t nHostPort, unsigned char nGameState, string nGameName, string nPassword ) : CBaseGame( nGHost, nMap, nSaveGame, nHostPort, nGameState, nGameName, string( ), string( ), string( ) ){ m_VirtualHostName = "|cFFC04040Admin"; m_MuteLobby = true; m_Password = nPassword;}CAdminGame :: ~CAdminGame( ){}void CAdminGame :: SendWelcomeMessage( CGamePlayer *player ){ SendChat( player, " " ); SendChat( player, " " ); SendChat( player, "GHost++ Admin Game http://forum.codelain.com/" ); SendChat( player, "-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-" ); SendChat( player, "Commands: addadmin, checkadmin, countadmins, deladmin" ); SendChat( player, "Commands: disable, enable, end, exit, getgame, getgames" ); SendChat( player, "Commands: hostsg, load, loadsg, map, password, priv, privby" ); SendChat( player, "Commands: pub, pubby, quit, saygame, saygames, unhost" );}void CAdminGame :: EventPlayerJoined( CPotentialPlayer *potential, CIncomingJoinPlayer *joinPlayer ){ uint32_t Time = GetTime( ); for( vector<TempBan> :: iterator i = m_TempBans.begin( ); i != m_TempBans.end( ); ) { // remove old tempbans if( Time >= (*i).second ) i = m_TempBans.erase( i ); else { if( (*i).first == potential->GetExternalIPString( ) ) { // tempbanned, goodbye // this is just going to close the socket which displays a "game not found" message to the player potential->SetDeleteMe( true ); CONSOLE_Print( "[ADMINGAME] player [" + joinPlayer->GetName( ) + "] at ip [" + (*i).first + "] is trying to join the game but is tempbanned" ); return; } i++; } } CBaseGame :: EventPlayerJoined( potential, joinPlayer );}void CAdminGame :: EventPlayerBotCommand( CGamePlayer *player, string command, string payload ){ CBaseGame :: EventPlayerBotCommand( player, command, payload ); // todotodo: don't be lazy string User = player->GetName( ); string Command = command; string Payload = payload; if( player->GetLoggedIn( ) ) { CONSOLE_Print( "[ADMINGAME] admin [" + User + "] sent command [" + Command + "] with payload [" + Payload + "]" ); /***************** * ADMIN COMMANDS * ******************/ // // !ADDADMIN // if( Command == "addadmin" && !Payload.empty( ) ) { // extract the name and the server // e.g. "Varlock useast.battle.net" -> name: "Varlock", server: "useast.battle.net" string Name; string Server; stringstream SS; SS << Payload; SS >> Name; if( SS.eof( ) ) { if( m_GHost->m_BNETs.size( ) == 1 ) Server = m_GHost->m_BNETs[0]->GetServer( ); else CONSOLE_Print( "[ADMINGAME] missing input #2 to addadmin command" ); } else SS >> Server; if( !Server.empty( ) ) { if( m_GHost->m_DB->AdminCheck( Server, Name ) ) SendChat( player, m_GHost->m_Language->UserIsAlreadyAnAdmin( Server, Name ) ); else { if( m_GHost->m_DB->AdminAdd( Server, Name ) ) SendChat( player, m_GHost->m_Language->AddedUserToAdminDatabase( Server, Name ) ); else SendChat( player, m_GHost->m_Language->ErrorAddingUserToAdminDatabase( Server, Name ) ); } } } // // !CHECKADMIN // if( Command == "checkadmin" && !Payload.empty( ) ) { // extract the name and the server // e.g. "Varlock useast.battle.net" -> name: "Varlock", server: "useast.battle.net" string Name; string Server; stringstream SS; SS << Payload; SS >> Name; if( SS.eof( ) ) { if( m_GHost->m_BNETs.size( ) == 1 ) Server = m_GHost->m_BNETs[0]->GetServer( ); else CONSOLE_Print( "[ADMINGAME] missing input #2 to checkadmin command" ); } else SS >> Server; if( !Server.empty( ) ) { if( m_GHost->m_DB->AdminCheck( Server, Name ) ) SendChat( player, m_GHost->m_Language->UserIsAnAdmin( Server, Name ) ); else SendChat( player, m_GHost->m_Language->UserIsNotAnAdmin( Server, Name ) ); } } // // !COUNTADMINS // if( Command == "countadmins" ) { string Server = Payload; if( Server.empty( ) && m_GHost->m_BNETs.size( ) == 1 ) Server = m_GHost->m_BNETs[0]->GetServer( ); if( !Server.empty( ) ) { uint32_t Count = m_GHost->m_DB->AdminCount( Server ); if( Count == 0 ) SendChat( player, m_GHost->m_Language->ThereAreNoAdmins( Server ) ); else if( Count == 1 ) SendChat( player, m_GHost->m_Language->ThereIsAdmin( Server ) ); else SendChat( player, m_GHost->m_Language->ThereAreAdmins( Server, UTIL_ToString( Count ) ) ); } } // // !DELADMIN // if( Command == "deladmin" && !Payload.empty( ) ) { // extract the name and the server // e.g. "Varlock useast.battle.net" -> name: "Varlock", server: "useast.battle.net" string Name; string Server; stringstream SS; SS << Payload; SS >> Name; if( SS.eof( ) ) { if( m_GHost->m_BNETs.size( ) == 1 ) Server = m_GHost->m_BNETs[0]->GetServer( ); else CONSOLE_Print( "[ADMINGAME] missing input #2 to deladmin command" ); } else SS >> Server; if( !Server.empty( ) ) { if( !m_GHost->m_DB->AdminCheck( Server, Name ) ) SendChat( player, m_GHost->m_Language->UserIsNotAnAdmin( Server, Name ) ); else { if( m_GHost->m_DB->AdminRemove( Server, Name ) ) SendChat( player, m_GHost->m_Language->DeletedUserFromAdminDatabase( Server, Name ) ); else SendChat( player, m_GHost->m_Language->ErrorDeletingUserFromAdminDatabase( Server, Name ) ); } } } // // !DISABLE // if( Command == "disable" ) { SendChat( player, m_GHost->m_Language->BotDisabled( ) ); m_GHost->m_Enabled = false; } // // !ENABLE // if( Command == "enable" ) { SendChat( player, m_GHost->m_Language->BotEnabled( ) ); m_GHost->m_Enabled = true; } // // !END // if( Command == "end" && !Payload.empty( ) ) { // todotodo: what if a game ends just as you're typing this command and the numbering changes? uint32_t GameNumber = UTIL_ToUInt32( Payload ) - 1; if( GameNumber < m_GHost->m_Games.size( ) ) { SendChat( player, m_GHost->m_Language->EndingGame( m_GHost->m_Games[GameNumber]->GetDescription( ) ) ); CONSOLE_Print( "[GAME: " + m_GHost->m_Games[GameNumber]->GetGameName( ) + "] is over (admin ended game)" ); m_GHost->m_Games[GameNumber]->StopPlayers( "was disconnected (admin ended game)" ); } else SendChat( player, m_GHost->m_Language->GameNumberDoesntExist( Payload ) ); } // // !EXIT // !QUIT // if( Command == "exit" || Command == "quit" ) { if( Payload == "force" ) m_Exiting = true; else { if( m_GHost->m_CurrentGame || !m_GHost->m_Games.empty( ) ) SendChat( player, m_GHost->m_Language->AtLeastOneGameActiveUseForceToShutdown( ) ); else m_Exiting = true; } } // // !GETGAME // if( Command == "getgame" && !Payload.empty( ) ) { uint32_t GameNumber = UTIL_ToUInt32( Payload ) - 1; if( GameNumber < m_GHost->m_Games.size( ) ) SendChat( player, m_GHost->m_Language->GameNumberIs( Payload, m_GHost->m_Games[GameNumber]->GetDescription( ) ) ); else SendChat( player, m_GHost->m_Language->GameNumberDoesntExist( Payload ) ); } // // !GETGAMES // if( Command == "getgames" ) { if( m_GHost->m_CurrentGame ) SendChat( player, m_GHost->m_Language->GameIsInTheLobby( m_GHost->m_CurrentGame->GetDescription( ), UTIL_ToString( m_GHost->m_Games.size( ) ), UTIL_ToString( m_GHost->m_MaxGames ) ) ); else SendChat( player, m_GHost->m_Language->ThereIsNoGameInTheLobby( UTIL_ToString( m_GHost->m_Games.size( ) ), UTIL_ToString( m_GHost->m_MaxGames ) ) ); } if( Command == "hostsg" && !Payload.empty( ) ) m_GHost->CreateGame( GAME_PRIVATE, true, Payload, User, User, string( ), false ); // // !LOAD (load config file) // !MAP // if( Command == "load" || Command == "map" ) { if( Payload.empty( ) ) SendChat( player, m_GHost->m_Language->CurrentlyLoadedMapCFGIs( m_GHost->m_Map->GetCFGFile( ) ) ); else { // only load files in the current directory just to be safe if( Payload.find( "/" ) != string :: npos || Payload.find( "\\" ) != string :: npos ) SendChat( player, m_GHost->m_Language->UnableToLoadConfigFilesOutside( ) ); else { string File = m_GHost->m_MapCFGPath + Payload + ".cfg"; if( UTIL_FileExists( File ) ) { // we have to be careful here because we didn't copy the map data when creating the game (there's only one global copy) // therefore if we change the map data while a game is in the lobby everything will get screwed up // the easiest solution is to simply reject the command if a game is in the lobby if( m_GHost->m_CurrentGame ) SendChat( player, m_GHost->m_Language->UnableToLoadConfigFileGameInLobby( ) ); else { SendChat( player, m_GHost->m_Language->LoadingConfigFile( File ) ); CConfig MapCFG; MapCFG.Read( File ); m_GHost->m_Map->Load( &MapCFG, File ); } } else SendChat( player, m_GHost->m_Language->UnableToLoadConfigFileDoesntExist( File ) ); } } } // // !LOADSG // if( Command == "loadsg" && !Payload.empty( ) ) { // only load files in the current directory just to be safe if( Payload.find( "/" ) != string :: npos || Payload.find( "\\" ) != string :: npos ) SendChat( player, m_GHost->m_Language->UnableToLoadSaveGamesOutside( ) ); else { string File = m_GHost->m_SaveGamePath + Payload + ".w3z"; string FileNoPath = Payload + ".w3z"; if( UTIL_FileExists( File ) ) { if( m_GHost->m_CurrentGame ) SendChat( player, m_GHost->m_Language->UnableToLoadSaveGameGameInLobby( ) ); else { SendChat( player, m_GHost->m_Language->LoadingSaveGame( File ) ); m_GHost->m_SaveGame->Load( File, false ); m_GHost->m_SaveGame->ParseSaveGame( ); m_GHost->m_SaveGame->SetFileName( File ); m_GHost->m_SaveGame->SetFileNameNoPath( FileNoPath ); } } else SendChat( player, m_GHost->m_Language->UnableToLoadSaveGameDoesntExist( File ) ); } } // // !PRIV (host private game) // if( Command == "priv" && !Payload.empty( ) ) m_GHost->CreateGame( GAME_PRIVATE, false, Payload, User, User, string( ), false ); // // !PRIVBY (host private game by other player) // if( Command == "privby" && !Payload.empty( ) ) { // extract the owner and the game name // e.g. "Varlock dota 6.54b arem ~~~" -> owner: "Varlock", game name: "dota 6.54b arem ~~~" string Owner; string GameName; string :: size_type GameNameStart = Payload.find( " " ); if( GameNameStart != string :: npos ) { Owner = Payload.substr( 0, GameNameStart ); GameName = Payload.substr( GameNameStart + 1 ); m_GHost->CreateGame( GAME_PRIVATE, false, GameName, Owner, User, string( ), false ); } } // // !PUB (host public game) // if( Command == "pub" && !Payload.empty( ) ) m_GHost->CreateGame( GAME_PUBLIC, false, Payload, User, User, string( ), false ); // // !PUBBY (host public game by other player) // if( Command == "pubby" && !Payload.empty( ) ) { // extract the owner and the game name // e.g. "Varlock dota 6.54b arem ~~~" -> owner: "Varlock", game name: "dota 6.54b arem ~~~" string Owner; string GameName; string :: size_type GameNameStart = Payload.find( " " ); if( GameNameStart != string :: npos ) { Owner = Payload.substr( 0, GameNameStart ); GameName = Payload.substr( GameNameStart + 1 ); m_GHost->CreateGame( GAME_PUBLIC, false, GameName, Owner, User, string( ), false ); } } // // !SAYGAME // if( Command == "saygame" && !Payload.empty( ) ) { // extract the game number and the message // e.g. "3 hello everyone" -> game number: "3", message: "hello everyone" uint32_t GameNumber; string Message; stringstream SS; SS << Payload; SS >> GameNumber; if( SS.fail( ) ) CONSOLE_Print( "[GAME: " + m_GameName + "] bad input #1 to saygame command" ); else { if( SS.eof( ) ) CONSOLE_Print( "[GAME: " + m_GameName + "] missing input #2 to saygame command" ); else { getline( SS, Message ); string :: size_type Start = Message.find_first_not_of( " " ); if( Start != string :: npos ) Message = Message.substr( Start ); if( GameNumber - 1 < m_GHost->m_Games.size( ) ) m_GHost->m_Games[GameNumber - 1]->SendAllChat( "ADMIN: " + Message ); else SendChat( player, m_GHost->m_Language->GameNumberDoesntExist( UTIL_ToString( GameNumber ) ) ); } } } // // !SAYGAMES // if( Command == "saygames" && !Payload.empty( ) ) { if( m_GHost->m_CurrentGame ) m_GHost->m_CurrentGame->SendAllChat( Payload ); for( vector<CBaseGame *> :: iterator i = m_GHost->m_Games.begin( ); i != m_GHost->m_Games.end( ); i++ ) (*i)->SendAllChat( "ADMIN: " + Payload ); } // // !UNHOST // if( Command == "unhost" ) { if( m_GHost->m_CurrentGame ) { if( m_GHost->m_CurrentGame->GetCountDownStarted( ) ) SendChat( player, m_GHost->m_Language->UnableToUnhostGameCountdownStarted( m_GHost->m_CurrentGame->GetDescription( ) ) ); else { SendChat( player, m_GHost->m_Language->UnhostingGame( m_GHost->m_CurrentGame->GetDescription( ) ) ); m_GHost->m_CurrentGame->SetExiting( true ); } } else SendChat( player, m_GHost->m_Language->UnableToUnhostGameNoGameInLobby( ) ); } } else CONSOLE_Print( "[ADMINGAME] user [" + User + "] sent command [" + Command + "] with payload [" + Payload + "]" ); /********************* * NON ADMIN COMMANDS * *********************/ // // !PASSWORD // if( Command == "password" && !player->GetLoggedIn( ) ) { if( !m_Password.empty( ) && Payload == m_Password ) { CONSOLE_Print( "[ADMINGAME] user [" + User + "] logged in" ); SendChat( player, m_GHost->m_Language->AdminLoggedIn( ) ); player->SetLoggedIn( true ); } else { uint32_t LoginAttempts = player->GetLoginAttempts( ) + 1; player->SetLoginAttempts( LoginAttempts ); CONSOLE_Print( "[ADMINGAME] user [" + User + "] login attempt failed" ); SendChat( player, m_GHost->m_Language->AdminInvalidPassword( UTIL_ToString( LoginAttempts ) ) ); if( LoginAttempts >= 1 ) { player->SetDeleteMe( true ); player->SetLeftReason( "was kicked for too many failed login attempts" ); // tempban for 5 seconds to prevent bruteforcing m_TempBans.push_back( TempBan( player->GetExternalIPString( ), GetTime( ) + 5 ) ); } } }}