//interfacemultiplayer.cpp
//the multiplayer game setup menus

#include <OpenGL/gl.h>
#include "gametime.h"
#include <string.h>
#include <stdio.h>
#include "controls.h"
#include "text.h"
#include "interface.h"
#include "screen.h"
#include "network.h"
#include "gameinitexit.h"
#include "gamesystem.h"
#include "carphysics.h"
#include "carselection.h"
#include "mapselection.h"
#include "config.h"
#include "interfaceutil.h"
#include "tracker.h"
#include "random.h"
#include "gamesound.h"
#include "error.h"
#include "initexit.h"
#include "environment.h"
#include "localtracker.h"
#include "writeout.h"
#include "interfacemultiplayer.h"
#include "reg_tool_3.h"
#include "rt3_redline.h"
#undef MemoryMove
#include "gamemem.h"

enum{
	kMultiplayerHostControls,
	kMultiplayerAICars,
	kMultiplayerGameMode,
	kMultiplayerSelectMap,
	kMultiplayerSelectCar,
	//kMultiplayerMiniGame,
	kMultiplayerStartGame,
	kMultiplayerExit,
	//kMultiplayerChat,
	kNumMultiplayerOptions,
	kMultiplayerStartGameSignal,
	kMultiplayerForcedExit
};

UInt64 gLicenses[kMaxPlayers];
UInt32 gLicenseCopies[kMaxPlayers];

typedef struct{
	float t;
	tInterfaceMenuDescribtion *menu;
	tGameInfo *gInfo;
	float lastSendTime;
	int playerCar,playerID;
	int locked;
	UInt8 playerColor;
	tChatBuffer cb;
} tMultiplayerMenuData;

int gConfirmedVersion=-1;
int gPlayerListShowEntry=-1;
char gDisconnectString[256];
tChatBuffer *gChatBuffer;
int gOldstartable;
int gReady=false;

void AddNewPlayer(tGameInfo *gInfo,int netID)
{	
	//answer with a message telling the player his ID
	NetworkSendPacket(kMessageTypeID,&(gInfo->numNetPlayers),sizeof(UInt8),kMessagePriorityHigh,netID);
	//and update the gInfo structure 
	if(gInfo->numNetPlayers)
		gInfo->playerCars[gInfo->numNetPlayers]=-1;
	gInfo->netID[gInfo->numNetPlayers]=netID;
	gInfo->playerInited[gInfo->numNetPlayers]=false;
	gInfo->playerVersions[gInfo->numNetPlayers]=0;
	gInfo->numNetPlayers++;
	if(gInfo->numPlayers<gInfo->numNetPlayers)
		gInfo->numPlayers=gInfo->numNetPlayers;

	gInfo->version++;
	gInfo->playerVersions[0]=gInfo->version;
	SystemNotify();
}

void FixGInfoForLeftPlayers(tGameInfo *gInfo)
{
	int minPlayerLeft=-1;
	int playerLeft;
	//PrintConsoleString("Fixing GInfo");
	do
	{
		playerLeft=-1;
		
		for(int i=1;i<gInfo->numNetPlayers;i++)
			if(gInfo->netID[i]==-1)
			{
				playerLeft=i;		
				minPlayerLeft=i;
			}
				
		if(playerLeft!=-1)
		{
			//PrintConsoleString("Player %d left",playerLeft);
			//shift down all the player information for players with higher player ids
			for(UInt8 i=playerLeft;i<gInfo->numPlayers-1;i++)
			{
				gInfo->playerCars[i]=gInfo->playerCars[i+1];
				gInfo->netID[i]=gInfo->netID[i+1];
				gInfo->ping[i]=gInfo->ping[i+1];
				gInfo->playerAddOns[i]=gInfo->playerAddOns[i+1];
				gInfo->playerColors[i]=gInfo->playerColors[i+1];
				gInfo->playerVersions[i]=gInfo->playerVersions[i+1];
				gInfo->playerInited[i]=gInfo->playerInited[i+1];
				strcpy(gInfo->playerCarNames[i],gInfo->playerCarNames[i+1]);
				strcpy(gInfo->playerNames[i],gInfo->playerNames[i+1]);
				gLicenses[i]=gLicenses[i+1];
				gLicenseCopies[i]=gLicenseCopies[i+1];
			}
			for(UInt8 i=gInfo->numPlayers-1;i<kMaxPlayers;i++)
			{
				gInfo->playerCars[i]=-1;
				gInfo->netID[i]=-1;
				gInfo->playerCarNames[i][0]='\0';
				gInfo->playerNames[i][0]='\0';
				gInfo->playerInited[i]=false;
			}
			gInfo->numPlayers--;
			gInfo->numNetPlayers--;
		}
	}
	while(playerLeft!=-1);
	
	if(minPlayerLeft!=-1)	
		for(UInt8 i=minPlayerLeft;i<gInfo->numNetPlayers;i++)
			//send a message to the player telling him/her to update his/her info
		{
			NetworkSendPacket(kMessageTypeID,&i,sizeof(UInt8),kMessagePriorityHigh,gInfo->netID[i]);		
			//PrintConsoleString("member %d is now player %d",gInfo->netID[i],i);
		}
	gInfo->version++;
	gInfo->playerVersions[0]=gInfo->version;
}

//adds the line in str to the chat buffer, and scrolls buffer if necessary
void ChatBufferInsert(tChatMessage *msg,tChatBuffer *cb)
{
	msg->recvTime=TimeGetSeconds();
	if(msg->str[0]!='\0')
	{
		if(cb->chatBufferLines<kChatBufferMaxLines)
		{
			cb->chatBuffer[cb->chatBufferLines]=*msg;
			cb->chatBufferLines++;
		}
		else //scrolling is needed
		{
			for(int i=0;i<kChatBufferMaxLines-1;i++)
				cb->chatBuffer[i]=cb->chatBuffer[i+1];
			cb->chatBuffer[kChatBufferMaxLines-1]=*msg;			
		}
	}	
}

void VerifyLicenseCopies(tGameInfo *gInfo)
{
	for(int i=1;i<gInfo->numNetPlayers;i++)
		if(gInfo->playerInited[i])
			if(gLicenses[i])
			{
				int numLicensesUsed=1;
				for(int j=0;j<i;j++)
				{
					int same;
					qRT3_LicenseIsSameCode(gLicenses[i],gLicenses[j],same);
					if(same)
						numLicensesUsed++;
				}
				if(numLicensesUsed>gLicenseCopies[i])
				{
					NetworkDisconnectPlayer(gInfo->netID[i],kNetworkDisconnectLicenseCopies);
					tChatMessage m;
					memset((void*)&m,0,sizeof(tChatMessage));
					m.flags=kChatFlagSystem;
					sprintf(m.str,"### %s's license code is used by too many players.",gInfo->playerNames[i]);
					NetworkSendPacket(kMessageTypeChat,&m,sizeof(tChatMessage),kMessagePriorityHigh,kMessageSendToAll);					
				}
				int valid=true;
				int invalid=false;
				if(!invalid)qRT3_LicenseIsSameCode(gLicenses[i],RT3_PIRATED_CODE_01,invalid);
				if(!invalid)qRT3_LicenseIsSameCode(gLicenses[i],RT3_PIRATED_CODE_02,invalid);
				if(!invalid)qRT3_LicenseIsSameCode(gLicenses[i],RT3_PIRATED_FAKE_03,invalid);
				if(!invalid)qRT3_LicenseIsSameCode(gLicenses[i],RT3_PIRATED_FAKE_04,invalid);
				if(!invalid)qRT3_LicenseIsSameCode(gLicenses[i],RT3_PIRATED_FAKE_05,invalid);
				if(!invalid)qRT3_LicenseIsSameCode(gLicenses[i],RT3_PIRATED_FAKE_06,invalid);
				if(!invalid)qRT3_LicenseIsSameCode(gLicenses[i],RT3_PIRATED_FAKE_07,invalid);
				if(!invalid)qRT3_LicenseIsSameCode(gLicenses[i],RT3_PIRATED_FAKE_08,invalid);
				if(!invalid)qRT3_LicenseIsSameCode(gLicenses[i],RT3_PIRATED_FAKE_09,invalid);
				if(!invalid)qRT3_LicenseIsSameCode(gLicenses[i],RT3_PIRATED_FAKE_10,invalid);
				if(!invalid)qRT3_LicenseIsSameCode(gLicenses[i],RT3_PIRATED_FAKE_11,invalid);
				if(!invalid)qRT3_LicenseIsSameCode(gLicenses[i],RT3_PIRATED_FAKE_12,invalid);
				if(!invalid)qRT3_LicenseIsSameCode(gLicenses[i],RT3_PIRATED_FAKE_13,invalid);
				if(!invalid)qRT3_LicenseIsSameCode(gLicenses[i],RT3_PIRATED_FAKE_14,invalid);
				if(!invalid)qRT3_LicenseIsSameCode(gLicenses[i],RT3_PIRATED_FAKE_15,invalid);
				if(!invalid)qRT3_LicenseIsSameCode(gLicenses[i],RT3_PIRATED_FAKE_16,invalid);
				if(!invalid)qRT3_LicenseIsSameCode(gLicenses[i],RT3_PIRATED_FAKE_17,invalid);
				if(!invalid)qRT3_LicenseIsSameCode(gLicenses[i],RT3_PIRATED_FAKE_18,invalid);
				if(!invalid)qRT3_LicenseIsSameCode(gLicenses[i],RT3_PIRATED_FAKE_19,invalid);
				if(!invalid)qRT3_LicenseIsSameCode(gLicenses[i],RT3_PIRATED_FAKE_20,invalid);
				if(!invalid)qRT3_LicenseTestSum(gLicenses[i],valid);
				valid=!invalid;
				if(!valid)
				{
					NetworkDisconnectPlayer(gInfo->netID[i],kNetworkDisconnectPirate);
					tChatMessage m;
					memset((void*)&m,0,sizeof(tChatMessage));
					m.flags=kChatFlagSystem;
					sprintf(m.str,"### %s's license code is invalid.",gInfo->playerNames[i]);
					NetworkSendPacket(kMessageTypeChat,&m,sizeof(tChatMessage),kMessagePriorityHigh,kMessageSendToAll);					
				}
			}
			else if(gConfig->onlyRegisteredPlayers)
			{
				NetworkDisconnectPlayer(gInfo->netID[i],kNetworkDisconnectNoDemo);
				tChatMessage m;
				memset((void*)&m,0,sizeof(tChatMessage));
				m.flags=kChatFlagSystem;
				sprintf(m.str,"### %s is not registered.",gInfo->playerNames[i]);
				NetworkSendPacket(kMessageTypeChat,&m,sizeof(tChatMessage),kMessagePriorityHigh,kMessageSendToAll);					
			}
}

