// CentralServer.cpp
// copyright 2000 Verant Interactive
// Author: Justin Randall

//-----------------------------------------------------------------------

#include "FirstCentralServer.h"
#include "CentralServer.h"

#include "CentralCSHandler.h"
#include "CentralServerMetricsData.h"
#include "CharacterCreationTracker.h"
#include "ChatServerConnection.h"
#include "ConfigCentralServer.h"
#include "ConnectionServerConnection.h"
#include "ConsoleConnection.h"
#include "ConsoleManager.h"
#include "CustomerServiceConnection.h"
#include "fileInterface/StdioFile.h"
#include "GameServerConnection.h"
#include "LoginServerConnection.h"
#include "PlanetManager.h"
#include "PlanetServerConnection.h"
#include "TaskConnection.h"
#include "UnicodeUtils.h"
#include "UniverseManager.h"
#include "serverKeyShare/KeyShare.h"
#include "serverMetrics/MetricsManager.h"
#include "serverNetworkMessages/AccountFeatureIdResponse.h"
#include "serverNetworkMessages/AddCharacterMessage.h"
#include "serverNetworkMessages/AddObjectIdBlockMessage.h"
#include "serverNetworkMessages/AdjustAccountFeatureIdResponse.h"
#include "serverNetworkMessages/CentralConnectionServerMessages.h"
#include "serverNetworkMessages/CentralGameServerMessages.h"
#include "serverNetworkMessages/CentralPingMessage.h"
#include "serverNetworkMessages/CentralPlanetServerConnect.h"
#include "serverNetworkMessages/CentralTaskMessages.h"
#include "serverNetworkMessages/CharacterListMessage.h"
#include "serverNetworkMessages/ChatServerOnline.h"
#include "serverNetworkMessages/ChunkObjectListMessage.h"
#include "serverNetworkMessages/ClaimRewardsMessage.h"
#include "serverNetworkMessages/ClaimRewardsReplyMessage.h"
#include "serverNetworkMessages/ConnectionServerDown.h"
#include "serverNetworkMessages/CreateNewObjectMessage.h"
#include "serverNetworkMessages/EnumerateServers.h"
#include "serverNetworkMessages/ExchangeListCreditsMessage.h"
#include "serverNetworkMessages/ExcommunicateGameServerMessage.h"
#include "serverNetworkMessages/FailedToLoadObjectMessage.h"
#include "serverNetworkMessages/FeatureIdTransactionResponse.h"
#include "serverNetworkMessages/ForceUnloadObjectMessage.h"
#include "serverNetworkMessages/GameServerForLoginMessage.h"
#include "serverNetworkMessages/GameServerReadyMessage.h"
#include "serverNetworkMessages/GameServerUniverseLoadedMessage.h"
#include "serverNetworkMessages/GameTaskManagerMessages.h"
#include "serverNetworkMessages/LoadObjectMessage.h"
#include "serverNetworkMessages/LocateStructureMessage.h"
#include "serverNetworkMessages/LoginConnectionServerAddress.h"
#include "serverNetworkMessages/LoginCreateCharacterAckMessage.h"
#include "serverNetworkMessages/LoginCreateCharacterMessage.h"
#include "serverNetworkMessages/LoginKeyPush.h"
#include "serverNetworkMessages/LoginRestoreCharacterMessage.h"
#include "serverNetworkMessages/LoginUpgradeAccountMessage.h"
#include "serverNetworkMessages/MessageToAckMessage.h"
#include "serverNetworkMessages/MessageToMessage.h"
#include "serverNetworkMessages/PlanetLoadCharacterMessage.h"
#include "serverNetworkMessages/PopulationListMessage.h"
#include "serverNetworkMessages/PreloadFinishedMessage.h"
#include "serverNetworkMessages/PreloadRequestCompleteMessage.h"
#include "serverNetworkMessages/ProfilerOperationMessage.h"
#include "serverNetworkMessages/RandomName.h"
#include "serverNetworkMessages/ReleaseAuthoritativeMessage.h"
#include "serverNetworkMessages/RenameCharacterMessage.h"
#include "serverNetworkMessages/RequestChunkMessage.h"
#include "serverNetworkMessages/RequestGameServerForLoginMessage.h"
#include "serverNetworkMessages/RequestObjectIdsMessage.h"
#include "serverNetworkMessages/RequestObjectMessage.h"
#include "serverNetworkMessages/RestartServerMessage.h"
#include "serverNetworkMessages/SceneTransferMessages.h"
#include "serverNetworkMessages/ServerDeleteCharacterMessage.h"
#include "serverNetworkMessages/SetConnectionServerPublic.h"
#include "serverNetworkMessages/SetObjectPositionMessage.h"
#include "serverNetworkMessages/TaskProcessDiedMessage.h"
#include "serverNetworkMessages/TaskSpawnProcess.h"
#include "serverNetworkMessages/TransferCharacterData.h"
#include "serverNetworkMessages/TransferCharacterDataArchive.h"
#include "serverNetworkMessages/UnloadObjectMessage.h"
#include "serverNetworkMessages/UpdateLoginConnectionServerStatus.h"
#include "serverNetworkMessages/UpdatePlayerCountMessage.h"
#include "serverNetworkMessages/ValidateAccountMessage.h"
#include "serverNetworkMessages/ValidateAccountReplyMessage.h"
#include "serverNetworkMessages/ValidateCharacterForLoginMessage.h"
#include "serverNetworkMessages/ValidateCharacterForLoginReplyMessage.h"
#include "serverNetworkMessages/VerifyAndLockName.h"
#include "sharedNetworkMessages/ConsoleChannelMessages.h"
#include "serverUtility/ClusterWideDataManagerList.h"
#include "serverUtility/ServerClock.h"
#include "serverUtility/SetupServerUtility.h"
#include "sharedDebug/Profiler.h"
#include "sharedFile/Iff.h"
#include "sharedFoundation/ApplicationVersion.h"
#include "sharedFoundation/CalendarTime.h"
#include "sharedFoundation/Clock.h"
#include "sharedFoundation/ConfigFile.h"
#include "sharedFoundation/FormattedString.h"
#include "sharedFoundation/Os.h"
#include "sharedFoundation/Timer.h"
#include "sharedLog/Log.h"
#include "sharedLog/SetupSharedLog.h"
#include "sharedMathArchive/VectorArchive.h"
#include "sharedNetwork/Connection.h"
#include "sharedNetwork/NetworkSetupData.h"
#include "sharedNetwork/Service.h"
#include "sharedNetworkMessages/GenericValueTypeMessage.h"
#include "sharedNetworkMessages/NameErrors.h"
#include "sharedNetworkMessages/NetworkStringIds.h"
#include "sharedUtility/SetupSharedUtility.h"
#include "sharedUtility/StartingLocationData.h"
#include "sharedUtility/StartingLocationManager.h"
#include "TransferServerConnection.h"
#include "StationPlayersCollectorConnection.h"
#include "AuctionTransferClient.h"
#include "unicodeArchive/UnicodeArchive.h"
#include <stdio.h>

#include "sharedFoundation/CrcConstexpr.hpp"

#include "webAPI.h"

using namespace StellaBellum;

namespace CentralServerNamespace
{
	bool gs_connectionServersPublic=false;
	bool gs_clusterIsLocked=false;
	bool gs_clusterIsSecret=false;

	std::map<std::string, std::string> ms_sceneToHostMap;

	// every connection server started on the cluster will
	// have a entry in this vector which contains the node
	// on which to start that particular connection server;
	// every connection server that is started will be assigned
	// a 1-based number, which corresponds to its position
	// in this vector; that is, the connection server assigned
	// number 1 will be at position 0 in this vector
	std::vector<std::string> s_connectionServerHostList;

	std::pair<std::string, unsigned short> s_customerServiceServerChatServerServiceAddress;
	std::pair<std::string, unsigned short> s_customerServiceServerGameServerServiceAddress;
	Service * s_commodityServerService = 0;
	bool s_retryTransferServerConnection = false;
	bool s_retryStationPlayersCollectorConnection = false;

	std::map<StationId, uint32> m_purgeAccountToLoginServerMap;
	std::map<NetworkId, std::pair<time_t, int> > s_pendingRenameCharacter;
}

using namespace CentralServerNamespace;

// ======================================================================

CentralServer::CentralObject::CentralObject() :
m_sceneId(),
m_authoritativeProcess(0),
m_proxyProcessList()
{

}

//-----------------------------------------------------------------------

CentralServer::CentralObject::CentralObject(const CentralServer::SceneId & sceneId, uint32 authProcess) :
m_sceneId(sceneId),
m_authoritativeProcess(authProcess),
m_proxyProcessList()
{

}

//-----------------------------------------------------------------------

CentralServer::CentralServer() :
Singleton<CentralServer>(),
MessageDispatch::Receiver(),
m_connectionServerConnections(),
m_chatServerConnections(),
m_csServerConnections(),
m_gameServers(),
m_accountConnectionMap(),
m_dbProcessServerProcessId(0),
m_done(false),
m_gameService(0),
m_chatService(0),
m_connService(0),
m_csService(0),
m_planetService(0),
m_consoleService(0),
m_transferServerConnection(0),
m_stationPlayersCollectorConnection(0),
m_gameServerConnections(),
m_gameServerConnectionsList(),
m_playerSceneMap(),
m_loginServerConnectionMap(),
m_loginServerKeys(0),
m_loginService(0),
m_pAuctionTransferClient(0),
m_messagesWaitingForPlanetServer(),
m_pendingLoadingObjects(),
m_pendingNewObjects(),
m_nextFreeProcessId(0),
m_taskManager(0),
m_taskService(0),
m_commandLine(),
m_pendingTransfers(),
m_totalPlayerCount(0),
m_totalFreeTrialCount(0),
m_totalEmptySceneCount(0),
m_totalTutorialSceneCount(0),
m_totalFalconSceneCount(0),
m_metricsData(0),
m_pendingPlanetServers(),
m_planetsWaitingForPreload(),
m_planetServers(),
m_nextPlanetWatcherPort(ConfigCentralServer::getFirstPlanetWatcherPort()),
m_databaseBacklogged(false),
m_lastLoadingStateTime(0),
m_timeClusterStarted(time(0)),
m_clusterStartupTime(-1),
m_timeClusterWentIntoLoadingState(time(0)),
m_clusterId(0),
m_serverPings(),
m_shutdownTotalTime(0),
m_shutdownMaxTime(0),
m_shutdownSystemMessage(Unicode::narrowToWide("")),
m_shutdownPhase(0),
m_shutdownHaveDatabaseSaveStart(false),
m_shutdownHaveDatabaseComplete(false),
m_curTime(0),
m_lastTimeSystemTimeMismatchNotification(0),
m_lastTimeSystemTimeMismatchNotificationDescription(),
m_disconnectedTaskManagerList(),
m_populationStatistics(),
m_timePopulationStatisticsRefresh(0),
m_timePopulationStatisticsNextRefresh(0),
m_gcwScoreStatistics(),
m_timeGcwScoreStatisticsRefresh(0),
m_timeGcwScoreStatisticsNextRefresh(0),
m_lastLoginTimeStatistics(),
m_createTimeStatistics(),
m_timeLastLoginTimeStatisticsRefresh(0),
m_timeLastLoginTimeStatisticsNextRefresh(0),
m_numberOfCharacterMatchRequests(0),
m_numberOfCharacterMatchResults(0),
m_timeSpentOnCharacterMatchRequestsMs(0),
m_timeCharacterMatchStatisticsNextRefresh(0)
{
	m_curTime = static_cast<uint32>(time(0));
	m_loginServerKeys = new KeyServer(20);

	connectToMessage("LoginKeyPush");

	//Connection Messages
	connectToMessage("ClientConnectionClosed"); // from connection server
	connectToMessage("ConnectionOpened");
	connectToMessage("RequestClusterShutdown");
	connectToMessage("GameConnectionClosed");
	connectToMessage("CentralGameServerConnect");
	connectToMessage("CentralGameServerDbProcessServerProcessId");
	connectToMessage("LoginConnectionOpened");
	connectToMessage("LoginConnectionClosed");
	connectToMessage("ClusterId");
	connectToMessage("GameServerReadyMessage");
	connectToMessage("GameServerUniverseLoadedMessage");
	connectToMessage("ConnectionServerConnectionClosed");
	connectToMessage("ConnectionServerConnectionOpened");
	connectToMessage("TaskConnectionClosed");
	connectToMessage("TaskConnectionOpened");
	connectToMessage("RandomNameRequest"); // from connection server
	connectToMessage("RandomNameResponse"); // from game server
	connectToMessage("VerifyAndLockNameRequest"); // from connection server	
	connectToMessage("VerifyAndLockNameResponse"); // from game server	
	
	//Object Messages
	connectToMessage("RequestObjectMessage");
	connectToMessage("RequestChunkMessage");
	connectToMessage("ChunkObjectListMessage");
	connectToMessage("CreateNewObjectMessage");
	connectToMessage("SetObjectPositionMessage");
	connectToMessage("FailedToLoadObjectMessage");
	connectToMessage("ReleaseAuthoritativeMessage");
	connectToMessage("ForceUnloadObjectMessage");
	connectToMessage("RequestUnloadObjectMessage");
	connectToMessage("MessageToMessage");
	connectToMessage("MessageToAckMessage");
	connectToMessage("LocateStructureMessage");

	//Create character messages
	connectToMessage("ConnectionCreateCharacter");
	connectToMessage("DatabaseCreateCharacterSuccess");
	connectToMessage("GameCreateCharacterFailed");
	connectToMessage("RequestGameServerForLoginMessage");
	connectToMessage("GameServerForLoginMessage");
	connectToMessage("LoginCreateCharacterAckMessage");
	connectToMessage("LoginUpgradeAccountMessage");
	connectToMessage("LoginRestoreCharacterMessage");
	connectToMessage("NewCharacterCreated");

	connectToMessage("ServerDeleteCharacterMessage");
	connectToMessage("ValidateCharacterForLoginMessage");
	connectToMessage("ValidateCharacterForLoginReplyMessage");
	connectToMessage("EnableCharacterMessage");
	connectToMessage("EnableCharacterReplyMessage");
	connectToMessage("DisableCharacterMessage");
	connectToMessage("DisableCharacterReplyMessage");

	connectToMessage("RequestOIDsMessage");
	connectToMessage("AddOIDBlockMessage");
	connectToMessage("CharacterListMessage");
	connectToMessage("LoggedInMessage");
	connectToMessage("CentralPlanetServerConnect");
	connectToMessage("PlanetCreateProxy");
	connectToMessage("PlanetRemoveProxy");

	connectToMessage("RequestSceneTransfer");
	connectToMessage("SceneTransferMessage");
	connectToMessage("PlanetLoadCharacterMessage");

	connectToMessage("ConnSrvDropDupeConns");
	connectToMessage("CntrlSrvDropDupeConns");

	connectToMessage("PlayerDroppedFromGameServerCrash");

	// Station exchange messages
	connectToMessage("ExchangeListCreditsMessage");

	// Chat Server messages
	connectToMessage("ChatServerConnectionOpened");
	connectToMessage("ChatServerConnectionClosed");
	connectToMessage("ChatServerOnline");

	// Chat Server messages
	connectToMessage("CustomerServiceConnectionOpened");
	connectToMessage("CustomerServiceConnectionClosed");

	connectToMessage("ValidateAccountMessage");
	connectToMessage("ValidateAccountReplyMessage");

	connectToMessage("RequestPreloadListMessage");
	connectToMessage("PreloadListMessage");
	connectToMessage("PreloadRequestCompleteMessage");
	connectToMessage("PreloadFinishedMessage");
	connectToMessage("DatabaseBackloggedMessage");

	connectToMessage("UpdatePlayerCountMessage");
	connectToMessage("RenameCharacterMessageEx");
	connectToMessage("PlayerRenameRequestSubmitted");
	connectToMessage("PlayerRenameRequestCompleted");
	connectToMessage("PopulationListMessage");

	connectToMessage("SetConnectionServerPublic");

	connectToMessage("ProfilerOperationMessage");
	connectToMessage("CentralPingMessage");

	connectToMessage("DatabaseSaveStart");
	connectToMessage("DatabaseSaveComplete");
	connectToMessage("ShutdownCluster");
	connectToMessage("AbortShutdown");
	connectToMessage("SetSceneForPlayer");
	connectToMessage("DatabaseConsoleReplyMessage");
	connectToMessage("TaskProcessDiedMessage");
	connectToMessage("SystemTimeMismatchNotification");
	connectToMessage("DisconnectedTaskManagerMessage");
	connectToMessage("TransferServerConnectionClosed");
	connectToMessage("PlanetRequestSave");
	connectToMessage("TransferReplyLoginLocationData");
	connectToMessage("CustomerServiceServerChatServerServiceAddress");
	connectToMessage("CustomerServiceServerGameServerServiceAddress");
	connectToMessage("ChatClosedConnectionWithGameServer");

	connectToMessage("ReconnectToTransferServer");
	connectToMessage("ClaimRewardsMessage");
	connectToMessage("ClaimRewardsReplyMessage");
	connectToMessage("PurgeStructuresForAccountMessage");
	connectToMessage("WarnStructuresAboutPurgeMessage");
	connectToMessage("PurgeCompleteMessage");
	connectToMessage("RestartServerMessage");
	connectToMessage("RestartServerByRoleMessage");
	connectToMessage("ExcommunicateGameServerMessage");
	connectToMessage("RestartPlanetMessage");
	
	// Cluster state
	connectToMessage("UpdateClusterLockedAndSecretState");

	// Population statistics
	connectToMessage("PopStatRsp");
	connectToMessage("LLTStatRsp");

	// GCW score statistics
	connectToMessage("GcwScoreStatRsp");

	// Character Search statistics
	connectToMessage("LfgStatRsp");

	// Account feature Id management
	connectToMessage("AdjustAccountFeatureIdResponse");
	connectToMessage("AccountFeatureIdResponse");
	connectToMessage("FeatureIdTransactionResponse");

	// Unlocked Slot management
	connectToMessage("OccupyUnlockedSlotRsp");
	connectToMessage("VacateUnlockedSlotRsp");
	connectToMessage("SwapUnlockedSlotRsp");

	// Cluster wide data manager
	ClusterWideDataManagerList::install();
	ClusterWideDataManagerList::registerMessage(*this);
	ClusterWideDataManagerList::setLockTimeoutValue(ConfigCentralServer::getClusterWideDataLockTimeout());

	CentralCSHandler::install();

	gs_connectionServersPublic = ConfigCentralServer::getStartPublic();

	m_metricsData = new CentralServerMetricsData;
	MetricsManager::install(m_metricsData, false, "CentralServer", "", 0);
	ConsoleManager::install();
	CharacterCreationTracker::install();

	IGNORE_RETURN(UniverseManager::getInstance()); //make sure it's constructed
}

//-----------------------------------------------------------------------

CentralServer::~CentralServer()
{
	ConsoleManager::remove();
	
	CentralCSHandler::remove();

	ms_sceneToHostMap.clear();

	// released during CentralServer::remove()
	m_gameService = 0;
	m_chatService = 0;
	m_connService = 0;
	m_csService = 0;
	m_planetService = 0;
	m_consoleService = 0;
	m_transferServerConnection = 0;
	m_stationPlayersCollectorConnection = 0;
	m_loginServerKeys = 0; //lint !e423 : leak
	m_loginService = 0;
	m_taskManager = 0;
	m_taskService = 0;
	m_metricsData = 0; //lint !e423 : leak
}

