/*
** Command & Conquer Generals(tm)
** Copyright 2025 Electronic Arts Inc.
**
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see .
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: GameSpy.cpp //////////////////////////////////////////////////////
// GameSpy callbacks, etc
// Author: Matthew D. Campbell, February 2002
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "GameSpy/GP/GP.h"
#include "GameSpy/gstats/gpersist.h"
#include "GameNetwork/FirewallHelper.h"
#include "GameNetwork/GameSpy.h"
#include "GameNetwork/GameSpyChat.h"
#include "GameNetwork/GameSpyGameInfo.h"
#include "GameNetwork/GameSpyOverlay.h"
#include "GameNetwork/GameSpyGP.h"
#include "GameNetwork/GameSpyPersistentStorage.h"
#include "GameNetwork/GameSpyThread.h"
#include "GameNetwork/NAT.h"
#include "GameClient/Shell.h"
#include "GameClient/MessageBox.h"
#include "GameClient/GameText.h"
#include "GameClient/MapUtil.h"
#include "Common/Version.h"
#include "Common/MultiplayerSettings.h"
#include "Common/PlayerTemplate.h"
#include "Common/RandomValue.h"
#include "Common/GlobalData.h"
#include "Common/UserPreferences.h"
#include "GameLogic/ScriptEngine.h"
MutexClass TheGameSpyMutex;
static UnsignedInt mainThreadID = 0;
static Bool inThread = false;
#define ISMAINTHREAD ( ThreadClass::_Get_Current_Thread_ID() == mainThreadID )
GameSpyThreadClass *TheGameSpyThread = NULL;
/// polling/update thread function
void GameSpyThreadClass::Thread_Function()
{
//poll network and update GameSpy object
while (running)
{
inThread = true;
if (m_doLogin)
{
m_doLogin = false;
TheGameSpyChat->login(m_nick, m_pass, m_email);
}
if (m_readStats)
{
m_readStats = false;
TheGameSpyPlayerInfo->threadReadFromServer();
}
if (m_updateLocale)
{
m_updateLocale = false;
TheGameSpyPlayerInfo->threadSetLocale(m_locale);
}
if (m_updateWins)
{
m_updateWins = false;
TheGameSpyPlayerInfo->threadSetLocale(m_wins);
}
if (m_updateLosses)
{
m_updateLosses = false;
TheGameSpyPlayerInfo->threadSetLocale(m_losses);
}
inThread = false;
Switch_Thread();
}
}
AsciiString GameSpyThreadClass::getNextShellScreen( void )
{
MutexClass::LockClass m(TheGameSpyMutex, 0);
if (m.Failed())
return AsciiString::TheEmptyString;
return m_nextShellScreen;
}
Bool GameSpyThreadClass::showLocaleSelect( void )
{
MutexClass::LockClass m(TheGameSpyMutex, 0);
if (m.Failed())
return false;
return m_showLocaleSelect;
}
void GameSpyThreadClass::setNextShellScreen( AsciiString nextShellScreen )
{
MutexClass::LockClass m(TheGameSpyMutex);
m_nextShellScreen = nextShellScreen;
}
void GameSpyThreadClass::setShowLocaleSelect( Bool val )
{
MutexClass::LockClass m(TheGameSpyMutex);
m_showLocaleSelect = val;
}
void createRoomCallback(PEER peer, PEERBool success, PEERJoinResult result, RoomType roomType, void *param);
class GameSpyChat : public GameSpyChatInterface
{
public:
GameSpyChat();
virtual ~GameSpyChat();
virtual void init( void );
virtual void reset( void );
virtual void update( void );
virtual Bool isConnected( void );
virtual void login(AsciiString loginName, AsciiString password = AsciiString::TheEmptyString, AsciiString email = AsciiString::TheEmptyString);
virtual void reconnectProfile( void );
virtual void disconnectFromChat( void );
virtual void UTMRoom( RoomType roomType, const char *key, const char *val, Bool authenticate = FALSE );
virtual void UTMPlayer( const char *name, const char *key, const char *val, Bool authenticate = FALSE );
virtual void startGame( void );
virtual void leaveRoom( RoomType roomType );
virtual void setReady( Bool ready );
virtual void enumPlayers( RoomType roomType, peerEnumPlayersCallback callback, void *userData );
virtual void startListingGames( peerListingGamesCallback callback );
virtual void stopListingGames( void );
virtual void joinGroupRoom( Int ID );
virtual void joinStagingRoom( GServer server, AsciiString password );
virtual void createStagingRoom( AsciiString gameName, AsciiString password, Int maxPlayers );
virtual void joinBestGroupRoom( void );
void loginQuick(AsciiString loginName);
void loginProfile(AsciiString loginName, AsciiString password, AsciiString email);
void setProfileID( Int ID ) { m_profileID = ID; }
void _connectCallback(PEER peer, PEERBool success, void * param);
void _nickErrorCallback(PEER peer, int type, const char * nick, void * param);
void _GPConnectCallback(GPConnection * pconnection, GPConnectResponseArg * arg, void * param);
void _GPReconnectCallback(GPConnection * pconnection, GPConnectResponseArg * arg, void * param);
inline void finishJoiningGroupRoom( void ) { m_joiningGroupRoom = false; }
inline void finishJoiningStagingRoom( void ) { m_joiningStagingRoom = false; }
private:
UnsignedInt m_loginTimeoutPeriod; // in ms
UnsignedInt m_loginTimeout;
Bool m_joiningGroupRoom;
Bool m_joiningStagingRoom;
GameSpyThreadClass thread;
};
GameSpyChatInterface *TheGameSpyChat;
GameSpyChatInterface *createGameSpyChat( void )
{
mainThreadID = ThreadClass::_Get_Current_Thread_ID();
return NEW GameSpyChat;
}
void GameSpyChatInterface::clearGroupRoomList(void)
{
m_groupRooms.clear();
}
GameSpyChat::GameSpyChat()
{
m_peer = NULL;
}
GameSpyChat::~GameSpyChat()
{
reset();
if (thread.Is_Running())
thread.Stop();
TheGameSpyThread = NULL;
}
void GameSpyChat::init( void )
{
reset();
DEBUG_ASSERTCRASH(!thread.Is_Running(), ("Thread is running"));
thread.Execute();
thread.Set_Priority(0);
TheGameSpyThread = &thread;
}
void GameSpyChat::reset( void )
{
MutexClass::LockClass m(TheGameSpyMutex);
m_loginName.clear();
m_password.clear();
m_email.clear();
m_usingProfiles = false;
m_profileID = 0;
if (m_peer)
peerShutdown(m_peer);
m_peer = NULL;
m_groupRooms.clear();
m_currentGroupRoomID = 0;
m_loginTimeoutPeriod = 5000;
m_loginTimeout = 0;
m_joiningGroupRoom = false;
m_joiningStagingRoom = false;
//if (thread.Is_Running())
//thread.Stop();
}
void GameSpyChat::update( void )
{
MutexClass::LockClass m(TheGameSpyMutex, 0);
if (!m.Failed() && m_peer)
{
if (!TheGameSpyThread->getNextShellScreen().isEmpty())
{
TheShell->pop();
TheShell->push(TheGameSpyThread->getNextShellScreen());
TheGameSpyThread->setNextShellScreen( AsciiString.TheEmptyString );
}
if (TheGameSpyThread->showLocaleSelect())
{
TheGameSpyThread->setShowLocaleSelect(false);
WindowLayout *layout = NULL;
layout = TheWindowManager->winCreateLayout( AsciiString( "Menus/PopupLocaleSelect.wnd" ) );
layout->runInit();
layout->hide( FALSE );
layout->bringForward();
TheWindowManager->winSetModal( layout->getFirstWindow() );
}
//if (!isConnected())
//return;
peerThink(m_peer);
gpProcess(TheGPConnection);
if (TheNAT != NULL) {
NATStateType NATState = TheNAT->update();
if (NATState == NATSTATE_DONE)
{
GameSpyLaunchGame();
}
else if (NATState == NATSTATE_FAILED)
{
GameSpyLaunchGame();
}
}
if (TheFirewallHelper != NULL) {
if (TheFirewallHelper->behaviorDetectionUpdate()) {
TheGlobalData->m_firewallBehavior = TheFirewallHelper->getFirewallBehavior();
OptionPreferences *pref = NEW OptionPreferences;
char num[16];
num[0] = 0;
itoa(TheGlobalData->m_firewallBehavior, num, 10);
AsciiString numstr;
numstr = num;
(*pref)["FirewallBehavior"] = numstr;
pref->write();
// we are now done with the firewall helper
delete TheFirewallHelper;
TheFirewallHelper = NULL;
}
}
UnsignedInt now = timeGetTime();
if (m_loginTimeout && now > m_loginTimeout)
{
// login timed out
m_loginTimeout = 0;
GSMessageBoxOk(UnicodeString(L"Error connecting"), UnicodeString(L"Timed out connecting"), NULL);
// Enable controls again
//EnableLoginControls(TRUE);
if (TheGameSpyGame)
delete TheGameSpyGame;
TheGameSpyGame = NULL;
if (m_peer)
peerShutdown(m_peer);
m_peer = NULL;
gpDisconnect(TheGPConnection);
gpDestroy(TheGPConnection);
}
}
}
Bool GameSpyChat::isConnected( void )
{
return m_peer && peerIsConnected(m_peer);
}
void GameSpyChat::UTMRoom( RoomType roomType, const char *key, const char *val, Bool authenticate )
{
peerUTMRoom( m_peer, roomType, key, val, (authenticate)?PEERTrue:PEERFalse );
}
void GameSpyChat::UTMPlayer( const char *name, const char *key, const char *val, Bool authenticate )
{
peerUTMPlayer( m_peer, name, key, val, (authenticate)?PEERTrue:PEERFalse );
}
void GameSpyChat::startGame( void )
{
peerStartGame( m_peer, NULL, PEER_STOP_REPORTING );
}
void GameSpyChat::leaveRoom( RoomType roomType )
{
peerLeaveRoom( m_peer, roomType, NULL );
}
void GameSpyChat::setReady( Bool ready )
{
peerSetReady( m_peer, (ready)?PEERTrue:PEERFalse );
}
void GameSpyChat::enumPlayers( RoomType roomType, peerEnumPlayersCallback callback, void *userData )
{
peerEnumPlayers( m_peer, roomType, callback, userData );
}
void GameSpyChat::startListingGames( peerListingGamesCallback callback )
{
peerSetUpdatesRoomChannel( m_peer, "#gmtest_updates" );
peerStartListingGames( m_peer, NULL, callback, NULL );
}
void GameSpyChat::stopListingGames( void )
{
peerStopListingGames( m_peer );
}
void GameSpyChat::joinGroupRoom( Int ID )
{
if (m_joiningGroupRoom || m_joiningStagingRoom)
return;
m_joiningGroupRoom = true;
if (getCurrentGroupRoomID())
{
leaveRoom(GroupRoom);
setCurrentGroupRoomID(0);
}
peerJoinGroupRoom(m_peer, ID, JoinRoomCallback, (void *)ID, PEERFalse);
}
void GameSpyChat::joinStagingRoom( GServer server, AsciiString password )
{
if (m_joiningGroupRoom || m_joiningStagingRoom)
return;
m_joiningStagingRoom = true;
peerJoinStagingRoom(m_peer, server, password.str(), JoinRoomCallback, server, PEERFalse);
}
void GameSpyChat::createStagingRoom( AsciiString gameName, AsciiString password, Int maxPlayers )
{
if (m_joiningGroupRoom || m_joiningStagingRoom)
return;
m_joiningStagingRoom = true;
Int oldGroupID = TheGameSpyChat->getCurrentGroupRoomID();
TheGameSpyChat->leaveRoom(GroupRoom);
TheGameSpyChat->setCurrentGroupRoomID(0);
DEBUG_LOG(("GameSpyChat::createStagingRoom - creating staging room, name is %s\n", m_loginName.str()));
peerCreateStagingRoom(m_peer, m_loginName.str(), maxPlayers, password.str(), createRoomCallback, (void *)oldGroupID, PEERFalse);
}
void GameSpyChat::joinBestGroupRoom( void )
{
if (m_groupRooms.size())
{
int minID = -1;
int minPlayers = 1000;
GroupRoomMap::iterator iter = m_groupRooms.begin();
while (iter != m_groupRooms.end())
{
GameSpyGroupRoom room = iter->second;
DEBUG_LOG(("Group room %d: %s (%d, %d, %d, %d)\n", room.m_groupID, room.m_name.str(), room.m_numWaiting, room.m_maxWaiting,
room.m_numGames, room.m_numPlaying));
if (minPlayers > 25 && room.m_numWaiting < minPlayers)
{
minID = room.m_groupID;
minPlayers = room.m_numWaiting;
}
++iter;
}
if (minID > 0)
{
joinGroupRoom(minID);
}
else
{
GSMessageBoxOk(UnicodeString(L"Oops"), UnicodeString(L"No empty group rooms"), NULL);
}
}
else
{
GSMessageBoxOk(UnicodeString(L"Oops"), UnicodeString(L"No group rooms"), NULL);
}
}
void GameSpyChat::disconnectFromChat( void )
{
TheScriptEngine->signalUIInteract("GameSpyLogout");
if (m_peer)
{
peerShutdown(m_peer);
m_peer = NULL;
}
if (TheGameSpyGame)
delete TheGameSpyGame;
TheGameSpyGame = NULL;
}
//-----------------------------------------------------------------------
void DisconnectedCallback(PEER peer, const char * reason,
void * param)
{
TheScriptEngine->signalUIInteract("GameSpyLogout");
if (TheGameSpyGame)
delete TheGameSpyGame;
TheGameSpyGame = NULL;
GSMessageBoxOk(TheGameText->fetch("GUI:GSErrorTitle"), TheGameText->fetch("GUI:GSDisconnected"), NULL);
WindowLayout * mainMenu = TheShell->findScreenByFilename("Menus/MainMenu.wnd");
if (mainMenu)
{
while (TheShell->top() != mainMenu)
{
TheShell->pop();
}
}
}
void ReadyChangedCallback(PEER peer, const char * nick,
PEERBool ready, void * param)
{
if (TheGameSpyGame)
{
Int slotNum = TheGameSpyGame->getSlotNum(nick);
if (slotNum >= 0)
{
GameSlot *slot = TheGameSpyGame->getSlot(slotNum);
if (slot)
{
if (ready) {
slot->setAccept();
} else {
slot->unAccept();
}
if (TheGameSpyGame->amIHost())
{
peerUTMRoom(TheGameSpyChat->getPeer(), StagingRoom, "SL/", GameInfoToAsciiString(TheGameSpyGame).str(), PEERFalse);
}
WOLDisplaySlotList();
}
}
}
}
void GameStartedCallback(PEER peer, unsigned int IP,
const char * message, void * param)
{
GameSpyStartGame();
}
void populateLobbyPlayerListbox(void);
void PlayerJoinedCallback(PEER peer, RoomType roomType,
const char * nick, void * param)
{
if (roomType == GroupRoom && TheGameSpyChat->getCurrentGroupRoomID())
{
populateLobbyPlayerListbox();
}
if (roomType == StagingRoom && TheGameSpyGame)
{
DEBUG_LOG(("StagingRoom, Game OK\n"));
for (Int i=1; igetSlot(i);
if (slot && slot->getState() == SLOT_OPEN)
{
DEBUG_LOG(("Putting %s in slot %d\n", nick, i));
UnicodeString uName;
uName.translate(nick);
slot->setState(SLOT_PLAYER, uName);
slot->setColor(-1);
slot->setPlayerTemplate(-1);
slot->setStartPos(-1);
slot->setTeamNumber(-1);
TheGameSpyGame->resetAccepted();
Int id;
UnsignedInt ip;
PEERBool success = peerGetPlayerInfoNoWait(TheGameSpyChat->getPeer(), nick, &ip, &id);
DEBUG_LOG(("PlayerJoinedCallback - result from peerGetPlayerInfoNoWait, %d: ip=%d.%d.%d.%d, id=%d\n", success,
(ip >> 24) & 0xff,
(ip >> 16) & 0xff,
(ip >> 8) & 0xff,
ip & 0xff,
id));
success = PEERFalse; // silence compiler warnings in release build
slot->setIP(ip);
if (TheGameSpyGame->amIHost())
{
peerUTMRoom(TheGameSpyChat->getPeer(), StagingRoom, "SL/", GameInfoToAsciiString(TheGameSpyGame).str(), PEERFalse);
}
WOLDisplaySlotList();
break;
}
}
if (i == MAX_SLOTS)
{
// we got all the way through without room for him - kick him
if (TheGameSpyGame->amIHost())
{
peerUTMRoom(TheGameSpyChat->getPeer(), StagingRoom, "SL/", GameInfoToAsciiString(TheGameSpyGame).str(), PEERFalse);
}
}
}
}
void PlayerLeftCallback(PEER peer, RoomType roomType,
const char * nick, const char * reason,
void * param)
{
if (roomType == GroupRoom && TheGameSpyChat->getCurrentGroupRoomID())
{
populateLobbyPlayerListbox();
}
if (roomType == StagingRoom && TheGameSpyGame)
{
Int slotNum = TheGameSpyGame->getSlotNum(nick);
if (slotNum >= 0)
{
GameSlot *slot = TheGameSpyGame->getSlot(slotNum);
if (slot)
{
slot->setState(SLOT_OPEN);
DEBUG_LOG(("Removing %s from slot %d\n", nick, slotNum));
if (TheGameSpyGame->amIHost())
{
peerUTMRoom(TheGameSpyChat->getPeer(), StagingRoom, "SL/", GameInfoToAsciiString(TheGameSpyGame).str(), PEERFalse);
}
TheGameSpyGame->resetAccepted();
WOLDisplaySlotList();
}
}
}
}
void PlayerChangedNickCallback(PEER peer, RoomType roomType,
const char * oldNick,
const char * newNick, void * param)
{
if (TheGameSpyChat->getCurrentGroupRoomID())
{
populateLobbyPlayerListbox();
}
}
void PingCallback(PEER peer, const char * nick, int ping,
void * param)
{
DEBUG_LOG(("PingCallback\n"));
}
void CrossPingCallback(PEER peer, const char * nick1,
const char * nick2, int crossPing,
void * param)
{
DEBUG_LOG(("CrossPingCallback\n"));
}
static void RoomUTMCallback(PEER peer, RoomType roomType, const char * nick,
const char * command, const char * parameters,
PEERBool authenticated, void * param)
{
DEBUG_LOG(("RoomUTMCallback: %s says %s = [%s]\n", nick, command, parameters));
if (roomType == StagingRoom && TheGameSpyGame)
{
Int slotNum = TheGameSpyGame->getSlotNum(nick);
if (slotNum == 0 && !TheGameSpyGame->amIHost())
{
if (!strcmp(command, "SL"))
{
AsciiString options = parameters;
options.trim();
ParseAsciiStringToGameInfo(TheGameSpyGame, options.str());
WOLDisplaySlotList();
}
else if (!strcmp(command, "HWS")) // HostWantsStart
{
Int slotNum = TheGameSpyGame->getLocalSlotNum();
GameSlot *slot = TheGameSpyGame->getSlot(slotNum);
if (slot->isAccepted() == false)
{
GameSpyAddText(TheGameText->fetch("GUI:HostWantsToStart"), GSCOLOR_DEFAULT);
}
}
}
}
}
static void PlayerUTMCallback(PEER peer, const char * nick,
const char * command, const char * parameters,
PEERBool authenticated, void * param)
{
DEBUG_LOG(("PlayerUTMCallback: %s says %s = [%s]\n", nick, command, parameters));
if (TheGameSpyGame)
{
Int slotNum = TheGameSpyGame->getSlotNum(nick);
if (slotNum != 0 && TheGameSpyGame->amIHost())
{
if (!strcmp(command, "REQ"))
{
AsciiString options = parameters;
options.trim();
Bool change = false;
Bool shouldUnaccept = false;
AsciiString key;
options.nextToken(&key, "=");
Int val = atoi(options.str()+1);
UnsignedInt uVal = atoi(options.str()+1);
DEBUG_LOG(("GameOpt request: key=%s, val=%s from player %d\n", key.str(), options.str(), slotNum));
GameSlot *slot = TheGameSpyGame->getSlot(slotNum);
if (!slot)
return;
if (key == "Color")
{
if (val >= -1 && val < TheMultiplayerSettings->getNumColors() && val != slot->getColor())
{
Bool colorAvailable = TRUE;
if(val != -1 )
{
for(Int i=0; i getSlot(i);
if(val == checkSlot->getColor() && slot != checkSlot)
{
colorAvailable = FALSE;
break;
}
}
}
if(colorAvailable)
slot->setColor(val);
change = true;
}
else
{
DEBUG_LOG(("Rejecting invalid color %d\n", val));
}
}
else if (key == "PlayerTemplate")
{
if (val >= PLAYERTEMPLATE_MIN && val < ThePlayerTemplateStore->getPlayerTemplateCount() && val != slot->getPlayerTemplate())
{
slot->setPlayerTemplate(val);
change = true;
shouldUnaccept = true;
}
else
{
DEBUG_LOG(("Rejecting invalid PlayerTemplate %d\n", val));
}
}
else if (key == "StartPos")
{
if (val >= -1 && val < MAX_SLOTS && val != slot->getStartPos())
{
slot->setStartPos(val);
change = true;
shouldUnaccept = true;
}
else
{
DEBUG_LOG(("Rejecting invalid startPos %d\n", val));
}
}
else if (key == "Team")
{
if (val >= -1 && val < MAX_SLOTS/2 && val != slot->getTeamNumber())
{
slot->setTeamNumber(val);
change = true;
shouldUnaccept = true;
}
else
{
DEBUG_LOG(("Rejecting invalid team %d\n", val));
}
}
else if (key == "IP")
{
if (uVal != slot->getIP())
{
slot->setIP(uVal);
change = true;
shouldUnaccept = true;
}
else
{
DEBUG_LOG(("Rejecting invalid IP %d\n", uVal));
}
}
else if (key == "NAT")
{
if ((val >= FirewallHelperClass::FIREWALL_TYPE_SIMPLE) &&
(val <= FirewallHelperClass::FIREWALL_TYPE_DESTINATION_PORT_DELTA))
{
slot->setNATBehavior((FirewallHelperClass::FirewallBehaviorType)val);
DEBUG_LOG(("Setting NAT behavior to %d for player %d\n", val, slotNum));
change = true;
}
else
{
DEBUG_LOG(("Rejecting invalid NAT behavior %d from player %d\n", val, slotNum));
}
}
if (change)
{
if (shouldUnaccept)
TheGameSpyGame->resetAccepted();
peerUTMRoom(TheGameSpyChat->getPeer(), StagingRoom, "SL/", GameInfoToAsciiString(TheGameSpyGame).str(), PEERFalse);
WOLDisplaySlotList();
DEBUG_LOG(("Slot value is color=%d, PlayerTemplate=%d, startPos=%d, team=%d, IP=0x%8.8X\n",
slot->getColor(), slot->getPlayerTemplate(), slot->getStartPos(), slot->getTeamNumber(), slot->getIP()));
DEBUG_LOG(("Slot list updated to %s\n", GameInfoToAsciiString(TheGameSpyGame).str()));
}
}
}
}
}
void GOABasicCallback(PEER peer, PEERBool playing, char * outbuf,
int maxlen, void * param)
{
_snprintf(outbuf, maxlen, "\\gamename\\c&cgenerals\\gamever\\%s\\location\\%d",
TheVersion->getAsciiVersion().str(), 0);
outbuf[maxlen-1] = 0;
DEBUG_LOG(("GOABasicCallback: [%s]\n", outbuf));
TheGameSpyGame->gotGOACall();
}
void GOAInfoCallback(PEER peer, PEERBool playing, char * outbuf,
int maxlen, void * param)
{
_snprintf(outbuf, maxlen, "\\hostname\\%s\\hostport\\%d\\mapname\\%s\\gametype\\%s\\numplayers\\%d\\maxplayers\\%d\\gamemode\\%s",
TheGameSpyChat->getLoginName().str(), 0, TheGameSpyGame->getMap().str(), "battle", TheGameSpyGame->getNumPlayers(), TheGameSpyGame->getMaxPlayers(), "wait");
outbuf[maxlen-1] = 0;
DEBUG_LOG(("GOAInfoCallback: [%s]\n", outbuf));
TheGameSpyGame->gotGOACall();
}
void GOARulesCallback(PEER peer, PEERBool playing, char * outbuf,
int maxlen, void * param)
{
_snprintf(outbuf, maxlen, "\\password\\0\\seed\\%d",
TheGameSpyGame->getSeed());
outbuf[maxlen-1] = 0;
DEBUG_LOG(("GOARulesCallback: [%s]\n", outbuf));
TheGameSpyGame->gotGOACall();
}
void GOAPlayersCallback(PEER peer, PEERBool playing, char * outbuf,
int maxlen, void * param)
{
AsciiString buf, tmp;
for (Int i=0; igetSlot(i);
AsciiString name;
switch (slot->getState())
{
case SLOT_OPEN:
name = "O";
break;
case SLOT_CLOSED:
name = "X";
break;
case SLOT_EASY_AI:
name = "CE";
break;
case SLOT_MED_AI:
name = "CM";
break;
case SLOT_BRUTAL_AI:
name = "CB";
break;
case SLOT_PLAYER:
{
AsciiString tmp;
tmp.translate(slot->getName());
name.format("H%s", tmp.str());
}
break;
default:
name = "?";
break;
}
tmp.format("\\player_%d\\%s\\color_%d\\%d\\faction_%d\\%d\\team_%d\\%d\\pos_%d\\%d",
i, name.str(),
i, slot->getColor(),
i, slot->getPlayerTemplate(),
i, slot->getTeamNumber(),
i, slot->getStartPos());
buf.concat(tmp);
}
_snprintf(outbuf, maxlen, buf.str());
outbuf[maxlen-1] = 0;
DEBUG_LOG(("GOAPlayersCallback: [%s]\n", outbuf));
TheGameSpyGame->gotGOACall();
}
void JoinRoomCallback(PEER peer, PEERBool success, PEERJoinResult result, RoomType roomType, void *param)
{
DEBUG_LOG(("JoinRoomCallback: success==%d, result==%d\n", success, result));
switch (roomType)
{
case GroupRoom:
{
((GameSpyChat *)TheGameSpyChat)->finishJoiningGroupRoom();
if (success)
{
// update our internal group room ID
TheGameSpyChat->setCurrentGroupRoomID((Int)param);
// see if we need to change screens
WindowLayout *layout = TheShell->top();
AsciiString windowFile = layout->getFilename();
DEBUG_LOG(("Joined group room, active screen was %s\n", windowFile.str()));
if (windowFile.compareNoCase("Menus/WOLCustomLobby.wnd") == 0)
{
// already on the right screen - just refresh it
//GroupRoomJoinLobbyRefresh();
}
else
{
// change to the right screen
TheShell->pop();
TheShell->push("Menus/WOLCustomLobby.wnd");
}
}
else
{
// failed to join lobby - bail to welcome screen
WindowLayout *layout = TheShell->top();
AsciiString windowFile = layout->getFilename();
DEBUG_LOG(("Joined group room, active screen was %s\n", windowFile.str()));
if (windowFile.compareNoCase("Menus/WOLWelcomeMenu.wnd") != 0)
{
TheShell->pop();
TheShell->push("Menus/WOLWelcomeMenu.wnd");
}
GSMessageBoxOk(UnicodeString(L"Oops"), UnicodeString(L"Unable to join group room"), NULL);
}
// Update buddy location
if (TheGameSpyChat->getUsingProfile())
{
if (TheGameSpyChat->getCurrentGroupRoomID())
{
AsciiString s;
s.format("c&cgenerals://0.0.0.0:0/%d", TheGameSpyChat->getCurrentGroupRoomID());
gpSetStatus(TheGPConnection, GP_CHATTING, "Chatting", s.str());
}
else
{
gpSetStatus(TheGPConnection, GP_ONLINE, "Online", "");
}
}
}
break;
case StagingRoom:
{
((GameSpyChat *)TheGameSpyChat)->finishJoiningStagingRoom();
if (success)
{
DEBUG_LOG(("JoinRoomCallback - Joined staging room\n"));
GServer server = (GServer)param;
// leave any chat channels
TheGameSpyChat->leaveRoom(GroupRoom);
TheGameSpyChat->setCurrentGroupRoomID(0);
// set up game info
TheGameSpyGame->enterGame();
TheGameSpyGame->setServer(server);
GameSlot *slot = TheGameSpyGame->getSlot(0);
AsciiString options, hostName;
hostName = ServerGetPlayerStringValue(server, 0, "player", "");
UnicodeString uHostName;
uHostName.translate(hostName.str() + 1); // go past the 'H'
slot->setState(SLOT_PLAYER, uHostName);
UnsignedInt localIP = peerGetLocalIP(TheGameSpyChat->getPeer());
GetLocalChatConnectionAddress("peerchat.gamespy.com", 6667, localIP);
localIP = ntohl(localIP); // The IP returned from GetLocalChatConnectionAddress is in network byte order.
options.format("IP=%d", localIP);
peerUTMPlayer(TheGameSpyChat->getPeer(), hostName.str(), "REQ/", options.str(), PEERFalse);
options.format("NAT=%d", TheFirewallHelper->getFirewallBehavior());
peerUTMPlayer(TheGameSpyChat->getPeer(), hostName.str(), "REQ/", options.str(), PEERFalse);
// refresh the map cache
TheMapCache->updateCache();
// switch screens
TheShell->pop();
TheShell->push("Menus/GameSpyGameOptionsMenu.wnd");
}
else
{
// let the user know
GSMessageBoxOk(UnicodeString(L"Oops"), UnicodeString(L"Unable to join game"), NULL);
DEBUG_LOG(("JoinRoomCallback - Failed to join staging room.\n"));
}
// Update buddy location
if (TheGameSpyChat->getUsingProfile())
{
if (TheGameSpyChat->getCurrentGroupRoomID())
{
AsciiString s;
s.format("c&cgenerals://0.0.0.0:0/%d", TheGameSpyChat->getCurrentGroupRoomID());
gpSetStatus(TheGPConnection, GP_CHATTING, "Chatting", s.str());
}
else
{
gpSetStatus(TheGPConnection, GP_ONLINE, "Online", "");
}
}
break;
}
}
//*didJoin = (success == PEERJoinSuccess || success == PEERTrue);
}
void createRoomCallback(PEER peer, PEERBool success, PEERJoinResult result, RoomType roomType, void *param)
{
DEBUG_LOG(("CreateRoomCallback: success==%d, result==%d\n", success, result));
if (roomType != StagingRoom)
{
DEBUG_CRASH(("Non-staging room create!"));
return;
}
((GameSpyChat *)TheGameSpyChat)->finishJoiningStagingRoom();
Int oldGroupID = (Int)param;
if (success)
{
// set up the game info
UnsignedInt localIP = peerGetLocalIP(TheGameSpyChat->getPeer());
DEBUG_LOG(("createRoomCallback - peerGetLocalIP returned %d.%d.%d.%d as the local IP\n",
localIP >> 24, (localIP >> 16) & 0xff, (localIP >> 8) & 0xff, localIP & 0xff));
// GetLocalChatConnectionAddress("peerchat.gamespy.com", 6667, localIP);
// DEBUG_LOG(("createRoomCallback - GetLocalChatConnectionAddress returned %d.%d.%d.%d as the local IP\n",
// localIP >> 24, (localIP >> 16) & 0xff, (localIP >> 8) & 0xff, localIP & 0xff));
localIP = ntohl(localIP); // The IP returned from GetLocalChatConnectionAddress is in network byte order.
TheGameSpyGame->setLocalIP(localIP);
UnicodeString name;
name.translate(TheGameSpyChat->getLoginName());
TheGameSpyGame->enterGame();
// TheGameSpyGame->setSeed(GameClientRandomValue(0, INT_MAX - 1));
TheGameSpyGame->setSeed(GetTickCount());
GameSlot newSlot;
newSlot.setState(SLOT_PLAYER, name);
newSlot.setIP(localIP);
TheGameSpyGame->setSlot(0,newSlot);
TheMapCache->updateCache();
TheGameSpyGame->setMap(TheGlobalData->m_mapName);
AsciiString asciiMap;
asciiMap = TheGlobalData->m_mapName;
asciiMap.toLower();
std::map::iterator it = TheMapCache->find(asciiMap);
if (it != TheMapCache->end())
{
TheGameSpyGame->getSlot(0)->setMapAvailability(true);
TheGameSpyGame->setMapCRC( it->second.m_CRC );
TheGameSpyGame->setMapSize( it->second.m_filesize );
TheGameSpyGame->adjustSlotsForMap(); // BGC- adjust the slots for the new map.
}
// change to the proper screen
TheShell->pop();
TheShell->push("Menus/GameSpyGameOptionsMenu.wnd");
}
else
{
// join the lobby again
TheGameSpyChat->joinGroupRoom(oldGroupID);
GSMessageBoxOk(UnicodeString(L"Oops"), UnicodeString(L"Unable to create game"), NULL);
}
// Update buddy location
if (TheGameSpyChat->getUsingProfile())
{
if (TheGameSpyChat->getCurrentGroupRoomID())
{
AsciiString s;
s.format("c&cgenerals://0.0.0.0:0/%d", TheGameSpyChat->getCurrentGroupRoomID());
gpSetStatus(TheGPConnection, GP_CHATTING, "Chatting", s.str());
}
else
{
gpSetStatus(TheGPConnection, GP_ONLINE, "Online", "");
}
}
}
// Gets called once for each group room when listing group rooms.
// After this has been called for each group room, it will be
// called one more time with groupID==0 and name==NULL.
/////////////////////////////////////////////////////////////////
void ListGroupRoomsCallback(PEER peer, PEERBool success,
int groupID, GServer server,
const char * name, int numWaiting,
int maxWaiting, int numGames,
int numPlaying, void * param)
{
DEBUG_LOG(("ListGroupRoomsCallback\n"));
if (success)
{
if (groupID)
{
GroupRoomMap *grMap = TheGameSpyChat->getGroupRooms();
(*grMap)[groupID].m_name = name;
(*grMap)[groupID].m_numWaiting = numWaiting;
(*grMap)[groupID].m_maxWaiting = maxWaiting;
(*grMap)[groupID].m_numGames = numGames;
(*grMap)[groupID].m_numPlaying = numPlaying;
(*grMap)[groupID].m_groupID = groupID;
}
else
{
// we've got the complete list.
UpdateGroupRoomList();
}
}
}
// Called when peerConnect completes.
static void connectCallback(PEER peer, PEERBool success, void * param)
{
((GameSpyChat *)TheGameSpyChat)->_connectCallback(peer, success, param);
}
static void nickErrorCallback(PEER peer, int type, const char * nick, void * param)
{
((GameSpyChat *)TheGameSpyChat)->_nickErrorCallback(peer, type, nick, param);
}
static void GPConnectCallback(GPConnection * pconnection, GPConnectResponseArg * arg, void * param)
{
((GameSpyChat *)TheGameSpyChat)->_GPConnectCallback(pconnection, arg, param);
}
static void GPReconnectCallback(GPConnection * pconnection, GPConnectResponseArg * arg, void * param)
{
((GameSpyChat *)TheGameSpyChat)->_GPReconnectCallback(pconnection, arg, param);
}
void GameSpyChat::_connectCallback(PEER peer, PEERBool success, void * param)
{
m_loginTimeout = 0;
if (!success) {
GSMessageBoxOk(UnicodeString(L"Error connecting"), UnicodeString(L"Failed to connect"), NULL);
DEBUG_LOG(("GameSpyChat::_connectCallback - failed to connect.\n"));
}
if(!success)
{
peerShutdown(m_peer);
m_peer = NULL;
gpDisconnect(TheGPConnection);
gpDestroy(TheGPConnection);
// Enable controls again
//EnableLoginControls(TRUE);
return;
}
DEBUG_LOG(("Connected as profile %d (%s)\n", m_profileID, m_loginName.str()));
TheGameSpyGame = NEW GameSpyGameInfo;
// Enable controls again
//EnableLoginControls(TRUE);
// the readFromServer() call will set the screen
if (m_profileID)
{
TheGameSpyPlayerInfo->readFromServer();
}
TheScriptEngine->signalUIInteract("GameSpyLogin");
TheShell->popImmediate();
TheShell->push("Menus/WOLWelcomeMenu.wnd");
clearGroupRoomList();
peerListGroupRooms(m_peer, ListGroupRoomsCallback, NULL, PEERFalse);
}
// Called if there's an error with the nick.
void GameSpyChat::_nickErrorCallback(PEER peer, int type, const char * nick, void * param)
{
// Let the user know.
/////////////////////
if(type == PEER_IN_USE)
{
AsciiString nickStr = nick;
AsciiString origName, appendedVal;
nickStr.nextToken(&origName, "{}");
nickStr.nextToken(&appendedVal, "{}");
Int intVal = 1;
if (!appendedVal.isEmpty())
{
intVal = atoi(appendedVal.str()) + 1;
}
DEBUG_LOG(("Nickname taken: origName=%s, tries=%d\n", origName.str(), intVal));
if (intVal < 10)
{
nickStr.format("%s{%d}", origName.str(), intVal);
// Retry the connect with a similar nick.
DEBUG_LOG(("GameSpyChat::_nickErrorCallback - nick was taken, setting to %s\n", nickStr.str()));
m_loginName = nickStr;
peerRetryWithNick(peer, nickStr.str());
}
else
{
GSMessageBoxOk(UnicodeString(L"Error connecting"), UnicodeString(L"That nickname is already taken; please choose another one."), NULL);
// Cancel the connect.
peerRetryWithNick(peer, NULL);
// Enable controls again
//EnableLoginControls(TRUE);
m_loginTimeout = 0;
}
}
else
{
GSMessageBoxOk(UnicodeString(L"Error connecting"), UnicodeString(L"That nickname contains at least 1 invalid character, please choose another one."), NULL);
// Cancel the connect.
peerRetryWithNick(peer, NULL);
// Enable controls again
//EnableLoginControls(TRUE);
m_loginTimeout = 0;
}
}
void GameSpyChat::_GPConnectCallback(GPConnection * pconnection, GPConnectResponseArg * arg, void * param)
{
DEBUG_LOG(("GPConnectCallback:\n"));
GPResult *res = (GPResult *)param;
*res = arg->result;
setProfileID(arg->profile);
if (*res != GP_NO_ERROR)
{
// couldn't connect. bummer.
GSMessageBoxOk(UnicodeString(L"Error connecting"), UnicodeString(L"Error connecting to buddy server"), NULL);
gpDisconnect(TheGPConnection);
gpDestroy(TheGPConnection);
m_loginTimeout = 0;
return;
}
// Connect to chat.
///////////////////
peerConnect(m_peer, m_loginName.str(), m_profileID, nickErrorCallback, connectCallback, NULL, PEERFalse);
}
static Bool inGPReconnect = false;
void GameSpyChat::_GPReconnectCallback(GPConnection * pconnection, GPConnectResponseArg * arg, void * param)
{
inGPReconnect = false;
DEBUG_LOG(("GPConnectCallback:\n"));
setProfileID(arg->profile);
if (arg->result != GP_NO_ERROR)
{
// couldn't connect. bummer.
GSMessageBoxOk(UnicodeString(L"Error connecting"), UnicodeString(L"Error connecting to buddy server"), NULL);
gpDisconnect(TheGPConnection);
gpDestroy(TheGPConnection);
return;
}
else
{
// yay! we're back in!
GSMessageBoxOk(UnicodeString(L"Connected!"), UnicodeString(L"Reonnected to buddy server"), NULL);
}
}
void GameSpyChat::loginProfile(AsciiString loginName, AsciiString password, AsciiString email)
{
// Connect to GP.
///////////////////
m_profileID = 0;
gpInitialize(TheGPConnection, 0);
gpSetCallback(TheGPConnection, GP_ERROR, (GPCallback)GPErrorCallback, NULL);
gpSetCallback(TheGPConnection, GP_RECV_BUDDY_REQUEST, (GPCallback)GPRecvBuddyRequestCallback, NULL);
gpSetCallback(TheGPConnection, GP_RECV_BUDDY_MESSAGE, (GPCallback)GPRecvBuddyMessageCallback, NULL);
gpSetCallback(TheGPConnection, GP_RECV_BUDDY_STATUS, (GPCallback)GPRecvBuddyStatusCallback, NULL);
GPResult res = GP_PARAMETER_ERROR;
m_loginName = loginName;
DEBUG_LOG(("GameSpyChat::loginProfile - m_loginName set to %s\n", m_loginName.str()));
m_password = password;
m_email = email;
gpConnect(TheGPConnection, loginName.str(), email.str(), password.str(), GP_FIREWALL, GP_NON_BLOCKING, (GPCallback)GPConnectCallback, &res);
/*
if (res != GP_NO_ERROR)
{
// couldn't connect. bummer.
GSMessageBoxOk(UnicodeString(L"Error connecting"), UnicodeString(L"Error connecting to buddy server"), NULL);
gpDisconnect(TheGPConnection);
gpDestroy(TheGPConnection);
loginTimeout = 0;
return;
}
// Connect to chat.
///////////////////
GameSpyLocalNickname = loginName;
peerConnect(TheGameSpyChat->getPeer(), loginName.str(), GameSpyLocalProfile, nickErrorCallback, connectCallback, NULL, PEERFalse);
*/
}
void GameSpyChat::reconnectProfile( void )
{
if (inGPReconnect)
return;
inGPReconnect = true;
gpInitialize(TheGPConnection, 0);
gpSetCallback(TheGPConnection, GP_ERROR, (GPCallback)GPErrorCallback, NULL);
gpSetCallback(TheGPConnection, GP_RECV_BUDDY_REQUEST, (GPCallback)GPRecvBuddyRequestCallback, NULL);
gpSetCallback(TheGPConnection, GP_RECV_BUDDY_MESSAGE, (GPCallback)GPRecvBuddyMessageCallback, NULL);
gpSetCallback(TheGPConnection, GP_RECV_BUDDY_STATUS, (GPCallback)GPRecvBuddyStatusCallback, NULL);
gpConnect(TheGPConnection, m_loginName.str(), m_email.str(), m_password.str(), GP_FIREWALL, GP_NON_BLOCKING, (GPCallback)GPReconnectCallback, NULL);
}
void GameSpyChat::loginQuick(AsciiString login)
{
m_profileID = 0; // no buddy stuff
// Connect to chat.
///////////////////
m_loginName = login;
DEBUG_LOG(("GameSpyChat::loginQuick - setting login to %s\n", m_loginName));
peerConnect(m_peer, login.str(), 0, nickErrorCallback, connectCallback, NULL, PEERFalse);
}
void GameSpyChat::login(AsciiString loginName, AsciiString password, AsciiString email)
{
MutexClass::LockClass m(TheGameSpyMutex);
if ( ISMAINTHREAD )
{
thread.queueLogin(loginName, password, email);
return;
}
// Setup the callbacks.
///////////////////////
PEERCallbacks callbacks;
memset(&callbacks, 0, sizeof(PEERCallbacks));
callbacks.disconnected = DisconnectedCallback;
callbacks.readyChanged = ReadyChangedCallback;
callbacks.roomMessage = RoomMessageCallback;
callbacks.playerMessage = PlayerMessageCallback;
callbacks.gameStarted = GameStartedCallback;
callbacks.playerJoined = PlayerJoinedCallback;
callbacks.playerLeft = PlayerLeftCallback;
callbacks.playerChangedNick = PlayerChangedNickCallback;
callbacks.ping = PingCallback;
callbacks.crossPing = CrossPingCallback;
callbacks.roomUTM = RoomUTMCallback;
callbacks.playerUTM = PlayerUTMCallback;
callbacks.GOABasic = GOABasicCallback;
callbacks.GOAInfo = GOAInfoCallback;
callbacks.GOARules = GOARulesCallback;
callbacks.GOAPlayers = GOAPlayersCallback;
//callbacks.globalKeyChanged = GlobalKeyChanged;
callbacks.param = NULL;
// Init peer.
/////////////
m_peer = peerInitialize(&callbacks);
if(!m_peer)
{
GSMessageBoxOk(UnicodeString(L"No Peer"), UnicodeString(L"No Peer"), NULL);
return;
}
// Setup which rooms to do pings and cross-pings in.
////////////////////////////////////////////////////
PEERBool pingRooms[NumRooms];
PEERBool crossPingRooms[NumRooms];
pingRooms[TitleRoom] = PEERTrue;
pingRooms[GroupRoom] = PEERTrue;
pingRooms[StagingRoom] = PEERFalse;
crossPingRooms[TitleRoom] = PEERFalse;
crossPingRooms[GroupRoom] = PEERFalse;
crossPingRooms[StagingRoom] = PEERTrue;
// Set the title.
/////////////////
if(!peerSetTitle(m_peer, "gmtest", "HA6zkS", "gmtest", "HA6zkS", 15, pingRooms, crossPingRooms))
{
GSMessageBoxOk(UnicodeString(L"Error setting title"), UnicodeString(L"Error setting title"), NULL);
peerShutdown(m_peer);
m_peer = NULL;
return;
}
//EnableLoginControls( FALSE );
m_loginTimeout = timeGetTime() + m_loginTimeoutPeriod;
if (!loginName.isEmpty() && !email.isEmpty() && !password.isEmpty())
{
m_usingProfiles = true;
loginProfile(loginName, password, email);
}
else
{
m_usingProfiles = false;
loginQuick(loginName);
}
}