Ejemplo n.º 1
0
	virtual void OnComplete()
	{
		// We've received the game state from an existing player - now
		// we need to send it onwards to the newly rejoining player

		// Find the session corresponding to the rejoining host (if any)
		CNetServerSession* session = NULL;
		for (size_t i = 0; i < m_Server.m_Sessions.size(); ++i)
		{
			if (m_Server.m_Sessions[i]->GetHostID() == m_RejoinerHostID)
			{
				session = m_Server.m_Sessions[i];
				break;
			}
		}

		if (!session)
		{
			LOGMESSAGE("Net server: rejoining client disconnected before we sent to it");
			return;
		}

		// Store the received state file, and tell the client to start downloading it from us
		// TODO: this will get kind of confused if there's multiple clients downloading in parallel;
		// they'll race and get whichever happens to be the latest received by the server,
		// which should still work but isn't great
		m_Server.m_JoinSyncFile = m_Buffer;
		CJoinSyncStartMessage message;
		session->SendMessage(&message);
	}
Ejemplo n.º 2
0
bool CNetServerWorker::OnDisconnect(void* context, CFsmEvent* event)
{
	ENSURE(event->GetType() == (uint)NMT_CONNECTION_LOST);

	CNetServerSession* session = (CNetServerSession*)context;
	CNetServerWorker& server = session->GetServer();

	server.OnUserLeave(session);

	return true;
}
Ejemplo n.º 3
0
bool CNetServerWorker::OnLoadedGame(void* context, CFsmEvent* event)
{
	ENSURE(event->GetType() == (uint)NMT_LOADED_GAME);

	CNetServerSession* session = (CNetServerSession*)context;
	CNetServerWorker& server = session->GetServer();

	// We're in the loading state, so wait until every player has loaded before
	// starting the game
	ENSURE(server.m_State == SERVER_STATE_LOADING);
	server.CheckGameLoadStatus(session);

	return true;
}
Ejemplo n.º 4
0
bool CNetServerWorker::OnReady(void* context, CFsmEvent* event)
{
	ENSURE(event->GetType() == (uint)NMT_READY);

	CNetServerSession* session = (CNetServerSession*)context;
	CNetServerWorker& server = session->GetServer();

	CReadyMessage* message = (CReadyMessage*)event->GetParamRef();

	message->m_GUID = session->GetGUID();

	server.Broadcast(message);

	return true;
}
Ejemplo n.º 5
0
bool CNetServerWorker::OnRejoined(void* context, CFsmEvent* event)
{
	// A client has finished rejoining and the loading screen disappeared.
	ENSURE(event->GetType() == (uint)NMT_REJOINED);

	CNetServerSession* session = (CNetServerSession*)context;
	CNetServerWorker& server = session->GetServer();

	CRejoinedMessage* message = (CRejoinedMessage*)event->GetParamRef();

	message->m_GUID = session->GetGUID();

	server.Broadcast(message);

	return true;
}
Ejemplo n.º 6
0
bool CNetServerWorker::OnClientHandshake(void* context, CFsmEvent* event)
{
	ENSURE(event->GetType() == (uint)NMT_CLIENT_HANDSHAKE);

	CNetServerSession* session = (CNetServerSession*)context;
	CNetServerWorker& server = session->GetServer();

	CCliHandshakeMessage* message = (CCliHandshakeMessage*)event->GetParamRef();
	if (message->m_ProtocolVersion != PS_PROTOCOL_VERSION)
	{
		session->Disconnect(NDR_INCORRECT_PROTOCOL_VERSION);
		return false;
	}

	CSrvHandshakeResponseMessage handshakeResponse;
	handshakeResponse.m_UseProtocolVersion = PS_PROTOCOL_VERSION;
	handshakeResponse.m_Message = server.m_WelcomeMessage;
	handshakeResponse.m_Flags = 0;
	session->SendMessage(&handshakeResponse);

	return true;
}
Ejemplo n.º 7
0
bool CNetServerWorker::OnInGame(void* context, CFsmEvent* event)
{
	// TODO: should split each of these cases into a separate method

	CNetServerSession* session = (CNetServerSession*)context;
	CNetServerWorker& server = session->GetServer();

	CNetMessage* message = (CNetMessage*)event->GetParamRef();
	if (message->GetType() == (uint)NMT_SIMULATION_COMMAND)
	{
		CSimulationMessage* simMessage = static_cast<CSimulationMessage*> (message);

		// Send it back to all clients immediately
		server.Broadcast(simMessage);

		// Save all the received commands
		if (server.m_SavedCommands.size() < simMessage->m_Turn + 1)
			server.m_SavedCommands.resize(simMessage->m_Turn + 1);
		server.m_SavedCommands[simMessage->m_Turn].push_back(*simMessage);

		// TODO: we should do some validation of ownership (clients can't send commands on behalf of opposing players)

		// TODO: we shouldn't send the message back to the client that first sent it
	}
	else if (message->GetType() == (uint)NMT_SYNC_CHECK)
	{
		CSyncCheckMessage* syncMessage = static_cast<CSyncCheckMessage*> (message);
		server.m_ServerTurnManager->NotifyFinishedClientUpdate(session->GetHostID(), syncMessage->m_Turn, syncMessage->m_Hash);
	}
	else if (message->GetType() == (uint)NMT_END_COMMAND_BATCH)
	{
		CEndCommandBatchMessage* endMessage = static_cast<CEndCommandBatchMessage*> (message);
		server.m_ServerTurnManager->NotifyFinishedClientCommands(session->GetHostID(), endMessage->m_Turn);
	}

	return true;
}
Ejemplo n.º 8
0
bool CNetServerWorker::OnInGame(void* context, CFsmEvent* event)
{
	// TODO: should split each of these cases into a separate method

	CNetServerSession* session = (CNetServerSession*)context;
	CNetServerWorker& server = session->GetServer();

	CNetMessage* message = (CNetMessage*)event->GetParamRef();
	if (message->GetType() == (uint)NMT_SIMULATION_COMMAND)
	{
		CSimulationMessage* simMessage = static_cast<CSimulationMessage*> (message);

		// Ignore messages sent by one player on behalf of another player
		// unless cheating is enabled
		bool cheatsEnabled = false;
		ScriptInterface& scriptInterface = server.GetScriptInterface();
		JSContext* cx = scriptInterface.GetContext();
		JSAutoRequest rq(cx);
		JS::RootedValue settings(cx);
		scriptInterface.GetProperty(server.m_GameAttributes.get(), "settings", &settings);
		if (scriptInterface.HasProperty(settings, "CheatsEnabled"))
			scriptInterface.GetProperty(settings, "CheatsEnabled", cheatsEnabled);

		PlayerAssignmentMap::iterator it = server.m_PlayerAssignments.find(session->GetGUID());
		// When cheating is disabled, fail if the player the message claims to
		// represent does not exist or does not match the sender's player name
		if (!cheatsEnabled && (it == server.m_PlayerAssignments.end() || it->second.m_PlayerID != simMessage->m_Player))
			return true;

		// Send it back to all clients immediately
		server.Broadcast(simMessage);

		// Save all the received commands
		if (server.m_SavedCommands.size() < simMessage->m_Turn + 1)
			server.m_SavedCommands.resize(simMessage->m_Turn + 1);
		server.m_SavedCommands[simMessage->m_Turn].push_back(*simMessage);

		// TODO: we shouldn't send the message back to the client that first sent it
	}
	else if (message->GetType() == (uint)NMT_SYNC_CHECK)
	{
		CSyncCheckMessage* syncMessage = static_cast<CSyncCheckMessage*> (message);
		server.m_ServerTurnManager->NotifyFinishedClientUpdate(session->GetHostID(), session->GetUserName(), syncMessage->m_Turn, syncMessage->m_Hash);
	}
	else if (message->GetType() == (uint)NMT_END_COMMAND_BATCH)
	{
		CEndCommandBatchMessage* endMessage = static_cast<CEndCommandBatchMessage*> (message);
		server.m_ServerTurnManager->NotifyFinishedClientCommands(session->GetHostID(), endMessage->m_Turn);
	}

	return true;
}
Ejemplo n.º 9
0
bool CNetServerWorker::OnJoinSyncingLoadedGame(void* context, CFsmEvent* event)
{
	// A client rejoining an in-progress game has now finished loading the
	// map and deserialized the initial state.
	// The simulation may have progressed since then, so send any subsequent
	// commands to them and set them as an active player so they can participate
	// in all future turns.
	// 
	// (TODO: if it takes a long time for them to receive and execute all these
	// commands, the other players will get frozen for that time and may be unhappy;
	// we could try repeating this process a few times until the client converges
	// on the up-to-date state, before setting them as active.)

	ENSURE(event->GetType() == (uint)NMT_LOADED_GAME);

	CNetServerSession* session = (CNetServerSession*)context;
	CNetServerWorker& server = session->GetServer();

	CLoadedGameMessage* message = (CLoadedGameMessage*)event->GetParamRef();

	u32 turn = message->m_CurrentTurn;
	u32 readyTurn = server.m_ServerTurnManager->GetReadyTurn();

	// Send them all commands received since their saved state,
	// and turn-ended messages for any turns that have already been processed
	for (size_t i = turn + 1; i < std::max(readyTurn+1, (u32)server.m_SavedCommands.size()); ++i)
	{
		if (i < server.m_SavedCommands.size())
			for (size_t j = 0; j < server.m_SavedCommands[i].size(); ++j)
				session->SendMessage(&server.m_SavedCommands[i][j]);

		if (i <= readyTurn)
		{
			CEndCommandBatchMessage endMessage;
			endMessage.m_Turn = i;
			endMessage.m_TurnLength = server.m_ServerTurnManager->GetSavedTurnLength(i);
			session->SendMessage(&endMessage);
		}
	}

	// Tell the turn manager to expect commands from this new client
	server.m_ServerTurnManager->InitialiseClient(session->GetHostID(), readyTurn);

	// Tell the client that everything has finished loading and it should start now
	CLoadedGameMessage loaded;
	loaded.m_CurrentTurn = readyTurn;
	session->SendMessage(&loaded);

	return true;
}
Ejemplo n.º 10
0
bool CNetServerWorker::OnAuthenticate(void* context, CFsmEvent* event)
{
	ENSURE(event->GetType() == (uint)NMT_AUTHENTICATE);

	CNetServerSession* session = (CNetServerSession*)context;
	CNetServerWorker& server = session->GetServer();

	// Prohibit joins while the game is loading
	if (server.m_State == SERVER_STATE_LOADING)
	{
		LOGMESSAGE("Refused connection while the game is loading");
		session->Disconnect(NDR_SERVER_LOADING);
		return true;
	}

	CAuthenticateMessage* message = (CAuthenticateMessage*)event->GetParamRef();
	CStrW username = server.DeduplicatePlayerName(SanitisePlayerName(message->m_Name));

	// Optionally allow observers to join after the game has started
	bool observerLateJoin = false;
	ScriptInterface& scriptInterface = server.GetScriptInterface();
	JSContext* cx = scriptInterface.GetContext();
	JSAutoRequest rq(cx);
	JS::RootedValue settings(cx);
	scriptInterface.GetProperty(server.m_GameAttributes.get(), "settings", &settings);
	if (scriptInterface.HasProperty(settings, "ObserverLateJoin"))
		scriptInterface.GetProperty(settings, "ObserverLateJoin", observerLateJoin);

	// If the game has already started, only allow rejoins
	bool isRejoining = false;
	if (server.m_State != SERVER_STATE_PREGAME)
	{
		// Search for an old disconnected player of the same name
		// (TODO: if GUIDs were stable, we should use them instead)
		isRejoining =
			observerLateJoin ||
			std::find_if(
				server.m_PlayerAssignments.begin(), server.m_PlayerAssignments.end(),
				[&username] (const std::pair<CStr, PlayerAssignment>& pair)
				{ return !pair.second.m_Enabled && pair.second.m_Name == username; })
			!= server.m_PlayerAssignments.end();

		// Players who weren't already in the game are not allowed to join now that it's started
		if (!isRejoining)
		{
			LOGMESSAGE("Refused connection after game start from not-previously-known user \"%s\"", utf8_from_wstring(username));
			session->Disconnect(NDR_SERVER_ALREADY_IN_GAME);
			return true;
		}
	}

	// TODO: check server password etc?

	u32 newHostID = server.m_NextHostID++;

	session->SetUserName(username);
	session->SetGUID(message->m_GUID);
	session->SetHostID(newHostID);

	CAuthenticateResultMessage authenticateResult;
	authenticateResult.m_Code = isRejoining ? ARC_OK_REJOINING : ARC_OK;
	authenticateResult.m_HostID = newHostID;
	authenticateResult.m_Message = L"Logged in";
	session->SendMessage(&authenticateResult);

	server.OnUserJoin(session);

	if (isRejoining)
	{
		// Request a copy of the current game state from an existing player,
		// so we can send it on to the new player

		// Assume session 0 is most likely the local player, so they're
		// the most efficient client to request a copy from
		CNetServerSession* sourceSession = server.m_Sessions.at(0);
		sourceSession->GetFileTransferer().StartTask(
			shared_ptr<CNetFileReceiveTask>(new CNetFileReceiveTask_ServerRejoin(server, newHostID))
		);

		session->SetNextState(NSS_JOIN_SYNCING);
	}

	return true;
}
Ejemplo n.º 11
0
bool CNetServerWorker::RunStep()
{
	// Check for messages from the game thread.
	// (Do as little work as possible while the mutex is held open,
	// to avoid performance problems and deadlocks.)
	
	m_ScriptInterface->GetRuntime()->MaybeIncrementalGC(0.5f);
	
	JSContext* cx = m_ScriptInterface->GetContext();
	JSAutoRequest rq(cx);

	std::vector<std::pair<int, CStr> > newAssignPlayer;
	std::vector<bool> newStartGame;
	std::vector<std::pair<CStr, int> > newPlayerReady;
	std::vector<bool> newPlayerResetReady;
	std::vector<std::string> newGameAttributes;
	std::vector<u32> newTurnLength;

	{
		CScopeLock lock(m_WorkerMutex);

		if (m_Shutdown)
			return false;

		newStartGame.swap(m_StartGameQueue);
		newPlayerReady.swap(m_PlayerReadyQueue);
		newPlayerResetReady.swap(m_PlayerResetReadyQueue);
		newAssignPlayer.swap(m_AssignPlayerQueue);
		newGameAttributes.swap(m_GameAttributesQueue);
		newTurnLength.swap(m_TurnLengthQueue);
	}

	for (size_t i = 0; i < newAssignPlayer.size(); ++i)
		AssignPlayer(newAssignPlayer[i].first, newAssignPlayer[i].second);

	for (size_t i = 0; i < newPlayerReady.size(); ++i)
		SetPlayerReady(newPlayerReady[i].first, newPlayerReady[i].second);

	if (!newPlayerResetReady.empty())
		ClearAllPlayerReady();

	if (!newGameAttributes.empty())
	{
		JS::RootedValue gameAttributesVal(cx);
		GetScriptInterface().ParseJSON(newGameAttributes.back(), &gameAttributesVal);
		UpdateGameAttributes(&gameAttributesVal);
	}

	if (!newTurnLength.empty())
		SetTurnLength(newTurnLength.back());

	// Do StartGame last, so we have the most up-to-date game attributes when we start
	if (!newStartGame.empty())
		StartGame();

	// Perform file transfers
	for (size_t i = 0; i < m_Sessions.size(); ++i)
		m_Sessions[i]->GetFileTransferer().Poll();

	// Process network events:

	ENetEvent event;
	int status = enet_host_service(m_Host, &event, HOST_SERVICE_TIMEOUT);
	if (status < 0)
	{
		LOGERROR("CNetServerWorker: enet_host_service failed (%d)", status);
		// TODO: notify game that the server has shut down
		return false;
	}

	if (status == 0)
	{
		// Reached timeout with no events - try again
		return true;
	}

	// Process the event:

	switch (event.type)
	{
	case ENET_EVENT_TYPE_CONNECT:
	{
		// Report the client address
		char hostname[256] = "(error)";
		enet_address_get_host_ip(&event.peer->address, hostname, ARRAY_SIZE(hostname));
		LOGMESSAGE("Net server: Received connection from %s:%u", hostname, (unsigned int)event.peer->address.port);

		// Set up a session object for this peer

		CNetServerSession* session = new CNetServerSession(*this, event.peer);

		m_Sessions.push_back(session);

		SetupSession(session);

		ENSURE(event.peer->data == NULL);
		event.peer->data = session;

		HandleConnect(session);

		break;
	}

	case ENET_EVENT_TYPE_DISCONNECT:
	{
		// If there is an active session with this peer, then reset and delete it

		CNetServerSession* session = static_cast<CNetServerSession*>(event.peer->data);
		if (session)
		{
			LOGMESSAGE("Net server: Disconnected %s", DebugName(session).c_str());

			// Remove the session first, so we won't send player-update messages to it
			// when updating the FSM
			m_Sessions.erase(remove(m_Sessions.begin(), m_Sessions.end(), session), m_Sessions.end());

			session->Update((uint)NMT_CONNECTION_LOST, NULL);

			delete session;
			event.peer->data = NULL;
		}

		break;
	}

	case ENET_EVENT_TYPE_RECEIVE:
	{
		// If there is an active session with this peer, then process the message

		CNetServerSession* session = static_cast<CNetServerSession*>(event.peer->data);
		if (session)
		{
			// Create message from raw data
			CNetMessage* msg = CNetMessageFactory::CreateMessage(event.packet->data, event.packet->dataLength, GetScriptInterface());
			if (msg)
			{
				LOGMESSAGE("Net server: Received message %s of size %lu from %s", msg->ToString().c_str(), (unsigned long)msg->GetSerializedLength(), DebugName(session).c_str());

				HandleMessageReceive(msg, session);

				delete msg;
			}
		}

		// Done using the packet
		enet_packet_destroy(event.packet);

		break;
	}

	case ENET_EVENT_TYPE_NONE:
		break;
	}

	return true;
}