//processes network packets while the host is waiting for players to join
void WaitForPlayers(tGameInfo *gInfo,tChatBuffer *cb)
{
	float timeStamp=TimeGetSeconds();
	
	//received any network messages?
	tNetworkPacket message;
	while(NetworkReceivePacket(&message))
	{
		switch(message.what)
		{		
			//has a player joined?
			case kMessageTypePlayerJoined:
				{
					UInt32 reply=kVersion;
					U32Swap(reply);
					NetworkSendPacket(kMessageTypeVersion,&reply,sizeof(UInt32),kMessagePriorityHigh,message.from);				
					//PrintConsoleString("Player %d joined, NT#:%d",gInfo->numNetPlayers,message.from);
				}
				break;
				
			case kMessageTypeVersion:	
				U32Swap(*(UInt32*)message.data);
				if(*((UInt32*)message.data)>=kMinNetworkCompatibleVersion)			
					AddNewPlayer(gInfo,message.from);
				else
				{
					UInt8 reason=kJoinDeniedVersion;
					NetworkSendPacket(kMessageTypeJoinDenied,&reason,sizeof(UInt8),kMessagePriorityHigh,message.from);									
				}
				break;
				
			//a player has left
			case kMessageTypePlayerLeft:
				{
					int netID=message.from;
					int id=-1;
					//find the players id
					for(int i=0;i<gInfo->numNetPlayers;i++)
						if(gInfo->netID[i]==netID)	
							id=i;
					
					if(id!=-1)
					{
						tChatMessage m;
						memset((void*)&m,0,sizeof(tChatMessage));
						m.flags=kChatFlagSystem;
						if(gInfo->playerVersions[id]!=-2)
							sprintf(m.str,"### %s \255#n\255is disconnected due to network problems.",gInfo->playerNames[id]);
						else
							sprintf(m.str,"### %s \255#n\255quit the game.",gInfo->playerNames[id]);
						SoundReInit();
						PlayInterfaceSound(FileGetReference("systemchat.wav"));
						NetworkSendPacket(kMessageTypeChat,&m,sizeof(m),kMessagePriorityHigh,kMessageSendToAll);					

						gInfo->netID[id]=-1;
						FixGInfoForLeftPlayers(gInfo);
					}
				}
				break;
				
			case kMessageTypeBye:
				{
					int netID=message.from;
					int id=-1;
					//find the players id
					for(int i=0;i<gInfo->numNetPlayers;i++)
						if(gInfo->netID[i]==netID)	
							id=i;
					
					if(id!=-1)
					{
						gInfo->playerVersions[id]=-2;
						/*tChatMessage m;
						memset((void*)&m,0,sizeof(tChatMessage));
						m.flags=kChatFlagSystem;
						sprintf(m.str,"### %s is quitting.",gInfo->playerNames[id]);
						SoundReInit();
						PlayInterfaceSound(FileGetReference("systemchat.wav"));
						NetworkSendPacket(kMessageTypeChat,&m,sizeof(m),kMessagePriorityHigh,kMessageSendToAll);*/					
					}
				}
				break;
				
			//a player has told us which car he wishes to use:
			case kMessageTypeCarID:
				{
					int netID=message.from;
					SInt8 id=-1;
					//find the players id
					for(int i=0;i<gInfo->numNetPlayers;i++)
						if(gInfo->netID[i]==netID)	
							id=i;
					
					if(id!=-1)
					{
						if(id!=((tCarIDMessage*)message.data)->playerID)
							NetworkSendPacket(kMessageTypeID,&id,sizeof(UInt8),kMessagePriorityHigh,netID);

						//update the gInfo structure 
						if(_stricmp(gInfo->playerNames[id],((tCarIDMessage*)message.data)->playerName))
						{
							strcpy(gInfo->playerNames[id],((tCarIDMessage*)message.data)->playerName);
							tChatMessage m;
							memset((void*)&m,0,sizeof(tChatMessage));
							m.flags=kChatFlagSystem;
							SoundReInit();
							PlayInterfaceSound(FileGetReference("systemchat.wav"));
							sprintf(m.str,"### %s \255#n\255joined the game.",gInfo->playerNames[id]);
							NetworkSendPacket(kMessageTypeChat,&m,sizeof(m),kMessagePriorityHigh,kMessageSendToAll);					
						}
						
						if(gInfo->playerCars[id]!=FileGetReference(((tCarIDMessage*)message.data)->carName)||!gInfo->playerInited[id])
						{
							gInfo->playerCars[id]=FileGetReference(((tCarIDMessage*)message.data)->carName);
						//	gInfo->checksums[((tCarIDMessage*)message.data)->playerID]=((tCarIDMessage*)message.data)->checksum;
						//	U32Swap(gInfo->checksums[((tCarIDMessage*)message.data)->playerID]);
							
							strcpy(gInfo->playerCarNames[id],((tCarIDMessage*)message.data)->carName);
							gInfo->version++;
							gInfo->playerVersions[0]=gInfo->version;
						}
						gInfo->playerInited[id]=true;
						gInfo->playerColors[id]=((tCarIDMessage*)message.data)->color;
						
						gLicenses[id]=((tCarIDMessage*)message.data)->license;
						gLicenseCopies[id]=((tCarIDMessage*)message.data)->licenseCopies;
						
						U64Swap(gLicenses[id]);
						U32Swap(gLicenseCopies[id]);
						VerifyLicenseCopies(gInfo);
					}
				}
				break;
			
			//we have received a chat message
			case kMessageTypeChat:
				//fill in the chat receive buffer
				ChatBufferInsert((tChatMessage*)message.data,cb);
				if(((tChatMessage*)message.data)->flags&kChatFlagAlert)
				{
					SoundReInit();
					int tmp=gConfig->interfaceSounds;
					gConfig->interfaceSounds=true;
					if(((tChatMessage*)message.data)->flags&(kChatFlagSystem|kChatFlagAlert))
						PlayInterfaceSound(FileGetReference("systemchat.wav"));
					else
						PlayInterfaceSound(FileGetReference("chat.wav"));
					gConfig->interfaceSounds=tmp;
					SystemNotify();
				}
				else
				{
					SoundReInit();
					if(((tChatMessage*)message.data)->flags&(kChatFlagSystem|kChatFlagAlert))
						PlayInterfaceSound(FileGetReference("systemchat.wav"));
					else
						PlayInterfaceSound(FileGetReference("chat.wav"));
				}
				break;
			
			case kMessageTypeVersionConfirmed:
				S32Swap(((int*)message.data)[0]);	
				S32Swap(((int*)message.data)[1]);	
				if(gInfo->playerVersions[((int*)message.data)[1]]<((int*)message.data)[0])
					gInfo->playerVersions[((int*)message.data)[1]]=((int*)message.data)[0];
				break;		
					
			case kMessageTypeVersionDenied:
				S32Swap(((int*)message.data)[1]);	
				gInfo->playerVersions[((int*)message.data)[1]]=-1;
				break;			
		}
		NetworkDisposePacket(&message);
	}
}

void ConvertGInfoSwap(tGameInfo *gInfo)
{
	U32Swap(gInfo->version);
	for(int i=0;i<kMaxPlayers;i++)
	{
		S32Swap(gInfo->playerAddOns[i]);
		S32Swap(gInfo->playerVersions[i]);		
		F32Swap(gInfo->ping[i]);
	}
}

void ConvertGInfoSwap(tNetGameInfo *gInfo)
{
	U32Swap(gInfo->version);
	for(int i=0;i<kMaxPlayers;i++)
	{
		S32Swap(gInfo->playerAddOns[i]);
		S32Swap(gInfo->playerVersions[i]);		
		F32Swap(gInfo->ping[i]);
	}
}

//Send out Ping requests to all the clients
void GatherPingStats(tGameInfo *gInfo)
{
	for(int i=0;i<gInfo->numNetPlayers;i++)
		gInfo->ping[i]=NetworkGetPlayerPing(gInfo->netID[i]);
}

//tell the other players on the network that player has chosen a new car
void UpdatePlayerCar(tMultiplayerMenuData *userData)
{
	tCarIDMessage message;
	message.playerID=userData->playerID;
	message.color=userData->playerColor;
//	message.checksum=FileGetChecksum(userData->playerCar);
//	U32Swap(message.checksum);

	message.license=RT3_GetLicenseCode();
	message.licenseCopies=RT3_GetLicenseCopies();
	U64Swap(message.license);
	U32Swap(message.licenseCopies);

	sprintf(message.playerName,"%s%s",RT3_IsRegistered()?"":"\255demo.png\255 ",gConfig->playerName);
	strcpy(message.carName,FileGetName(userData->playerCar));
	NetworkSendPacket(kMessageTypeCarID,&message,sizeof(tCarIDMessage),kMessagePriorityHigh,kMessageSendToAllButSelf);
}

void GameInfoSend(tGameInfo *gInfo)
{
	strcpy(gInfo->environmentName,FileGetName(gInfo->environment));
	strcpy(gInfo->mapName,FileGetName(gInfo->map));
	for(int i=0;i<gInfo->numPlayers;i++)
		strcpy(gInfo->playerCarNames[i],FileGetName(gInfo->playerCars[i]));
}

void GameInfoReceive(tGameInfo *gInfo)
{
	gInfo->environment=FileGetReference(gInfo->environmentName);
	gInfo->map=FileGetReference(gInfo->mapName);
	for(int i=0;i<gInfo->numPlayers;i++)
		gInfo->playerCars[i]=FileGetReference(gInfo->playerCarNames[i]);
}



void CompressGInfoToPacket(tNetGameInfo *gInfoPacket,tGameInfo *gInfoSnd)
{
	GameInfoSend(gInfoSnd);
	gInfoPacket->inited=gInfoSnd->inited;
	gInfoPacket->numNetPlayers=gInfoSnd->numNetPlayers;
	gInfoPacket->numPlayers=gInfoSnd->numPlayers;
	gInfoPacket->reverse=gInfoSnd->reverse;
	gInfoPacket->numLaps=gInfoSnd->numLaps;
	gInfoPacket->network=gInfoSnd->network;
	gInfoPacket->arcade=gInfoSnd->arcade;
	gInfoPacket->carsOnSpeed=gInfoSnd->carsOnSpeed;
	gInfoPacket->demolition=gInfoSnd->demolition;
	gInfoPacket->version=gInfoSnd->version;
	strcpy(gInfoPacket->environmentName,gInfoSnd->environmentName);
	strcpy(gInfoPacket->mapName,gInfoSnd->mapName);
	for(int i=0;i<kMaxPlayers;i++)
	{
		gInfoPacket->playerColors[i]=gInfoSnd->playerColors[i];
		gInfoPacket->netID[i]=gInfoSnd->netID[i];
		gInfoPacket->playerAddOns[i]=gInfoSnd->playerAddOns[i];
		gInfoPacket->ping[i]=gInfoSnd->ping[i];
		gInfoPacket->playerVersions[i]=gInfoSnd->playerVersions[i];
		strcpy(gInfoPacket->playerCarNames[i],gInfoSnd->playerCarNames[i]);
		strcpy(gInfoPacket->playerNames[i],gInfoSnd->playerNames[i]);
	}
	ConvertGInfoSwap(gInfoPacket);
}

void ExtractGInfoFromPacket(tGameInfo *gInfoRcv,tNetGameInfo *gInfoPacket)
{
	gInfoRcv->inited=gInfoPacket->inited;
	gInfoRcv->numNetPlayers=gInfoPacket->numNetPlayers;
	gInfoRcv->numPlayers=gInfoPacket->numPlayers;
	gInfoRcv->reverse=gInfoPacket->reverse;
	gInfoRcv->numLaps=gInfoPacket->numLaps;
	gInfoRcv->network=gInfoPacket->network;
	gInfoRcv->arcade=gInfoPacket->arcade;
	gInfoRcv->carsOnSpeed=gInfoPacket->carsOnSpeed;
	gInfoRcv->demolition=gInfoPacket->demolition;
	gInfoRcv->version=gInfoPacket->version;
	gInfoRcv->maxTime=0;
	gInfoRcv->silverTime=0;
	gInfoRcv->goldTime=0;	
	strcpy(gInfoRcv->environmentName,gInfoPacket->environmentName);
	strcpy(gInfoRcv->mapName,gInfoPacket->mapName);
	for(int i=0;i<kMaxPlayers;i++)
	{
		gInfoRcv->playerColors[i]=gInfoPacket->playerColors[i];
		gInfoRcv->netID[i]=gInfoPacket->netID[i];
		gInfoRcv->playerAddOns[i]=gInfoPacket->playerAddOns[i];
		gInfoRcv->ping[i]=gInfoPacket->ping[i];
		gInfoRcv->playerVersions[i]=gInfoPacket->playerVersions[i];
		strcpy(gInfoRcv->playerCarNames[i],gInfoPacket->playerCarNames[i]);
		strcpy(gInfoRcv->playerNames[i],gInfoPacket->playerNames[i]);
	}
	ConvertGInfoSwap(gInfoRcv);
	GameInfoReceive(gInfoRcv);
}

//Send out a packet telling players to start the game
void StartNetGame(tGameInfo *gInfo)
{
	tNetGameInfo msg;
	CompressGInfoToPacket(&msg,gInfo);
	NetworkSendPacket(kMessageTypeStart,&msg,sizeof(tNetGameInfo),kMessagePriorityHigh,kMessageSendToAllButSelf);
}

