//
// Asura_Network.cpp
//
// Created by Kevin Lea - 13/06/2001 11:32:38
// 
// Copyright (C) 2001 Rebellion, All Rights Reserved.
//

#include "Core/Asura.h"

#include "Network/Asura_Network.h"

#include "Core/Asura_Core.h"
#include "Core/Asura_Mempool.h"
#include "Core/Asura_String.h"
#include "Core/Asura_Timers.h"
#include "Core/Memory/Asura_Mem.h"
#include "Debugging/Asura_Assert.h"
#include "Debugging/Asura_CPUProfile.h"
#include "Debugging/Asura_Logfile.h"
#include "Entity/Asura_Entity.h"
#ifdef ASURA_SUPPORT_COMMAND_CONSOLE
#include "Interface/Asura_CommandConsole.h"
#endif
#include "Network/Asura_Message.h"
#include "Network/Asura_MessageHeader.h"
#ifdef ASURA_SUPPORT_NETMSG_SYSTEM
#include "Network/Asura_NetMsg.h"
#endif
#include "Network/Asura_Player.h"
#if defined(ASURA_USE_STREAMING)
#include "Streaming/Asura_Streaming_System.h"
#include "Streaming/Asura_Streaming_ZoneEntities.h"
#endif	// defined(ASURA_USE_STREAMING)

#ifdef ASURA_USE_COLLISIONFX
#include "SpecialFX/Asura_CollisionFX_System.h"
#endif //ASURA_USE_COLLISIONFX


#if ( defined ASURA_SUPPORT_EXTERNAL_NETWORK ) && !( defined ASURA_SUPPORT_NETMSG_SYSTEM )
#error You should be defining ASURA_SUPPORT_NETMSG_SYSTEM when ASURA_SUPPORT_EXTERNAL_NETWORK is defined
#endif

// C O N S T A N T S /////////////////////////////////////////////////////////

// Message buffers should be small enough to fit into a single UDP packet,
// otherwise there is a higher probability of losing the data, as all data in
// a multi-packet send is lost if any of the packets goes astray.
// Sadly, this is variable between ISPs. MTU (max transmission unit) is 1500
// bytes for ethernet LAN. A doc on the PS2 newsgroups (sce.pub.trc.network, 
// thread "Minimum Bandwidth Supported") lists a bunch of European ISPs and 
// they have a "max packet size" ranging from 1454 to 1466. UDP headers are 28
// bytes; I'm not sure if these are included in the latter numbers or not.
// A safe estimate might be 1454-28 = 1426 bytes.
// There is an overhead for sending the asura message of 20 bytes
// KOR - 11/02/2004 - Xbox payload limit is 1304 bytes, and ANTS has a 2 byte
// per packet overhead. Shave off an extra word to be safe.
// KOR - 13/05/2005 - PSP MTU is 1444 bytes. It will automatically fragment anything
// larger than this (up to 65,523 bytes), but this increases the risk of the whole
// packet being lost if any one of the fragments are lost.
// AAM - 29/07/2005 - Something appears to have changed to add more stuff for Xbox
// LAN play, so I'm decreasing this a little to compensate. TODO: work out correct size
// AS - 19/12/2007 - Bring it down even more as 360 has a maximum limit of 1264 - 2 for the 2 byte size
// at the start of the packet
// TY - 01/05/2009 - Steamworks has a limit of 1200 (apparently some routers drop larger UDP packets)
static const u_int ASURA_NETWORK_MESSAGE_BUFFER_SIZE = 1200 - sizeof(Asura_MessageHeader) - 4;

static const u_int uASURA_NETWORK_MAX_RECYCLED_GUIDS = 32;
static const u_int uASURA_NETWORK_GUID_RECYCLE_TIME = 30000;	// 30 secs
static const Asura_Guid uASURA_NETWORK_OPTIMUM_MAX_GUID = 1031;

static const u_int uASURA_MAX_PLAYERS_PER_MACHINE = 4;	// ANTS limits this to 1. You still need 4 for split-screen.

static const u_int uASURA_NETWORK_MAX_MSG_DATA_SIZE = 1024;

// C L A S S E S /////////////////////////////////////////////////////////////
class Asura_Message_Node
{
	public:
		Asura_Message* Message;
		Asura_Message_Node* Next;
};	

// (MH - 27.07.2004) implemented Guid recycling - when a machine disconnects its Guids are
// added to the Asura_Network_GuidRecycler. When a new Guid is requested and it will be outside
// the optimal range of 1000-1031 a check is made to see it one can be used from the recycler.
// There is a minimum time enforced before a guid can be reused to avoid any problems from 
// clients taking a little while to receive the messages destroying the old entity.
class Asura_Network_GuidRecycler_Entry
{
	public:

		Asura_Guid m_uGuid;
		u_int m_uTime;
};

class Asura_Network_GuidRecycler
{

	public:

		Asura_Network_GuidRecycler()
		{
			m_uNumEntries = 0;
		}

		// Add() - recycle a guid
		void Add(Asura_Guid uGuid)
		{
			if (uGuid > uASURA_NETWORK_OPTIMUM_MAX_GUID)
			{
				// this guid is above the optimal range anyway so don't bother trying to 
				// reuse it.
				return;
			}

			#ifdef ASURA_ASSERT
			for (u_int u = 0; u < m_uNumEntries; ++u)
			{
				Asura_Assert(m_axEntries[u].m_uGuid != uGuid, "duplicate guid in recycler");
			}
			#endif // #ifdef ASURA_ASSERT

			if (m_uNumEntries < uASURA_NETWORK_MAX_RECYCLED_GUIDS)
			{
				u_int uEntry = m_uNumEntries++;
				m_axEntries[uEntry].m_uGuid = uGuid;
				m_axEntries[uEntry].m_uTime = Asura_Timers::GetMSecsSinceStart();
			}
			else
			{
				Asura_Assert(false, "not enough storage to recycle guid");
			}
		}

		// Get() - get a recycled guid to use. returns ASURA_GUID_UNREGISTERED if there aren't any.
		Asura_Guid Get()
		{
			if (m_uNumEntries)
			{
				// we've got some guids to recycle - oldest is at 0 so just check if it's old enough
				u_int uCurTime = Asura_Timers::GetMSecsSinceStart();
				Asura_Assert(uCurTime >= m_axEntries[0].m_uTime, "time went backwards?");
				u_int uElapsedTime = uCurTime - m_axEntries[0].m_uTime;
				if (uElapsedTime >= uASURA_NETWORK_GUID_RECYCLE_TIME)
				{
					// We can use this one. 
					Asura_Guid uReturnValue = m_axEntries[0].m_uGuid;

					// reduce num entries and shuffle the list up to the head.
					--m_uNumEntries;
					for (u_int u = 0; u < m_uNumEntries; ++u)
					{
						m_axEntries[u] = m_axEntries[u+1];
					}
					
					return uReturnValue;
				}

			}
			return ASURA_GUID_UNREGISTERED;
		}

	private:

		Asura_Network_GuidRecycler_Entry m_axEntries[uASURA_NETWORK_MAX_RECYCLED_GUIDS];
		u_int m_uNumEntries;		
};


class Asura_Network_ClientMachineStatus
{
	public:

		Asura_Network_ClientMachineStatus()
		{
			m_uNumberOfPlayers = 0;
			m_uMaxNumberOfPlayers = 0;
		}

		void Initialise(u_int uMaxNumberOfPlayers)
		{
			Asura_Assert(uMaxNumberOfPlayers <= uASURA_MAX_PLAYERS_PER_MACHINE, "max players too big");

			m_uMaxNumberOfPlayers = uMaxNumberOfPlayers;
		}

		void Reset(void)
		{
			m_uNumberOfPlayers = 0;
			m_uMaxNumberOfPlayers = 0;
		}

		~Asura_Network_ClientMachineStatus()
		{
		}

		bool DoesMachineHavePlayer(Asura_Guid uGuid) const
		{
			for (u_int i=0; i<m_uNumberOfPlayers; i++)
			{
				if (m_auPlayerGuids[i] == uGuid)
				{
					return true;
				}
			}

			return false;
		}

		void AddPlayer(Asura_Guid uGuid)
		{
			Asura_Assert(m_uNumberOfPlayers<m_uMaxNumberOfPlayers,"too many players");
				
			if (m_uNumberOfPlayers<m_uMaxNumberOfPlayers)
			{
				m_auPlayerGuids[m_uNumberOfPlayers++] = uGuid;
			}
			else
			{
#ifdef ASURA_SUPPORT_COMMAND_CONSOLE
				Asura_CommandConsole::Printf(L" ERROR: cannot add player %d to machine: too many players (%d) ", uGuid, m_uNumberOfPlayers );
#endif
			}
		}

		void RemovePlayers( void ) const
		{
			Asura_Message* pxMsg = Asura_Network::CreateMessage( ASURA_MSG_PLAYERDISCONNECTED, sizeof(Asura_MsgData_PlayerDisconnected) );
            if( pxMsg )
            {
                pxMsg->To = ASURA_GUID_SERVER;
			    pxMsg->From = ASURA_GUID_SERVER;

			    for( u_int u = 0; u < m_uNumberOfPlayers; ++u )
			    {
					Asura_MsgData_PlayerDisconnected* pxData = static_cast<Asura_MsgData_PlayerDisconnected*>(pxMsg->GetData());
					if (pxData)
					{
						pxData->PlayerGuid = m_auPlayerGuids[u];
						pxData->m_eReason  = ASURA_NDR_UNKNOWN;
					}
                    Asura_Network::Send( pxMsg, false );
                }

                Asura_Network::DestroyMessage( pxMsg );
            }
		}

		u_int GetNumberOfPlayers() const { return m_uNumberOfPlayers; }
		u_int GetMaxNumberOfPlayers() const { return m_uMaxNumberOfPlayers; }

        Asura_Guid GetPlayerGuid( u_int uIdx ) const { return m_auPlayerGuids[uIdx]; }
		
	private:
		u_int		m_uMaxNumberOfPlayers;
		u_int		m_uNumberOfPlayers;
		Asura_Guid  m_auPlayerGuids[uASURA_MAX_PLAYERS_PER_MACHINE];
};


#ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
// Class to buffer up data destined for a particular machine.
// Buffer size is hardcoded only to allow a single UDP packet in order to reduce 
// the chances of it being dropped. (If any part of a multi-packet message goes 
// astray the whole message is lost). 
class Asura_Network_MessageBuffer
{
public:

	enum DestinationType
	{
		// having an 'all clients' destination in addition to a buffer per client
		// might lead to a greater number of messages being delivered out-of-order.
		// It might save a bit of memory though.
#ifdef ASURA_NETWORK_USE_BROADCAST_FOR_GAME_DATA
		Dest_AllClients = -1,
#endif
		Dest_Server = 0,
		Dest_Client1,	// other client IDs follow
	};

	Asura_Network_MessageBuffer(int iDestinationMachine, bool bGuaranteed);
	~Asura_Network_MessageBuffer();

	void AddMessage(const Asura_Message* pxMessage);
	void Flush();

private:
	
	void SendMessageToMyDestination(const Asura_Message* pxMessage);

	char* const m_pcBuffer;
	u_int m_uPosition;
    u_int m_uMsgCount;
	int m_iDestination;
	bool m_bGuaranteed;
};


class Asura_Network_MessageBuffer_Manager
{

	enum
	{
		MSG_NOT_GUARANTEED,
		MSG_GUARANTEED,
		MSG_NUM_BUFFERS_PER_MACHINE
	};

public:
	
	Asura_Network_MessageBuffer_Manager(int iNumMachines);
	~Asura_Network_MessageBuffer_Manager();

	void AddMessage(const Asura_Message* pxMessage, int iDestinationMachine);
#ifdef ASURA_NETWORK_USE_BROADCAST_FOR_GAME_DATA
	void AddBroadcastMessage(const Asura_Message* pxMessage);
#endif
	void FlushAll();

private:
	
	Asura_Network_MessageBuffer** m_paxBufferArray[MSG_NUM_BUFFERS_PER_MACHINE];
#ifdef ASURA_NETWORK_USE_BROADCAST_FOR_GAME_DATA
	Asura_Network_MessageBuffer* m_pxBroadcastBuffer;	// only supports non-guaranteed
#endif
	int m_iNumMachines;
};
#endif // #ifdef ASURA_SUPPORT_EXTERNAL_NETWORK


// class to store info for each player in an array so it is possible to iterate
// for 0 to numplayers-1, even for information only available by guid.
// Possibly extend this to provide player name, rather than dealing with player
// names in project-specific code.
class Asura_Network_PlayerInfo
{
	public:

		Asura_Guid m_uGuid;
};

// G L O B A L S /////////////////////////////////////////////////////////////
DEBUGVAR bool dbg_bPrintMessagesAddedToQueue = false;
DEBUGVAR bool dbg_bPrintAllBufferedMessages = false;
DEBUGVAR u_int ASURA_NETWORK_MAX_NO_OF_MESSAGES = 1000;//4000;// (MH - 02.05.2003) made debugvar.
#ifdef ASURA_SUPPORT_NETMSG_SYSTEM
DEBUGVAR bool dbg_bTestNetMsgSystemFromClient = false;
#endif
static Asura_Message_Node* g_pxMessageQueue = 0;
static Asura_Message_Node* g_pxMessageQueueLast = 0;
static void* g_pMessageData = NULL;
static char g_acMessageDataBuffer[uASURA_NETWORK_MAX_MSG_DATA_SIZE];

DEBUGVAR u_int dbg_uMaxSimultaneousClientsReceivingCreateMessages = 4;
DEBUGVAR u_int dbg_uMaxCreateMessagesPerClientPerBatch = 10;
DEBUGVAR float dbg_fCreateMessageBatchTimestep = 0.1f;

// S T A T I C   C L A S S   D A T A /////////////////////////////////////////
float			Asura_Network::s_fTimeSinceLastClientUpdates;
bool			Asura_Network::s_bSendThisFrame;
bool			Asura_Network::s_bExternalNetworkActive;
bool			Asura_Network::s_bIsPlayingOnInternet = false;
bool			Asura_Network::s_bServer;
bool			Asura_Network::s_bDedicatedServer;
bool			Asura_Network::s_bWaitingForConnection;
bool			Asura_Network::s_bConnectionOkay;
bool			Asura_Network::s_bReadyForMessages;
bool			Asura_Network::s_bReceivedValidClientGuids;
Asura_Mempool*	Asura_Network::s_pxMessagePool = NULL;
u_int			Asura_Network::s_uMaxNumberOfPlayers;
u_int			Asura_Network::s_uMaxNumberOfMachines;
u_int			Asura_Network::s_uNumberOfPlayers;
Asura_Network_PlayerInfo* Asura_Network::s_pxPlayerInfo;
u_int			Asura_Network::s_uNumberOfMachines;
ASURA_NETWORK_DISCONNECT_REASON Asura_Network::s_eReasonForDisconnection = ASURA_NDR_NOTDISCONNECTED;
Asura_UnicodeChar Asura_Network::s_awcHostName[ASURA_NETWORK_MAXHOSTNAMECHARS + 1] = L"host";

Asura_Network_ClientMachineStatus*	Asura_Network::s_pxClientMachineStatus;
Asura_Tracking_Handle_List*			Asura_Network::s_pxClientMachineHandleList;

void(*Asura_Network::s_pfnProject_ServerMessageHandler)(const Asura_Message* pxMsg);
void(*Asura_Network::s_pfnProject_ClientMessageHandler)(const Asura_Message* pxMsg);

Asura_Network_MessageBuffer_Manager* Asura_Network::s_pxMsgBuffManager = NULL;

Asura_Guid Asura_Network::s_uNextClientGuid;		// the next guid to be used on this machine if a player is created.
Asura_Guid Asura_Network::s_uInitialClientGuid;		// the first guid allocated to this machine for player creation
Asura_Guid Asura_Network::s_uNextGuidForNewClients;	// the next guid this server will send to clients
Asura_Network_GuidRecycler* Asura_Network::s_pxGuidRecycler = NULL;

#if ASURA_NETWORK_LOGGING
Asura_Network::MSG_LOG_LEVEL	Asura_Network::s_eMsgLogLevel = MSG_LOG_LEVEL_NONE;
bool							Asura_Network::s_bLogMessagesSent = true;
bool							Asura_Network::s_bLogMessagesReceived = false;
#endif

u_int Asura_Network::s_uTimeOfLastMessageReceived = 0;

Asura_LinkList<Asura_Guid> Asura_Network::s_xClientsAwaitingCreateMessages;
void(*Asura_Network::s_pfnClientCreateMessagesSentFn)(Asura_Guid uClientGuid) = NULL;

Asura_LinkList<Asura_Network::CreatingClientData*> Asura_Network::s_xClientsReceivingCreateMessages;
float Asura_Network::s_fTimeUntilNextCreateMessageBatch = 0.0f; // TODO - should this initialise to the max time, instead of sending some messages immediately?

#ifdef ASURA_SUPPORT_NETMSG_SYSTEM
Asura_LinkList<Asura_Guid> Asura_Network::s_xClientsIgnoringNetMsgUpdates;
#endif //#ifdef ASURA_SUPPORT_NETMSG_SYSTEM

// I N T E R N A L   F U N C T I O N S ///////////////////////////////////////
static void* AllocateMessageData( u_int uSize )
{
	if ( (!g_pMessageData) && (uSize <= uASURA_NETWORK_MAX_MSG_DATA_SIZE) )
	{
		g_pMessageData = g_acMessageDataBuffer;
		return g_pMessageData;
	}
	return Asura_Allocate( uSize );
}

static void FreeMessageData( void* pData )
{
	if (g_pMessageData == pData)
	{
		Asura_Assert(g_acMessageDataBuffer == pData, "");
		g_pMessageData = NULL;
	}
	else
	{
		Asura_Deallocate( pData );
	}
}


