02061d74c2
(as received from Jonas Echterhoff)
2236 lines
74 KiB
C++
2236 lines
74 KiB
C++
//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,"");
|
|
} |