void CheckGInfoVersion(tMultiplayerMenuData *userData,int playerID)
{
	if(playerID==-1)
		return;
	int ok=true;
	for(int i=0;i<userData->gInfo->numPlayers;i++)
		if(userData->gInfo->playerCars[i]==kFileErr&&(userData->gInfo->playerInited[i]||i==0))
		{
			tChatMessage m;
			m.flags=kChatFlagSystem;
			if(playerID!=i)
				sprintf(m.str,"### You don't have the data file for the car used by %s",userData->gInfo->playerNames[i]);
			else
				sprintf(m.str,"### The other players don't have the data file used by your car.");			
			SoundReInit();
			PlayInterfaceSound(FileGetReference("systemchat.wav"));
			ChatBufferInsert(&m,&userData->cb);
			ok=false;
		}
		else if(playerID==i)
		{
			tCarDefinition *car=(tCarDefinition*)FileGetParsedDataPtr(userData->gInfo->playerCars[i],kParserTypeCarDesc,sizeof(tCarDefinition));
			if(!car)
				ok=false;
			else if(!car->demoAvailable&&!RT3_IsRegistered())
			{
				tChatMessage m;
				m.flags=kChatFlagSystem;
				sprintf(m.str,"### You can't play the selected car, because you didn't register Redline.");
				SoundReInit();
				PlayInterfaceSound(FileGetReference("systemchat.wav"));
				ChatBufferInsert(&m,&userData->cb);
				ok=false;
			}	
		}
	if(userData->gInfo->environment==kFileErr)
	{
		tChatMessage m;
		m.flags=kChatFlagSystem;
		sprintf(m.str,"### You don't have the data file for the weather used.");
		SoundReInit();
		PlayInterfaceSound(FileGetReference("systemchat.wav"));
		ChatBufferInsert(&m,&userData->cb);
		ok=false;
	}
	if(userData->gInfo->map==kFileErr)
	{
		tChatMessage m;
		m.flags=kChatFlagSystem;
		sprintf(m.str,"### You don't have the data file for the track used.");
		SoundReInit();
		PlayInterfaceSound(FileGetReference("systemchat.wav"));
		ChatBufferInsert(&m,&userData->cb);
		ok=false;
	}
	else
	{
		tMapInfo *mapInfo=(tMapInfo*)FileGetParsedDataPtr(userData->gInfo->map,kParserTypeMapInfoDesc,sizeof(tMapInfo));
		if(!mapInfo->demoAvailable&&!RT3_IsRegistered())
		{
			tChatMessage m;
			m.flags=kChatFlagSystem;
			sprintf(m.str,"### You can't play the selected track, because you didn't register Redline.");
			SoundReInit();
			PlayInterfaceSound(FileGetReference("systemchat.wav"));
			ChatBufferInsert(&m,&userData->cb);
			ok=false;
		}	
	}
	
//	int reply[2];
//	reply[0]=gInfo->version;
//	reply[1]=playerID;
	if(ok)
	{
//		NetworkSendPacket(kMessageTypeVersionConfirmed,reply,sizeof(int)*2,kMessagePriorityHigh,kMessageSendToHostOnly);
		gConfirmedVersion=userData->gInfo->version;
		gReady=true;
	}
	else
	{
//		NetworkSendPacket(kMessageTypeVersionDenied,reply,sizeof(int)*2,kMessagePriorityHigh,kMessageSendToHostOnly);
		gConfirmedVersion=-1;
		gReady=false;
	}
//	lastSendTime=TimeGetSeconds();
}

//processes network packets while the client is waiting for the host to give the start signal
//returns true if start signal is recieved, false otherwise.
int WaitForStartSignal(tMultiplayerMenuData *userData,int *exit)
{
	tGameInfo *gInfo=userData->gInfo;
	tChatBuffer *cb=&userData->cb;	

	//received any network messages?
	tNetworkPacket message;
	while(NetworkReceivePacket(&message))
	{
		switch(message.what)
		{
			//got the start signal?
			case kMessageTypeStart:		
				ExtractGInfoFromPacket(userData->gInfo,(tNetGameInfo*)message.data);
				if(gInfo->netID[userData->playerID]!=NetworkGetLocalNetID())
					for(int i=0;i<gInfo->numNetPlayers;i++)
						if(gInfo->netID[i]==NetworkGetLocalNetID())
						{
							userData->playerID=i;
							//PrintConsoleString("fix: we are now player %d",userData->playerID);
							UpdatePlayerCar(userData);
							break;
						}
				NetworkDisposePacket(&message);
				*exit=kMultiplayerStartGameSignal;
				return true;
			
			//got an GameInfo structure update?
			case kMessageTypeGameInfo:
				ExtractGInfoFromPacket(userData->gInfo,(tNetGameInfo*)message.data);
				if(gInfo->netID[userData->playerID]!=NetworkGetLocalNetID())
					for(int i=0;i<gInfo->numNetPlayers;i++)
						if(gInfo->netID[i]==NetworkGetLocalNetID())
						{
							userData->playerID=i;
							//PrintConsoleString("fix: we are now player %d",userData->playerID);
							UpdatePlayerCar(userData);
							break;
						}
				if(gReady)
					CheckGInfoVersion(userData,userData->playerID);
				//GatherPingStats(gInfo);
				break;
			
			//got a chat message?
			case kMessageTypeChat:
				//fill in the chat receive buffer
				ChatBufferInsert((tChatMessage*)message.data,cb);
				if(((tChatMessage*)message.data)->flags&kChatFlagAlert)
				{
					SoundReInit();
					int tmp=gConfig->interfaceSounds;
					gConfig->interfaceSounds=true;
					if(((tChatMessage*)message.data)->flags&(kChatFlagSystem|kChatFlagAlert))
						PlayInterfaceSound(FileGetReference("systemchat.wav"));
					else
						PlayInterfaceSound(FileGetReference("chat.wav"));
					gConfig->interfaceSounds=tmp;
					SystemNotify();
				}
				else
				{
					SoundReInit();
					if(((tChatMessage*)message.data)->flags&(kChatFlagSystem|kChatFlagAlert))
						PlayInterfaceSound(FileGetReference("systemchat.wav"));
					else
						PlayInterfaceSound(FileGetReference("chat.wav"));
				}
				break;
			
			//change of our id
			case kMessageTypeID:
				userData->playerID=*(UInt8*)message.data;
				//PrintConsoleString("we are now player %d",userData->playerID);
				UpdatePlayerCar(userData);
				break;
			
			//game terminated by host
			case kMessageTypeBye:
				if(message.from==0)
				{
					sprintf(gDisconnectString,"%s stopped hosting and quit.",gInfo->playerNames[0]);
					*exit=kMultiplayerForcedExit;
					NetworkDisposePacket(&message);
					return true;
				}
				return false;

			case kMessageTypeGameTerminated:
				*exit=kMultiplayerForcedExit;
				NetworkDisposePacket(&message);
				return true;

			case kMessageTypeKicked:
				switch(*(UInt8*)message.data)
				{
					case kNetworkDisconnectBan:
						sprintf(gDisconnectString,"You have been kicked out of the game by the host.");
						break;
					case kNetworkDisconnectLicenseCopies:
						if(RT3_GetLicenseCopies()>1)
							sprintf(gDisconnectString,"Your license code is only valid for %d copies.",RT3_GetLicenseCopies());
						else
							sprintf(gDisconnectString,"Your license code is only valid for one copy.");							
						break;
					case kNetworkDisconnectPirate:
						sprintf(gDisconnectString,"The host did not accept your license code.");
						break;
					case kNetworkDisconnectNoDemo:
						sprintf(gDisconnectString,"The host does not allow unregistered players.");
						break;
					default:
						sprintf(gDisconnectString,"You were disconnected by the host.");
						break;						
				}
				*exit=kMultiplayerForcedExit;
				NetworkDisposePacket(&message);
				return true;
			
		}
		NetworkDisposePacket(&message);
	}
	return false;
}

void MultiplayerMenuUpdate(tMultiplayerMenuData *userData,int host)
{
	tInterfaceMenuDescribtion *menu=userData->menu;
	if(userData->gInfo->inited)
		if(userData->gInfo->network&kGameInfoNetworkForcePlayerCar)
			userData->playerCar=userData->gInfo->playerCars[0];
	/*if(userData->playerCar!=kFileErr)
	{
		tCarDefinition *car=(tCarDefinition*)FileGetParsedDataPtr(userData->playerCar,kParserTypeCarDesc,sizeof(tCarDefinition));
		sprintf(menu->items[kMultiplayerSelectCar].label,"Car: \255#a\255%s...",car->carName);
	}
	else
		sprintf(menu->items[kMultiplayerSelectCar].label,"Car: \255#a\255Data File Mismatch");	*/
	if(userData->gInfo->inited)
	{
		if(userData->gInfo->map!=-1)
		{
			tMapInfo *mapInfo=(tMapInfo*)FileGetParsedDataPtr(userData->gInfo->map,kParserTypeMapInfoDesc,sizeof(tMapInfo));
			tEnvironment *env=(tEnvironment*)FileGetParsedDataPtr(userData->gInfo->environment,kParserTypeEnvironmentDesc,sizeof(tEnvironment));
			//sprintf(menu->items[kMultiplayerSelectMap].label,"Map: \255#a\255%s (%s%s, %d Laps)...",mapInfo->name,userData->gInfo->reverse?"Reverse, ":"",env->name,userData->gInfo->numLaps);
			
		}
	/*	else
			sprintf(menu->items[kMultiplayerSelectMap].label,"Map: \255#a\255Data File Mismatch");
		switch(userData->gInfo->arcade)
		{
			case 0: sprintf(menu->items[kMultiplayerGameMode].label,"Game Mode: \255#a\255Simulation"); break;
			case 1: sprintf(menu->items[kMultiplayerGameMode].label,"Game Mode: \255#a\255Arcade"); break;
			case 2: sprintf(menu->items[kMultiplayerGameMode].label,"Game Mode: \255#a\255Turbo Arcade"); break;
		}*/
	}
	if(!host)
	{
		if(userData->gInfo->network&kGameInfoNetworkForcePlayerCar)
			menu->items[kMultiplayerSelectCar].flags|=kInterfaceMenuItemDisabled;
		else
			menu->items[kMultiplayerSelectCar].flags&=~kInterfaceMenuItemDisabled;
		
		if(userData->playerID==-1)
			menu->items[kMultiplayerStartGame].flags|=kInterfaceMenuItemDisabled;
		else if(gConfirmedVersion!=userData->gInfo->playerVersions[userData->playerID])
		{
			//menu->items[kMultiplayerStartGame].flags|=kInterfaceMenuItemDisabled;
			float time=TimeGetSeconds();
			if(time-userData->lastSendTime>0.5)
			{
				int reply[2];
				reply[0]=gConfirmedVersion;
				reply[1]=userData->playerID;
				S32Swap(reply[0]);
				S32Swap(reply[1]);
				if(gConfirmedVersion!=-1)
					NetworkSendPacket(kMessageTypeVersionConfirmed,reply,sizeof(int)*2,kMessagePriorityHigh,kMessageSendToHostOnly);
				else
					NetworkSendPacket(kMessageTypeVersionDenied,reply,sizeof(int)*2,kMessagePriorityHigh,kMessageSendToHostOnly);
				userData->lastSendTime=time;
			}
		}
		else
			menu->items[kMultiplayerStartGame].flags&=~kInterfaceMenuItemDisabled;
		
		if(userData->playerID!=-1)
		{
//			int startable=userData->gInfo->playerVersions[userData->playerID]==userData->gInfo->version;
			int startable=gReady;
			if(!startable)
				strcpy(menu->items[kMultiplayerStartGame].label,"Ready to Race: \255#a\255No");		
			else
				strcpy(menu->items[kMultiplayerStartGame].label,"Ready to Race: \255#a\255Yes");		
			if(!startable&&gOldstartable)
				menu->items[kMultiplayerStartGame].sel+=3.5;
			gOldstartable=startable;
		}
	}	
	else
	{
		int startable=true;		
//		int compatible=true;		
		for(int i=1;i<userData->gInfo->numNetPlayers;i++)
			if(userData->gInfo->playerVersions[i]!=userData->gInfo->version)
			{
				startable=false;	
//				if(userData->gInfo->playerVersions[i]==-1)
//					compatible=false;
			}
//		if(!compatible)
//			strcpy(menu->items[kMultiplayerStartGame].label,"Data Files don't match");
//		else
		if(startable&&!gOldstartable)
			menu->items[kMultiplayerStartGame].sel+=3.5;
	 	if(!startable)
			strcpy(menu->items[kMultiplayerStartGame].label,"Send Ready Notification");
		else
			strcpy(menu->items[kMultiplayerStartGame].label,"Start Game");
		gOldstartable=startable;
	}	
}