// A S U R A   N E T W O R K /////////////////////////////////////////////////
void Asura_Network::Initialise(bool bExternalNetworkRequired, 
							   bool bIsServer, 
							   void(*pfnServerMessageHandler)(const Asura_Message* pxMessage) /* = 0 */, 
							   void(*pfnClientMessageHandler)(const Asura_Message* pxMessage) /* = 0 */, 
							   u_int uNumberOfLocalPlayers /* = 4 */, 
							   u_int uMaxNumberOfPlayers /* = 16 */, 
							   u_int uMaxNumberOfMachines /* = 16 */,
							   const char* szIPAddressToConnectTo /* = 0 */)
{
#ifndef ASURA_SUPPORT_EXTERNAL_NETWORK
	Asura_Assert(!bExternalNetworkRequired, "external network not supported, must define ASURA_SUPPORT_EXTERNAL_NETWORK");
#endif
    Asura_Assert(!Asura_Network::IsNetworkInitialised(), "network already initialised");
	Asura_Assert(uNumberOfLocalPlayers <= uMaxNumberOfPlayers, "too many players");
	uMaxNumberOfPlayers = Asura_Maths::Max(uNumberOfLocalPlayers, uMaxNumberOfPlayers);
	Asura_Assert(uMaxNumberOfMachines, "can't have no machines in Asura_Network::Initialise");
	uMaxNumberOfMachines = Asura_Maths::Max(1u, uMaxNumberOfMachines);

	s_bSendThisFrame = false;
	s_fTimeSinceLastClientUpdates = 0.0f;
	s_bReadyForMessages = false;
	s_bReceivedValidClientGuids = false;
	g_pxMessageQueue = 0;
	g_pxMessageQueueLast = 0;
	s_uNumberOfMachines	= 0;
	s_eReasonForDisconnection = ASURA_NDR_NOTDISCONNECTED;

	// SNH - 07/03/2005 12:08:22 - changed this from 0, so in RT's lobby the player count is correct.
	// (MH - 21.03.2005) also make sure that s_pxPlayerInfo gets updated correctly at this point.
	s_uNumberOfPlayers = uNumberOfLocalPlayers;	

	// (MH - 17.09.2003) on server reserve enough player guids for our players.
	// on client, these will be received from the server so set them to invalid value.
	if (bIsServer)
	{
		s_uNextClientGuid = ASURA_GUID_CLIENT_START;
		s_uInitialClientGuid = s_uNextClientGuid;
		s_uNextGuidForNewClients = ASURA_GUID_CLIENT_START + uNumberOfLocalPlayers;
	}
	else
	{
		s_uNextClientGuid = ASURA_GUID_UNREGISTERED;
		s_uInitialClientGuid = ASURA_GUID_UNREGISTERED;
		s_uNextGuidForNewClients = ASURA_GUID_UNREGISTERED;
	}

	s_bExternalNetworkActive = bExternalNetworkRequired;
	s_bServer = bIsServer;

	// KJL - 21/08/2003 15:00:53 - no local players indicates a dedicated server
	s_bDedicatedServer = (uNumberOfLocalPlayers == 0);
	if (s_bDedicatedServer)
	{
		Asura_OutputToDebugger(( "Asura_Network INFO: dedicated %s client entities\n", pfnClientMessageHandler ? "has" : "does not have" ));
	}
		
	if (!s_bExternalNetworkActive)
	{
		s_uMaxNumberOfMachines = 1;
	}
	else
	{
		s_uMaxNumberOfMachines = uMaxNumberOfMachines;
		// (MH - 23.02.2005) need an extra machine if dedicated server as server itself uses up a slot.
		// The understanding has been that num machines means client machines.
		if (s_bDedicatedServer)
		{
			s_uMaxNumberOfMachines++;
		}
	}
	s_uMaxNumberOfPlayers = uMaxNumberOfPlayers;
	s_pxPlayerInfo = new Asura_Network_PlayerInfo[uMaxNumberOfPlayers];

	s_pxMessagePool = new Asura_Mempool(sizeof(Asura_Message), ASURA_NETWORK_MAX_NO_OF_MESSAGES);

	s_pxClientMachineStatus = new Asura_Network_ClientMachineStatus[s_uMaxNumberOfMachines];
	s_pxClientMachineHandleList = new Asura_Tracking_Handle_List(s_uMaxNumberOfMachines);

	// add server machine to machine list (server should always be the first 
	// machine on the list (i.e. zeroth)
	AddMachineToGame(uNumberOfLocalPlayers);

	// add server's local players to the client machine list (moved from GetNewClientGuid so RT can have guids in the lobby)
	if(bIsServer)
	{
		for(u_int uPlayer = 0; uPlayer < uNumberOfLocalPlayers; ++uPlayer)
		{
			Asura_Guid uGuid = ASURA_GUID_CLIENT_START + uPlayer;
			s_pxClientMachineStatus[0].AddPlayer(uGuid);
			// (MH - 21.03.2005) make sure that s_pxPlayerInfo is filled out according to s_uNumberOfPlayers.
			s_pxPlayerInfo[uPlayer].m_uGuid = uGuid;
		}
	}

	s_pfnProject_ServerMessageHandler = pfnServerMessageHandler;
    Asura_Assert( (s_pfnProject_ServerMessageHandler != NULL) == s_bServer, "" );
    
	s_pfnProject_ClientMessageHandler = pfnClientMessageHandler;

#ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
	if (s_bExternalNetworkActive)
	{
		// (MH - 17.09.2003) probably good to init these before calling StartServer
		// as once it is started messages might start being sent!
#ifdef ASURA_SUPPORT_NETMSG_SYSTEM
		Asura_NetMsg_System::Initialise();
#endif

		if (s_bServer)
		{
			s_pxMsgBuffManager = new Asura_Network_MessageBuffer_Manager(s_uMaxNumberOfMachines);
		}
		else
		{
			s_pxMsgBuffManager = new Asura_Network_MessageBuffer_Manager(1);
		}
		
		PlatformInitialise();

		if (s_bServer)
		{
			StartServer();

			s_pxGuidRecycler = new Asura_Network_GuidRecycler;
		}
		else
		{
			s_bWaitingForConnection = false;

			StartClient(szIPAddressToConnectTo);
		}


	}
#endif // #ifdef ASURA_SUPPORT_EXTERNAL_NETWORK

	if (s_bServer)
	{
		s_bWaitingForConnection = false;
		s_bConnectionOkay = true;
	}

	// SNH - 22/08/2003 12:33:01 - moved outside the if(s_bExternalNetworkActive)
	Asura_OutputToDebugger(("Network initialised. External network = %d, Server = %d.\n", s_bExternalNetworkActive, s_bServer));
}

// SNH - 27/08/2003 15:00:36 - reset client machine player lists and next available client guid
void Asura_Network::ResetPlayerStatus()
{
	// don't alter the client machine list here. 'Players' are added to machines on connection (when client 
	//	guids are sent) and are left there until disconnection.

	s_uNextClientGuid = s_uInitialClientGuid;

	Asura_OutputToDebugger(("Valid client guids reset\n"));
}

void Asura_Network::Shutdown(void)
{
#if 0 // - the world isn't ready for this yet.
	// (MH - 21.12.2006) if any entities still exist we should get rid of them now.
	// There have been cases where entities had message pointers waiting to be sent
	// and the network was shutdown while they were outstanding.

	// but it seems quit to game does network shutdown&init for some reason...
	//Asura_Entity_System::Reset();
	Asura_Classified_List_It<Asura_Entity_Server> xServerIt(Asura_Entity_Server::GetList());
	Asura_Classified_List_It<Asura_Entity_Client> xClientIt(Asura_Entity_Client::GetList());
	Asura_Hashed_List_It<Asura_Entity_Player> xPlayerIt(Asura_Entity_Player::GetList());
	Asura_Assert(xServerIt.Done(), "server entities exist at Asura_Network::Shutdown()");
	Asura_Assert(xClientIt.Done(), "client entities exist at Asura_Network::Shutdown()");
	Asura_Assert(xPlayerIt.Done(), "player entities exist at Asura_Network::Shutdown()");
#endif

	s_bReadyForMessages = false;
	s_bSendThisFrame = false;
	s_fTimeSinceLastClientUpdates = 0.0f;
	if (s_bExternalNetworkActive)
	{
		FlushBufferedMessages();

#ifdef ASURA_SUPPORT_NETMSG_SYSTEM
		Asura_NetMsg_System::Shutdown();
#endif

		if (s_bServer)
		{
			StopServer();
		}
		else
		{
			StopClient();
		}

		PlatformShutdown();
	}
#if _DEBUG && defined(ASURA_SUPPORT_NETMSG_SYSTEM)
	else
	{
		// we still register messages in debug builds.
		Asura_NetMsg::UnregisterAll();
	}
#endif

	// destroy any messages in queue - must be done before message pool is deleted
	while(g_pxMessageQueue)
	{
		Asura_Message_Node* next = g_pxMessageQueue->Next;
		
		DestroyMessage(g_pxMessageQueue->Message);

		delete g_pxMessageQueue;
		g_pxMessageQueue = next;
	}
	g_pxMessageQueueLast = 0;

	// MGH 24/9/2007: The message pool should be empty at this point; if not then we may be leaking memory,
	// since deleting the pool doesn't free the allocated message data.
	Asura_Assertf(!s_pxMessagePool || s_pxMessagePool->GetNumAllocated() == 0, ("Leaked %d network messages. Possible memory leak.", s_pxMessagePool->GetNumAllocated()));
#ifdef ASURA_ASSERT
	if(s_pxMessagePool && s_pxMessagePool->GetNumAllocated())
	{
		Asura_Mempool_ConstIterator xIt(*s_pxMessagePool);
		while(!xIt.Done())
		{
			const Asura_Message* pxMessage = reinterpret_cast<const Asura_Message*>(xIt.GetCurrent());
			const char* szID = Asura_Network::GetMsgIDDesc(pxMessage->GetMsgID());
			Asura_Assertf(0, ("Leaked message from %u to %u, ID %s", pxMessage->GetFromGuid(), pxMessage->GetToGuid(), szID));
			xIt.Next();
		}
	}
#endif

	delete s_pxMessagePool;
    s_pxMessagePool = NULL;

	delete[] s_pxPlayerInfo;
	s_pxPlayerInfo = NULL;

	delete [] s_pxClientMachineStatus;
	s_pxClientMachineStatus = NULL;

	delete s_pxClientMachineHandleList;
	s_pxClientMachineHandleList = NULL;

#ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
	if (s_bExternalNetworkActive)
	{
		delete s_pxMsgBuffManager;
		s_pxMsgBuffManager = NULL;
	}
#endif

	delete s_pxGuidRecycler;
	s_pxGuidRecycler = NULL;

	s_pfnProject_ClientMessageHandler = NULL;
	s_pfnProject_ServerMessageHandler = NULL;

	s_xClientsAwaitingCreateMessages.Clear();
#ifdef ASURA_SUPPORT_NETMSG_SYSTEM
	s_xClientsIgnoringNetMsgUpdates.Clear();
#endif //#ifdef ASURA_SUPPORT_NETMSG_SYSTEM
	// Clear this list manually, since we need to delete the data.
	Asura_ListElt<CreatingClientData*>* pxElement = s_xClientsReceivingCreateMessages.Front();
	while (pxElement)
	{
		Asura_ListElt<CreatingClientData*>* pxCurrentElement = pxElement;
		pxElement = pxElement->Next();
		CreatingClientData* pxData = pxCurrentElement->Data();
		s_xClientsReceivingCreateMessages.Remove(pxCurrentElement);
		delete pxData;
	}

	s_pfnClientCreateMessagesSentFn = NULL;
	s_fTimeUntilNextCreateMessageBatch = 0.0f; // TODO - should this initialise to the max time, instead of sending some messages immediately?
	s_bExternalNetworkActive = false;

	Asura_OutputToDebugger(("Network shut down.\n"));
}


// (MH - 08.01.2004) hmm, it looks as though Dredd created the player on the 
// client before Asura_Network::StartGame() was called so it was possible that
// the client guid hadn't been received yet. I think it got away with it due
// to the level load happening between requesting it and the player being 
// created and DirectPlay carried on receiving messages during the load. The
// new GameSpy GT2 code doesn't run during level loads so it no longer works 
// reliably. Therefore I'm going to get code to call this function before 
// creating players. It is a bettern solution to Asura_Network::StartGame()
// blocking in any case.
bool Asura_Network::IsWaitingForClientGuid(void)
{
    Asura_Assert(Asura_Network::IsNetworkInitialised(), "network not initialised");
	if (!s_bServer)
	{
		return !s_bReceivedValidClientGuids;
	}
	else
	{
		return false;
	}
}

void Asura_Network::StartGame(void)
{
    Asura_Assert(Asura_Network::IsNetworkInitialised(), "network not initialised");

	// KJL - 16/07/2003 18:52:39 - set time so that update occurs during first frame
	ForceClientUpdateAtEndOfFrame();

	Asura_Assert(!IsWaitingForClientGuid(), "client guids not recieved; will block"); //see comment above IsWaitingForClientGuid
	if (!s_bServer)
	{
		while(!s_bReceivedValidClientGuids)
		{
			Update();
			Asura_Timers::HandlePrimaryTimer();
		}
	}

	s_bReadyForMessages = true;// (MH - 22.05.2003) was before wait for client guids.

// (MH - 01.09.2003) moved into initialise()
//	if (s_bExternalNetworkActive && s_bServer)
//	{	
//		NewUpdatePackage();
//	}

	Platform_StartGame();
	
// (MH - 11.02.2004) looking at this, it seems to be unlikely to give the correct result.
// The message is sent guaranteed when the server calls StartGame so most clients won't 
// receive it as they won't be connected yet, and if they do it might have been delayed
// due to being resent. So I'm removing it. The initial instant sync is done by the valid
// client guid message (which also suffers from being guaranteed).
//	// if server, ensure client clocks are in sync
//	if (s_bServer)
//	{
//		Asura_Message* pxTimeSyncMessage = CreateMessage(ASURA_MSG_STARTGAME);
//		if (pxTimeSyncMessage)
//		{
//			pxTimeSyncMessage->UseGuaranteedDelivery();
//			pxTimeSyncMessage->To = ASURA_GUID_ALL_OTHER_CLIENTMACHINES;
//			Send(pxTimeSyncMessage);
//		}
//	}
}

void Asura_Network::SetServerLANReporting(bool bEnable)
{
	Platform_SetServerLANReporting(bEnable);
}

void Asura_Network::ForceClientUpdateAtEndOfFrame( void )
{
    s_fTimeSinceLastClientUpdates = fASURA_NETWORK_TIME_BETWEEN_ENTITY_UPDATES;
	s_bSendThisFrame = true;
}

bool Asura_Network::ShouldMessageBeSentToClient( const Asura_Message* pxMessage, Asura_Guid uClientID )
{
#ifdef ASURA_SUPPORT_NETMSG_SYSTEM
	if (pxMessage && pxMessage->GetMsgID() == ASURA_MSG_NETMSGUPDATE)
	{
		const Asura_ListElt<Asura_Guid>* pxElement = s_xClientsIgnoringNetMsgUpdates.Front();
		while (pxElement)
		{
			if (pxElement->Data() == uClientID)
			{
				return false;
			}

			pxElement = pxElement->Next();
		}
	}
#endif //#ifdef ASURA_SUPPORT_NETMSG_SYSTEM

	return Project_ShouldMessageBeSentToClient( pxMessage, uClientID );
}


//#include "Asura_Text_Output.h" 
void Asura_Network::Update(void)
{
    // (MH - 28.03.2003) allow this to be called whenever
	if (!Asura_Network::IsNetworkInitialised()) return;

	ASURA_CPU_PROFILE__START_LABEL(ASURA_CPU_PROFILE__NETWORKMESSAGES, "Network Messages");

	// (MH - 31.01.2007) work out whether we want to actually send data over the network this frame.
	// This happens at fASURA_NETWORK_TIME_BETWEEN_ENTITY_UPDATES intervals (0.1s). It used to be 
	// done in EndOfFrameUpdate(), but I've changed it so other code can find out whether we 're 
	// sending this frame or not.
#ifdef ASURA_UPDATE_CLIENTENTITIES_EVERY_FRAME
	s_bSendThisFrame = true;
#else //#ifdef ASURA_UPDATE_CLIENTENTITIES_EVERY_FRAME
	{
		s_fTimeSinceLastClientUpdates += Asura_Timers::GetFrameTime();

		// KOR - 10/03/2006 - in GCC builds, this was only updating every
		// (fASURA_NETWORK_TIME_BETWEEN_ENTITY_UPDATES + frametime) seconds,
		// presumably because it was *just* under fASURA_NETWORK_TIME_BETWEEN_ENTITY_UPDATES
		// on the previous frame. Adding this slack fixes this problem.
		const float fEpsilon = 0.01f;
		if (s_fTimeSinceLastClientUpdates >= fASURA_NETWORK_TIME_BETWEEN_ENTITY_UPDATES - fEpsilon)
		{
			s_bSendThisFrame = true;
		}
	}
#endif //#else //#ifdef ASURA_UPDATE_CLIENTENTITIES_EVERY_FRAME

	if (s_bExternalNetworkActive)
	{
		PlatformUpdate();
		
		// AJG - 21/01/07 - Initialisation list update. Since clients on the server never get added to these lists, the external network active test is a safe limitation even if the game always tries to add all clients to the list.
		if (IsServer())
		{
			UpdateClientCreateMessageList();
		}

		//if (s_bReadyForMessages)
		{
			Platform_LockMessageQueue();

			u_int uNumberOfMessages = 0;

			// if not the server, sync the clocks with the first non-guaranteed message
			// MH (11.11.05) - this code used to pick the first message for syncing the 
			// time, when it should have been picking the earliest message. Since NetMsg
			// has a sent time of when it was started (probably 0.1 s earlier than when 
			// it is actually sent) it is nearly always the earliest message but gets added
			// to the packed message buffer last. This code searches for the earliest message.
			// TODO: possibly should use (ServerTime when package was sent - 0.1s) instead?
			if (g_pxMessageQueue && !IsServer())
			{
				// loop through messages to find earliest
				float fEarliest = Asura_Maths::LargeFloat;
				Asura_Message_Node* pxMsgNode = g_pxMessageQueue;
				while (pxMsgNode)
				{
					const Asura_Message* pxMessage = pxMsgNode->Message;
					if ( (!pxMessage->IsUsingGuaranteedDelivery()) || (!Asura_Timers::HasServerAbsoluteTimeBeenSynchronised()) )
					{
						if (pxMessage->GetSentTime() < fEarliest)
						{
							fEarliest = pxMessage->GetSentTime();
						}
					}
					pxMsgNode = pxMsgNode->Next;
				}

				if (fEarliest != Asura_Maths::LargeFloat)
				{
					Asura_Timers::UpdateServerAbsoluteTime(fEarliest, GetLatency(), false);
				}
			}

			while(g_pxMessageQueue)
			{
				Asura_Message_Node* pxNext = g_pxMessageQueue->Next;

				
				// send the message, but don't change the sent time
				Send(g_pxMessageQueue->Message, true, false);

				delete g_pxMessageQueue;
			
				g_pxMessageQueue = pxNext;

				uNumberOfMessages++;
			}


			g_pxMessageQueueLast = 0;

//			Asura_Text_Output::Print(10,10,0xffffffff,0,"Number of queued messages %d",uNumberOfMessages);
		
#if _DEBUG
			Asura_Assert(s_pxMessagePool->GetNumAllocated() == 0, "clearing message queue didn't clear out message pool");
#endif
			
			Platform_UnlockMessageQueue();

			if (uNumberOfMessages)
			{
				s_uTimeOfLastMessageReceived = Asura_Timers::GetMSecsSinceStart();
			}
		}
		
	}

	ASURA_CPU_PROFILE__STOP(ASURA_CPU_PROFILE__NETWORKMESSAGES);
}


void Asura_Network::EndOfFrameUpdate(void)
{
	ASURA_CPU_PROFILE__START_LABEL(ASURA_CPU_PROFILE__NETWORKMESSAGES, "Network Messages");

    Asura_Assert(Asura_Network::IsNetworkInitialised(), "network not initialised");

	if (s_bSendThisFrame)
	{
		// Don't send for at least another 0.1s
		s_fTimeSinceLastClientUpdates = 0;

		if (s_bServer)
		{
			#ifdef ASURA_SUPPORT_NETMSG_SYSTEM
			if (s_bExternalNetworkActive)
			{
				Asura_NetMsg_System::BeginUpdate();
			}
			#endif

			// tell the entity system it's the last chance to create messages before the next send
			Asura_Entity_System::GetClientUpdates(true);
		}

		if (s_bExternalNetworkActive && s_bServer)
		{
			#ifdef ASURA_SUPPORT_NETMSG_SYSTEM
			// this update is from the server
			Asura_NetMsg_System::SendUpdate();
			#endif

			#ifdef ASURA_USE_COLLISIONFX
			Asura_CollisionFX_System::SendClientUpdateMessage();
			#endif //ASURA_USE_COLLISIONFX
		}

		// send any messages that have been packed into the message buffers.
		FlushBufferedMessages();

		s_bSendThisFrame = false;
	}
	else if (s_bServer)
	{
		#ifdef ASURA_SUPPORT_NETMSG_SYSTEM
		if (s_bExternalNetworkActive)
		{
			Asura_NetMsg_System::BeginUpdate();
		}
		#endif
		
		// give the entity system a chance to do some work creating messages
		Asura_Entity_System::GetClientUpdates(false);
	}

	Platform_EndOfFrame();

	ASURA_CPU_PROFILE__STOP(ASURA_CPU_PROFILE__NETWORKMESSAGES);
}


