diff --git a/Core/GameEngine/Source/GameClient/GUI/LoadScreen.cpp b/Core/GameEngine/Source/GameClient/GUI/LoadScreen.cpp index 1a45016368d..f0c0512a8e4 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" @@ -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" @@ -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(); @@ -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); @@ -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); @@ -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); @@ -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) @@ -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); diff --git a/Generals/Code/GameEngine/Include/Common/MessageStream.h b/Generals/Code/GameEngine/Include/Common/MessageStream.h index 61efa69b9be..3dea88f4dfa 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 dd9bb98ce1a..2bd00f36d00 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++; } + static Bool isMovieAbortRequested(); + protected: // snapshot methods diff --git a/Generals/Code/GameEngine/Include/GameLogic/GameLogic.h b/Generals/Code/GameEngine/Include/GameLogic/GameLogic.h index 82930c4cf22..1facfe883d2 100644 --- a/Generals/Code/GameEngine/Include/GameLogic/GameLogic.h +++ b/Generals/Code/GameEngine/Include/GameLogic/GameLogic.h @@ -152,7 +152,7 @@ class GameLogic : public SubsystemInterface, public Snapshot ObjectID allocateObjectID(); ///< Returns a new unique object id // super hack - void startNewGame( Bool loadSaveGame ); + void startNewGame( Bool saveGame ); void loadMapINI( AsciiString mapName ); void updateLoadProgress( Int progress ); @@ -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(); @@ -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 @@ -262,6 +265,9 @@ class GameLogic : public SubsystemInterface, public Snapshot private: + void tryStartNewGame( Bool loadSaveGame ); + Bool m_quitToDesktopAfterMatch; + void updateDisplayBusyState(); void pauseGameLogic(Bool paused); diff --git a/Generals/Code/GameEngine/Source/Common/MessageStream.cpp b/Generals/Code/GameEngine/Source/Common/MessageStream.cpp index 5df63dfb0e3..875abf9e85e 100644 --- a/Generals/Code/GameEngine/Source/Common/MessageStream.cpp +++ b/Generals/Code/GameEngine/Source/Common/MessageStream.cpp @@ -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) @@ -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) 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 d5b957f7dfb..7309c7b2eb7 100644 --- a/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/QuitMenu.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/QuitMenu.cpp @@ -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() { @@ -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() diff --git a/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp b/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp index ad59f27d9e9..a3e467717a5 100644 --- a/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp @@ -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(); @@ -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. */ diff --git a/Generals/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp b/Generals/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp index a8f6abfa763..272c7978665 100644 --- a/Generals/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp @@ -3699,27 +3699,20 @@ 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()) + { + Bool force = FALSE; + if (msg->getArgumentCount() > 0) { - if (TheRecorder->getMode() == RECORDERMODETYPE_RECORD) - { - TheRecorder->stopRecording(); - } - TheGameLogic->clearGameData(); + force = msg->getArgument(0)->boolean; } - TheGameEngine->setQuitting(TRUE); + TheGameLogic->quit(TRUE, force); disp = DESTROY_MESSAGE; break; + } +#if defined(RTS_DEBUG) //------------------------------------------------------------------------------- DEMO MESSAGES //----------------------------------------------------------------------------------------- case GameMessage::MSG_META_DEMO_SWITCH_TEAMS: @@ -5055,6 +5048,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 bf20ed2d157..d85d03c27ac 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -106,6 +106,8 @@ #include "GameNetwork/LANAPICallbacks.h" #include "GameNetwork/NetworkInterface.h" +struct QuitGameException {}; + DECLARE_PERF_TIMER(SleepyMaintenance) #include "Common/UnitTimings.h" //Contains the DO_UNIT_TIMINGS define jba. @@ -244,6 +246,7 @@ GameLogic::GameLogic() m_logicTimeScaleEnabledMemory = FALSE; m_loadScreen = nullptr; m_forceGameStartByTimeOut = FALSE; + m_quitToDesktopAfterMatch = FALSE; #ifdef DUMP_PERF_STATS m_overallFailedPathfinds = 0; #endif @@ -924,6 +927,10 @@ void GameLogic::updateLoadProgress( Int progress ) if( m_loadScreen ) m_loadScreen->update( progress ); + if (TheGameEngine->getQuitting() || m_quitToDesktopAfterMatch) + { + throw QuitGameException(); + } } // ------------------------------------------------------------------------------------------------ @@ -965,7 +972,18 @@ void GameLogic::setGameMode( GameMode mode ) // ------------------------------------------------------------------------------------------------ void GameLogic::startNewGame( Bool loadingSaveGame ) { + try + { + tryStartNewGame(loadingSaveGame); + } + catch (QuitGameException&) + { + // TheSuperHackers @info The application is cleanly aborting the loading process + } +} +void GameLogic::tryStartNewGame( Bool loadingSaveGame ) +{ #ifdef DUMP_PERF_STATS __int64 startTime64; __int64 endTime64,freq64; @@ -2068,8 +2086,6 @@ void GameLogic::startNewGame( Bool loadingSaveGame ) { TheInGameUI->messageNoFormat( TheGameText->FETCH_OR_SUBSTITUTE( "GUI:FastForwardInstructions", L"Press F to toggle Fast Forward" ) ); } - - } //----------------------------------------------------------------------------------------- @@ -3621,6 +3637,86 @@ void GameLogic::exitGame() TheMessageStream->appendMessage(GameMessage::MSG_CLEAR_GAME_DATA); } +// ------------------------------------------------------------------------------------------------ +void GameLogic::quit(Bool toDesktop, Bool force) +{ + if (isInGame()) + { + if (isInInteractiveGame()) + { + if (force == FALSE) + { + // TheSuperHackers @info Check TheInGameUI to prevent a potential early-startup or late-shutdown crash. + if (TheGameEngine->isActive() && (!TheInGameUI || !TheInGameUI->isQuitMenuVisible())) + { + // TheSuperHackers @info For window X-button clicks (force == FALSE), show the quit menu. + // We verify we are not loading. Toggling the UI during a load screen would crash. + if (!isLoadingMap() && !isLoadingSave()) + { + ToggleQuitMenu(); + return; + } + } + } + + 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 && !isLoadingMap() && !isLoadingSave()) + { + TheScriptEngine->forceUnfreezeTime(); + TheScriptEngine->doUnfreezeTime(); + } + + if (toDesktop) + { + if (isInMultiplayerGame()) + { + m_quitToDesktopAfterMatch = TRUE; + if (!isLoadingMap() && !isLoadingSave()) + { + exitGame(); + } + } + else + { + if (!isLoadingMap() && !isLoadingSave()) + { + 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 369d76f11e7..4705b1f08d8 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp @@ -277,9 +277,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(); diff --git a/Generals/Code/Main/WinMain.cpp b/Generals/Code/Main/WinMain.cpp index cf1fcc8b27a..ee41808aa84 100644 --- a/Generals/Code/Main/WinMain.cpp +++ b/Generals/Code/Main/WinMain.cpp @@ -56,6 +56,7 @@ #include "GameClient/InGameUI.h" #include "GameClient/GameClient.h" #include "GameLogic/GameLogic.h" ///< @todo for demo, remove +#include "Common/PlayerList.h" #include "GameClient/Mouse.h" #include "GameClient/IMEManager.h" #include "Win32Device/GameClient/Win32Mouse.h" @@ -366,10 +367,24 @@ LRESULT CALLBACK WndProc( HWND hWnd, UINT message, // ------------------------------------------------------------------------ case WM_CLOSE: - TheGameEngine->checkAbnormalQuitting(); - TheGameEngine->reset(); - TheGameEngine->setQuitting(TRUE); - _exit(EXIT_SUCCESS); + if (TheGameEngine && !TheGameEngine->getQuitting()) + { + // TheSuperHackers @info ThePlayerList initialized ensures we can safely append messages. + // If not, we're likely in early startup/late shutdown, so we hard-quit instead. + if (TheMessageStream && ThePlayerList) + { + Bool altDown = (GetAsyncKeyState(VK_MENU) & 0x8000) != 0; + Bool f4Down = (GetAsyncKeyState(VK_F4) & 0x8000) != 0; + Bool isAltF4 = altDown && f4Down; + + GameMessage *msg = TheMessageStream->appendMessage(GameMessage::MSG_META_DEMO_INSTANT_QUIT); + msg->appendBooleanArgument(isAltF4); + } + else + { + TheGameEngine->setQuitting(TRUE); + } + } return 0; //------------------------------------------------------------------------- diff --git a/GeneralsMD/Code/GameEngine/Include/GameClient/GameClient.h b/GeneralsMD/Code/GameEngine/Include/GameClient/GameClient.h index 7959a404cd7..e143fbd2b05 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameClient/GameClient.h +++ b/GeneralsMD/Code/GameEngine/Include/GameClient/GameClient.h @@ -155,6 +155,7 @@ class GameClient : public SubsystemInterface, void incrementRenderedObjectCount() { m_renderedObjectCount++; } virtual void notifyTerrainObjectMoved(Object *obj) = 0; + static Bool isMovieAbortRequested(); protected: diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h index 160d1dbd4cf..675bc1474bb 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h @@ -211,6 +211,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(); @@ -261,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 isQuitToDesktopRequested() const { return m_quitToDesktopAfterMatch; } + protected: // snapshot methods @@ -270,6 +273,9 @@ class GameLogic : public SubsystemInterface, public Snapshot private: + void tryStartNewGame( Bool loadSaveGame ); + Bool m_quitToDesktopAfterMatch; + void updateDisplayBusyState(); void pauseGameLogic(Bool paused); 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 5273e4e6387..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,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() { @@ -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() diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp index 786395fbffb..d81064ee50c 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp @@ -556,6 +556,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(); @@ -789,6 +794,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. */ diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp index 089b1566bc0..6abaf0441fd 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; @@ -4078,26 +4074,17 @@ GameMessageDisposition CommandTranslator::translateGameMessage(const GameMessage } - - -#ifdef ALLOW_ALT_F4 case GameMessage::MSG_META_DEMO_INSTANT_QUIT: - { - if (TheGameLogic->isInGame()) + { + Bool force = FALSE; + if (msg->getArgumentCount() > 0) { - if (TheRecorder->getMode() == RECORDERMODETYPE_RECORD) - { - TheRecorder->stopRecording(); - } - TheGameLogic->clearGameData(); + force = msg->getArgument(0)->boolean; } - TheGameEngine->setQuitting(TRUE); + TheGameLogic->quit(TRUE, force); disp = DESTROY_MESSAGE; break; - } -#endif - - + } //------------------------------------------------------------------------------- DEMO MESSAGES @@ -5588,6 +5575,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 1d671f180f5..fb542f65cd6 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -113,6 +113,8 @@ #include +struct QuitGameException {}; + DECLARE_PERF_TIMER(SleepyMaintenance) #include "Common/UnitTimings.h" //Contains the DO_UNIT_TIMINGS define jba. @@ -262,6 +264,7 @@ GameLogic::GameLogic() m_loadingMap = FALSE; m_loadingSave = FALSE; m_clearingGameData = FALSE; + m_quitToDesktopAfterMatch = FALSE; } //------------------------------------------------------------------------------------------------- @@ -1061,6 +1064,10 @@ void GameLogic::updateLoadProgress( Int progress ) if( m_loadScreen ) m_loadScreen->update( progress ); + if (TheGameEngine->getQuitting() || m_quitToDesktopAfterMatch) + { + throw QuitGameException(); + } } // ------------------------------------------------------------------------------------------------ @@ -1101,6 +1108,18 @@ void GameLogic::setGameMode( GameMode mode ) * point and ready to load up with all the data */ // ------------------------------------------------------------------------------------------------ void GameLogic::startNewGame( Bool loadingSaveGame ) +{ + try + { + tryStartNewGame(loadingSaveGame); + } + catch (QuitGameException&) + { + // TheSuperHackers @info The application is cleanly aborting the loading process + } +} + +void GameLogic::tryStartNewGame( Bool loadingSaveGame ) { #ifdef DUMP_PERF_STATS @@ -2368,8 +2387,6 @@ void GameLogic::startNewGame( Bool loadingSaveGame ) { TheInGameUI->messageNoFormat( TheGameText->FETCH_OR_SUBSTITUTE( "GUI:FastForwardInstructions", L"Press F to toggle Fast Forward" ) ); } - - } //----------------------------------------------------------------------------------------- @@ -4170,6 +4187,86 @@ void GameLogic::exitGame() TheMessageStream->appendMessage(GameMessage::MSG_CLEAR_GAME_DATA); } +// ------------------------------------------------------------------------------------------------ +void GameLogic::quit(Bool toDesktop, Bool force) +{ + if (isInGame()) + { + if (isInInteractiveGame()) + { + if (force == FALSE) + { + // TheSuperHackers @info Check TheInGameUI to prevent a potential early-startup or late-shutdown crash. + if (TheGameEngine->isActive() && (!TheInGameUI || !TheInGameUI->isQuitMenuVisible())) + { + // TheSuperHackers @info For window X-button clicks (force == FALSE), show the quit menu. + // We verify we are not loading. Toggling the UI during a load screen would crash. + if (!isLoadingMap() && !isLoadingSave()) + { + ToggleQuitMenu(); + return; + } + } + } + + 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 && !isLoadingMap() && !isLoadingSave()) + { + TheScriptEngine->forceUnfreezeTime(); + TheScriptEngine->doUnfreezeTime(); + } + + if (toDesktop) + { + if (isInMultiplayerGame()) + { + m_quitToDesktopAfterMatch = TRUE; + if (!isLoadingMap() && !isLoadingSave()) + { + exitGame(); + } + } + else + { + if (!isLoadingMap() && !isLoadingSave()) + { + 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 a136378ad62..eac04089cd3 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(); diff --git a/GeneralsMD/Code/Main/WinMain.cpp b/GeneralsMD/Code/Main/WinMain.cpp index baac82f8a13..3a0a6ac7dd9 100644 --- a/GeneralsMD/Code/Main/WinMain.cpp +++ b/GeneralsMD/Code/Main/WinMain.cpp @@ -57,6 +57,7 @@ #include "GameClient/InGameUI.h" #include "GameClient/GameClient.h" #include "GameLogic/GameLogic.h" ///< @todo for demo, remove +#include "Common/PlayerList.h" #include "GameClient/Mouse.h" #include "GameClient/IMEManager.h" #include "Win32Device/GameClient/Win32Mouse.h" @@ -369,28 +370,37 @@ LRESULT CALLBACK WndProc( HWND hWnd, UINT message, case WM_QUERYENDSESSION: { - TheMessageStream->appendMessage(GameMessage::MSG_META_DEMO_INSTANT_QUIT); + // TheSuperHackers @info ThePlayerList initialized ensures we can safely append messages. + // If not, we're likely in early startup/late shutdown, so we hard-quit instead. + if (TheMessageStream && ThePlayerList) + { + GameMessage *msg = TheMessageStream->appendMessage(GameMessage::MSG_META_DEMO_INSTANT_QUIT); + msg->appendBooleanArgument(TRUE); // Force quit on Windows shutdown + } + else + { + TheGameEngine->setQuitting(TRUE); + } return 0; //don't allow Windows to shutdown while game is running. } // ------------------------------------------------------------------------ case WM_CLOSE: - if (!TheGameEngine->getQuitting()) + if (TheGameEngine && !TheGameEngine->getQuitting()) { - //user is exiting without using the menus - - //This method didn't work in cinematics because we don't process messages. - //But it's the cleanest way to exit that's similar to using menus. - TheMessageStream->appendMessage(GameMessage::MSG_META_DEMO_INSTANT_QUIT); - - //This method used to disable quitting. We just put up the options screen instead. - //TheMessageStream->appendMessage(GameMessage::MSG_META_OPTIONS); + if (TheMessageStream && ThePlayerList) + { + Bool altDown = (GetAsyncKeyState(VK_MENU) & 0x8000) != 0; + Bool f4Down = (GetAsyncKeyState(VK_F4) & 0x8000) != 0; + Bool isAltF4 = altDown && f4Down; - //This method works everywhere but isn't as clean at shutting down. - //TheGameEngine->checkAbnormalQuitting(); //old way to log disconnections for ALT-F4 - //TheGameEngine->reset(); - //TheGameEngine->setQuitting(TRUE); - //_exit(EXIT_SUCCESS); + GameMessage *msg = TheMessageStream->appendMessage(GameMessage::MSG_META_DEMO_INSTANT_QUIT); + msg->appendBooleanArgument(isAltF4); + } + else + { + TheGameEngine->setQuitting(TRUE); + } } return 0;