void FillAIPlayers(tMultiplayerMenuData *userData,tGameInfo *gInfo)
{
	int numCars=gInfo->numNetPlayers+gConfig->numEnemies;
	if(numCars>(gConfig->allowHugeGames?kMaxPlayers:6))numCars=(gConfig->allowHugeGames?kMaxPlayers:6);
	
	int availableCars[kMaxCars];
	int carCount;
	GetAvailableCars(availableCars,&carCount,false,false);

	for(int i=gInfo->numNetPlayers;i<numCars;i++)
	{
		if(userData->gInfo->network&kGameInfoNetworkForcePlayerCar)
		{
			tCarDefinition *car=(tCarDefinition*)FileGetParsedDataPtr(userData->playerCar,kParserTypeCarDesc,sizeof(tCarDefinition));
			userData->gInfo->playerCars[i]=userData->playerCar;
			userData->gInfo->playerColors[i]=(userData->playerColor+i)%car->numColors;
		}
		else
		{
			gInfo->playerCars[i]=gConfig->opponentCars[i-gInfo->numNetPlayers];
			gInfo->playerColors[i]=gConfig->opponentColors[i-gInfo->numNetPlayers];
			if(gInfo->playerCars[i]==-1||gInfo->playerCars[i]==0)
			{
				gInfo->playerCars[i]=availableCars[0];
				gInfo->playerColors[i]=0;
			}
		}
		gInfo->netID[i]=0;
		gInfo->ping[i]=0;
		gInfo->playerVersions[i]=0;
		strcpy(gInfo->playerNames[i],"AI Driver");
	}
	gInfo->numPlayers=numCars;
}


int NetworkHostCallback(tMultiplayerMenuData *userData)
{
	int showLonelyMessage;
	NetworkAdvertiseIdle(userData->gInfo,&showLonelyMessage,userData->locked,false);
	if(showLonelyMessage)
	{
		tChatMessage m;
		m.flags=kChatFlagSystem;
		sprintf(m.str,"### Players from the Internet may be unable to join you because of the way your router/network is configured.");
		SoundReInit();
		PlayInterfaceSound(FileGetReference("systemchat.wav"));
		ChatBufferInsert(&m,&userData->cb);
	}

	GameInfoSend(userData->gInfo);
	GatherPingStats(userData->gInfo);
	float time=TimeGetSeconds();
	if(time-userData->lastSendTime>1.0)
	{
		userData->lastSendTime=time;
		tNetGameInfo msg;
		CompressGInfoToPacket(&msg,userData->gInfo);
		NetworkSendPacket(kMessageTypeGameInfo,&msg,sizeof(tNetGameInfo),kMessagePriorityHigh,kMessageSendToAllButSelf);
	}
	
	WaitForPlayers(userData->gInfo,&userData->cb);
	userData->gInfo->playerCars[0]=userData->playerCar;
	tCarDefinition *car=(tCarDefinition*)FileGetParsedDataPtr(userData->playerCar,kParserTypeCarDesc,sizeof(tCarDefinition));
	if(userData->gInfo->network&kGameInfoNetworkForcePlayerCar)
		for(int i=1;i<userData->gInfo->numNetPlayers;i++)
		{
			userData->gInfo->playerCars[i]=userData->playerCar;
			userData->gInfo->playerColors[i]=(userData->playerColor+i)%car->numColors;
		}
	sprintf(userData->gInfo->playerNames[0],"%s%s",RT3_IsRegistered()?"":"\255demo.png\255 ",gConfig->playerName);
	userData->gInfo->playerColors[0]=userData->playerColor;
	if(gConfig->cantGoBackwards)
		userData->gInfo->network|=kGameInfoNetworkCantGoBackwards;
	else
		userData->gInfo->network&=~kGameInfoNetworkCantGoBackwards;

	FillAIPlayers(userData,userData->gInfo);
	
	MultiplayerMenuUpdate(userData,true);
	return -1;
}

int NetworkClientCallback(tMultiplayerMenuData *userData)
{
	int exit=false;
	tChatMessage chatReceive;
	if(WaitForStartSignal(userData,&exit))
		return exit;
/*		if(exit)
			return kMultiplayerExit;
		else
			return kMultiplayerStartGameSignal;*/


	if(userData->gInfo->inited)
		GameInfoReceive(userData->gInfo);

	MultiplayerMenuUpdate(userData,false);
	return -1;
}

#define kPingGreen 0.1
#define kPingYellow 0.2
#define kPingRed 0.35

void RenderChatBuffer(tChatBuffer *cb,float xleft,float xright,float y,float textsize,char *prompt)
{
	int chatBufferDrawLines[kChatBufferMaxLines];
	int chatBufferTotalDrawLines=0;

	for(int i=0;i<cb->chatBufferLines;i++)
	{
		char str[256];
		int offset=0;
		chatBufferDrawLines[i]=0;
		do{
			strcpy(str,cb->chatBuffer[i].str+offset);
			int word=true;
			while(TextWidth(str,textsize)>(xright-xleft)*kTextXPos)
			{
				word=str[strlen(str)-1]==' ';
				str[strlen(str)-1]='\0';
			}
			if(!word)
			{
				int i=strlen(str)-1;
				while(str[i]!=' '&&i>0)
					i--;
				if(i>0&&i>strlen(str)-15)
					str[i]='\0';
			}
			offset+=strlen(str);
			chatBufferDrawLines[i]++;
			chatBufferTotalDrawLines++;
		}while(offset<strlen(cb->chatBuffer[i].str));		
	}
	
	int drawline=0;
	if(chatBufferTotalDrawLines>kChatBufferMaxLines)drawline=kChatBufferMaxLines-chatBufferTotalDrawLines;
	float t=TimeGetSeconds();

	char str[1024];
	for(int i=0;i<cb->chatBufferLines;i++)
	{
		int offset=0;
		chatBufferDrawLines[i]=0;
		do{
			strcpy(str,cb->chatBuffer[i].str+offset);
			int word=true;
			while(TextWidth(str,textsize)>(xright-xleft)*kTextXPos)
			{
				word=str[strlen(str)-1]==' ';
				str[strlen(str)-1]='\0';
			}
			if(!word)
			{
				int i=strlen(str)-1;
				while(str[i]!=' '&&i>0)
					i--;
				if(i>0&&i>strlen(str)-15)
					str[i]='\0';
			}
			float r=cb->chatBuffer[i].flags&kChatFlagSystem?0:1;
			float g=cb->chatBuffer[i].flags&kChatFlagAlert?0:1;
			float b=cb->chatBuffer[i].flags&(kChatFlagSystem+kChatFlagAlert)?0:1;
			if(drawline>=0)
				if(offset&&!(cb->chatBuffer[i].flags&(kChatFlagAlert|kChatFlagSystem)))
					TextPrintfToBufferFormatedColored(Vector(xleft,y-drawline*0.08),textsize,kTextAlignLeft,r,g,b,(t-cb->chatBuffer[i].recvTime)*4,"\255#a\255%s",str);	
				else
					TextPrintfToBufferFormatedColored(Vector(xleft,y-drawline*0.08),textsize,kTextAlignLeft,r,g,b,(t-cb->chatBuffer[i].recvTime)*4,str);	
			offset+=strlen(str);
			drawline++;		
		}while(offset<strlen(cb->chatBuffer[i].str));		
	}
	do{
		sprintf(str,"> \255#a\255%s",prompt++);
	}while(TextWidth(str,textsize)>(xright-xleft)*kTextXPos&&*prompt!='\0');
	if((int)(TimeGetSeconds()*4)%2)
		sprintf(str,"%s_",str);
	TextPrintfToBufferFormated(Vector(xleft,y-kChatBufferMaxLines*0.08),textsize,kTextAlignLeft,str);	
}

typedef struct{
	char name[256],sub[256];
	float r,g,b,a,sr,sg,sb,sa;
} tPlayerListRenderEntry;

void RenderPlayerList(char *title,int numEntries,tPlayerListRenderEntry *entries)
{
	TextPrintfToBufferFormated(Vector(0.3,0.65),0.028,kTextAlignLeft,title);
	static float shift=0;
	static float lt=0;
	static float blockt=0;
	if(numEntries>6)
	{
		float t=TimeGetSeconds();
		if(!gSystemSuspended&&t>blockt+1.5)
		{
			float shiftSize=numEntries+1;
			shift+=t-lt;
			while(shift>shiftSize)
				shift-=shiftSize;
		}
		lt=t;
		if(gPlayerListShowEntry!=-1)
		{
			shift=gPlayerListShowEntry;
			gPlayerListShowEntry=-1;
			blockt=t;
		}
	}
	else
		shift=0;

	for(int j=0;j<(numEntries>6?numEntries+8:numEntries);j++)
	{
		float sh=j-shift;
		float offs=0.55-sh*0.09;
		if(sh+0.5>=0&&sh-0.5<=6)
		{
			float a=1;
			if(numEntries>6)
			{
				if(sh<0.5)a=sh+0.5;
				if(sh>5.5)a=6.5-sh;
			}
			if(j%(numEntries+1)!=numEntries)
			{
				TextPrintfToBufferFormatedColored(Vector(0.3,offs),0.028,kTextAlignLeft+kTextAutoCut,entries[j%(numEntries+1)].r,entries[j%(numEntries+1)].g,entries[j%(numEntries+1)].b,entries[j%(numEntries+1)].a*a,entries[j%(numEntries+1)].name);
				TextPrintfToBufferFormatedColored(Vector(0.9,offs-0.05),0.02,kTextAlignRight,entries[j%(numEntries+1)].sr,entries[j%(numEntries+1)].sg,entries[j%(numEntries+1)].sb,entries[j%(numEntries+1)].a*a,entries[j%(numEntries+1)].sub);
			}
		}
	}
}


void StripText(char *txt,float size,float l)
{
	int len=strlen(txt);
	while(TextWidth(txt,size)>l)
		txt[--len]='\0';
}