void Asura_Network::BeginClientToServerUpdates(void)
{
//#ifdef ASURA_SUPPORT_NETMSG_SYSTEM
//	if (s_bExternalNetworkActive)
//	{
//		Asura_NetMsg_System::BeginUpdate(false);
//	}
//#endif
}


void Asura_Network::EndClientToServerUpdates(void)
{
//#ifdef ASURA_SUPPORT_NETMSG_SYSTEM
//	if (s_bExternalNetworkActive)
//	{
//		// this update is from the client, not the server
//		Asura_NetMsg_System::EndUpdate(false);
//	}
//#endif
}


void Asura_Network::StartServer(void)
{
    Asura_Assert(Asura_Network::IsNetworkInitialised(), "network not initialised");
	if(s_bExternalNetworkActive)
	{
		#ifdef ASURA_ASSERT
		bool bOK = 
		#endif
		PlatformStartServer();
		Asura_Assert(bOK, "failed to start server. Need to add error handling.");
	}
}


void Asura_Network::StopServer(void)
{
    Asura_Assert(Asura_Network::IsNetworkInitialised(), "network not initialised");
	if(s_bExternalNetworkActive)
	{
		PlatformStopServer();
	}
}


void Asura_Network::StartClient(const char* szIPAddressToConnectTo)
{
    Asura_Assert(Asura_Network::IsNetworkInitialised(), "network not initialised");
	if(s_bExternalNetworkActive)
	{
		Asura_Assert(szIPAddressToConnectTo, "must specific server address");
		// set the waiting flags
		s_bWaitingForConnection = true;
		s_bConnectionOkay = false;
		// (MH - 17.09.2003) note: I moved the above 2 status flags setting
		// to above StartClient just in case the other thread changes them
		// before it even returns (might happen on LAN).
		PlatformStartClient(szIPAddressToConnectTo);
	}
}


void Asura_Network::StopClient(void)
{
    Asura_Assert(Asura_Network::IsNetworkInitialised(), "network not initialised");
	if(s_bExternalNetworkActive)
	{
		PlatformStopClient();
	}
}


#ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
void Asura_Network::BufferMessageToServer(const Asura_Message* pxMessage)
{
	Asura_Assert(!IsServer(), "server shouldn't send messages to itself over external network");

#ifdef ASURA_SUPPORT_NETMSG_SYSTEM
	// (MH - 27.05.2003) see if this message is registered with NetMsg system
	// (MH - 25.05.2005) if an update is guaranteed for some reason, send it direct.
	if (!pxMessage->IsUsingGuaranteedDelivery())
	{
		if (Asura_NetMsg_System::AddAsuraMessage(pxMessage))
		{
			return;
		}
	}
#endif // #ifdef ASURA_SUPPORT_NETMSG_SYSTEM

	// machine 0 is always server
	s_pxMsgBuffManager->AddMessage(pxMessage, 0);
}


void Asura_Network::BufferMessageToAllRemoteClients(const Asura_Message* pxMessage)
{
	Asura_Assert(IsServer(), "only server should send messages to clients");

	// could improve this possibly by having a buffer manager for 'all clients'.
	// That would increase the chances of messages ending up out-of-order though.

#ifdef ASURA_NETWORK_USE_BROADCAST_FOR_GAME_DATA
    // (MH - 03.10.2005) only bother using shared buffer non-guaranteed and if NetMsg system isn't doing LOD updates.
	if (!pxMessage->IsUsingGuaranteedDelivery())
	{
		bool bUseSharedBuffer = !Asura_NetMsg_System::AreLODUpdatesAllowed() || !Asura_NetMsg_System::IsUsingLODUpdates();
		if (bUseSharedBuffer)
		{
			s_pxMsgBuffManager->AddBroadcastMessage( pxMessage );
			return;
		}
	}
#endif // #ifdef ASURA_NETWORK_USE_BROADCAST_FOR_GAME_DATA

	// skip machine 0 - always server
	Asura_Tracking_Handle_List::Iterator xIterator = s_pxClientMachineHandleList->GetIterator();
	xIterator.Begin();
    while (!xIterator.End())
    {
        int iMachine = xIterator.GetCurrentHandle();
		
		if (iMachine != 0)
		{
			s_pxMsgBuffManager->AddMessage(pxMessage, iMachine);
		}

        xIterator.Next();
    }
}


void Asura_Network::BufferMessageToSingleClient(const Asura_Message* pxMessage, int iMachine)
{
	// machine 0 is always server
	if (iMachine == 0)
	{
		BufferMessageToServer(pxMessage);
	}
	else
	{
		Asura_Assert(IsServer(), "only server should send messages to clients");
		
		s_pxMsgBuffManager->AddMessage(pxMessage, iMachine);
	}
}
#endif // #ifdef ASURA_SUPPORT_EXTERNAL_NETWORK


void Asura_Network::Send( Asura_Message* pxMsg, bool bDestroyMsg, bool bSetSentTime )
{
    Asura_Assert(Asura_Network::IsNetworkInitialised(), "network not initialised");
	Asura_Assert(s_bServer||s_bExternalNetworkActive,"No server OR external network!?");
    Asura_Assert(pxMsg, "NULL message ptr");

	// (MH - 25.11.2003) now setting SentTime when message gets sent instead of 
	// relying on calling function setting it correctly.
	// Must not do this if this message came from the message queue, or if we're in a recursion.
	if (bSetSentTime)
	{
		pxMsg->SentTime = Asura_Timers::GetServerAbsoluteTime();
	}
	
	// test message packing by packing and unpacking on this machine.
#if 0 //_DEBUG
	{
		const bool bGuaranteed = pxMsg->IsUsingGuaranteedDelivery();
		u_char aucBuffer[16384];
		u_int uTotalSize = Asura_MessagePacking::PackMessageToBuffer( aucBuffer, 16384, *pxMsg );
		Asura_Assert(uTotalSize, "");

		DestroyMessage(pxMsg);

		Asura_MessageHeader xMsgHeader;
		u_int uSizeOfPackedData = 0;
		const void* pData = Asura_MessagePacking::UnpackBufferToMessageHeader( aucBuffer, bGuaranteed, xMsgHeader, uSizeOfPackedData );

		pxMsg = Asura_Network::CreateMessage(xMsgHeader.m_usMsgID, xMsgHeader.m_uDataSize, xMsgHeader.m_usEntityClassification);
		
		if (pxMsg)
		{
			pxMsg->From = xMsgHeader.m_uFromGuid;
			pxMsg->To = xMsgHeader.m_uToGuid;
			pxMsg->SentTime = xMsgHeader.m_fSentTime;
			 
			if (xMsgHeader.m_uDataSize)
			{
				if (uSizeOfPackedData)
				{
					Asura_NetMsg_System::UnpackMessageData(*pxMsg, uSizeOfPackedData, pData);
				}
				else
				{
					Asura_Mem::Copy(pxMsg->GetData(), pData, xMsgHeader.m_uDataSize);
				}
			}
		}

	}
#endif // _DEBUG - test message packing by packing and unpacking on this machine.

	switch (pxMsg->To)
	{
		case ASURA_GUID_SERVER:
		{
			if (s_bServer)
			{
				ServerMessageHandler(pxMsg);
			}
#ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
			else
			{
				if (s_bExternalNetworkActive)
				{
					BufferMessageToServer(pxMsg);
				}
			}
#endif
			break;
		}

		case ASURA_GUID_CLIENT:
		{
			ClientMessageHandler(pxMsg);
			break;
		}

		case ASURA_GUID_ALL_PLAYERS:
		{		
			Asura_Entity_System::DeliverMessageToAllPlayers(pxMsg);

#ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
			if (s_bServer)
			{
				if (s_bExternalNetworkActive)
				{
					AddToClientUpdatePackage(pxMsg);
				}
				#if _DEBUG && defined(ASURA_SUPPORT_NETMSG_SYSTEM)
				else
				{
					// attempt to register the message.
					const Asura_NetMsg* pxNetMsg = Asura_NetMsg::FindNetMsgObject(pxMsg->GetMsgID(), pxMsg->GetEntityClassification());
					if (!pxNetMsg)
					{
						// not found, attempt to get the entity system to register it.
						Asura_Entity_System::RegisterUpdateMessage(pxMsg->GetMsgID(), pxMsg->GetEntityClassification());
					}
				}
				#endif // #if _DEBUG && defined(ASURA_SUPPORT_NETMSG_SYSTEM)
			}
#endif // #ifdef ASURA_SUPPORT_EXTERNAL_NETWORK

			break;
		}

		case ASURA_GUID_OTHER_PLAYERS:
		{		
			Asura_Entity_System::DeliverMessageToOtherPlayers(pxMsg);

#ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
			if (s_bServer)
			{
				if (s_bExternalNetworkActive)
				{
					AddToClientUpdatePackage(pxMsg);
				}
				#if _DEBUG && defined(ASURA_SUPPORT_NETMSG_SYSTEM)
				else
				{
					// attempt to register the message.
					const Asura_NetMsg* pxNetMsg = Asura_NetMsg::FindNetMsgObject(pxMsg->GetMsgID(), pxMsg->GetEntityClassification());
					if (!pxNetMsg)
					{
						// not found, attempt to get the entity system to register it.
						Asura_Entity_System::RegisterUpdateMessage(pxMsg->GetMsgID(), pxMsg->GetEntityClassification());
					}
				}
				#endif // #if _DEBUG && defined(ASURA_SUPPORT_NETMSG_SYSTEM)
			}
#endif // #ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
			break;
		}

		case ASURA_GUID_MY_CLIENT_ENTITIES:
		{
#if _DEBUG && defined(ASURA_NETMSG_PACK_ALL_DATA)
			// test NetMsg packing works correctly, even before remote clients join
			if (s_bExternalNetworkActive && GetNumberOfMachines() == 1 && 
				!Asura_Network::IsMessageDataInNetworkByteOrder(pxMsg->GetMsgID(), pxMsg->GetEntityClassification()))
			{
				u_int uDataSize;
				const void* pData;
				Asura_NetMsg_System::PackMessageData(*pxMsg, uDataSize, pData);
			}
#endif
			if( IsClient() )
            {

				Asura_Entity_System::DeliverMessageToClientEntity(pxMsg);
    		}

#ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
			if (s_bServer)
			{
				if (s_bExternalNetworkActive)
				{
					AddToClientUpdatePackage(pxMsg);
				}
				#if _DEBUG && defined(ASURA_SUPPORT_NETMSG_SYSTEM)
				else
				{
					// attempt to register the message.
					const Asura_NetMsg* pxNetMsg = Asura_NetMsg::FindNetMsgObject(pxMsg->GetMsgID(), pxMsg->GetEntityClassification());
					if (!pxNetMsg)
					{
						// not found, attempt to get the entity system to register it.
						Asura_Entity_System::RegisterUpdateMessage(pxMsg->GetMsgID(), pxMsg->GetEntityClassification());
					}
				}
				#endif // #if _DEBUG && defined(ASURA_SUPPORT_NETMSG_SYSTEM)
			}
#endif // #ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
			break;
		}

		case ASURA_GUID_ALL_MACHINES:
			Asura_Assert(IsServer(),"this isn't a server!");
			pxMsg->To = ASURA_GUID_SERVER;
            ServerMessageHandler( pxMsg );
            // And fall through to CLIENTMACHINES case.
			//lint -fallthrough
		case ASURA_GUID_ALL_CLIENTMACHINES:
		{
			Asura_Assert(IsServer(),"this isn't a server!");

			pxMsg->To = ASURA_GUID_CLIENT;
	
#ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
			Asura_Tracking_Handle_List::Iterator xIterator = s_pxClientMachineHandleList->GetIterator();
			xIterator.Begin();
            while (!xIterator.End())
            {
                int uIndex = xIterator.GetCurrentHandle();
				
				// skip the server (always machine 0)
				if (uIndex!=0)
				{
					BufferMessageToSingleClient(pxMsg, uIndex);
				}


                xIterator.Next();
            }
#endif // #ifdef ASURA_SUPPORT_EXTERNAL_NETWORK

			if( IsClient() )
			{
				// send to this machine, destroy message, but keep SentTime
				Send(pxMsg, true, false);
				// return as break leads to DestroyMessage being called.
				return;
			}
			break;
		}

		case ASURA_GUID_ALL_OTHER_CLIENTMACHINES:
		{
			Asura_Assert(IsServer(),"this isn't a server!");

			pxMsg->To = ASURA_GUID_CLIENT;

#ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
			if (s_bExternalNetworkActive)
			{
				BufferMessageToAllRemoteClients( pxMsg );
			}
#endif
			break;
		}
		
		case ASURA_GUID_OTHER_PLAYERS_CLIENTMACHINES:
		{
			Asura_Assert(IsServer(),"this isn't a server!");
		
			pxMsg->To = ASURA_GUID_CLIENT;
	
#ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
			Asura_Tracking_Handle_List::Iterator xIterator = s_pxClientMachineHandleList->GetIterator();
			xIterator.Begin();
            while (!xIterator.End())
            {
                int iIndex = xIterator.GetCurrentHandle();
				
				// skip the server (always machine 0)
				// and whichever machine has the player in the From field
				if (iIndex!=0 && !s_pxClientMachineStatus[iIndex].DoesMachineHavePlayer(pxMsg->From))
				{
					BufferMessageToSingleClient(pxMsg, iIndex);
				}


                xIterator.Next();
            }
#endif // #ifdef ASURA_SUPPORT_EXTERNAL_NETWORK

			// finally, send on to server (if client machine, which dedicated server might not be) 
			// and have it deleted.
			if( IsClient() && !s_pxClientMachineStatus[0].DoesMachineHavePlayer(pxMsg->From) )
			{
				// send to this machine, destroy message, but keep SentTime
				Send(pxMsg, true, false);
				// return as break leads to DestroyMessage being called.
				return;
			}
			break;
		}

		case ASURA_GUID_PLAYERS_CLIENTMACHINE:
		{
			Asura_Assert(IsServer(),"this isn't a server!");

			pxMsg->To = ASURA_GUID_CLIENT;

			bool bFoundMachine = false;
			Asura_Tracking_Handle_List::Iterator xIterator = s_pxClientMachineHandleList->GetIterator();
			xIterator.Begin();
            while (!xIterator.End())
            {
                int iIndex = xIterator.GetCurrentHandle();
				
				if (s_pxClientMachineStatus[iIndex].DoesMachineHavePlayer(pxMsg->From))
				{
					bFoundMachine = true;
					if (iIndex==0)
					{
						// machine is Server
						// send to this machine, destroy message, but keep SentTime
						Send(pxMsg, true, false);
						// return as break leads to DestroyMessage being called.
						return;
					}
					else
					{
#ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
						BufferMessageToSingleClient(pxMsg, iIndex);
#endif
					}
					break;
				}

                xIterator.Next();
            }

			if (!bFoundMachine)
			{
				Asura_Assertf(bFoundMachine, ("no machine found to send message to client machine with Guid %d", pxMsg->From));
#ifdef ASURA_SUPPORT_COMMAND_CONSOLE
				static u_int ls_uLastConsoleMsgTime = 0;
				u_int uCurTime = Asura_Timers::GetMSecsSinceStart();
				// only display this message once every 5 minutes.
				if (uCurTime > ls_uLastConsoleMsgTime + (60000*5))
				{
					ls_uLastConsoleMsgTime = uCurTime;
					Asura_CommandConsole::Printf( L"client machine with Guid %d does not exist. Known machines (%d) have players:-", pxMsg->From, GetNumberOfMachines() );
					Asura_Tracking_Handle_List::Iterator xIt = s_pxClientMachineHandleList->GetIterator();
					xIt.Begin();
					while (!xIt.End())
					{
						int iIndex = xIt.GetCurrentHandle();
						if (s_pxClientMachineStatus[iIndex].GetNumberOfPlayers())
						{
							Asura_CommandConsole::Printf( L"%d: %d", iIndex, s_pxClientMachineStatus[iIndex].GetPlayerGuid(0) );
						}
						xIt.Next();
					}
					Asura_CommandConsole::Printf( L"This message will not be displayed again for at least 5 minutes" );
				}
#endif // #ifdef ASURA_SUPPORT_COMMAND_CONSOLE
			}
			break;
		}

		case ASURA_GUID_PLAYER :	
		{
			// client machine
#ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
			if( !s_bServer )
			{
				// pass message to directly server for handling there
				BufferMessageToServer(pxMsg);
				break;
			}
#endif // #ifdef ASURA_SUPPORT_EXTERNAL_NETWORK

			// server
			Asura_Assert(IsServer(),"Sanity Check - this isn't a server!");

			// search through the client machines for the player in the from field
			Asura_Tracking_Handle_List::Iterator xIterator = s_pxClientMachineHandleList->GetIterator();
			xIterator.Begin();
            while (!xIterator.End())
            {
				int iIndex = xIterator.GetCurrentHandle();
				
				if (s_pxClientMachineStatus[iIndex].DoesMachineHavePlayer(pxMsg->From))
				{
					if (iIndex==0)
					{
						// machine is Server
						pxMsg->To = pxMsg->From; // need to switch the from and to fields for the 
												 // entity system message delivery to work correctly
						Asura_Entity_System::DeliverMessageToPlayerEntity(pxMsg);
#if _DEBUG && defined(ASURA_NETMSG_PACK_ALL_DATA)
						// test NetMsg packing works correctly, even before remote clients join
						if (s_bExternalNetworkActive && GetNumberOfMachines() == 1 && 
							!Asura_Network::IsMessageDataInNetworkByteOrder(pxMsg->GetMsgID(), pxMsg->GetEntityClassification()))
						{
							u_int uDataSize;
							const void* pData;
							Asura_NetMsg_System::PackMessageData(*pxMsg, uDataSize, pData);
						}
#endif
					}
					else
					{
#ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
						// readdress message to send to a client machine
						pxMsg->To = ASURA_GUID_PLAYER_ONTHISMACHINE;
						BufferMessageToSingleClient(pxMsg, iIndex);
#else 
						Asura_Assert(false, "not handled");
#endif
					}
					break;
				}
				xIterator.Next();
			}
		
			break;
		}

		case ASURA_GUID_PLAYER_ONTHISMACHINE :
		{
			pxMsg->To = pxMsg->From; // need to switch the from and to fields for the 
										 // entity system message delivery to work correctly
			Asura_Entity_System::DeliverMessageToPlayerEntity(pxMsg);
#if _DEBUG && defined(ASURA_NETMSG_PACK_ALL_DATA)
			// test NetMsg packing works correctly, even before remote clients join
			if (s_bExternalNetworkActive && GetNumberOfMachines() == 1 && 
				!Asura_Network::IsMessageDataInNetworkByteOrder(pxMsg->GetMsgID(), pxMsg->GetEntityClassification()))
			{
				u_int uDataSize;
				const void* pData;
				Asura_NetMsg_System::PackMessageData(*pxMsg, uDataSize, pData);
			}
#endif
			break;
		}
	
		default:
		{
			if (s_bServer)
			{
#ifdef ASURA_SUPPORT_NETMSG_SYSTEM
				// HACK to test the system on a single machine
				if (dbg_bTestNetMsgSystemFromClient)
				{
					if (!Asura_NetMsg_System::AddAsuraMessage(pxMsg))
					{
						Asura_Entity_System::DeliverMessageToServerEntity(pxMsg);
					}
				}
				else
#endif // #ifdef ASURA_SUPPORT_NETMSG_SYSTEM
				{
#if defined(ASURA_USE_STREAMING)
					const Asura_Guid			uToGuid			= pxMsg->GetToGuid();
					const Asura_Guid			uFromGuid		= pxMsg->GetFromGuid();
					const Asura_Entity_Server*	pxToEntity		= Asura_Entity_Server::FindEntity(uToGuid);
					const Asura_Entity_Server*	pxFromEntity	= Asura_Entity_Server::FindEntity(uFromGuid);
					const Asura_Hash_ID			uToZoneHash		= (pxToEntity == NULL)
																? ASURA_HASH_ID_UNSET
																: pxToEntity->GetZoneHashID();
					const Asura_Hash_ID			uFromZoneHash	= (pxFromEntity == NULL)
																? ASURA_HASH_ID_UNSET
																: pxFromEntity->GetZoneHashID();

					// Do our zones differ?
					// MGH 20/6/2006: Some messages from the static message system need to be queued as the handlers aren't set.
					if(uToZoneHash == uFromZoneHash && !Asura_Streaming_System::GetShouldQueueMessages())
					{
						Asura_Entity_System::DeliverMessageToServerEntity(pxMsg);
					}
					else
					{
						Asura_Streaming_Zone*	pxToZone	= Asura_Streaming_System::FindMasterZone(uToZoneHash);

						if(pxToZone == NULL)
						{
							Asura_Entity_System::DeliverMessageToServerEntity(pxMsg);
						}
						else
						{
							Asura_Streaming_ZoneEntities*	pxEntities	= pxToZone->GetEntities();

							if(pxEntities == NULL)
							{
								Asura_Entity_System::DeliverMessageToServerEntity(pxMsg);
							}
							else
							{
								pxEntities->AddMessageToQueue(pxMsg);
								return;
							}
						}
					}
#else
					Asura_Entity_System::DeliverMessageToServerEntity(pxMsg);
#endif	// defined(ASURA_USE_STREAMING)
				}
			}
			else
			{
#ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
				// (MH - 13.05.2003) should probably address the message as 
				// To: ASURA_GUID_SERVER_ENTITY, From: Guid.
				// restricting To: field to values from 0-255 and valid guids
				// (From: field) from 0-16777215 (1<<24-1) would enable better
				// packing in messages that get send across the external network.
				BufferMessageToServer(pxMsg);			
#else
				Asura_Assert(false, "not handled");
#endif
			}
			break;
		}
		
	}
	
	// message no longer required; delete it
    if( bDestroyMsg )
    {
	    DestroyMessage(pxMsg);
    }
}	