//-----------------------------------------------------------------------
void CentralServer::addGameServer(GameServerConnection * newGameServer)
{
	uint32	pid = newGameServer->getProcessId();

	LOG("ServerStartup", ("Adding server %lu for scene %s", pid, newGameServer->getSceneId().c_str()));

	IGNORE_RETURN(m_gameServers.insert(SceneGameMap::value_type(
		newGameServer->getSceneId(), newGameServer)));
	m_gameServerConnections[pid] = newGameServer;

	if (newGameServer->getSceneId() != "database")
	{
		m_gameServerConnectionsList.push_back(newGameServer);

		if (ConfigCentralServer::getGameServerConnectionPendingAllocatedSizeLimit() != 0)
			newGameServer->setTcpClientPendingSendAllocatedSizeLimit(ConfigCentralServer::getGameServerConnectionPendingAllocatedSizeLimit());

		PlanetManager::addGameServerForScene(newGameServer->getSceneId(), newGameServer);

		// Have the new game server connect to the db and notify it of what game servers exist
		std::vector<uint32> serverList;

		GameServerConnection const * const databaseProcessConnection = CentralServer::getInstance().getGameServer(CentralServer::getInstance().getDbProcessServerProcessId());

		FATAL(!databaseProcessConnection, ("Tried to add a game server without a database process connection?"));

		for (std::map<uint32, GameServerConnection *>::iterator i = m_gameServerConnections.begin(); i != m_gameServerConnections.end(); ++i)
		{
			GameServerConnection const * const g = (*i).second;
			if (g != databaseProcessConnection && g->getProcessId() != pid)
				serverList.push_back(g->getProcessId());
		}

		GenericValueTypeMessage<std::pair<std::vector<uint32>, std::pair<uint32, std::pair<std::string, uint16> > > > const setupMessage(
			"GameServerSetupMessage",
			std::make_pair(
				serverList,
				std::make_pair(
					databaseProcessConnection->getProcessId(),
					std::make_pair(
						databaseProcessConnection->getGameServiceAddress(),
						databaseProcessConnection->getGameServicePort()))));

		newGameServer->send(setupMessage, true);
	}
}

//-----------------------------------------------------------------------

void CentralServer::broadcastToChatServers(const GameNetworkMessage & message) const
{
	for (std::set<ChatServerConnection *>::const_iterator i = m_chatServerConnections.begin(); i != m_chatServerConnections.end(); ++i)
		(*i)->send(message, true);
}

//-----------------------------------------------------------------------

void CentralServer::broadcastToCustomerServiceServers(const GameNetworkMessage & message) const
{
	for (std::set<CustomerServiceConnection *>::const_iterator i = m_csServerConnections.begin(); i != m_csServerConnections.end(); ++i)
		(*i)->send(message, true);
}

//-----------------------------------------------------------------------

void CentralServer::broadcastToGameServers(const GameNetworkMessage & message) const
{
	for (SceneGameMap::const_iterator i = m_gameServers.begin(); i != m_gameServers.end(); ++i)
		(*i).second->send(message, true);
}

//-----------------------------------------------------------------------
// /**
//  * Clears the object process map for an object.
//  *
//  * @param objectId		id of object we want to clear
//  */
// void CentralServer::clearObjectMap(const NetworkId & objectId)
// {
// 	IGNORE_RETURN(m_centralObjectMap.erase(objectId));
// }

//-----------------------------------------------------------------------

bool CentralServer::getIsClusterPublic() const
{
	return gs_connectionServersPublic;
}

//-----------------------------------------------------------------------

bool CentralServer::getIsClusterLocked() const
{
	return gs_clusterIsLocked;
}

//-----------------------------------------------------------------------

bool CentralServer::getIsClusterSecret() const
{
	return gs_clusterIsSecret;
}

//-----------------------------------------------------------------------

const std::string& CentralServer::getHostForScene(const std::string& scene) const
{
	static std::string anyString = "any";
	std::map<std::string, std::string>::iterator i = ms_sceneToHostMap.find(scene);
	if (i != ms_sceneToHostMap.end())
	{
		return i->second;
	}
	return anyString;
}

// ----------------------------------------------------------------------

/**
 * Get the list of game servers that are ready
 */
void CentralServer::getReadyGameServers(std::vector<uint32> &theList)
{
	std::map<uint32, GameServerConnection *>::const_iterator i;
	for (i = m_gameServerConnections.begin(); i!=m_gameServerConnections.end(); ++i)
	{
		if ((*i).second->getProcessId()!=getDbProcessServerProcessId() && i->second->getReady())
			theList.push_back(i->second->getProcessId());
	}
}

//-------------------------------------------------------------

GameServerConnection * CentralServer::getRandomGameServer(void)
{
	if (m_gameServerConnectionsList.empty())
		return nullptr;

	// m_gameServerConnectionsList ***DOES NOT*** contain the DB server so
	// we don't have to worry about checking for and excluding the DB server
	size_t const listSize = m_gameServerConnectionsList.size();
	size_t const indexChosenGameServer = (::rand() % listSize);
	if (m_gameServerConnectionsList[indexChosenGameServer]->getReady())
		return m_gameServerConnectionsList[indexChosenGameServer];

	// randomly chosen game server is not ready, find next ready game server
	size_t indexNextGameServer = (indexChosenGameServer + 1);
	if (indexNextGameServer == listSize)
		indexNextGameServer = 0;

	while (indexNextGameServer != indexChosenGameServer)
	{
		if (m_gameServerConnectionsList[indexNextGameServer]->getReady())
			return m_gameServerConnectionsList[indexNextGameServer];

		++indexNextGameServer;
		if (indexNextGameServer == listSize)
			indexNextGameServer = 0;
	}

	return nullptr;
}

//-----------------------------------------------------------------------

/**
* Returns a game server that is handling a scene.
*
* @param scene		the scene
*
* @return a game server connection for that scene
*/
GameServerConnection * CentralServer::getGameServer(const SceneId &scene) const
{
	// @todo: we should pick a gameserver based on load

	CentralServer::ServersList range = getGameServers(scene);
	SceneGameMap::const_iterator iter = range.first;
	while (iter != range.second)
	{
		if (iter->second->getReady())
			return iter->second;
		++iter;
	}
	return 0;
}

//-----------------------------------------------------------------------

/**
* Returns the game server for a given process id.
*
* @param processId		the process id
*
* @return the game server connection
*/
GameServerConnection * CentralServer::getGameServer(const uint32 processId) const
{
	GameServerConnection * result = 0;
	std::map<uint32, GameServerConnection *>::const_iterator i = m_gameServerConnections.find(processId);
	if(i != m_gameServerConnections.end())
		result = (*i).second;
	return result;
}

//-----------------------------------------------------------------------

/**
* Returns a list of game servers that are handling a scene.
*
* @param scene		the scene
*
* @return an iterator pair for a range of GameServerConnections
*/
const CentralServer::ServersList CentralServer::getGameServers(const SceneId &scene) const
{
	return m_gameServers.equal_range(scene); //lint !e64 //Type mismatch
}

//-----------------------------------------------------------------------

void CentralServer::pushAllKeys(ConnectionServerConnection * targetConnectionServer) const
{
	DEBUG_FATAL(static_cast<int>(m_loginServerKeys->getKeyCount()) < 0, ("Invalid number of keys (uint overflow) in CentralServer.h"));

	for(int i = static_cast<int>(m_loginServerKeys->getKeyCount()) - 1; i >= 0 ; i --)
	{
		ConnectionKeyPush pk(m_loginServerKeys->getKey(static_cast<uint32>(i)));
		targetConnectionServer->send(pk, true);
	}
}

//-----------------------------------------------------------------------

void CentralServer::launchStartingProcesses() const
{
	static bool startingProcessesLaunched = false;
	if (startingProcessesLaunched)
		return;

	// we can only launch starting processes once we have a connection to the
	// TaskManager *AND* have received our cluster id from the LoginServer
	if (!m_taskManager || !m_taskManager->isConnected() || (m_clusterId == 0))
	{
		REPORT_LOG(!m_taskManager, ("CentralServer not launching starting processes because m_taskManager is null\n"));
		REPORT_LOG(!m_taskManager->isConnected(), ("CentralServer not launching starting processes because m_taskManager->isConnected() is false\n"));
		REPORT_LOG((m_clusterId==0), ("CentralServer not launching starting processes because m_clusterId is 0 - not received from the LoginServer yet\n"));

		return;
	}

	REPORT_LOG(true, ("CentralServer now launching starting processes because has a connection to the TaskManager *AND* has received cluster Id (%u) from the LoginServer\n", m_clusterId));

	startingProcessesLaunched = true;

	// spawn a connection server
	launchStartingConnectionServers();

	std::string options = "-s dbProcess centralServerAddress=";
	if(getGameService())
	{
		options += getGameService()->getBindAddress();
	}
	else
	{
		options += NetworkHandler::getHostName();
	}

	TaskSpawnProcess pd(ConfigCentralServer::getDbServerHost(), "SwgDatabaseServer", options);
	m_taskManager->send(pd, true);

	options = "-s ChatServer centralServerAddress=";
	if(getChatService())
	{
		options += getChatService()->getBindAddress();
	}
	else
	{
		options += NetworkHandler::getHostName();
	}
	options += " clusterName=";
	options += ConfigCentralServer::getClusterName();
	TaskSpawnProcess pc(ConfigCentralServer::getChatServerHost(), "ChatServer", options);
	m_taskManager->send(pc, true);

	options = "-s CustomerServiceServer centralServerAddress=";
	if(getCustomerService())
	{
		options += getCustomerService()->getBindAddress();
	}
	else
	{
		options += NetworkHandler::getHostName();
	}

	options += " clusterName=";
	options += ConfigCentralServer::getClusterName();
	TaskSpawnProcess pcs("any", "CustomerServiceServer", options);
	m_taskManager->send(pcs, true);
}

//-----------------------------------------------------------------------

void CentralServer::launchStartingConnectionServers() const
{
	// if there are several planets listed explicitly in the config file/command line,
	// spawn them

	int i = 0;

	// check for nesteds
	int const numberOfStartingConnectionServers = ConfigCentralServer::getNumberOfStartingConnectionServers();
	for (i = 0; i < numberOfStartingConnectionServers; ++i)
	{
		char const * const host = ConfigCentralServer::getStartingConnectionServer(i);
		if (host)
		{
			char const * listenAddress = strchr(host,':');
			DEBUG_FATAL(!listenAddress, ("Could not start up connection server because string %s has no listen address", host));
			std::string hostString(host, static_cast<unsigned long>(listenAddress - host));
			++listenAddress;

			char const * publicPort = strchr(listenAddress,':');
			DEBUG_FATAL(!publicPort, ("Could not start up connection server because string %s has no public port", host));
			std::string listenString(listenAddress, static_cast<unsigned long>(publicPort - listenAddress));
			++publicPort;

			char const * privatePort = strchr(publicPort,':');
			DEBUG_FATAL(!privatePort, ("Could not start up connection server because string %s has no private port", host));
			std::string publicPortString(publicPort, static_cast<unsigned long>(privatePort - publicPort));
			++privatePort;
			std::string privatePortString(privatePort);

			DEBUG_REPORT_LOG(true, ("Spawning Connection Server on %s listen:%s public:%s priv:%s\n", hostString.c_str(), listenString.c_str(), publicPortString.c_str(), privatePortString.c_str()));

			// must update s_connectionServerHostList before calling startConnectionServer()
			s_connectionServerHostList.push_back(hostString);
			startConnectionServer(s_connectionServerHostList.size(), listenString, &publicPortString, &privatePortString, 0);
		}
	}

	// if there were none specified, use defaults
	if(i == 0)
	{
		// must update s_connectionServerHostList before calling startConnectionServer()
		s_connectionServerHostList.push_back("node0");
		startConnectionServer(s_connectionServerHostList.size(), NetworkHandler::getHostName(), 0, 0, 0);
	}

}

//-----------------------------------------------------------------------

void CentralServer::launchStartingPlanetServers()
{
	// if there are several planets listed explicitly in the config file/command line,
	// spawn them
	int i = 0;
	int const numberOfStartPlanets = ConfigCentralServer::getNumberOfStartPlanets();

	for (i = 0; i < numberOfStartPlanets ; ++i)
	{
		char const * const p = ConfigCentralServer::getStartPlanet(i);
		if (p)
		{
			FATAL(!*p, ("CentralServer::launchStartingPlanetServers: ConfigCentralServer::getStartPlanet(%d) specified a non-nullptr but empty planet name", i));

			std::string planetName;
			std::string hostName;
			char const * planet = p;
			NOT_NULL(planet);
			char const * host = strchr(planet,':');

			if (host)
			{
				planetName = std::string(planet, static_cast<unsigned long>(host - planet) );
				++host;
				hostName = host;
			}
			else
			{
				planetName = planet;
			}

			startPlanetServer(hostName, planetName, 0);
			ms_sceneToHostMap[planetName] = hostName;
		}
	}

	// if there were no planets specified, get the configfile default and
	// start that planet
	if(i == 0)
	{
		startPlanetServer("any", ConfigCentralServer::getStartPlanet(), 0);
	}
}

//-----------------------------------------------------------------------

void CentralServer::launchCommoditiesServer()
{
	std::string options;
	TaskSpawnProcess csp("any", "CommoditiesServer", options);
	CentralServer::getInstance().sendTaskMessage(csp);
}

//-----------------------------------------------------------------------