void NetworkRenderCallback(tMultiplayerMenuData *userData,int selection,tInterfaceMenuDescribtion *menu)
{
	InterfaceRenderReplay(NULL,selection,menu);
	
	//print list of players
	if(userData->gInfo->inited)
	{
		tPlayerListRenderEntry pl[kMaxPlayers];
		for(int i=0;i<userData->gInfo->numPlayers;i++)
		{
			if(userData->gInfo->playerCars[i]!=kFileErr)
			{
				pl[i].a=userData->gInfo->playerVersions[i]==userData->gInfo->version?1:0.5;
				if(i>=userData->gInfo->numNetPlayers||i==0)pl[i].a=1;
				if(i==userData->playerID)
					pl[i].r=pl[i].b=0;
				tCarDefinition *car=(tCarDefinition*)FileGetParsedDataPtr(userData->gInfo->playerCars[i],kParserTypeCarDesc,sizeof(tCarDefinition));
				int col=userData->gInfo->playerColors[i];
				if(col>=car->numColors)
					pl[i].r=pl[i].g=pl[i].b=1;
				else
				{
					pl[i].r=car->colors[col].x;
					pl[i].g=car->colors[col].y;
					pl[i].b=car->colors[col].z;
				}
				sprintf(pl[i].name,"\255#w\255%s: \255#n\255%s",userData->gInfo->playerNames[i],car->carName);
			}
			else{
				pl[i].r=1;pl[i].g=pl[i].b=0;
				pl[i].a=0.5;
				if(!userData->gInfo->playerInited[i])
					sprintf(pl[i].name,"%s: Joining",userData->gInfo->playerNames[i]);			
				else
					sprintf(pl[i].name,"%s: Data File Mismatch",userData->gInfo->playerNames[i]);			
			}
			if(i<userData->gInfo->numNetPlayers&&userData->gInfo->ping[i])
			{
				if(userData->gInfo->ping[i]<kPingGreen) {pl[i].sr=0;pl[i].sg=1;pl[i].sb=0;}
				else if(userData->gInfo->ping[i]<kPingYellow) {pl[i].sr=(userData->gInfo->ping[i]-kPingGreen)/(kPingYellow-kPingGreen);pl[i].sg=1;pl[i].sb=0;}
				else if(userData->gInfo->ping[i]<kPingRed) {pl[i].sr=1;pl[i].sg=1-(userData->gInfo->ping[i]-kPingYellow)/(kPingRed-kPingYellow);pl[i].sb=0;}
				else {pl[i].sr=1;pl[i].sg=0;pl[i].sb=0;}
				sprintf(pl[i].sub,"Ping: %3.0fms",userData->gInfo->ping[i]*1000.0);
			}
			else
				strcpy(pl[i].sub,"");
		}

		TextPrintfToBufferFormated(Vector(0.3,0.75),0.028,kTextAlignLeft,"Players waiting in Lobby: \255#a\255%d",NetworkCountPlayers());			
		char title[256];
		if(userData->gInfo->numPlayers-userData->gInfo->numNetPlayers)
			sprintf(title,"Players in game: \255#a\255%d \255#n\255AIs:\255#a\255%d",userData->gInfo->numNetPlayers,userData->gInfo->numPlayers-userData->gInfo->numNetPlayers);
		else
			sprintf(title,"Players in game: \255#a\255%d",userData->gInfo->numNetPlayers);
		RenderPlayerList(title,userData->gInfo->numPlayers,pl);
					
		if(userData->gInfo->map!=kFileErr)
		{
			tMapInfo *mapInfo=(tMapInfo*)FileGetParsedDataPtr(userData->gInfo->map,kParserTypeMapInfoDesc,sizeof(tMapInfo));
			if(mapInfo->image)
				menu->image=mapInfo->image;
			else
				menu->image=kFileErr;
			menu->imageXScale=0.12;
			menu->imageYScale=0.06;	
			menu->imageXPos=0.01;
			menu->imageYPos=0.1;
			char str[1024];
			sprintf(str,"Track: \255#a\255%s",mapInfo->name);
			StripText(str,0.028,0.26);
			TextPrintfToBufferFormated(Vector(-0.22,0.65),0.028,kTextAlignLeft+kTextAutoCut,str);
		}
		else
		{
			menu->image=kFileErr;
			TextPrintfToBufferFormated(Vector(-0.22,0.65),0.028,kTextAlignLeft+kTextAutoCut,"Track: \255#r\255File not available");			
		}
		if(userData->gInfo->environment!=kFileErr)
		{
			tEnvironment *env=(tEnvironment*)FileGetParsedDataPtr(userData->gInfo->environment,kParserTypeEnvironmentDesc,sizeof(tEnvironment));
			if(userData->gInfo->map!=kFileErr)
			{
				tMapInfo *mapInfo=(tMapInfo*)FileGetParsedDataPtr(userData->gInfo->map,kParserTypeMapInfoDesc,sizeof(tMapInfo));
				if(mapInfo->useAltEnv&&env->hasAltEnv)
					env=(tEnvironment*)FileGetParsedDataPtr(env->altEnv,kParserTypeEnvironmentDesc,sizeof(tEnvironment));
			}
			TextPrintfToBufferFormated(Vector(-0.22,0.0),0.028,kTextAlignLeft+kTextAutoWrap,"Laps: \255#a\255%d  \255#n\255Weather: \255#a\255%s  \255#n\255Direction: \255#a\255%s",userData->gInfo->numLaps,env->name,userData->gInfo->reverse?"Reverse":"Normal");
		}
		else
			TextPrintfToBufferFormated(Vector(-0.22,0.0),0.028,kTextAlignLeft+kTextAutoWrap,"Laps: \255#a\255%d  \255#n\255Weather: \255#r\255File not available  \255#n\255Direction: \255#a\255%s",userData->gInfo->numLaps,userData->gInfo->reverse?"Reverse":"Normal");
		

		switch(userData->gInfo->arcade)
		{
			case 0:
				TextPrintfToBufferFormated(Vector(-0.22,0.57),0.028,kTextAlignLeft,"Game Mode: \255#a\255Simulation");
				break;
			case 1:
				TextPrintfToBufferFormated(Vector(-0.22,0.57),0.028,kTextAlignLeft,"Game Mode: \255#a\255Arcade");
				break;
			case 2:
				TextPrintfToBufferFormated(Vector(-0.22,0.57),0.028,kTextAlignLeft,"Game Mode: \255#a\255Turbo");
				break;
			case 3:
				TextPrintfToBufferFormated(Vector(-0.22,0.57),0.028,kTextAlignLeft,"Game Mode: \255#a\255Strict");
				break;
		}


	}
	else
		TextPrintfToBufferFormated(Vector(-0.22,0.57),0.028,kTextAlignLeft,"Waiting for Data...");	

	RenderChatBuffer(&userData->cb,-0.22,0.9,-0.22,0.028,menu->type);
}

enum{
//	kHostControlAICars,
	kHostControlForcePlayerCar,
	kHostControlKickPlayer,
	kHostControlTrackerName,
	kHostControlPassword,
	kHostControlOnlyRegistered,
	kHostControlCantGoBackwards,
	kHostControlLockGame,
	kHostControlReturn,
	kHostControlNumOptions
};


void MultiplayerKickPlayer(tMultiplayerMenuData *userData)
{
	if(userData->gInfo->numNetPlayers<2)
		return;
		
	tInterfaceMenuDescribtion menu;
	InterfaceInitMenu(&menu,userData->gInfo->numNetPlayers,"Select a player to kick:");

	menu.TimerCallback=(int(*)(void*))NetworkHostCallback;
	menu.userData=userData;
	
	for(int i=1;i<userData->gInfo->numNetPlayers;i++)
		strcpy(menu.items[i-1].label,userData->gInfo->playerNames[i]);
	
	strcpy(menu.items[userData->gInfo->numNetPlayers-1].label,"Cancel");
	menu.items[userData->gInfo->numNetPlayers-2].lineSpacing*=1.5;
	
	int sel=InterfaceGetUserMenuSelection(&menu);
	InterfaceDisposeMenu(&menu);

	if(sel!=kInterfaceMenuEsc&&sel!=userData->gInfo->numNetPlayers-1)
	{
		NetworkDisconnectPlayer(userData->gInfo->netID[sel+1],kNetworkDisconnectBan);
		tChatMessage m;
		memset((void*)&m,0,sizeof(tChatMessage));
		m.flags=kChatFlagSystem;
		sprintf(m.str,"### %s was kicked by host.",userData->gInfo->playerNames[sel+1]);
		NetworkSendPacket(kMessageTypeChat,&m,sizeof(tChatMessage),kMessagePriorityHigh,kMessageSendToAll);					
	}
}

void HostControl(tMultiplayerMenuData *userData)
{
	tInterfaceMenuDescribtion menu;
	InterfaceInitMenu(&menu,kHostControlNumOptions,"Host Controls");
	char itemStrings[][32]={"Force host car: ","Kick Player...","Game Name: ","Password: ","Registered players only: ","Allow going backwards:","Lock Game:","Return to chat"};
	for(int i=0;i<menu.numItems;i++)
		strcpy(menu.items[i].label,itemStrings[i]);

	char itemDesc[][256]={
		"This forces all players to use the same car as yours.",
		"You can use this to remove unwanted players from the game.",
		"Name for this game that other players will see.",
		"Password required to join the game. Leave empty to allow everyone to join.",
		"Turn on this option to allow only registered players in the game.",
		"You can use this to force players to go into the right direction of the track - otherwise they will be reset.",
		"Allows you to lock the game, so no new players can join.",
		"Return to chat screen.",
		};
	
	for(int i=0;i<menu.numItems;i++)
		strcpy(menu.items[i].describtion,itemDesc[i]);
		
	menu.items[kHostControlReturn-1].lineSpacing*=1.5;
	menu.items[kHostControlOnlyRegistered].flags|=kInterfaceMenuItemArrowInput;
	if(!RT3_IsRegistered())
		menu.items[kHostControlOnlyRegistered].flags|=kInterfaceMenuItemDisabled;
				
	menu.items[kHostControlLockGame].flags|=kInterfaceMenuItemArrowInput;
	menu.items[kHostControlForcePlayerCar].flags|=kInterfaceMenuItemArrowInput;
	menu.items[kHostControlCantGoBackwards].flags|=kInterfaceMenuItemArrowInput;
	menu.items[kHostControlTrackerName].flags|=kInterfaceMenuItemTypeable;
	menu.items[kHostControlPassword].flags|=kInterfaceMenuItemTypeable|kInterfaceMenuItemTypeHidden;
	if(userData->gInfo->numNetPlayers<2)
		menu.items[kHostControlKickPlayer].flags|=kInterfaceMenuItemDisabled;
	
	strcpy(menu.items[kHostControlTrackerName].type,gConfig->gameName);
	strcpy(menu.items[kHostControlPassword].type,gConfig->password);
	menu.TimerCallback=(int(*)(void*))(NetworkHostCallback);
	menu.RenderCallback=InterfaceRenderReplay;
	menu.userData=userData;
	sprintf(menu.items[kHostControlLockGame].label,"Lock Game: \255#a\255%s",userData->locked?"Yes":"No");
	sprintf(menu.items[kHostControlOnlyRegistered].label,"Registered players only: \255#a\255%s",gConfig->onlyRegisteredPlayers?"Yes":"No");
	sprintf(menu.items[kHostControlForcePlayerCar].label,"Force host car: \255#a\255%s",userData->gInfo->network&kGameInfoNetworkForcePlayerCar?"Yes":"No");
	sprintf(menu.items[kHostControlCantGoBackwards].label,"Allow going backwards: \255#a\255%s",gConfig->cantGoBackwards?"No":"Yes");
	
	int exit=false;
	InterfaceMenuZoomAnimation(&menu,0,true);
	do{
		sprintf(menu.items[kHostControlLockGame].label,"Lock Game: \255#a\255%s",userData->locked?"Yes":"No");
		sprintf(menu.items[kHostControlOnlyRegistered].label,"Registered players only: \255#a\255%s",gConfig->onlyRegisteredPlayers?"Yes":"No");
		sprintf(menu.items[kHostControlCantGoBackwards].label,"Allow going backwards: \255#a\255%s",gConfig->cantGoBackwards?"No":"Yes");
		sprintf(menu.items[kHostControlForcePlayerCar].label,"Force host car: \255#a\255%s",userData->gInfo->network&kGameInfoNetworkForcePlayerCar?"Yes":"No");
		int sel=InterfaceGetUserMenuSelection(&menu);
		switch(menu.initialSelection=(sel&kInterfaceMenuItemMask))
		{
			case kHostControlKickPlayer:
				MultiplayerKickPlayer(userData);
				exit=true;
				break;
			case kHostControlLockGame:
				userData->locked=!userData->locked;
				break;				
			case kHostControlOnlyRegistered:
				gConfig->onlyRegisteredPlayers=!gConfig->onlyRegisteredPlayers;
				break;				
			case kHostControlCantGoBackwards:
				gConfig->cantGoBackwards=!gConfig->cantGoBackwards;
				break;				
			case kHostControlForcePlayerCar:
				userData->gInfo->network^=kGameInfoNetworkForcePlayerCar;
				userData->gInfo->version++;
				userData->gInfo->playerVersions[0]=userData->gInfo->version;
				break;				
			case kHostControlReturn:
			case kInterfaceMenuEsc:
				exit=true;
				break;
		}
	}while(!exit);
	strcpy(gConfig->gameName,menu.items[kHostControlTrackerName].type);
	strcpy(gConfig->password,menu.items[kHostControlPassword].type);
	NetworkChangePassword(gConfig->password);
	NetworkAdvertiseIdle(userData->gInfo,NULL,userData->locked,true);
	if(userData->locked)
		NetworkLockOutNewPlayers();
	else
		NetworkUnlockOutNewPlayers();
	//InterfaceMenuZoomAnimation(&menu,menu.initialSelection,false);
	InterfaceDisposeMenu(&menu);	
}