Asura_Message* Asura_Network::DuplicateMessage( const Asura_Message* pxSrcMsg, bool bPointToExistingData )
{
    const void* pvSrcData = pxSrcMsg->GetData();
    const u_int uDataSize = pxSrcMsg->GetDataSize();

	Asura_Message* pxNewMsg = CreateMessage( pxSrcMsg->GetMsgID(),
                                             uDataSize,
                                             pxSrcMsg->GetEntityClassification(),
                                             (bPointToExistingData ? pvSrcData : NULL) );
    
    if( !pxNewMsg ) { return NULL; }

    pxNewMsg->To            = pxSrcMsg->To;
    pxNewMsg->From          = pxSrcMsg->From;
    pxNewMsg->SentTime      = pxSrcMsg->SentTime;
    pxNewMsg->m_bGuaranteed = pxSrcMsg->m_bGuaranteed;

    if( uDataSize && !bPointToExistingData )
    {
        Asura_Mem::Copy( pxNewMsg->GetData(), pxSrcMsg->GetData(), uDataSize );
    }

    return pxNewMsg;
}

/// Richard May - 20/01/2003 15:17:28 - This function makes a copy of the message and sends it to the specified Guid. If
/// the from field is left as ASURA_GUID_UNREGISTERED (the default), the from field in the original message is used 
void Asura_Network::Forward( const Asura_Message* pxMessage, Asura_Guid uTo, Asura_Guid uFrom/*=ASURA_GUID_UNREGISTERED*/, bool bForceUseGuaranteed/*=false*/ )
{	
    Asura_Assert( pxMessage, "ERROR - message pointer is invalid" );
    if( !pxMessage ) { return; }
  
    Asura_Message* pxNewMessage = Asura_Network::DuplicateMessage( pxMessage, false );
    if( !pxNewMessage ) { return; }

    pxNewMessage->To = uTo;
    pxNewMessage->SetAsForwarded();

    if( uFrom != ASURA_GUID_UNREGISTERED )
    {
        pxNewMessage->From = uFrom;
    }

    if( bForceUseGuaranteed )
    {
        pxNewMessage->m_bGuaranteed = bForceUseGuaranteed;
    }

    Asura_Network::Send( pxNewMessage );
}

// SendToSpecificMachine allows a message to be sent to a single client machine from the server.
// It is currently only used for routing Create messages to a specific machine in order to 
// conserve bandwidth. If uClientGuid is ASURA_GUID_UNREGISTERED the message is sent to all
// client machines.
void Asura_Network::SendToSpecificMachine( Asura_Message* pxMsg, Asura_Guid uClientGuid )
{
	Asura_Assert(IsServer(), "not server");

	if (uClientGuid == ASURA_GUID_UNREGISTERED)
	{
		Send(pxMsg);
	}
	else
	{
		Asura_Handle iMachine = GetMachineForPlayer( uClientGuid, false );	// don't include bots.
		if (iMachine != ASURA_INVALID_HANDLE)
		{
			pxMsg->SentTime = Asura_Timers::GetServerAbsoluteTime();
			
			// server is machine 0
			if (iMachine == 0)
			{
#if _DEBUG && defined(ASURA_NETMSG_PACK_ALL_DATA)
				// test NetMsg packing works correctly, even before remote clients join
				if (s_bExternalNetworkActive && GetNumberOfMachines() == 1 && 
					!Asura_Network::IsMessageDataInNetworkByteOrder(pxMsg->GetMsgID(), pxMsg->GetEntityClassification()))
				{
					u_int uDataSize;
					const void* pData;
					Asura_NetMsg_System::PackMessageData(*pxMsg, uDataSize, pData);
				}
#endif
				Asura_Entity_System::DeliverMessageToClientEntity(pxMsg);
			}
			else
			{
#ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
				BufferMessageToSingleClient( pxMsg, iMachine );
#else
				Asura_Assert(false, "not handled");
#endif
			}
		}
		DestroyMessage(pxMsg);
	}
}


Asura_Message* Asura_Network::CreateMessage(unsigned short messageID, 
                                            unsigned int dataSize /*= 0*/, 
                                            unsigned short usEntityClassification /*= 0*/,
											const void* pData /* = 0 */)
{
    // (MH - 01.08.2002) added check that network system is initialised.
    // This is to cope with destructors creating messages after network has been shutdown.
    if (!s_pxMessagePool)
    {
        return NULL;
    }

	Asura_Message* m = (Asura_Message*)s_pxMessagePool->Allocate();
	
	Asura_Assert(m, "failed to allocate network message");
    if (m)
    {
		// (MH - 14.05.2003) added initialisers for To and From, should probably have parameters.
		// also note that UseGuaranteed can be set by a function call, this should have a parameter.
		// Finally, DeliveryTime is not currently supported but still (sometimes) gets set. It might
		// be useful to have a means of delaying messages, but this would also need implementing in 
		// the external network message sending code - you might as well transmit the message and let 
		// the client delay it rather than hold it on the server.
        m->To = ASURA_GUID_UNREGISTERED;
		m->From = ASURA_GUID_UNREGISTERED;

		m->m_usMsgID = messageID;
        m->m_usEntityClassification = usEntityClassification;

	    m->m_uDataSize = dataSize;
		m->m_bAllocatedOwnData = false;

	    if (dataSize)
	    {
		    if (pData)
			{
				// access to this as writable ptr is controlled by access function
				// which checks the validity of this const_cast.
				m->m_pData = const_cast<void*>(pData);
			}
			else
			{
				m->m_pData = AllocateMessageData(dataSize);
				if (m->m_pData)
				{
					m->m_bAllocatedOwnData = true;
				}
				else
				{
					// (MH - 28.04.2003) if can't alloc the data, msg is useless
					DestroyMessage(m);
					return NULL;
				}
			}
	    }
	    else
	    {
		    m->m_pData = NULL;
	    }

	    m->m_bGuaranteed = false;
		m->m_bIsForwarded = false;
    }

	return m;
}


void Asura_Network::DestroyMessage(Asura_Message* message)
{
    Asura_Assert(Asura_Network::IsNetworkInitialised(), "network not initialised");

	if (message->m_bAllocatedOwnData)
	{
		FreeMessageData(message->m_pData);
	}
	s_pxMessagePool->Deallocate(message);
}


#ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
void Asura_Network::AddMessageToQueue(Asura_Message* pxMessage)
{
    Asura_Assert(Asura_Network::IsNetworkInitialised(), "network not initialised");

	if (dbg_bPrintMessagesAddedToQueue)
	{
#ifdef _DEBUG
		Asura_OutputToDebugger(("AddMessage ID = %d, From Guid %d, To Guid %d, Data Size = %d. Data = 0x%08x. %d allocated\n", 
			pxMessage->m_usMsgID, pxMessage->From, pxMessage->To, pxMessage->m_uDataSize, pxMessage->m_uDataSize ? pxMessage->GetData() : 0, s_pxMessagePool->GetNumAllocated()));
#endif
	}

	if (pxMessage->GetMsgID() == ASURA_MSG_PACKEDMESSAGES)
	{
		Asura_Assert(pxMessage->GetDataSize(), "no data in packed message!");
		// create a new message for each packed message contained in the data
		const char* pcDataPtr = reinterpret_cast<const char*>(pxMessage->GetData());
		const char* const pcEndOfData = pcDataPtr + pxMessage->GetDataSize();
		while (pcDataPtr < pcEndOfData)
		{
			u_int uSizeOfPackedData = 0;
			Asura_MessageHeader xMsgHeader;
			pcDataPtr = reinterpret_cast<const char*>
				(Asura_MessagePacking::UnpackBufferToMessageHeader(pcDataPtr, 
					pxMessage->IsUsingGuaranteedDelivery(), xMsgHeader, uSizeOfPackedData));

			Asura_Message* pxUnpackedMessage = CreateMessage(xMsgHeader.m_usMsgID, 
				xMsgHeader.GetDataSize(), xMsgHeader.m_usEntityClassification);
			if (pxUnpackedMessage)
			{
				if (pxMessage->IsUsingGuaranteedDelivery())
				{
					pxUnpackedMessage->UseGuaranteedDelivery();
				}

				pxUnpackedMessage->From = xMsgHeader.m_uFromGuid;
				pxUnpackedMessage->To = xMsgHeader.m_uToGuid;
				pxUnpackedMessage->SentTime = xMsgHeader.m_fSentTime;
				if (xMsgHeader.GetDataSize())
				{
					if (uSizeOfPackedData)
					{
						Asura_NetMsg_System::UnpackMessageData(*pxUnpackedMessage, uSizeOfPackedData, pcDataPtr);
					}
					else
					{
						Asura_Mem::Copy(pxUnpackedMessage->GetData(), pcDataPtr, xMsgHeader.GetDataSize());
					}
				}
				AddMessageToQueue(pxUnpackedMessage);
			}
			pcDataPtr += uSizeOfPackedData ? uSizeOfPackedData : xMsgHeader.GetDataSize();
		}
		// finished with the original message now, get rid of it.
		DestroyMessage(pxMessage);
	}
	else
	{
		Asura_Message_Node* node = new Asura_Message_Node;

// used to be implemented as a stack!
//		node->Next = g_pxMessageQueue;
//		node->Message = pxMessage;
//		g_pxMessageQueue = node;

		if (g_pxMessageQueueLast)
		{
			Asura_Assert(g_pxMessageQueue, "last should be 0 iff head is 0");
			g_pxMessageQueueLast->Next = node;
		}
		else
		{
			Asura_Assert(!g_pxMessageQueue, "last should be 0 iff head is 0");
			g_pxMessageQueue = node;
		}

		g_pxMessageQueueLast = node;

		node->Next = 0;
		node->Message = pxMessage;

	}
}


void Asura_Network::AddMessageToQueue(const Asura_MessageHeader* pxMsgHeader, const void* pData, bool bWasSendGuaranteed)
{
#if ASURA_NETWORK_LOGGING
	if (GetMsgLogLevel() != MSG_LOG_LEVEL_NONE && s_bLogMessagesReceived)
	{
		bool bIndent = false;
		bool bReceived = true;
		int iMachine = -1;
		Asura_Network::LogMsgInfo( *pxMsgHeader, pData, bIndent, bReceived, iMachine);
	}
#endif//ASURA_NETWORK_LOGGING

	Asura_Network::Platform_LockMessageQueue();

	Asura_Message* m = Asura_Network::CreateMessage(pxMsgHeader->m_usMsgID, pxMsgHeader->GetDataSize(), pxMsgHeader->m_usEntityClassification);
	
	if (m)
	{
		if (bWasSendGuaranteed)
		{
			m->UseGuaranteedDelivery();
		}

		m->From = pxMsgHeader->m_uFromGuid;
		m->To = pxMsgHeader->m_uToGuid;
		m->SentTime = pxMsgHeader->m_fSentTime;
		 
		if (pxMsgHeader->GetDataSize())
		{
			Asura_Mem::Copy(m->GetData(), pData, pxMsgHeader->GetDataSize());
		}

		Asura_Network::AddMessageToQueue(m);
	}

	Asura_Network::Platform_UnlockMessageQueue();
}
#endif // #ifdef ASURA_SUPPORT_EXTERNAL_NETWORK


// GetClientID returns the Guid of the first local player. This is so 
// client entities can identify the machine they are on when requesting 
// things like Create() messages which don't need to be sent to all machines.
Asura_Guid Asura_Network::GetClientID()
{
	if (IsDedicatedServer())
	{
		return ASURA_GUID_UNREGISTERED;
	}

	Asura_Assert(IsClient(), "no client");
	Asura_Assert(!IsWaitingForClientGuid(), "client guid not yet received");
	Asura_Assert(s_uInitialClientGuid != ASURA_GUID_UNREGISTERED, "invalid client guid");

	return s_uInitialClientGuid;
}

// GetLastClientID returns the same result as GetClientID(), but can be
// (and should only be) called once Asura_Network has been shutdown. Allows 
// GUID indexed data in systems which may be active after Asura_Network has 
// shutdown to still be accessed for the local player.
Asura_Guid Asura_Network::GetLastClientID()
{
	if (IsDedicatedServer())
	{
		return ASURA_GUID_UNREGISTERED;
	}

	Asura_Assert(!IsNetworkInitialised(), "Use GetClientID to retrive ClientID while network is still active");
	Asura_Assert(s_uInitialClientGuid != ASURA_GUID_UNREGISTERED, "invalid client guid");

	return s_uInitialClientGuid;
}

Asura_Guid Asura_Network::GetNewClientGuid(u_int uMachineNumber)
{
    Asura_Assert(Asura_Network::IsNetworkInitialised(), "network not initialised");
	Asura_Assert(s_uNumberOfPlayers <= s_uMaxNumberOfPlayers, "too many players! should have been stopped by now!");

	u_int uGuid = ASURA_GUID_UNREGISTERED;

	if(uMachineNumber == 0)
	{
		// adding to this machine - could be client or server
		if (IsServer())
		{
			// ensure we've got a GUID reserved
			Asura_Assert( s_uNextGuidForNewClients > s_uNextClientGuid, "GUID clash with server/remote players");
		}
		uGuid = s_uNextClientGuid;
		s_uNextClientGuid++;
	}
	else if(IsServer())
	{
		// a client has connected. Send it the next free guid.
		// (MH - 27.07.2004) first check if we should / can use a recycled one
		if (s_uNextGuidForNewClients > uASURA_NETWORK_OPTIMUM_MAX_GUID && s_pxGuidRecycler)
		{
			uGuid = s_pxGuidRecycler->Get();
		}

		if (uGuid == ASURA_GUID_UNREGISTERED)
		{
			uGuid = s_uNextGuidForNewClients;
			s_uNextGuidForNewClients++;
		}
	}

	// SNH - 24/01/2005 15:32:30 - moved the bit that adds players to the client machine list.
	//	For server's local players this is now done in Asura_Network::Initialise.
	//	For remote players it's done when client guids are dispatched in ANTS::ProcessMachineConnection
	// NB that client machines won't therefore have any player/machine information, but they shouldn't need it.
	Asura_Assert(uGuid != ASURA_GUID_UNREGISTERED, "Can't add players to other machines if we're not a server");

	return uGuid;
}

Asura_Handle Asura_Network::AddMachineToGame(const u_int uMaxNumberOfPlayers)
{
    Asura_Assert(Asura_Network::IsNetworkInitialised(), "network not initialised");

	Asura_Handle iHandle;
	
	s_pxClientMachineHandleList->GetNewHandle(iHandle);

	if (iHandle == ASURA_INVALID_HANDLE)
	{
		return iHandle;
	}

	s_pxClientMachineStatus[iHandle].Reset();
	s_pxClientMachineStatus[iHandle].Initialise(uMaxNumberOfPlayers);

	s_uNumberOfMachines++;

	return iHandle;

}

bool Asura_Network::RemoveMachineFromGame(const Asura_Handle iHandle)
{
    Asura_Assert(Asura_Network::IsNetworkInitialised(), "network not initialised");
	Asura_Assert(IsServer(), "Shouldn't be called on a client machine");
	if(!IsServer()) return false;

	// first remove the players on this machine from the list of players and reduce num players.
	for (u_int uPlayerToRemove = 0; uPlayerToRemove < s_pxClientMachineStatus[iHandle].GetNumberOfPlayers(); ++uPlayerToRemove)
	{
		Asura_Guid uGuid = s_pxClientMachineStatus[iHandle].GetPlayerGuid(uPlayerToRemove);
		u_int uPlayer;
		for (uPlayer = 0; uPlayer < s_uNumberOfPlayers; ++uPlayer)
		{
			if (s_pxPlayerInfo[uPlayer].m_uGuid == uGuid)
			{
				// swap the last entry in for this one.
				s_pxPlayerInfo[uPlayer] = s_pxPlayerInfo[s_uNumberOfPlayers - 1];
				break;
			}
		}

		// Remove this player from the initialising game lists.
		RemoveClientFromInitialisationLists(uGuid);

		Asura_Assert(uPlayer != s_uNumberOfPlayers, "failed to find player to remove");
		--s_uNumberOfPlayers;
	}
		
	// (MH - 27.07.2004) recycle guids
	if (s_pxGuidRecycler)
	{
		for (u_int u = 0; u < s_pxClientMachineStatus[iHandle].GetNumberOfPlayers(); ++u)
		{
			s_pxGuidRecycler->Add(s_pxClientMachineStatus[iHandle].GetPlayerGuid(u));
		}
	}

	s_pxClientMachineStatus[iHandle].RemovePlayers();

	s_pxClientMachineStatus[iHandle].Reset();

	s_pxClientMachineHandleList->ReleaseHandle(iHandle);

	s_uNumberOfMachines--;

	return true;
}