void CentralServer::receiveMessage(const MessageDispatch::Emitter & source, const MessageDispatch::MessageBase & message)
{
	const uint32 messageType = message.getType();

	switch (messageType) {
		case constcrc("LoginKeyPush") : {
			// receiving another key
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			const LoginKeyPush k(ri);
			m_loginServerKeys->pushKey(k.getKey());
			DEBUG_REPORT_LOG(true, ("Received session key.\n"));
			ConnectionServerConnectionList::iterator i = m_connectionServerConnections.begin();
			for (; i != m_connectionServerConnections.end(); ++i) {
				(*i)->send(k, true);
			}
			break;
		}
		case constcrc("RequestClusterShutdown") : {
			REPORT_LOG(true, ("An admin requested we shutdown the entire cluster\n"));
			m_done = true;
			break;
		}
		case constcrc("ClientConnectionClosed") : {
			Archive::ReadIterator ri = static_cast<GameNetworkMessage const &>(message).getByteStream().begin();
			GenericValueTypeMessage <StationId> const msg(ri);

			ConnectionServerSUIDMap::iterator i = m_accountConnectionMap.find(msg.getValue());
			if ((i != m_accountConnectionMap.end()) && (i->second == &source)) {
				// Erase the entry
				m_accountConnectionMap.erase(i);
			}
			break;
		}
		case constcrc("ConnectionOpened") : {
			if (!dynamic_cast<const LoginServerConnection *>(&source)) {
				ServerConnection * newServer = const_cast<ServerConnection *>(safe_cast < ServerConnection const * >
				                                                                                                 (&source));
				uint32 pid = newServer->getProcessId();

				CentralGameServerSetProcessId pidMessage(pid, ServerClock::getInstance()
						.getSubtractInterval(), ConfigCentralServer::getClusterName());
				newServer->send(pidMessage, true);
			}
			break;
		}
		case constcrc("ConnectionServerConnectionClosed") : {
			DEBUG_REPORT_LOG(true, ("Handling connection server crash.\n"));
			ConnectionServerConnection const *c = safe_cast < ConnectionServerConnection const * > (&source);
			removeFromAccountConnectionMap(c->getId());
			break;
		}
		case constcrc("GameConnectionClosed") : {
			DEBUG_REPORT_LOG(true, ("Game server closed connection\n"));
			GameServerConnection const *g = safe_cast < GameServerConnection const * > (&source);
			removeGameServer(g);
			break;
		}
		case constcrc("LoginConnectionOpened") : {
			LoginServerConnection *l = const_cast<LoginServerConnection *>(safe_cast < LoginServerConnection const * >
			                                                                                                       (&source));
			m_loginServerConnectionMap[l->getProcessId()] = l;
			//Send connection server data
			ConnectionServerConnectionList::iterator i = m_connectionServerConnections.begin();
			for (; i != m_connectionServerConnections.end(); ++i) {
				const ConnectionServerConnection *const csc = *i;
				if ((csc->getClientServicePortPrivate() != 0) || (csc->getClientServicePortPublic() != 0)) {
					const LoginConnectionServerAddress csa(csc->getId(), csc->getClientServiceAddress(), csc
							->getClientServicePortPrivate(), csc->getClientServicePortPublic(), csc
							->getPlayerCount(), csc->getPingPort());
					l->send(csa, true);
				}
			}

			// send preload is finished, if it is
			PreloadFinishedMessage msg(isPreloadFinished());
			l->send(msg, true);

			CharacterCreationTracker::getInstance().retryLoginServerCreates();
			break;
		}
		case constcrc("LoginConnectionClosed") : {
			LoginServerConnection const *l = safe_cast<const LoginServerConnection *>(&source);
			LoginServerConnectionMapType::iterator i = m_loginServerConnectionMap.find(l->getProcessId());
			if (i != m_loginServerConnectionMap.end()) {
				m_loginServerConnectionMap.erase(i);
			}

			// In development mode, try to reconnect to the login server if we aren't shutting down
			if (ConfigCentralServer::getDevelopmentMode() && !m_done) {
				connectToLoginServer();
			}

			CharacterCreationTracker::getInstance().onLoginServerDisconnect(l->getProcessId());
			break;
		}
		case constcrc("ClusterId") : {
			Archive::ReadIterator ri = static_cast<GameNetworkMessage const &>(message).getByteStream().begin();
			GenericValueTypeMessage <uint32> const msg(ri);

			if (m_clusterId == 0) {
				FATAL(((msg.getValue() < 1) ||
				       (msg.getValue() > 255)), ("Cluster Id (%lu) must be between 1 and 255 inclusive", msg
						.getValue()));

				m_clusterId = static_cast<uint8>(msg.getValue());

				// start launching processes
				launchStartingProcesses();
			}
			break;
		}
		case constcrc("CentralGameServerConnect") : {
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			CentralGameServerConnect c(ri);

			GameServerConnection *g = const_cast<GameServerConnection *>(safe_cast < GameServerConnection const * >
			                                                                                                    (&source));

			if (g != nullptr) {

				FATAL(ConfigCentralServer::getValidateBuildVersionNumber() &&
				      strcmp(ApplicationVersion::getInternalVersion(), c.getBuildVersionNumber().c_str()),
						("Build version number mismatch: central server (%s), remote server %s (%s)",
								ApplicationVersion::getInternalVersion(), g->getRemoteAddress().c_str(), c
								.getBuildVersionNumber().c_str()));

				// a game server (or db process) has connected...
				addGameServer(g);

				//Send connection server data
				ConnectionServerConnectionList::iterator i = m_connectionServerConnections.begin();
				for (; i != m_connectionServerConnections.end(); ++i) {
					if ((*i)->getGameServicePort() != 0) {
						ConnectionServerAddress csa((*i)->getGameServiceAddress(), (*i)->getGameServicePort());
						g->send(csa, true);
					}
				}

				std::set<ChatServerConnection *>::const_iterator chatIter;
				for (chatIter = m_chatServerConnections.begin();
						chatIter != m_chatServerConnections.end(); ++chatIter) {
					if ((*chatIter)->getGameServicePort()) {
						ChatServerOnline cso((*chatIter)->getRemoteAddress(), (*chatIter)->getGameServicePort());
						g->send(cso, true);
					}
				}

				const GenericValueTypeMessage <std::pair<std::string, unsigned short>> address("CustomerServiceServerGameServerServiceAddress", std::make_pair(s_customerServiceServerGameServerServiceAddress
						.first, s_customerServiceServerGameServerServiceAddress.second));
				g->send(address, true);
			}
			break;
		}
		case constcrc("ChatClosedConnectionWithGameServer") : {
			GameServerConnection *g = const_cast<GameServerConnection *>(safe_cast < GameServerConnection const * >
			                                                                                                    (&source));
			std::set<ChatServerConnection *>::const_iterator chatIter;
			for (chatIter = m_chatServerConnections.begin(); chatIter != m_chatServerConnections.end(); ++chatIter) {
				if ((*chatIter)->getGameServicePort()) {
					ChatServerOnline cso((*chatIter)->getRemoteAddress(), (*chatIter)->getGameServicePort());
					g->send(cso, true);
				}
			}
			break;
		}
		case constcrc("CustomerServiceServerGameServerServiceAddress") : {
			//DEBUG_REPORT_LOG(true, ("CentralServer::receiveMessage(CustomerServiceServerGameServerServiceAddress)\n"));

			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			const GenericValueTypeMessage <std::pair<std::string, unsigned short>> address(ri);
			s_customerServiceServerGameServerServiceAddress.first = address.getValue().first;
			s_customerServiceServerGameServerServiceAddress.second = address.getValue().second;

			broadcastToGameServers(address);
			break;
		}
		case constcrc("TaskConnectionClosed") : {
			//		Net::getInstance().connect(Network::Address("127.0.0.1", ConfigCentralServer::getTaskManagerPort()), TaskConnection());
			break;
		}
		case constcrc("TaskConnectionOpened") : {
			DEBUG_REPORT_LOG(true, ("Task manager connection opened\n"));
			break;
		}
		case constcrc("CentralGameServerDbProcessServerProcessId") : {
			DEBUG_REPORT_LOG(true, ("dbProcess connected\n"));
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			CentralGameServerDbProcessServerProcessId d(ri);
			m_dbProcessServerProcessId = d.getServerProcessId();
			ServerClock::getInstance().setSubtractInterval(d.getSubtractInterval());

			launchCommoditiesServer();
			launchStartingPlanetServers();
			break;
		}

		case constcrc("RequestChunkMessage") : {
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			RequestChunkMessage t(ri);

			// Forward this message to the dbProcess
			sendToGameServer(m_dbProcessServerProcessId, t, true);
			break;
		}
		case constcrc("LocateStructureMessage") : {
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			LocateStructureMessage t(ri);
			sendToPlanetServer(t.getSceneId(), t, true);
			break;
		}
		case constcrc("ForceUnloadObjectMessage") : {
			//N.B.  This message can come from a game server or from the planet server.
			DEBUG_WARNING(true, ("Received ForceUnloadObject.  Need to implement this\n"));

			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			ForceUnloadObjectMessage t(ri);

			//@todo: figure out some way to handle this (such as forwarding to PlanetServers), or remove every case where it's sent
			//forceUnload(t.getId(),t.getPermaDelete());
			break;
		}
			//Character Creation Messages
		case constcrc("ConnectionCreateCharacter") : {
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			ConnectionCreateCharacter c(ri);

			LOG("TraceCharacterCreation", ("%d received ConnectionCreateCharacter", c.getStationId()));
			CharacterCreationTracker::getInstance().handleCreateNewCharacter(c);
			break;
		}
		case constcrc("GameCreateCharacterFailed") : {
			DEBUG_REPORT_LOG(true, ("Game server advises central that character creation failed\n"));
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			GameCreateCharacterFailed f(ri);
			CharacterCreationTracker::getInstance()
					.handleGameCreateCharacterFailed(f.getStationId(), f.getName(), f.getErrorMessage(), f
							.getOptionalDetailedErrorMessage());
			break;
		}
		case constcrc("DatabaseCreateCharacterSuccess") : {
			DEBUG_REPORT_LOG(true, ("Database Process advises central that character creation succeeded\n"));
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			DatabaseCreateCharacterSuccess s(ri);
			CharacterCreationTracker::getInstance()
					.handleDatabaseCreateCharacterSuccess(s.getStationId(), s.getCharacterName(), s.getObjectId(), s
							.getTemplateId(), s.getJedi());
			break;
		}
		case constcrc("LoginCreateCharacterAckMessage") : {
			DEBUG_REPORT_LOG(true, ("Login Server advises central that character creation succeeded\n"));
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			LoginCreateCharacterAckMessage s(ri);
			CharacterCreationTracker::getInstance().handleLoginCreateCharacterAck(s.getStationId());
			break;
		}
		case constcrc("LoginRestoreCharacterMessage") : {
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			LoginRestoreCharacterMessage msg(ri);

			IGNORE_RETURN(sendToArbitraryLoginServer(msg));
			break;
		}
		case constcrc("NewCharacterCreated") : {
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			GenericValueTypeMessage <StationId> const ncc(ri);

			CentralServer::getInstance().sendToAllConnectionServers(ncc, true);
			break;
		}
		case constcrc("DatabaseConsoleReplyMessage") : {
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			GenericValueTypeMessage <std::pair<std::string, std::string>> msg(ri);

			IGNORE_RETURN(sendToRandomGameServer(msg));
			break;
		}
		case constcrc("LoginUpgradeAccountMessage") : {
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			LoginUpgradeAccountMessage msg(ri);

			if (msg.getIsAck()) {
				MessageToMessage const reply(
						MessageToPayload(msg.getReplyToObject(), NetworkId::cms_invalid, msg.getReplyMessage(), msg
								.getPackedMessageData(), 0, false, MessageToPayload::DT_c, NetworkId::cms_invalid, std::string(), 0),
						0);
				sendToAllGameServers(reply, true);
			}
			else {
				IGNORE_RETURN(sendToArbitraryLoginServer(msg));
			}
			break;
		}
		case constcrc("RandomNameRequest") : {
			GameServerConnection *gameServer = getRandomGameServer();
			if (gameServer) {
				Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
				RandomNameRequest crnr(ri);
				gameServer->send(crnr, true);
			}
			break;
		}
		case constcrc("RandomNameResponse") : {
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			RandomNameResponse crnr(ri);
			ConnectionServerConnection *conn = getConnectionServerForAccount(crnr.getStationId());
			if (conn) {
				conn->send(crnr, true);
			}
			else {
				DEBUG_REPORT_LOG(true, ("Could not send name to client because unable to determine which connection server to use.\n"));
			}
			break;
		}
		case constcrc("VerifyAndLockNameRequest") : {
			GameServerConnection *gameServer = getRandomGameServer();
			if (gameServer) {
				Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
				VerifyAndLockNameRequest valnr(ri);
				gameServer->send(valnr, true);
			}
			break;
		}
		case constcrc("VerifyAndLockNameResponse") : {
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			VerifyAndLockNameResponse valnr(ri);
			ConnectionServerConnection *conn = getConnectionServerForAccount(valnr.getStationId());
			if (conn) {
				conn->send(valnr, true);
			}
			else {
				DEBUG_REPORT_LOG(true, ("Could not send name lock response to client because unable to determine which connection server to use.\n"));
			}
			break;
		}
		case constcrc("RequestOIDsMessage") : {
			DEBUG_REPORT_LOG(true, ("Got RequestOIDsMessage.\n"));
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			RequestOIDsMessage m(ri);

			if (m.getLogRequest()) {
				LOG("ObjectIdManager", ("Requesting %lu more object ids for pid %lu", m.getHowMany(), m.getServerId()));
			}

			sendToGameServer(m_dbProcessServerProcessId, m, true);
			break;
		}
		case constcrc("AddOIDBlockMessage") : {
			DEBUG_REPORT_LOG(true, ("Got AddOIDBlockMessage.\n"));
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			AddOIDBlockMessage m(ri);

			if (m.getLogRequest()) {
				LOG("ObjectIdManager", ("Returning object ids (%s - %s) for pid %lu", m.getStart().getValueString()
				                                                                       .c_str(), m.getEnd()
				                                                                                  .getValueString()
				                                                                                  .c_str(), m
						.getServerId()));
			}

			sendToGameServer(m.getServerId(), m, true);
			break;
		}
		case constcrc("LoggedInMessage") : {
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			LoggedInMessage m(ri);
			DEBUG_REPORT_LOG(true, ("Pending character %lu is logging in or dropping\n", m.getAccountNumber()));

			// Once they're logged in, Central doesn't need to know about them anymore:
			removeFromAccountConnectionMap(m.getAccountNumber());
			break;
		}
		case constcrc("CharacterListMessage") : {
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			CharacterListMessage m(ri);

			// Find the client connection and send the character to it.
			DEBUG_REPORT_LOG(true, ("Got CharacterListMessage for %lu.\n", m.getAccountNumber()));
			ConnectionServerConnection *conn = getConnectionServerForAccount(m.getAccountNumber());

			if (conn) {
				conn->send(m, true);
			}
			else {
				DEBUG_REPORT_LOG(true, ("Warning:  received CharacterListMessage for client that is not connected."));
			}
			break;
		}
		case constcrc("ValidateCharacterForLoginMessage") : {
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			ValidateCharacterForLoginMessage m(ri);

			// ask DBProcess to check whether the character is valid
			//pendingCharactersLogin[m.getSuid()] = safe_cast<ConnectionServerConnection const *>(&source);
			if (hasDBConnection()) {
				sendToDBProcess(m, true);
			}
			break;
		}
		case constcrc("ValidateCharacterForLoginReplyMessage") : {
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			ValidateCharacterForLoginReplyMessage msg(ri);

			ConnectionServerConnection *conn = getConnectionServerForAccount(msg.getSuid());
			if (conn) {
				conn->send(msg, true);
			}
			else {
				DEBUG_REPORT_LOG(true, ("Trying to handle ValidateCharacterForLoginReplyMessage for account %lu, but could not determine which connection server to use.\n", msg
						.getSuid()));
			}
			break;
		}
		case constcrc("EnableCharacterMessage") : {
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			GenericValueTypeMessage < std::pair < std::pair < StationId, NetworkId >, std::string > > msg(ri);

			LOG("CustomerService", ("EnableCharacter %d, %s request from %s\n", msg.getValue().first.first, msg
					.getValue().first.second.getValueString().c_str(), msg.getValue().second.c_str()));
			// forward this request to LoginServer
			IGNORE_RETURN(sendToArbitraryLoginServer(msg));
			break;
		}
		case constcrc("EnableCharacterReplyMessage") : {
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			GenericValueTypeMessage <std::pair<std::string, std::string>> msg(ri);
			GenericValueTypeMessage <std::pair<std::string, std::string>> reply("DatabaseConsoleReplyMessage", std::make_pair(msg
					.getValue().first, msg.getValue().second));
			getRandomGameServer()->send(reply, true);
			break;
		}
		case constcrc("DisableCharacterMessage") : {
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			GenericValueTypeMessage < std::pair < std::pair < StationId, NetworkId >, std::string > > msg(ri);

			LOG("CustomerService", ("DisableCharacter %d, %s request from %s\n", msg.getValue().first.first, msg
					.getValue().first.second.getValueString().c_str(), msg.getValue().second.c_str()));
			// forward this request to LoginServer
			IGNORE_RETURN(sendToArbitraryLoginServer(msg));
			break;
		}
		case constcrc("DisableCharacterReplyMessage") : {
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			GenericValueTypeMessage <std::pair<std::string, std::string>> msg(ri);
			GenericValueTypeMessage <std::pair<std::string, std::string>> reply("DatabaseConsoleReplyMessage", std::make_pair(msg
					.getValue().first, msg.getValue().second));
			getRandomGameServer()->send(reply, true);
			break;
		}
		case constcrc("TransferReplyLoginLocationData") : {
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			GenericValueTypeMessage <TransferCharacterData> reply(ri);
			// If this request has a CS Tool Id associated with it, it is an admin request for the CSTool,
			// and so we should send it directly to the connection server, and not depend on a 
			// transfer server existing.

			if (reply.getValue().getCSToolId() > 0) {
				GenericValueTypeMessage <TransferCharacterData> loginMessage("TransferLoginCharacterToSourceServer", reply
						.getValue());
				ConnectionServerConnection *conn = getAnyConnectionServer();
				if (conn) {
					conn->send(loginMessage, true);
				}
				return;
			}

			LOG("CustomerService", ("CharacterTransfer: Received TransferReplyLoginLocationData from database for character %s\n", reply
					.getValue().getSourceCharacterName().c_str()));

			if (reply.getValue().getTransferRequestSource() == TransferRequestMoveValidation::TRS_transfer_server) {
				CentralServer::getInstance().sendToTransferServer(reply);
			}
			else {
				// send character to ConnectionServer for login to a game server
				ConnectionServerConnection *connectionServer = CentralServer::getInstance().getAnyConnectionServer();
				if (connectionServer) {
					const GenericValueTypeMessage <TransferCharacterData> login("TransferLoginCharacterToSourceServer", reply
							.getValue());
					connectionServer->send(login, true);

					LOG("CustomerService", ("CharacterTransfer: Sending TransferLoginCharacterToSourceServer to ConnectionServer : %s", login
							.getValue().toString().c_str()));
				}
			}
			break;
		}
		case constcrc("CentralPlanetServerConnect") : {
			PlanetServerConnection *g = const_cast<PlanetServerConnection *>(safe_cast < PlanetServerConnection
			const
			* > (&source));
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			CentralPlanetServerConnect msg(ri);
			g->setGameServerConnectionData(msg.getConnectionAddress(), msg.getGameServerPort());
			PlanetManager::addServer(msg.getSceneId(), g);

			std::map < std::string, std::pair < std::pair < std::string, std::string >, time_t > > ::iterator
			f = m_pendingPlanetServers.find(msg.getSceneId());
			if (f != m_pendingPlanetServers.end()) {
				m_pendingPlanetServers.erase(f);
			}

			IGNORE_RETURN(m_planetServers.insert(std::make_pair(msg.getSceneId(), g)));

			if (isPreloadFinished()) {
				m_timeClusterWentIntoLoadingState = 0;
			}
			else if (m_timeClusterWentIntoLoadingState <= 0) {
				m_timeClusterWentIntoLoadingState = time(0);
			}

			//  handle planet transfers and logins for planet server that aren't up yet
			std::vector<Archive::ByteStream>::iterator t;
			for (t = m_messagesWaitingForPlanetServer.begin(); t != m_messagesWaitingForPlanetServer.end();) {
				Archive::ReadIterator tri = t->begin();
				const GameNetworkMessage gnm(tri);
				const uint32 mt = gnm.getType();

				tri = t->begin();
				switch (mt) {
					case constcrc("RequestGameServerForLoginMessage") : {
						const RequestGameServerForLoginMessage loginMessage(tri);
						if (loginMessage.getScene() == msg.getSceneId()) {
							t = m_messagesWaitingForPlanetServer.erase(t);
							handleRequestGameServerForLoginMessage(loginMessage);
						}
						break;
					}
					case constcrc("RequestSceneTransfer") : {
						const RequestSceneTransfer sceneMessage(tri);
						if (sceneMessage.getSceneName() == msg.getSceneId()) {
							t = m_messagesWaitingForPlanetServer.erase(t);
							handleRequestSceneTransfer(sceneMessage);
						}
						break;
					}
					default : {
						t = m_messagesWaitingForPlanetServer.erase(t);
						WARNING_STRICT_FATAL(true, ("Unknown message type waiting for planet server"));
						break;
					}
				}
			}
			break;
		}
		case constcrc("RequestSceneTransfer") : {
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			const RequestSceneTransfer msg(ri);

			handleRequestSceneTransfer(msg);
			break;
		}
		case constcrc("SceneTransferMessage") : {
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			SceneTransferMessage msg(ri);

			sendToGameServer(msg.getSourceGameServer(), msg, true);
			break;
		}
		case constcrc("GameServerReadyMessage") : {
			GameServerConnection const *g = safe_cast < GameServerConnection const * > (&source);
			UniverseManager::getInstance().onGameServerReady(*g);
			break;
		}
		case constcrc("GameServerUniverseLoadedMessage") : {
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			const GameServerUniverseLoadedMessage msg(ri);

			// forward to the universe game server as ack that
			// the game server who sent this message has received
			// its UniverseCompleteMessage from the universe game server
			if (msg.getSourceOfUniverseDataProcessId() != getDbProcessServerProcessId()) {
				sendToGameServer(msg.getSourceOfUniverseDataProcessId(), msg, true);
			}

			CharacterCreationTracker::getInstance().retryGameServerCreates();
			break;
		}
		case constcrc("MessageToMessage") : {
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			MessageToMessage msg(ri);

			WARNING_STRICT_FATAL(true, ("CentralServer receieved a messageTo.  These should not go to Central anymore.  Sender was server %u\n",
					safe_cast < GameServerConnection const * > (&source)->getProcessId()));
			break;
		}
		case constcrc("MessageToAckMessage") : {
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			MessageToAckMessage msg(ri);

			WARNING_STRICT_FATAL(true, ("CentralServer receieved a MessageToAckMessage.  These should not go to Central anymore.  Sender was server %u\n",
					safe_cast < GameServerConnection const * > (&source)->getProcessId()));
			break;
		}
		case constcrc("ChatServerConnectionOpened") : {
			// enumerate servers
			ChatServerConnection *chatServer = const_cast<ChatServerConnection *>(safe_cast < ChatServerConnection
			const
			* > (&source));
			IGNORE_RETURN(m_chatServerConnections.insert(chatServer));
			const int ct = static_cast<int>(EnumerateServers::CONNECTION_SERVER);
			for (ConnectionServerConnectionList::const_iterator ci = m_connectionServerConnections.begin();
					ci != m_connectionServerConnections.end(); ++ci) {
				ConnectionServerConnection *c = (*ci);

				if ((c != nullptr)
				    && !c->getChatServiceAddress().empty()
				    && (c->getChatServicePort() != 0)) {
					EnumerateServers e(true, c->getChatServiceAddress(), c->getChatServicePort(), ct);
					chatServer->send(e, true);
				}
				else {
					LOG("ChatServer", ("receiveMessage() ChatServerConnectionOpened - Invalid connection address in m_connectionServerConnections, skipping entry"));
				}
			}

			// Tell the single chat server the address to communicate with the customer service server

			if (!s_customerServiceServerChatServerServiceAddress.first.empty()) {
				//DEBUG_REPORT_LOG(true, ("CentralServer::receiveMessage(ChatServerConnectionOpened)\n"));

				const GenericValueTypeMessage <std::pair<std::string, unsigned short>> msg("CustomerServiceServerChatServerServiceAddress", std::make_pair(s_customerServiceServerChatServerServiceAddress
						.first, s_customerServiceServerChatServerServiceAddress.second));

				chatServer->send(msg, true);
			}
			break;
		}
		case constcrc("CustomerServiceServerChatServerServiceAddress") : {
			//DEBUG_REPORT_LOG(true, ("CentralServer::receiveMessage(CustomerServiceServerChatServerServiceAddress)\n"));

			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			const GenericValueTypeMessage <std::pair<std::string, unsigned short>> address(ri);

			s_customerServiceServerChatServerServiceAddress.first = address.getValue().first;
			s_customerServiceServerChatServerServiceAddress.second = address.getValue().second;

			// Tell the chat servers the address to communicate with the customer service server

			std::set<ChatServerConnection *>::iterator iterChatServerConnections = m_chatServerConnections.begin();

			for (; iterChatServerConnections != m_chatServerConnections.end(); ++iterChatServerConnections) {
				ChatServerConnection *connection = (*iterChatServerConnections);

				if (connection != nullptr) {
					connection->send(address, true);
				}
				else {
					REPORT_LOG(true, ("Trying to send the customer service server: chat server service address to a nullptr chat server\n"));
				}
			}
			break;
		}
		case constcrc("CustomerServiceConnectionOpened") : {
			CustomerServiceConnection *csServer = const_cast<CustomerServiceConnection *>(safe_cast <
			CustomerServiceConnection const
			* > (&source));
			IGNORE_RETURN(m_csServerConnections.insert(csServer));
			const int ct = static_cast<int>(EnumerateServers::CONNECTION_SERVER);
			for (ConnectionServerConnectionList::const_iterator ci = m_connectionServerConnections.begin();
					ci != m_connectionServerConnections.end(); ++ci) {
				ConnectionServerConnection *c = (*ci);

				if ((c != nullptr)
				    && !c->getCustomerServiceAddress().empty()
				    && (c->getCustomerServicePort() != 0)) {
					EnumerateServers e(true, c->getCustomerServiceAddress(), c->getCustomerServicePort(), ct);
					csServer->send(e, true);
				}
				else {
					LOG("CustServ", ("receiveMessage() CustomerServiceConnectionOpened - Invalid connection address in m_connectionServerConnections, skipping entry"));
				}
			}
			break;
		}
		case constcrc("ChatServerConnectionClosed") : {
			ChatServerConnection *chatServer = const_cast<ChatServerConnection *>(safe_cast < ChatServerConnection
			const
			* > (&source));
			IGNORE_RETURN(m_chatServerConnections.erase(chatServer));

			// spawn a new chat server!
			std::string options = "-s ChatServer centralServerAddress=";
			if (CentralServer::getInstance().getChatService()) {
				options += CentralServer::getInstance().getChatService()->getBindAddress();
			}
			else {
				options += NetworkHandler::getHostName();
			}

			options += " clusterName=";
			options += ConfigCentralServer::getClusterName();
			TaskSpawnProcess pc(ConfigCentralServer::getChatServerHost(), "ChatServer", options, ConfigCentralServer::getChatServerRestartDelayTimeSeconds());
			CentralServer::getInstance().sendTaskMessage(pc);

			break;
		}
		case constcrc("CustomerServiceConnectionClosed") : {
			CustomerServiceConnection *csServer = const_cast<CustomerServiceConnection *>(safe_cast <
			CustomerServiceConnection const
			* > (&source));
			IGNORE_RETURN(m_csServerConnections.erase(csServer));

			// spawn a new cs server!
			std::string options = "-s CustomerServiceServer centralServerAddress=";
			if (CentralServer::getInstance().getCustomerService()) {
				options += CentralServer::getInstance().getCustomerService()->getBindAddress();
			}
			else {
				options += NetworkHandler::getHostName();
			}

			options += " clusterName=";
			options += ConfigCentralServer::getClusterName();
			TaskSpawnProcess pc("any", "CustomerServiceServer", options);
			CentralServer::getInstance().sendTaskMessage(pc);
			break;
		}
		case constcrc("ChatServerOnline") : {
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			ChatServerOnline cso(ri);
			ChatServerConnection *csc = const_cast<ChatServerConnection *>(safe_cast < ChatServerConnection const * >
			                                                                                                      (&source));
			csc->setGameServicePort(cso.getPort());
			SceneGameMap::const_iterator iter;
			for (iter = m_gameServers.begin(); iter != m_gameServers.end(); ++iter) {
				GameServerConnection *conn = (*iter).second;
				conn->send(cso, true);
			}

			break;
		}
		case constcrc("RequestGameServerForLoginMessage") : {
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			const RequestGameServerForLoginMessage msg(ri);

			time_t const timeNow = ::time(nullptr);
			PlayerSceneMapType::const_iterator i = m_playerSceneMap.find(msg.getCharacterId());
			if ((i != m_playerSceneMap.end()) && (i->second.second > timeNow)) {
				ServerConnection * c = const_cast<ServerConnection *>(safe_cast < ServerConnection const * > (&source));
				GenericValueTypeMessage <std::pair<NetworkId, uint32>> const loginDeniedRecentCTS("LoginDeniedRecentCTS", std::make_pair(msg
						.getCharacterId(), msg.getStationId()));
				c->send(loginDeniedRecentCTS, true);
			}
			else {
				std::map < NetworkId, std::pair < time_t, int > > ::const_iterator
				const iterFind = s_pendingRenameCharacter.find(msg.getCharacterId());
				if ((iterFind != s_pendingRenameCharacter.end()) && (iterFind->second.first > timeNow)) {
					ServerConnection * c = const_cast<ServerConnection *>(safe_cast < ServerConnection const * >
					                                                                                         (&source));
					GenericValueTypeMessage <std::pair<NetworkId, uint32>> const loginDeniedPendingPlayerRenameRequest("LoginDeniedPendingPlayerRenameRequest", std::make_pair(msg
							.getCharacterId(), msg.getStationId()));
					c->send(loginDeniedPendingPlayerRenameRequest, true);
				}
				else {
					handleRequestGameServerForLoginMessage(msg);
				}
			}
			break;
		}
		case constcrc("GameServerForLoginMessage") : {
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			GameServerForLoginMessage msg(ri);

			handleGameServerForLoginMessage(msg);
			break;
		}


		case constcrc("ExchangeListCreditsMessage") : {
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			ExchangeListCreditsMessage msg(ri);

			handleExchangeListCreditsMessage(msg);
			break;
		}


		case constcrc("PlanetLoadCharacterMessage") : {
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			PlanetLoadCharacterMessage msg(ri);

			// let all the game servers know we are about to load a character from the DB
			GenericValueTypeMessage <std::pair<NetworkId, uint32>> const aboutToLoadCharacterFromDB("AboutToLoadCharacterFromDB", std::make_pair(msg
					.getCharacterId(), msg.getGameServerId()));
			sendToAllGameServersExceptDBProcess(aboutToLoadCharacterFromDB, true);

			sendToDBProcess(msg, true);
			break;
		}
		case constcrc("ConnSrvDropDupeConns") : {
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			GenericValueTypeMessage <std::pair<uint32, std::string>> const msg(ri);

			// A ConnectionServer has received and validated a new connection from the client

			// request that all other ConnectionServers drop any existing connection(s) for the account
			std::string galaxyConnectionInfo = "(";
			galaxyConnectionInfo += ConfigCentralServer::getClusterName();

			if (!msg.getValue().second.empty()) {
				galaxyConnectionInfo += ", ";
				galaxyConnectionInfo += msg.getValue().second;
			}

			galaxyConnectionInfo += ")";

			GenericValueTypeMessage <std::pair<uint32, std::string>> const dropDuplicateConnections("CntrlSrvDropDupeConns", std::make_pair(msg
					.getValue().first, galaxyConnectionInfo));
			sendToAllConnectionServers(dropDuplicateConnections, true, dynamic_cast<Connection const *>(&source));

			// request (via LoginServer) that all ConnectionServers on all other galaxies drop any existing connection(s) for the account
			if (ConfigCentralServer::getDisconnectDuplicateConnectionsOnOtherGalaxies()) {
				IGNORE_RETURN(sendToArbitraryLoginServer(dropDuplicateConnections));
			}
			break;
		}
		case constcrc("CntrlSrvDropDupeConns") : {
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			GenericValueTypeMessage <std::pair<uint32, std::string>> const msg(ri);

			// request that all ConnectionServers drop any existing connection(s) for the account
			sendToAllConnectionServers(msg, true);
			break;
		}
		case constcrc("PlayerDroppedFromGameServerCrash") : {
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			GenericValueTypeMessage <std::set<NetworkId>> const m(ri);

			std::set<NetworkId>::const_iterator const end = m.getValue().end();
			for (std::set<NetworkId>::const_iterator iter = m.getValue().begin(); iter != end; ++iter) {
				IGNORE_RETURN(m_playerSceneMap.erase(*iter));
				LOG("TRACE_LOGIN", ("Forgetting sceneId (because of game server crash) for character (%s) -- will query the database on next login", iter
						->getValueString().c_str()));
			}
			break;
		}
		case constcrc("ServerDeleteCharacterMessage") : {
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			LoginServerConnection const *l = safe_cast < LoginServerConnection const * > (&source);
			ServerDeleteCharacterMessage msg(ri);
			msg.setLoginServerId(l->getProcessId());

			if (hasDBConnection()) {
				LOG("CustomerService", ("Player:deleted character %s for stationId %u", msg.getCharacterId()
				                                                                           .getValueString()
				                                                                           .c_str(), msg
						.getStationId()));
				sendToDBProcess(msg, true);

				// let the game servers know that the character is being deleted
				GenericValueTypeMessage <NetworkId> const msg2("DeleteCharacterNotificationMessage", msg
						.getCharacterId());
				IGNORE_RETURN(sendToRandomGameServer(msg2));
			}
			break;
		}
		case constcrc("UpdatePlayerCountMessage") : {
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			UpdatePlayerCountMessage msg(ri);

			ConnectionServerConnection *csc = const_cast<ConnectionServerConnection *>(dynamic_cast<const ConnectionServerConnection *>(&source));
			if (csc) {
				// Update our stored values for the connection server
				csc->setPlayerCount(msg.getCount());
				csc->setFreeTrialCount(msg.getFreeTrialCount());
				csc->setEmptySceneCount(msg.getEmptySceneCount());
				csc->setTutorialSceneCount(msg.getTutorialSceneCount());
				csc->setFalconSceneCount(msg.getFalconSceneCount());

				// Update the login server with the new values
				UpdateLoginConnectionServerStatus ulc(csc->getId(), csc->getClientServicePortPublic(), csc
						->getClientServicePortPrivate(), msg.getCount());
				sendToAllLoginServers(ulc);

				// Update the login servers with new population values
				sendPopulationUpdateToLoginServer();
			}
			else {
				WARNING_STRICT_FATAL(true, ("Got UpdatePlayerCountMessage from something that wasn't a ConnectionServer.\n"));
			}
			break;
		}
		case constcrc("ValidateAccountMessage") : {
			DEBUG_REPORT_LOG(true, ("ValidateAccountMessage\n"));
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			ValidateAccountMessage msg(ri);

			// Store the connection information for the account
			addToAccountConnectionMap(msg
					.getStationId(), const_cast<ConnectionServerConnection *>(dynamic_cast<const ConnectionServerConnection *>(&source)), msg
					.getSubscriptionBits());

			// Pass the validation to a LoginServer
			IGNORE_RETURN(sendToArbitraryLoginServer(msg));
			break;
		}
		case constcrc("ValidateAccountReplyMessage") : {
			DEBUG_REPORT_LOG(true, ("ValidateAccountReplyMessage\n"));
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			ValidateAccountReplyMessage msg(ri);

			ConnectionServerConnection *conn = getConnectionServerForAccount(msg.getStationId());
			if (conn) {
				conn->send(msg, true);
			}
			break;
		}
		case constcrc("PreloadRequestCompleteMessage") : {
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			PreloadRequestCompleteMessage msg(ri);

			sendToDBProcess(msg, true);
			break;
		}
		case constcrc("ReconnectToTransferServer") : {
			if (ConfigCentralServer::getTransferServerPort()) {
				if (!getInstance().m_transferServerConnection) {
					getInstance()
							.m_transferServerConnection = new TransferServerConnection(ConfigCentralServer::getTransferServerAddress(), ConfigCentralServer::getTransferServerPort());
					s_retryTransferServerConnection = true;
				}
			}
			break;
		}
		case constcrc("ReconnectToStationPlayersCollector") : {
			if (ConfigCentralServer::getStationPlayersCollectorPort()) {
				if (!getInstance().m_stationPlayersCollectorConnection) {
					getInstance()
							.m_stationPlayersCollectorConnection = new StationPlayersCollectorConnection(ConfigCentralServer::getStationPlayersCollectorAddress(), ConfigCentralServer::getStationPlayersCollectorPort());
					s_retryStationPlayersCollectorConnection = true;
				}
			}
			break;
		}
		case constcrc("PreloadFinishedMessage") : {
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			PreloadFinishedMessage msg(ri);

			const PlanetServerConnection *conn = dynamic_cast<const PlanetServerConnection *>(&source);
			WARNING_STRICT_FATAL(!conn, ("Programmer bug:  got PreloadFinishedMessaage from something that wasn't a PlanetServer.\n"));
			if (conn) {
				if (msg.getFinished()) {
					IGNORE_RETURN(m_planetsWaitingForPreload.erase(conn->getSceneId()));
					if (isPreloadFinished()) {
						m_timeClusterWentIntoLoadingState = 0;

						DEBUG_REPORT_LOG(true, ("Preload finished on all planets.\n"));

						// record how long it took the cluster to come up
						if (m_clusterStartupTime == -1) {
							m_clusterStartupTime = static_cast<int>(time(0) - m_timeClusterStarted) / 60;

							// let all the game servers know that the cluster has completed its initial startup
							GenericValueTypeMessage<bool> clusterStartComplete("ClusterStartComplete", true);
							sendToAllGameServersExceptDBProcess(clusterStartComplete, true);
						}
						else {
							// let all the game servers know that the cluster has recovered from a crash
							GenericValueTypeMessage<bool> clusterStartComplete("ClusterStartComplete", false);
							sendToAllGameServersExceptDBProcess(clusterStartComplete, true);
						}

						sendToAllLoginServers(msg);
						sendTaskMessage(msg);
						m_lastLoadingStateTime = time(0);
						// connect to the character transfer server
						if (ConfigCentralServer::getTransferServerPort()) {
							if (!getInstance().m_transferServerConnection) {
								getInstance()
										.m_transferServerConnection = new TransferServerConnection(ConfigCentralServer::getTransferServerAddress(), ConfigCentralServer::getTransferServerPort());
								s_retryTransferServerConnection = true;
							}
						}

						// connect to the station players collector
						if (ConfigCentralServer::getStationPlayersCollectorPort()) {
							if (!getInstance().m_stationPlayersCollectorConnection) {
								getInstance()
										.m_stationPlayersCollectorConnection = new StationPlayersCollectorConnection(ConfigCentralServer::getStationPlayersCollectorAddress(), ConfigCentralServer::getStationPlayersCollectorPort());
								s_retryStationPlayersCollectorConnection = true;
							}
						}
					}
					else if (m_timeClusterWentIntoLoadingState <= 0) {
						m_timeClusterWentIntoLoadingState = time(0);
					}
				}
				else {
					if (getInstance().m_transferServerConnection != nullptr) {
						getInstance().m_transferServerConnection->disconnect();
						getInstance().m_transferServerConnection = 0;
						s_retryTransferServerConnection = false;
					}

					if (getInstance().m_stationPlayersCollectorConnection != nullptr) {
						getInstance().m_stationPlayersCollectorConnection->disconnect();
						getInstance().m_stationPlayersCollectorConnection = 0;
						s_retryStationPlayersCollectorConnection = false;
					}


					IGNORE_RETURN(m_planetsWaitingForPreload.insert(conn->getSceneId()));

					if (isPreloadFinished()) {
						m_timeClusterWentIntoLoadingState = 0;
					}
					else if (m_timeClusterWentIntoLoadingState <= 0) {
						m_timeClusterWentIntoLoadingState = time(0);
					}


					sendToAllLoginServers(msg);
					sendTaskMessage(msg);
				}
			}
			break;
		}
		case constcrc("RenameCharacterMessageEx") : {
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			RenameCharacterMessageEx msg(ri);

			IGNORE_RETURN(sendToArbitraryLoginServer(RenameCharacterMessage(msg.getCharacterId(), msg.getNewName(), msg
					.getRequestedBy())));

			// if the player requested the rename, also rename the chat avatar, so mail, friends list, and ignore list will migrate
			if ((msg.getRenameCharacterMessageSource() == RenameCharacterMessageEx::RCMS_player_request) &&
			    !msg.getLastNameChangeOnly() &&
			    !ConfigFile::getKeyBool("CharacterRename", "disableRenameChatAvatar", false)) {
				broadcastToChatServers(msg);
			}
			break;
		}
		case constcrc("PlayerRenameRequestSubmitted") : {
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			GenericValueTypeMessage < std::pair < unsigned
			int, std::pair < NetworkId, std::pair < std::string, bool > > > >
			const msg(ri);

			GenericValueTypeMessage<unsigned int> kick("TransferKickConnectedClients", msg.getValue().first);
			CentralServer::getInstance().sendToAllLoginServers(kick);
			CentralServer::getInstance().sendToAllConnectionServers(kick, true);

			// update the list of pending rename requests, to prevent a character with a pending rename request from logging in
			std::map < NetworkId, std::pair < time_t, int > > ::iterator
			const iterFind = s_pendingRenameCharacter.find(msg.getValue().second.first);
			if (iterFind != s_pendingRenameCharacter.end()) {
				++(iterFind->second.second);
				iterFind->second.first = ::time(nullptr) + 3600; // 1 hour timeout
			}
			else {
				s_pendingRenameCharacter
						.insert(std::make_pair(msg.getValue().second.first, std::make_pair((::time(nullptr) +
						                                                                    3600), 1))); // 1 hour timeout
			}

			// tell the chat server to destroy any avatar with the new name, but only if the first name changed
			if (!msg.getValue().second.second.second) {
				GenericValueTypeMessage <std::string> const chatDestroyAvatar("ChatDestroyAvatar", msg.getValue().second
				                                                                                      .second.first);
				broadcastToChatServers(chatDestroyAvatar);
			}
			break;
		}
		case constcrc("PlayerRenameRequestCompleted") : {
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			GenericValueTypeMessage <std::pair<unsigned int, NetworkId>> const msg(ri);

			// update the list of pending rename requests, to prevent a character with a pending rename request from logging in
			std::map < NetworkId, std::pair < time_t, int > > ::iterator
			const iterFind = s_pendingRenameCharacter.find(msg.getValue().second);
			if (iterFind != s_pendingRenameCharacter.end()) {
				if (iterFind->second.second <= 1) {
					s_pendingRenameCharacter.erase(iterFind);
				}
				else {
					--(iterFind->second.second);
				}
			}
			break;
		}
		case constcrc("SetConnectionServerPublic") : {
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			SetConnectionServerPublic msg(ri);
			if (msg.getIsPublic()) {
				gs_connectionServersPublic = true;
			}
			else {
				gs_connectionServersPublic = false;
			}

			sendToAllConnectionServers(msg, true);
			break;
		}
		case constcrc("ProfilerOperationMessage") : {
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			ProfilerOperationMessage msg(ri);
			sendToAllGameServers(msg, true);
			sendToAllPlanetServers(msg, true);
			sendToAllConnectionServers(msg, true);
			break;
		}
		case constcrc("PopulationListMessage") : {
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			PopulationListMessage msg(ri);
			sendToAllGameServers(msg, true);
			break;
		}
		case constcrc("CentralPingMessage") : {
			GameServerConnection const *g = safe_cast < GameServerConnection const * > (&source);
			NOT_NULL(g);
			LOG("CentralServerPings", ("Got reply from %lu", g->getProcessId()));
			IGNORE_RETURN(m_serverPings.erase(g->getProcessId()));
			break;
		}
		case constcrc("DatabaseBackloggedMessage") : {
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			GenericValueTypeMessage<bool> dbbacklog(ri);

			m_databaseBacklogged = dbbacklog.getValue();
			bool const preloadFinished = isPreloadFinished();

			if (preloadFinished) {
				m_timeClusterWentIntoLoadingState = 0;
			}
			else if (m_timeClusterWentIntoLoadingState <= 0) {
				m_timeClusterWentIntoLoadingState = time(0);
			}

			PreloadFinishedMessage msg(preloadFinished);
			sendToAllLoginServers(msg);
			sendTaskMessage(msg);
			break;
		}
		case constcrc("DatabaseSaveStart") : {
			//LOG("CentralServer", ("Received DatabaseSaveStart network message."));
			if (m_shutdownPhase == 4) {
				LOG("CentralServerShutdown", ("Shutdown Phase %d: Setting indicator for receipt of DatabaseSaveStart.", m_shutdownPhase));
				m_shutdownHaveDatabaseSaveStart = true;
				checkShutdownProcess();
			}
			else if (m_shutdownPhase == 5 && m_shutdownHaveDatabaseSaveStart) {
				LOG("CentralServerShutdown", ("Shutdown Phase %d: Setting indicator that final database save cycle is complete.", m_shutdownPhase));
				m_shutdownHaveDatabaseComplete = true;
				checkShutdownProcess();
			}
			break;
		}
		case constcrc("DatabaseSaveComplete") : {
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			GenericValueTypeMessage<int> msg(ri);
			//LOG("CentralServer", ("Received DatabaseSaveComplete network message."));

			// tell all the Planet Servers that the save finished
			sendToAllPlanetServers(msg, true);

			// don't want to indicate this yet until we are at the beginning of a full cycle
			if (m_shutdownPhase == 5 && m_shutdownHaveDatabaseSaveStart) {
				LOG("CentralServerShutdown", ("Shutdown Phase %d: Setting indicator that final database save cycle is complete.", m_shutdownPhase));
				m_shutdownHaveDatabaseComplete = true;
				checkShutdownProcess();
			}
			break;
		}
		case constcrc("PlanetRequestSave") : {
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			GameNetworkMessage msg(ri);

			sendToDBProcess(msg, true);
			break;
		}
		case constcrc("ShutdownCluster") : {
			LOG("CentralServerShutdown", ("Received ShutdownCluster network message."));
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			ShutdownCluster m(ri);
			startShutdownProcess(m.getTimeToShutdown(), m.getMaxTime(), m.getSystemMessage());
			break;
		}
		case constcrc("AbortShutdown") : {
			LOG("CentralServerShutdown", ("Received AbortShutdown network message."));
			abortShutdownProcess();
			break;
		}


		case constcrc("SetSceneForPlayer") : {
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			GenericValueTypeMessage < std::pair < NetworkId, std::pair < std::string, bool > > > ssfp(ri);

			if (ssfp.getValue().second.first.empty()) {
				static const std::string loginTrace("TRACE_LOGIN");

				IGNORE_RETURN(m_playerSceneMap.erase(ssfp.getValue().first));
				LOG(loginTrace, ("Forgetting sceneId for character (%s) -- will query the database on next login", ssfp
						.getValue().first.getValueString().c_str()));
			}
			else {
				PlayerSceneMapType::iterator i = m_playerSceneMap.find(ssfp.getValue().first);
				if (i != m_playerSceneMap.end()) {
					i->second.first = ssfp.getValue().second.first;
					if (ssfp.getValue().second.second) {
						i->second.second = ::time(nullptr) +
						                   static_cast<time_t>(ConfigCentralServer::getCtsDenyLoginThresholdSeconds());
					}
				}
				else {
					m_playerSceneMap[ssfp.getValue().first] = std::make_pair(ssfp.getValue().second
					                                                             .first, (ssfp.getValue().second.second
					                                                                      ? (::time(nullptr) +
					                                                                         static_cast<time_t>(ConfigCentralServer::getCtsDenyLoginThresholdSeconds()))
					                                                                      : 0));
				}
			}
			break;
		}
		case constcrc("TaskProcessDiedMessage") : {
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			TaskProcessDiedMessage died(ri);
			LOG("TaskProcessDied", ("received TaskProcessDied for %s:%i", died.getProcessName().c_str(), died
					.getProcessId()));;
			if (died.getProcessName().find("SwgGameServer") != std::string::npos) {
				LOG("TaskProcessDied", ("Dead process %i is a game server", died.getProcessId()));
				// extract sceneId
				size_t pos = died.getProcessName().find("sceneID=");
				if (pos != std::string::npos) {
					pos += std::string("sceneID=").length();
					size_t end = died.getProcessName().find_first_of(' ', pos);
					if (end != std::string::npos) {
						std::string scene = died.getProcessName().substr(pos, end - pos);
						LOG("TaskProcessDied", ("Dead game server process %i was running sceneID %s, advising planet server", died
								.getProcessId(), scene.c_str()));
						std::map<std::string, PlanetServerConnection *>::iterator f = m_planetServers.find(scene);
						if (f != m_planetServers.end()) {
							f->second->send(died, true);
						}
					}
				}
			}
			break;
		}
		case constcrc("SystemTimeMismatchNotification") : {
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			GenericValueTypeMessage <std::string> msg(ri);

			m_lastTimeSystemTimeMismatchNotification = time(0);
			m_lastTimeSystemTimeMismatchNotificationDescription = msg.getValue();
			break;
		}
		case constcrc("DisconnectedTaskManagerMessage") : {
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			GenericValueTypeMessage <std::string> msg(ri);

			m_disconnectedTaskManagerList = msg.getValue();
			break;
		}
		case constcrc("TransferServerConnectionClosed") : {
			// connect to the character transfer server
			if (ConfigCentralServer::getTransferServerPort()) {
				if (s_retryTransferServerConnection) {
					getInstance()
							.m_transferServerConnection = new TransferServerConnection(ConfigCentralServer::getTransferServerAddress(), ConfigCentralServer::getTransferServerPort());
				}
			}
			break;
		}
		case constcrc("StationPlayersCollectorConnectionClosed") : {
			// connect to the station players collector
			if (ConfigCentralServer::getStationPlayersCollectorPort()) {
				if (s_retryStationPlayersCollectorConnection) {
					getInstance()
							.m_stationPlayersCollectorConnection = new StationPlayersCollectorConnection(ConfigCentralServer::getStationPlayersCollectorAddress(), ConfigCentralServer::getStationPlayersCollectorPort());
				}
			}
			break;
		}
		case constcrc("ClaimRewardsMessage") : {
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			ClaimRewardsMessage msg(ri);
			sendToArbitraryLoginServer(msg);
			break;
		}
		case constcrc("ClaimRewardsReplyMessage") : {
			DEBUG_REPORT_LOG(true, ("Central got ClaimRewardsReplyMessage\n"));
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			ClaimRewardsReplyMessage msg(ri);
			sendToGameServer(msg.getGameServer(), msg, true);
			break;
		}
		case constcrc("PurgeStructuresForAccountMessage") :
		case constcrc("WarnStructuresAboutPurgeMessage") : {
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			GenericValueTypeMessage <StationId> msg(ri);
			LoginServerConnection const *l = dynamic_cast<LoginServerConnection const *>(&source);
			if (l) {
				m_purgeAccountToLoginServerMap[msg.getValue()] = l
						->getProcessId(); // remember which login server is handling this purge
				sendToDBProcess(msg, true);
			}
			break;
		}
		case constcrc("PurgeCompleteMessage") : {
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			GenericValueTypeMessage <StationId> msg(ri);

			std::map<StationId, uint32>::iterator i = m_purgeAccountToLoginServerMap.find(msg.getValue());
			if (i != m_purgeAccountToLoginServerMap.end()) {
				sendToLoginServer(i->second, msg);
				m_purgeAccountToLoginServerMap.erase(i);
			}
			break;
		}
		case constcrc("RestartServerMessage") : {
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			RestartServerMessage msg(ri);

			sendToPlanetServer(msg.getScene(), msg, true);
			break;
		}
		case constcrc("RestartServerByRoleMessage") : {
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			GenericValueTypeMessage <std::pair<std::string, uint32>> msg(ri);

			sendToPlanetServer(msg.getValue().first, msg, true);
			break;
		}
		case constcrc("ExcommunicateGameServerMessage") : {
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			ExcommunicateGameServerMessage msg(ri);

			excommunicateServer(msg);
			break;
		}
		case constcrc("RestartPlanetMessage") : {
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			GenericValueTypeMessage <std::string> msg(ri);

			GenericValueTypeMessage<int> const shutdownMsg("ShutdownMessage", 0);
			sendToPlanetServer(msg.getValue(), shutdownMsg, true);
			break;
		}
		case constcrc("UpdateClusterLockedAndSecretState") : {
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			GenericValueTypeMessage <std::pair<bool, bool>> const msg(ri);

			gs_clusterIsLocked = msg.getValue().first;
			gs_clusterIsSecret = msg.getValue().second;
			break;
		}
		case constcrc("PopStatRsp") : {
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			GenericValueTypeMessage <std::map<std::string, int>> const msg(ri);

			m_timePopulationStatisticsRefresh = ::time(nullptr);
			m_populationStatistics = msg.getValue();
			break;
		}
		case constcrc("GcwScoreStatRsp") : {
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			GenericValueTypeMessage < std::pair < std::map < std::string, int >, std::pair < std::map < std::string,
					std::pair < int64, int64 > >, std::map < std::string, std::pair < int64, int64 > > > > >
			const msg(ri);

			m_timeGcwScoreStatisticsRefresh = ::time(nullptr);
			std::string const timeGcwScoreStatisticsRefreshStr = CalendarTime::convertEpochToTimeStringLocal(m_timeGcwScoreStatisticsRefresh);

			std::map<std::string, int> const &gcwImperialScorePercentile = msg.getValue().first;
			std::map <std::string, std::pair<int64, int64>> const &gcwImperialScore = msg.getValue().second.first;
			std::map <std::string, std::pair<int64, int64>> const &gcwRebelScore = msg.getValue().second.second;
			std::map < std::string, std::pair < int64, int64 > > ::const_iterator
			iterFind;
			std::map<std::string, int>::const_iterator iterFindImperialScorePercentile;
			std::pair < std::map < std::string, std::pair < int, std::pair < std::string, std::string > > > ::iterator,
					bool > iterInsertion;
			int imperialScorePercentile;
			std::string scoreText, scoreTextDesc;

			for (std::map < std::string, std::pair < int64, int64 > > ::const_iterator iterImp = gcwImperialScore
					.begin(); iterImp != gcwImperialScore.end();
			++iterImp)
			{
				iterFindImperialScorePercentile = gcwImperialScorePercentile.find(iterImp->first);
				if (iterFindImperialScorePercentile != gcwImperialScorePercentile.end()) {
					imperialScorePercentile = iterFindImperialScorePercentile->second;
				}
				else {
					imperialScorePercentile = 0;
				}

				iterFind = gcwRebelScore.find(iterImp->first);
				if (iterFind != gcwRebelScore.end()) {
					scoreText = FormattedString<1024>()
							.sprintf("Imp (%s, %s) Reb (%s, %s) - ", NetworkId(iterImp->second.first).getValueString()
							                                                                         .c_str(), NetworkId(iterImp
									->second.second).getValueString().c_str(), NetworkId(iterFind->second.first)
									.getValueString().c_str(), NetworkId(iterFind->second.second).getValueString()
							                                                                     .c_str());
				}
				else {
					scoreText = FormattedString<1024>()
							.sprintf("Imp (%s, %s) Reb (0, 0) - ", NetworkId(iterImp->second.first).getValueString()
							                                                                       .c_str(), NetworkId(iterImp
									->second.second).getValueString().c_str());
				}

				scoreTextDesc = scoreText + timeGcwScoreStatisticsRefreshStr;

				iterInsertion = m_gcwScoreStatistics.insert(std::make_pair("gcwScore." +
				                                                           iterImp->first, std::make_pair(imperialScorePercentile, std::make_pair(scoreText, scoreTextDesc))));
				if (!iterInsertion.second) {
					if (iterInsertion.first->second.second.first != scoreText) {
						iterInsertion.first->second.first = imperialScorePercentile;
						iterInsertion.first->second.second.first = scoreText;
						iterInsertion.first->second.second.second = scoreTextDesc;
					}
				}
			}

			for (std::map < std::string, std::pair < int64, int64 > > ::const_iterator iterReb = gcwRebelScore.begin();
					iterReb != gcwRebelScore.end();
			++iterReb)
			{
				iterFind = gcwImperialScore.find(iterReb->first);
				if (iterFind == gcwImperialScore.end()) {
					iterFindImperialScorePercentile = gcwImperialScorePercentile.find(iterReb->first);
					if (iterFindImperialScorePercentile != gcwImperialScorePercentile.end()) {
						imperialScorePercentile = iterFindImperialScorePercentile->second;
					}
					else {
						imperialScorePercentile = 0;
					}

					scoreText = FormattedString<1024>()
							.sprintf("Imp (0, 0) Reb (%s, %s) - ", NetworkId(iterReb->second.first).getValueString()
							                                                                       .c_str(), NetworkId(iterReb
									->second.second).getValueString().c_str());
					scoreTextDesc = scoreText + timeGcwScoreStatisticsRefreshStr;

					iterInsertion = m_gcwScoreStatistics.insert(std::make_pair("gcwScore." +
					                                                           iterReb->first, std::make_pair(imperialScorePercentile, std::make_pair(scoreText, scoreTextDesc))));
					if (!iterInsertion.second) {
						if (iterInsertion.first->second.second.first != scoreText) {
							iterInsertion.first->second.first = imperialScorePercentile;
							iterInsertion.first->second.second.first = scoreText;
							iterInsertion.first->second.second.second = scoreTextDesc;
						}
					}
				}
			}
			break;
		}
		case constcrc("LLTStatRsp") : {
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			GenericValueTypeMessage < std::pair < std::map < int, std::pair < std::string, int > >, std::map < int,
					std::pair < std::string, int > > > >
			const msg(ri);

			m_timeLastLoginTimeStatisticsRefresh = ::time(nullptr);
			m_lastLoginTimeStatistics = msg.getValue().first;
			m_createTimeStatistics = msg.getValue().second;
			break;
		}
		case constcrc("LfgStatRsp") : {
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			GenericValueTypeMessage <std::pair<unsigned long, std::pair<unsigned long, unsigned long> >> const msg(ri);

			m_numberOfCharacterMatchRequests += msg.getValue().first;
			m_numberOfCharacterMatchResults += msg.getValue().second.first;
			m_timeSpentOnCharacterMatchRequestsMs += msg.getValue().second.second;
			break;
		}
		case constcrc("OccupyUnlockedSlotRsp") : {
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			GenericValueTypeMessage < std::pair < std::pair < int, NetworkId >, uint32 > >
			const occupyUnlockedSlotRsp(ri);

			// CS log the response
			LoginUpgradeAccountMessage::OccupyUnlockedSlotResponse const response = static_cast<LoginUpgradeAccountMessage::OccupyUnlockedSlotResponse>(occupyUnlockedSlotRsp
					.getValue().first.first);
			if (response == LoginUpgradeAccountMessage::OUSR_success) {
				LOG("CustomerService", ("JediUnlockedSlot:Player %s OccupyUnlockedSlot request SUCCESS", occupyUnlockedSlotRsp
						.getValue().first.second.getValueString().c_str()));
			}
			else if (response == LoginUpgradeAccountMessage::OUSR_db_error) {
				LOG("CustomerService", ("JediUnlockedSlot:Player %s OccupyUnlockedSlot request FAILED - internal db error", occupyUnlockedSlotRsp
						.getValue().first.second.getValueString().c_str()));
			}
			else if (response == LoginUpgradeAccountMessage::OUSR_account_has_no_unlocked_slot) {
				LOG("CustomerService", ("JediUnlockedSlot:Player %s OccupyUnlockedSlot request FAILED - account doesn't have an unlocked slot", occupyUnlockedSlotRsp
						.getValue().first.second.getValueString().c_str()));
			}
			else if (response == LoginUpgradeAccountMessage::OUSR_account_has_no_unoccupied_unlocked_slot) {
				LOG("CustomerService", ("JediUnlockedSlot:Player %s OccupyUnlockedSlot request FAILED - account has no unoccupied unlocked slot", occupyUnlockedSlotRsp
						.getValue().first.second.getValueString().c_str()));
			}
			else if (response == LoginUpgradeAccountMessage::OUSR_cluster_already_has_unlocked_slot_character) {
				LOG("CustomerService", ("JediUnlockedSlot:Player %s OccupyUnlockedSlot request FAILED - cluster already has an unlocked slot character", occupyUnlockedSlotRsp
						.getValue().first.second.getValueString().c_str()));
			}
			else {
				LOG("CustomerService", ("JediUnlockedSlot:Player %s OccupyUnlockedSlot request FAILED - unknown result code (%d)", occupyUnlockedSlotRsp
						.getValue().first.second.getValueString().c_str(), occupyUnlockedSlotRsp.getValue().first
				                                                                                .first));
			}

			GameServerConnection *gs = getGameServer(occupyUnlockedSlotRsp.getValue().second);
			if (!gs) {
				gs = getRandomGameServer();
			}

			if (gs) {
				gs->send(occupyUnlockedSlotRsp, true);
			}
			break;
		}
		case constcrc("VacateUnlockedSlotRsp") : {
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			GenericValueTypeMessage < std::pair < std::pair < int, NetworkId >, std::pair < uint32, uint32 > > >
			const vacateUnlockedSlotRsp(ri);

			// CS log the response
			LoginUpgradeAccountMessage::VacateUnlockedSlotResponse const response = static_cast<LoginUpgradeAccountMessage::VacateUnlockedSlotResponse>(vacateUnlockedSlotRsp
					.getValue().first.first);
			if (response == LoginUpgradeAccountMessage::VUSR_success) {
				LOG("CustomerService", ("JediUnlockedSlot:Player %s VacateUnlockedSlot request SUCCESS", vacateUnlockedSlotRsp
						.getValue().first.second.getValueString().c_str()));

				// to safeguard against any sort of timing exploit to create another normal
				// slot character while this one is being converted to normal, thus allowing
				// the account to have more normal slot characters on this cluster than is
				// allowed, make it look like a normal character has been created on this
				// account on this cluster, and that mechanism will prevent such an exploit

				// let all connection servers know that a new character has been created for the station account
				GenericValueTypeMessage <StationId> const ncc("NewCharacterCreated", static_cast<StationId>(vacateUnlockedSlotRsp
						.getValue().second.first));
				CentralServer::getInstance().sendToAllConnectionServers(ncc, true);
			}
			else if (response == LoginUpgradeAccountMessage::VUSR_db_error) {
				LOG("CustomerService", ("JediUnlockedSlot:Player %s VacateUnlockedSlot request FAILED - internal db error", vacateUnlockedSlotRsp
						.getValue().first.second.getValueString().c_str()));
			}
			else if (response == LoginUpgradeAccountMessage::VUSR_account_has_no_unlocked_slot) {
				LOG("CustomerService", ("JediUnlockedSlot:Player %s VacateUnlockedSlot request FAILED - account doesn't have an unlocked slot", vacateUnlockedSlotRsp
						.getValue().first.second.getValueString().c_str()));
			}
			else if (response == LoginUpgradeAccountMessage::VUSR_not_unlocked_slot_character) {
				LOG("CustomerService", ("JediUnlockedSlot:Player %s VacateUnlockedSlot request FAILED - character is not an unlocked slot character", vacateUnlockedSlotRsp
						.getValue().first.second.getValueString().c_str()));
			}
			else if (response == LoginUpgradeAccountMessage::VUSR_no_available_normal_character_slot) {
				LOG("CustomerService", ("JediUnlockedSlot:Player %s VacateUnlockedSlot request FAILED - no available normal character slot for the account on this galaxy", vacateUnlockedSlotRsp
						.getValue().first.second.getValueString().c_str()));
			}
			else {
				LOG("CustomerService", ("JediUnlockedSlot:Player %s VacateUnlockedSlot request FAILED - unknown result code (%d)", vacateUnlockedSlotRsp
						.getValue().first.second.getValueString().c_str(), vacateUnlockedSlotRsp.getValue().first
				                                                                                .first));
			}

			GameServerConnection *gs = getGameServer(vacateUnlockedSlotRsp.getValue().second.second);
			if (!gs) {
				gs = getRandomGameServer();
			}

			if (gs) {
				gs->send(vacateUnlockedSlotRsp, true);
			}
			break;
		}
		case constcrc("SwapUnlockedSlotRsp") : {
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			GenericValueTypeMessage < std::pair < std::pair < int, NetworkId >, std::pair < uint32, std::pair <
			                                                                                        NetworkId,
					std::string > > > >
			const swapUnlockedSlotRsp(ri);

			// CS log the response
			LoginUpgradeAccountMessage::SwapUnlockedSlotResponse const response = static_cast<LoginUpgradeAccountMessage::SwapUnlockedSlotResponse>(swapUnlockedSlotRsp
					.getValue().first.first);
			if (response == LoginUpgradeAccountMessage::SUSR_success) {
				LOG("CustomerService", ("JediUnlockedSlot:Player %s SwapUnlockedSlot with %s (%s) request SUCCESS", swapUnlockedSlotRsp
						.getValue().first.second.getValueString().c_str(), swapUnlockedSlotRsp.getValue().second.second
				                                                                              .first.getValueString()
				                                                                              .c_str(), swapUnlockedSlotRsp
						.getValue().second.second.second.c_str()));
			}
			else if (response == LoginUpgradeAccountMessage::SUSR_db_error) {
				LOG("CustomerService", ("JediUnlockedSlot:Player %s SwapUnlockedSlot with %s request FAILED - internal db error", swapUnlockedSlotRsp
						.getValue().first.second.getValueString().c_str(), swapUnlockedSlotRsp.getValue().second.second
				                                                                              .first.getValueString()
				                                                                              .c_str()));
			}
			else if (response == LoginUpgradeAccountMessage::SUSR_account_has_no_unlocked_slot) {
				LOG("CustomerService", ("JediUnlockedSlot:Player %s SwapUnlockedSlot with %s request FAILED - account doesn't have an unlocked slot", swapUnlockedSlotRsp
						.getValue().first.second.getValueString().c_str(), swapUnlockedSlotRsp.getValue().second.second
				                                                                              .first.getValueString()
				                                                                              .c_str()));
			}
			else if (response == LoginUpgradeAccountMessage::SUSR_not_unlocked_slot_character) {
				LOG("CustomerService", ("JediUnlockedSlot:Player %s SwapUnlockedSlot with %s request FAILED - source character is not an unlocked slot character", swapUnlockedSlotRsp
						.getValue().first.second.getValueString().c_str(), swapUnlockedSlotRsp.getValue().second.second
				                                                                              .first.getValueString()
				                                                                              .c_str()));
			}
			else if (response == LoginUpgradeAccountMessage::SUSR_invalid_target_character) {
				LOG("CustomerService", ("JediUnlockedSlot:Player %s SwapUnlockedSlot with %s request FAILED - target character is either not valid, not on the same account, or not on this galaxy", swapUnlockedSlotRsp
						.getValue().first.second.getValueString().c_str(), swapUnlockedSlotRsp.getValue().second.second
				                                                                              .first.getValueString()
				                                                                              .c_str()));
			}
			else if (response == LoginUpgradeAccountMessage::SUSR_target_character_already_unlocked_slot_character) {
				LOG("CustomerService", ("JediUnlockedSlot:Player %s SwapUnlockedSlot with %s request FAILED - target character is already an unlocked slot character", swapUnlockedSlotRsp
						.getValue().first.second.getValueString().c_str(), swapUnlockedSlotRsp.getValue().second.second
				                                                                              .first.getValueString()
				                                                                              .c_str()));
			}
			else {
				LOG("CustomerService", ("JediUnlockedSlot:Player %s SwapUnlockedSlot with %s request FAILED - unknown result code (%d)", swapUnlockedSlotRsp
						.getValue().first.second.getValueString().c_str(), swapUnlockedSlotRsp.getValue().second.second
				                                                                              .first.getValueString()
				                                                                              .c_str(), swapUnlockedSlotRsp
						.getValue().first.first));
			}

			GameServerConnection *gs = getGameServer(swapUnlockedSlotRsp.getValue().second.first);
			if (!gs) {
				gs = getRandomGameServer();
			}

			if (gs) {
				gs->send(swapUnlockedSlotRsp, true);
			}
			break;
		}
		case constcrc("AdjustAccountFeatureIdResponse") : {
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			AdjustAccountFeatureIdResponse const msg(ri);

			GameServerConnection *gs = getGameServer(msg.getGameServer());
			if (!gs) {
				gs = getRandomGameServer();
			}

			if (gs) {
				gs->send(msg, true);
			}
			break;
		}
		case constcrc("AccountFeatureIdResponse") : {
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			AccountFeatureIdResponse const msg(ri);

			GameServerConnection *gs = getGameServer(msg.getGameServer());
			if (!gs) {
				gs = getRandomGameServer();
			}

			if (gs) {
				gs->send(msg, true);
			}
			break;
		}
		case constcrc("FeatureIdTransactionResponse") : {
			Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
			FeatureIdTransactionResponse const msg(ri);

			GameServerConnection *gs = getGameServer(msg.getGameServer());
			if (!gs) {
				gs = getRandomGameServer();
			}

			if (gs) {
				gs->send(msg, true);
			}

			break;
		}
		default :
			if (ClusterWideDataManagerList::handleMessage(source, message)) {
				// nothing else to do with the message since it was
				// handled by the Cluster wide data manager
			}
			break;
	}
}