void InitMultiPlayerMenu(tInterfaceMenuDescribtion *menu,int host,tMultiplayerMenuData *userData)
{
	InterfaceInitMenu(menu,kNumMultiplayerOptions,"Multiplayer");

	char itemStrings[][32]={"Host Controls...","Select AI Cars...","Set Game Mode","Select Track","Select Car","Start Game","Exit Game","Chat Prompt: "};
	for(int i=0;i<menu->numItems;i++)
	{
		strcpy(menu->items[i].label,itemStrings[i]);
		menu->items[i].size=0.03;//*=i<kMultiplayerStartGame?0.75:0.85;
		menu->items[i].lineSpacing=0.1;//*=i<kMultiplayerStartGame?0.75:0.85;
	}
	for(int i=kMultiplayerStartGame;i<menu->numItems;i++){
		menu->items[i].flags|=kInterfaceMenuItemFixedPos;
		menu->items[i].fixedX=-0.9;
		menu->items[i].fixedY=-0.74-(i-kMultiplayerStartGame)*0.1;
	}

	menu->items[kMultiplayerStartGame-1].lineSpacing*=1.5;
	if(!host)
	{
		menu->items[kMultiplayerSelectMap].flags|=kInterfaceMenuItemDisabled;
		menu->items[kMultiplayerHostControls].flags|=kInterfaceMenuItemDisabled;
		menu->items[kMultiplayerAICars].flags|=kInterfaceMenuItemDisabled;
		menu->items[kMultiplayerGameMode].flags|=kInterfaceMenuItemDisabled;
//		menu->items[kMultiplayerMiniGame].flags|=kInterfaceMenuItemDisabled;
		menu->items[kMultiplayerStartGame].flags|=kInterfaceMenuItemArrowInput;
	}
	menu->items[kMultiplayerSelectCar].flags|=kInterfaceMenuItemArrowInput;
	menu->items[kMultiplayerSelectMap].flags|=kInterfaceMenuItemArrowInput;
	menu->items[kMultiplayerGameMode].flags|=kInterfaceMenuItemArrowInput;
//	menu->items[kMultiplayerChat].flags|=kInterfaceMenuItemTypeable;
//	menu->items[kMultiplayerChat].maxTypeXPos=0.35;
	
	menu->background=FileGetReference("background-multiplayer2.tif");
	menu->joinDisable=true;
	menu->itemsYPos+=0.1;
	//menu->itemsXPos=-0.87;
	
	menu->menuTypeable=true;
	menu->imageXScale=0.12;
	menu->imageYScale=0.06;	
	menu->imageXPos+=0.01;
	menu->imageYPos+=0.1;
	
	menu->initialSelection=kMultiplayerStartGame;

	menu->TimerCallback=(int(*)(void*))(host?NetworkHostCallback:NetworkClientCallback);
	menu->RenderCallback=(void(*)(void*,int,void*))NetworkRenderCallback;
	menu->userData=userData;
	
	gOldstartable=host;
}

void RunMiniGame(tGameInfo *gInfo)
{
	tGameInfo miniGInfo;
	InitGInfo(&miniGInfo);
	miniGInfo.map=FileGetReference("pit.mapinfo");
	miniGInfo.environment=FileGetReference("sunset.env");
	miniGInfo.demolition=true;
	miniGInfo.numPlayers=gInfo->numNetPlayers;
	miniGInfo.numNetPlayers=gInfo->numNetPlayers;
	miniGInfo.playerID=0;
	for(int i=0;i<gInfo->numNetPlayers;i++)
	{
		miniGInfo.playerCars[i]=FileGetReference("oldford.car");
		strcpy(miniGInfo.playerNames[i],gInfo->playerNames[i]);
	}
	miniGInfo.network=true;

	tNetGameInfo msg;
	CompressGInfoToPacket(&msg,&miniGInfo);
	NetworkSendPacket(kMessageTypeGameInfo,&msg,sizeof(tNetGameInfo),kMessagePriorityHigh,kMessageSendToAllButSelf);

	RunGame(&miniGInfo);	

	for(int i=1;i<miniGInfo.numPlayers;i++)
		if(miniGInfo.netID[i]==-1)
			gInfo->netID[i]=-1;

	FixGInfoForLeftPlayers(gInfo);

	CompressGInfoToPacket(&msg,gInfo);
	NetworkSendPacket(kMessageTypeGameInfo,&msg,sizeof(tNetGameInfo),kMessagePriorityHigh,kMessageSendToAllButSelf);
}

int SelectRaceMode(tFileRef map,int (*TimerCallback)(void*),void *userData);

int HandleMultiplayerMenuItem(tInterfaceMenuDescribtion *menu,tMultiplayerMenuData *userData,int item,int host)
{
	int callbackResponse=-1;		
	if(*menu->type&&!(item&(kInterfaceMenuLeftArrow|kInterfaceMenuRightArrow))&&(item!=kMultiplayerStartGameSignal)&&(item!=kMultiplayerForcedExit))
	{
		tChatMessage m;
		memset((void*)&m,0,sizeof(tChatMessage));
		m.flags=0;
		sprintf(m.str,"%s:\255#a\255 %s",gConfig->playerName,menu->type);
		NetworkSendPacket(kMessageTypeChat,&m,sizeof(tChatMessage),kMessagePriorityHigh,kMessageSendToAll);					
		*menu->type='\0';
	}
	else switch(item&kInterfaceMenuItemMask)
	{
		case kMultiplayerGameMode:
			if(item&kInterfaceMenuLeftArrow){
				userData->gInfo->arcade--;
				if(userData->gInfo->arcade<0)
					userData->gInfo->arcade=kGameModeStrict;//(item&kInterfaceMenuEasterEgg?2:1);
			}
			else if(item&kInterfaceMenuRightArrow)
			{
				userData->gInfo->arcade=(userData->gInfo->arcade+1);
				if(userData->gInfo->arcade>kGameModeStrict)//(item&kInterfaceMenuEasterEgg?2:1))
					userData->gInfo->arcade=kGameModeSim;
			}
			else
			{
				int arcade=SelectRaceMode(userData->gInfo->map,(int(*)(void*))menu->TimerCallback,userData);
				if(arcade==-1)
					break;
				userData->gInfo->arcade=arcade;
			}
			userData->gInfo->version++;
			userData->gInfo->playerVersions[0]=userData->gInfo->version;
			break;

		case kMultiplayerSelectMap:
			{
				if(item&kInterfaceMenuLeftArrow)
					SelectPrevMap(&userData->gInfo->map);
				else if(item&kInterfaceMenuRightArrow)
					SelectNextMap(&userData->gInfo->map);
				else
					if(!InterfaceMapSelection(userData->gInfo,(int(*)(void*))menu->TimerCallback,userData,false))
						break;
				userData->gInfo->version++;
				userData->gInfo->playerVersions[0]=userData->gInfo->version;
			}
			break;
			
		case kMultiplayerSelectCar:
			{
				tFileRef oldCar=userData->playerCar;
				if((userData->gInfo->network&kGameInfoNetworkForcePlayerCar&&userData->playerID>0)||userData->playerID==-1)
					break;
				gPlayerListShowEntry=userData->playerID;
				if(item&kInterfaceMenuLeftArrow)
					SelectPrevCar(&userData->playerCar,&userData->playerColor,true,true);
				else if(item&kInterfaceMenuRightArrow)
					SelectNextCar(&userData->playerCar,&userData->playerColor,true,true);
				else{
					InterfaceCarSelection(&userData->playerCar,kCarSelectionQuickMode,&userData->playerColor,(int(*)(void*))menu->TimerCallback,userData,&callbackResponse);
					if(callbackResponse!=-1)
						return HandleMultiplayerMenuItem(menu,userData,callbackResponse,host);
				}
				if(userData->playerID==0&&oldCar!=userData->playerCar)
				{
					userData->gInfo->version++;
					userData->gInfo->playerVersions[0]=userData->gInfo->version;
				}
				else if(userData->gInfo->playerCars[userData->playerID]!=userData->playerCar||userData->gInfo->playerColors[userData->playerID]!=userData->playerColor)
					UpdatePlayerCar(userData);
					
				gConfig->lastCar=userData->playerCar;
				gConfig->lastColor=userData->playerColor;
			}
			break;
			
		case kMultiplayerHostControls:
			HostControl(userData);
			break;
		
		case kMultiplayerAICars:
			tFileRef opponents[11];
			UInt8 colors[11];
			int numOpponents;
			InterfaceSelectOpponentCars(&numOpponents,opponents,colors,(int(*)(void*))(NetworkHostCallback),userData);
			break;

		case kMultiplayerStartGame:
			if(host)
			{
				int startable=1;
				for(int i=0;i<userData->gInfo->numNetPlayers;i++)
					if(userData->gInfo->playerVersions[i]!=userData->gInfo->version)
					{
						tChatMessage m;
						memset((void*)&m,0,sizeof(tChatMessage));
						m.flags=kChatFlagAlert;
						strcpy(m.str,"### Please Select Ready, Host wants to start game!!!");
						NetworkSendPacket(kMessageTypeChat,&m,sizeof(m),kMessagePriorityHigh,userData->gInfo->netID[i]);					
						startable=0;
					}
				return startable;
			}
			else if(userData->gInfo->inited&&userData->playerID!=-1)
			{
				//if(userData->gInfo->playerVersions[userData->playerID]!=userData->gInfo->version)
				if(!gReady)
					CheckGInfoVersion(userData,userData->playerID);
				else
				{
					int reply[2];
					reply[0]=userData->gInfo->version;
					reply[1]=userData->playerID;
					S32Swap(reply[0]);
					S32Swap(reply[1]);
					NetworkSendPacket(kMessageTypeVersionDenied,reply,sizeof(int)*2,kMessagePriorityHigh,kMessageSendToHostOnly);
					gConfirmedVersion=-1;
					gReady=false;
					gOldstartable=false;
				}			
			}
			break;

		/*case kMultiplayerMiniGame:
			if(host)
				RunMiniGame(userData->gInfo);
			break;*/
			
		case kMultiplayerExit:
		case kInterfaceMenuEsc:
			NetworkSendPacket(kMessageTypeBye,NULL,0,kMessagePriorityHigh,kMessageSendToAll);					
		case kMultiplayerForcedExit:
			return -1;
		case kMultiplayerStartGameSignal:
			return 1;
	}
	return 0;
}

void MultiplayerMenu(int host,tMultiplayerMenuData *userData)
{
	tInterfaceMenuDescribtion menu;
	userData->menu=&menu;
	InitMultiPlayerMenu(&menu,host,userData);
	int exit=false;
	while(!exit)
	{
		int start=false;
		int zoom=true;
		do{
			if(zoom)
				InterfaceMenuZoomAnimation(&menu,-1,true);
			
			int item=InterfaceGetUserMenuSelection(&menu);
			int response=HandleMultiplayerMenuItem(&menu,userData,item,host);
			menu.initialSelection=(item&kInterfaceMenuItemMask);
		//	if(/*menu.initialSelection!=kMultiplayerChat||*/menu.initialSelection!=kMultiplayerStartGame||item&(kInterfaceMenuLeftArrow|kInterfaceMenuRightArrow))
				zoom=false;
		//	else 
		//		zoom=true;
			
			if(response<0)
				exit=true;
			else if(response>0)start=true;
		}while(!exit&&!start);
		if(start)
		{
			if(host)
				StartNetGame(userData->gInfo);
			else{
				GameInfoReceive(userData->gInfo);
				userData->gInfo->playerID=userData->playerID;
			}
			NetworkLockOutNewPlayers();
			if(host)
				NetworkAdvertiseIdle(userData->gInfo,NULL,true,true);

			SystemNotify();
			RunGame(userData->gInfo);
			gReady=false;
			strcpy(menu.type,gGameChatMessage);
			
			NetworkUnlockOutNewPlayers();
			if(host)
				FixGInfoForLeftPlayers(userData->gInfo);
		}
	}
	InterfaceDisposeMenu(&menu);
	gStartIdleTime=TimeGetSeconds();
}

//hosts a network game
void InterfaceHostNetworkGame()
{
	NetworkClearPacketQueue();
	
	LocalTrackerStartAdvertising(gConfig->gameName);

	tGameInfo gInfo;
	HostNetGame(gConfig->maxPlayers,gConfig->password);	

	InitGInfo(&gInfo);

	if(gConfig->lastCar==-1||gConfig->lastCar==0)
	{
		UInt8 color;
		SelectNextCar(&gConfig->lastCar,&color,true,true);
		gConfig->lastColor=color;
	}
	gInfo.network=true;
	gInfo.playerCars[0]=gConfig->lastCar;
	gInfo.playerColors[0]=gConfig->lastColor;
	gInfo.map=gConfig->lastRoad;
	if(gInfo.map==kFileErr)
		SelectNextMap(&gInfo.map);
	gInfo.numLaps=gConfig->lastLaps;
	gInfo.environment=gConfig->lastEnv;
	gInfo.inited=true;
	gInfo.arcade=gConfig->arcade;
	gLicenses[0]=RT3_GetLicenseCode();
	gLicenseCopies[0]=RT3_GetLicenseCopies();
	
	tMultiplayerMenuData userData;
	userData.gInfo=&gInfo;
	userData.lastSendTime=0;
	userData.playerCar=gConfig->lastCar;
	userData.playerID=0;
	userData.locked=false;
	userData.t=INFINITY;
	userData.playerColor=gConfig->lastColor;
	userData.cb.chatBufferLines=0;
	gChatBuffer=&userData.cb;
	
	MultiplayerMenu(true,&userData);

	NetworkStopAdvertising();
	LocalTrackerStopAdvertising();
	ExitNetGame();
}


