diff --git a/Core/GameEngine/CMakeLists.txt b/Core/GameEngine/CMakeLists.txt
index 0f36ff63383..66c5666acfe 100644
--- a/Core/GameEngine/CMakeLists.txt
+++ b/Core/GameEngine/CMakeLists.txt
@@ -547,6 +547,7 @@ set(GAMEENGINE_SRC
Include/GameNetwork/NetCommandWrapperList.h
Include/GameNetwork/NetPacket.h
Include/GameNetwork/NetPacketStructs.h
+ Include/GameNetwork/NetworkAutoStart.h
Include/GameNetwork/NetworkDefs.h
Include/GameNetwork/NetworkInterface.h
Include/GameNetwork/networkutil.h
@@ -1141,6 +1142,7 @@ set(GAMEENGINE_SRC
Source/GameNetwork/NetPacket.cpp
Source/GameNetwork/NetPacketStructs.cpp
Source/GameNetwork/Network.cpp
+ Source/GameNetwork/NetworkAutoStart.cpp
Source/GameNetwork/NetworkUtil.cpp
Source/GameNetwork/Transport.cpp
Source/GameNetwork/udp.cpp
diff --git a/Core/GameEngine/Include/GameNetwork/LANAPICallbacks.h b/Core/GameEngine/Include/GameNetwork/LANAPICallbacks.h
index fdc5212253a..88434cc22e1 100644
--- a/Core/GameEngine/Include/GameNetwork/LANAPICallbacks.h
+++ b/Core/GameEngine/Include/GameNetwork/LANAPICallbacks.h
@@ -73,6 +73,7 @@ extern const Color acceptFalseColor;
void lanUpdateSlotList();
void updateGameOptions();
void setLANPlayerTooltip(LANPlayer* player);
+void StartLANGame();
//Enum is used for the utility function so other windows do not need
//to know about controls on LanGameOptions window.
diff --git a/Core/GameEngine/Include/GameNetwork/NetworkAutoStart.h b/Core/GameEngine/Include/GameNetwork/NetworkAutoStart.h
new file mode 100644
index 00000000000..28bfd9f433d
--- /dev/null
+++ b/Core/GameEngine/Include/GameNetwork/NetworkAutoStart.h
@@ -0,0 +1,78 @@
+/*
+** Command & Conquer Generals Zero Hour(tm)
+** Copyright 2026 TheSuperHackers
+**
+** 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 .
+*/
+
+#pragma once
+
+#if defined(RTS_DEBUG)
+
+#include "Common/AsciiString.h"
+#include "Common/UnicodeString.h"
+#include "GameNetwork/LANAPI.h"
+
+// TheSuperHackers @feature bobtista 10/08/2026 Automate network match startup
+// for multi-instance testing.
+class NetworkAutoStart
+{
+public:
+ enum { MIN_EXPECTED_PLAYERS = 1 };
+
+ enum Mode
+ {
+ MODE_NONE,
+ MODE_DIRECT_CONNECT,
+ };
+
+ enum Role
+ {
+ ROLE_NONE,
+ ROLE_HOST,
+ ROLE_JOIN,
+ };
+
+ static Bool setMode(AsciiString mode);
+ static Bool setHost(Int expectedPlayers);
+ static Bool setJoin(AsciiString hostAddress);
+ static Bool setLocalAddress(AsciiString localAddress);
+ static Bool setPlayerName(AsciiString playerName);
+ static Bool setMapName(AsciiString mapName);
+ static Bool setTimeoutSeconds(Int seconds);
+
+ static Bool hasArguments();
+ static Bool isEnabled();
+ static Bool shouldOpenDirectConnect();
+ static void markDirectConnectOpened();
+
+ static AsciiString getMapName();
+ static UnsignedInt getLocalAddress();
+ static UnicodeString getPlayerName();
+
+ static void updateDirectConnect();
+ static void updateGameOptions();
+ static void onGameCreate(LANAPIInterface::ReturnType result);
+ static void onGameJoin(LANAPIInterface::ReturnType result);
+ static void onLocalAddressSet(Bool result);
+ static void onGameStartFailure();
+ static void onGameStart();
+
+private:
+ static Bool validateConfiguration();
+ static Bool checkTimeout();
+ static void fail(const char *message);
+};
+
+#endif
diff --git a/Core/GameEngine/Source/Common/CommandLine.cpp b/Core/GameEngine/Source/Common/CommandLine.cpp
index 772830f0f67..bbbc4ffc4af 100644
--- a/Core/GameEngine/Source/Common/CommandLine.cpp
+++ b/Core/GameEngine/Source/Common/CommandLine.cpp
@@ -25,6 +25,8 @@
#include "PreRTS.h" // This must go first in EVERY cpp file in the GameEngine
+#include
+
#include "Common/ArchiveFileSystem.h"
#include "Common/CommandLine.h"
#include "Common/CRCDebug.h"
@@ -35,6 +37,7 @@
#include "GameClient/TerrainVisual.h" // for TERRAIN_LOD_MIN definition
#include "GameClient/GameText.h"
#include "GameNetwork/NetworkDefs.h"
+#include "GameNetwork/NetworkAutoStart.h"
#include "WWLib/trim.h"
@@ -484,6 +487,101 @@ Int parseYRes(char *args[], int num)
}
#if defined(RTS_DEBUG)
+static Bool parsePositiveInt(const char *text, Int &result)
+{
+ if (text == nullptr || *text < '0' || *text > '9')
+ return false;
+
+ UnsignedInt value = 0;
+ do
+ {
+ const UnsignedInt digit = *text - '0';
+ if (value > ((UnsignedInt)INT_MAX - digit) / 10u)
+ return false;
+ value = value * 10u + digit;
+ ++text;
+ } while (*text >= '0' && *text <= '9');
+
+ if (*text != '\0')
+ return false;
+
+ result = (Int)value;
+ return true;
+}
+
+Int parseAutoNetworkMode(char *args[], int num)
+{
+ if (num > 1 && NetworkAutoStart::setMode(args[1]))
+ return 2;
+
+ printf("Invalid -autoNetworkMode. Supported value: direct\n");
+ exit(1);
+ return 1;
+}
+
+Int parseAutoNetworkHost(char *args[], int num)
+{
+ Int expectedPlayers = 0;
+ if (num > 1 && parsePositiveInt(args[1], expectedPlayers) && NetworkAutoStart::setHost(expectedPlayers))
+ return 2;
+
+ printf("Invalid -autoNetworkHost. Pass an expected player count from %d to %d and do not combine it with -autoNetworkJoin.\n",
+ NetworkAutoStart::MIN_EXPECTED_PLAYERS, MAX_SLOTS);
+ exit(1);
+ return 1;
+}
+
+Int parseAutoNetworkJoin(char *args[], int num)
+{
+ if (num > 1 && NetworkAutoStart::setJoin(args[1]))
+ return 2;
+
+ printf("Invalid -autoNetworkJoin. Pass a dotted IPv4 host address and do not combine it with -autoNetworkHost.\n");
+ exit(1);
+ return 1;
+}
+
+Int parseAutoNetworkLocalAddress(char *args[], int num)
+{
+ if (num > 1 && NetworkAutoStart::setLocalAddress(args[1]))
+ return 2;
+
+ printf("Invalid -autoNetworkLocalAddress. Pass a dotted IPv4 local address.\n");
+ exit(1);
+ return 1;
+}
+
+Int parseAutoNetworkName(char *args[], int num)
+{
+ if (num > 1 && NetworkAutoStart::setPlayerName(args[1]))
+ return 2;
+
+ printf("Invalid -autoNetworkName. Pass a non-empty player name.\n");
+ exit(1);
+ return 1;
+}
+
+Int parseAutoNetworkMap(char *args[], int num)
+{
+ if (num > 1 && NetworkAutoStart::setMapName(args[1]))
+ return 2;
+
+ printf("Invalid -autoNetworkMap. Pass a non-empty map path.\n");
+ exit(1);
+ return 1;
+}
+
+Int parseAutoNetworkTimeout(char *args[], int num)
+{
+ Int timeoutSeconds = 0;
+ if (num > 1 && parsePositiveInt(args[1], timeoutSeconds) && NetworkAutoStart::setTimeoutSeconds(timeoutSeconds))
+ return 2;
+
+ printf("Invalid -autoNetworkTimeout. Pass a positive number of seconds.\n");
+ exit(1);
+ return 1;
+}
+
//=============================================================================
//=============================================================================
Int parseLatencyAverage(char *args[], int num)
@@ -1141,11 +1239,23 @@ static CommandLineParam paramsForStartup[] =
// (If you have 4 cores, call it with -jobs 4)
// If you do not call this, all replays will be simulated in sequence in the same process.
{ "-jobs", parseJobs },
+
+#if defined(RTS_DEBUG)
+ { "-autoNetworkMode", parseAutoNetworkMode },
+#endif
};
// These Params are parsed during Engine Init before INI data is loaded
static CommandLineParam paramsForEngineInit[] =
{
+#if defined(RTS_DEBUG)
+ { "-autoNetworkHost", parseAutoNetworkHost },
+ { "-autoNetworkJoin", parseAutoNetworkJoin },
+ { "-autoNetworkLocalAddress", parseAutoNetworkLocalAddress },
+ { "-autoNetworkName", parseAutoNetworkName },
+ { "-autoNetworkMap", parseAutoNetworkMap },
+ { "-autoNetworkTimeout", parseAutoNetworkTimeout },
+#endif
{ "-nologo", parseNoLogo }, // TheSuperHackers @tweak Is now available in Release builds.
{ "-noshellmap", parseNoShellMap },
{ "-noShellAnim", parseNoWindowAnimation }, // TheSuperHackers @tweak Is now available in Release builds.
diff --git a/Core/GameEngine/Source/GameNetwork/LANAPICallbacks.cpp b/Core/GameEngine/Source/GameNetwork/LANAPICallbacks.cpp
index a1ecfbb3238..6f889972434 100644
--- a/Core/GameEngine/Source/GameNetwork/LANAPICallbacks.cpp
+++ b/Core/GameEngine/Source/GameNetwork/LANAPICallbacks.cpp
@@ -45,6 +45,7 @@
#include "GameLogic/GameLogic.h"
#include "GameNetwork/FileTransfer.h"
#include "GameNetwork/LANAPICallbacks.h"
+#include "GameNetwork/NetworkAutoStart.h"
#include "GameNetwork/networkutil.h"
LANAPI *TheLAN = nullptr;
@@ -243,6 +244,9 @@ void LANAPI::OnGameStart()
if (!filesOk || TheMapCache->findMap(m_currentGame->getMap()) == nullptr)
{
DEBUG_LOG(("After transfer, we didn't really have the map. Bailing..."));
+#if defined(RTS_DEBUG)
+ NetworkAutoStart::onGameStartFailure();
+#endif
OnPlayerLeave(m_name);
removeGame(m_currentGame);
m_currentGame = nullptr;
@@ -271,6 +275,10 @@ void LANAPI::OnGameStart()
// Set the seeds
InitRandom( m_currentGame->getSeed() );
DEBUG_LOG(("InitRandom( %d )", m_currentGame->getSeed()));
+
+#if defined(RTS_DEBUG)
+ NetworkAutoStart::onGameStart();
+#endif
}
}
@@ -515,6 +523,15 @@ void LANAPI::OnPlayerJoin( Int slot, UnicodeString playerName )
void LANAPI::OnGameJoin( ReturnType ret, LANGameInfo *theGame )
{
+#if defined(RTS_DEBUG)
+ if (NetworkAutoStart::isEnabled())
+ {
+ NetworkAutoStart::onGameJoin(ret);
+ if (ret != RET_OK)
+ return;
+ }
+#endif
+
if (ret == RET_OK)
{
LANbuttonPushed = true;
@@ -605,6 +622,15 @@ void LANAPI::OnGameList( LANGameInfo *gameList )
void LANAPI::OnGameCreate( ReturnType ret )
{
+#if defined(RTS_DEBUG)
+ if (NetworkAutoStart::isEnabled())
+ {
+ NetworkAutoStart::onGameCreate(ret);
+ if (ret != RET_OK)
+ return;
+ }
+#endif
+
if (ret == RET_OK)
{
diff --git a/Core/GameEngine/Source/GameNetwork/NetworkAutoStart.cpp b/Core/GameEngine/Source/GameNetwork/NetworkAutoStart.cpp
new file mode 100644
index 00000000000..638fca6293a
--- /dev/null
+++ b/Core/GameEngine/Source/GameNetwork/NetworkAutoStart.cpp
@@ -0,0 +1,512 @@
+/*
+** Command & Conquer Generals Zero Hour(tm)
+** Copyright 2026 TheSuperHackers
+**
+** 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 .
+*/
+
+#include "PreRTS.h"
+
+#if defined(RTS_DEBUG)
+
+#include
+
+#include "GameClient/ClientInstance.h"
+#include "GameClient/MapUtil.h"
+#include "GameNetwork/LANAPICallbacks.h"
+#include "GameNetwork/NetworkAutoStart.h"
+
+namespace
+{
+enum {
+ DefaultStartupTimeoutMilliseconds = 30000,
+ ActionRetryMilliseconds = 1000,
+ MillisecondsPerSecond = 1000,
+ IPv4OctetCount = 4,
+ IPv4BitsPerOctet = 8,
+ MaxIPv4OctetValue = 255,
+};
+
+const UnsignedInt IPv4BroadcastAddress = UINT_MAX;
+
+NetworkAutoStart::Mode s_mode = NetworkAutoStart::MODE_NONE;
+NetworkAutoStart::Role s_role = NetworkAutoStart::ROLE_NONE;
+Int s_expectedPlayers = 0;
+UnsignedInt s_hostAddress = 0;
+UnsignedInt s_localAddress = 0;
+AsciiString s_playerName;
+AsciiString s_mapName;
+UnsignedInt s_timeoutMilliseconds = DefaultStartupTimeoutMilliseconds;
+UnsignedInt s_startTime = 0;
+UnsignedInt s_lastActionTime = 0;
+Bool s_hasArguments = false;
+Bool s_directConnectOpened = false;
+Bool s_actionPending = false;
+Bool s_startRequested = false;
+Bool s_gameStarted = false;
+Bool s_failed = false;
+
+Bool ParseIPv4Address(AsciiString address, UnsignedInt &result)
+{
+ const char *cursor = address.str();
+ result = 0;
+ for (Int octet = 0; octet < IPv4OctetCount; ++octet)
+ {
+ if (*cursor < '0' || *cursor > '9')
+ {
+ return false;
+ }
+
+ UnsignedInt value = 0;
+ do
+ {
+ value = value * 10 + (*cursor - '0');
+ if (value > MaxIPv4OctetValue)
+ {
+ return false;
+ }
+ ++cursor;
+ } while (*cursor >= '0' && *cursor <= '9');
+
+ result = (result << IPv4BitsPerOctet) | value;
+ if (octet + 1 < IPv4OctetCount)
+ {
+ if (*cursor != '.')
+ {
+ return false;
+ }
+ ++cursor;
+ }
+ else if (*cursor != '\0')
+ {
+ return false;
+ }
+ }
+
+ return result != 0 && result != IPv4BroadcastAddress;
+}
+
+Bool CanAcceptMap(LANGameInfo *game, LANGameSlot *slot)
+{
+ if (slot->hasMap())
+ {
+ return true;
+ }
+
+ const MapMetaData *mapData = TheMapCache->findMap(game->getMap());
+ if (mapData != nullptr)
+ {
+ return !mapData->m_isOfficial;
+ }
+
+ return WouldMapTransfer(game->getMap());
+}
+} // namespace
+
+Bool NetworkAutoStart::setMode(AsciiString mode)
+{
+ s_hasArguments = true;
+ if (mode.compareNoCase("direct") == 0)
+ {
+ s_mode = MODE_DIRECT_CONNECT;
+ rts::ClientInstance::setMultiInstance(true);
+ rts::ClientInstance::skipPrimaryInstance();
+ return true;
+ }
+
+ return false;
+}
+
+Bool NetworkAutoStart::setHost(Int expectedPlayers)
+{
+ s_hasArguments = true;
+ if (s_role == ROLE_JOIN || expectedPlayers < MIN_EXPECTED_PLAYERS || expectedPlayers > MAX_SLOTS)
+ {
+ return false;
+ }
+
+ s_role = ROLE_HOST;
+ s_expectedPlayers = expectedPlayers;
+ return true;
+}
+
+Bool NetworkAutoStart::setJoin(AsciiString hostAddress)
+{
+ s_hasArguments = true;
+ hostAddress.trim();
+ if (s_role == ROLE_HOST || hostAddress.isEmpty())
+ {
+ return false;
+ }
+
+ UnsignedInt resolvedAddress = 0;
+ if (!ParseIPv4Address(hostAddress, resolvedAddress))
+ {
+ return false;
+ }
+
+ s_role = ROLE_JOIN;
+ s_hostAddress = resolvedAddress;
+ return true;
+}
+
+Bool NetworkAutoStart::setLocalAddress(AsciiString localAddress)
+{
+ s_hasArguments = true;
+ localAddress.trim();
+ if (localAddress.isEmpty())
+ {
+ return false;
+ }
+
+ return ParseIPv4Address(localAddress, s_localAddress);
+}
+
+Bool NetworkAutoStart::setPlayerName(AsciiString playerName)
+{
+ s_hasArguments = true;
+ playerName.trim();
+ if (playerName.isEmpty())
+ {
+ return false;
+ }
+
+ s_playerName = playerName;
+ return true;
+}
+
+Bool NetworkAutoStart::setMapName(AsciiString mapName)
+{
+ s_hasArguments = true;
+ mapName.trim();
+ if (mapName.isEmpty())
+ {
+ return false;
+ }
+
+ s_mapName = mapName;
+ return true;
+}
+
+Bool NetworkAutoStart::setTimeoutSeconds(Int seconds)
+{
+ s_hasArguments = true;
+ if (seconds < 1 || (UnsignedInt)seconds > UINT_MAX / MillisecondsPerSecond)
+ {
+ return false;
+ }
+
+ s_timeoutMilliseconds = (UnsignedInt)seconds * MillisecondsPerSecond;
+ return true;
+}
+
+Bool NetworkAutoStart::hasArguments()
+{
+ return s_hasArguments;
+}
+
+Bool NetworkAutoStart::isEnabled()
+{
+ return !s_failed && s_mode != MODE_NONE && s_role != ROLE_NONE;
+}
+
+Bool NetworkAutoStart::validateConfiguration()
+{
+ if (s_failed)
+ {
+ return false;
+ }
+
+ if (s_mode == MODE_NONE)
+ {
+ fail("-autoNetworkMode direct is required");
+ return false;
+ }
+
+ if (s_role == ROLE_NONE)
+ {
+ fail("either -autoNetworkHost or -autoNetworkJoin is required");
+ return false;
+ }
+
+ return true;
+}
+
+Bool NetworkAutoStart::shouldOpenDirectConnect()
+{
+ if (!s_hasArguments || s_directConnectOpened || !validateConfiguration())
+ {
+ return false;
+ }
+
+ return s_mode == MODE_DIRECT_CONNECT;
+}
+
+void NetworkAutoStart::markDirectConnectOpened()
+{
+ s_directConnectOpened = true;
+ if (s_startTime == 0)
+ {
+ s_startTime = timeGetTime();
+ }
+}
+
+AsciiString NetworkAutoStart::getMapName()
+{
+ return s_mapName;
+}
+
+UnsignedInt NetworkAutoStart::getLocalAddress()
+{
+ return s_localAddress;
+}
+
+UnicodeString NetworkAutoStart::getPlayerName()
+{
+ UnicodeString name;
+ if (s_playerName.isNotEmpty())
+ {
+ name.translate(s_playerName);
+ }
+ else
+ {
+ name.format(L"AutoNet%02u", rts::ClientInstance::getInstanceId());
+ }
+ name.truncateTo(g_lanPlayerNameLength);
+ return name;
+}
+
+Bool NetworkAutoStart::checkTimeout()
+{
+ if (s_failed || s_gameStarted)
+ {
+ return true;
+ }
+
+ const UnsignedInt now = timeGetTime();
+ if (s_startTime != 0 && now - s_startTime >= s_timeoutMilliseconds)
+ {
+ fail("network match startup timed out");
+ return true;
+ }
+
+ return false;
+}
+
+void NetworkAutoStart::fail(const char *message)
+{
+ if (s_failed)
+ {
+ return;
+ }
+
+ s_failed = true;
+ s_actionPending = false;
+ DEBUG_LOG(("NetworkAutoStart failed: %s", message));
+ printf("NetworkAutoStart failed: %s\n", message);
+}
+
+void NetworkAutoStart::updateDirectConnect()
+{
+ if (!isEnabled() || s_mode != MODE_DIRECT_CONNECT || checkTimeout() || TheLAN == nullptr)
+ {
+ return;
+ }
+
+ const UnsignedInt now = timeGetTime();
+ if (s_actionPending || (s_lastActionTime != 0 && now - s_lastActionTime < ActionRetryMilliseconds))
+ {
+ return;
+ }
+
+ TheLAN->RequestSetName(getPlayerName());
+ s_lastActionTime = now;
+ s_actionPending = true;
+
+ if (s_role == ROLE_HOST)
+ {
+ DEBUG_LOG(("NetworkAutoStart creating Direct Connect game for %d players", s_expectedPlayers));
+ TheLAN->RequestGameCreate(UnicodeString::TheEmptyString, true);
+ }
+ else
+ {
+ DEBUG_LOG(("NetworkAutoStart joining Direct Connect host 0x%08X", s_hostAddress));
+ TheLAN->RequestGameJoinDirectConnect(s_hostAddress);
+ }
+}
+
+void NetworkAutoStart::updateGameOptions()
+{
+ if (!isEnabled() || checkTimeout() || TheLAN == nullptr || TheLAN->GetMyGame() == nullptr)
+ {
+ return;
+ }
+
+ LANGameInfo *game = TheLAN->GetMyGame();
+ if (s_role == ROLE_JOIN)
+ {
+ const Int localSlot = game->getLocalSlotNum();
+ if (localSlot < 0)
+ {
+ return;
+ }
+
+ LANGameSlot *slot = game->getLANSlot(localSlot);
+ const UnsignedInt now = timeGetTime();
+ if (slot != nullptr && !slot->isAccepted() &&
+ (s_lastActionTime == 0 || now - s_lastActionTime >= ActionRetryMilliseconds))
+ {
+ TheLAN->RequestHasMap();
+ if (!CanAcceptMap(game, slot))
+ {
+ fail("required map is unavailable and cannot be transferred");
+ return;
+ }
+
+ TheLAN->RequestAccept();
+ s_lastActionTime = now;
+ }
+ return;
+ }
+
+ if (s_startRequested)
+ {
+ return;
+ }
+
+ const MapMetaData *mapData = TheMapCache->findMap(game->getMap());
+ if (mapData == nullptr)
+ {
+ fail("selected map was not found");
+ return;
+ }
+ if (mapData->m_numPlayers < s_expectedPlayers)
+ {
+ fail("selected map has fewer slots than -autoNetworkHost requires");
+ return;
+ }
+
+ Int humanPlayers = 0;
+ for (Int humanIndex = 0; humanIndex < MAX_SLOTS; ++humanIndex)
+ {
+ LANGameSlot *slot = game->getLANSlot(humanIndex);
+ if (slot != nullptr && slot->isHuman())
+ {
+ ++humanPlayers;
+ }
+ }
+
+ if (humanPlayers > s_expectedPlayers)
+ {
+ fail("more players joined than -autoNetworkHost expects");
+ return;
+ }
+ if (humanPlayers != s_expectedPlayers)
+ {
+ return;
+ }
+
+ LANGameSlot *hostSlot = game->getLANSlot(0);
+ if (hostSlot == nullptr)
+ {
+ fail("host slot is unavailable");
+ return;
+ }
+ hostSlot->setAccept();
+ for (Int acceptedIndex = 0; acceptedIndex < MAX_SLOTS; ++acceptedIndex)
+ {
+ LANGameSlot *slot = game->getLANSlot(acceptedIndex);
+ if (slot != nullptr && slot->isHuman() && !slot->isAccepted())
+ {
+ return;
+ }
+ }
+
+ const UnsignedInt now = timeGetTime();
+ if (s_lastActionTime == 0 || now - s_lastActionTime >= ActionRetryMilliseconds)
+ {
+ DEBUG_LOG(("NetworkAutoStart starting Direct Connect game with %d players", humanPlayers));
+ s_lastActionTime = now;
+ s_startRequested = true;
+ StartLANGame();
+ }
+}
+
+void NetworkAutoStart::onGameCreate(LANAPIInterface::ReturnType result)
+{
+ if (!isEnabled())
+ {
+ return;
+ }
+
+ if (result == LANAPIInterface::RET_OK)
+ {
+ return;
+ }
+
+ s_actionPending = false;
+ fail("could not create Direct Connect game");
+}
+
+void NetworkAutoStart::onGameJoin(LANAPIInterface::ReturnType result)
+{
+ if (!isEnabled())
+ {
+ return;
+ }
+
+ if (result == LANAPIInterface::RET_OK)
+ {
+ return;
+ }
+
+ s_actionPending = false;
+ if (result == LANAPIInterface::RET_TIMEOUT || result == LANAPIInterface::RET_GAME_GONE)
+ {
+ DEBUG_LOG(("NetworkAutoStart will retry Direct Connect join after result %d", result));
+ return;
+ }
+
+ fail("Direct Connect join was rejected");
+}
+
+void NetworkAutoStart::onLocalAddressSet(Bool result)
+{
+ if (isEnabled() && !result)
+ {
+ fail("could not bind the Direct Connect local address");
+ }
+}
+
+void NetworkAutoStart::onGameStartFailure()
+{
+ if (isEnabled())
+ {
+ fail("required map could not be transferred");
+ }
+}
+
+void NetworkAutoStart::onGameStart()
+{
+ if (!isEnabled())
+ {
+ return;
+ }
+
+ s_gameStarted = true;
+ DEBUG_LOG(("NetworkAutoStart entered the network game"));
+ printf("NetworkAutoStart entered the network game\n");
+}
+
+#endif
diff --git a/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/LanGameOptionsMenu.cpp b/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/LanGameOptionsMenu.cpp
index 0520332611d..ca821037df5 100644
--- a/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/LanGameOptionsMenu.cpp
+++ b/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/LanGameOptionsMenu.cpp
@@ -53,6 +53,7 @@
#include "GameNetwork/LANAPI.h"
#include "GameNetwork/IPEnumeration.h"
#include "GameNetwork/LANAPICallbacks.h"
+#include "GameNetwork/NetworkAutoStart.h"
#include "Common/MultiplayerSettings.h"
#include "GameClient/GameText.h"
#include "GameNetwork/GUIUtil.h"
@@ -211,7 +212,7 @@ static void playerTooltip(GameWindow *window,
setLANPlayerTooltip(player);
}
-void StartPressed()
+void StartLANGame()
{
LANGameInfo *myGame = TheLAN->GetMyGame();
@@ -780,8 +781,13 @@ void LanGameOptionsMenuInit( WindowLayout *layout, void *userData )
slot->setColor( pref.getPreferredColor() );
slot->setPlayerTemplate( pref.getPreferredFaction() );
slot->setNATBehavior(FirewallHelperClass::FIREWALL_TYPE_SIMPLE);
- game->setMap( pref.getPreferredMap() );
- AsciiString lowerMap = pref.getPreferredMap();
+ AsciiString mapName = pref.getPreferredMap();
+#if defined(RTS_DEBUG)
+ if (NetworkAutoStart::isEnabled() && NetworkAutoStart::getMapName().isNotEmpty())
+ mapName = NetworkAutoStart::getMapName();
+#endif
+ game->setMap(mapName);
+ AsciiString lowerMap = mapName;
lowerMap.toLower();
std::map::iterator it = TheMapCache->find(lowerMap);
if (it != TheMapCache->end())
@@ -975,6 +981,14 @@ void LanGameOptionsMenuShutdown( WindowLayout *layout, void *userData )
//-------------------------------------------------------------------------------------------------
void LanGameOptionsMenuUpdate( WindowLayout * layout, void *userData)
{
+#if defined(RTS_DEBUG)
+ if (NetworkAutoStart::isEnabled() && TheLAN != nullptr)
+ {
+ TheLAN->update();
+ NetworkAutoStart::updateGameOptions();
+ }
+#endif
+
if(LANisShuttingDown && TheShell->isAnimFinished() && TheTransitionHandler->isFinished())
shutdownComplete(layout);
//TheLAN->update(); // this is handled in the lobby
@@ -1160,7 +1174,7 @@ WindowMsgHandledType LanGameOptionsMenuSystem( GameWindow *window, UnsignedInt m
{
if (TheLAN->AmIHost())
{
- StartPressed();
+ StartLANGame();
//TheLAN->RequestGameStart();
}
else
diff --git a/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/MainMenu.cpp b/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/MainMenu.cpp
index 0097330bb16..fd6e2d30480 100644
--- a/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/MainMenu.cpp
+++ b/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/MainMenu.cpp
@@ -69,6 +69,7 @@
#include "GameNetwork/DownloadManager.h"
#include "GameNetwork/GameSpy/MainMenuUtils.h"
+#include "GameNetwork/NetworkAutoStart.h"
#include "GameClient/InGameUI.h"
@@ -748,6 +749,15 @@ void ResolutionDialogUpdate()
void DownloadMenuUpdate( WindowLayout *layout, void *userData );
void MainMenuUpdate( WindowLayout *layout, void *userData )
{
+#if defined(RTS_DEBUG)
+ if (NetworkAutoStart::shouldOpenDirectConnect())
+ {
+ NetworkAutoStart::markDirectConnectOpened();
+ TheShell->push("Menus/NetworkDirectConnect.wnd");
+ return;
+ }
+#endif
+
if( TheGameLogic->isInGame() && !TheGameLogic->isInShellGame() )
{
return;
diff --git a/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/NetworkDirectConnect.cpp b/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/NetworkDirectConnect.cpp
index 1412088c582..400cfcfc91d 100644
--- a/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/NetworkDirectConnect.cpp
+++ b/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/NetworkDirectConnect.cpp
@@ -49,6 +49,7 @@
#include "GameNetwork/IPEnumeration.h"
#include "GameNetwork/LANAPI.h"
#include "GameNetwork/LANAPICallbacks.h"
+#include "GameNetwork/NetworkAutoStart.h"
// window ids ------------------------------------------------------------------------------
@@ -252,12 +253,25 @@ void NetworkDirectConnectInit( WindowLayout *layout, void *userData )
LANbuttonPushed = false;
LANisShuttingDown = false;
- if (TheLAN == nullptr)
+ Bool automatedStartup = FALSE;
+ UnsignedInt autoLocalIP = 0;
+#if defined(RTS_DEBUG)
+ if (NetworkAutoStart::isEnabled())
{
- TheLAN = NEW LANAPI();
- TheLAN->init();
+ automatedStartup = TRUE;
+ autoLocalIP = NetworkAutoStart::getLocalAddress();
+ }
+#endif
+
+ if (!automatedStartup)
+ {
+ if (TheLAN == nullptr)
+ {
+ TheLAN = NEW LANAPI();
+ TheLAN->init();
+ }
+ TheLAN->reset();
}
- TheLAN->reset();
buttonPushed = false;
isShuttingDown = false;
@@ -305,6 +319,8 @@ void NetworkDirectConnectInit( WindowLayout *layout, void *userData )
OptionPreferences prefs;
UnsignedInt IP = prefs.getOnlineIPAddress();
+ if (autoLocalIP != 0)
+ IP = autoLocalIP;
IPEnumeration IPs;
@@ -317,7 +333,7 @@ void NetworkDirectConnectInit( WindowLayout *layout, void *userData )
/// @todo: display error and exit lan lobby if no IPs are found
}
- Bool foundIP = FALSE;
+ Bool foundIP = autoLocalIP != 0;
EnumeratedIP *tempIP = IPlist;
while ((tempIP != nullptr) && (foundIP == FALSE)) {
if (IP == tempIP->getIP()) {
@@ -333,8 +349,17 @@ void NetworkDirectConnectInit( WindowLayout *layout, void *userData )
// IP = IPlist->getIP();
// }
- TheLAN->init();
- TheLAN->SetLocalIP(IP);
+ if (automatedStartup)
+ {
+#if defined(RTS_DEBUG)
+ NetworkAutoStart::onLocalAddressSet(TheLAN->SetLocalIP(IP));
+#endif
+ }
+ else
+ {
+ TheLAN->init();
+ TheLAN->SetLocalIP(IP);
+ }
}
UnsignedInt ip = TheLAN->GetLocalIP();
@@ -393,6 +418,14 @@ void NetworkDirectConnectShutdown( WindowLayout *layout, void *userData )
//-------------------------------------------------------------------------------------------------
void NetworkDirectConnectUpdate( WindowLayout * layout, void *userData)
{
+#if defined(RTS_DEBUG)
+ if (NetworkAutoStart::isEnabled() && TheLAN != nullptr)
+ {
+ TheLAN->update();
+ NetworkAutoStart::updateDirectConnect();
+ }
+#endif
+
// We'll only be successful if we've requested to
if(isShuttingDown && TheShell->isAnimFinished() && TheTransitionHandler->isFinished())
shutdownComplete(layout);
diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/LanGameOptionsMenu.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/LanGameOptionsMenu.cpp
index 609a0f772cd..f6d43ea874a 100644
--- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/LanGameOptionsMenu.cpp
+++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/LanGameOptionsMenu.cpp
@@ -56,6 +56,7 @@
#include "GameNetwork/LANAPI.h"
#include "GameNetwork/IPEnumeration.h"
#include "GameNetwork/LANAPICallbacks.h"
+#include "GameNetwork/NetworkAutoStart.h"
#include "Common/MultiplayerSettings.h"
#include "GameClient/GameText.h"
#include "GameNetwork/GUIUtil.h"
@@ -218,7 +219,7 @@ static void playerTooltip(GameWindow *window,
setLANPlayerTooltip(player);
}
-void StartPressed()
+void StartLANGame()
{
LANGameInfo *myGame = TheLAN->GetMyGame();
@@ -856,10 +857,15 @@ void LanGameOptionsMenuInit( WindowLayout *layout, void *userData )
slot->setColor( pref.getPreferredColor() );
slot->setPlayerTemplate( pref.getPreferredFaction() );
slot->setNATBehavior(FirewallHelperClass::FIREWALL_TYPE_SIMPLE);
- game->setMap( pref.getPreferredMap() );
+ AsciiString mapName = pref.getPreferredMap();
+#if defined(RTS_DEBUG)
+ if (NetworkAutoStart::isEnabled() && NetworkAutoStart::getMapName().isNotEmpty())
+ mapName = NetworkAutoStart::getMapName();
+#endif
+ game->setMap(mapName);
game->setStartingCash( pref.getStartingCash() );
game->setSuperweaponRestriction( pref.getSuperweaponRestricted() ? 1 : 0 );
- AsciiString lowerMap = pref.getPreferredMap();
+ AsciiString lowerMap = mapName;
lowerMap.toLower();
std::map::iterator it = TheMapCache->find(lowerMap);
if (it != TheMapCache->end())
@@ -1070,6 +1076,14 @@ void LanGameOptionsMenuShutdown( WindowLayout *layout, void *userData )
//-------------------------------------------------------------------------------------------------
void LanGameOptionsMenuUpdate( WindowLayout * layout, void *userData)
{
+#if defined(RTS_DEBUG)
+ if (NetworkAutoStart::isEnabled() && TheLAN != nullptr)
+ {
+ TheLAN->update();
+ NetworkAutoStart::updateGameOptions();
+ }
+#endif
+
if(LANisShuttingDown && TheShell->isAnimFinished() && TheTransitionHandler->isFinished())
shutdownComplete(layout);
//TheLAN->update(); // this is handled in the lobby
@@ -1263,7 +1277,7 @@ WindowMsgHandledType LanGameOptionsMenuSystem( GameWindow *window, UnsignedInt m
{
if (TheLAN->AmIHost())
{
- StartPressed();
+ StartLANGame();
//TheLAN->RequestGameStart();
}
else
diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/MainMenu.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/MainMenu.cpp
index f51953e454f..1b63af43e9d 100644
--- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/MainMenu.cpp
+++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/MainMenu.cpp
@@ -72,6 +72,7 @@
#include "GameNetwork/DownloadManager.h"
#include "GameNetwork/GameSpy/MainMenuUtils.h"
+#include "GameNetwork/NetworkAutoStart.h"
#include "GameClient/InGameUI.h"
@@ -785,6 +786,15 @@ void ResolutionDialogUpdate()
void DownloadMenuUpdate( WindowLayout *layout, void *userData );
void MainMenuUpdate( WindowLayout *layout, void *userData )
{
+#if defined(RTS_DEBUG)
+ if (NetworkAutoStart::shouldOpenDirectConnect())
+ {
+ NetworkAutoStart::markDirectConnectOpened();
+ TheShell->push("Menus/NetworkDirectConnect.wnd");
+ return;
+ }
+#endif
+
if( TheGameLogic->isInGame() && !TheGameLogic->isInShellGame() )
{
return;
diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/NetworkDirectConnect.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/NetworkDirectConnect.cpp
index 7c9c462f9b9..9afa817e837 100644
--- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/NetworkDirectConnect.cpp
+++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/NetworkDirectConnect.cpp
@@ -49,6 +49,7 @@
#include "GameNetwork/IPEnumeration.h"
#include "GameNetwork/LANAPI.h"
#include "GameNetwork/LANAPICallbacks.h"
+#include "GameNetwork/NetworkAutoStart.h"
// window ids ------------------------------------------------------------------------------
@@ -252,12 +253,25 @@ void NetworkDirectConnectInit( WindowLayout *layout, void *userData )
LANbuttonPushed = false;
LANisShuttingDown = false;
- if (TheLAN == nullptr)
+ Bool automatedStartup = FALSE;
+ UnsignedInt autoLocalIP = 0;
+#if defined(RTS_DEBUG)
+ if (NetworkAutoStart::isEnabled())
{
- TheLAN = NEW LANAPI();
- TheLAN->init();
+ automatedStartup = TRUE;
+ autoLocalIP = NetworkAutoStart::getLocalAddress();
+ }
+#endif
+
+ if (!automatedStartup)
+ {
+ if (TheLAN == nullptr)
+ {
+ TheLAN = NEW LANAPI();
+ TheLAN->init();
+ }
+ TheLAN->reset();
}
- TheLAN->reset();
buttonPushed = false;
isShuttingDown = false;
@@ -305,6 +319,8 @@ void NetworkDirectConnectInit( WindowLayout *layout, void *userData )
OptionPreferences prefs;
UnsignedInt IP = prefs.getOnlineIPAddress();
+ if (autoLocalIP != 0)
+ IP = autoLocalIP;
IPEnumeration IPs;
@@ -317,7 +333,7 @@ void NetworkDirectConnectInit( WindowLayout *layout, void *userData )
/// @todo: display error and exit lan lobby if no IPs are found
}
- Bool foundIP = FALSE;
+ Bool foundIP = autoLocalIP != 0;
EnumeratedIP *tempIP = IPlist;
while ((tempIP != nullptr) && (foundIP == FALSE)) {
if (IP == tempIP->getIP()) {
@@ -333,8 +349,17 @@ void NetworkDirectConnectInit( WindowLayout *layout, void *userData )
// IP = IPlist->getIP();
// }
- TheLAN->init();
- TheLAN->SetLocalIP(IP);
+ if (automatedStartup)
+ {
+#if defined(RTS_DEBUG)
+ NetworkAutoStart::onLocalAddressSet(TheLAN->SetLocalIP(IP));
+#endif
+ }
+ else
+ {
+ TheLAN->init();
+ TheLAN->SetLocalIP(IP);
+ }
}
UnsignedInt ip = TheLAN->GetLocalIP();
@@ -393,6 +418,14 @@ void NetworkDirectConnectShutdown( WindowLayout *layout, void *userData )
//-------------------------------------------------------------------------------------------------
void NetworkDirectConnectUpdate( WindowLayout * layout, void *userData)
{
+#if defined(RTS_DEBUG)
+ if (NetworkAutoStart::isEnabled() && TheLAN != nullptr)
+ {
+ TheLAN->update();
+ NetworkAutoStart::updateDirectConnect();
+ }
+#endif
+
// We'll only be successful if we've requested to
if(isShuttingDown && TheShell->isAnimFinished() && TheTransitionHandler->isFinished())
shutdownComplete(layout);