//-----------------------------------------------------------------------
void CentralServer::removeConnectionServerConnection(const ConnectionServerConnection * conn)
{
	DEBUG_REPORT_LOG(true, ("Removing connection server connection\n"));
	if (conn)
	{
		startConnectionServer(conn->getConnectionServerNumber(),
							  conn->getGameServiceAddress(),
							  conn->getClientServicePortPublic(),
							  conn->getClientServicePortPrivate(), true,
							  ConfigCentralServer::getConnectionServerRestartDelayTimeSeconds());

		ConnectionServerConnectionList::iterator i = m_connectionServerConnections.begin();
		for(;i != m_connectionServerConnections.end();++i)
		{
			if (conn->getId() == (*i)->getId())
			{
				ConnectionServerDown c((*i)->getId());
				sendToAllLoginServers(c);
				IGNORE_RETURN(m_connectionServerConnections.erase(i));
				return;
			}

		}

		// Clean up all the account info for the connection
		removeFromAccountConnectionMap(conn->getId());
	}
}
//-----------------------------------------------------------------------
/**
* Removes all references to a game server from the server.
*
* @param gameServer		the game server connection to remove
*/
void CentralServer::removeGameServer(GameServerConnection const *gameServer)
{
	if (gameServer != nullptr)
	{
		uint32 const pid = gameServer->getProcessId();

		LOG("ServerStartup", ("Game Server %lu went down", pid));

		//@todo this whole function needs a re-write.
		NOT_NULL(gameServer);
		std::map<uint32, GameServerConnection *>::iterator const i = m_gameServerConnections.find(pid);
		if (i == m_gameServerConnections.end())
			return;

		for (std::vector<GameServerConnection *>::iterator ii = m_gameServerConnectionsList.begin(); ii != m_gameServerConnectionsList.end();)
		{
			if ((*ii) == i->second)
				ii = m_gameServerConnectionsList.erase(ii);
			else
				++ii;
		}

		m_gameServerConnections.erase(i);

		/** @todo: this is slow, find a better way (probably by creating reverse
		* lookup table(s))
		*/
		if (pid == m_dbProcessServerProcessId)
		{
			DEBUG_REPORT_LOG(true, ("Database process died -- Central will exit and let the cluster restart\n"));
			m_done = true;
			return; //lint !e527 Unreachable
		}

		DEBUG_WARNING(true, ("Game server %lu crashed", pid));

		for (SceneGameMap::iterator j = m_gameServers.begin(); j != m_gameServers.end();)
		{
			if ((*j).second == gameServer)
				m_gameServers.erase(j++);
			else
				++j;
		}

		UniverseManager::getInstance().onGameServerDisconnect(*gameServer);
		PlanetManager::onGameServerDisconnect(gameServer);
		CharacterCreationTracker::getInstance().onGameServerDisconnect(pid);
		ClusterWideDataManagerList::onGameServerDisconnect(pid);

		ExcommunicateGameServerMessage const excommunicateMessage(pid, 0, "");
		sendToAllGameServersExceptDBProcess(excommunicateMessage, true);
	}
	else 
	{
		DEBUG_WARNING(true, ("A game server crashed but our process ID ptr is nullptr."));
	}
}