enum{
	kHostOptionsTracker,
	kHostOptionsTrackerName,
	kHostOptionsMaxPlayers,
	kHostOptionsPassword,
	kHostOptionsOnlyRegistered,
	kHostOptionsPlayerName,
	kHostOptionsStart,
	kHostOptionsCancel,
	kNumHostOptions
};

void InterfaceSetupHost()
{
	tInterfaceMenuDescribtion menu;
	InterfaceInitMenu(&menu,kNumHostOptions,"Host Game");
	
	menu.RenderCallback=InterfaceRenderReplay;
	strcpy(menu.items[kHostOptionsCancel].label,"Return to previous menu");
	strcpy(menu.items[kHostOptionsStart].label,"Start Hosting");
	strcpy(menu.items[kHostOptionsTrackerName].label,"Game Name: ");
	strcpy(menu.items[kHostOptionsPassword].label,"Game Password: ");
	strcpy(menu.items[kHostOptionsOnlyRegistered].label,"Registered players only: ");
	strcpy(menu.items[kHostOptionsPlayerName].label,"Local Player Name: ");

	char itemDesc[][256]={
		"Advertises the game to other players on the internet tracker.",
		"Name for this game that other players will see.",
		"Maximum number of players who can join the game.",
		"Password required to join the game. Leave empty to allow everyone to join.",
		"Turn on this option to allow only registered players in the game.",
		"Your player name.",
		"Start hosting the game.",
		"Return to multiplayer selection menu.",
		};
	for(int i=0;i<menu.numItems;i++)
		strcpy(menu.items[i].describtion,itemDesc[i]);

	for(int i=0;i<kHostOptionsStart;i++)
	{
		menu.items[i].size*=0.75;
		menu.items[i].lineSpacing*=0.75;
	}
	menu.items[kHostOptionsStart-1].lineSpacing*=1.5;
	
	menu.items[kHostOptionsTracker].flags|=kInterfaceMenuItemArrowInput;
	menu.items[kHostOptionsTrackerName].flags|=kInterfaceMenuItemTypeable;
	menu.items[kHostOptionsMaxPlayers].flags|=kInterfaceMenuItemArrowInput;
	strcpy(menu.items[kHostOptionsTrackerName].type,gConfig->gameName);
	menu.items[kHostOptionsPassword].flags|=kInterfaceMenuItemTypeable;
	menu.items[kHostOptionsPassword].flags|=kInterfaceMenuItemTypeable|kInterfaceMenuItemTypeHidden;
	strcpy(menu.items[kHostOptionsPassword].type,gConfig->password);
	menu.items[kHostOptionsPlayerName].flags|=kInterfaceMenuItemTypeable;
	menu.items[kHostOptionsPlayerName].maxTypeLength=kMaxNameLength-1;
	strcpy(menu.items[kHostOptionsPlayerName].type,gConfig->playerName);
	menu.items[kHostOptionsOnlyRegistered].flags|=kInterfaceMenuItemArrowInput;
	if(!RT3_IsRegistered())
		menu.items[kHostOptionsOnlyRegistered].flags|=kInterfaceMenuItemDisabled;

	int exit=false;
	int zoom=false;
	menu.initialSelection=kHostOptionsStart;
	do{
		sprintf(menu.items[kHostOptionsTracker].label,"List Game on Tracker: \255#a\255%s",gConfig->trackerEnable?"Yes":"No");
		sprintf(menu.items[kHostOptionsMaxPlayers].label,"Max Number of Players: \255#a\255%d",gConfig->maxPlayers);
		sprintf(menu.items[kHostOptionsOnlyRegistered].label,"Registered players only: \255#a\255%s",gConfig->onlyRegisteredPlayers?"Yes":"No");
		if(!zoom){
			InterfaceMenuZoomAnimation(&menu,-1,true);
			zoom=true;
		}
		
		int sel=InterfaceGetUserMenuSelection(&menu);
		switch(menu.initialSelection=(sel&kInterfaceMenuItemMask))
		{
			case kHostOptionsTracker:
				gConfig->trackerEnable=!gConfig->trackerEnable;
				break;
			
			case kHostOptionsMaxPlayers:
				if(sel&kInterfaceMenuLeftArrow){
					if(gConfig->maxPlayers>2)
						gConfig->maxPlayers--;
				}
				else
					if(gConfig->maxPlayers<(gConfig->allowHugeGames?kMaxPlayers:6))
						gConfig->maxPlayers++;
				break;
								
			case kHostOptionsOnlyRegistered:
				gConfig->onlyRegisteredPlayers=!gConfig->onlyRegisteredPlayers;
				break;				

			case kHostOptionsPassword:
			case kHostOptionsTrackerName:
			case kHostOptionsPlayerName:
			case kHostOptionsStart:
			case kInterfaceMenuOK:
				strcpy(gConfig->gameName,menu.items[kHostOptionsTrackerName].type);
				strcpy(gConfig->password,menu.items[kHostOptionsPassword].type);
				strcpy(gConfig->playerName,menu.items[kHostOptionsPlayerName].type);
				InterfaceHostNetworkGame();
			case kHostOptionsCancel:
			case kInterfaceMenuEsc:
				exit=true;
				break;
		}
	}while(!exit);
	WriteOutFile(FileGetReference(kConfigFileName),gConfig,kParserTypeConfigDesc);
	InterfaceDisposeMenu(&menu);
}

//join a network game
void InterfaceJoinNetworkGame(char *address,char *alias)
{
	int err;
	char errorString[256];

	InterfaceDrawStrings("Connecting to Network Game...",address,kFileErr);
	if(gConfig->lastCar==-1||gConfig->lastCar==0)
	{
		UInt8 color;
		SelectNextCar(&gConfig->lastCar,&color,true,true);
		gConfig->lastColor=color;
	}

	//try to join a game at the given address
	if(!JoinNetGame(address,errorString))
		if(*alias)
		{
			if(!JoinNetGame(alias,errorString))
			{
				if(*errorString)
					InterfaceDisplayMessage(-1,"Unable To Connect.",errorString);
				return;
			}
		}
		else
		{
			if(*errorString)
				InterfaceDisplayMessage(-1,"Unable To Connect.",errorString);
			return;
		}

	NetworkSearchGamesStop();
	
	tGameInfo gInfo;
	tMultiplayerMenuData userData;
	gInfo.inited=false;
	userData.gInfo=&gInfo;
	userData.lastSendTime=0;
	userData.playerColor=gConfig->lastColor;
	userData.playerCar=gConfig->lastCar;
	userData.playerID=-1;
	userData.cb.chatBufferLines=0;
	gChatBuffer=&userData.cb;
	gConfirmedVersion=0;
	gReady=false;
		
	MultiplayerMenu(false,&userData);
	NetworkCountPlayersStop();
}

int gLastLocalGameListUpdate=0;

#define kMaxGames 256
#define kMaxLobbyPlayers 256

typedef struct{
	tInterfaceMenuDescribtion *menu;
	tGameListEntry games[kMaxGames];
	int numGames;
	tGameListEntry localGames[kMaxGames];
	int numLocalGames;
	tGameListPlayerEntry players[kMaxLobbyPlayers];
	int numPlayers;	
	int inited;
	int numGamesInMenu;
	tChatBuffer cb;
} tFindGamesMenuData;

enum{
	kLobbyHostGame,
	kLobbyJoinIP,
	kLobbyExit,
	kNumLobbyMenuItems
};

int FindGamesCallback(tFindGamesMenuData *userData)
{
	int updated;
	NetworkSearchGames(&updated,userData->games,&userData->numGames,kMaxGames,userData->players,&userData->numPlayers,kMaxLobbyPlayers);
	if(gLocalGameListUpdate!=gLastLocalGameListUpdate)
	{
		updated=true;	
		userData->numLocalGames=gNumLocalGames;
		if(userData->numLocalGames>kMaxGames)
			userData->numLocalGames=kMaxGames;
		MemoryMove(userData->localGames,gLocalGames,userData->numLocalGames*sizeof(tGameListEntry));
		gLastLocalGameListUpdate=gLocalGameListUpdate;
	}
	if(updated)
		userData->inited=true;
	//if(userData->numGames!=userData->numGamesInMenu)
	{
		if(userData->numGames==0&&userData->numLocalGames==0)
		{
			if(userData->inited)
				strcpy(userData->menu->items[kNumLobbyMenuItems].label,"<no games available>");
			else
				strcpy(userData->menu->items[kNumLobbyMenuItems].label,"<connecting to tracker>");
			strcpy(userData->menu->items[kNumLobbyMenuItems].describtion,"");
			userData->menu->numItems=kNumLobbyMenuItems+1;
			userData->menu->items[kNumLobbyMenuItems].flags|=kInterfaceMenuItemDisabled;
		}
		else
		{			
			for(int i=0;i<userData->numLocalGames;i++)
			{
				sprintf(userData->menu->items[kNumLobbyMenuItems+i].label,"%s \255rendezvous.pct\255",userData->localGames[i].name);
				userData->menu->items[kNumLobbyMenuItems+i].flags=0;
			}
			for(int i=userData->numLocalGames;i<userData->numGames+userData->numLocalGames;i++)
			{
				if(userData->games[i-userData->numLocalGames].loaded)
				{
					userData->menu->items[kNumLobbyMenuItems+i].flags=0;
					if(userData->games[i-userData->numLocalGames].numPlayers>0)
					{
						sprintf(userData->menu->items[kNumLobbyMenuItems+i].label,"%s (%d/%d) %s",userData->games[i-userData->numLocalGames].name
								,userData->games[i-userData->numLocalGames].numNetPlayers?userData->games[i-userData->numLocalGames].numNetPlayers:userData->games[i-userData->numLocalGames].numPlayers
								,userData->games[i-userData->numLocalGames].maxPlayers
								,userData->games[i-userData->numLocalGames].password?" \255lock.pct\255":"");
						if(userData->games[i-userData->numLocalGames].numNetPlayers>=userData->games[i-userData->numLocalGames].maxPlayers)
							userData->menu->items[kNumLobbyMenuItems+i].flags|=kInterfaceMenuItemDisabled;
					}
					else
						strcpy(userData->menu->items[kNumLobbyMenuItems+i].label,userData->games[i-userData->numLocalGames].host);				
					if(userData->games[i-userData->numLocalGames].locked)
						userData->menu->items[kNumLobbyMenuItems+i].flags|=kInterfaceMenuItemDisabled;
					if(userData->games[i-userData->numLocalGames].onlyRegistered&&!RT3_IsRegistered())
						userData->menu->items[kNumLobbyMenuItems+i].flags|=kInterfaceMenuItemDisabled;
				}
				else
				{
					strcpy(userData->menu->items[kNumLobbyMenuItems+i].label,"<loading>");								
					userData->menu->items[kNumLobbyMenuItems+i].flags|=kInterfaceMenuItemDisabled;
				}
			}
			userData->menu->numItems=userData->numGames+userData->numLocalGames+kNumLobbyMenuItems;
		}
		userData->numGamesInMenu=userData->numGames+userData->numLocalGames;
	}
		
	tChatMessage msg;
	int snd=false;
	while(TrackerMessageReceive(&msg))
	{
		ChatBufferInsert(&msg,&userData->cb);
		if(!(msg.flags&kChatFlagSilent))
			snd=true;
	}
	if(snd)
	{
		SoundReInit();
		if(msg.flags&(kChatFlagSystem|kChatFlagAlert))
			PlayInterfaceSound(FileGetReference("systemchat.wav"));
		else
			PlayInterfaceSound(FileGetReference("chat.wav"));
	}
	
	return -1;
}

