/* ** Command & Conquer Generals Zero Hour(tm) ** Copyright 2025 Electronic Arts Inc. ** ** This program is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program. If not, see . */ //////////////////////////////////////////////////////////////////////////////// // // // (c) 2001-2003 Electronic Arts Inc. // // // //////////////////////////////////////////////////////////////////////////////// // FILE: Shell.cpp //////////////////////////////////////////////////////////////////////////////// // Author: Colin Day, September 2001 // Description: Shell menu representations /////////////////////////////////////////////////////////////////////////////////////////////////// // INCLUDES /////////////////////////////////////////////////////////////////////////////////////// #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine #include "Common/RandomValue.h" #include "GameClient/Shell.h" #include "GameClient/WindowLayout.h" #include "GameClient/GameWindowManager.h" #include "GameClient/GameWindowTransitions.h" #include "GameClient/IMEManager.h" #include "GameClient/AnimateWindowManager.h" #include "GameClient/ShellMenuScheme.h" #include "GameLogic/GameLogic.h" #include "GameNetwork/GameSpyOverlay.h" #include "GameNetwork/GameSpy/PeerDefsImplementation.h" #include // PUBLIC DATA //////////////////////////////////////////////////////////////////////////////////// Shell *TheShell = NULL; ///< the shell singleton definition #ifdef _INTERNAL // for occasional debugging... //#pragma optimize("", off) //#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") #endif // PUBLIC FUNCTIONS /////////////////////////////////////////////////////////////////////////////// //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- Shell::Shell( void ) { Int i; m_screenCount = 0; for( i = 0; i < MAX_SHELL_STACK; i++ ) m_screenStack[ i ] = NULL; m_pendingPush = FALSE; m_pendingPop = FALSE; m_pendingPushName.set( "" ); m_isShellActive = TRUE; m_shellMapOn = FALSE; m_background = NULL; m_clearBackground = FALSE; m_animateWindowManager = NEW AnimateWindowManager; m_schemeManager = NEW ShellMenuSchemeManager; m_saveLoadMenuLayout = NULL; m_popupReplayLayout = NULL; //Added By Sadullah Nader //Initializations m_optionsLayout = NULL; m_screenCount = 0; // } // end Shell //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- Shell::~Shell( void ) { WindowLayout *newTop = top(); while(newTop) { popImmediate(); newTop = top(); } if(m_background) { m_background->destroyWindows(); m_background->deleteInstance(); m_background = NULL; } if(m_animateWindowManager) delete m_animateWindowManager; m_animateWindowManager = NULL; if(m_schemeManager) delete m_schemeManager; m_schemeManager = NULL; // delete the save/load menu if present if( m_saveLoadMenuLayout ) { m_saveLoadMenuLayout->destroyWindows(); m_saveLoadMenuLayout->deleteInstance(); m_saveLoadMenuLayout = NULL; } //end if // delete the replay save menu if present if( m_popupReplayLayout ) { m_popupReplayLayout->destroyWindows(); m_popupReplayLayout->deleteInstance(); m_popupReplayLayout = NULL; } //end if // delete the options menu if present. if (m_optionsLayout != NULL) { m_optionsLayout->destroyWindows(); m_optionsLayout->deleteInstance(); m_optionsLayout = NULL; } } // end ~Shell //------------------------------------------------------------------------------------------------- /** Initialize the shell system */ //------------------------------------------------------------------------------------------------- void Shell::init( void ) { INI ini; // Read from INI all the ShellMenuScheme ini.load( AsciiString( "Data\\INI\\Default\\ShellMenuScheme.ini" ), INI_LOAD_OVERWRITE, NULL ); ini.load( AsciiString( "Data\\INI\\ShellMenuScheme.ini" ), INI_LOAD_OVERWRITE, NULL ); if( m_schemeManager ) m_schemeManager->init(); } // end init //------------------------------------------------------------------------------------------------- /** Reset the shell system to a clean state just as though init had * just been called and ready to re-use */ //------------------------------------------------------------------------------------------------- void Shell::reset( void ) { if (TheIMEManager) TheIMEManager->detatch(); // pop all screens while( m_screenCount ) pop(); m_animateWindowManager->reset(); } // end reset //------------------------------------------------------------------------------------------------- /** Update shell system cycle. All windows are updated that are on the stack, starting * with the top layout and progressing to the bottom one */ //------------------------------------------------------------------------------------------------- void Shell::update( void ) { static Int lastUpdate = timeGetTime(); static const Int shellUpdateDelay = 30; // try to update 30 frames a second Int now = timeGetTime(); // // we keep the shell updates fixed in time so that we can write consitent animation // speeds during the screen update functions // if( now - lastUpdate >= ((1000.0f / shellUpdateDelay ) - 1) ) { // run the updates for every window layout on the stack for( Int i = m_screenCount - 1; i >= 0; i-- ) { DEBUG_ASSERTCRASH( m_screenStack[ i ], ("Top of shell stack is NULL!\n") ); m_screenStack[ i ]->runUpdate( NULL ); } // end for i if(TheGlobalData->m_shellMapOn && m_shellMapOn &&m_background) { m_background->destroyWindows(); m_background->deleteInstance(); m_background = NULL; } // Update the animate window manager m_animateWindowManager->update(); m_schemeManager->update(); // mark last time we ran the updates lastUpdate = now; } // end if } // end update //------------------------------------------------------------------------------------------------- /** Find a screen via the .wnd script filename loaded */ //------------------------------------------------------------------------------------------------- WindowLayout *Shell::findScreenByFilename( AsciiString filename ) { if (filename.isEmpty()) return NULL; // search screen list WindowLayout *screen; Int i; for( i = 0; i < MAX_SHELL_STACK; i++ ) { screen = m_screenStack[ i ]; if( screen && filename.compareNoCase(screen->getFilename()) == 0 ) return screen; } // end for i return NULL; } // end findScreenByFilename //------------------------------------------------------------------------------------------------- /** Hide or unhide all window layouts loaded */ //------------------------------------------------------------------------------------------------- void Shell::hide( Bool hide ) { Int i; for( i = 0; i < MAX_SHELL_STACK; i++ ) if( m_screenStack[ i ] ) m_screenStack[ i ]->hide( hide ); if (TheIMEManager) TheIMEManager->detatch(); } // end hide //------------------------------------------------------------------------------------------------- /** Push layout onto shell */ //------------------------------------------------------------------------------------------------- void Shell::push( AsciiString filename, Bool shutdownImmediate ) { // sanity if( filename.isEmpty() ) return; if(TheGameSpyInfo) GameSpyCloseAllOverlays(); #ifdef DEBUG_LOGGING DEBUG_LOG(("Shell:push(%s) - stack was\n", filename.str())); for (Int i=0; igetFilename().str())); } #endif // make sure we have an available spot for another screen if( m_screenCount >= MAX_SHELL_STACK ) { DEBUG_LOG(( "Unable to load screen '%s', max '%d' reached\n", filename, MAX_SHELL_STACK )); return; } // end if // set a push as pending with the layout name passed in m_pendingPush = TRUE; m_pendingPushName = filename; // get the top of the current stack WindowLayout *currentTop = top(); // // if we have someting on the top of the stack we won't do the push // right now, we will instead shutdown the top, and when the top tells // us it's done shutting down (via the shutdownComplete() method) we do // the push then // if( currentTop && !currentTop->isHidden() ) { // run the shutdown currentTop->runShutdown( &shutdownImmediate ); } // end if else { // just call shutdownComplete() which will immediately cause the push to happen shutdownComplete( NULL ); } // end else // if (TheIMEManager) // TheIMEManager->detatch(); } // end push //------------------------------------------------------------------------------------------------- /** Pop top layout of the stack. Note that we don't actually do the pop right here, * we instead run the layout shutdown. That shutdown() in turn notifies the * shell when the shutdown is complete and at that point we do the actual pop */ //------------------------------------------------------------------------------------------------- void Shell::pop( void ) { WindowLayout *screen = top(); if(TheGameSpyInfo) GameSpyCloseAllOverlays(); // sanity if( screen == NULL ) return; #ifdef DEBUG_LOGGING DEBUG_LOG(("Shell:pop() - stack was\n")); for (Int i=0; igetFilename().str())); } #endif // set a pop as pending m_pendingPop = TRUE; // // run the shutdown function for the screen, when it's actually shutdown it // will call Shell::shutdownComplete(), where the pending pop will be seen // and the actual pop will occur // Bool immediatePop = FALSE; screen->runShutdown( &immediatePop ); if (TheIMEManager) TheIMEManager->detatch(); } // end pop //------------------------------------------------------------------------------------------------- /** When you need to immediately pop a screen off the stack use this method. It * gives the screen the opportunity to shutdown and tells the shutdown * method that an immediate pop is going to take place. When control returns * from the shutdown() for the screen, it will be immediately popped off * the stack */ //------------------------------------------------------------------------------------------------- void Shell::popImmediate( void ) { WindowLayout *screen = top(); // sanity if( screen == NULL ) return; #ifdef DEBUG_LOGGING DEBUG_LOG(("Shell:popImmediate() - stack was\n")); for (Int i=0; igetFilename().str())); } #endif // do NOT set pending pop, we are going to force a pop after the shutdown is run m_pendingPop = FALSE; // run the shutdown Bool immediatePop = TRUE; screen->runShutdown( &immediatePop ); // pop the screen of the stack doPop( FALSE ); if (TheIMEManager) TheIMEManager->detatch(); } // end popImmediate //------------------------------------------------------------------------------------------------- /** Run the initialize function for the top of the stack just as though it was pushed * on the stack. We want this behavior when we want to act like the top was just * pushed on the stack, but it's already there (ie going from in game back to the * pre-game shell menus */ //------------------------------------------------------------------------------------------------- void Shell::showShell( Bool runInit ) { DEBUG_LOG(("Shell:showShell() - %s (%s)\n", TheGlobalData->m_initialFile.str(), (top())?top()->getFilename().str():"no top screen")); if(!TheGlobalData->m_initialFile.isEmpty()) { return; } // runInit is used if we want show shell to run if(runInit) { WindowLayout *layout = top(); if( layout ) { layout->runInit( NULL ); // layout->bringForward(); } } // @todo remove this hack // TheGlobalData->m_inGame = FALSE; // add in the background stuff // if(TheGlobalData->m_shellMapOn) // { // if( top() ) // top()->hide(TRUE); // m_background = TheWindowManager->winCreateLayout("Menus/BlankWindow.wnd"); // DEBUG_ASSERTCRASH(m_background,("We Couldn't Load Menus/BlankWindow.wnd")); // m_background->hide(FALSE); // m_background->bringForward(); // if (TheGameLogic->isInGame()) // TheMessageStream->appendMessage( GameMessage::MSG_CLEAR_GAME_DATA ); // // TheGlobalData->m_pendingFile = TheGlobalData->m_shellMapName; // GameMessage *msg = TheMessageStream->appendMessage( GameMessage::MSG_NEW_GAME ); // msg->appendIntegerArgument(GAME_SHELL); // } // else // { // // m_background = TheWindowManager->winCreateLayout("Menus/BlankWindow.wnd"); // // DEBUG_ASSERTCRASH(m_background,("We Couldn't Load Menus/BlankWindow.wnd")); // m_background->hide(FALSE); // if (top()) // top()->bringForward(); // // } if (!TheGlobalData->m_shellMapOn && m_screenCount == 0) { #ifdef _PROFILE Profile::StopRange("init"); #endif //else TheShell->push( AsciiString("Menus/MainMenu.wnd") ); } m_isShellActive = TRUE; } // end showShell void Shell::showShellMap(Bool useShellMap ) { // we don't want any of this to show if we're loading straight into a file if(TheGlobalData->m_initialFile.isNotEmpty() || !TheGameLogic ) return; if(useShellMap && TheGlobalData->m_shellMapOn) { // we're already in a shell game, return if(TheGameLogic->isInGame() && TheGameLogic->getGameMode() == GAME_SHELL) return; // we're in some other kind of game, clear it out foo! if(TheGameLogic->isInGame()) TheMessageStream->appendMessage( GameMessage::MSG_CLEAR_GAME_DATA ); TheWritableGlobalData->m_pendingFile = TheGlobalData->m_shellMapName; InitGameLogicRandom(0); GameMessage *msg = TheMessageStream->appendMessage( GameMessage::MSG_NEW_GAME ); msg->appendIntegerArgument(GAME_SHELL); m_shellMapOn = TRUE; } else { // we're in a shell game, stop it! if(TheGameLogic->isInGame() && TheGameLogic->getGameMode() == GAME_SHELL) TheMessageStream->appendMessage( GameMessage::MSG_CLEAR_GAME_DATA ); // if the shell is active,we need a background if(!m_isShellActive) return; if(!m_background) m_background = TheWindowManager->winCreateLayout("Menus/BlankWindow.wnd"); DEBUG_ASSERTCRASH(m_background,("We Couldn't Load Menus/BlankWindow.wnd")); m_background->getFirstWindow()->winSetStatus(WIN_STATUS_IMAGE); m_background->hide(FALSE); if (top()) top()->bringForward(); m_shellMapOn = FALSE; m_clearBackground = FALSE; } } //------------------------------------------------------------------------------------------------- /** Run the shutdown() function for the top of the stack just like we're going to pop * it off but DO NOT pop it off the stack. We want this behavior when leaving the * pre-game menus and entering the game and want the shell to still exist and contain * the stack information but don't want it to go away */ //------------------------------------------------------------------------------------------------- void Shell::hideShell( void ) { // If we have the 3d background running, mark it to close m_clearBackground = TRUE; DEBUG_LOG(("Shell:hideShell() - %s\n", (top())?top()->getFilename().str():"no top screen")); WindowLayout *layout = top(); if( layout ) { Bool immediatePop = TRUE; layout->runShutdown( &immediatePop ); } // end if if (TheIMEManager) TheIMEManager->detatch(); // Mark that the shell is no longer up. m_isShellActive = FALSE; } // end hideShell //------------------------------------------------------------------------------------------------- /** Return the top layout on the stack */ //------------------------------------------------------------------------------------------------- WindowLayout *Shell::top( void ) { // emtpy stack if( m_screenCount == 0 ) return NULL; // top layout is at count index return m_screenStack[ m_screenCount - 1 ]; } // end top // PRIVATE FUNCTIONS ////////////////////////////////////////////////////////////////////////////// //------------------------------------------------------------------------------------------------- /** Add screen to our list */ //------------------------------------------------------------------------------------------------- void Shell::linkScreen( WindowLayout *screen ) { // sanity if( screen == NULL ) return; // check to see if at top already if( m_screenCount == MAX_SHELL_STACK ) { DEBUG_CRASH(( "No room in shell stack for screen\n" )); return; } // end if // add to array at top index m_screenStack[ m_screenCount++ ] = screen; } // end linkScreen //------------------------------------------------------------------------------------------------- /** Remove screen from our list */ //------------------------------------------------------------------------------------------------- void Shell::unlinkScreen( WindowLayout *screen ) { // sanity if( screen == NULL ) return; DEBUG_ASSERTCRASH( m_screenStack[ m_screenCount - 1 ] == screen, ("Screen not on top of stack\n") ); // remove reference to screen and decrease count if( m_screenStack[ m_screenCount - 1 ] == screen ) m_screenStack[ --m_screenCount ] = NULL; } // end unlinkScreen //------------------------------------------------------------------------------------------------- /** Actually do the work for a push */ //------------------------------------------------------------------------------------------------- void Shell::doPush( AsciiString layoutFile ) { if(TheGameSpyInfo) GameSpyCloseAllOverlays(); WindowLayout *newScreen; // create new layout and load from window manager newScreen = TheWindowManager->winCreateLayout( layoutFile ); DEBUG_ASSERTCRASH( newScreen != NULL, ("Shell unable to load pending push layout\n") ); // link screen to the top linkScreen( newScreen ); if (TheIMEManager) TheIMEManager->detatch(); // run the init function automatically newScreen->runInit( NULL ); newScreen->bringForward(); } // end doPush //------------------------------------------------------------------------------------------------- /** Actually do the work for a pop */ //------------------------------------------------------------------------------------------------- void Shell::doPop( Bool impendingPush ) { WindowLayout *currentTop = top(); // there better be a top of the stack since we're popping DEBUG_ASSERTCRASH( currentTop, ("Shell: No top of stack and we want to pop!\n") ); // remove this screen from our list unlinkScreen( currentTop ); // delete all the windows in the screen currentTop->destroyWindows(); // release the screen object back to the memory pool currentTop->deleteInstance(); // run the init for the new top of the stack if present WindowLayout *newTop = top(); if( newTop && !impendingPush ) { newTop->runInit( NULL ); //newTop->bringForward(); } if (TheIMEManager) TheIMEManager->detatch(); } // end doPop //------------------------------------------------------------------------------------------------- /** This is called when a layout has finished its shutdown process. Layouts are * shutdown when a new screen is being pushed on the stack, or when we are * popping the current screen off the top of the stack. It is here that we * can look for any pending push or pop operations and actually do them * * NOTE: It is possible for the screen parameter to be NULL when we are * short circuiting the shutdown logic because there is no layout * to actually shutdown (ie, the stack is empty and we push) */ //------------------------------------------------------------------------------------------------- void Shell::shutdownComplete( WindowLayout *screen, Bool impendingPush ) { // there should never be a pending push AND pop operation DEBUG_ASSERTCRASH( m_pendingPush == FALSE || m_pendingPop == FALSE, ("There is a pending push AND pop in the shell. Not allowed!\n") ); // Reset the AnimateWindowManager m_animateWindowManager->reset(); // check for pending push or pop if( m_pendingPush ) { // do the push doPush( m_pendingPushName ); // no more pending pushy for you! m_pendingPush = FALSE; m_pendingPushName.set( "" ); } // end if else if( m_pendingPop ) { // do the pop doPop( impendingPush ); // no more pending pop for you! m_pendingPop = FALSE; } // end else if if(m_clearBackground) { if(m_background) { m_background->destroyWindows(); m_background->deleteInstance(); m_background = NULL; m_clearBackground = FALSE; } } } // end shutdownComplete void Shell::registerWithAnimateManager( GameWindow *win, AnimTypes animType, Bool needsToFinish, UnsignedInt delayMS) { if(!m_animateWindowManager) { DEBUG_CRASH(("We called registerWithAnimateManager and we don't have an Animate Manager created")); return; } if (TheGlobalData->m_animateWindows) m_animateWindowManager->registerGameWindow(win,animType,needsToFinish, 500,delayMS); } Bool Shell::isAnimFinished( void ) { // check the new way also. if (!TheTransitionHandler->isFinished()) return FALSE; if(!m_animateWindowManager) { DEBUG_CRASH(("We called registerWithAnimateManager and we don't have an Animate Manager created")); return TRUE; } if (TheGlobalData->m_animateWindows) return m_animateWindowManager->isFinished(); else return TRUE; } void Shell::reverseAnimatewindow( void ) { if(!m_animateWindowManager) { DEBUG_CRASH(("We called registerWithAnimateManager and we don't have an Animate Manager created")); return; } if (TheGlobalData->m_animateWindows) m_animateWindowManager->reverseAnimateWindow(); } Bool Shell::isAnimReversed( void ) { if(!m_animateWindowManager) { DEBUG_CRASH(("We called registerWithAnimateManager and we don't have an Animate Manager created")); return TRUE; } if (TheGlobalData->m_animateWindows) return m_animateWindowManager->isReversed(); else return TRUE; } void Shell::loadScheme( AsciiString name ) { if(!m_schemeManager) return; m_schemeManager->setShellMenuScheme( name ); } // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ WindowLayout *Shell::getSaveLoadMenuLayout( void ) { // if layout has not been created, create it now if( m_saveLoadMenuLayout == NULL ) m_saveLoadMenuLayout = TheWindowManager->winCreateLayout( AsciiString( "Menus/PopupSaveLoad.wnd" ) ); // sanity DEBUG_ASSERTCRASH( m_saveLoadMenuLayout, ("Unable to create save/load menu layout\n") ); // return the layout return m_saveLoadMenuLayout; } // end getSaveLoadMenuLayout // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ WindowLayout *Shell::getPopupReplayLayout( void ) { // if layout has not been created, create it now if( m_popupReplayLayout == NULL ) m_popupReplayLayout = TheWindowManager->winCreateLayout( AsciiString( "Menus/PopupReplay.wnd" ) ); // sanity DEBUG_ASSERTCRASH( m_popupReplayLayout, ("Unable to create replay save menu layout\n") ); // return the layout return m_popupReplayLayout; } // end getSaveLoadMenuLayout // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ WindowLayout *Shell::getOptionsLayout( Bool create ) { // if layout has not been created, create it now if ((m_optionsLayout == NULL) && (create == TRUE)) { m_optionsLayout = TheWindowManager->winCreateLayout( AsciiString( "Menus/OptionsMenu.wnd" ) ); // sanity DEBUG_ASSERTCRASH( m_optionsLayout, ("Unable to create options menu layout\n") ); } // return the layout return m_optionsLayout; } // end getOptionsLayout // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ void Shell::destroyOptionsLayout() { if (m_optionsLayout != NULL) { m_optionsLayout->destroyWindows(); m_optionsLayout->deleteInstance(); m_optionsLayout = NULL; } }