From 765d92b50a60dfd0dbbbe1773e31b7550a58bc84 Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Fri, 20 Feb 2026 03:40:02 +0100 Subject: [PATCH 1/9] Implement graceful exit for Alt+F4 and window close events --- .../GameEngine/Include/GameLogic/GameLogic.h | 2 + .../GUI/GUICallbacks/Menus/QuitMenu.cpp | 29 +--------- .../GameClient/MessageStream/CommandXlat.cpp | 20 +------ .../Source/GameLogic/System/GameLogic.cpp | 58 +++++++++++++++++++ .../GameLogic/System/GameLogicDispatch.cpp | 3 +- 5 files changed, 65 insertions(+), 47 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h index 48522894f90..9623d848a44 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h @@ -136,6 +136,7 @@ class GameLogic : public SubsystemInterface, public Snapshot Bool isInGameLogicUpdate( void ) const { return m_isInUpdate; } Bool hasUpdated() const { return m_hasUpdated; } ///< Returns true if the logic frame has advanced in the current client/render update UnsignedInt getFrame( void ); ///< Returns the current simulation frame number + void quit(Bool toDesktop); UnsignedInt getCRC( Int mode = CRC_CACHED, AsciiString deepCRCFileName = AsciiString::TheEmptyString ); ///< Returns the CRC void setObjectIDCounter( ObjectID nextObjID ) { m_nextObjID = nextObjID; } @@ -405,6 +406,7 @@ class GameLogic : public SubsystemInterface, public Snapshot void xferObjectTOC( Xfer *xfer ); ///< save/load object TOC for current state of map void prepareLogicForObjectLoad( void ); ///< prepare engine for object data from game file + Bool m_quitToDesktopAfterMatch; }; // INLINE ///////////////////////////////////////////////////////////////////////////////////////// diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/QuitMenu.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/QuitMenu.cpp index fc0c4520266..5547d31df29 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/QuitMenu.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/QuitMenu.cpp @@ -139,21 +139,7 @@ static void exitQuitMenu() { // destroy the quit menu destroyQuitMenu(); - - // clear out all the game data - if ( TheGameLogic->isInMultiplayerGame() && !TheGameLogic->isInSkirmishGame() && !TheGameInfo->isSandbox() ) - { - GameMessage *msg = TheMessageStream->appendMessage(GameMessage::MSG_SELF_DESTRUCT); - msg->appendBooleanArgument(TRUE); - } - TheGameLogic->exitGame(); - // TheGameLogic->clearGameData(); - // display the menu on top of the shell stack - // TheShell->showShell(); - - // this will trigger an exit - // TheGameEngine->setQuitting( TRUE ); - TheInGameUI->setClientQuiet( TRUE ); + TheGameLogic->quit(FALSE); } static void noExitQuitMenu() { @@ -164,18 +150,7 @@ static void quitToDesktopQuitMenu() { // destroy the quit menu destroyQuitMenu(); - - if (TheGameLogic->isInGame()) - { - if (TheRecorder->getMode() == RECORDERMODETYPE_RECORD) - { - TheRecorder->stopRecording(); - } - TheGameLogic->clearGameData(); - } - TheGameEngine->setQuitting(TRUE); - TheInGameUI->setClientQuiet( TRUE ); - + TheGameLogic->quit(TRUE); } static void surrenderQuitMenu() diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp index c90804d231f..239bdc2a452 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp @@ -91,10 +91,6 @@ #include "ww3d.h" - -#define dont_ALLOW_ALT_F4 - - #if defined(RTS_DEBUG) /*non-static*/ Real TheSkateDistOverride = 0.0f; @@ -4076,26 +4072,12 @@ GameMessageDisposition CommandTranslator::translateGameMessage(const GameMessage } - - -#ifdef ALLOW_ALT_F4 case GameMessage::MSG_META_DEMO_INSTANT_QUIT: { - if (TheGameLogic->isInGame()) - { - if (TheRecorder->getMode() == RECORDERMODETYPE_RECORD) - { - TheRecorder->stopRecording(); - } - TheGameLogic->clearGameData(); - } - TheGameEngine->setQuitting(TRUE); + TheGameLogic->quit(TRUE); disp = DESTROY_MESSAGE; break; } -#endif - - //------------------------------------------------------------------------------- DEMO MESSAGES diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp index dcb20cc70cd..fc880969e51 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -141,6 +141,8 @@ enum { OBJ_HASH_SIZE = 8192 }; /// The GameLogic singleton instance GameLogic *TheGameLogic = nullptr; +extern GameInfo *TheGameInfo; + static void findAndSelectCommandCenter(Object *obj, void* alreadyFound); @@ -262,6 +264,7 @@ GameLogic::GameLogic( void ) m_loadingMap = FALSE; m_loadingSave = FALSE; m_clearingGameData = FALSE; + m_quitToDesktopAfterMatch = FALSE; } //------------------------------------------------------------------------------------------------- @@ -4170,6 +4173,61 @@ void GameLogic::exitGame() TheMessageStream->appendMessage(GameMessage::MSG_CLEAR_GAME_DATA); } +// ------------------------------------------------------------------------------------------------ +void GameLogic::quit(Bool toDesktop) +{ + if (isInGame()) + { + if (isInMultiplayerGame() && !isInSkirmishGame() && TheGameInfo && !TheGameInfo->isSandbox()) + { + GameMessage *msg = TheMessageStream->appendMessage(GameMessage::MSG_SELF_DESTRUCT); + msg->appendBooleanArgument(TRUE); + } + + if (TheRecorder && TheRecorder->getMode() == RECORDERMODETYPE_RECORD) + { + TheRecorder->stopRecording(); + } + + setGamePaused(FALSE); + if (TheScriptEngine) + { + TheScriptEngine->forceUnfreezeTime(); + TheScriptEngine->doUnfreezeTime(); + } + + if (toDesktop) + { + if (isInMultiplayerGame()) + { + m_quitToDesktopAfterMatch = TRUE; + exitGame(); + } + else + { + clearGameData(); + } + } + else + { + exitGame(); + } + } + + if (toDesktop) + { + if (!isInMultiplayerGame()) + { + TheGameEngine->setQuitting(TRUE); + } + } + + if (TheInGameUI) + { + TheInGameUI->setClientQuiet(TRUE); + } +} + // ------------------------------------------------------------------------------------------------ /** A new GameLogic object has been constructed, therefore create * a corresponding drawable and bind them together. */ diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp index a3a2c757854..41eca907e0b 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp @@ -280,9 +280,10 @@ void GameLogic::clearGameData( Bool showScoreScreen ) // if(shellGame) - if (TheGlobalData->m_initialFile.isEmpty() == FALSE) + if (TheGlobalData->m_initialFile.isEmpty() == FALSE || m_quitToDesktopAfterMatch) { TheGameEngine->setQuitting(TRUE); + m_quitToDesktopAfterMatch = FALSE; } HideControlBar(); From 4fda496d03ad85a01eef13e727f442ceac796e95 Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Sat, 21 Feb 2026 18:44:03 +0100 Subject: [PATCH 2/9] Implement graceful exit during loading and movie screens --- .../Source/GameClient/GUI/LoadScreen.cpp | 47 ++++++++++--------- .../Include/GameClient/GameClient.h | 2 + .../GameEngine/Include/GameLogic/GameLogic.h | 3 +- .../Source/GameClient/GameClient.cpp | 32 +++++++++++++ .../GameClient/MessageStream/CommandXlat.cpp | 1 + .../Source/GameLogic/System/GameLogic.cpp | 14 +++++- 6 files changed, 74 insertions(+), 25 deletions(-) diff --git a/Core/GameEngine/Source/GameClient/GUI/LoadScreen.cpp b/Core/GameEngine/Source/GameClient/GUI/LoadScreen.cpp index 59154f13f45..1a0dfccbd81 100644 --- a/Core/GameEngine/Source/GameClient/GUI/LoadScreen.cpp +++ b/Core/GameEngine/Source/GameClient/GUI/LoadScreen.cpp @@ -68,6 +68,7 @@ #include "GameClient/Display.h" #include "GameClient/GadgetProgressBar.h" #include "GameClient/GadgetStaticText.h" +#include "GameClient/GameClient.h" #include "GameClient/GameText.h" #include "GameClient/GameWindowManager.h" #include "GameClient/GameWindowTransitions.h" @@ -75,6 +76,7 @@ #include "GameClient/LoadScreen.h" #include "GameClient/MapUtil.h" #include "GameClient/Mouse.h" +#include "Common/MessageStream.h" #include "GameClient/Shell.h" #include "GameClient/VideoPlayer.h" #include "GameClient/WindowLayout.h" @@ -157,7 +159,8 @@ LoadScreen::~LoadScreen( void ) void LoadScreen::update( Int percent ) { TheGameEngine->serviceWindowsOS(); - if (TheGameEngine->getQuitting()) + TheMessageStream->propagateMessages(); + if (TheGameEngine->getQuitting() || TheGameLogic->m_quitToDesktopAfterMatch) return; //don't bother with any of this if the player is exiting game. TheWindowManager->update(); @@ -539,20 +542,11 @@ void SinglePlayerLoadScreen::init( GameInfo *game ) Int shiftedPercent = -FRAME_FUDGE_ADD + 1; while (m_videoStream->frameIndex() < m_videoStream->frameCount() - 1 ) { - // TheSuperHackers @feature User can now skip video by pressing ESC - if (TheKeyboard) + if (TheGameClient->isMovieAbortRequested()) { - TheKeyboard->UPDATE(); - KeyboardIO *io = TheKeyboard->findKey(KEY_ESC, KeyboardIO::STATUS_UNUSED); - if (io && BitIsSet(io->state, KEY_STATE_DOWN)) - { - io->setUsed(); - break; - } + break; } - TheGameEngine->serviceWindowsOS(); - if(!m_videoStream->isFrameReady()) { Sleep(1); @@ -634,6 +628,11 @@ void SinglePlayerLoadScreen::init( GameInfo *game ) fudgeFactor = 30 * ((currTime - begin)/ INT_TO_REAL(delay )); GadgetProgressBarSetProgress(m_progressBar, fudgeFactor); + if (TheGameClient->isMovieAbortRequested()) + { + break; + } + TheWindowManager->update(); TheDisplay->draw(); Sleep(100); @@ -1054,20 +1053,11 @@ void ChallengeLoadScreen::init( GameInfo *game ) Int shiftedPercent = -FRAME_FUDGE_ADD + 1; while (m_videoStream->frameIndex() < m_videoStream->frameCount() - 1 ) { - // TheSuperHackers @feature User can now skip video by pressing ESC - if (TheKeyboard) + if (TheGameClient->isMovieAbortRequested()) { - TheKeyboard->UPDATE(); - KeyboardIO *io = TheKeyboard->findKey(KEY_ESC, KeyboardIO::STATUS_UNUSED); - if (io && BitIsSet(io->state, KEY_STATE_DOWN)) - { - io->setUsed(); - break; - } + break; } - TheGameEngine->serviceWindowsOS(); - if(!m_videoStream->isFrameReady()) { Sleep(1); @@ -1109,7 +1099,13 @@ void ChallengeLoadScreen::init( GameInfo *game ) // if we're min speced m_videoStream->frameGoto(m_videoStream->frameCount()); // zero based while(!m_videoStream->isFrameReady()) + { + if (TheGameClient->isMovieAbortRequested()) + { + break; + } Sleep(1); + } m_videoStream->frameDecompress(); m_videoStream->frameRender(m_videoBuffer); if(m_videoBuffer) @@ -1126,6 +1122,11 @@ void ChallengeLoadScreen::init( GameInfo *game ) fudgeFactor = 30 * ((currTime - begin)/ INT_TO_REAL(delay )); GadgetProgressBarSetProgress(m_progressBar, fudgeFactor); + if (TheGameClient->isMovieAbortRequested()) + { + break; + } + TheWindowManager->update(); TheDisplay->draw(); Sleep(100); diff --git a/GeneralsMD/Code/GameEngine/Include/GameClient/GameClient.h b/GeneralsMD/Code/GameEngine/Include/GameClient/GameClient.h index e1427047606..a27a6901513 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameClient/GameClient.h +++ b/GeneralsMD/Code/GameEngine/Include/GameClient/GameClient.h @@ -155,6 +155,8 @@ class GameClient : public SubsystemInterface, void incrementRenderedObjectCount() { m_renderedObjectCount++; } virtual void notifyTerrainObjectMoved(Object *obj) = 0; + virtual Bool isMovieAbortRequested( void ); + protected: diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h index 9623d848a44..d705265525f 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h @@ -262,6 +262,8 @@ class GameLogic : public SubsystemInterface, public Snapshot // this should be called only by UpdateModule, thanks. void friend_awakenUpdateModule(Object* obj, UpdateModulePtr update, UnsignedInt whenToWakeUp); + Bool m_quitToDesktopAfterMatch; + protected: // snapshot methods @@ -406,7 +408,6 @@ class GameLogic : public SubsystemInterface, public Snapshot void xferObjectTOC( Xfer *xfer ); ///< save/load object TOC for current state of map void prepareLogicForObjectLoad( void ); ///< prepare engine for object data from game file - Bool m_quitToDesktopAfterMatch; }; // INLINE ///////////////////////////////////////////////////////////////////////////////////////// diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp index d59578dff2c..7ce3211a463 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp @@ -556,6 +556,11 @@ void GameClient::update( void ) Int beginTime = timeGetTime(); while(beginTime + 4000 > timeGetTime() ) { + if (TheGameClient->isMovieAbortRequested()) + { + break; + } + TheWindowManager->update(); // redraw all views, update the GUI TheDisplay->draw(); @@ -793,6 +798,33 @@ void GameClient::updateHeadless() TheParticleSystemManager->reset(); } + // TheSuperHackers Check if the user has requested to abort movie +Bool GameClient::isMovieAbortRequested( void ) +{ + // User can skip video by pressing ESC + if (TheKeyboard) + { + TheKeyboard->UPDATE(); + KeyboardIO *io = TheKeyboard->findKey(KEY_ESC, KeyboardIO::STATUS_UNUSED); + if (io && BitIsSet(io->state, KEY_STATE_DOWN)) + { + io->setUsed(); + return TRUE; + } + } + + // Service OS for Window Close / Alt-F4 events + TheGameEngine->serviceWindowsOS(); + TheMessageStream->propagateMessages(); + + if (TheGameEngine->getQuitting() || (TheGameLogic && TheGameLogic->m_quitToDesktopAfterMatch)) + { + return TRUE; + } + + return FALSE; +} + /** ----------------------------------------------------------------------------------------------- * Call the given callback function for each object contained within the given region. */ diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp index 239bdc2a452..d8365a79707 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp @@ -5568,6 +5568,7 @@ static Bool isSystemMessage( const GameMessage *msg ) case GameMessage::MSG_LOGIC_CRC: case GameMessage::MSG_SET_REPLAY_CAMERA: case GameMessage::MSG_FRAME_TICK: + case GameMessage::MSG_META_DEMO_INSTANT_QUIT: return TRUE; } return FALSE; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp index fc880969e51..33bdaa0257d 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -1055,6 +1055,8 @@ static void populateRandomStartPosition( GameInfo *game ) } } +struct QuitGameException {}; + // ------------------------------------------------------------------------------------------------ /** Update the load screen progress */ // ------------------------------------------------------------------------------------------------ @@ -1064,6 +1066,10 @@ void GameLogic::updateLoadProgress( Int progress ) if( m_loadScreen ) m_loadScreen->update( progress ); + if (TheGameEngine->getQuitting() || m_quitToDesktopAfterMatch) + { + throw QuitGameException(); + } } // ------------------------------------------------------------------------------------------------ @@ -1105,6 +1111,8 @@ void GameLogic::setGameMode( GameMode mode ) // ------------------------------------------------------------------------------------------------ void GameLogic::startNewGame( Bool loadingSaveGame ) { + try + { #ifdef DUMP_PERF_STATS __int64 startTime64; @@ -2372,7 +2380,11 @@ void GameLogic::startNewGame( Bool loadingSaveGame ) TheInGameUI->messageNoFormat( TheGameText->FETCH_OR_SUBSTITUTE( "GUI:FastForwardInstructions", L"Press F to toggle Fast Forward" ) ); } - + } + catch (QuitGameException&) + { + // TheSuperHackers: The application is cleanly aborting the loading process. + } } //----------------------------------------------------------------------------------------- From 4465409ccac071d4b3aa983f92505a6880afeee5 Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Sun, 22 Feb 2026 02:54:48 +0100 Subject: [PATCH 3/9] Backport graceful exit logic from GeneralsMD --- .../Include/GameClient/GameClient.h | 2 + .../GameEngine/Include/GameLogic/GameLogic.h | 3 + .../GUI/GUICallbacks/Menus/QuitMenu.cpp | 29 +------- .../Source/GameClient/GameClient.cpp | 32 +++++++++ .../GameClient/MessageStream/CommandXlat.cpp | 10 +-- .../Source/GameLogic/System/GameLogic.cpp | 72 ++++++++++++++++++- .../GameLogic/System/GameLogicDispatch.cpp | 3 +- 7 files changed, 114 insertions(+), 37 deletions(-) diff --git a/Generals/Code/GameEngine/Include/GameClient/GameClient.h b/Generals/Code/GameEngine/Include/GameClient/GameClient.h index 9f73f894b84..f3dd95b709a 100644 --- a/Generals/Code/GameEngine/Include/GameClient/GameClient.h +++ b/Generals/Code/GameEngine/Include/GameClient/GameClient.h @@ -150,6 +150,8 @@ class GameClient : public SubsystemInterface, UnsignedInt getRenderedObjectCount() const { return m_renderedObjectCount; } void incrementRenderedObjectCount() { m_renderedObjectCount++; } + virtual Bool isMovieAbortRequested( void ); + protected: // snapshot methods diff --git a/Generals/Code/GameEngine/Include/GameLogic/GameLogic.h b/Generals/Code/GameEngine/Include/GameLogic/GameLogic.h index ec6e60bb01d..0e55e9e7b84 100644 --- a/Generals/Code/GameEngine/Include/GameLogic/GameLogic.h +++ b/Generals/Code/GameEngine/Include/GameLogic/GameLogic.h @@ -131,6 +131,7 @@ class GameLogic : public SubsystemInterface, public Snapshot Bool isInGameLogicUpdate( void ) const { return m_isInUpdate; } Bool hasUpdated() const { return m_hasUpdated; } ///< Returns true if the logic frame has advanced in the current client/render update UnsignedInt getFrame( void ); ///< Returns the current simulation frame number + void quit(Bool toDesktop); UnsignedInt getCRC( Int mode = CRC_CACHED, AsciiString deepCRCFileName = AsciiString::TheEmptyString ); ///< Returns the CRC void setObjectIDCounter( ObjectID nextObjID ) { m_nextObjID = nextObjID; } @@ -243,6 +244,8 @@ class GameLogic : public SubsystemInterface, public Snapshot // this should be called only by UpdateModule, thanks. void friend_awakenUpdateModule(Object* obj, UpdateModulePtr update, UnsignedInt whenToWakeUp); + Bool m_quitToDesktopAfterMatch; + protected: // snapshot methods diff --git a/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/QuitMenu.cpp b/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/QuitMenu.cpp index e9d1f7267c5..8b728eb587e 100644 --- a/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/QuitMenu.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/QuitMenu.cpp @@ -139,21 +139,7 @@ static void exitQuitMenu() { // destroy the quit menu destroyQuitMenu(); - - // clear out all the game data - if ( TheGameLogic->isInMultiplayerGame() && !TheGameLogic->isInSkirmishGame() && !TheGameInfo->isSandbox() ) - { - GameMessage *msg = TheMessageStream->appendMessage(GameMessage::MSG_SELF_DESTRUCT); - msg->appendBooleanArgument(TRUE); - } - TheGameLogic->exitGame(); - // TheGameLogic->clearGameData(); - // display the menu on top of the shell stack - // TheShell->showShell(); - - // this will trigger an exit - // TheGameEngine->setQuitting( TRUE ); - TheInGameUI->setClientQuiet( TRUE ); + TheGameLogic->quit(FALSE); } static void noExitQuitMenu() { @@ -164,18 +150,7 @@ static void quitToDesktopQuitMenu() { // destroy the quit menu destroyQuitMenu(); - - if (TheGameLogic->isInGame()) - { - if (TheRecorder->getMode() == RECORDERMODETYPE_RECORD) - { - TheRecorder->stopRecording(); - } - TheGameLogic->clearGameData(); - } - TheGameEngine->setQuitting(TRUE); - TheInGameUI->setClientQuiet( TRUE ); - + TheGameLogic->quit(TRUE); } static void surrenderQuitMenu() diff --git a/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp b/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp index 78f6f63984b..48814c78399 100644 --- a/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp @@ -535,6 +535,11 @@ void GameClient::update( void ) Int beginTime = timeGetTime(); while(beginTime + 4000 > timeGetTime() ) { + if (TheGameClient->isMovieAbortRequested()) + { + break; + } + TheWindowManager->update(); // redraw all views, update the GUI TheDisplay->draw(); @@ -755,6 +760,33 @@ void GameClient::updateHeadless() TheParticleSystemManager->reset(); } + // TheSuperHackers Check if the user has requested to abort movie +Bool GameClient::isMovieAbortRequested( void ) +{ + // User can skip video by pressing ESC + if (TheKeyboard) + { + TheKeyboard->UPDATE(); + KeyboardIO *io = TheKeyboard->findKey(KEY_ESC, KeyboardIO::STATUS_UNUSED); + if (io && BitIsSet(io->state, KEY_STATE_DOWN)) + { + io->setUsed(); + return TRUE; + } + } + + // Service OS for Window Close / Alt-F4 events + TheGameEngine->serviceWindowsOS(); + TheMessageStream->propagateMessages(); + + if (TheGameEngine->getQuitting() || (TheGameLogic && TheGameLogic->m_quitToDesktopAfterMatch)) + { + return TRUE; + } + + return FALSE; +} + /** ----------------------------------------------------------------------------------------------- * Call the given callback function for each object contained within the given region. */ diff --git a/Generals/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp b/Generals/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp index 42932540419..415b8f842ca 100644 --- a/Generals/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp @@ -3706,15 +3706,8 @@ GameMessageDisposition CommandTranslator::translateGameMessage(const GameMessage //------------------------------------------------------------------------------- DEMO MESSAGES //----------------------------------------------------------------------------------------- case GameMessage::MSG_META_DEMO_INSTANT_QUIT: - if (TheGameLogic->isInGame()) { - if (TheRecorder->getMode() == RECORDERMODETYPE_RECORD) - { - TheRecorder->stopRecording(); - } - TheGameLogic->clearGameData(); - } - TheGameEngine->setQuitting(TRUE); + TheGameLogic->quit(TRUE); disp = DESTROY_MESSAGE; break; @@ -5053,6 +5046,7 @@ static Bool isSystemMessage( const GameMessage *msg ) case GameMessage::MSG_LOGIC_CRC: case GameMessage::MSG_SET_REPLAY_CAMERA: case GameMessage::MSG_FRAME_TICK: + case GameMessage::MSG_META_DEMO_INSTANT_QUIT: return TRUE; } return FALSE; diff --git a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp index ed6339f87e6..ea28c0e5358 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -130,6 +130,8 @@ enum { OBJ_HASH_SIZE = 8192 }; /// The GameLogic singleton instance GameLogic *TheGameLogic = nullptr; +extern GameInfo *TheGameInfo; + static void findAndSelectCommandCenter(Object *obj, void* alreadyFound); @@ -244,6 +246,7 @@ GameLogic::GameLogic( void ) m_logicTimeScaleEnabledMemory = FALSE; m_loadScreen = nullptr; m_forceGameStartByTimeOut = FALSE; + m_quitToDesktopAfterMatch = FALSE; #ifdef DUMP_PERF_STATS m_overallFailedPathfinds = 0; #endif @@ -911,6 +914,8 @@ static void populateRandomStartPosition( GameInfo *game ) } } +struct QuitGameException {}; + // ------------------------------------------------------------------------------------------------ /** Update the load screen progress */ // ------------------------------------------------------------------------------------------------ @@ -920,6 +925,10 @@ void GameLogic::updateLoadProgress( Int progress ) if( m_loadScreen ) m_loadScreen->update( progress ); + if (TheGameEngine->getQuitting() || m_quitToDesktopAfterMatch) + { + throw QuitGameException(); + } } // ------------------------------------------------------------------------------------------------ @@ -966,6 +975,8 @@ void GameLogic::setGameMode( GameMode mode ) // ------------------------------------------------------------------------------------------------ void GameLogic::startNewGame( Bool saveGame ) { + try + { #ifdef DUMP_PERF_STATS __int64 startTime64; @@ -2067,7 +2078,11 @@ void GameLogic::startNewGame( Bool saveGame ) TheInGameUI->messageNoFormat( TheGameText->FETCH_OR_SUBSTITUTE( "GUI:FastForwardInstructions", L"Press F to toggle Fast Forward" ) ); } - + } + catch (QuitGameException&) + { + // TheSuperHackers: The application is cleanly aborting the loading process. + } } //----------------------------------------------------------------------------------------- @@ -3619,6 +3634,61 @@ void GameLogic::exitGame() TheMessageStream->appendMessage(GameMessage::MSG_CLEAR_GAME_DATA); } +// ------------------------------------------------------------------------------------------------ +void GameLogic::quit(Bool toDesktop) +{ + if (isInGame()) + { + if (isInMultiplayerGame() && !isInSkirmishGame() && TheGameInfo && !TheGameInfo->isSandbox()) + { + GameMessage *msg = TheMessageStream->appendMessage(GameMessage::MSG_SELF_DESTRUCT); + msg->appendBooleanArgument(TRUE); + } + + if (TheRecorder && TheRecorder->getMode() == RECORDERMODETYPE_RECORD) + { + TheRecorder->stopRecording(); + } + + setGamePaused(FALSE); + if (TheScriptEngine) + { + TheScriptEngine->forceUnfreezeTime(); + TheScriptEngine->doUnfreezeTime(); + } + + if (toDesktop) + { + if (isInMultiplayerGame()) + { + m_quitToDesktopAfterMatch = TRUE; + exitGame(); + } + else + { + clearGameData(); + } + } + else + { + exitGame(); + } + } + + if (toDesktop) + { + if (!isInMultiplayerGame()) + { + TheGameEngine->setQuitting(TRUE); + } + } + + if (TheInGameUI) + { + TheInGameUI->setClientQuiet(TRUE); + } +} + // ------------------------------------------------------------------------------------------------ /** A new GameLogic object has been constructed, therefore create * a corresponding drawable and bind them together. */ diff --git a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp index ef4e62d534e..79b5b6a915b 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp @@ -275,9 +275,10 @@ void GameLogic::clearGameData( Bool showScoreScreen ) // if(shellGame) - if (TheGlobalData->m_initialFile.isEmpty() == FALSE) + if (TheGlobalData->m_initialFile.isEmpty() == FALSE || m_quitToDesktopAfterMatch) { TheGameEngine->setQuitting(TRUE); + m_quitToDesktopAfterMatch = FALSE; } HideControlBar(); From 036ed84a3da6940cd2202d7609282977c1c01af6 Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Sun, 22 Feb 2026 03:32:51 +0100 Subject: [PATCH 4/9] Implemented feedback --- .../GameEngine/Source/GameClient/GUI/LoadScreen.cpp | 4 ++-- .../Code/GameEngine/Include/Common/MessageStream.h | 2 +- .../Code/GameEngine/Include/GameClient/GameClient.h | 2 +- .../GameEngine/Source/GameClient/GameClient.cpp | 8 ++++---- .../Source/GameClient/MessageStream/CommandXlat.cpp | 13 ++++--------- .../Source/GameLogic/System/GameLogic.cpp | 4 +--- .../Code/GameEngine/Include/GameClient/GameClient.h | 3 +-- .../GameEngine/Source/GameClient/GameClient.cpp | 8 ++++---- .../Source/GameLogic/System/GameLogic.cpp | 4 +--- 9 files changed, 19 insertions(+), 29 deletions(-) diff --git a/Core/GameEngine/Source/GameClient/GUI/LoadScreen.cpp b/Core/GameEngine/Source/GameClient/GUI/LoadScreen.cpp index 1a0dfccbd81..aff0c1e657a 100644 --- a/Core/GameEngine/Source/GameClient/GUI/LoadScreen.cpp +++ b/Core/GameEngine/Source/GameClient/GUI/LoadScreen.cpp @@ -60,6 +60,7 @@ #include "Common/GameEngine.h" #include "Common/GameLOD.h" #include "Common/GameState.h" +#include "Common/MessageStream.h" #include "Common/MultiplayerSettings.h" #include "Common/Player.h" #include "Common/PlayerList.h" @@ -76,7 +77,6 @@ #include "GameClient/LoadScreen.h" #include "GameClient/MapUtil.h" #include "GameClient/Mouse.h" -#include "Common/MessageStream.h" #include "GameClient/Shell.h" #include "GameClient/VideoPlayer.h" #include "GameClient/WindowLayout.h" @@ -160,7 +160,7 @@ void LoadScreen::update( Int percent ) { TheGameEngine->serviceWindowsOS(); TheMessageStream->propagateMessages(); - if (TheGameEngine->getQuitting() || TheGameLogic->m_quitToDesktopAfterMatch) + if (TheGameEngine->getQuitting() || (TheGameLogic && TheGameLogic->m_quitToDesktopAfterMatch)) return; //don't bother with any of this if the player is exiting game. TheWindowManager->update(); diff --git a/Generals/Code/GameEngine/Include/Common/MessageStream.h b/Generals/Code/GameEngine/Include/Common/MessageStream.h index 25413a73f80..a4fa3621cef 100644 --- a/Generals/Code/GameEngine/Include/Common/MessageStream.h +++ b/Generals/Code/GameEngine/Include/Common/MessageStream.h @@ -277,6 +277,7 @@ class GameMessage : public MemoryPoolObject MSG_META_TOGGLE_PAUSE_ALT, ///< TheSuperHackers @feature Toggle game pause (alternative mapping) MSG_META_STEP_FRAME, ///< TheSuperHackers @feature Step one frame MSG_META_STEP_FRAME_ALT, ///< TheSuperHackers @feature Step one frame (alternative mapping) + MSG_META_DEMO_INSTANT_QUIT, ///< bail out of game immediately // META items that are really for debug/demo/development use only... @@ -289,7 +290,6 @@ class GameMessage : public MemoryPoolObject MSG_META_DEMO_LOD_INCREASE, ///< increase LOD by 1 MSG_META_DEMO_TOGGLE_ZOOM_LOCK, ///< Toggle the camera zoom lock on/off MSG_META_DEMO_PLAY_CAMEO_MOVIE, ///< Play a movie in the cameo spot - MSG_META_DEMO_INSTANT_QUIT, ///< bail out of game immediately MSG_META_DEMO_TOGGLE_SPECIAL_POWER_DELAYS, ///< Toggle special power delays on/off MSG_META_DEMO_BATTLE_CRY, ///< battle cry MSG_META_DEMO_SWITCH_TEAMS, ///< switch local control to another team diff --git a/Generals/Code/GameEngine/Include/GameClient/GameClient.h b/Generals/Code/GameEngine/Include/GameClient/GameClient.h index f3dd95b709a..b27659389df 100644 --- a/Generals/Code/GameEngine/Include/GameClient/GameClient.h +++ b/Generals/Code/GameEngine/Include/GameClient/GameClient.h @@ -150,7 +150,7 @@ class GameClient : public SubsystemInterface, UnsignedInt getRenderedObjectCount() const { return m_renderedObjectCount; } void incrementRenderedObjectCount() { m_renderedObjectCount++; } - virtual Bool isMovieAbortRequested( void ); + virtual Bool isMovieAbortRequested(); protected: diff --git a/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp b/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp index 48814c78399..4895398ccec 100644 --- a/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp @@ -760,10 +760,10 @@ void GameClient::updateHeadless() TheParticleSystemManager->reset(); } - // TheSuperHackers Check if the user has requested to abort movie -Bool GameClient::isMovieAbortRequested( void ) +// TheSuperHackers @feature Check if the user has requested to abort movie +Bool GameClient::isMovieAbortRequested() { - // User can skip video by pressing ESC + // TheSuperHackers @feature User can skip video by pressing ESC if (TheKeyboard) { TheKeyboard->UPDATE(); @@ -775,7 +775,7 @@ Bool GameClient::isMovieAbortRequested( void ) } } - // Service OS for Window Close / Alt-F4 events +// TheSuperHackers @feature Service OS for Window Close / Alt-F4 events TheGameEngine->serviceWindowsOS(); TheMessageStream->propagateMessages(); diff --git a/Generals/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp b/Generals/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp index 415b8f842ca..164c9cd88f3 100644 --- a/Generals/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp @@ -3697,20 +3697,15 @@ GameMessageDisposition CommandTranslator::translateGameMessage(const GameMessage break; } - - -#if defined(RTS_DEBUG) - //------------------------------------------------------------------------- BEGIN DEMO MESSAGES - //------------------------------------------------------------------------- BEGIN DEMO MESSAGES - //------------------------------------------------------------------------- BEGIN DEMO MESSAGES - //------------------------------------------------------------------------------- DEMO MESSAGES - //----------------------------------------------------------------------------------------- + case GameMessage::MSG_META_DEMO_INSTANT_QUIT: - { + { TheGameLogic->quit(TRUE); disp = DESTROY_MESSAGE; break; + } +#if defined(RTS_DEBUG) //------------------------------------------------------------------------------- DEMO MESSAGES //----------------------------------------------------------------------------------------- case GameMessage::MSG_META_DEMO_SWITCH_TEAMS: diff --git a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp index ea28c0e5358..9f1ef16c53e 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -130,8 +130,6 @@ enum { OBJ_HASH_SIZE = 8192 }; /// The GameLogic singleton instance GameLogic *TheGameLogic = nullptr; -extern GameInfo *TheGameInfo; - static void findAndSelectCommandCenter(Object *obj, void* alreadyFound); @@ -2081,7 +2079,7 @@ void GameLogic::startNewGame( Bool saveGame ) } catch (QuitGameException&) { - // TheSuperHackers: The application is cleanly aborting the loading process. + // TheSuperHackers @info The application is cleanly aborting the loading process } } diff --git a/GeneralsMD/Code/GameEngine/Include/GameClient/GameClient.h b/GeneralsMD/Code/GameEngine/Include/GameClient/GameClient.h index a27a6901513..4a528905dd5 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameClient/GameClient.h +++ b/GeneralsMD/Code/GameEngine/Include/GameClient/GameClient.h @@ -155,8 +155,7 @@ class GameClient : public SubsystemInterface, void incrementRenderedObjectCount() { m_renderedObjectCount++; } virtual void notifyTerrainObjectMoved(Object *obj) = 0; - virtual Bool isMovieAbortRequested( void ); - + virtual Bool isMovieAbortRequested(); protected: diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp index 7ce3211a463..a5cd0e78d6d 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp @@ -798,10 +798,10 @@ void GameClient::updateHeadless() TheParticleSystemManager->reset(); } - // TheSuperHackers Check if the user has requested to abort movie -Bool GameClient::isMovieAbortRequested( void ) +// TheSuperHackers @feature Check if the user has requested to abort movie +Bool GameClient::isMovieAbortRequested() { - // User can skip video by pressing ESC + // TheSuperHackers @feature User can skip video by pressing ESC if (TheKeyboard) { TheKeyboard->UPDATE(); @@ -813,7 +813,7 @@ Bool GameClient::isMovieAbortRequested( void ) } } - // Service OS for Window Close / Alt-F4 events + // TheSuperHackers @feature Service OS for Window Close / Alt-F4 events TheGameEngine->serviceWindowsOS(); TheMessageStream->propagateMessages(); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp index 33bdaa0257d..296a34f77df 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -141,8 +141,6 @@ enum { OBJ_HASH_SIZE = 8192 }; /// The GameLogic singleton instance GameLogic *TheGameLogic = nullptr; -extern GameInfo *TheGameInfo; - static void findAndSelectCommandCenter(Object *obj, void* alreadyFound); @@ -2383,7 +2381,7 @@ void GameLogic::startNewGame( Bool loadingSaveGame ) } catch (QuitGameException&) { - // TheSuperHackers: The application is cleanly aborting the loading process. + // TheSuperHackers @info The application is cleanly aborting the loading process } } From 6b28aedfdcc128aad10f0f7e12869c8993c74e2b Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Tue, 24 Feb 2026 23:27:56 +0100 Subject: [PATCH 5/9] Apply suggestion from @greptile-apps[bot] Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- Generals/Code/GameEngine/Source/GameClient/GameClient.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp b/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp index e684077e792..75c8032670e 100644 --- a/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp @@ -775,7 +775,7 @@ Bool GameClient::isMovieAbortRequested() } } -// TheSuperHackers @feature Service OS for Window Close / Alt-F4 events + // TheSuperHackers @feature Service OS for Window Close / Alt-F4 events TheGameEngine->serviceWindowsOS(); TheMessageStream->propagateMessages(); From 6f2b0b88f9ba9b94701f7a84da51bde05a6359aa Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Fri, 27 Feb 2026 03:22:28 +0100 Subject: [PATCH 6/9] refine graceful exit logic and apply suggestions --- Core/GameEngine/Source/GameClient/GUI/LoadScreen.cpp | 10 +++++----- .../Code/GameEngine/Include/GameClient/GameClient.h | 2 +- Generals/Code/GameEngine/Include/GameLogic/GameLogic.h | 2 +- .../Code/GameEngine/Source/GameClient/GameClient.cpp | 3 +-- .../GameEngine/Source/GameLogic/System/GameLogic.cpp | 5 ++++- .../Code/GameEngine/Include/GameClient/GameClient.h | 2 +- .../Code/GameEngine/Include/GameLogic/GameLogic.h | 2 +- .../Code/GameEngine/Source/GameClient/GameClient.cpp | 3 +-- .../GameEngine/Source/GameLogic/System/GameLogic.cpp | 5 ++++- 9 files changed, 19 insertions(+), 15 deletions(-) diff --git a/Core/GameEngine/Source/GameClient/GUI/LoadScreen.cpp b/Core/GameEngine/Source/GameClient/GUI/LoadScreen.cpp index eae715316b9..b0a5b065429 100644 --- a/Core/GameEngine/Source/GameClient/GUI/LoadScreen.cpp +++ b/Core/GameEngine/Source/GameClient/GUI/LoadScreen.cpp @@ -542,7 +542,7 @@ void SinglePlayerLoadScreen::init( GameInfo *game ) Int shiftedPercent = -FRAME_FUDGE_ADD + 1; while (m_videoStream->frameIndex() < m_videoStream->frameCount() - 1 ) { - if (TheGameClient->isMovieAbortRequested()) + if (GameClient::isMovieAbortRequested()) { break; } @@ -628,7 +628,7 @@ void SinglePlayerLoadScreen::init( GameInfo *game ) fudgeFactor = 30 * ((currTime - begin)/ INT_TO_REAL(delay )); GadgetProgressBarSetProgress(m_progressBar, fudgeFactor); - if (TheGameClient->isMovieAbortRequested()) + if (GameClient::isMovieAbortRequested()) { break; } @@ -1053,7 +1053,7 @@ void ChallengeLoadScreen::init( GameInfo *game ) Int shiftedPercent = -FRAME_FUDGE_ADD + 1; while (m_videoStream->frameIndex() < m_videoStream->frameCount() - 1 ) { - if (TheGameClient->isMovieAbortRequested()) + if (GameClient::isMovieAbortRequested()) { break; } @@ -1100,7 +1100,7 @@ void ChallengeLoadScreen::init( GameInfo *game ) m_videoStream->frameGoto(m_videoStream->frameCount()); // zero based while(!m_videoStream->isFrameReady()) { - if (TheGameClient->isMovieAbortRequested()) + if (GameClient::isMovieAbortRequested()) { break; } @@ -1122,7 +1122,7 @@ void ChallengeLoadScreen::init( GameInfo *game ) fudgeFactor = 30 * ((currTime - begin)/ INT_TO_REAL(delay )); GadgetProgressBarSetProgress(m_progressBar, fudgeFactor); - if (TheGameClient->isMovieAbortRequested()) + if (GameClient::isMovieAbortRequested()) { break; } diff --git a/Generals/Code/GameEngine/Include/GameClient/GameClient.h b/Generals/Code/GameEngine/Include/GameClient/GameClient.h index e0c277d3805..2bd00f36d00 100644 --- a/Generals/Code/GameEngine/Include/GameClient/GameClient.h +++ b/Generals/Code/GameEngine/Include/GameClient/GameClient.h @@ -150,7 +150,7 @@ class GameClient : public SubsystemInterface, UnsignedInt getRenderedObjectCount() const { return m_renderedObjectCount; } void incrementRenderedObjectCount() { m_renderedObjectCount++; } - virtual Bool isMovieAbortRequested(); + static Bool isMovieAbortRequested(); protected: diff --git a/Generals/Code/GameEngine/Include/GameLogic/GameLogic.h b/Generals/Code/GameEngine/Include/GameLogic/GameLogic.h index bf17974bb89..ff5f4cf8c1d 100644 --- a/Generals/Code/GameEngine/Include/GameLogic/GameLogic.h +++ b/Generals/Code/GameEngine/Include/GameLogic/GameLogic.h @@ -131,7 +131,6 @@ class GameLogic : public SubsystemInterface, public Snapshot Bool isInGameLogicUpdate() const { return m_isInUpdate; } Bool hasUpdated() const { return m_hasUpdated; } ///< Returns true if the logic frame has advanced in the current client/render update UnsignedInt getFrame(); ///< Returns the current simulation frame number - void quit(Bool toDesktop); UnsignedInt getCRC( Int mode = CRC_CACHED, AsciiString deepCRCFileName = AsciiString::TheEmptyString ); ///< Returns the CRC void setObjectIDCounter( ObjectID nextObjID ) { m_nextObjID = nextObjID; } @@ -197,6 +196,7 @@ class GameLogic : public SubsystemInterface, public Snapshot UnsignedInt getFrameObjectsChangedTriggerAreas() {return m_frameObjectsChangedTriggerAreas;} void exitGame(); + void quit(Bool toDesktop); void clearGameData(Bool showScoreScreen = TRUE); ///< Clear the game data void closeWindows(); diff --git a/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp b/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp index 75c8032670e..8fb8fcde69a 100644 --- a/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp @@ -535,7 +535,7 @@ void GameClient::update() Int beginTime = timeGetTime(); while(beginTime + 4000 > timeGetTime() ) { - if (TheGameClient->isMovieAbortRequested()) + if (GameClient::isMovieAbortRequested()) { break; } @@ -760,7 +760,6 @@ void GameClient::updateHeadless() TheParticleSystemManager->reset(); } -// TheSuperHackers @feature Check if the user has requested to abort movie Bool GameClient::isMovieAbortRequested() { // TheSuperHackers @feature User can skip video by pressing ESC diff --git a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp index 47839bd5c88..0bc6ff01c31 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -3637,7 +3637,9 @@ void GameLogic::quit(Bool toDesktop) { if (isInGame()) { - if (isInMultiplayerGame() && !isInSkirmishGame() && TheGameInfo && !TheGameInfo->isSandbox()) + const Bool isSandbox = TheGameInfo && TheGameInfo->isSandbox(); + + if (isInMultiplayerGame() && TheGameInfo && !isSandbox) { GameMessage *msg = TheMessageStream->appendMessage(GameMessage::MSG_SELF_DESTRUCT); msg->appendBooleanArgument(TRUE); @@ -3649,6 +3651,7 @@ void GameLogic::quit(Bool toDesktop) } setGamePaused(FALSE); + if (TheScriptEngine) { TheScriptEngine->forceUnfreezeTime(); diff --git a/GeneralsMD/Code/GameEngine/Include/GameClient/GameClient.h b/GeneralsMD/Code/GameEngine/Include/GameClient/GameClient.h index 283752df1f4..e143fbd2b05 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameClient/GameClient.h +++ b/GeneralsMD/Code/GameEngine/Include/GameClient/GameClient.h @@ -155,7 +155,7 @@ class GameClient : public SubsystemInterface, void incrementRenderedObjectCount() { m_renderedObjectCount++; } virtual void notifyTerrainObjectMoved(Object *obj) = 0; - virtual Bool isMovieAbortRequested(); + static Bool isMovieAbortRequested(); protected: diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h index 8cd3c395eb1..1e33cfa563b 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h @@ -136,7 +136,6 @@ class GameLogic : public SubsystemInterface, public Snapshot Bool isInGameLogicUpdate() const { return m_isInUpdate; } Bool hasUpdated() const { return m_hasUpdated; } ///< Returns true if the logic frame has advanced in the current client/render update UnsignedInt getFrame(); ///< Returns the current simulation frame number - void quit(Bool toDesktop); UnsignedInt getCRC( Int mode = CRC_CACHED, AsciiString deepCRCFileName = AsciiString::TheEmptyString ); ///< Returns the CRC void setObjectIDCounter( ObjectID nextObjID ) { m_nextObjID = nextObjID; } @@ -212,6 +211,7 @@ class GameLogic : public SubsystemInterface, public Snapshot UnsignedInt getFrameObjectsChangedTriggerAreas() {return m_frameObjectsChangedTriggerAreas;} void exitGame(); + void quit(Bool toDesktop); void clearGameData(Bool showScoreScreen = TRUE); ///< Clear the game data void closeWindows(); diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp index 9e4f82ebc57..238cf5b0051 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp @@ -556,7 +556,7 @@ void GameClient::update() Int beginTime = timeGetTime(); while(beginTime + 4000 > timeGetTime() ) { - if (TheGameClient->isMovieAbortRequested()) + if (GameClient::isMovieAbortRequested()) { break; } @@ -798,7 +798,6 @@ void GameClient::updateHeadless() TheParticleSystemManager->reset(); } -// TheSuperHackers @feature Check if the user has requested to abort movie Bool GameClient::isMovieAbortRequested() { // TheSuperHackers @feature User can skip video by pressing ESC diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp index 52cf669fb95..abb6c01a21f 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -4188,7 +4188,9 @@ void GameLogic::quit(Bool toDesktop) { if (isInGame()) { - if (isInMultiplayerGame() && !isInSkirmishGame() && TheGameInfo && !TheGameInfo->isSandbox()) + const Bool isSandbox = TheGameInfo && TheGameInfo->isSandbox(); + + if (isInMultiplayerGame() && TheGameInfo && !isSandbox) { GameMessage *msg = TheMessageStream->appendMessage(GameMessage::MSG_SELF_DESTRUCT); msg->appendBooleanArgument(TRUE); @@ -4200,6 +4202,7 @@ void GameLogic::quit(Bool toDesktop) } setGamePaused(FALSE); + if (TheScriptEngine) { TheScriptEngine->forceUnfreezeTime(); From c655da42563aca9258da4ca4299dfbb93022b9a5 Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Tue, 3 Mar 2026 22:10:07 +0100 Subject: [PATCH 7/9] added tryStartNewGame to avoid long catch --- .../Code/GameEngine/Include/GameLogic/GameLogic.h | 1 + .../Source/GameLogic/System/GameLogic.cpp | 15 +++++++++------ .../Code/GameEngine/Include/GameLogic/GameLogic.h | 1 + .../Source/GameLogic/System/GameLogic.cpp | 15 +++++++++------ 4 files changed, 20 insertions(+), 12 deletions(-) diff --git a/Generals/Code/GameEngine/Include/GameLogic/GameLogic.h b/Generals/Code/GameEngine/Include/GameLogic/GameLogic.h index ff5f4cf8c1d..7d27118e0ae 100644 --- a/Generals/Code/GameEngine/Include/GameLogic/GameLogic.h +++ b/Generals/Code/GameEngine/Include/GameLogic/GameLogic.h @@ -153,6 +153,7 @@ class GameLogic : public SubsystemInterface, public Snapshot // super hack void startNewGame( Bool saveGame ); + void tryStartNewGame( Bool saveGame ); void loadMapINI( AsciiString mapName ); void updateLoadProgress( Int progress ); diff --git a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp index 0bc6ff01c31..12d8ed3c04a 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -975,7 +975,15 @@ void GameLogic::startNewGame( Bool saveGame ) { try { + tryStartNewGame(saveGame); + } + catch (QuitGameException&) + { + } +} +void GameLogic::tryStartNewGame( Bool saveGame ) +{ #ifdef DUMP_PERF_STATS __int64 startTime64; __int64 endTime64,freq64; @@ -2075,14 +2083,9 @@ void GameLogic::startNewGame( Bool saveGame ) { TheInGameUI->messageNoFormat( TheGameText->FETCH_OR_SUBSTITUTE( "GUI:FastForwardInstructions", L"Press F to toggle Fast Forward" ) ); } - - } - catch (QuitGameException&) - { - // TheSuperHackers @info The application is cleanly aborting the loading process - } } + //----------------------------------------------------------------------------------------- static void findAndSelectCommandCenter(Object *obj, void* alreadyFound) { diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h index 1e33cfa563b..2bd30a053dd 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h @@ -158,6 +158,7 @@ class GameLogic : public SubsystemInterface, public Snapshot // super hack void startNewGame( Bool loadSaveGame ); + void tryStartNewGame( Bool loadSaveGame ); void loadMapINI( AsciiString mapName ); void updateLoadProgress( Int progress ); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp index abb6c01a21f..2ca8cf9a452 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -1111,6 +1111,15 @@ void GameLogic::startNewGame( Bool loadingSaveGame ) { try { + tryStartNewGame(loadingSaveGame); + } + catch (QuitGameException&) + { + } +} + +void GameLogic::tryStartNewGame( Bool loadingSaveGame ) +{ #ifdef DUMP_PERF_STATS __int64 startTime64; @@ -2377,12 +2386,6 @@ void GameLogic::startNewGame( Bool loadingSaveGame ) { TheInGameUI->messageNoFormat( TheGameText->FETCH_OR_SUBSTITUTE( "GUI:FastForwardInstructions", L"Press F to toggle Fast Forward" ) ); } - - } - catch (QuitGameException&) - { - // TheSuperHackers @info The application is cleanly aborting the loading process - } } //----------------------------------------------------------------------------------------- From 4b7a87252b0a6a3b45fdc9794e6b6c5734dba30f Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Tue, 3 Mar 2026 22:18:14 +0100 Subject: [PATCH 8/9] added comment and removed blank link --- Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp | 2 +- .../Code/GameEngine/Source/GameLogic/System/GameLogic.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp index 12d8ed3c04a..e4bc4efa649 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -979,6 +979,7 @@ void GameLogic::startNewGame( Bool saveGame ) } catch (QuitGameException&) { + // TheSuperHackers @info The application is cleanly aborting the loading process } } @@ -2085,7 +2086,6 @@ void GameLogic::tryStartNewGame( Bool saveGame ) } } - //----------------------------------------------------------------------------------------- static void findAndSelectCommandCenter(Object *obj, void* alreadyFound) { diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp index 2ca8cf9a452..bc1a6da4fa1 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -1115,6 +1115,7 @@ void GameLogic::startNewGame( Bool loadingSaveGame ) } catch (QuitGameException&) { + // TheSuperHackers @info The application is cleanly aborting the loading process } } From c6ee10659173d829c81b660bfcdeb0e4ce40f100 Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Tue, 3 Mar 2026 23:25:57 +0100 Subject: [PATCH 9/9] Added isquitmenuvisible as check --- .../GUI/GUICallbacks/Menus/QuitMenu.cpp | 4 ++-- .../Source/GameLogic/System/GameLogic.cpp | 17 ++++++++++++----- .../GUI/GUICallbacks/Menus/QuitMenu.cpp | 4 ++-- .../Source/GameLogic/System/GameLogic.cpp | 17 ++++++++++++----- 4 files changed, 28 insertions(+), 14 deletions(-) diff --git a/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/QuitMenu.cpp b/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/QuitMenu.cpp index 910f0b157bc..2bf1e959f41 100644 --- a/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/QuitMenu.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/QuitMenu.cpp @@ -137,9 +137,9 @@ void destroyQuitMenu() */ static void exitQuitMenu() { + TheGameLogic->quit(FALSE); // destroy the quit menu destroyQuitMenu(); - TheGameLogic->quit(FALSE); } static void noExitQuitMenu() { @@ -148,9 +148,9 @@ static void noExitQuitMenu() static void quitToDesktopQuitMenu() { + TheGameLogic->quit(TRUE); // destroy the quit menu destroyQuitMenu(); - TheGameLogic->quit(TRUE); } static void surrenderQuitMenu() diff --git a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp index e4bc4efa649..75790105739 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -3640,12 +3640,19 @@ void GameLogic::quit(Bool toDesktop) { if (isInGame()) { - const Bool isSandbox = TheGameInfo && TheGameInfo->isSandbox(); - - if (isInMultiplayerGame() && TheGameInfo && !isSandbox) + if (isInInteractiveGame()) { - GameMessage *msg = TheMessageStream->appendMessage(GameMessage::MSG_SELF_DESTRUCT); - msg->appendBooleanArgument(TRUE); + if (!TheInGameUI->isQuitMenuVisible()) + { + ToggleQuitMenu(); + return; + } + + if (isInMultiplayerGame() && TheGameInfo && !TheGameInfo->isSandbox()) + { + GameMessage *msg = TheMessageStream->appendMessage(GameMessage::MSG_SELF_DESTRUCT); + msg->appendBooleanArgument(TRUE); + } } if (TheRecorder && TheRecorder->getMode() == RECORDERMODETYPE_RECORD) diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/QuitMenu.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/QuitMenu.cpp index ab68e8183b4..e35afd50b21 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/QuitMenu.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/QuitMenu.cpp @@ -137,9 +137,9 @@ void destroyQuitMenu() */ static void exitQuitMenu() { + TheGameLogic->quit(FALSE); // destroy the quit menu destroyQuitMenu(); - TheGameLogic->quit(FALSE); } static void noExitQuitMenu() { @@ -148,9 +148,9 @@ static void noExitQuitMenu() static void quitToDesktopQuitMenu() { + TheGameLogic->quit(TRUE); // destroy the quit menu destroyQuitMenu(); - TheGameLogic->quit(TRUE); } static void surrenderQuitMenu() diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp index bc1a6da4fa1..a77b33a0d4c 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -4192,12 +4192,19 @@ void GameLogic::quit(Bool toDesktop) { if (isInGame()) { - const Bool isSandbox = TheGameInfo && TheGameInfo->isSandbox(); - - if (isInMultiplayerGame() && TheGameInfo && !isSandbox) + if (isInInteractiveGame()) { - GameMessage *msg = TheMessageStream->appendMessage(GameMessage::MSG_SELF_DESTRUCT); - msg->appendBooleanArgument(TRUE); + if (!TheInGameUI->isQuitMenuVisible()) + { + ToggleQuitMenu(); + return; + } + + if (isInMultiplayerGame() && TheGameInfo && !TheGameInfo->isSandbox()) + { + GameMessage *msg = TheMessageStream->appendMessage(GameMessage::MSG_SELF_DESTRUCT); + msg->appendBooleanArgument(TRUE); + } } if (TheRecorder && TheRecorder->getMode() == RECORDERMODETYPE_RECORD)