Redline/source/interfacemultiplayer.cpp

2234 lines
74 KiB
C++
Raw Permalink Normal View History

//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"
#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,"");
}