void Asura_Network::AddPlayerToMachine(Asura_Guid uClientGuid, u_int uMachineNumber)
{
	Asura_Assert(IsServer(), "Shouldn't be called on a client machine");
	if(!IsServer()) return;

	Asura_Assert(uMachineNumber < s_uMaxNumberOfMachines, "Machine number too high");
	if(uMachineNumber >= s_uMaxNumberOfMachines) return;

	Asura_Assert(s_uNumberOfPlayers < s_uMaxNumberOfPlayers, "Too many players");
	if(s_uNumberOfPlayers >= s_uMaxNumberOfPlayers) return;

	Asura_Assert(IsNetworkInitialised(), "Network not initialised");

	s_pxClientMachineStatus[uMachineNumber].AddPlayer(uClientGuid);

	// SNH: moved this here from GetNewClientGuid so we're a bit more consistant between local and remote players.
	if (uClientGuid != ASURA_GUID_UNREGISTERED)
	{
		// add guid to player info array and increment num players
		s_pxPlayerInfo[s_uNumberOfPlayers++].m_uGuid = uClientGuid;
	}
}

Asura_Guid Asura_Network::GetPlayerGuid(u_int uPlayerIndex)
{
	Asura_Assert(IsServer(), "Shouldn't be called on a client machine");
	if (IsServer() && s_pxPlayerInfo && uPlayerIndex < GetNumberOfPlayers())
	{
		return s_pxPlayerInfo[uPlayerIndex].m_uGuid;
	}
	return ASURA_GUID_UNREGISTERED;		
}


float Asura_Network::GetLatency(void)
{
    // (MH - 18.09.2002) added check here so tools can call this.
    if (!Asura_Network::IsNetworkInitialised())
    {
        return 0.0f;
    }
	return Platform_GetLatency();
}

u_int Asura_Network::GetLatencyForPlayer(Asura_Guid uPlayerGuid)
{
	if(!Asura_Network::IsNetworkInitialised() || !Asura_Network::IsExternalNetworkActive() || !Asura_Network::IsServer())
	{
		return 0;
	}

	return Platform_GetLatencyForPlayer(uPlayerGuid);
}

void Asura_Network::GetNetworkBandwidthUsage(u_int& uSend, u_int& uReceive)
{
    if (!Asura_Network::IsNetworkInitialised())
    {
        uSend = 0;
		uReceive = 0;
    }
	else
	{
		Platform_GetNetworkBandwidthUsage(uSend, uReceive);
	}
}

void Asura_Network::GetPeakNetworkBandwidthUsage(u_int& uSend, u_int& uReceive)
{
    if (!Asura_Network::IsNetworkInitialised())
    {
        uSend = 0;
		uReceive = 0;
    }
	else
	{
		Platform_GetPeakNetworkBandwidthUsage(uSend, uReceive);
	}
}

bool Asura_Network::IsPlayerConnected( Asura_Guid uPlayerGUID, bool bIncludeBots )
{
    return( ASURA_INVALID_HANDLE != GetMachineForPlayer(uPlayerGUID, bIncludeBots) );
}

Asura_Handle Asura_Network::GetMachineForPlayer( Asura_Guid uPlayerGUID, bool bIncludeBots )
{
    Asura_Handle iMachineHandle = ASURA_INVALID_HANDLE;

    if( Asura_Network::IsNetworkInitialised() && Asura_Network::IsServer() )
    {
        Asura_Tracking_Handle_List_Iterator xIt = s_pxClientMachineHandleList->GetIterator();
        for( xIt.Begin(); !xIt.End(); xIt.Next() )
        {
            const Asura_Handle iHandle = xIt.GetCurrentHandle();
            if( s_pxClientMachineStatus[iHandle].DoesMachineHavePlayer(uPlayerGUID) )
            {
                iMachineHandle = iHandle;
                break;
            }
        }

        if( ASURA_INVALID_HANDLE == iMachineHandle && bIncludeBots && Project_IsBotConnected(uPlayerGUID) )
        {
            // Assume for now bots will always be on server...
            iMachineHandle = 0;
        }
    }

    return iMachineHandle;
}


#ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
void Asura_Network::AddToClientUpdatePackage(Asura_Message* pxMsg)
{
#if defined ASURA_SUPPORT_EXTERNAL_NETWORK \
		&& defined ASURA_SUPPORT_NETMSG_SYSTEM
	// (MH - 25.05.2005) if an update is guaranteed for some reason,
	// send it direct.
	if ( (pxMsg->IsUsingGuaranteedDelivery())
		|| (!Asura_NetMsg_System::AddAsuraMessage(pxMsg)) )
#endif
	{
		// messages which aren't registered in NetMsg system get sent directly.
		BufferMessageToAllRemoteClients(pxMsg);
	}
}
#endif


void Asura_Network::FlushBufferedMessages(void)
{
#ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
	if (s_bExternalNetworkActive)
	{
		Asura_Assert(s_pxMsgBuffManager, "no network buffer manager");
		s_pxMsgBuffManager->FlushAll();
	}
#endif
}




void Asura_Network::SetServerName(const Asura_UnicodeChar* wszServerName) 
{ 
#ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
	Asura_PrintfToBfr(s_awcHostName, L"%s", ASURA_NETWORK_MAXHOSTNAMECHARS + 1, true, wszServerName);
	Platform_SetServerName(s_awcHostName);
#endif //ASURA_SUPPORT_EXTERNAL_NETWORK
}


Asura_Guid Asura_Network::GetPlayerGUID( const Asura_UnicodeChar* wszPlayerName, bool bIncludeBots )
{
    return( Asura_Network::IsNetworkInitialised() ? Project_GetPlayerGUID(wszPlayerName, bIncludeBots) : ASURA_GUID_UNREGISTERED );
}

const Asura_UnicodeChar* Asura_Network::GetPlayerName( Asura_Guid uPlayerGUID )
{
    return( Asura_Network::IsNetworkInitialised() ? Project_GetPlayerName(uPlayerGUID) : NULL );
}

#ifdef ASURA_SUPPORT_COMMAND_CONSOLE
bool Asura_Network::AddConsoleCmdsAndVars()
{
#if defined(ASURA_SUPPORT_EXTERNAL_NETWORK)
	if (s_bExternalNetworkActive)
	{
		Asura_CommandConsole::AddCmd( L"Network.NetStats", ConsoleCmd_NetStats, Asura_CommandConsole::REMOVE_AT_RESET );
	}
#endif

	// list players and remove players are added by some project code so not doing it here.
	return true;
}

bool Asura_Network::ConsoleCmd_ListPlayers( bool bHelp )
{
    if( bHelp )
    {
        Asura_CommandConsole::Printf( L"Displays a list of connected players and their IDs." );
    }
    else
    {
        bool bAny = false;

		if(Asura_Network::IsDedicatedServer())
        {
            Asura_Tracking_Handle_List_Iterator xIt = s_pxClientMachineHandleList->GetIterator();
            for( xIt.Begin(); !xIt.End(); xIt.Next() )
            {
                const Asura_Network_ClientMachineStatus& xMachineStatus = s_pxClientMachineStatus[xIt.GetCurrentHandle()];
                for( u_int uPlayer = 0; uPlayer < xMachineStatus.GetNumberOfPlayers(); ++uPlayer )
                {
                    const Asura_Guid uGUID = xMachineStatus.GetPlayerGuid( uPlayer );
                    Asura_CommandConsole::Printf( L"  %6u %s", uGUID, GetPlayerName(uGUID) );
                    bAny = true;
                }
            }

			bAny |= Project_ListBotsInConsole();
        }
		else
		{
			bAny |= Project_ListClientPlayersInConsole();
		}

        if( !bAny )
        {
            Asura_CommandConsole::Printf( L"No players connected." );
        }
    }
    return true;
}

bool Asura_Network::ConsoleCmd_RemovePlayer( const Asura_UnicodeChar* wszPlayerNameOrGUID, bool bInterrogate, bool bHelp )
{
    if( bInterrogate || bHelp )
    {
        Asura_CommandConsole::Printf( L"Removes the specified player from the game. You may use either the" );
        Asura_CommandConsole::Printf( L"player's name or their ID. IDs are listed by the " ASURA_CONSOLECMD_LISTPLAYERS L" command." );
        Asura_CommandConsole::Printf( L"IDs are checked before names. Prefix the ID with a # symbol to skip" );
        Asura_CommandConsole::Printf( L"checking names." );
        return true;
    }
    else
    {
        if( Asura_Network::IsServer() )
        {
            Asura_Guid uKickGUID = Console_GetGUIDFromInput( wszPlayerNameOrGUID );
            if( ASURA_GUID_UNREGISTERED != uKickGUID )
            {
                // Check it's not a human on the server machine.
                if( 0 == GetMachineForPlayer(uKickGUID, false) )
                {
                    Asura_CommandConsole::Printf( L"Can't kick players playing on server machine." );
                }
                else
                {
                    Asura_CommandConsole::Printf( L"Removing %s (#%u)...", GetPlayerName(uKickGUID), uKickGUID );
                    return RemovePlayer( uKickGUID, ASURA_NDR_SERVERKICK );
                }
            }
        }
        else
        {
            Asura_CommandConsole::Printf( L"Only the host can remove players." );
        }
        return false;
    }
}

bool Asura_Network::ConsoleCmd_NetStats( bool bHelp )
{
	if (bHelp)
	{
		Asura_CommandConsole::Printf(L"Shows network statistics");
	}
	else
	{
		float fLatency = Asura_Network::GetLatency();
		float fLoss	   = Asura_Network::GetPacketLossPercent();
		u_int uRecv, uSend, uRecvPeak, uSendPeak;
		Asura_Network::GetNetworkBandwidthUsage(uSend, uRecv);
		Asura_Network::GetPeakNetworkBandwidthUsage(uSendPeak, uRecvPeak);

		Asura_CommandConsole::Printf(L"-----------------------------------------------");
		Asura_CommandConsole::Printf(L"- NETSTATS                                    -");
		Asura_CommandConsole::Printf(L"-----------------------------------------------");
		Asura_CommandConsole::Printf(L"- Players       : %u", Asura_Network::GetNumberOfPlayers());
		Asura_CommandConsole::Printf(L"- Latency       : %d ms.", (int) (fLatency * 1000.0f));
		Asura_CommandConsole::Printf(L"- Loss          : %.1f %%.", Asura_Maths::FtoD(fLoss));
		Asura_CommandConsole::Printf(L"- Sending       : %d (max %d) bytes/sec.", uSend, uSendPeak);
		Asura_CommandConsole::Printf(L"- Receiving     : %d (max %d) bytes/sec.", uRecv, uRecvPeak);
		Asura_CommandConsole::Printf(L"-");
	}
	return true;
}

Asura_Guid Asura_Network::Console_GetGUIDFromInput( const Asura_UnicodeChar* wszInput )
{
    bool bForceGUID = ( L'#' == *wszInput );

    Asura_Guid uGUIDFromGUID = ASURA_GUID_UNREGISTERED;
    Asura_Guid uGUIDFromName = ASURA_GUID_UNREGISTERED;
    Asura_Guid uFinalGUID    = ASURA_GUID_UNREGISTERED;

    // GUID?
    {
        int iValue;
        
        if( Asura_StringUtil::GetIntValue( (bForceGUID ? wszInput + 1 : wszInput), &iValue) )
        {
            // If it appears to be a GUID, check it's a valid one.
            if( iValue >= 0 && GetPlayerName(iValue) )
            {
                uGUIDFromGUID = iValue;
            }
        }
    }

    // Does it appear to be a name?
    if( !bForceGUID )
    {
        uGUIDFromName = GetPlayerGUID( wszInput, true );
    }

    if( ASURA_GUID_UNREGISTERED == uGUIDFromGUID && ASURA_GUID_UNREGISTERED == uGUIDFromName )
    {
        Asura_CommandConsole::Printf( L"Invalid player name/ID." );
    }
    else
    {
        if( ASURA_GUID_UNREGISTERED != uGUIDFromGUID && ASURA_GUID_UNREGISTERED != uGUIDFromName && uGUIDFromGUID != uGUIDFromName )
        {
            // Ambiguity. Tell the user to resolve it. To help with this,
            // get the GUID for the player with the GUID-like name.
            Asura_CommandConsole::Printf( L"There's both a name and an ID for '%s'. Use either ", wszInput );
            Asura_CommandConsole::Printf( L"'" ASURA_CONSOLECMD_REMOVEPLAYER L" #%i' or '" ASURA_CONSOLECMD_REMOVEPLAYER L" #%i' to resolve.", uGUIDFromGUID, uGUIDFromName );
        }
        else
        {
            uFinalGUID = ( ASURA_GUID_UNREGISTERED == uGUIDFromGUID ? uGUIDFromName : uGUIDFromGUID );
        }
    }

    return uFinalGUID;
}

#endif // #ifdef ASURA_SUPPORT_COMMAND_CONSOLE

bool Asura_Network::RemovePlayer( Asura_Guid uGUID, ASURA_NETWORK_DISCONNECT_REASON eReason /*=ASURA_NDR_UNKNOWN*/ )
{
	Asura_Assert(IsServer(), "can only remove players from the server");

    // Check it's not a human on the server machine.
    if( 0 == GetMachineForPlayer(uGUID, false) )
	{
		Asura_Assert(false, "can't kick human from server with this call");
		return false;
	}

	if( IsPlayerConnected(uGUID, false) )
    {
        Asura_Message* pxMsg = CreateMessage( ASURA_MSG_PLAYERDISCONNECTED, sizeof(Asura_MsgData_PlayerDisconnected) );
        if( pxMsg )
        {
			Asura_MsgData_PlayerDisconnected* pxData = reinterpret_cast<Asura_MsgData_PlayerDisconnected*>(pxMsg->GetData());

			if(pxData)
			{
				pxData->PlayerGuid = uGUID;
				pxData->m_eReason = eReason;
			}

            // Send message to client to tell them to disconnect.
            pxMsg->To = ASURA_GUID_PLAYERS_CLIENTMACHINE;
            pxMsg->From = uGUID;
			pxMsg->UseGuaranteedDelivery();
            Send( pxMsg, false ); 

			// Send message to ourselves as well since we can't rely on the
			// client receiving and acting upon that message.
			pxMsg->To = ASURA_GUID_SERVER;
			pxMsg->From = ASURA_GUID_SERVER;
			Send( pxMsg );
		}
		// (MH - 13.07.2004) make sure that the transport layer frees up resources for this player.
		Asura_Handle iMachine = GetMachineForPlayer(uGUID, false);
		Asura_Assert(iMachine != ASURA_INVALID_HANDLE, "invalid handle for connected player's machine");
		if (iMachine != ASURA_INVALID_HANDLE)
		{
			Platform_RemoveMachineFromGame(iMachine, 5.0f);	// kick the machine within 5 seconds
		}

		RemoveClientFromInitialisationLists(uGUID);

		return true;
	}
    else if( IsPlayerConnected(uGUID, true) )
    {
        // Need to leave it up to project-specific code to deal with bots.
        return Project_RemoveBot( uGUID );
    }

    return false;
}


#if ASURA_NETWORK_LOGGING

#include "Entity/Asura_Entity_Classification.h"

static Asura_Network::MSG_LOG_LEVEL g_eForceNetworkMsgLogLevel = Asura_Network::MSG_LOG_LEVEL_NUM_LEVELS;

static const char* NETWORK_MSG_LOGFILE_SENT = "logfiles\\network_msgs.log";
static const char* NETWORK_MSG_LOGFILE_RECEIVED = "logfiles\\network_msgs_received.log";

void Asura_Network::SetMsgLogLevel( MSG_LOG_LEVEL eLevel, bool bSent /* = true */, bool bReceived /* = false */)
{
    static bool bEverBeenOnSent = false;
    static bool bEverBeenOnReceived = false;

    Asura_Assert( MSG_LOG_LEVEL_NUM_LEVELS != eLevel, "" );
    s_eMsgLogLevel = eLevel;

	s_bLogMessagesSent = bSent;
	s_bLogMessagesReceived = bReceived;
    if( !bEverBeenOnSent && MSG_LOG_LEVEL_NONE != s_eMsgLogLevel && bSent )
    {
        Asura_SimpleLogfileClear( NETWORK_MSG_LOGFILE_SENT );
        bEverBeenOnSent = true;
    }
    if( !bEverBeenOnReceived && MSG_LOG_LEVEL_NONE != s_eMsgLogLevel && bReceived )
    {
        Asura_SimpleLogfileClear( NETWORK_MSG_LOGFILE_RECEIVED );
        bEverBeenOnReceived = true;
    }
}