//-----------------------------------------------------------------------

void CentralServer::done()
{
	getInstance().m_done = true;
}

//-----------------------------------------------------------------------

void CentralServer::run(void)
{
	SetupSharedLog::install("CentralServer");
	SetupSharedUtility::Data utilityData;
	SetupSharedUtility::install (utilityData);
	SetupServerUtility::install();

	gs_connectionServersPublic = ConfigCentralServer::getStartPublic();
	// listen for game server connect requests
	static CentralServer & cserver = getInstance();

	NetworkSetupData setup;
	setup.maxConnections = 100;
	setup.bindInterface = ConfigCentralServer::getGameServiceBindInterface();
	setup.port = ConfigCentralServer::getGameServicePort();
	Service * gs = new Service(ConnectionAllocator<GameServerConnection>(), setup);
	cserver.m_gameService = gs;

	// listen for planet server connection requests
	setup.bindInterface = ConfigCentralServer::getPlanetServiceBindInterface();
	setup.port = ConfigCentralServer::getPlanetServicePort();
	Service * ps = new Service(ConnectionAllocator<PlanetServerConnection>(), setup);
	cserver.m_planetService = ps;

	setup.port = ConfigCentralServer::getChatServicePort();
	setup.maxConnections = 32;
	setup.bindInterface = ConfigCentralServer::getChatServiceBindInterface();
	Service * cs = new Service(ConnectionAllocator<ChatServerConnection>(), setup);
	cserver.m_chatService = cs;

	setup.port = ConfigCentralServer::getCustomerServicePort();
	setup.bindInterface = ConfigCentralServer::getCustomerServiceBindInterface();
	Service * css = new Service(ConnectionAllocator<CustomerServiceConnection>(), setup);
	cserver.m_csService = css;

	setup.port = ConfigCentralServer::getConnectionServicePort();
	setup.bindInterface = ConfigCentralServer::getConnectionServiceBindInterface();
	Service * cons = new Service(ConnectionAllocator<ConnectionServerConnection>(), setup);
	NOT_NULL(cons);
	cserver.m_connService = cons;

	setup.port = ConfigCentralServer::getConsoleServicePort();
	setup.bindInterface = ConfigCentralServer::getConsoleServiceBindInterface();
	cserver.m_consoleService = new Service(ConnectionAllocator<ConsoleConnection>(), setup);

	setup.port = ConfigCentralServer::getCommodityServerServicePort();
	setup.bindInterface = ConfigCentralServer::getCommodityServerServiceBindInterface();
	s_commodityServerService = new Service(ConnectionAllocator<ServerConnection>(), setup);
	
	setup.port = ConfigCentralServer::getLoginServicePort();
	if (ConfigCentralServer::getDevelopmentMode())
		cserver.connectToLoginServer();
	else
	{
		setup.bindInterface = ConfigCentralServer::getLoginServiceBindInterface();
		cserver.m_loginService = new Service(ConnectionAllocator<LoginServerConnection>(), setup);
	}

	// connect to the task manager
	cserver.m_taskManager = new TaskConnection("127.0.0.1", ConfigCentralServer::getTaskManagerPort());
	
	unsigned long startTime = Clock::timeMs();
	unsigned long nextLoadingLogTime=0;
	unsigned long nextPingTime=0;
	unsigned long nextPopulationLogTime = 0;

	LOG("ServerStartup",("CentralServer starting"));
#ifndef WIN32
	if( FileExists( ".shutdown") )
	{
		LOG("CentralServer", ("Removing stale .shutdown file."));
		IGNORE_RETURN(::remove( ".shutdown" ));
	}
	if( FileExists( ".abortshutdown" ) )
	{
		LOG("CentralServer", ("Removing stale .abortshutdown file."));
		IGNORE_RETURN(::remove( ".abortshutdown" ));
	}
	if( FileExists( ".startanymissingplanet") )
	{
		LOG("CentralServer", ("Removing stale .startanymissingplanet file."));
		IGNORE_RETURN(::remove( ".startanymissingplanet" ));
	}
	if( FileExists( ".startanymissinggameserver") )
	{
		LOG("CentralServer", ("Removing stale .startanymissinggameserver file."));
		IGNORE_RETURN(::remove( ".startanymissinggameserver" ));
	}
#endif

	while (!cserver.m_done)
	{
		unsigned long lastFrameTime = 0;
		unsigned long frameStartTime = Clock::timeMs();
		
		PROFILER_AUTO_BLOCK_DEFINE("main loop");

		bool barrierReached = true;

		do
		{
			PROFILER_AUTO_BLOCK_DEFINE("BarrierWait");

			if (!Os::update())
				cserver.m_done = true;

			{
				PROFILER_AUTO_BLOCK_DEFINE("NetworkHandler::update");
				NetworkHandler::update();
			}

			{
				PROFILER_AUTO_BLOCK_DEFINE("MetricsManager::update");
				unsigned long curTime = Clock::timeMs();
				MetricsManager::update(static_cast<float>(curTime - startTime));
				startTime = curTime;
			}

			if (ConfigCentralServer::getShouldSleep())
			{
				PROFILER_AUTO_BLOCK_DEFINE("Os::sleep");
				Os::sleep(1);
			}

		} while (!barrierReached && !cserver.m_done);

		//@todo Central needs to run a clock so we can schedule re-tries with the login server.
		{
			PROFILER_AUTO_BLOCK_DEFINE("NetworkHandler::dispatch");
			NetworkHandler::dispatch();
		}

		{
			PROFILER_AUTO_BLOCK_DEFINE("update");
			cserver.update();
		}

		NetworkHandler::clearBytesThisFrame();

		{
			//Peridically log loading status
			unsigned long curTime = Clock::timeMs();
			if (curTime > nextLoadingLogTime)
			{
				nextLoadingLogTime = curTime + 10000;
				for (std::set<std::string>::const_iterator i=getInstance().m_planetsWaitingForPreload.begin(); i!=getInstance().m_planetsWaitingForPreload.end(); ++i)
					LOG("Preload",("Waiting for planet %s",i->c_str()));
			}

			//Perodically ping all the servers
			if (ConfigCentralServer::getServerPingTimeout() != 0 && static_cast<int>(curTime-nextPingTime) > 0)
			{
				nextPingTime = curTime + static_cast<uint32>(ConfigCentralServer::getServerPingTimeout() * 1000);
				getInstance().doServerPings();
			}

			//Periodically write out population to a log every 5 minutes
			if (curTime > nextPopulationLogTime)
			{
				nextPopulationLogTime = curTime + 300000;
				LOG("PopulationLog", ("Current Population: %d", getInstance().getPlayerCount()));
			}
		}




		ServerClock::getInstance().incrementServerFrame();
		if(ConfigCentralServer::getShutdown())
		{
			cserver.done();
		}

		unsigned long currentTime = Clock::timeMs();
		lastFrameTime = currentTime - frameStartTime;
		if(lastFrameTime > 1000)
		{
			LOG("profile", ("Long loop (%u ms):\n%s", lastFrameTime, PROFILER_GET_LAST_FRAME_DATA()));
		}
	}

	LOG("ServerStartup",("CentralServer exiting"));

	SetupSharedLog::remove();
	CentralServer::remove();
}

