/*
** 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. //
// //
////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#define WIN32_LEAN_AND_MEAN // only bare bones windows stuff wanted
#include "Common/CRC.h"
#include "Common/GameState.h"
#include "Common/Registry.h"
#include "GameNetwork/LANAPI.h"
#include "GameNetwork/NetworkUtil.h"
#include "Common/GlobalData.h"
#include "Common/RandomValue.h"
#include "GameClient/GameText.h"
#include "GameClient/MapUtil.h"
#include "Common/UserPreferences.h"
#include "GameLogic/GameLogic.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
static const UnsignedShort lobbyPort = 8086; ///< This is the UDP port used by all LANAPI communication
AsciiString GetMessageTypeString(UnsignedInt type);
const UnsignedInt LANAPI::s_resendDelta = 10 * 1000; ///< This is how often we announce ourselves to the world
/*
LANGame::LANGame( void )
{
m_gameName = L"";
int player;
for (player = 0; player < MAX_SLOTS; ++player)
{
m_playerName[player] = L"";
m_playerIP[player]= 0;
m_playerAccepted[player] = false;
}
m_lastHeard = 0;
m_inProgress = false;
m_next = NULL;
}
*/
LANAPI::LANAPI( void ) : m_transport(NULL)
{
DEBUG_LOG(("LANAPI::LANAPI() - max game option size is %d, sizeof(LANMessage)=%d, MAX_PACKET_SIZE=%d\n",
m_lanMaxOptionsLength, sizeof(LANMessage), MAX_PACKET_SIZE));
m_lastResendTime = 0;
//
m_lobbyPlayers = NULL;
m_games = NULL;
m_name = L""; // safe default?
m_pendingAction = ACT_NONE;
m_expiration = 0;
m_localIP = 0;
m_inLobby = true;
m_isInLANMenu = TRUE;
m_currentGame = NULL;
m_broadcastAddr = INADDR_BROADCAST;
m_directConnectRemoteIP = 0;
m_actionTimeout = 5000; // ms
m_lastUpdate = 0;
m_transport = new Transport;
m_isActive = TRUE;
}
LANAPI::~LANAPI( void )
{
reset();
if (m_transport)
delete m_transport;
}
void LANAPI::init( void )
{
m_gameStartTime = 0;
m_gameStartSeconds = 0;
m_transport->reset();
m_transport->init(m_localIP, lobbyPort);
m_transport->allowBroadcasts(true);
m_pendingAction = ACT_NONE;
m_expiration = 0;
m_inLobby = true;
m_isInLANMenu = TRUE;
m_currentGame = NULL;
m_directConnectRemoteIP = 0;
m_lastGameopt = "";
unsigned long bufSize = UNLEN + 1;
char userName[UNLEN + 1];
if (!GetUserName(userName, &bufSize))
{
strcpy(userName, "unknown");
}
m_userName = userName;
bufSize = MAX_COMPUTERNAME_LENGTH + 1;
char computerName[MAX_COMPUTERNAME_LENGTH + 1];
if (!GetComputerName(computerName, &bufSize))
{
strcpy(computerName, "unknown");
}
m_hostName = computerName;
}
void LANAPI::reset( void )
{
if (m_inLobby)
{
LANMessage msg;
fillInLANMessage( &msg );
msg.LANMessageType = LANMessage::MSG_REQUEST_LOBBY_LEAVE;
sendMessage(&msg);
}
m_transport->update();
LANGameInfo *theGame = m_games;
LANGameInfo *deletableGame = NULL;
while (theGame)
{
deletableGame = theGame;
theGame = theGame->getNext();
delete deletableGame;
}
LANPlayer *thePlayer = m_lobbyPlayers;
LANPlayer *deletablePlayer = NULL;
while (thePlayer)
{
deletablePlayer = thePlayer;
thePlayer = thePlayer->getNext();
delete deletablePlayer;
}
m_games = NULL;
m_lobbyPlayers = NULL;
m_directConnectRemoteIP = 0;
m_pendingAction = ACT_NONE;
m_expiration = 0;
m_inLobby = true;
m_isInLANMenu = TRUE;
m_currentGame = NULL;
}
void LANAPI::sendMessage(LANMessage *msg, UnsignedInt ip /* = 0 */)
{
if (ip != 0)
{
m_transport->queueSend(ip, lobbyPort, (unsigned char *)msg, sizeof(LANMessage) /*, 0, 0 */);
}
else if ((m_currentGame != NULL) && (m_currentGame->getIsDirectConnect()))
{
Int localSlot = m_currentGame->getLocalSlotNum();
for (Int i = 0; i < MAX_SLOTS; ++i)
{
if (i != localSlot) {
GameSlot *slot = m_currentGame->getSlot(i);
if ((slot != NULL) && (slot->isHuman())) {
m_transport->queueSend(slot->getIP(), lobbyPort, (unsigned char *)msg, sizeof(LANMessage) /*, 0, 0 */);
}
}
}
}
else
{
m_transport->queueSend(m_broadcastAddr, lobbyPort, (unsigned char *)msg, sizeof(LANMessage) /*, 0, 0 */);
}
}
AsciiString GetMessageTypeString(UnsignedInt type)
{
AsciiString returnString;
switch (type)
{
case LANMessage::MSG_REQUEST_LOCATIONS:
returnString.format( "Request Locations (%d)",type);
break;
case LANMessage::MSG_GAME_ANNOUNCE:
returnString.format("Game Announce (%d)",type);
break;
case LANMessage::MSG_LOBBY_ANNOUNCE:
returnString.format("Lobby Announce (%d)",type);
break;
case LANMessage::MSG_REQUEST_JOIN:
returnString.format("Request Join (%d)",type);
break;
case LANMessage::MSG_JOIN_ACCEPT:
returnString.format("Join Accept (%d)",type);
break;
case LANMessage::MSG_JOIN_DENY:
returnString.format("Join Deny (%d)",type);
break;
case LANMessage::MSG_REQUEST_GAME_LEAVE:
returnString.format("Request Game Leave (%d)",type);
break;
case LANMessage::MSG_REQUEST_LOBBY_LEAVE:
returnString.format("Request Lobby Leave (%d)",type);
break;
case LANMessage::MSG_SET_ACCEPT:
returnString.format("Set Accept(%d)",type);
break;
case LANMessage::MSG_CHAT:
returnString.format("Chat (%d)",type);
break;
case LANMessage::MSG_GAME_START:
returnString.format("Game Start (%d)",type);
break;
case LANMessage::MSG_GAME_START_TIMER:
returnString.format("Game Start Timer (%d)",type);
break;
case LANMessage::MSG_GAME_OPTIONS:
returnString.format("Game Options (%d)",type);
break;
case LANMessage::MSG_REQUEST_GAME_INFO:
returnString.format("Request GameInfo (%d)", type);
break;
case LANMessage::MSG_INACTIVE:
returnString.format("Inactive (%d)", type);
break;
default:
returnString.format("Unknown Message (%d)",type);
}
return returnString;
}
void LANAPI::checkMOTD( void )
{
#if defined(_DEBUG) || defined(_INTERNAL)
if (TheGlobalData->m_useLocalMOTD)
{
// for a playtest, let's log some play statistics, eh?
if (TheGlobalData->m_playStats <= 0)
TheWritableGlobalData->m_playStats = 30;
static UnsignedInt oldMOTDCRC = 0;
UnsignedInt newMOTDCRC = 0;
AsciiString asciiMOTD;
char buf[4096];
FILE *fp = fopen(TheGlobalData->m_MOTDPath.str(), "r");
Int len;
if (fp)
{
while( (len = fread(buf, 1, 4096, fp)) > 0 )
{
buf[len] = 0;
asciiMOTD.concat(buf);
}
fclose(fp);
CRC crcObj;
crcObj.computeCRC(asciiMOTD.str(), asciiMOTD.getLength());
newMOTDCRC = crcObj.get();
}
if (oldMOTDCRC != newMOTDCRC)
{
// different MOTD... display it
oldMOTDCRC = newMOTDCRC;
AsciiString line;
while (asciiMOTD.nextToken(&line, "\n"))
{
if (line.getCharAt(line.getLength()-1) == '\r')
line.removeLastChar(); // there is a trailing '\r'
if (line.isEmpty())
{
line = " ";
}
UnicodeString uniLine;
uniLine.translate(line);
OnChat( UnicodeString(L"MOTD"), 0, uniLine, LANCHAT_SYSTEM );
}
}
}
#endif
}
extern Bool LANbuttonPushed;
extern Bool LANSocketErrorDetected;
void LANAPI::update( void )
{
if(LANbuttonPushed)
return;
static const UnsignedInt LANAPIUpdateDelay = 200;
UnsignedInt now = timeGetTime();
if( now > m_lastUpdate + LANAPIUpdateDelay)
{
m_lastUpdate = now;
}
else
{
return;
}
// Let the UDP socket breathe
if ((m_transport->update() == FALSE) && (LANSocketErrorDetected == FALSE)) {
if (m_isInLANMenu == TRUE) {
LANSocketErrorDetected = TRUE;
}
}
// Handle any new messages
int i;
for (i=0; im_inBuffer[i].length > 0)
{
// Process the new message
UnsignedInt senderIP = m_transport->m_inBuffer[i].addr;
if (senderIP == m_localIP)
{
m_transport->m_inBuffer[i].length = 0;
continue;
}
LANMessage *msg = (LANMessage *)(m_transport->m_inBuffer[i].data);
//DEBUG_LOG(("LAN message type %s from %ls (%s@%s)\n", GetMessageTypeString(msg->LANMessageType).str(),
// msg->name, msg->userName, msg->hostName));
switch (msg->LANMessageType)
{
// Location specification
case LANMessage::MSG_REQUEST_LOCATIONS: // Hey, where is everybody?
// DEBUG_LOG(("LANAPI::update - got a MSG_REQUEST_LOCATIONS from 0x%08x\n", senderIP));
handleRequestLocations( msg, senderIP );
break;
case LANMessage::MSG_GAME_ANNOUNCE: // Here someone is, and here's his game info!
// DEBUG_LOG(("LANAPI::update - got a MSG_GAME_ANNOUNCE from 0x%08x\n", senderIP));
handleGameAnnounce( msg, senderIP );
break;
case LANMessage::MSG_LOBBY_ANNOUNCE: // Hey, I'm in the lobby!
// DEBUG_LOG(("LANAPI::update - got a MSG_LOBBY_ANNOUNCE from 0x%08x\n", senderIP));
handleLobbyAnnounce( msg, senderIP );
break;
case LANMessage::MSG_REQUEST_GAME_INFO:
// DEBUG_LOG(("LANAPI::update - got a MSG_REQUEST_GAME_INFO from 0x%08x\n", senderIP));
handleRequestGameInfo( msg, senderIP );
break;
// Joining games
case LANMessage::MSG_REQUEST_JOIN: // Let me in! Let me in!
// DEBUG_LOG(("LANAPI::update - got a MSG_REQUEST_JOIN from 0x%08x\n", senderIP));
handleRequestJoin( msg, senderIP );
break;
case LANMessage::MSG_JOIN_ACCEPT: // Okay, you can join.
// DEBUG_LOG(("LANAPI::update - got a MSG_JOIN_ACCEPT from 0x%08x\n", senderIP));
handleJoinAccept( msg, senderIP );
break;
case LANMessage::MSG_JOIN_DENY: // Go away! We don't want any!
// DEBUG_LOG(("LANAPI::update - got a MSG_JOIN_DENY from 0x%08x\n", senderIP));
handleJoinDeny( msg, senderIP );
break;
// Leaving games, lobby
case LANMessage::MSG_REQUEST_GAME_LEAVE: // I'm outa here!
// DEBUG_LOG(("LANAPI::update - got a MSG_REQUEST_GAME_LEAVE from 0x%08x\n", senderIP));
handleRequestGameLeave( msg, senderIP );
break;
case LANMessage::MSG_REQUEST_LOBBY_LEAVE: // I'm outa here!
// DEBUG_LOG(("LANAPI::update - got a MSG_REQUEST_LOBBY_LEAVE from 0x%08x\n", senderIP));
handleRequestLobbyLeave( msg, senderIP );
break;
// Game options, chat, etc
case LANMessage::MSG_SET_ACCEPT: // I'm cool with everything as is.
handleSetAccept( msg, senderIP );
break;
case LANMessage::MSG_MAP_AVAILABILITY: // Map status
handleHasMap( msg, senderIP );
break;
case LANMessage::MSG_CHAT: // Just spouting my mouth off.
handleChat( msg, senderIP );
break;
case LANMessage::MSG_GAME_START: // Hold on; we're starting!
handleGameStart( msg, senderIP );
break;
case LANMessage::MSG_GAME_START_TIMER:
handleGameStartTimer( msg, senderIP );
break;
case LANMessage::MSG_GAME_OPTIONS: // Here's some info about the game.
// DEBUG_LOG(("LANAPI::update - got a MSG_GAME_OPTIONS from 0x%08x\n", senderIP));
handleGameOptions( msg, senderIP );
break;
case LANMessage::MSG_INACTIVE: // someone is telling us that we're inactive.
handleInActive( msg, senderIP );
break;
default:
DEBUG_LOG(("Unknown LAN message type %d\n", msg->LANMessageType));
}
// Mark it as read
m_transport->m_inBuffer[i].length = 0;
}
} // End message handling
if(LANbuttonPushed)
return;
// Send out periodic I'm Here messages
if (now > s_resendDelta + m_lastResendTime)
{
m_lastResendTime = now;
if (m_inLobby)
{
RequestSetName(m_name);
}
else if (m_currentGame && !m_currentGame->isGameInProgress())
{
if (AmIHost())
{
RequestGameOptions( GenerateGameOptionsString(), true );
RequestGameAnnounce( );
}
else
{
AsciiString text;
text.format("User=%s", m_userName.str());
RequestGameOptions( text, true );
text.format("Host=%s", m_hostName.str());
RequestGameOptions( text, true );
RequestGameOptions( "HELLO", false );
}
}
else if (m_currentGame)
{
// game is in progress - RequestGameAnnounce will check if we should send it
RequestGameAnnounce();
}
}
Bool playerListChanged = false;
Bool gameListChanged = false;
// Weed out people we haven't heard from in a while
LANPlayer *player = m_lobbyPlayers;
while (player)
{
if (player->getLastHeard() + s_resendDelta*2 < now)
{
// He's gone!
removePlayer(player);
LANPlayer *nextPlayer = player->getNext();
delete player;
player = nextPlayer;
playerListChanged = true;
}
else
{
player = player->getNext();
}
}
// Weed out people we haven't heard from in a while
LANGameInfo *game = m_games;
while (game)
{
if (game != m_currentGame && game->getLastHeard() + s_resendDelta*2 < now)
{
// He's gone!
removeGame(game);
LANGameInfo *nextGame = game->getNext();
delete game;
game = nextGame;
gameListChanged = true;
}
else
{
game = game->getNext();
}
}
if ( m_currentGame && !m_currentGame->isGameInProgress() )
{
if ( !AmIHost() && (m_currentGame->getLastHeard() + s_resendDelta*16 < now) )
{
// We haven't heard from the host in a while. Bail.
// Actually, fake a host leaving message. :)
LANMessage msg;
fillInLANMessage( &msg );
msg.LANMessageType = LANMessage::MSG_REQUEST_GAME_LEAVE;
wcsncpy(msg.name, m_currentGame->getPlayerName(0).str(), g_lanPlayerNameLength);
msg.name[g_lanPlayerNameLength] = 0;
handleRequestGameLeave(&msg, m_currentGame->getIP(0));
UnicodeString text;
text = TheGameText->fetch("LAN:HostNotResponding");
OnChat(UnicodeString::TheEmptyString, m_localIP, text, LANCHAT_SYSTEM);
}
else if ( AmIHost() )
{
// Check each player for timeouts
for (int p=1; pgetIP(p) && m_currentGame->getPlayerLastHeard(p) + s_resendDelta*8 < now)
{
LANMessage msg;
fillInLANMessage( &msg );
UnicodeString theStr;
theStr.format(TheGameText->fetch("LAN:PlayerDropped"), m_currentGame->getPlayerName(p).str());
msg.LANMessageType = LANMessage::MSG_REQUEST_GAME_LEAVE;
wcsncpy(msg.name, m_currentGame->getPlayerName(p).str(), g_lanPlayerNameLength);
msg.name[g_lanPlayerNameLength] = 0;
handleRequestGameLeave(&msg, m_currentGame->getIP(p));
OnChat(UnicodeString::TheEmptyString, m_localIP, theStr, LANCHAT_SYSTEM);
}
}
}
}
if (playerListChanged)
{
OnPlayerList(m_lobbyPlayers);
}
if (gameListChanged)
{
OnGameList(m_games);
}
// Time out old actions
if (m_pendingAction != ACT_NONE && now > m_expiration)
{
switch (m_pendingAction)
{
case ACT_JOIN:
OnGameJoin(RET_TIMEOUT, NULL);
m_pendingAction = ACT_NONE;
m_currentGame = NULL;
m_inLobby = true;
break;
case ACT_LEAVE:
OnPlayerLeave(m_name);
m_pendingAction = ACT_NONE;
m_currentGame = NULL;
m_inLobby = true;
break;
case ACT_JOINDIRECTCONNECT:
OnGameJoin(RET_TIMEOUT, NULL);
m_pendingAction = ACT_NONE;
m_currentGame = NULL;
m_inLobby = true;
break;
default:
m_pendingAction = ACT_NONE;
}
}
// send out "game starting" messages
if ( m_gameStartTime && m_gameStartSeconds && m_gameStartTime <= now )
{
// m_gameStartTime is when the next message goes out
// m_gameStartSeconds is how many seconds remain in the message
RequestGameStartTimer( m_gameStartSeconds );
}
else if (m_gameStartTime && m_gameStartTime <= now)
{
// DEBUG_LOG(("m_gameStartTime=%d, now=%d, m_gameStartSeconds=%d\n", m_gameStartTime, now, m_gameStartSeconds));
ResetGameStartTimer();
RequestGameStart();
}
// Check for an MOTD every few seconds
static UnsignedInt lastMOTDCheck = 0;
static const UnsignedInt motdInterval = 30000;
if (now > lastMOTDCheck + motdInterval)
{
checkMOTD();
lastMOTDCheck = now;
}
}
// Request functions generate network traffic
void LANAPI::RequestLocations( void )
{
LANMessage msg;
msg.LANMessageType = LANMessage::MSG_REQUEST_LOCATIONS;
fillInLANMessage( &msg );
sendMessage(&msg);
}
void LANAPI::RequestGameJoin( LANGameInfo *game, UnsignedInt ip /* = 0 */ )
{
if ((m_pendingAction != ACT_NONE) && (m_pendingAction != ACT_JOINDIRECTCONNECT))
{
OnGameJoin( RET_BUSY, NULL );
return;
}
if (!game)
{
OnGameJoin( RET_GAME_GONE, NULL );
return;
}
LANMessage msg;
msg.LANMessageType = LANMessage::MSG_REQUEST_JOIN;
fillInLANMessage( &msg );
msg.GameToJoin.gameIP = game->getSlot(0)->getIP();
msg.GameToJoin.exeCRC = TheGlobalData->m_exeCRC;
msg.GameToJoin.iniCRC = TheGlobalData->m_iniCRC;
AsciiString s = "";
GetStringFromRegistry("\\ergc", "", s);
strncpy(msg.GameToJoin.serial, s.str(), g_maxSerialLength);
msg.GameToJoin.serial[g_maxSerialLength-1] = '\0';
sendMessage(&msg, ip);
m_pendingAction = ACT_JOIN;
m_expiration = timeGetTime() + m_actionTimeout;
}
void LANAPI::RequestGameJoinDirectConnect(UnsignedInt ipaddress)
{
if (m_pendingAction != ACT_NONE)
{
OnGameJoin( RET_BUSY, NULL );
return;
}
if (ipaddress == 0)
{
OnGameJoin( RET_GAME_GONE, NULL );
return;
}
m_directConnectRemoteIP = ipaddress;
LANMessage msg;
msg.LANMessageType = LANMessage::MSG_REQUEST_GAME_INFO;
fillInLANMessage(&msg);
msg.PlayerInfo.ip = GetLocalIP();
wcsncpy(msg.PlayerInfo.playerName, m_name.str(), m_name.getLength());
msg.PlayerInfo.playerName[m_name.getLength()] = 0;
sendMessage(&msg, ipaddress);
m_pendingAction = ACT_JOINDIRECTCONNECT;
m_expiration = timeGetTime() + m_actionTimeout;
}
void LANAPI::RequestGameLeave( void )
{
LANMessage msg;
msg.LANMessageType = LANMessage::MSG_REQUEST_GAME_LEAVE;
fillInLANMessage( &msg );
wcsncpy(msg.GameToLeave.gameName, (m_currentGame)?m_currentGame->getName().str():L"", g_lanGameNameLength);
msg.GameToLeave.gameName[g_lanGameNameLength] = 0;
sendMessage(&msg);
m_transport->update(); // Send immediately, before OnPlayerLeave below resets everything.
if (m_currentGame && m_currentGame->getIP(0) == m_localIP)
{
// Exit out immediately if we're hosting
OnPlayerLeave(m_name);
removeGame(m_currentGame);
m_currentGame = NULL;
m_inLobby = true;
}
else
{
m_pendingAction = ACT_LEAVE;
m_expiration = timeGetTime() + m_actionTimeout;
}
}
void LANAPI::RequestGameAnnounce( void )
{
// In game - are we a game host?
if (m_currentGame && !(m_currentGame->getIsDirectConnect()))
{
if (m_currentGame->getIP(0) == m_localIP || (m_currentGame->isGameInProgress() && TheNetwork && TheNetwork->isPacketRouter())) // if we're in game we should reply if we're the packet router
{
LANMessage reply;
fillInLANMessage( &reply );
reply.LANMessageType = LANMessage::MSG_GAME_ANNOUNCE;
AsciiString gameOpts = GameInfoToAsciiString(m_currentGame);
strncpy(reply.GameInfo.options,gameOpts.str(),m_lanMaxOptionsLength);
wcsncpy(reply.GameInfo.gameName, m_currentGame->getName().str(), g_lanGameNameLength);
reply.GameInfo.gameName[g_lanGameNameLength] = 0;
reply.GameInfo.inProgress = m_currentGame->isGameInProgress();
reply.GameInfo.isDirectConnect = m_currentGame->getIsDirectConnect();
sendMessage(&reply);
}
}
}
void LANAPI::RequestAccept( void )
{
if (m_inLobby || !m_currentGame)
return;
LANMessage msg;
fillInLANMessage( &msg );
msg.LANMessageType = LANMessage::MSG_SET_ACCEPT;
msg.Accept.isAccepted = true;
wcsncpy(msg.Accept.gameName, m_currentGame->getName().str(), g_lanGameNameLength);
msg.Accept.gameName[g_lanGameNameLength] = 0;
sendMessage(&msg);
}
void LANAPI::RequestHasMap( void )
{
if (m_inLobby || !m_currentGame)
return;
LANMessage msg;
fillInLANMessage( &msg );
msg.LANMessageType = LANMessage::MSG_MAP_AVAILABILITY;
msg.MapStatus.hasMap = m_currentGame->getSlot(m_currentGame->getLocalSlotNum())->hasMap();
wcsncpy(msg.MapStatus.gameName, m_currentGame->getName().str(), g_lanGameNameLength);
msg.MapStatus.gameName[g_lanGameNameLength] = 0;
CRC mapNameCRC;
//mapNameCRC.computeCRC(m_currentGame->getMap().str(), m_currentGame->getMap().getLength());
AsciiString portableMapName = TheGameState->realMapPathToPortableMapPath(m_currentGame->getMap());
mapNameCRC.computeCRC(portableMapName.str(), portableMapName.getLength());
msg.MapStatus.mapCRC = mapNameCRC.get();
sendMessage(&msg);
if (!msg.MapStatus.hasMap)
{
UnicodeString text;
UnicodeString mapDisplayName;
const MapMetaData *mapData = TheMapCache->findMap( m_currentGame->getMap() );
Bool willTransfer = TRUE;
if (mapData)
{
mapDisplayName.format(L"%ls", mapData->m_displayName.str());
if (mapData->m_isOfficial)
willTransfer = FALSE;
}
else
{
mapDisplayName.format(L"%hs", TheGameState->getMapLeafName(m_currentGame->getMap()).str());
willTransfer = WouldMapTransfer(m_currentGame->getMap());
}
if (willTransfer)
text.format(TheGameText->fetch("GUI:LocalPlayerNoMapWillTransfer"), mapDisplayName.str());
else
text.format(TheGameText->fetch("GUI:LocalPlayerNoMap"), mapDisplayName.str());
OnChat(UnicodeString(L"SYSTEM"), m_localIP, text, LANCHAT_SYSTEM);
}
}
void LANAPI::RequestChat( UnicodeString message, ChatType format )
{
LANMessage msg;
fillInLANMessage( &msg );
wcsncpy(msg.Chat.gameName, (m_currentGame)?m_currentGame->getName().str():L"", g_lanGameNameLength);
msg.Chat.gameName[g_lanGameNameLength] = 0;
msg.LANMessageType = LANMessage::MSG_CHAT;
msg.Chat.chatType = format;
wcsncpy(msg.Chat.message, message.str(), g_lanMaxChatLength);
msg.Chat.message[g_lanMaxChatLength] = 0;
sendMessage(&msg);
OnChat(m_name, m_localIP, message, format);
}
void LANAPI::RequestGameStart( void )
{
if (m_inLobby || !m_currentGame || m_currentGame->getIP(0) != m_localIP)
return;
LANMessage msg;
msg.LANMessageType = LANMessage::MSG_GAME_START;
fillInLANMessage( &msg );
sendMessage(&msg);
m_transport->update(); // force a send
OnGameStart();
}
void LANAPI::ResetGameStartTimer( void )
{
m_gameStartTime = 0;
m_gameStartSeconds = 0;
}
void LANAPI::RequestGameStartTimer( Int seconds )
{
if (m_inLobby || !m_currentGame || m_currentGame->getIP(0) != m_localIP)
return;
UnsignedInt now = timeGetTime();
m_gameStartTime = now + 1000;
m_gameStartSeconds = (seconds) ? seconds - 1 : 0;
LANMessage msg;
msg.LANMessageType = LANMessage::MSG_GAME_START_TIMER;
msg.StartTimer.seconds = seconds;
fillInLANMessage( &msg );
sendMessage(&msg);
m_transport->update(); // force a send
OnGameStartTimer(seconds);
}
void LANAPI::RequestGameOptions( AsciiString gameOptions, Bool isPublic, UnsignedInt ip /* = 0 */ )
{
DEBUG_ASSERTCRASH(gameOptions.getLength() < m_lanMaxOptionsLength, ("Game options string is too long!"));
if (!m_currentGame)
return;
LANMessage msg;
fillInLANMessage( &msg );
msg.LANMessageType = LANMessage::MSG_GAME_OPTIONS;
strncpy(msg.GameOptions.options, gameOptions.str(), m_lanMaxOptionsLength);
msg.GameOptions.options[m_lanMaxOptionsLength] = 0;
sendMessage(&msg, ip);
m_lastGameopt = gameOptions;
int player;
for (player = 0; playergetIP(player) == m_localIP)
{
OnGameOptions(m_localIP, player, AsciiString(msg.GameOptions.options));
break;
}
}
// We can request game options (side, color, etc) while we don't have a slot yet. Of course, we don't need to
// call OnGameOptions for those, so it's okay to silently fail.
//DEBUG_ASSERTCRASH(player != MAX_SLOTS, ("Requested game options, but we're not in slot list!");
}
void LANAPI::RequestGameCreate( UnicodeString gameName, Bool isDirectConnect )
{
// No games of the same name should exist... Ignore that for now.
/// @todo: make sure LAN games with identical names don't crash things like in RA2.
if ((!m_inLobby || m_currentGame) && !isDirectConnect)
{
DEBUG_ASSERTCRASH(m_inLobby && m_currentGame, ("Can't create a game while in one!"));
OnGameCreate(LANAPIInterface::RET_BUSY);
return;
}
if (m_pendingAction != ACT_NONE)
{
OnGameCreate(LANAPIInterface::RET_BUSY);
return;
}
// Create the local game object
m_inLobby = false;
LANGameInfo *myGame = NEW LANGameInfo;
myGame->setSeed(GetTickCount());
// myGame->setInProgress(false);
myGame->enterGame();
UnicodeString s;
s.format(L"%8.8X%8.8X", m_localIP, myGame->getSeed());
if (gameName.isEmpty())
s.concat(m_name);
else
s.concat(gameName);
while (s.getLength() > g_lanGameNameLength)
s.removeLastChar();
DEBUG_LOG(("Setting local game name to '%ls'\n", s.str()));
myGame->setName(s);
LANGameSlot newSlot;
newSlot.setState(SLOT_PLAYER, m_name);
newSlot.setIP(m_localIP);
newSlot.setPort(NETWORK_BASE_PORT_NUMBER); // LAN game, everyone has a unique IP, so it's ok to use the same port.
newSlot.setLastHeard(0);
newSlot.setLogin(m_userName);
newSlot.setHost(m_hostName);
myGame->setSlot(0,newSlot);
myGame->setNext(NULL);
LANPreferences pref;
AsciiString mapName = pref.getPreferredMap();
myGame->setMap(mapName);
myGame->setIsDirectConnect(isDirectConnect);
myGame->setLastHeard(timeGetTime());
m_currentGame = myGame;
/// @todo: Need to initialize the players elsewere.
/* for (int player = 1; player < MAX_SLOTS; ++player)
{
myGame->setPlayerName(player, UnicodeString(L""));
myGame->setIP(player, 0);
myGame->setAccepted(player, false);
}*/
// Add the game to the local game list
addGame(myGame);
// Send an announcement
//RequestSlotList();
/*
LANMessage msg;
wcsncpy(msg.name, m_name.str(), g_lanPlayerNameLength);
msg.name[g_lanPlayerNameLength] = 0;
wcscpy(msg.GameInfo.gameName, myGame->getName().str());
for (player=0; playergetPlayerName(player).str());
msg.GameInfo.ip[player] = myGame->getIP(player);
msg.GameInfo.playerAccepted[player] = myGame->getAccepted(player);
}
msg.LANMessageType = LANMessage::MSG_GAME_ANNOUNCE;
*/
OnGameCreate(LANAPIInterface::RET_OK);
}
/*static const char slotListID = 'S';
static const char gameOptionsID = 'G';
static const char acceptID = 'A';
static const char wannaStartID = 'W';
AsciiString LANAPI::createSlotString( void )
{
AsciiString slotList;
slotList.concat(slotListID);
for (int i=0; igetLANSlot(i);
AsciiString str;
if (slot->isHuman())
{
str = "H";
LANPlayer *user = slot->getUser();
DEBUG_ASSERTCRASH(user, ("Human player has no User*!"));
AsciiString name;
name.translate(user->getName());
str.concat(name);
str.concat(',');
}
else if (slot->isAI())
{
if (slot->getState() == SLOT_EASY_AI)
str = "CE,";
if (slot->getState() == SLOT_MED_AI)
str = "CM,";
else
str = "CB,";
}
else if (slot->getState() == SLOT_OPEN)
{
str = "O,";
}
else if (slot->getState() == SLOT_CLOSED)
{
str = "X,";
}
else
{
DEBUG_ASSERTCRASH(false, ("Bad slot type"));
str = "X,";
}
slotList.concat(str);
}
return slotList;
}
*/
/*
void LANAPI::RequestSlotList( void )
{
LANMessage reply;
reply.LANMessageType = LANMessage::MSG_GAME_ANNOUNCE;
wcsncpy(reply.name, m_name.str(), g_lanPlayerNameLength);
reply.name[g_lanPlayerNameLength] = 0;
int player;
for (player = 0; player < MAX_SLOTS; ++player)
{
wcsncpy(reply.GameInfo.name[player], m_currentGame->getPlayerName(player).str(), g_lanPlayerNameLength);
reply.GameInfo.name[player][g_lanPlayerNameLength] = 0;
reply.GameInfo.ip[player] = m_currentGame->getIP(player);
reply.GameInfo.playerAccepted[player] = m_currentGame->getSlot(player)->isAccepted();
}
wcsncpy(reply.GameInfo.gameName, m_currentGame->getName().str(), g_lanGameNameLength);
reply.GameInfo.gameName[g_lanGameNameLength] = 0;
reply.GameInfo.inProgress = m_currentGame->isGameInProgress();
sendMessage(&reply);
OnSlotList(LANAPIInterface::RET_OK, m_currentGame);
} // void LANAPI::RequestSlotList( void )
*/
void LANAPI::RequestSetName( UnicodeString newName )
{
newName.trim();
if (m_pendingAction != ACT_NONE)
{
// Can't change name while joining games
OnNameChange(m_localIP, newName);
return;
}
// Set up timer
m_lastResendTime = timeGetTime();
if (m_inLobby && m_pendingAction == ACT_NONE)
{
m_name = newName;
LANMessage msg;
fillInLANMessage( &msg );
msg.LANMessageType = LANMessage::MSG_LOBBY_ANNOUNCE;
sendMessage(&msg);
// Update the interface
LANPlayer *player = LookupPlayer(m_localIP);
if (!player)
{
player = NEW LANPlayer;
player->setIP(m_localIP);
}
else
{
removePlayer(player);
}
player->setName(m_name);
player->setHost(m_hostName);
player->setLogin(m_userName);
player->setLastHeard(timeGetTime());
addPlayer(player);
OnNameChange(player->getIP(), player->getName());
}
}
void LANAPI::fillInLANMessage( LANMessage *msg )
{
if (!msg)
return;
wcsncpy(msg->name, m_name.str(), g_lanPlayerNameLength);
msg->name[g_lanPlayerNameLength] = 0;
strncpy(msg->userName, m_userName.str(), g_lanLoginNameLength);
msg->userName[g_lanLoginNameLength] = 0;
strncpy(msg->hostName, m_hostName.str(), g_lanHostNameLength);
msg->hostName[g_lanHostNameLength] = 0;
}
void LANAPI::RequestLobbyLeave( Bool forced )
{
LANMessage msg;
msg.LANMessageType = LANMessage::MSG_REQUEST_LOBBY_LEAVE;
fillInLANMessage( &msg );
sendMessage(&msg);
if (forced)
m_transport->update();
}
// Misc utility functions
LANGameInfo * LANAPI::LookupGame( UnicodeString gameName )
{
LANGameInfo *theGame = m_games;
while (theGame && theGame->getName() != gameName)
{
theGame = theGame->getNext();
}
return theGame; // NULL means we didn't find anything.
}
LANGameInfo * LANAPI::LookupGameByListOffset( Int offset )
{
LANGameInfo *theGame = m_games;
if (offset < 0)
return NULL;
while (offset-- && theGame)
{
theGame = theGame->getNext();
}
return theGame; // NULL means we didn't find anything.
}
void LANAPI::removeGame( LANGameInfo *game )
{
LANGameInfo *g = m_games;
if (!game)
{
return;
}
else if (m_games == game)
{
m_games = m_games->getNext();
}
else
{
while (g->getNext() && g->getNext() != game)
{
g = g->getNext();
}
if (g->getNext() == game)
{
g->setNext(game->getNext());
}
else
{
// Odd. We went the whole way without finding it in the list.
DEBUG_ASSERTCRASH(false, ("LANGameInfo wasn't in the list"));
}
}
}
LANPlayer * LANAPI::LookupPlayer( UnsignedInt playerIP )
{
LANPlayer *thePlayer = m_lobbyPlayers;
while (thePlayer && thePlayer->getIP() != playerIP)
{
thePlayer = thePlayer->getNext();
}
return thePlayer; // NULL means we didn't find anything.
}
void LANAPI::removePlayer( LANPlayer *player )
{
LANPlayer *p = m_lobbyPlayers;
if (!player)
{
return;
}
else if (m_lobbyPlayers == player)
{
m_lobbyPlayers = m_lobbyPlayers->getNext();
}
else
{
while (p->getNext() && p->getNext() != player)
{
p = p->getNext();
}
if (p->getNext() == player)
{
p->setNext(player->getNext());
}
else
{
// Odd. We went the whole way without finding it in the list.
DEBUG_ASSERTCRASH(false, ("LANPlayer wasn't in the list"));
}
}
}
void LANAPI::addGame( LANGameInfo *game )
{
if (!m_games)
{
m_games = game;
game->setNext(NULL);
return;
}
else
{
if (game->getName().compareNoCase(m_games->getName()) < 0)
{
game->setNext(m_games);
m_games = game;
return;
}
else
{
LANGameInfo *g = m_games;
while (g->getNext() && g->getNext()->getName().compareNoCase(game->getName()) > 0)
{
g = g->getNext();
}
game->setNext(g->getNext());
g->setNext(game);
return;
}
}
}
void LANAPI::addPlayer( LANPlayer *player )
{
if (!m_lobbyPlayers)
{
m_lobbyPlayers = player;
player->setNext(NULL);
return;
}
else
{
if (player->getName().compareNoCase(m_lobbyPlayers->getName()) < 0)
{
player->setNext(m_lobbyPlayers);
m_lobbyPlayers = player;
return;
}
else
{
LANPlayer *p = m_lobbyPlayers;
while (p->getNext() && p->getNext()->getName().compareNoCase(player->getName()) > 0)
{
p = p->getNext();
}
player->setNext(p->getNext());
p->setNext(player);
return;
}
}
}
Bool LANAPI::SetLocalIP( UnsignedInt localIP )
{
Bool retval = TRUE;
m_localIP = localIP;
m_transport->reset();
retval = m_transport->init(m_localIP, lobbyPort);
m_transport->allowBroadcasts(true);
return retval;
}
void LANAPI::SetLocalIP( AsciiString localIP )
{
UnsignedInt resolvedIP = ResolveIP(localIP);
SetLocalIP(resolvedIP);
}
Bool LANAPI::AmIHost( void )
{
return m_currentGame && m_currentGame->getIP(0) == m_localIP;
}
void LANAPI::setIsActive(Bool isActive) {
DEBUG_LOG(("LANAPI::setIsActive - entering\n"));
if (isActive != m_isActive) {
DEBUG_LOG(("LANAPI::setIsActive - m_isActive changed to %s\n", isActive ? "TRUE" : "FALSE"));
if (isActive == FALSE) {
if ((m_inLobby == FALSE) && (m_currentGame != NULL)) {
LANMessage msg;
fillInLANMessage( &msg );
msg.LANMessageType = LANMessage::MSG_INACTIVE;
sendMessage(&msg);
DEBUG_LOG(("LANAPI::setIsActive - sent an IsActive message\n"));
}
}
}
m_isActive = isActive;
}