void Asura_Network::GetMsgIDDesc(u_int uMsgID, const char** pszDesc)
{
    #define CASE_TO_STR( _str, _case ) \
        case (_case): (_str) = #_case; break

    static char acBuffer[32];
    Asura_PrintfToBfr( acBuffer, "[misc (%d)]", 32, true, uMsgID );
    *pszDesc = acBuffer;

    switch( uMsgID )
    {
		CASE_TO_STR( *pszDesc, ASURA_MSG_INVALID );
		CASE_TO_STR( *pszDesc, ASURA_MSG_INITIALISE );
		CASE_TO_STR( *pszDesc, ASURA_MSG_DESTRUCT );
		CASE_TO_STR( *pszDesc, ASURA_MSG_ACTIVATE );
		CASE_TO_STR( *pszDesc, ASURA_MSG_DEACTIVATE );
		CASE_TO_STR( *pszDesc, ASURA_MSG_TRIGGER );
		CASE_TO_STR( *pszDesc, ASURA_MSG_SPLITTER_ACTIVATEOUTPUT );
		CASE_TO_STR( *pszDesc, ASURA_MSG_SPLITTER_DEACTIVATEOUTPUT );
		CASE_TO_STR( *pszDesc, ASURA_MSG_SPLITTER_TRIGGEROUTPUT );
		CASE_TO_STR( *pszDesc, ASURA_MSG_CHANGE_ANIMATION );
		CASE_TO_STR( *pszDesc, ASURA_MSG_GLASS_SMASH );
		CASE_TO_STR( *pszDesc, ASURA_MSG_UNTRIGGER );
		CASE_TO_STR( *pszDesc, ASURA_MSG_LOCK );
		CASE_TO_STR( *pszDesc, ASURA_MSG_UNLOCK );
		CASE_TO_STR( *pszDesc, ASURA_MSG_LIFTCALL );
		CASE_TO_STR( *pszDesc, ASURA_MSG_RESET );
		CASE_TO_STR( *pszDesc, ASURA_MSG_SWITCHON );
		CASE_TO_STR( *pszDesc, ASURA_MSG_SWITCHOFF );
		CASE_TO_STR( *pszDesc, ASURA_MSG_TEMPLATE_TRIGGERINPUT );
		CASE_TO_STR( *pszDesc, ASURA_MSG_TEMPLATE_TRIGGEROUTPUT );
		CASE_TO_STR( *pszDesc, ASURA_MSG_ACTIVATECUTSCENEWITHTIME );
		CASE_TO_STR( *pszDesc, ASURA_MSG_LOGICTRIGGER_INPUTON );
		CASE_TO_STR( *pszDesc, ASURA_MSG_LOGICTRIGGER_INPUTOFF );
		CASE_TO_STR( *pszDesc, ASURA_MSG_CUTSCENE_ENABLEEVENT );
		CASE_TO_STR( *pszDesc, ASURA_MSG_CUTSCENE_DISABLEEVENT );
		CASE_TO_STR( *pszDesc, ASURA_MSG_TIMELINETRIGGER_ENABLEMESSAGE );
		CASE_TO_STR( *pszDesc, ASURA_MSG_TIMELINETRIGGER_DISABLEMESSAGE );
		CASE_TO_STR( *pszDesc, ASURA_MSG_TEXTUREANIMATION );
		CASE_TO_STR( *pszDesc, ASURA_MSG_HIDE );
		CASE_TO_STR( *pszDesc, ASURA_MSG_UNHIDE );
		CASE_TO_STR( *pszDesc, ASURA_MSG_COLLISIONSON );
		CASE_TO_STR( *pszDesc, ASURA_MSG_COLLISIONSOFF );
		CASE_TO_STR( *pszDesc, ASURA_MSG_METAMUSIC_ACTIVATEZONE );
		CASE_TO_STR( *pszDesc, ASURA_MSG_METAMUSIC_DEACTIVATEZONE );
		CASE_TO_STR( *pszDesc, ASURA_MSG_SET_FSFX );
		CASE_TO_STR( *pszDesc, ASURA_MSG_AUDIO_REVERB );
		CASE_TO_STR( *pszDesc, ASURA_MSG_AUDIO_EVENT );
		CASE_TO_STR( *pszDesc, ASURA_MSG_AUDIO_MIXINGGROUP );
		CASE_TO_STR( *pszDesc, ASURA_MSG_AUDIO_AMBIENCE );
		CASE_TO_STR( *pszDesc, ASURA_MSG_SET_DESTRUCTIBLE );
		CASE_TO_STR( *pszDesc, ASURA_MSG_SET_FSFX_NEW );
		CASE_TO_STR( *pszDesc, ASURA_MSG_SOUND_MIXER_PRESET );
		CASE_TO_STR( *pszDesc, ASURA_MSG_AXON_CREATE_GOAL );
		CASE_TO_STR( *pszDesc, ASURA_MSG_AXON_SET_PAUSED );
		CASE_TO_STR( *pszDesc, ASURA_MSG_SETTIMER );
		CASE_TO_STR( *pszDesc, ASURA_MSG_TIMETRIGGER_SETTRIGGERTIME );
		CASE_TO_STR( *pszDesc, ASURA_MSG_TIMETRIGGER_ADDTOTRIGGERTIME );
		CASE_TO_STR( *pszDesc, ASURA_MSG_TIMELINETRIGGER_SETTRIGGERTIME );
		CASE_TO_STR( *pszDesc, ASURA_MSG_PAUSETIMER );
		CASE_TO_STR( *pszDesc, ASURA_MSG_UNPAUSETIMER );
		CASE_TO_STR( *pszDesc, ASURA_MSG_COUNTEDTRIGGER_SETCOUNTER );
		CASE_TO_STR( *pszDesc, ASURA_MSG_COUNTEDTRIGGER_ENABLEMESSAGE );
		CASE_TO_STR( *pszDesc, ASURA_MSG_COUNTEDTRIGGER_DISABLEMESSAGE );
		CASE_TO_STR( *pszDesc, ASURA_MSG_COUNTEDTRIGGER_SETTRIGGERCOUNT );
		CASE_TO_STR( *pszDesc, ASURA_MSG_PROXY_ACTIVATE );
		CASE_TO_STR( *pszDesc, ASURA_MSG_PROXY_DEACTIVATE );
		CASE_TO_STR( *pszDesc, ASURA_MSG_PROXY_ADDTARGET );
		CASE_TO_STR( *pszDesc, ASURA_MSG_PROXY_REMOVETARGET );
		CASE_TO_STR( *pszDesc, ASURA_MSG_PROXY_SETTARGET );
		CASE_TO_STR( *pszDesc, ASURA_MSG_AXON_GAMESCENE_LEAVE_GAMESCENE_AI );
		CASE_TO_STR( *pszDesc, ASURA_MSG_AXON_GS2_PATHFIND_TO_NODE );
		CASE_TO_STR( *pszDesc, ASURA_MSG_AXON_GAMESCENE_NEVER_STOP_AT_NODE );
		CASE_TO_STR( *pszDesc, ASURA_MSG_AXON_GAMESCENE_LEAVE_NODE_IF_THERE );
		CASE_TO_STR( *pszDesc, ASURA_MSG_AXON_GS2_ANIM_SEQUENCE_DATA );
		CASE_TO_STR( *pszDesc, ASURA_MSG_AXON_GAMESCENE_TELEPORT_TO_NODE );
		CASE_TO_STR( *pszDesc, ASURA_MSG_UNUSED_1 );
		CASE_TO_STR( *pszDesc, ASURA_MSG_UNUSED_2 );
		CASE_TO_STR( *pszDesc, ASURA_MSG_UNUSED_3 );
		CASE_TO_STR( *pszDesc, ASURA_MSG_AXON_GS2_DONT_STOP_AT_NODE );
		CASE_TO_STR( *pszDesc, ASURA_MSG_AXON_GS2_SET_NODE_PARAMS );
		CASE_TO_STR( *pszDesc, ASURA_MSG_LIFT_START );
		CASE_TO_STR( *pszDesc, ASURA_MSG_LIFT_STOP );
		CASE_TO_STR( *pszDesc, ASURA_MSG_LIFT_REVERSE );
		CASE_TO_STR( *pszDesc, ASURA_MSG_LIFT_SETFLAGS );
		CASE_TO_STR( *pszDesc, ASURA_MSG_LIFT_NODE_SETFLAGS );
		CASE_TO_STR( *pszDesc, ASURA_MSG_DIALOGUE_FROMINSTANCE );
		CASE_TO_STR( *pszDesc, ASURA_MSG_DIALOGUE_PLAYLINE );
		CASE_TO_STR( *pszDesc, ASURA_MSG_DIALOGUE_PLAYLINEONENTITY );
		CASE_TO_STR( *pszDesc, ASURA_MSG_UNUSED_0 );
		CASE_TO_STR( *pszDesc, ASURA_MSG_GUIMENU_ACTIVATE );
		CASE_TO_STR( *pszDesc, ASURA_MSG_AXON_GAMESCENE_LEAVE_NODE );
		CASE_TO_STR( *pszDesc, ASURA_MSG_AXON_GAMESCENE_ATTRACT_TO_NODE );
		CASE_TO_STR( *pszDesc, ASURA_MSG_DIALOGUE_CANCELSUBTITLE );
		CASE_TO_STR( *pszDesc, ASURA_MSG_AXON_GAMESCENE_ALWAYS_STOP_AT_NODE );
		CASE_TO_STR( *pszDesc, ASURA_MSG_LIFT_GOTO_NODE );
		CASE_TO_STR( *pszDesc, ASURA_MSGID_TELEPORT_ENTITY );
		CASE_TO_STR( *pszDesc, ASURA_MSG_CUTSCENE_SETCAMERA );
		CASE_TO_STR( *pszDesc, ASURA_MSG_DYNAMICLIGHT_FADE );
		CASE_TO_STR( *pszDesc, ASURA_MSG_COUNTEDTRIGGER_ADDCOUNTER );
		CASE_TO_STR( *pszDesc, ASURA_MSG_TIMETRIGGER_ADDTIME );
		CASE_TO_STR( *pszDesc, ASURA_MSG_CHANGE_CONSOLEVAR );
		CASE_TO_STR( *pszDesc, ASURA_MSG_AXON_GAMESCENE_ACTOR_ATTRACT_TO_NODE );
		CASE_TO_STR( *pszDesc, ASURA_MSG_SOUND_MIXER_PRESET_QUEUE );
		CASE_TO_STR( *pszDesc, ASURA_MSG_SOUND_MIXER_PRESET_UNQUEUE );
		CASE_TO_STR( *pszDesc, ASURA_MSG_SOUND_SET_REVERB );
		CASE_TO_STR( *pszDesc, ASURA_MSG_AXON_GAMESCENE_SET_ATTRACTOR_FLAGS );
		CASE_TO_STR( *pszDesc, ASURA_MSG_PLAY_PFX_ON_ENTITY);
		CASE_TO_STR( *pszDesc, ASURA_MSG_OBJECTIVE_READY );
		CASE_TO_STR( *pszDesc, ASURA_MSG_OBJECTIVE_COMPLETE );
		CASE_TO_STR( *pszDesc, ASURA_MSG_OBJECTIVE_FAIL );
		CASE_TO_STR( *pszDesc, ASURA_MSG_OBJECTIVE_SKIP );
		CASE_TO_STR( *pszDesc, ASURA_MSG_AXON_GS2_TAKE_LINK );
		CASE_TO_STR( *pszDesc, ASURA_MSG_AXON_GS2_LEAVE_NODE );
		CASE_TO_STR( *pszDesc, ASURA_MSG_AXON_GS2_EXIT_GAMESCENE );
		CASE_TO_STR( *pszDesc, ASURA_MSG_AXON_GS2_SET_LINK_WEIGHT );
		CASE_TO_STR( *pszDesc, ASURA_MSG_AXON_GS2_ANIM_CHANGE );
#ifdef ASURA_USE_AXON
		CASE_TO_STR( *pszDesc, ASURA_MSG_AXON_GS2_ATTRACTOR_PULSE );
		CASE_TO_STR( *pszDesc, ASURA_MSG_AXON_GS2_ATTRACTOR_CANCEL );
#endif // #ifdef ASURA_USE_AXON
		CASE_TO_STR( *pszDesc, ASURA_MSG_AXON_GS2_TELEPORT_TO_NODE );
		CASE_TO_STR( *pszDesc, ASURA_MSG_END_OF_STATIC_MESSAGES );
		CASE_TO_STR( *pszDesc, ASURA_MSG_CLIENTUPDATE );
		CASE_TO_STR( *pszDesc, ASURA_MSG_CLIENTCREATE );
#ifdef ASURA_ALLOW_INACTIVE_CLIENT_ENTITIES
		CASE_TO_STR( *pszDesc, ASURA_MSG_CLIENTCREATEINACTIVE );
#endif //#ifdef ASURA_ALLOW_INACTIVE_CLIENT_ENTITIES
		CASE_TO_STR( *pszDesc, ASURA_MSG_PACKEDMESSAGES );
		CASE_TO_STR( *pszDesc, ASURA_MSG_NETMSGUPDATE );
		CASE_TO_STR( *pszDesc, ASURA_MSG_CLIENT_REQUESTCREATEMSG );
		CASE_TO_STR( *pszDesc, ASURA_MSG_VALIDCLIENTGUID );
		CASE_TO_STR( *pszDesc, ASURA_MSG_PLAYERCONNECTED );
		CASE_TO_STR( *pszDesc, ASURA_MSG_PLAYERDISCONNECTED );
		CASE_TO_STR( *pszDesc, ASURA_MSG_CUTSCENECONTROLLER_REQ_PROFILE_CUTSCENE );
		CASE_TO_STR( *pszDesc, ASURA_MSG_CUTSCENECONTROLLER_PROFILE_CUTSCENE );
		CASE_TO_STR( *pszDesc, ASURA_MSG_CUTSCENECONTROLLER_ACTIVATE );
		CASE_TO_STR( *pszDesc, ASURA_MSG_CUTSCENECONTROLLER_CLIENTUPDATE );
		CASE_TO_STR( *pszDesc, ASURA_MSG_CUTSCENECONTROLLER_UPDATEEVENTSTATE );
#ifdef ASURA_SUPPORT_SERVERCUTSCENES
		CASE_TO_STR( *pszDesc, ASURA_MSG_SERVERCUTSCENEACTOR_CLIENTUPDATE_STANDARD );
		CASE_TO_STR( *pszDesc, ASURA_MSG_SERVERCUTSCENEACTOR_CLIENTUPDATE_PRIMARYANIMATION );
		CASE_TO_STR( *pszDesc, ASURA_MSG_SERVERCUTSCENEACTOR_CLIENTUPDATE_SECONDARYANIMATION );
		CASE_TO_STR( *pszDesc, ASURA_MSG_SERVERCUTSCENEACTOR_CLIENTUPDATE_PRIMARYANIMATION_TOPOS );
		CASE_TO_STR( *pszDesc, ASURA_MSG_SERVERCUTSCENEACTOR_CLIENTUPDATE_SECONDARYANIMATION_TOPOS );
		CASE_TO_STR( *pszDesc, ASURA_MSG_SERVERCUTSCENEACTOR_CLIENTUPDATE_ADDATTACHMENTSHAPE );
		CASE_TO_STR( *pszDesc, ASURA_MSG_SERVERCUTSCENEACTOR_CLIENTUPDATE_ADDATTACHMENTHIERARCHY );
		CASE_TO_STR( *pszDesc, ASURA_MSG_SERVERCUTSCENEACTOR_CLIENTUPDATE_ADDATTACHMENTACTOR );
		CASE_TO_STR( *pszDesc, ASURA_MSG_SERVERCUTSCENEACTOR_CLIENTUPDATE_ADDATTACHMENTENTITY );
		CASE_TO_STR( *pszDesc, ASURA_MSG_SERVERCUTSCENEACTOR_CLIENTUPDATE_REMOVEATTACHMENT );
		CASE_TO_STR( *pszDesc, ASURA_MSG_SERVERCUTSCENEACTORSHAPE_CLIENTUPDATE_STANDARD );
#endif
		CASE_TO_STR( *pszDesc, ASURA_MSG_HIDEFORCUTSCENE );
		CASE_TO_STR( *pszDesc, ASURA_MSG_GLASS_APPLYDECAL );
		CASE_TO_STR( *pszDesc, ASURA_MSG_GLASS_SMASHWITHPOSITION );
		CASE_TO_STR( *pszDesc, ASURA_MSG_GLASS_CLIENTUPDATE );
		CASE_TO_STR( *pszDesc, ASURA_MSG_CREATE_BEAM );
		CASE_TO_STR( *pszDesc, ASURA_MSG_MUSICSYSTEM_PLAY );
		CASE_TO_STR( *pszDesc, ASURA_MSG_METAMUSIC_PLAY );
		CASE_TO_STR( *pszDesc, ASURA_MSG_METAMUSIC_CHANGESTATE );
		CASE_TO_STR( *pszDesc, ASURA_MSG_METAMUSIC_RESTARTLASTTRACK );
		CASE_TO_STR( *pszDesc, ASURA_MSG_EXPLOSION_CREATE );
		CASE_TO_STR( *pszDesc, ASURA_MSG_PLAYER_MULTIPLECOMMANDS );
		CASE_TO_STR( *pszDesc, ASURA_MSG_PLAYER_POSITIONVALIDATED );
		CASE_TO_STR( *pszDesc, ASURA_MSG_PLAYER_POSITIONCORRECTED );
#ifdef ASURA_SUPPORT_COMMAND_CONSOLE_REMOTE
		CASE_TO_STR( *pszDesc, ASURA_MSG_COMMANDCONSOLE_REMOTE_NONCE_REQ );
		CASE_TO_STR( *pszDesc, ASURA_MSG_COMMANDCONSOLE_REMOTE_NONCE );
		CASE_TO_STR( *pszDesc, ASURA_MSG_COMMANDCONSOLE_REMOTE_EXEC_CMD );
		CASE_TO_STR( *pszDesc, ASURA_MSG_COMMANDCONSOLE_REMOTE_EXEC_CMD_RESULT );
#endif
#ifdef ASURA_SUPPORT_MULTIPLAYER_COMMS
		CASE_TO_STR( *pszDesc, ASURA_MSG_MULTIPLAYERCOMMS_TALK );
		CASE_TO_STR( *pszDesc, ASURA_MSG_MULTIPLAYERCOMMS_TEAMTALK );
		CASE_TO_STR( *pszDesc, ASURA_MSG_MULTIPLAYERCOMMS_TELL );
#endif
		CASE_TO_STR( *pszDesc, ASURA_MSG_LOOKATTRIGGER_CLIENTCOUNTCHANGE );
		CASE_TO_STR( *pszDesc, ASURA_MSG_VOICEDATA );
		CASE_TO_STR( *pszDesc, ASURA_MSG_NOTIFYFINISHED );
		CASE_TO_STR( *pszDesc, ASURA_MSG_ENVTEXTUREANIMATION );
		CASE_TO_STR( *pszDesc, ASURA_MSG_SKIP_CUTSCENES );
		CASE_TO_STR( *pszDesc, ASURA_MSG_DIALOGUE_TRIGGER );
		CASE_TO_STR( *pszDesc, ASURA_MSG_LINK_TO_PARENT );
		CASE_TO_STR( *pszDesc, ASURA_MSG_LINK_TO_PARENT_WITH_PHYSICS );
		CASE_TO_STR( *pszDesc, ASURA_MSG_LINK_TO_PARENT_BONE_RELATIVE );
		CASE_TO_STR( *pszDesc, ASURA_MSG_LINK_TO_PARENT_STICKY );
		CASE_TO_STR( *pszDesc, ASURA_MSG_UNLINK_FROM_PARENT );
		CASE_TO_STR( *pszDesc, ASURA_MSGID_TELEPORT );
		CASE_TO_STR( *pszDesc, ASURA_MSGID_BEGIN_PLAYER_TELEPORT );
		CASE_TO_STR( *pszDesc, ASURA_MSG_MOVE_START );
		CASE_TO_STR( *pszDesc, ASURA_MSG_MOVE_ABORT );
		CASE_TO_STR( *pszDesc, ASURA_MSG_MOVE_FINISHED );
		CASE_TO_STR( *pszDesc, ASURA_MSG_MOVECHAINCOMPLETE );
		CASE_TO_STR( *pszDesc, ASURA_MSG_ATTRACTOR_FLAGS );
		CASE_TO_STR( *pszDesc, ASURA_MSG_PLAYSOUNDEVENT );
		CASE_TO_STR( *pszDesc, ASURA_MSG_STREAMINGBACKGROUNDSOUND_FLAGS );
		CASE_TO_STR( *pszDesc, ASURA_MSG_RESTORE_HEALTH );
		CASE_TO_STR( *pszDesc, ASURA_MSG_CONTAINER_CHANGE_STATE );
		CASE_TO_STR( *pszDesc, ASURA_MSG_CONTAINER_CHANGE_STATE_EXTENDED );
		CASE_TO_STR( *pszDesc, ASURA_MSG_CONTAINER_CHANGE_HEALTH );
		CASE_TO_STR( *pszDesc, ASURA_MSG_CONTAINER_CLIENTUPDATE_EXTENDED1 );
		CASE_TO_STR( *pszDesc, ASURA_MSG_CONTAINER_CLIENTUPDATE_EXTENDED );
		CASE_TO_STR( *pszDesc, ASURA_MSG_DYNAMIC_MUSIC_TRIGGER );
		CASE_TO_STR( *pszDesc, ASURA_MSG_DIALOGUE_CONTROLLER_TOGGLE_LINE );
		CASE_TO_STR( *pszDesc, ASURA_MSG_DIALOGUE_CONTROLLER_INTERRUPT );
		CASE_TO_STR( *pszDesc, ASURA_MSG_DIALOGUE_CONTROLLER_TRIGGER_LINE );
		CASE_TO_STR( *pszDesc, ASURA_MSG_APPLY_LINEAR_FORCE );
		CASE_TO_STR( *pszDesc, ASURA_MSG_APPLY_RADIAL_FORCE );
		CASE_TO_STR( *pszDesc, ASURA_MSG_DIALOGUE_PLAYLINE_CONTROLLER );
		CASE_TO_STR( *pszDesc, ASURA_MSG_DIALOGUE_STOPLINE_CONTROLLER );
		CASE_TO_STR( *pszDesc, ASURA_MSG_FMV_CONTROLLER_PLAY );
		CASE_TO_STR( *pszDesc, ASURA_MSG_FMV_CONTROLLER_PAUSE );
		CASE_TO_STR( *pszDesc, ASURA_MSG_COLLISIONFX_CLIENTUPDATE );
		CASE_TO_STR( *pszDesc, ASURA_MSG_COLLISIONFX_CLIENTCREATEDECAL );

		default:
            Project_GetMsgIDDesc( uMsgID, pszDesc );
            break;
    }

    #undef CASE_TO_STR
}