// ----------------------------------------------------------------------
/**
 * Called every frame to do any required updates.
 */
void CentralServer::update()
{
	static int loopCount = 0;
	static int apiLoopCount = 0;
	static int shutdownCheckLoopCount = 0;

	m_curTime = static_cast<uint32>(time(0));
	
	// Tell the LoginServers if necessary
	if ((++loopCount > ConfigCentralServer::getUpdatePlayerCountFrequency()))
	{
		loopCount = 0;

		// Update the population on the server
		sendPopulationUpdateToLoginServer();
	}


	// update the webAPI if specified
	int webUpdateIntervalSeconds = ConfigCentralServer::getWebUpdateIntervalSeconds();

	// assuming that every 5th frame is ~1 second, we can multiply and then check
	if (webUpdateIntervalSeconds && (++apiLoopCount > (webUpdateIntervalSeconds*1000)) )
	{
		apiLoopCount = 0;

		// update the web api
		sendMetricsToWebAPI();
	}

	if ( ConfigCentralServer::getAuctionEnabled() ) // allow auctions?
	{
		if ( m_pAuctionTransferClient == nullptr )
		{
               		const char* hostName[1] = { ConfigCentralServer::getAuctionServer() };
               		const short port[1] = { (short)ConfigCentralServer::getAuctionPort() };

               		std::string s_id = ConfigCentralServer::getAuctionIDPrefix();
			s_id += ConfigCentralServer::getClusterName(); 

       			const char *identifier[1];
               		identifier[ 0 ] = s_id.c_str();

               		m_pAuctionTransferClient = new AuctionTransferClient( hostName, port, 1, identifier, 1 );
		}
		m_pAuctionTransferClient->process();
  	}
	else if ( m_pAuctionTransferClient )
	{
		delete( m_pAuctionTransferClient );
		m_pAuctionTransferClient = 0;
	}

	// check every 5th frame (one second roughly?)
	if ( ++shutdownCheckLoopCount > 5 )
	{
		shutdownCheckLoopCount = 0;

		checkShutdownProcess();
	}

	// update cluster wide data manager
	ClusterWideDataManagerList::update(Clock::frameTime());
}

// ----------------------------------------------------------------------
void CentralServer::sendPopulationUpdateToLoginServer()
{
	// Add up all the population totals (these are referenced by the metrics data)
	m_totalPlayerCount        = 0;
	m_totalFreeTrialCount     = 0;
	m_totalEmptySceneCount    = 0;
	m_totalTutorialSceneCount = 0;
	m_totalFalconSceneCount   = 0;

	
	ConnectionServerConnectionList::const_iterator i;
	for (i=m_connectionServerConnections.begin(); i!=m_connectionServerConnections.end(); ++i)
	{
		if (*i)
		{
			m_totalPlayerCount        += (**i).getPlayerCount();
			m_totalFreeTrialCount     += (**i).getFreeTrialCount();
			m_totalEmptySceneCount    += (**i).getEmptySceneCount();
			m_totalTutorialSceneCount += (**i).getTutorialSceneCount();
			m_totalFalconSceneCount   += (**i).getFalconSceneCount();
		}
	}

	bool loadedRecently=false;
	if (!isPreloadFinished() || (time(0)-m_lastLoadingStateTime < static_cast<time_t>(ConfigCentralServer::getRecentLoadingStateSeconds())))
		loadedRecently=true;

	UpdatePlayerCountMessage upm(loadedRecently, m_totalPlayerCount, m_totalFreeTrialCount, m_totalEmptySceneCount, m_totalTutorialSceneCount, m_totalFalconSceneCount);
	sendToAllLoginServers(upm);
}

void CentralServer::sendMetricsToWebAPI()
{
	static const std::string metricsURL(ConfigCentralServer::getMetricsDataURL());
	
	if (!metricsURL.empty())
	{
		// create the object
		webAPI api(metricsURL);

		// add our data
		api.addJsonData<int>("totalPlayerCount", m_totalPlayerCount);
		api.addJsonData<int>("totalGameServers", (m_gameServers.size() - 1));
		api.addJsonData<int>("totalPlanetServers", m_planetServers.size());
		api.addJsonData<int>("totalTutorialSceneCount", m_totalTutorialSceneCount);
		
#ifdef _DEBUG
		if (api.submit()) {
			bool status = api.getNullableValue<bool>("status");
			
			if (status)
			{
				std::string message = api.getString("message");
				
				if (message.empty())
				{
					message = "No message returned.";
				}
				
				WARNING(true, ("Error sending stats: %s", message.c_str()));
			}
			
			WARNING(true, ("Success sending server stats to API."));
		}
		else
		{
			WARNING(true, ("Error sending stats."));
		}
#else
		api.submit();
#endif
	}
}

//-----------------------------------------------------------------------

void CentralServer::sendTaskMessage(const GameNetworkMessage & source)
{
	if(m_taskManager)
		m_taskManager->send(source, true);
	DEBUG_REPORT_LOG(!m_taskManager, ("There is no task manager connection, but app is attempting to send to one\n"));
}

//-----------------------------------------------------------------------

void CentralServer::sendToGameServer(const uint32 gameServerProcessId, const GameNetworkMessage & message, const bool reliable) const
{
	GameServerConnection * g = getGameServer(gameServerProcessId);
	if(g)
	{
		g->send(message, reliable);
	}
	else
	{
		DEBUG_WARNING(true,("Attempted to send to game server %i, without connection.\n",gameServerProcessId));
	}
}

// ----------------------------------------------------------------------

void CentralServer::sendToLoginServer(uint32 loginServerId, const GameNetworkMessage &message)
{
	LoginServerConnectionMapType::iterator i=m_loginServerConnectionMap.find(loginServerId);
	if (i!=m_loginServerConnectionMap.end() && i->second)
		i->second->send(message,true);
}

// ----------------------------------------------------------------------

void CentralServer::sendToAllGameServers(const GameNetworkMessage & message, const bool reliable)
{
	for (std::map<uint32, GameServerConnection *>::const_iterator i = m_gameServerConnections.begin(); i!=m_gameServerConnections.end(); ++i)
	{
		(*i).second->send(message, reliable);
	}
}

// ----------------------------------------------------------------------

void CentralServer::sendToAllGameServersExceptDBProcess(const GameNetworkMessage & message, const bool reliable)
{
	for (std::map<uint32, GameServerConnection *>::const_iterator i = m_gameServerConnections.begin(); i!=m_gameServerConnections.end(); ++i)
	{
		if ((*i).first != getDbProcessServerProcessId())
			(*i).second->send(message, reliable);
	}
}

// ----------------------------------------------------------------------

void CentralServer::sendToAllPlanetServers(const GameNetworkMessage & message, const bool reliable)
{
	for (std::map<std::string, PlanetServerConnection *>::const_iterator i = m_planetServers.begin(); i != m_planetServers.end(); ++i)
		(*i).second->send(message, reliable);
}

// ----------------------------------------------------------------------

bool CentralServer::hasPlanetServer(const std::string & sceneName) const
{
	for (std::map<std::string, PlanetServerConnection *>::const_iterator i = m_planetServers.begin(); i != m_planetServers.end(); ++i)
		if (i->second->getSceneId() == sceneName)
			return true;
	return false;
}

// ----------------------------------------------------------------------

void CentralServer::sendToPlanetServer(const std::string &sceneId, const GameNetworkMessage & message, const bool reliable)
{
	for (std::map<std::string, PlanetServerConnection *>::const_iterator i = m_planetServers.begin(); i != m_planetServers.end(); ++i)
		if (i->second->getSceneId() == sceneId)
			(*i).second->send(message, reliable);
}

// ----------------------------------------------------------------------

void CentralServer::sendToAllConnectionServers(const GameNetworkMessage & message, const bool reliable,  Connection const * exclude /*= nullptr*/)
{
	// send to all connection servers
	for (ConnectionServerConnectionList::const_iterator i = m_connectionServerConnections.begin(); i != m_connectionServerConnections.end(); ++i)
	{
		if ((*i) && (*i != exclude))
			(*i)->send(message, reliable);
	}
}

//-----------------------------------------------------------------------

void CentralServer::sendToDBProcess(const GameNetworkMessage & message, const bool reliable) const
{
	GameServerConnection * g = getGameServer(getDbProcessServerProcessId());
	if(g)
	{
		g->send(message, reliable);
	}
	else
	{
		DEBUG_FATAL(!m_done,("Attempted to send message to DBProcess without connection (there should always be a connection to DBProcess)."));
	}
}

// ----------------------------------------------------------------------

