Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
765d92b
Implement graceful exit for Alt+F4 and window close events
githubawn Feb 20, 2026
4fda496
Implement graceful exit during loading and movie screens
githubawn Feb 21, 2026
4465409
Backport graceful exit logic from GeneralsMD
githubawn Feb 22, 2026
036ed84
Implemented feedback
githubawn Feb 22, 2026
27abc6b
Merge branch 'main' into fix/graceful-exit-logic
githubawn Feb 24, 2026
6b28aed
Apply suggestion from @greptile-apps[bot]
githubawn Feb 24, 2026
6f2b0b8
refine graceful exit logic and apply suggestions
githubawn Feb 27, 2026
c655da4
added tryStartNewGame to avoid long catch
githubawn Mar 3, 2026
4b7a872
added comment and removed blank link
githubawn Mar 3, 2026
c6ee106
Added isquitmenuvisible as check
githubawn Mar 3, 2026
5249bf9
Make m_quitToDesktopAfterMatch private.
githubawn Mar 18, 2026
e61b8cd
Merge branch 'main' into fix/graceful-exit-logic
githubawn Mar 18, 2026
f8e6218
fix upstream gamelogic
githubawn Mar 18, 2026
9058ad7
added skirmish guard back
githubawn Mar 18, 2026
b7ded01
added Missing !getQuitting() guard in Generals WM_CLOSE
githubawn Mar 18, 2026
f5133f3
implement review feedback
githubawn Mar 18, 2026
9025859
small changes to comments
githubawn Mar 18, 2026
5e2f096
fixes frameDecompress() being called on a potentially non-ready frame
githubawn Mar 18, 2026
573f408
simplified CommandXlat
githubawn Mar 22, 2026
abf2b36
Potential null dereference in WM_QUERYENDSESSION else branch
githubawn Mar 22, 2026
4030b7d
move variable and include to more logical place
githubawn Mar 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 28 additions & 23 deletions Core/GameEngine/Source/GameClient/GUI/LoadScreen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -68,6 +69,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"
Expand Down Expand Up @@ -157,7 +159,8 @@ LoadScreen::~LoadScreen()
void LoadScreen::update( Int percent )
{
TheGameEngine->serviceWindowsOS();
if (TheGameEngine->getQuitting())
TheMessageStream->propagateMessages();
if (TheGameEngine->getQuitting() || (TheGameLogic && TheGameLogic->isQuitToDesktopRequested()))
return; //don't bother with any of this if the player is exiting game.

TheWindowManager->update();
Expand Down Expand Up @@ -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 (GameClient::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);
Expand Down Expand Up @@ -634,6 +628,11 @@ void SinglePlayerLoadScreen::init( GameInfo *game )
fudgeFactor = 30 * ((currTime - begin)/ INT_TO_REAL(delay ));
GadgetProgressBarSetProgress(m_progressBar, fudgeFactor);

if (GameClient::isMovieAbortRequested())
{
break;
}

TheWindowManager->update();
TheDisplay->draw();
Sleep(100);
Expand Down Expand Up @@ -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 (GameClient::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);
Expand Down Expand Up @@ -1109,7 +1099,17 @@ void ChallengeLoadScreen::init( GameInfo *game )
// if we're min speced
m_videoStream->frameGoto(m_videoStream->frameCount()); // zero based
while(!m_videoStream->isFrameReady())
{
if (GameClient::isMovieAbortRequested())
{
break;
}
Sleep(1);
}
if (!m_videoStream->isFrameReady())
{
return;
}
m_videoStream->frameDecompress();
m_videoStream->frameRender(m_videoBuffer);
if(m_videoBuffer)
Expand All @@ -1126,6 +1126,11 @@ void ChallengeLoadScreen::init( GameInfo *game )
fudgeFactor = 30 * ((currTime - begin)/ INT_TO_REAL(delay ));
GadgetProgressBarSetProgress(m_progressBar, fudgeFactor);

if (GameClient::isMovieAbortRequested())
{
break;
}

TheWindowManager->update();
TheDisplay->draw();
Sleep(100);
Expand Down
2 changes: 1 addition & 1 deletion Generals/Code/GameEngine/Include/Common/MessageStream.h
Original file line number Diff line number Diff line change
Expand Up @@ -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...
Expand All @@ -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
Expand Down
2 changes: 2 additions & 0 deletions Generals/Code/GameEngine/Include/GameClient/GameClient.h
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,8 @@ class GameClient : public SubsystemInterface,
UnsignedInt getRenderedObjectCount() const { return m_renderedObjectCount; }
void incrementRenderedObjectCount() { m_renderedObjectCount++; }

static Bool isMovieAbortRequested();

protected:

// snapshot methods
Expand Down
6 changes: 6 additions & 0 deletions Generals/Code/GameEngine/Include/GameLogic/GameLogic.h
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ class GameLogic : public SubsystemInterface, public Snapshot
UnsignedInt getFrameObjectsChangedTriggerAreas() {return m_frameObjectsChangedTriggerAreas;}

void exitGame();
void quit(Bool toDesktop, Bool force = FALSE);
void clearGameData(Bool showScoreScreen = TRUE); ///< Clear the game data
void closeWindows();

Expand Down Expand Up @@ -253,6 +254,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 isQuitToDesktopRequested() const { return m_quitToDesktopAfterMatch; }

protected:

// snapshot methods
Expand All @@ -262,6 +265,8 @@ class GameLogic : public SubsystemInterface, public Snapshot

private:

void tryStartNewGame( Bool loadSaveGame );

void updateDisplayBusyState();

void pauseGameLogic(Bool paused);
Expand Down Expand Up @@ -306,6 +311,7 @@ class GameLogic : public SubsystemInterface, public Snapshot
Bool m_loadingMap;
Bool m_loadingSave;
Bool m_clearingGameData;
Bool m_quitToDesktopAfterMatch;

Bool m_isInUpdate;
Bool m_hasUpdated;
Expand Down
2 changes: 1 addition & 1 deletion Generals/Code/GameEngine/Source/Common/MessageStream.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,7 @@ const char *GameMessage::getCommandTypeAsString(GameMessage::Type t)
CASE_LABEL(MSG_META_TOGGLE_PAUSE_ALT)
CASE_LABEL(MSG_META_STEP_FRAME)
CASE_LABEL(MSG_META_STEP_FRAME_ALT)
CASE_LABEL(MSG_META_DEMO_INSTANT_QUIT)

#if defined(RTS_DEBUG)
CASE_LABEL(MSG_META_DEMO_TOGGLE_BEHIND_BUILDINGS)
Expand All @@ -389,7 +390,6 @@ const char *GameMessage::getCommandTypeAsString(GameMessage::Type t)
CASE_LABEL(MSG_META_DEMO_LOD_INCREASE)
CASE_LABEL(MSG_META_DEMO_TOGGLE_ZOOM_LOCK)
CASE_LABEL(MSG_META_DEMO_PLAY_CAMEO_MOVIE)
CASE_LABEL(MSG_META_DEMO_INSTANT_QUIT)
CASE_LABEL(MSG_META_DEMO_TOGGLE_SPECIAL_POWER_DELAYS)
CASE_LABEL(MSG_META_DEMO_BATTLE_CRY)
CASE_LABEL(MSG_META_DEMO_SWITCH_TEAMS)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,23 +137,9 @@ void destroyQuitMenu()
*/
static void exitQuitMenu()
{
TheGameLogic->quit(FALSE);
// 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 );
}
static void noExitQuitMenu()
{
Expand All @@ -162,20 +148,9 @@ static void noExitQuitMenu()

static void quitToDesktopQuitMenu()
{
TheGameLogic->quit(TRUE);
// destroy the quit menu
destroyQuitMenu();

if (TheGameLogic->isInGame())
{
if (TheRecorder->getMode() == RECORDERMODETYPE_RECORD)
{
TheRecorder->stopRecording();
}
TheGameLogic->clearGameData();
}
TheGameEngine->setQuitting(TRUE);
TheInGameUI->setClientQuiet( TRUE );

}

static void surrenderQuitMenu()
Expand Down
31 changes: 31 additions & 0 deletions Generals/Code/GameEngine/Source/GameClient/GameClient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,11 @@ void GameClient::update()
Int beginTime = timeGetTime();
while(beginTime + 4000 > timeGetTime() )
{
if (GameClient::isMovieAbortRequested())
{
break;
}

TheWindowManager->update();
// redraw all views, update the GUI
TheDisplay->draw();
Expand Down Expand Up @@ -752,6 +757,32 @@ void GameClient::updateHeadless()
TheParticleSystemManager->update();
}

Bool GameClient::isMovieAbortRequested()
{
// TheSuperHackers @feature 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;
}
}

// TheSuperHackers @feature Service OS for Window Close / Alt-F4 events
TheGameEngine->serviceWindowsOS();
TheMessageStream->propagateMessages();

if (TheGameEngine->getQuitting() || (TheGameLogic && TheGameLogic->isQuitToDesktopRequested()))
{
return TRUE;
}

return FALSE;
}

/** -----------------------------------------------------------------------------------------------
* Call the given callback function for each object contained within the given region.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3699,27 +3699,16 @@ 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:
if (TheGameLogic->isInGame())
{
if (TheRecorder->getMode() == RECORDERMODETYPE_RECORD)
{
TheRecorder->stopRecording();
}
TheGameLogic->clearGameData();
}
TheGameEngine->setQuitting(TRUE);
{
Bool force = msg->getArgument(0)->boolean;
TheGameLogic->quit(TRUE, force);
disp = DESTROY_MESSAGE;
break;
}

#if defined(RTS_DEBUG)
//------------------------------------------------------------------------------- DEMO MESSAGES
//-----------------------------------------------------------------------------------------
case GameMessage::MSG_META_DEMO_SWITCH_TEAMS:
Expand Down Expand Up @@ -5055,6 +5044,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;
Expand Down
Loading
Loading