void Asura_Network::GetGuidDesc(Asura_Guid uGuid, const char** pszDesc)
{
    #define CASE_TO_STR( _str, _case ) \
        case (_case): (_str) = #_case; break

    switch (uGuid)
    {
        CASE_TO_STR( *pszDesc, ASURA_GUID_SERVER );
        CASE_TO_STR( *pszDesc, ASURA_GUID_CLIENT );
        CASE_TO_STR( *pszDesc, ASURA_GUID_ALL_PLAYERS );
        CASE_TO_STR( *pszDesc, ASURA_GUID_OTHER_PLAYERS );
        CASE_TO_STR( *pszDesc, ASURA_GUID_MY_CLIENT_ENTITIES );
        CASE_TO_STR( *pszDesc, ASURA_GUID_ALL_CLIENTMACHINES );
        CASE_TO_STR( *pszDesc, ASURA_GUID_ALL_OTHER_CLIENTMACHINES );
        CASE_TO_STR( *pszDesc, ASURA_GUID_OTHER_PLAYERS_CLIENTMACHINES );
        CASE_TO_STR( *pszDesc, ASURA_GUID_PLAYERS_CLIENTMACHINE );
        CASE_TO_STR( *pszDesc, ASURA_GUID_PLAYER );
        CASE_TO_STR( *pszDesc, ASURA_GUID_PLAYER_ONTHISMACHINE );
    }

    #undef CASE_TO_STR
}


void Asura_Network::GetEntityClassificationDesc( u_short usEntityClassification, const char** pszDesc )
{
    #define CASE_TO_STR( _str, _case ) \
        case (_case): (_str) = #_case; break

    switch (usEntityClassification)
    {
		CASE_TO_STR( *pszDesc, AsuraEntityClass_NotSet );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_TimeTrigger );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_VolumeTriggerRedundant );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_CutsceneController );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_ServerCutscene );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_ServerCutsceneActorHierarchy );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_Glass );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_PhysicsObject );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_PickupObjectRedundant );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_DestructibleLight );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_ParticleGenerator );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_SplitterBlock );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_ServerCutsceneActorCamera );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_CountedTrigger );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_SoundController );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_DirectionalWind_UNUSED );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_PointWind_UNUSED );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_AdvancedLight );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_ServerCutsceneActorShape );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_Door_UNUSED );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_AdvancedVolumeTrigger );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_Lift );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_DamageVolume );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_SwitchObject_UNUSED );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_MusicTrigger );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_ExtendedParticleGenerator );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_Explosion );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_DirectionalWindVolume_UNUSED );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_FMVTrigger );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_DebugObject_UNUSED );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_MessageSniffer_UNUSED );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_MetaMusicTrigger );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_MultiDamageVolume );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_PFX_Effect );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_Template );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_LookAtTrigger );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_ClockTrigger );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_Ragdoll );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_LogicTrigger );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_Tannoy_UNUSED );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_ClientVolumeTrigger );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_StartPoint );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_TimelineTrigger );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_EnvTextureAnimControl );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_CountedVolumeTrigger );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_Deformatron_UNUSED );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_FSFX_Effect_UNUSED );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_DebugMessageTrigger );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_AudioLocator );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_Fragment );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_Ephemeron );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_CameraVolume );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_ProxyTrigger );
        CASE_TO_STR( *pszDesc, AsuraEntityClass_Node );
        CASE_TO_STR( *pszDesc, AsuraEntityClass_OrientedNode );
        CASE_TO_STR( *pszDesc, AsuraEntityClass_GamesceneNode );
        CASE_TO_STR( *pszDesc, AsuraEntityClass_CoverpointNode );
        CASE_TO_STR( *pszDesc, AsuraEntityClass_GuardZone );
        CASE_TO_STR( *pszDesc, AsuraEntityClass_AttractorNode );
        CASE_TO_STR( *pszDesc, AsuraEntityClass_Spline );
        CASE_TO_STR( *pszDesc, AsuraEntityClass_GamesceneSpline );
        CASE_TO_STR( *pszDesc, AsuraEntityClass_GamesceneBase );
        CASE_TO_STR( *pszDesc, AsuraEntityClass_DialogueTrigger );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_LiftNode );
        CASE_TO_STR( *pszDesc, AsuraEntityClass_LiftSpline );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_Affector );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_Affector_Wind );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_Projector );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_Teleporter );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_TeleportDestination );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_DebugWaypointTrigger );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_DynamicLight );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_ForceField_Conveyor );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_AttractorController );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_ConsoleVar );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_LightShaft );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_StreamingBackgroundSoundController );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_WaypointBlockerVolume );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_VolumeTrigger );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_Objective );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_GS2Node );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_GS2Spline );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_GS2Attractor );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_Container );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_DynamicMusicTrigger );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_DialogueController );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_EffectVolume );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_FMVController );
		CASE_TO_STR( *pszDesc, AsuraEntityClass_FMVSpeaker );

		default:
            Project_GetEntityClassificationDesc( usEntityClassification, pszDesc );
            break;
	}

    #undef CASE_TO_STR
}


#ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
void Asura_Network::LogMsgInfo( const Asura_MessageHeader& xMsgHeader, const void* pData, bool bIndent, bool bReceived, int iMachine /* = -1 */)
{
	const bool bGuaranteed = xMsgHeader.IsGuaranteed();
	const char* szLogFile = bReceived ? NETWORK_MSG_LOGFILE_RECEIVED : NETWORK_MSG_LOGFILE_SENT;
	u_short usMsgID = xMsgHeader.m_usMsgID;
	Asura_Guid uTo = xMsgHeader.m_uToGuid;
	Asura_Guid uFrom = xMsgHeader.m_uFromGuid;
	u_int uDataSize = xMsgHeader.GetDataSize();

    Asura_Assert( s_eMsgLogLevel > MSG_LOG_LEVEL_NONE && s_eMsgLogLevel < MSG_LOG_LEVEL_NUM_LEVELS, "" );

	const char* szMsgID = NULL;
	GetMsgIDDesc(usMsgID, &szMsgID);

	u_int uBytesSent = Asura_MessagePacking::GetBytesRequiredForMessage(xMsgHeader);
    
	// create string of the form "<from> -> <to>"
	const u_int uMAX_FROM_TO_LENGTH = 64;
	const u_int uMAX_GUID_DIGITS = 10;
	char acFromTo[uMAX_FROM_TO_LENGTH];
	char acFrom[uMAX_GUID_DIGITS + 1];
	char acTo[uMAX_GUID_DIGITS + 1];
	bool bFrom = uFrom != ASURA_GUID_UNREGISTERED;
	bool bTo = uTo != ASURA_GUID_UNREGISTERED;

	const char* szMsgDest = NULL;
	GetGuidDesc( uTo, &szMsgDest );
	const char* szMsgFrom = NULL;
	GetGuidDesc( uFrom, &szMsgFrom );

	if (!szMsgDest)
	{
		Asura_PrintfToBfr(acTo, "%u", uMAX_GUID_DIGITS, true, uTo);
		szMsgDest = acTo;
	}
	if (!szMsgFrom)
	{
		Asura_PrintfToBfr(acFrom, "%u", uMAX_GUID_DIGITS, true, uFrom);
		szMsgFrom = acFrom;
	}

	Asura_PrintfToBfr( acFromTo, "%s %s %s", uMAX_FROM_TO_LENGTH, true, bFrom ? szMsgFrom : "", (bFrom || bTo) ? "->" : "", bTo ? szMsgDest : "");

	const char* szEntityClassification = "no entity class";
	GetEntityClassificationDesc( xMsgHeader.GetEntityClassification(), &szEntityClassification );
	if (xMsgHeader.GetEntityClassification() == AsuraEntityClass_NotSet)
	{
		szEntityClassification = "";
	}

	static u_int uLastFrame = UINT_MAX;
	const u_int uFrame = Asura_Timers::GetFrameNumber();
	if (uLastFrame != uFrame)
	{
		Asura_SimpleLogfileOutput(( szLogFile, "Frame %d\n", uFrame ));
		uLastFrame = uFrame;
	}

	// if we know what machine this is to/from put it after the time, otherwise leave blank
	char szToMachine[16] = "";
	const char* szToMachinePad = "";
    if (iMachine != -1)
    {
		Asura_PrintfToBfr( szToMachine, " %c-> %2d", 16, true, bGuaranteed ? '!' : '-', iMachine );
        szToMachinePad				  = "       ";
    }
	
	// if indenting, don't put the sent time and machine in
	if( bIndent )
    {
        Asura_SimpleLogfileOutput(( szLogFile, "%s         (%u, %s, %s) %s\n", szToMachinePad, uBytesSent, szMsgID, szEntityClassification, acFromTo));
    }
    else
    {
        const float fTime = Asura_Timers::GetGlobalGameTime();
        Asura_SimpleLogfileOutput(( szLogFile, "%7.2f%s: (%u, %s, %s) %s\n", Asura_Maths::FtoD(fTime), szToMachine, uBytesSent, szMsgID, szEntityClassification, acFromTo));
    }

    // Special case for packed messages: describe the contents.
    if( (ASURA_MSG_PACKEDMESSAGES == usMsgID) && (s_eMsgLogLevel >= MSG_LOG_LEVEL_EXPAND_PACKED_MESSAGES) )
    {
        const u_char* pucData = reinterpret_cast<const u_char*>(pData);
        const u_char* pucEnd  = pucData + uDataSize;
        
        while( pucData < pucEnd )
        {
//			const Asura_MessageHeader* pxNextHdr = reinterpret_cast<const Asura_MessageHeader*>( pucData );
//			u_int uTotalSize = sizeof(Asura_MessageHeader) + pxNextHdr->m_uDataSize;
//
//			LogMsgInfo( *pxNextHdr, pxNextHdr + 1, true );
//
//			pucData += uTotalSize;
			Asura_MessageHeader xNextHdr;
			u_int uSizeOfPackedData = 0;
			const void* pUnpackedBuffer = Asura_MessagePacking::UnpackBufferToMessageHeader(pucData, bGuaranteed, xNextHdr, uSizeOfPackedData);
			LogMsgInfo( xNextHdr, pUnpackedBuffer, true, bReceived, iMachine );
			pucData = reinterpret_cast<const u_char*>(pUnpackedBuffer) + (uSizeOfPackedData ? uSizeOfPackedData : xNextHdr.GetDataSize());
        }

        return;
    }
	// Special case for netmsg update: summary of contents
	else if ( (ASURA_MSG_NETMSGUPDATE == usMsgID) && (s_eMsgLogLevel >= MSG_LOG_LEVEL_EXPAND_NETMSG_CONTENTS) )
	{
#ifdef ASURA_SUPPORT_NETMSG_SYSTEM
		const u_char* pucData = reinterpret_cast<const u_char*>(pData);
		const u_int uMAX_NETMSG_ENTRIES = 64;
		u_char aucHeaderSize[uMAX_NETMSG_ENTRIES];
		u_short ausMsgID[uMAX_NETMSG_ENTRIES];
		u_short ausEntClass[uMAX_NETMSG_ENTRIES];
		Asura_Guid auGuid[uMAX_NETMSG_ENTRIES];
		u_short ausDataSize[uMAX_NETMSG_ENTRIES];
		bool abIsFullUpdate[uMAX_NETMSG_ENTRIES];
		bool abIsTimeOffset[uMAX_NETMSG_ENTRIES];

		u_int uNumSummaries = Asura_NetMsg_System::GetMessageSummaries(pucData, static_cast<u_short>(uDataSize), uMAX_NETMSG_ENTRIES, aucHeaderSize, ausMsgID, ausEntClass, auGuid, ausDataSize, abIsFullUpdate, abIsTimeOffset);
		Asura_Assert(uNumSummaries <= uMAX_NETMSG_ENTRIES, "");

		Asura_SimpleLogfileOutput(( szLogFile, "                 8 bytes, Asura Msg Header\n" ));
		Asura_SimpleLogfileOutput(( szLogFile, "                 1 byte, NetMsg Header\n" ));
		u_int uCurEntry = 0;
		while (uCurEntry < uNumSummaries)
		{
			u_int uHeaderSize = aucHeaderSize[uCurEntry];
			GetMsgIDDesc(ausMsgID[uCurEntry], &szMsgID);
			const char* szType = abIsFullUpdate[uCurEntry] ? "full" : "partial";
			u_short usDataSize = ausDataSize[uCurEntry];
			const char* szEntityClass = "unknown entity type";
			GetEntityClassificationDesc( ausEntClass[uCurEntry], &szEntityClass );
			Asura_Guid uGuid = auGuid[uCurEntry];

			if (abIsTimeOffset[uCurEntry])
			{
				Asura_Assert(usDataSize == 0, "time offsets should have no data");
				Asura_SimpleLogfileOutput(( szLogFile, "                 %u bits, Time Offset (%u ms).\n", uHeaderSize, uGuid ));
			}
			else
			{
				Asura_SimpleLogfileOutput(( szLogFile, "                 %u bits, NetMsg per-message Header\n", uHeaderSize ));
				Asura_SimpleLogfileOutput(( szLogFile, "                 %u bits, %s - %s, %s -> %u\n", usDataSize, szType, szMsgID, szEntityClass, uGuid ));
			}

			uCurEntry++;
		}
#else
		Asura_Assert(false, "shouldn't have netmsg data");
#endif
	}
}
#endif // #ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
#endif // #if ASURA_NETWORK_LOGGING

const char* Asura_Network::GetMsgIDDesc( u_int uMsgID )
{
#if ASURA_NETWORK_LOGGING
	const char* szDesc;
	GetMsgIDDesc( uMsgID, &szDesc );
	return szDesc;
#else
    static char acBuffer[11];
    Asura_PrintfToBfr( acBuffer, "%u", 11, true, uMsgID );
	return acBuffer;
#endif
}

const char* Asura_Network::GetGuidDesc( Asura_Guid uGuid )
{
#if ASURA_NETWORK_LOGGING
	const char* szDesc = 0;
	GetGuidDesc( uGuid, &szDesc );
	if (szDesc)
	{
		return szDesc;
	}
#endif
    // multiple guids likely to be needed at once so use multiple buffers
	static char aacBuffer[2][11];
	static u_int uBuffer = 0;
	uBuffer ^= 1;
    Asura_PrintfToBfr( aacBuffer[uBuffer], "%u", 11, true, uGuid );
	return aacBuffer[uBuffer];
}

const char* Asura_Network::GetEntityClassificationDesc( u_short usEntityClassification )
{
#if ASURA_NETWORK_LOGGING
	const char* szDesc = 0;
	GetEntityClassificationDesc( usEntityClassification, &szDesc );
	if (szDesc)
	{
		return szDesc;
	}
#endif
    static char acBuffer[6];
    Asura_PrintfToBfr( acBuffer, "%u", 6, true, usEntityClassification );
	return acBuffer;
}

u_int Asura_Network::GetMSecsSinceLastMessageReceived()
{
	// (AW - 27/07/05) - This info can be used to take measures against cheaters who unplug their
	// network cables to gain an advantage.
	return Asura_Timers::GetMSecsSinceStart() - s_uTimeOfLastMessageReceived;
}

void Asura_Network::RegisterClientForCreateMessages(Asura_Guid uGuid)
{
	Asura_Assert(IsServer(), "Only the server should be using the client creation lists.");
	Asura_Assertf(!s_xClientsAwaitingCreateMessages.Find(uGuid), ("Sending create messages to client guid %d twice.", uGuid));

	// If this client is on the server machine, send them all out straight away.
	if (Asura_Network::GetMachineForPlayer(uGuid, false) == 0)
	{
		SendAllClientCreateMessages(uGuid);
		if (s_pfnClientCreateMessagesSentFn)
		{
			s_pfnClientCreateMessagesSentFn(uGuid);
		}
	}
	else
	{
		s_xClientsAwaitingCreateMessages.Push_Back(uGuid);
	}
}

#ifdef ASURA_SUPPORT_NETMSG_SYSTEM
void Asura_Network::AddClientToIgnoreNetMsgUpdatesList(Asura_Guid uGuid)
{
	Asura_Assert(IsServer(), "Only the server should be using the net message ignore list.");
	Asura_Assertf(!s_xClientsAwaitingCreateMessages.Find(uGuid), ("Client %d set to ignore net message updates twice.", uGuid));

	// If this client is on the server machine, the call is irrelevant as net message updates are never sent to them anyway.
	if (!Asura_Network::GetMachineForPlayer(uGuid, false) == 0)
	{
		s_xClientsIgnoringNetMsgUpdates.Push_Front(uGuid);
	}
}

void Asura_Network::RemoveClientFromIgnoreNetMsgUpdatesList(Asura_Guid uGuid)
{
	Asura_Assert(IsServer(), "Only the server should be using the net message ignore list.");

	s_xClientsIgnoringNetMsgUpdates.Remove(uGuid);
}

const Asura_NetMsg* Asura_Network::RegisterNetMsg(u_short usMsgID, u_short usEntityClassification)
{

	const Asura_NetMsg* pxResult = Project_RegisterNetMsg(usMsgID, usEntityClassification);
#if defined(ASURA_NETMSG_PACK_ALL_DATA)
	if (!pxResult)
	{
		pxResult = Asura_Message_Registration::RegisterNetMsg(usMsgID, usEntityClassification);
	}
	if (!pxResult)
	{
		// see if it's an update message (these are currently taken care of by the entity system).
		bool bOK = Asura_Entity_System::RegisterUpdateMessage(usMsgID, usEntityClassification);
		if (bOK)
		{
			pxResult = Asura_NetMsg::FindNetMsgObject(usMsgID, usEntityClassification);
		}
#ifdef ASURA_LOGFILES_ENABLED
		else
		{
			// output the missing one to a log file. Combinations of msg ID and entity classification
			// which have already been flagged are stored in an array. A linear search is done to see
			// if this error has already been reported, if not it is added to the list. Once the max
			// number that can be stored is exceeded no further logging is done.
			u_int uEntClassAndMsgID = (usMsgID << 16) | usEntityClassification;
			bool bAlreadyReported = false;
			static const u_int uMAX_FAILURES_TO_REMEMBER = 64;
			static u_int g_auAlreadyReported[uMAX_FAILURES_TO_REMEMBER];
			static u_int g_uNumAlreadyReported;
			for (u_int u = 0; u < g_uNumAlreadyReported; ++u)
			{
				if (g_auAlreadyReported[u] == uEntClassAndMsgID)
				{
					bAlreadyReported = true;
				}
			}
			if (!bAlreadyReported)
			{
				Asura_Assert(g_uNumAlreadyReported < uMAX_FAILURES_TO_REMEMBER, "too many missing NetMsg registrations");
				if (g_uNumAlreadyReported < uMAX_FAILURES_TO_REMEMBER)
				{
					const char* const szLogfileName = "logfiles\\missing_netmsgs.txt";
					static bool g_bFirstTime = true;
					if (g_bFirstTime)
					{
						Asura_SimpleLogfileClear(szLogfileName);
						g_bFirstTime = false;
					}
					g_auAlreadyReported[g_uNumAlreadyReported++] = uEntClassAndMsgID;
					Asura_SimpleLogfileOutput((szLogfileName, "%s (%s)\n", GetMsgIDDesc(usMsgID), GetEntityClassificationDesc(usEntityClassification)));
				}
			}
		}
#endif
	}
#endif // #if defined(ASURA_NETMSG_PACK_ALL_DATA)
	return pxResult;
}