bool CentralServer::hasDBConnection() const
{
	const GameServerConnection * g = getGameServer(getDbProcessServerProcessId());
	return (g != nullptr);
}

//-----------------------------------------------------------------------

void CentralServer::setTaskManager(TaskConnection * newTaskManager)
{
	if (m_taskManager && newTaskManager != m_taskManager)
	{
		m_taskManager->setDisconnectReason("CentralServer::setTaskManager called");
		m_taskManager->disconnect();
	}
	m_taskManager = newTaskManager;
}

// ----------------------------------------------------------------------

void CentralServer::removePlanetServer(const PlanetServerConnection * p)
{
	for (std::map<std::string, PlanetServerConnection *>::iterator i = m_planetServers.begin(); i != m_planetServers.end();)
	{
		if ((*i).second == p)
		{
			DEBUG_REPORT_LOG(true,("Central lost connection to Planet Server %s\n",p->getSceneId().c_str()));
			i = m_planetServers.erase(i);

			if (isPreloadFinished())
				m_timeClusterWentIntoLoadingState = 0;
			else if (m_timeClusterWentIntoLoadingState <= 0)
				m_timeClusterWentIntoLoadingState = time(0);

			return;
		}
		else
		{
			++i;
		}
	}
}

// ----------------------------------------------------------------------

const std::string & CentralServer::getCommandLine() const
{
	return m_commandLine;
}

//-----------------------------------------------------------------------

void CentralServer::setCommandLine(const std::string & c)
{
	m_commandLine = c;
}

//-----------------------------------------------------------------------

const bool getStartLocation(const std::string & name, std::string & planetName, Vector & coordinates, NetworkId & cellId)
{
	const StartingLocationData * const sld = StartingLocationManager::findLocationByName (name);
	if (sld)
	{
		planetName    = sld->planet;
		coordinates.x = sld->x;
		coordinates.y = sld->y;
		coordinates.z = sld->z;
		cellId        = NetworkId (sld->cellId);
		return true;
	}

	REPORT_LOG(true, ("The start location \"%s\" could not be found in StartingLocationManager", name.c_str()));
	return false;
}

//-----------------------------------------------------------------------

void CentralServer::startPlanetServer(const std::string & host, const std::string & sceneId, const SpawnDelaySeconds spawnDelay)
{
	FATAL(sceneId.empty(), ("CentralServer::startPlanetServer: empty sceneId, host='%s'", host.c_str()));
	std::map<std::string, std::pair<std::pair<std::string, std::string>, time_t> >::const_iterator f = m_pendingPlanetServers.find(sceneId);
	if(f == m_pendingPlanetServers.end())
	{
		std::map<std::string, PlanetServerConnection *>::const_iterator pf = m_planetServers.find(sceneId);
		if(pf == m_planetServers.end())
		{
			std::string options = "-s PlanetServer centralServerAddress=";
			if(m_planetService)
			{
				options += m_planetService->getBindAddress();
			}
			else
			{
				options += NetworkHandler::getHostName();
			}
			options += " sceneID=";
			options += sceneId;

			char buffer[20];
			IGNORE_RETURN(_itoa(m_nextPlanetWatcherPort++,buffer,10));
			options += " watcherServicePort=";
			options += buffer;

			static unsigned int portBase = 0;
			IGNORE_RETURN(_itoa(portBase,buffer,10));
			portBase += 100;
			options += " gameServerDebuggingPortBase=";
			options += buffer;
			
			TaskSpawnProcess spawn(host.empty() ? std::string("any") : host, "PlanetServer", options, spawnDelay);
			CentralServer::getInstance().sendTaskMessage(spawn);
			m_pendingPlanetServers[sceneId] = std::make_pair(std::make_pair(host, options), ::time(nullptr));
			IGNORE_RETURN(m_planetsWaitingForPreload.insert(sceneId));

			bool const preloadFinished = isPreloadFinished();
			if (preloadFinished)
				m_timeClusterWentIntoLoadingState = 0;
			else if (m_timeClusterWentIntoLoadingState <= 0)
				m_timeClusterWentIntoLoadingState = time(0);

			if(getInstance().m_transferServerConnection != nullptr && !preloadFinished)
			{
				getInstance().m_transferServerConnection->disconnect();
				getInstance().m_transferServerConnection = 0;
				s_retryTransferServerConnection = false;
			}
			
			if(getInstance().m_stationPlayersCollectorConnection != nullptr && ! isPreloadFinished())
			{
				getInstance().m_stationPlayersCollectorConnection->disconnect();
				getInstance().m_stationPlayersCollectorConnection = 0;
				s_retryStationPlayersCollectorConnection = false;
			}

			PreloadFinishedMessage msg(preloadFinished);
			sendToAllLoginServers(msg);
		}
	}
}

//-----------------------------------------------------------------------
/**
* Forward the RequestGameServerForLoginMessage to the appropriate PlanetServer
*/
void CentralServer::handleRequestGameServerForLoginMessage(const RequestGameServerForLoginMessage & msg)
{
	static const std::string loginTrace("TRACE_LOGIN");

	std::string effectiveScene;
	PlayerSceneMapType::iterator i=m_playerSceneMap.find(msg.getCharacterId());
	if (i!=m_playerSceneMap.end())
	{
		effectiveScene=i->second.first;
		LOG(loginTrace, ("using sceneId (%s) from memory for character (%s)", effectiveScene.c_str(), msg.getCharacterId().getValueString().c_str()));
	}
	else
		effectiveScene=msg.getScene(); // only use the DB scene if we don't have more recent information

	// for CTS, the source character must currently be on one of the 10 original ground planets
	if (msg.getForCtsSourceCharacter())
	{
		static std::set<std::string> s_tenOriginalGroundPlanets;
		if (s_tenOriginalGroundPlanets.empty())
		{
			IGNORE_RETURN(s_tenOriginalGroundPlanets.insert("tatooine"));
			IGNORE_RETURN(s_tenOriginalGroundPlanets.insert("naboo"));
			IGNORE_RETURN(s_tenOriginalGroundPlanets.insert("corellia"));
			IGNORE_RETURN(s_tenOriginalGroundPlanets.insert("rori"));
			IGNORE_RETURN(s_tenOriginalGroundPlanets.insert("talus"));
			IGNORE_RETURN(s_tenOriginalGroundPlanets.insert("yavin4"));
			IGNORE_RETURN(s_tenOriginalGroundPlanets.insert("endor"));
			IGNORE_RETURN(s_tenOriginalGroundPlanets.insert("lok"));
			IGNORE_RETURN(s_tenOriginalGroundPlanets.insert("dantooine"));
			IGNORE_RETURN(s_tenOriginalGroundPlanets.insert("dathomir"));
		}

		if (s_tenOriginalGroundPlanets.count(effectiveScene) <= 0)
		{
			// fail CTS because source character is not on one of the 10 original ground planets
			LOG("CustomerService", ("CharacterTransfer: RequestGameServerForLoginMessage failed for source character (%s) stationId (%lu) because effective scene for the character (%s) is not one of the 10 original ground planets", msg.getCharacterId().getValueString().c_str(), msg.getStationId(), effectiveScene.c_str()));

			GenericValueTypeMessage<std::pair<NetworkId, unsigned int> > const failureMsg("CtsSrcCharWrongPlanet", std::make_pair(msg.getCharacterId(), msg.getStationId()));
			IGNORE_RETURN(ConnectionServerConnection::sendToPseudoClientConnection(msg.getStationId(), failureMsg));

			return;
		}
	}

	PlanetServerConnection *conn = PlanetManager::getPlanetServerForScene(effectiveScene);
	if (conn)
	{
		LOG(loginTrace, ("handling RequestGameServerForLoginMessage(%s)", msg.getCharacterId().getValueString().c_str()));
		conn->send(msg,true);
	}
	else
	{
		bool isPlanetValid = false;
		bool allowDynamicStart = ConfigFile::getKeyBool("CentralServer", "startPlanetsDynamically", false);

		std::map<std::string, std::string>::const_iterator f = ms_sceneToHostMap.find(msg.getScene());
		if(f != ms_sceneToHostMap.end())
		{
			isPlanetValid = true;
		}

		if(isPlanetValid || allowDynamicStart)
		{
			LOG(loginTrace, ("deferring RequestGameServerForLoginMessage(%s)", msg.getCharacterId().getValueString().c_str()));
			DEBUG_REPORT_LOG(true, ("Starting planet server for login"));
			startPlanetServer(getHostForScene(msg.getScene()), msg.getScene(), 0);
			m_messagesWaitingForPlanetServer.push_back(Archive::ByteStream());
			msg.pack(m_messagesWaitingForPlanetServer.back());
		}
		else
		{
			WARNING_STRICT_FATAL(true, ("Central received a request to start a planet (name=\"%s\") that is not in the startPlanet= list in the CentralServer config file.", msg.getScene().c_str()));
		}
	}
}

// ----------------------------------------------------------------------

void CentralServer::handleRequestSceneTransfer(const RequestSceneTransfer &msg)
{
	PlanetServerConnection *conn = PlanetManager::getPlanetServerForScene(msg.getSceneName());
	if (conn)
	{
		conn->send(msg,true);
	}
	else
	{
		DEBUG_REPORT_LOG(true, ("Starting planet server for login"));
		startPlanetServer(getHostForScene(msg.getSceneName()), msg.getSceneName(), 0);
		m_messagesWaitingForPlanetServer.push_back(Archive::ByteStream());
		msg.pack(m_messagesWaitingForPlanetServer.back());
	}
}

// ----------------------------------------------------------------------

/**
* Forward the GameServerForLoginMessage to the appropriate ConnectionServer
*/
void CentralServer::handleGameServerForLoginMessage(const GameServerForLoginMessage &msg)
{
	ConnectionServerConnection *conn = getConnectionServerForAccount(msg.getStationId());
	if (conn)
	{
		conn->send(msg,true);
	}
	else
	{
		if(! ConnectionServerConnection::sendToPseudoClientConnection(msg.getStationId(), msg))
		{
			LOG("TRACE_LOGIN", ("Trying to log account %i in, but could not determine which connection server to use.",msg.getStationId()));
		}
	}
}

// ----------------------------------------------------------------------

void CentralServer::handleExchangeListCreditsMessage(const ExchangeListCreditsMessage &msg)
{
	LOG("Exchange", ("Central Server got exchange list credits %d.",msg.getCredits()));
	if ( m_pAuctionTransferClient == nullptr )
	{
		// send failure packet
	}
	//////////////////////m_pAuctionTransferClient->addCoinToAuction( msg );
}





ConnectionServerConnection * CentralServer::getAnyConnectionServer()
{
	ConnectionServerConnection * result = 0;
	if(! m_connectionServerConnections.empty())
	{
		result = *(m_connectionServerConnections.begin());
		if(result)
		{
			int leastPlayers = result->getPlayerCount();

			std::vector<ConnectionServerConnection *>::iterator i;
			for(i = m_connectionServerConnections.begin(); i != m_connectionServerConnections.end();  ++i)
			{
				if((*i)->getPlayerCount() < leastPlayers)
					result = *i;
			}
		}
	}
	return result;
}

// ----------------------------------------------------------------------

ConnectionServerConnection * CentralServer::getConnectionServerForAccount(StationId suid)
{
	ConnectionServerConnection * result = nullptr;
	ConnectionServerSUIDMap::iterator i=m_accountConnectionMap.find(suid);
	if (i!=m_accountConnectionMap.end())
	{
		result = (*i).second;
	}
	else
	{
		result = ConnectionServerConnection::getConnectionForAccount(suid);
	}

	return result;
}

// ----------------------------------------------------------------------

void CentralServer::addToAccountConnectionMap(StationId suid, ConnectionServerConnection * cconn, uint32 subscriptionBits)
{
	m_accountConnectionMap[suid] = cconn;

	// remove corresponding "pseudo client connection"
	ConnectionServerConnection::removeFromAccountConnectionMap(suid);
}

// ----------------------------------------------------------------------

void CentralServer::removeFromAccountConnectionMap(StationId suid)
{
	ConnectionServerSUIDMap::iterator i=m_accountConnectionMap.find(suid);
	if (i!=m_accountConnectionMap.end())
	{
		// Erase the entry
		m_accountConnectionMap.erase(i++);
	}
}

// ----------------------------------------------------------------------

void CentralServer::removeFromAccountConnectionMap(int connectionServerConnectionId)
{
	ConnectionServerSUIDMap::iterator i;
	for(i = m_accountConnectionMap.begin(); i != m_accountConnectionMap.end();)
	{
		const ConnectionServerConnection* cconn = (*i).second;

		if(cconn && (cconn->getId() == connectionServerConnectionId))
		{
			// Erase the entry
			m_accountConnectionMap.erase(i++);
		}
		else
		{
			++i;
		}
	}

	// We could update the LoginServer population but people
	// leaving the server are not as important as people connecting
	// and so we will wait for update() to handle things
}

//--------------------------------------------------------------------------------

const Service * CentralServer::getChatService() const
{
	return m_chatService;
}

//--------------------------------------------------------------------------------

const Service * CentralServer::getCustomerService() const
{
	return m_csService;
}

//--------------------------------------------------------------------------------

const Service * CentralServer::getGameService() const
{
	return m_gameService;
}

//--------------------------------------------------------------------------------

void CentralServer::startConnectionServer(int connectionServerNumber, const std::string& listenAddress, uint16 publicPort, uint16 internalPort, bool, const SpawnDelaySeconds spawnDelay) const
{
	char tmp1[32];
	char tmp2[32];
	std::string publicString = _itoa(publicPort, tmp1, 10);
	std::string privateString = _itoa(internalPort, tmp2, 10);
	startConnectionServer(connectionServerNumber, listenAddress, &publicString, &privateString, spawnDelay);
}

//--------------------------------------------------------------------------------

void CentralServer::startConnectionServer(int connectionServerNumber, const std::string& listenAddress, const std::string * publicPort, const std::string * internalPort, const SpawnDelaySeconds spawnDelay) const
{
	UNREF(listenAddress);  //once we add dual nic support, we can use this
	if(!m_connService)
	{
		DEBUG_WARNING(true, ("Could not start connection server because there was no service for it"));
		return;
	}

	if (!ConfigCentralServer::getAllowZeroConnectionServerPort())
	{
		if (!publicPort)
		{
			WARNING(true, ("Could not start connection server because there were not client ports specified"));
			return;
		}
		if (*publicPort == "0")
		{
			WARNING(true, ("Could not start connection server because one or more client ports were 0."));
			return;
		}
	}

	// there should already be an entry in s_connectionServerHostList that
	// contains the node on which to start this particular connection server
	if ((connectionServerNumber <= 0) || (connectionServerNumber > static_cast<int>(s_connectionServerHostList.size())))
	{
		WARNING(true, ("Could not start connection server because connectionServerNumber=%d and s_connectionServerHostList.size()=%d", connectionServerNumber, static_cast<int>(s_connectionServerHostList.size())));
		return;
	}

	// spawn a connection server
	std::string options = "-s ConnectionServer clusterName=";
	options += ConfigCentralServer::getClusterName();

	if(gs_connectionServersPublic)
		options += " startPublicServer=true";

	options += " centralServerAddress=";
	options += m_connService->getBindAddress();

	char servicePort[32];

	options += " centralServerPort=";
	options += _itoa(m_connService->getBindPort(), servicePort, 10);

	if (publicPort != 0)
	{
		options += " clientServicePortPublic=";
		options += *publicPort;
	}
	if (internalPort != 0)
	{
		options += " clientServicePortPrivate=";
		options += *internalPort;
	}

	options += " connectionServerNumber=";
	options += _itoa(connectionServerNumber, servicePort, 10);

	DEBUG_REPORT_LOG(true, ("Spawning Connection server with options %s\n", options.c_str()));
	TaskSpawnProcess p(s_connectionServerHostList[connectionServerNumber-1], "ConnectionServer", options, spawnDelay);
	CentralServer::getInstance().sendTaskMessage(p);
}

// ----------------------------------------------------------------------

void CentralServer::sendToAllLoginServers(const GameNetworkMessage &message)
{
	for (LoginServerConnectionMapType::iterator i=m_loginServerConnectionMap.begin(); i!=m_loginServerConnectionMap.end(); ++i)
		i->second->send(message,true);
}

// ----------------------------------------------------------------------

uint32 CentralServer::sendToArbitraryLoginServer(const GameNetworkMessage &message, bool roundRobin /*= true*/)
{
	static unsigned int nextServer = 0;

	if (m_loginServerConnectionMap.empty())
		return 0;

	LoginServerConnectionMapType::const_iterator i = m_loginServerConnectionMap.begin();

	if (!roundRobin)
	{
		i->second->send(message,true);
		return i->second->getProcessId();
	}

	++nextServer;
	if (nextServer >= m_loginServerConnectionMap.size())
		nextServer = 0;

	std::advance(i, nextServer);

	i->second->send(message,true);
	return i->second->getProcessId();
}

// ----------------------------------------------------------------------

/**
 * In development mode, connect to a login server.
 */
void CentralServer::connectToLoginServer() const
{
	IGNORE_RETURN(new LoginServerConnection(ConfigCentralServer::getLoginServerAddress(), ConfigCentralServer::getLoginServerPort()));
}

//-----------------------------------------------------------------------

void CentralServer::remove()
{
	CentralServer & cs = getInstance();
	MetricsManager::remove();
	delete cs.m_metricsData;
	cs.m_metricsData = 0;

	delete cs.m_loginServerKeys;

	cs.m_messagesWaitingForPlanetServer.clear();
	cs.m_gameServerConnections.clear();
	cs.m_gameServerConnectionsList.clear();
	cs.m_connectionServerConnections.clear();

	delete cs.m_gameService;
	delete cs.m_chatService;
	delete cs.m_csService;
	delete cs.m_connService;
	delete cs.m_planetService;
	delete cs.m_loginService;
	if(cs.m_taskManager)
	{
		cs.m_taskManager->setDisconnectReason("CentralServer::remove");
		cs.m_taskManager->disconnect();
	}
	delete cs.m_taskService;
	delete cs.m_consoleService;
}

//-----------------------------------------------------------------------

std::vector<const GameServerConnection *> CentralServer::getGameServers() const
{
	std::vector<const GameServerConnection *> result;
	std::map<uint32, GameServerConnection *>::const_iterator i;
	for(i = m_gameServerConnections.begin(); i != m_gameServerConnections.end(); ++i)
	{
		result.push_back((*i).second);
	}
	return result;
}

// ----------------------------------------------------------------------

void CentralServer::sendToConnectionServerForAccount(StationId account, const GameNetworkMessage & message, const bool reliable)
{
	ConnectionServerConnection *conn = getConnectionServerForAccount(account);
	if (conn)
		conn->send(message,reliable);
}

// ----------------------------------------------------------------------

uint32 CentralServer::sendToRandomGameServer(const GameNetworkMessage &message)
{
	GameServerConnection *conn = getRandomGameServer();
	if (conn)
	{
		conn->send(message,true);
		return conn->getProcessId();
	}
	else
		return 0;
}

// ----------------------------------------------------------------------

void CentralServer::doServerPings()
{
	for (std::set<uint32>::iterator i=m_serverPings.begin(); i!=m_serverPings.end(); ++i)
	{
		LOG("CentralServerPings",("Dropping server %lu because it hasn't responded to CentralPingMessage",*i));
		GameServerConnection *conn=getGameServer(*i);
		if (conn)
		{
			// tell task manager to kill the offending process (in case it's hung)
			ExcommunicateGameServerMessage msg(*i, conn->getOsProcessId(), conn->getRemoteAddress());
			// disconnect, if it's not hung, it should die
			conn->setDisconnectReason("CentralServer::doServerPings, failure to respond to CentralPingMessage");
			conn->disconnect();
			sendTaskMessage(msg);
			excommunicateServer(msg);
		}
		else
		{
			LOG("CentralServerPings",("Didn't have connection to %lu to drop",*i));
			ExcommunicateGameServerMessage const excommunicateMessage(*i, 0, "");
			excommunicateServer(excommunicateMessage); // haven't received a reply (see CentralPingMessage, above)
		}
	}
	m_serverPings.clear();

	CentralPingMessage ping;
	for (std::map<uint32, GameServerConnection *>::iterator j=m_gameServerConnections.begin(); j!=m_gameServerConnections.end(); ++j)
	{
		if (j->first != getDbProcessServerProcessId())
		{
			LOG("CentralServerPings",("Pinging  %lu",j->first));
			IGNORE_RETURN(m_serverPings.insert(j->first));
			j->second->send(ping,true);
		}
	}
}