void FindGamesRenderCallback(tFindGamesMenuData *userData,int selection,tInterfaceMenuDescribtion *menu)
{
	InterfaceRenderReplay(NULL,selection,menu);

	if(selection-kNumLobbyMenuItems>=0&&selection-kNumLobbyMenuItems<userData->numGames+userData->numLocalGames)
	{
		tGameListEntry *game=selection-kNumLobbyMenuItems<userData->numLocalGames?userData->localGames+selection-kNumLobbyMenuItems:userData->games+selection-kNumLobbyMenuItems-userData->numLocalGames;
		if(!game->loaded)	
			return;
			
	//	TextPrintfToBufferFormated(Vector(0.3,0.65),0.028,kTextAlignLeft,"\255#a\255redline://%s",game->host);
		
		if(selection-kNumLobbyMenuItems>=userData->numLocalGames)
		{
			switch(game->arcade)
			{
				case 0:
					TextPrintfToBufferFormated(Vector(-0.22,0.57),0.028,kTextAlignLeft,"Game Mode: \255#a\255Simulation");
					break;
				case 1:
					TextPrintfToBufferFormated(Vector(-0.22,0.57),0.028,kTextAlignLeft,"Game Mode: \255#a\255Arcade");
					break;
				case 2:
					TextPrintfToBufferFormated(Vector(-0.22,0.57),0.028,kTextAlignLeft,"Game Mode: \255#a\255Turbo");
					break;
				case 3:
					TextPrintfToBufferFormated(Vector(-0.22,0.57),0.028,kTextAlignLeft,"Game Mode: \255#a\255Strict");
					break;
			}
			char str[1024];
			sprintf(str,"Track: \255#a\255%s",game->map);
			StripText(str,0.028,0.26);
			TextPrintfToBufferFormated(Vector(-0.22,0.65),0.028,kTextAlignLeft+kTextAutoCut,str);
			//TextPrintfToBufferFormated(Vector(0.28,0.35),0.028,kTextAlignLeft,"Password Protection: \255#a\255%s",game->password?"On":"Off");

		//	TextPrintfToBufferFormated(Vector(0.3,0.65),0.028,kTextAlignLeft,"Players in Game: \255#a\255%d/%d",game->numPlayers,game->maxPlayers);

			tPlayerListRenderEntry pl[kMaxPlayers];
			for(int j=0;j<game->numPlayers;j++)
			{
				sprintf(pl[j].name,"%s: \255#a\255%s",game->players[j].name,game->players[j].car);
				strcpy(pl[j].sub,game->players[j].location);
				pl[j].r=pl[j].g=pl[j].b=pl[j].a=1;
				pl[j].sr=pl[j].sg=pl[j].sb=1;
			}
			char title[256];
			sprintf(title,"Players in game: \255#a\255%d/%d",game->numNetPlayers,game->maxPlayers);
			RenderPlayerList(title,game->numPlayers,pl);

/*
			for(int j=0;j<game->numPlayers;j++)
			{
				TextPrintfToBufferFormated(Vector(0.3,0.65-j*0.1),0.028,kTextAlignLeft+kTextAutoCut,"%s: \255#a\255%s",game->players[j].name,game->players[j].car);
				TextPrintfToBufferFormated(Vector(0.97,0.6-j*0.1),0.022,kTextAlignRight,game->players[j].location);
			}	*/
		/*	else if(game->joinState==kJoinStateOK)
				TextPrintfToBufferFormated(Vector(0.28,0.24),0.028,kTextAlignLeft+kTextAutoWrap,"Joining this game shouldn't cause any troubles.");
			else if(game->joinState==kJoinStateReggie)
				TextPrintfToBufferFormated(Vector(0.28,0.24),0.028,kTextAlignLeft+kTextAutoWrap,"We'll need to pull off some tricks, but joining will probably work.");*/

			if(game->onlyRegistered&&!RT3_IsRegistered())
				TextPrintfToBufferFormated(Vector(-0.22,0.0),0.028,kTextAlignLeft+kTextAutoWrap,"\255#r\255Game is only accepting registered players.");				
			else if(game->locked)
				TextPrintfToBufferFormated(Vector(-0.22,0.0),0.028,kTextAlignLeft+kTextAutoWrap,"\255#r\255Game is in progress or not accepting new players.");
			else if(game->joinState==kJoinStateFail)
				TextPrintfToBufferFormated(Vector(-0.22,0.0),0.028,kTextAlignLeft+kTextAutoWrap,"\255#r\255Due to the way the networks are set up, you probably won't be able to join!");
			else if(game->password)
				TextPrintfToBufferFormated(Vector(-0.22,0.0),0.028,kTextAlignLeft+kTextAutoWrap,"This game is password protected!");
			
			if(game->mapRef!=kFileErr)
			{
				tMapInfo *mapInfo=(tMapInfo*)FileGetParsedDataPtr(game->mapRef,kParserTypeMapInfoDesc,sizeof(tMapInfo));
				if(mapInfo->image)
					menu->image=mapInfo->image;
				else
					menu->image=kFileErr;
				menu->imageXScale=0.12;
				menu->imageYScale=0.06;	
				menu->imageXPos=0.01;
				menu->imageYPos=0.1;
			}
			else
				menu->image=kFileErr;
		}
		else
		{
			menu->image=kFileErr;
			TextPrintfToBufferFormated(Vector(-0.22,0.57),0.028,kTextAlignLeft,"Game Mode: \255#a\255n/a");
			TextPrintfToBufferFormated(Vector(-0.22,0.65),0.028,kTextAlignLeft,"Track: \255#a\255n/a");
		//	TextPrintfToBufferFormated(Vector(0.28,0.35),0.028,kTextAlignLeft,"Password Protection: \255#a\255n/a");
		}
	}
	else
	{
		menu->image=FileGetReference("ambrosia.pct");
		menu->imageXScale=0.09;
		menu->imageYScale=0.12;	
		menu->imageXPos=0.01;
		menu->imageYPos=0.11;

		tPlayerListRenderEntry pl[kMaxLobbyPlayers];
		for(int j=0;j<userData->numPlayers;j++)
		{
			pl[j].r=pl[j].g=pl[j].b=pl[j].a=1;
			pl[j].sr=pl[j].sg=pl[j].sb=1;
			if(userData->players[j].startIdleTime!=-1)
				pl[j].a*=0.5;
			strcpy(pl[j].name,userData->players[j].name);
			if(userData->players[j].startIdleTime!=-1)
			{
				UInt32 idleTime=TimerGetSeconds()-userData->players[j].startIdleTime;
				if(idleTime>3600)
					sprintf(pl[j].sub,"Idle for %dh %dm",(int)(idleTime/3600),(((int)idleTime)%3600)/60);
				else
					sprintf(pl[j].sub,"Idle for %d minutes",(int)(idleTime/60));
			}
			else
				strcpy(pl[j].sub,userData->players[j].location);
		}
		char title[256];
		sprintf(title,"Players in lobby: \255#a\255%d",userData->numPlayers);
		RenderPlayerList(title,userData->numPlayers,pl);
	}
//	TextPrintfToBufferFormated(Vector(-0.95,0.65),0.028,kTextAlignLeft,"Active Games: %d",userData->numGames);
//	TextPrintfToBufferFormated(Vector( 0.22,0.65),0.028,kTextAlignRight,"Players idling on tracker: %d",userData->numPlayers);
	
	RenderChatBuffer(&userData->cb,-0.22,0.9,-0.22,0.028,menu->type);
}

void ManuallyJoinGame()
{
	char address[256]="";
	char exitMessage[256]="";
	if(InterfaceGetUserInputString("Enter IP Address:",address,256,false,false))
		InterfaceJoinNetworkGame(address,"");	
	ExitNetGame();
	NetworkPreSession();
	if(*gDisconnectString)
		InterfaceDisplayMessage(kFileErr,"Disconnected",gDisconnectString);
	strcpy(gDisconnectString,"");
}

tFindGamesMenuData gFindGamesUserData;

void InterfaceMultiplayerLobby()
{
	gFileErrorReporting=false;	
	tInterfaceMenuDescribtion menu;
	InterfaceInitMenu(&menu,kNumLobbyMenuItems+kMaxGames,"Multiplayer Lobby");
	strcpy(menu.items[kLobbyHostGame].label,"Host Game...");
	strcpy(menu.items[kLobbyJoinIP].label,"Enter IP Address...");
	strcpy(menu.items[kLobbyExit].label,"Exit Multiplayer");
	for(int i=0;i<kNumLobbyMenuItems;i++){
		menu.items[i].flags|=kInterfaceMenuItemFixedPos;
		menu.items[i].fixedX=-0.9;
		menu.items[i].fixedY=-0.64-i*0.1;
	}
	for(int i=0;i<kNumLobbyMenuItems+kMaxGames;i++){
		menu.items[i].size=0.03;
		menu.items[i].lineSpacing=0.1;
		menu.items[i].maxTypeXPos=-0.25;
	}

	NetworkPreSession();
	LocalTrackerSearchGamesInit();
	gLastLocalGameListUpdate=0;
	
	menu.userData=&gFindGamesUserData;
	menu.scrollEnable=true;
	menu.minScroll=-0.55;
	menu.itemsYPos+=0.1;
	menu.imageXScale=0.12;
	menu.imageYScale=0.06;	
	menu.imageXPos+=0.01;
	menu.imageYPos+=0.1;
	menu.menuTypeable=kMenuTypeableReggie;
	
	gFindGamesUserData.menu=&menu;
	gFindGamesUserData.numGamesInMenu=0;
	gFindGamesUserData.numGames=0;
	gFindGamesUserData.numPlayers=0;
	gFindGamesUserData.numLocalGames=0;
	gFindGamesUserData.inited=false;
	gFindGamesUserData.cb.chatBufferLines=0;
	
	menu.TimerCallback=(int(*)(void*))FindGamesCallback;
	menu.RenderCallback=(void(*)(void*,int,void*))FindGamesRenderCallback;
	menu.numItems=kNumLobbyMenuItems;
	menu.joinDisable=true;
	menu.background=FileGetReference("background-multiplayer2.tif");
	
	int exit=false;			
	do{
		menu.initialSelection=InterfaceGetUserMenuSelection(&menu);
		if(*menu.type)
		{
			char msg[1024];
			sprintf(msg,"%s:\255#a\255 %s",gConfig->playerName,menu.type);
			TrackerMessageSend(msg);
			*menu.type='\0';
		}
		else switch(menu.initialSelection)
		{
			/*case kLobbyChat:
				if(*menu.items[kLobbyChat].type)
				{
					char msg[1024];
					sprintf(msg,"%s:\255#a\255 %s",gConfig->playerName,menu.items[kLobbyChat].type);
					TrackerMessageSend(msg);
					*menu.items[kLobbyChat].type='\0';
				}
				break;*/
			case kLobbyHostGame:
				NetworkSearchGamesStop();
				//NetworkSearchGamesDone();
				LocalTrackerSearchGamesDone();
				//ExitNetGame();
				InterfaceSetupHost();
				NetworkCountPlayersStop();
				LocalTrackerSearchGamesInit();
				NetworkPreSession();
				break;
			case kLobbyJoinIP:
				LocalTrackerSearchGamesDone();
				ManuallyJoinGame();
				LocalTrackerSearchGamesInit();
				break;
			case kInterfaceMenuEsc:
			case kLobbyExit:
				exit=true;
				break;
			default:
				{
					LocalTrackerSearchGamesDone();
					char exitMessage[256]="";
					char address[256]="";
					strcpy(address,
						menu.initialSelection-kNumLobbyMenuItems<gFindGamesUserData.numLocalGames?
						gFindGamesUserData.localGames[menu.initialSelection-kNumLobbyMenuItems].host
						:gFindGamesUserData.games[menu.initialSelection-kNumLobbyMenuItems-gFindGamesUserData.numLocalGames].host
					);
					char alias[256]="";
					strcpy(alias,
						menu.initialSelection-kNumLobbyMenuItems<gFindGamesUserData.numLocalGames?""
						:gFindGamesUserData.games[menu.initialSelection-kNumLobbyMenuItems-gFindGamesUserData.numLocalGames].alias
					);
					InterfaceJoinNetworkGame(address,alias);	
					ExitNetGame();
					LocalTrackerSearchGamesInit();
					NetworkPreSession();
					if(*gDisconnectString)
						InterfaceDisplayMessage(kFileErr,"Disconnected",gDisconnectString);
					strcpy(gDisconnectString,"");
				}
		}
	}while(!exit);
	
	InterfaceDisposeMenu(&menu);

	LocalTrackerSearchGamesDone();
	NetworkSearchGamesStop();
	ExitNetGame();
	gFileErrorReporting=true;	
}

void DirectJoinGame(char *address)
{
	NetworkPreSession();
	char exitMessage[256]="";
	InterfaceJoinNetworkGame(address,"");	
	ExitNetGame();
	NetworkPreSession();
	if(*gDisconnectString)
		InterfaceDisplayMessage(kFileErr,"Disconnected",gDisconnectString);
	strcpy(gDisconnectString,"");
}