/* $Id: clientcommunicator.cpp,v 1.11.4.1 2006/03/26 08:24:22 chfreund Exp $ */ #include "clientcommunicator.hpp" #include "message.hpp" #include "serverentry.hpp" #include "serverconnectiondirect.hpp" #include "serverconnectiontcp.hpp" #include "serverconnectiontcpudp.hpp" #include "tcpconnection.hpp" #include "server.hpp" #include "client.hpp" #include "avatar.hpp" #include "string.hpp" ClientCommunicator::ClientCommunicator( Client* client ) : m_client( client ), m_serverConnection( 0 ), m_lastMessage( 0 ), m_world( 0 ), m_frameTimer( 0 ), m_waitingPlayer( 0 ) { m_mutex = SDL_CreateMutex(); m_cond = SDL_CreateCond(); m_baitSocket = SDLNet_UDP_Open( 0 ); ASSERT( m_baitSocket, "ClientCommunicator::ClientCommunicator: could not create bait socket (%s)\n", SDLNet_GetError() ); m_baitPacket = SDLNet_AllocPacket( 200 ); ASSERT( m_baitPacket, "ClientCommunicator::ClientCommunicator: could not allocate bait packet (%s)\n", SDLNet_GetError() ); /* m_baitPacket->data[0] = 0xb8; m_baitPacket->data[1] = 0xb8;*/ } ClientCommunicator::~ClientCommunicator() { // TODO: should the following be deleted here? // - world // - players if ( m_frameTimer ) SDL_RemoveTimer( m_frameTimer ); if ( m_serverConnection ) delete m_serverConnection; if ( m_lastMessage ) delete m_lastMessage; SDL_DestroyMutex( m_mutex ); SDL_DestroyCond( m_cond ); SDLNet_UDP_Close( m_baitSocket ); SDLNet_FreePacket( m_baitPacket ); } bool ClientCommunicator::openConnection( ServerEntry& server ) { switch ( server.type ) { case ServerEntry::DIRECT: { LOG( 2 ) INFO( "ClientCommunicator::openConnection: opening connection to local server\n" ); DirectServerEntry& directEntry = static_cast( server ); ServerConnectionDirect* directConnection = new ServerConnectionDirect( directEntry.server ); m_serverConnection = directConnection; m_clientID = directConnection->openConnection(); m_serverConnection->setClient( m_client ); return true; break; } case ServerEntry::REMOTE: /* LOG( 2 ) INFO( "ClientCommunicator::openConnection: opening connection to remote server\n" ); RemoteServerEntry& remoteEntry = static_cast( server ); ServerConnectionTCPUDP* remoteConnection = new ServerConnectionTCPUDP( remoteEntry ); m_serverConnection = remoteConnection; m_clientID = remoteConnection->openConnection();*/ m_serverConnection = m_serverConnectionFactory.openConnection( server ); if ( m_serverConnection ) { m_clientID = m_serverConnection->getClientID(); m_serverConnection->setClient( m_client ); return true; } break; } return false; } void ClientCommunicator::setActive() { ASSERT( m_serverConnection, "ClientCommunicator::setActive: no connection\n" ); // request the world from the server RequestMessage requestMessage; requestMessage.request = Message::WORLD; m_client->initializeConnectProgress(); m_serverConnection->setProgressListener( this ); m_serverConnection->sendMessage( &requestMessage ); // get the answer Message* message = 0; // TODO: timeout? while ( !( message = m_serverConnection->popMessage() ) ) ; m_serverConnection->setProgressListener( 0 ); m_client->hideConnectProgress(); ASSERT( message->type == Message::WORLD, "ClientCommunicator::setActive: expected WORLD message\n" ); WorldMessage* worldMessage = static_cast( message ); m_world = worldMessage->world; m_world->setMessageSink( m_client ); m_world->incrementFrame(); if ( m_serverConnection->getType() == ServerEntry::REMOTE ) { // create a surface from the pixel values in the map m_world->optimizeForClient( m_client->getVideo().getScreen()->format, SDL_SWSURFACE ); } m_frameTimer = SDL_AddTimer( 40, ClientCommunicator::frameHandler, this ); } void ClientCommunicator::closeConnection() { DBG( 2 ) INFO( "ClientCommunicator::closeConnection: start\n" ); SDL_mutexP( m_mutex ); SDL_RemoveTimer( m_frameTimer ); m_serverConnection->closeConnection(); delete m_serverConnection; m_serverConnection = 0; delete m_lastMessage; m_lastMessage = 0; m_player.clear(); m_playerInput.clear(); delete m_world; m_world = 0; SDL_mutexV( m_mutex ); DBG( 2 ) INFO( "ClientCommunicator::closeConnection: finish\n" ); } Uint8 ClientCommunicator::getPlayerNumber( Player* player ) const { for ( Uint8 i = 0; i < m_player.size(); i++ ) { if ( m_player[i] == player ) return i; } return 255; } void ClientCommunicator::addPlayer( Player* player, PlayerInput* input ) { SDL_mutexP( m_mutex ); // determine local player number Uint8 localPlayerNumber = m_player.size(); // add input object m_playerInput.push_back( input ); // create message for the server AddPlayerMessage* addPlayerMessage = new AddPlayerMessage(); addPlayerMessage->clientID = m_clientID; addPlayerMessage->localPlayerNumber = localPlayerNumber; addPlayerMessage->player = player; // send the message to the server m_serverConnection->sendMessage( addPlayerMessage ); // wait for the server answer to arrive m_waitingPlayer = player; while ( true ) { Message* message = m_lastMessage; m_lastMessage = 0; if ( !message || message->type != Message::ADD_PLAYER ) { SDL_CondWait( m_cond, m_mutex ); } else { ASSERT( message && message->type == Message::ADD_PLAYER, "ClientCommunicator::addPlayer: oops, wrong answer\n" ); AddPlayerMessage* addPlayerMessage2 = static_cast( message ); ASSERT( addPlayerMessage2->clientID == m_clientID, "ClientCommunicator::addPlayer: received answer refers not to local player\n" ); DBG( 2 ) INFO( "ClientCommunicator::addPlayer: adding player to list of local players (%5.2f)\n", SDL_GetTicks() / 1000.0 ); // add player to local list m_player.push_back( player ); delete message; break; } } SDL_mutexV( m_mutex ); // here or elsewhere? delete addPlayerMessage; } void ClientCommunicator::removePlayer( Player* player ) { SDL_mutexP( m_mutex ); // determine local player number Uint8 playerNumber = getPlayerNumber( player ); // remove player from local list m_player.erase( m_player.begin() + playerNumber ); SDL_mutexV( m_mutex ); // create message for the server Message* message = new RemovePlayerMessage( playerNumber ); // send the message to the server m_serverConnection->sendMessage( message ); // here or elsewhere? delete message; } void ClientCommunicator::processEvents() { SDL_mutexP( m_mutex ); if ( m_serverConnection ) { // create a new message containing the events for all local players EventMessage* message = new EventMessage( m_player.size() ); // Collect events from all players for ( unsigned int playerNumber = 0; playerNumber < m_player.size(); playerNumber++ ) { message->event[playerNumber] = m_playerInput[playerNumber]->getEvent(); } // send the message to the server m_serverConnection->sendMessage( message ); // here or elsewhere? delete message; } else { DBG( 4 ) INFO( "ClientCommunicator::processEvents: called although " "no connection is present\n" ); } SDL_mutexV( m_mutex ); } void ClientCommunicator::processEchos() { SDL_mutexP( m_mutex ); if ( m_serverConnection ) { // TODO: walk through message queue and look for echo messages // if we do not use a direct connection we have to update the // world by ourselves if ( m_serverConnection->getType() != ServerEntry::DIRECT ) { EchoMessage* echoMessage = 0; do { echoMessage = m_serverConnection->getEchoMessage( m_world->getFrame() ); if ( !echoMessage ) break; // check if we have to process waiting messages before the next frame if ( echoMessage->numberMessages > 0 ) { DBG( 4 ) INFO( "ClientCommunicator::processEchos: before processing echo for frame %d we have to wait for %d messages (%5.2f)\n", echoMessage->frame, echoMessage->numberMessages, SDL_GetTicks() / 1000.0 ); Uint32 index = 0; // SDL_mutexP( m_mutex ); while ( index < echoMessage->numberMessages ) { Message* message = m_serverConnection->peekMessage(); if ( !message ) { continue; } switch ( message->type ) { case Message::ADD_PLAYER: { DBG( 4 ) INFO( "ClientCommunicator::processEchos: handling ADD_PLAYER message (%5.2f)\n", SDL_GetTicks() / 1000.0 ); AddPlayerMessage* addPlayerMessage = static_cast( message ); if ( addPlayerMessage->frame >= echoMessage->frame ) continue; // add the player to the world m_serverConnection->popMessage(); if ( addPlayerMessage->clientID != m_clientID ) { Player* player = addPlayerMessage->player; if ( m_serverConnection->getType() != ServerEntry::DIRECT ) m_world->addPlayer( addPlayerMessage->player ); // inform client about joining player m_client->joinPlayer( player ); delete message; } else { ASSERT( m_waitingPlayer, "ClientCommunicator::processEchos: a player for this client arrived but none is waiting to be added\n" ); Player* player = addPlayerMessage->player; *m_waitingPlayer = *player; if ( m_serverConnection->getType() != ServerEntry::DIRECT ) m_world->addPlayer( m_waitingPlayer ); // inform client about joining player m_client->joinPlayer( m_waitingPlayer ); // if the added player is from this client, wake up the function // that waits for this answer m_lastMessage = message; // SDL_mutexV( m_mutex ); SDL_CondSignal( m_cond ); // SDL_mutexP( m_mutex ); } // increase message counter for this frame index++; break; } case Message::REMOVE_PLAYER: { DBG( 4 ) INFO( "ClientCommunicator::processEchos: handling REMOVE_PLAYER message\n" ); RemovePlayerMessage* removePlayerMessage = static_cast( message ); m_serverConnection->popMessage(); // get player from world Player* player = m_world->getPlayerByID( removePlayerMessage->playerNumber ); m_world->removePlayer( removePlayerMessage->playerNumber ); // inform client m_client->leavePlayer( player ); // cleanup delete player; delete removePlayerMessage; // increase message counter for this frame index++; break; } case Message::CHAT: { ChatMessage* chatMessage = static_cast( message ); m_serverConnection->popMessage(); DBG( 2 ) INFO( "ClientCommunicator::processEchos: handling CHAT message\n" ); m_client->showMessage( chatMessage->message, chatMessage->color ); delete chatMessage; index++; break; } default: // TODO: ignore all other packets? break; } } // SDL_mutexV( m_mutex ); } DBG( 5 ) INFO( "ClientCommunicator::processEchos: processing ECHO message for frame %d (random = %d, %x)\n", echoMessage->frame, echoMessage->randomCounter, echoMessage->event[0].get() ); ASSERT( echoMessage->randomCounter == m_world->getRandom().getCounter(), "ClientCommunicator::processEchos: lost synchronicity before frame %d (client: %d, server: %d, messages: %d)\n", echoMessage->frame, m_world->getRandom().getCounter(), echoMessage->randomCounter, echoMessage->numberMessages ); m_world->applyEvents( echoMessage ); m_world->doTimestep(); m_world->incrementFrame(); m_client->signalDisplayCond(); delete echoMessage; // update all players (better outside while loop?) // especially bots for ( unsigned int i = 0; i < m_player.size(); i++ ) m_player[i]->update(); } while ( echoMessage ); } // Check if connection is still valid if ( !m_serverConnection->isValid() ) { SDL_mutexV( m_mutex ); m_client->lostConnection(); return; } } else { DBG( 4 ) INFO( "ClientCommunicator::processEchos: called although " "no connection is present\n" ); } SDL_mutexV( m_mutex ); } void ClientCommunicator::sendChatMessage( String& message, const SDL_Color& color ) { ChatMessage chatMessage; chatMessage.message = message; chatMessage.color = color; LOG( 2 ) INFO( "ClientCommunicator::sendChatMessage: send chat message to server\n" ); m_serverConnection->sendMessage( &chatMessage ); } std::vector ClientCommunicator::lookForServers() { std::vector servers; BaitMessage baitMessage; m_baitPacket->len = baitMessage.getSerializeBufferSize(); Uint8* bufferPointer = m_baitPacket->data; baitMessage.serialize( bufferPointer ); LOG( 2 ) INFO( "ClientCommunicator::lookForServers: sending bait packets to local subnet\n" ); // one broadcast to all reachable subnets SDLNet_ResolveHost( &m_baitPacket->address, "255.255.255.255", 0xb8b8 ); CHECK( SDLNet_UDP_Send( m_baitSocket, -1, m_baitPacket ) == 1, "ClientCommunicator::lookForServers: could not send bait packet to local subnet (%s)\n", SDLNet_GetError() ); // check localhost SDLNet_ResolveHost( &m_baitPacket->address, "127.0.0.1", 0xb8b8 ); CHECK( SDLNet_UDP_Send( m_baitSocket, -1, m_baitPacket ) == 1, "ClientCommunicator::lookForServers: could not send bait packet to localhost (%s)\n", SDLNet_GetError() ); // TODO: check direct connection SDL_Delay( 500 ); while ( SDLNet_UDP_Recv( m_baitSocket, m_baitPacket ) == 1 ) { LOG( 2 ) INFO( "ClientCommunicator::lookForServers: received bait answer (len: %i) from %i.%i.%i.%i:%i\n", m_baitPacket->len, DECODE_IP( m_baitPacket->address.host ), m_baitPacket->address.port ); Message headerMessage; bufferPointer = m_baitPacket->data; headerMessage.deserialize( bufferPointer ); if ( headerMessage.type == Message::BAIT_ANSWER ) { BaitAnswerMessage answerMessage( headerMessage ); answerMessage.deserializeData( bufferPointer ); if ( answerMessage.magicData == 0xb8b8 ) { LOG( 2 ) INFO( "ClientCommunicator::lookForServers: valid server response\n" ); /* Uint16 serverPort; Uint8* dataPointer = reinterpret_cast( m_baitPacket->data + 2 ); Serialize::deserialize( dataPointer, serverPort );*/ // create server entry IPaddress serverAddress; serverAddress.host = m_baitPacket->address.host; serverAddress.port = answerMessage.serverPort; RemoteServerEntry* serverEntry = new RemoteServerEntry( serverAddress ); serverEntry->theme = answerMessage.theme; serverEntry->numberPlayers = answerMessage.numberPlayers; servers.push_back( serverEntry ); } } } return servers; } InfoMessage* ClientCommunicator::getServerInfo() { if ( !m_serverConnection ) return 0; RequestMessage requestMessage; requestMessage.request = Message::INFO; m_serverConnection->sendMessage( &requestMessage ); Message* message = 0; // TODO: timeout while ( !( message = m_serverConnection->popMessage() ) ) ; ASSERT( message->type == Message::INFO, "ClientCommunicator::getServerInfo: expected INFO message, got type %d\n", message->type ); return static_cast( message ); } void ClientCommunicator::recvProgress( Sint32 size, Sint32 received ) { m_client->updateConnectProgress( size, received ); } Uint32 ClientCommunicator::frameHandler( Uint32 interval, void* data ) { static Uint32 counter = 0; static int tick_reference = SDL_GetTicks(); Uint32 ticksBefore = SDL_GetTicks(); ClientCommunicator* communicator = static_cast( data ); communicator->processEvents(); communicator->processEchos(); Uint32 ticksAfter = SDL_GetTicks(); Uint32 duration = ticksAfter - ticksBefore; Uint32 newInterval = ( duration >= 40 ) ? 1 : 40 - duration; DBG( 5 ) { int ticks = SDL_GetTicks(); counter++; if ( ticks - tick_reference >= 1000 ) { INFO( "ClientCommunicator::frameHandler: called with %4.1f fps\n", counter * 1.0 ); counter = 0; tick_reference = ticks; } } return newInterval; }