// ----------------------------------------------------------------------

/**
 * Declare a game server anathema.  All other servers will shun this server.
 */
void CentralServer::excommunicateServer(const ExcommunicateGameServerMessage & msg)
{
	sendToAllGameServers(msg,true);
	sendToAllPlanetServers(msg,true);
	sendToAllConnectionServers(msg,true);
}

//-----------------------------------------------------------------------
void CentralServer::startShutdownProcess(const uint32 timeToShutdown, const uint32 maxTime, const Unicode::String &systemMessage)
{
	// sanity checking
	if ( m_shutdownPhase > 6)
	{
		WARNING("CentralServerShutdown", ("startShutdownProcess: invalid value in CentralServer::m_shutdownPhase.  Current value is %d. Resetting.", m_shutdownPhase));
		m_shutdownPhase = 0;
	}

	// server already in the process of shutting down
	if ( m_shutdownPhase > 0 )
	{
		LOG("CentralServerShutdown", ("startShutdownProcess: Server already in the process of shutting down.  Current shutdown phase is %d.", m_shutdownPhase));
		return;
	}

	if( timeToShutdown>maxTime )
	{
		LOG("CentralServerShutdown", ("startShutdownProcess: time to shutdown is greater than max time."));
		return;
	}
	m_shutdownPhase = 1;
	LOG("CentralServerShutdown",("Shutdown Phase %d: Shutdown sequence starting now.", m_shutdownPhase));
	LOG("CentralServerShutdown",("Shutdown Phase %d: time to shutdown=%dsec, max time to wait=%dsec, broadcast warning message=\"%s\".", m_shutdownPhase, timeToShutdown, maxTime, Unicode::wideToNarrow(systemMessage).c_str()));
	// this is the time in the future that we need to start the shutdown process.
	m_shutdownTotalTime = m_curTime + timeToShutdown;
	m_shutdownMaxTime = m_curTime + maxTime;
	m_shutdownSystemMessage = systemMessage;
	m_shutdownHaveDatabaseSaveStart = false;
	m_shutdownHaveDatabaseComplete = false;
	checkShutdownProcess();
}
//------------------------------------------------------------------------
void CentralServer::checkShutdownProcess()
{
	static bool warn30 = false;
	static bool warn10 = false;
	static bool warn0 = false;
	static uint32 lastWarn = m_curTime;
	static uint32 nextAbortCheck = m_curTime+1;
	static uint32 nextShutdownCheck = m_curTime+30;

	if( !m_shutdownPhase )
	{
#ifndef WIN32
		// check if a .shutdown file exists
		if( m_curTime > nextShutdownCheck )
		{
			nextShutdownCheck = m_curTime + 30;
			if( FileExists(".shutdown") )
			{
				StdioFile shutdownFile(".shutdown", "r");
				if(shutdownFile.isOpen())
				{
					char destBuffer[64] = {"\0"};
					if(shutdownFile.read(destBuffer, sizeof(destBuffer)) > 0)
					{
						destBuffer[sizeof(destBuffer) - 1] = 0;
						int shutdownTime = atoi(destBuffer);
						if(shutdownTime > 0)
						{
							LOG("CentralServerShutdown", ("Detected a .shutdown file.  Initiating shutdown sequence."));
							startShutdownProcess(shutdownTime, 7200, Unicode::narrowToWide("The server will be shutting down soon.  Please find a safe place to logout."));
						}
					}
					shutdownFile.close();
				}
				IGNORE_RETURN(::remove(".shutdown"));
				return;
			}

			if( FileExists( ".startanymissingplanet") )
			{
				LOG("CentralServer", ("Detected a .startanymissingplanet file.  Checking for any planets that are not started, and starting them."));

				for (std::map<std::string, std::string>::const_iterator iter = ms_sceneToHostMap.begin(); iter != ms_sceneToHostMap.end(); ++iter)
				{
					if (!hasPlanetServer(iter->first))
					{
						std::map<std::string, std::pair<std::pair<std::string, std::string>, time_t> >::iterator iterPendingPlanetServer = m_pendingPlanetServers.find(iter->first);
						if (iterPendingPlanetServer != m_pendingPlanetServers.end())
						{
							// if it's been "awhile" since we requested to restart the PlanetServer,
							// then assume that something has gone wrong, and try the restart again
							time_t const timeNow = ::time(nullptr);

							if ((iterPendingPlanetServer->second.second + static_cast<time_t>(ConfigCentralServer::getMaxTimeToWaitForPlanetServerStartSeconds())) < timeNow)
							{
								LOG("CentralServer", ("startanymissingplanet:Starting missing planet %s because it has been %s since we requested it to be started, which has exceeded the %s limit.", iter->first.c_str(), CalendarTime::convertSecondsToMS(static_cast<unsigned int>(timeNow - iterPendingPlanetServer->second.second)).c_str(), CalendarTime::convertSecondsToMS(static_cast<unsigned int>(ConfigCentralServer::getMaxTimeToWaitForPlanetServerStartSeconds())).c_str()));
								iterPendingPlanetServer->second.second = timeNow;
								TaskSpawnProcess spawn(iterPendingPlanetServer->second.first.first.empty() ? std::string("any") : iterPendingPlanetServer->second.first.first, "PlanetServer", iterPendingPlanetServer->second.first.second);
								CentralServer::getInstance().sendTaskMessage(spawn);
							}
							else
							{
								LOG("CentralServer", ("startanymissingplanet:It's only been %s since we requested for planet %s to be started, and we must wait %s before we can start it again.", CalendarTime::convertSecondsToMS(static_cast<unsigned int>(timeNow - iterPendingPlanetServer->second.second)).c_str(), iter->first.c_str(), CalendarTime::convertSecondsToMS(static_cast<unsigned int>(ConfigCentralServer::getMaxTimeToWaitForPlanetServerStartSeconds())).c_str()));
							}
						}
						else
						{
							LOG("CentralServer", ("startanymissingplanet:Hmmm... planet %s is missing, but also is not in the list of planets we have attempted to start, so we're ***NOT*** going to start it.", iter->first.c_str()));
						}
					}
				}

				IGNORE_RETURN(::remove( ".startanymissingplanet" ));
				return;
			}

			if( FileExists( ".startanymissinggameserver") )
			{
				LOG("CentralServer", ("Detected a .startanymissinggameserver file.  Checking for any game servers that are not started, and starting them."));

				const GenericValueTypeMessage<uint8> startAnyMissingGameServer("SAMGS", 0);
				sendToAllPlanetServers(startAnyMissingGameServer, true);

				IGNORE_RETURN(::remove( ".startanymissinggameserver" ));
				return;
			}
		}
#endif
		return;
	}

	// should we abort?
	if( m_curTime > nextAbortCheck )
	{
#ifndef WIN32
		nextAbortCheck = m_curTime + 1;
		if( FileExists(".abortshutdown") )
		{

			IGNORE_RETURN(::remove(".abortshutdown"));
			LOG("CentralServerShutdown", ("Shutdown Phase %d: Detected a .abortshutdown file.  Aborting shutdown sequence.", m_shutdownPhase));
			abortShutdownProcess();
			return;
		}
#endif
	}

	// Phase 1 is an immediate broadcast to players for the first shutdown warning
	if( m_shutdownPhase == 1 )
	{
		char strTimeLeft[1024];
		uint32 timeLeft = m_shutdownTotalTime - m_curTime;
		if( timeLeft < 1 )
		{
			strTimeLeft[0] = '\0';
		}
		else if( timeLeft < 60 )
		{
			sprintf(strTimeLeft, " ( %lusec left )", timeLeft);
		}
		else
		{
			sprintf(strTimeLeft, " ( %lumin left )", timeLeft/60);
		}

		LOG("CentralServerShutdown",("Shutdown Phase %d: Broadcasting first shutdown message to players: \"%s %s\"", m_shutdownPhase, Unicode::wideToNarrow(m_shutdownSystemMessage).c_str(), strTimeLeft));
		ConGenericMessage const msg("game any systemMessage " + Unicode::wideToNarrow(m_shutdownSystemMessage)+strTimeLeft, 0);
		IGNORE_RETURN(sendToRandomGameServer(msg));
		m_shutdownPhase = 2;
		lastWarn = m_curTime;
	}
	// Phase 2 is broadcasting a system message every 60sec while we wait for time to expire
	else if( m_shutdownPhase == 2 )
	{
		if(  m_curTime >= m_shutdownTotalTime)
		{
			LOG("CentralServerShutdown",("Shutdown Phase %d: Done broadcasting shutdown warning message to players . Advancing shutdown phase.", m_shutdownPhase));
			m_shutdownPhase = 3;
			warn30 = false;
			warn10 = false;
			warn0 = false;


		}
		// broadcast warning message to players every 60sec.
		else if( m_curTime >= lastWarn+60 )
		{
			char strTimeLeft[1024];
			uint32 timeLeft = m_shutdownTotalTime - m_curTime;
			if( timeLeft < 1 )
			{
				strTimeLeft[0] = '\0';
			}
			else if( timeLeft < 60 )
			{
				sprintf(strTimeLeft, " ( %lusec left)", timeLeft);
			}
			else
			{
				sprintf(strTimeLeft, " ( %lumin left)", timeLeft/60);
			}

			LOG("CentralServerShutdown",("Shutdown Phase %d: Broadcasting shutdown message to players: \"%s %s\"", m_shutdownPhase, Unicode::wideToNarrow(m_shutdownSystemMessage).c_str(), strTimeLeft));
			ConGenericMessage const msg("game any systemMessage " + Unicode::wideToNarrow(m_shutdownSystemMessage)+strTimeLeft, 0);
			IGNORE_RETURN(sendToRandomGameServer(msg));
			lastWarn = m_curTime;
		}
	}
	// warn the players they are about to be disconnected
	else if( m_shutdownPhase == 3 )
	{
		if( m_curTime>=m_shutdownTotalTime && !warn30 )
		{
			LOG("CentralServerShutdown",("Shutdown Phase %d: Broadcasting 30sec disconnect warning message to players.", m_shutdownPhase));
			ConGenericMessage const msg("game any systemMessage You will be disconnected in 30sec so the server can perform a final save before shutting down.  Please find a safe place to logout now.", 0);
			IGNORE_RETURN(sendToRandomGameServer(msg));
			warn30 = true;

		}
		else if( m_curTime >= (m_shutdownTotalTime+20) && !warn10 )
		{
			LOG("CentralServerShutdown",("Shutdown Phase %d: Broadcasting 10sec disconnect warning message to players.", m_shutdownPhase));
			ConGenericMessage const msg("game any systemMessage You will be disconnected in 10sec so the server can perform a final save before shutting down.  Please find a safe place to logout now.", 0);
			IGNORE_RETURN(sendToRandomGameServer(msg));
			warn10 = true;
		}
		else if( m_curTime >= (m_shutdownTotalTime+30) && !warn0 )
		{
			LOG("CentralServerShutdown",("Shutdown Phase %d: Broadcasting final disconnect warning message to players.", m_shutdownPhase));
			ConGenericMessage const msg("game any systemMessage You will now be disconnected so the server can perform a final save before shutting down.", 0);
			IGNORE_RETURN(sendToRandomGameServer(msg));
			warn0 = true;
		}
		else if( m_curTime >= (m_shutdownTotalTime+35) )
		{
			LOG("CentralServerShutdown",("Shutdown Phase %d: Setting server to private, disconnecting the players and now waiting for next database save cycle to begin.", m_shutdownPhase));
			gs_connectionServersPublic = false;
			SetConnectionServerPublic const msg(false);
			sendToAllConnectionServers(msg, true);
			m_shutdownPhase = 4;

			// send a message to the DB telling it we're shutting down
			GenericValueTypeMessage<bool> const msg2("ClusterShutdownMessage", true);
			sendToDBProcess(msg2, true);

			// send a save start to the DB; must be sent after the above
			// "ClusterShutdownMessage" message so the DB will know this
			// is the final save before cluster shutdown
			GenericValueTypeMessage<NetworkId> const msg3("StartSaveMessage", NetworkId::cms_invalid);
			sendToDBProcess(msg3, true);
		}
	}
	else if( m_shutdownPhase == 4 )
	{
		if( m_shutdownHaveDatabaseSaveStart )
		{
			LOG("CentralServerShutdown",("Shutdown Phase %d: Starting final database save cycle. Advancing shutdown phase.", m_shutdownPhase));
			m_shutdownPhase = 5;
		}
	}
	else if( m_shutdownPhase == 5 )
	{
		// we are done.  Shut it down.  Shut it down now!
		if( m_shutdownHaveDatabaseComplete)
		{
			LOG("CentralServerShutdown",("Shutdown Phase %d: Completed final database save cycle. Advancing shutdown phase.", m_shutdownPhase));
			m_shutdownPhase = 6;
			LOG("CentralServerShutdown",("Shutdown Phase %d: Instructing TaskManager to shutdown the cluster without restarting.", m_shutdownPhase));
			ConGenericMessage const msg("stop", 0);
			sendTaskMessage(msg);
		}
	}
	// maximum amount of time to wait has expired.  Shut it down now without waiting for anything else.
	else if( m_curTime >= m_shutdownMaxTime )
	{
		LOG("CentralServerShutdown",("Shutdown Phase %d: Maximum time has gone by.  Forcing a shutdown now.", m_shutdownPhase));
		m_shutdownPhase = 6;
		LOG("CentralServerShutdown",("Shutdown Phase %d: Instructing TaskManager to shutdown the cluster without restarting.", m_shutdownPhase));
		ConGenericMessage const msg("stop", 0);
		sendTaskMessage(msg);
	}
}

void CentralServer::abortShutdownProcess()
{
	LOG("CentralServerShutdown", ("Shutdown Phase %d: Aborting shutdown sequence.", m_shutdownPhase));
	if( m_shutdownPhase > 0 )
	{
		ConGenericMessage const msg("game any systemMessage The server shutdown has been aborted.  The server is no longer shutting down.", 0);
		IGNORE_RETURN(sendToRandomGameServer(msg));
	}

	// send a message to the DB telling it we're no longer shutting down
	GenericValueTypeMessage<bool> const msg("ClusterShutdownMessage", false);
	sendToDBProcess(msg, true);

	m_shutdownPhase = 0;
	m_shutdownTotalTime = 0;
	m_shutdownMaxTime = 0;
	m_shutdownSystemMessage = Unicode::narrowToWide("");
	m_shutdownHaveDatabaseSaveStart = false;
	m_shutdownHaveDatabaseComplete = false;
}

//-----------------------------------------------------------------------

void CentralServer::sendToTransferServer(const GameNetworkMessage & msg) const
{
	if(getInstance().m_transferServerConnection)
	{
		getInstance().m_transferServerConnection->send(msg, true);
	}
}

//-----------------------------------------------------------------------

//-----------------------------------------------------------------------

void CentralServer::sendToStationPlayersCollector(const GameNetworkMessage & msg) const
{
	if(getInstance().m_stationPlayersCollectorConnection)
	{
		getInstance().m_stationPlayersCollectorConnection->send(msg, true);
	}
}

//-----------------------------------------------------------------------

int CentralServer::getPlanetServersCount() const
{
	return m_planetServers.size();
}

//-----------------------------------------------------------------------

const Service * CentralServer::getCommoditiesService()
{
	return s_commodityServerService;
}

//-----------------------------------------------------------------------

int CentralServer::getClusterStartupTime() const
{
	if (m_clusterStartupTime != -1)
		return m_clusterStartupTime;
	else
		// return how long the cluster has been taking to come up
		return int((time(0) - m_timeClusterStarted) / 60);
}

//-----------------------------------------------------------------------

int CentralServer::getSecondsClusterHasBeenInLoadingState() const
{
	if (m_timeClusterWentIntoLoadingState > 0)
		return (time(0) - m_timeClusterWentIntoLoadingState);
	else
		return 0;
}

// ----------------------------------------------------------------------

const std::map<std::string, int> & CentralServer::getPopulationStatistics(time_t & refreshTime)
{
	// periodically request updated statistics from the game server
	time_t const timeNow = ::time(nullptr);
	if (m_timePopulationStatisticsNextRefresh <= timeNow)
	{
		GameServerConnection * universeGameServerConnection = getGameServer(UniverseManager::getInstance().getUniverseProcess());
		if (universeGameServerConnection)
		{
			const GenericValueTypeMessage<uint8> populationStatisticsRequest("PopStatReq", 0);
			universeGameServerConnection->send(populationStatisticsRequest, true);

			m_timePopulationStatisticsNextRefresh = timeNow + 60;
		}
	}

	refreshTime = m_timePopulationStatisticsRefresh;
	return m_populationStatistics;
}

// ----------------------------------------------------------------------

const std::map<std::string, std::pair<int, std::pair<std::string, std::string> > > & CentralServer::getGcwScoreStatistics(time_t & refreshTime)
{
	// periodically request updated statistics from the game server
	time_t const timeNow = ::time(nullptr);
	if (m_timeGcwScoreStatisticsNextRefresh <= timeNow)
	{
		GameServerConnection * universeGameServerConnection = getGameServer(UniverseManager::getInstance().getUniverseProcess());
		if (universeGameServerConnection)
		{
			const GenericValueTypeMessage<uint8> gcwScoreStatisticsRequest("GcwScoreStatReq", 0);
			universeGameServerConnection->send(gcwScoreStatisticsRequest, true);

			m_timeGcwScoreStatisticsNextRefresh = timeNow + 60;
		}
	}

	refreshTime = m_timeGcwScoreStatisticsRefresh;
	return m_gcwScoreStatistics;
}

// ----------------------------------------------------------------------

std::pair<std::map<int, std::pair<std::string, int> > const *, std::map<int, std::pair<std::string, int> > const *> CentralServer::getLastLoginTimeStatistics(time_t & refreshTime)
{
	// periodically request updated statistics from the game server
	time_t const timeNow = ::time(nullptr);
	if (m_timeLastLoginTimeStatisticsNextRefresh <= timeNow)
	{
		GameServerConnection * universeGameServerConnection = getGameServer(UniverseManager::getInstance().getUniverseProcess());
		if (universeGameServerConnection)
		{
			const GenericValueTypeMessage<uint8> lastLoginTimeStatisticsRequest("LLTStatReq", 0);
			universeGameServerConnection->send(lastLoginTimeStatisticsRequest, true);

			m_timeLastLoginTimeStatisticsNextRefresh = timeNow + (15 * 60);
		}
	}

	refreshTime = m_timeLastLoginTimeStatisticsRefresh;
	
	return std::make_pair(&m_lastLoginTimeStatistics, &m_createTimeStatistics);
}

// ----------------------------------------------------------------------

void CentralServer::getCharacterMatchStatistics(int & numberOfCharacterMatchRequests, int & numberOfCharacterMatchResultsPerRequest, int & timeSpentPerCharacterMatchRequestMs)
{
	// periodically request updated statistics from the game server
	time_t const timeNow = ::time(nullptr);
	if (m_timeCharacterMatchStatisticsNextRefresh <= timeNow)
	{
		const GenericValueTypeMessage<uint8> characterMatchStatisticsRequest("LfgStatReq", 0);
		sendToAllGameServersExceptDBProcess(characterMatchStatisticsRequest, true);

		m_timeCharacterMatchStatisticsNextRefresh = timeNow + 60;
	}

	// wraparound, reset statistics
	if (m_numberOfCharacterMatchRequests < 0)
	{
		m_numberOfCharacterMatchRequests = 0;
		m_numberOfCharacterMatchResults = 0;
		m_timeSpentOnCharacterMatchRequestsMs = 0;
	}

	numberOfCharacterMatchRequests = m_numberOfCharacterMatchRequests;
	numberOfCharacterMatchResultsPerRequest = (m_numberOfCharacterMatchRequests ? static_cast<int>(m_numberOfCharacterMatchResults / m_numberOfCharacterMatchRequests) : 0);
	timeSpentPerCharacterMatchRequestMs = (m_numberOfCharacterMatchRequests ? static_cast<int>(m_timeSpentOnCharacterMatchRequestsMs / m_numberOfCharacterMatchRequests) : 0);
}

// ======================================================================
//lint -e766 // todo : TRF Fix this