// IsMessageDataInNetworkByteOrder() returns true for any messages which are able to deal with 
// putting their data into network byte order themselves. This will need to be used for any 
// messages which can't use the NetMsg registration system to pack into a byte stream e.g. ones
// which have a variable unpacked data size.
bool Asura_Network::IsMessageDataInNetworkByteOrder(u_short usMsgID, u_short usEntityClassification)
{
	switch (usMsgID)
	{
		case ASURA_MSG_NETMSGUPDATE:
			return true;
	}
	return Project_IsMessageDataInNetworkByteOrder(usMsgID, usEntityClassification);
}
#endif //#ifdef ASURA_SUPPORT_NETMSG_SYSTEM

void Asura_Network::UpdateClientCreateMessageList()
{
	Asura_Assert(IsServer(), "Only the server should be using the client creation lists.");

	if (s_xClientsAwaitingCreateMessages.Size() == 0 && s_xClientsReceivingCreateMessages.Size() == 0) { return; }

	// First, set up some clients to receive the messages
	while ((s_xClientsAwaitingCreateMessages.Size() > 0) && (s_xClientsReceivingCreateMessages.Size() < dbg_uMaxSimultaneousClientsReceivingCreateMessages))
	{
		Asura_ListElt<Asura_Guid>* pxElement = s_xClientsAwaitingCreateMessages.Front();

		CreatingClientData* pxNewData = new CreatingClientData;
		if (pxNewData)
		{
			pxNewData->m_uClientGuid = pxElement->Data();
			// Create the list of guids to send create messages to.
			BuildEntitiesToCreateList(pxNewData->m_xEntitiesToCreate);

			s_xClientsReceivingCreateMessages.Push_Front(pxNewData);
			s_xClientsAwaitingCreateMessages.Remove(pxElement);
		}
		else
		{
			Asura_Assertf(false, ("Failed to set up data for create messages to send to client %d.", pxElement->Data()));
			break;
		}
	}

	// Then send some of these messages to the clients
	s_fTimeUntilNextCreateMessageBatch -= Asura_Timers::GetFrameTime();

	if (s_fTimeUntilNextCreateMessageBatch <= 0.0f)
	{
		// TODO - use up the excess time, or leave as-is and just reset to the full time?
		s_fTimeUntilNextCreateMessageBatch = dbg_fCreateMessageBatchTimestep;

		Asura_ListElt<CreatingClientData*>* pxElement = s_xClientsReceivingCreateMessages.Front();
		while (pxElement)
		{
			Asura_ListElt<CreatingClientData*>* pxCurrentElement = pxElement;
			pxElement = pxElement->Next();

			CreatingClientData* pxData = pxCurrentElement->Data();
			for (u_int u=0; (u < dbg_uMaxCreateMessagesPerClientPerBatch) && (pxData->m_xEntitiesToCreate.Size() > 0); ++u)
			{
				Asura_ListElt<Asura_Guid>* pxGuidElement = pxData->m_xEntitiesToCreate.Front();

				Asura_Entity_Server* pxEntity = Asura_Entity_Server::FindEntity(pxGuidElement->Data());
				if (pxEntity)
				{
					pxEntity->MakeCreateMessage(pxData->m_uClientGuid, true);
				}

				pxData->m_xEntitiesToCreate.Remove(pxGuidElement);
			}

			if (pxData->m_xEntitiesToCreate.Size() == 0)
			{
				// Finished sending messages, remove from the list.
				s_xClientsReceivingCreateMessages.Remove(pxCurrentElement);
				if (s_pfnClientCreateMessagesSentFn)
				{
					s_pfnClientCreateMessagesSentFn(pxData->m_uClientGuid);
				}
				delete pxData;
			}
		}
	}
}

void Asura_Network::RemoveClientFromInitialisationLists(Asura_Guid uGuid)
{
	Asura_Assert(IsServer(), "Only the server should be using the client creation lists.");

	// AJG - 21/01/08 - Remove from the client creation lists, if there. This does nothing if it's not in the list.
	s_xClientsAwaitingCreateMessages.Remove(uGuid);
#ifdef ASURA_SUPPORT_NETMSG_SYSTEM
	s_xClientsIgnoringNetMsgUpdates.Remove(uGuid);
#endif //#ifdef ASURA_SUPPORT_NETMSG_SYSTEM
	// If they're in the middle of receiving the messages, this is slightly more complex, as we need to delete the data too.
	Asura_ListElt<CreatingClientData*>* pxElement = s_xClientsReceivingCreateMessages.Front();
	while (pxElement)
	{
		CreatingClientData* pxData = pxElement->Data();
		if (pxData->m_uClientGuid == uGuid)
		{
			s_xClientsReceivingCreateMessages.Remove(pxElement);
			if (s_pfnClientCreateMessagesSentFn)
			{
				s_pfnClientCreateMessagesSentFn(uGuid);
			}
			delete pxData;
			break;
		}
		pxElement = pxElement->Next();
	}
}

void Asura_Network::BuildEntitiesToCreateList(Asura_LinkList<Asura_Guid>& xList)
{
	Asura_Assert(IsServer(), "Only the server should be using the client creation lists.");

	Asura_Classified_List_Const_It<Asura_Entity_Server> xIt(Asura_Entity_Server::GetList());

	while (!xIt.Done())
	{
		const Asura_Entity_Server* pxCurrent = xIt.GetCurrent();
		xIt.Next();

		if (ShouldAddToCreateMessageList(pxCurrent))
		{
			xList.Push_Back(pxCurrent->GetGuid());
		}
	}
}

void Asura_Network::SendAllClientCreateMessages(Asura_Guid uGuid)
{
	Asura_Assert(IsServer(), "Only the server should be using the client creation lists.");

	Asura_Classified_List_It<Asura_Entity_Server> xIt(Asura_Entity_Server::GetList());

	while (!xIt.Done())
	{
		Asura_Entity_Server* pxCurrent = xIt.GetCurrent();
		xIt.Next();

		if (ShouldAddToCreateMessageList(pxCurrent))
		{
			pxCurrent->MakeCreateMessage(uGuid, true);
		}
	}
}

bool Asura_Network::ShouldAddToCreateMessageList(const Asura_Entity_Server* pxEntity)
{
#ifdef ASURA_ALLOW_INACTIVE_CLIENT_ENTITIES

	// The standard behaviour if allowing inactive clients is for clients to be created only if they're active or if they don't have an update message (in which case they'll have to make sure they send a CLIENTCREATEINACTIVE message if appropriate).
	return pxEntity && pxEntity->GetHasCreateMessage() && (pxEntity->IsEntityActive() || !pxEntity->GetShouldAddToClientUpdateListWhenActivated());

#else //#ifdef ASURA_ALLOW_INACTIVE_CLIENT_ENTITIES

	// The standard behaviour if destroying inactive clients is for clients to be created only if they're active.
	return pxEntity && pxEntity->GetHasCreateMessage() && pxEntity->IsEntityActive();

#endif //#else //#ifdef ASURA_ALLOW_INACTIVE_CLIENT_ENTITIES
}

#ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
// A S U R A _ N E T W O R K _ M E S S A G E B U F F E R /////////////////////
Asura_Network_MessageBuffer::Asura_Network_MessageBuffer(int iDestinationMachine, bool bGuaranteed)
: m_pcBuffer(new char[ASURA_NETWORK_MESSAGE_BUFFER_SIZE])
, m_uPosition (0)
, m_uMsgCount( 0 )
, m_iDestination(iDestinationMachine)
, m_bGuaranteed(bGuaranteed)
{
	// NB all allocation done with initialiser list so ptr can be const address.
}


Asura_Network_MessageBuffer::~Asura_Network_MessageBuffer()
{
	delete[] m_pcBuffer;
}


void Asura_Network_MessageBuffer::AddMessage(const Asura_Message* pxMsg)
{
	// if sending to a single client machine, check if message is wanted.
	// Don't do the check for broadcast or for messages to server.
	if (m_iDestination >= Dest_Client1)
	{
		// see if any players on the machine should be sent the message
		bool bShouldSend = false;
		const Asura_Network_ClientMachineStatus* pxMachine = &Asura_Network::s_pxClientMachineStatus[m_iDestination];
		u_int uNumPlayers = pxMachine->GetNumberOfPlayers();
		Asura_Assert(uNumPlayers, "no players on machine");
		for (u_int u = 0; u < uNumPlayers; ++u)
		{
			Asura_Guid uGuid = pxMachine->GetPlayerGuid(u);
			if (Asura_Network::ShouldMessageBeSentToClient( pxMsg, uGuid ))
			{
				bShouldSend = true;
				break;
			}
		}
		if (!bShouldSend)
		{
			return;
		}
	}

	if (dbg_bPrintAllBufferedMessages)
	{
		Asura_OutputToDebugger(("Buffer Message ID = %d, From Guid %d, To Guid %d, Data Size = %d.\n", 
			pxMsg->GetMsgID(), pxMsg->From, pxMsg->To, pxMsg->GetDataSize()));
	}

	// the message buffers aren't threadsafe so check if this is being called from a different thread.
	if ( !Asura_Core::IsMainThread() )
	{
		SendMessageToMyDestination(pxMsg);
		return;
	}

	// send messages which won't fit directly
	if ( sizeof(Asura_MessageHeader) + pxMsg->GetDataSize() > ASURA_NETWORK_MESSAGE_BUFFER_SIZE )
	{
		SendMessageToMyDestination(pxMsg);
		return;
	}

	// try to pack the message.
	u_int uBytesUsed = Asura_MessagePacking::PackMessageToBuffer(reinterpret_cast<u_char*>
		(m_pcBuffer + m_uPosition), ASURA_NETWORK_MESSAGE_BUFFER_SIZE - m_uPosition, *pxMsg);
	if (!uBytesUsed)
	{
		Flush();
		uBytesUsed = Asura_MessagePacking::PackMessageToBuffer(reinterpret_cast<u_char*>(m_pcBuffer), ASURA_NETWORK_MESSAGE_BUFFER_SIZE, *pxMsg);
	}
	Asura_Assert(uBytesUsed, "message packing failed");

#ifdef ASURA_ASSERT
	// check this will unpack OK
	Asura_MessageHeader xTempMsgHeader;
	u_int uSizeOfPackedData = 0;
	const void* pData = Asura_MessagePacking::UnpackBufferToMessageHeader(m_pcBuffer + m_uPosition, 
		pxMsg->IsUsingGuaranteedDelivery(), xTempMsgHeader, uSizeOfPackedData);

	Asura_MessageHeader xTempMsgHeader2;
	xTempMsgHeader2 = *pxMsg;

	Asura_Assert(xTempMsgHeader == xTempMsgHeader2, "msg header didn't unpack");
	// (MH - 07.01.2009) now that we're sometimes packing message data this assert might not hold true.
	Asura_Assert(uSizeOfPackedData || !Asura_Mem::Compare(pData, pxMsg->GetData(), pxMsg->GetDataSize()), "msg data wasn't unpacked");
#endif

	if (uBytesUsed)
	{
		m_uPosition += uBytesUsed;
		++m_uMsgCount;
	}

	Asura_Assert(m_uPosition <= ASURA_NETWORK_MESSAGE_BUFFER_SIZE, "sanity check");
}


void Asura_Network_MessageBuffer::Flush()
{
	Asura_Assert(m_uPosition <= ASURA_NETWORK_MESSAGE_BUFFER_SIZE, "sanity check");
    Asura_Assert( !m_uPosition == !m_uMsgCount, "" );
	if (m_uPosition > 0)
	{
        Asura_Message* pxMsg;

#ifdef ASURA_NETMSG_PACK_ALL_DATA
		// temporary solution - need to change ANTS code called by SendMessageToMyDestination() to 
		// deal with packed message headers properly then there would be no overhead and no special
		// case code here.
		{
			pxMsg = Asura_Network::CreateMessage( ASURA_MSG_PACKEDMESSAGES, m_uPosition, 0, m_pcBuffer );
			if( pxMsg )
			{
				pxMsg->From = ( Asura_Network::IsServer() ? ASURA_GUID_SERVER : ASURA_GUID_CLIENT );
			}
		}
#else
		if( m_uMsgCount > 1 )
        {
		    pxMsg = Asura_Network::CreateMessage( ASURA_MSG_PACKEDMESSAGES, m_uPosition, 0, m_pcBuffer );
            if( pxMsg )
            {
			    pxMsg->From = ( Asura_Network::IsServer() ? ASURA_GUID_SERVER : ASURA_GUID_CLIENT );
            }
        }
        else
        {
            // Don't want the overhead of packing the message if there's only
            // a single one in there.
			Asura_MessageHeader xMsgHdr;
			u_int uSizeOfPackedData = 0;
			const void* pMsgData = Asura_MessagePacking::UnpackBufferToMessageHeader(
				reinterpret_cast<u_char*>(m_pcBuffer), m_bGuaranteed, xMsgHdr, uSizeOfPackedData);
            
		    pxMsg = Asura_Network::CreateMessage( xMsgHdr.m_usMsgID,
												  uSizeOfPackedData ? uSizeOfPackedData : xMsgHdr.GetDataSize(),
                                                  xMsgHdr.m_usEntityClassification,
                                                  pMsgData );

            if( pxMsg )
            {
                pxMsg->To       = xMsgHdr.m_uToGuid;
                pxMsg->From     = xMsgHdr.m_uFromGuid;
                pxMsg->SentTime = xMsgHdr.m_fSentTime;
            }

			if (dbg_bPrintAllBufferedMessages)
			{
				Asura_OutputToDebugger(("unbuffer Message ID = %d, From Guid %d, To Guid %d, Data Size = %d.\n", 
					pxMsg->GetMsgID(), pxMsg->From, pxMsg->To, pxMsg->GetDataSize()));
			}

        }
#endif

        if( pxMsg )
        {
			if( m_bGuaranteed ) { pxMsg->UseGuaranteedDelivery(); }
            
		    SendMessageToMyDestination( pxMsg );
		    Asura_Network::DestroyMessage( pxMsg );
        }

		m_uPosition = 0;
        m_uMsgCount = 0;
	}
}


void Asura_Network_MessageBuffer::SendMessageToMyDestination(const Asura_Message* pxMessage)
{
#if ASURA_NETWORK_LOGGING
	if (g_eForceNetworkMsgLogLevel != Asura_Network::MSG_LOG_LEVEL_NUM_LEVELS)
	{
		Asura_Network::SetMsgLogLevel(g_eForceNetworkMsgLogLevel);
		g_eForceNetworkMsgLogLevel = Asura_Network::MSG_LOG_LEVEL_NUM_LEVELS;
	}

	if (Asura_Network::GetMsgLogLevel() != Asura_Network::MSG_LOG_LEVEL_NONE && Asura_Network::s_bLogMessagesSent)
	{
		Asura_MessageHeader xMessageHeader;
		xMessageHeader = *pxMessage;
		bool bIndent = false;
		bool bReceived = false;
		int iMachine = m_iDestination;
		Asura_Network::LogMsgInfo( xMessageHeader, pxMessage->GetData(), bIndent, bReceived, iMachine );
	}
#endif

	if (m_iDestination == Dest_Server)
	{
		Asura_Network::Platform_SendMessageToServer(pxMessage);
	}
#ifdef ASURA_NETWORK_USE_BROADCAST_FOR_GAME_DATA
	else if (m_iDestination == Dest_AllClients)
	{
		Asura_Network::Platform_SendMessageToAllClients(pxMessage);
	}
#endif
	else
	{
		Asura_Network::Platform_SendMessageToClientMachine(pxMessage, m_iDestination);
	}
}


// A S U R A _ N E T W O R K _ M E S S A G E B U F F E R _ M A N A G E R /////
Asura_Network_MessageBuffer_Manager::Asura_Network_MessageBuffer_Manager(int iNumMachines)
{
	m_paxBufferArray[MSG_NOT_GUARANTEED] = new Asura_Network_MessageBuffer*[iNumMachines];
	m_paxBufferArray[MSG_GUARANTEED] = new Asura_Network_MessageBuffer*[iNumMachines];
	for (int i = 0; i < iNumMachines; ++i)
	{
		m_paxBufferArray[MSG_NOT_GUARANTEED][i] = new Asura_Network_MessageBuffer(i, false);
		m_paxBufferArray[MSG_GUARANTEED][i] = new Asura_Network_MessageBuffer(i, true);
	}
#ifdef ASURA_NETWORK_USE_BROADCAST_FOR_GAME_DATA
	m_pxBroadcastBuffer = new Asura_Network_MessageBuffer( Asura_Network_MessageBuffer::Dest_AllClients, false);
#endif
	m_iNumMachines = iNumMachines;
}


Asura_Network_MessageBuffer_Manager::~Asura_Network_MessageBuffer_Manager()
{
#ifdef ASURA_NETWORK_USE_BROADCAST_FOR_GAME_DATA
	delete m_pxBroadcastBuffer;
#endif
	for (int i = 0; i < m_iNumMachines; ++i)
	{
		delete m_paxBufferArray[MSG_GUARANTEED][i];
		delete m_paxBufferArray[MSG_NOT_GUARANTEED][i];
	}
	delete[] m_paxBufferArray[MSG_GUARANTEED];
	delete[] m_paxBufferArray[MSG_NOT_GUARANTEED];
}


void Asura_Network_MessageBuffer_Manager::AddMessage(const Asura_Message* pxMessage, int iDestinationMachine)
{
	u_int uIndex = pxMessage->IsUsingGuaranteedDelivery() ? MSG_GUARANTEED : MSG_NOT_GUARANTEED;
	Asura_Assert(iDestinationMachine >= 0 && iDestinationMachine < m_iNumMachines, "invalid destination");
	m_paxBufferArray[uIndex][iDestinationMachine]->AddMessage(pxMessage);
}


#ifdef ASURA_NETWORK_USE_BROADCAST_FOR_GAME_DATA
void Asura_Network_MessageBuffer_Manager::AddBroadcastMessage(const Asura_Message* pxMessage)
{
	Asura_Assert(!pxMessage->IsUsingGuaranteedDelivery(), "broadcasts can't be guaranteed");
	m_pxBroadcastBuffer->AddMessage(pxMessage);
}
#endif

void Asura_Network_MessageBuffer_Manager::FlushAll()
{
	for (int i = 0; i < m_iNumMachines; ++i)
	{
		m_paxBufferArray[MSG_NOT_GUARANTEED][i]->Flush();
		m_paxBufferArray[MSG_GUARANTEED][i]->Flush();
	}
#ifdef ASURA_NETWORK_USE_BROADCAST_FOR_GAME_DATA
	m_pxBroadcastBuffer->Flush();
#endif
}
#endif // #ifdef ASURA_SUPPORT_EXTERNAL_NETWORK

