/* ** Command & Conquer Generals(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: GameClient.cpp //////////////////////////////////////////////////// // Implementation of GameClient singleton // Author: Michael S. Booth, March 2001 /////////////////////////////////////////////////////////////////////////////// // SYSTEM INCLUDES //////////////////////////////////////////////////////////// #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine #include "GameClient/GameClient.h" // USER INCLUDES ////////////////////////////////////////////////////////////// #include "Common/ActionManager.h" #include "Common/GameEngine.h" #include "Common/GameState.h" #include "Common/GlobalData.h" #include "Common/PerfTimer.h" #include "Common/Player.h" #include "Common/PlayerList.h" #include "Common/ThingFactory.h" #include "Common/ThingTemplate.h" #include "Common/Xfer.h" #include "Common/GameLOD.h" #include "GameClient/Anim2D.h" #include "GameClient/CampaignManager.h" #include "GameClient/CommandXlat.h" #include "GameClient/ControlBar.h" #include "GameClient/Diplomacy.h" #include "GameClient/Display.h" #include "GameClient/DisplayStringManager.h" #include "GameClient/Drawable.h" #include "GameClient/DrawGroupInfo.h" #include "GameClient/Eva.h" #include "GameClient/GameWindowManager.h" #include "GameClient/GlobalLanguage.h" #include "GameClient/GraphDraw.h" #include "GameClient/GUICommandTranslator.h" #include "GameClient/HeaderTemplate.h" #include "GameClient/HintSpy.h" #include "GameClient/HotKey.h" #include "GameClient/IMEManager.h" #include "GameClient/InGameUI.h" #include "GameClient/Keyboard.h" #include "GameClient/LanguageFilter.h" #include "GameClient/LookAtXlat.h" #include "GameClient/MetaEvent.h" #include "GameClient/Mouse.h" #include "GameClient/ParticleSys.h" #include "GameClient/PlaceEventTranslator.h" #include "GameClient/RayEffect.h" #include "GameClient/SelectionXlat.h" #include "GameClient/Shell.h" #include "GameClient/TerrainVisual.h" #include "GameClient/View.h" #include "GameClient/VideoPlayer.h" #include "GameClient/WindowXlat.h" #include "GameLogic/FPUControl.h" #include "GameLogic/GameLogic.h" #include "GameLogic/GhostObject.h" #include "GameLogic/Object.h" #include "GameLogic/ScriptEngine.h" // For TheScriptEngine - jkmcd #ifdef _INTERNAL // for occasional debugging... //#pragma optimize("", off) //#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") #endif #define DRAWABLE_HASH_SIZE 8192 /// The GameClient singleton instance GameClient *TheGameClient = NULL; //------------------------------------------------------------------------------------------------- GameClient::GameClient() { // zero our translator list for( Int i = 0; i < MAX_CLIENT_TRANSLATORS; i++ ) m_translators[ i ] = TRANSLATOR_ID_INVALID; m_numTranslators = 0; m_commandTranslator = NULL; // Added By Sadullah Nader // Initializations missing and needed m_drawableTOC.clear(); // m_textBearingDrawableList.clear(); m_frame = 0; m_drawableList = NULL; m_nextDrawableID = (DrawableID)1; TheDrawGroupInfo = new DrawGroupInfo; } //std::vector preloadTextureNamesGlobalHack; //std::vector preloadTextureNamesGlobalHack2; //------------------------------------------------------------------------------------------------- GameClient::~GameClient() { #ifdef PERF_TIMERS delete TheGraphDraw; TheGraphDraw = NULL; #endif if (TheDrawGroupInfo) { delete TheDrawGroupInfo; TheDrawGroupInfo = NULL; } // clear any drawable TOC we might have m_drawableTOC.clear(); //DEBUG_LOG(("Preloaded texture files ------------------------------------------\n")); //for (Int oog=0; ooggetNextDrawable(); destroyDrawable( draw ); } m_drawableList = NULL; // delete the ray effects delete TheRayEffects; TheRayEffects = NULL; // delete the hot key manager delete TheHotKeyManager; TheHotKeyManager = NULL; // destroy the in-game user interface delete TheInGameUI; TheInGameUI = NULL; // delete the shell delete TheShell; TheShell = NULL; delete TheIMEManager; TheIMEManager = NULL; // delete window manager delete TheWindowManager; TheWindowManager = NULL; // delete the font library TheFontLibrary->reset(); delete TheFontLibrary; TheFontLibrary = NULL; delete TheMouse; TheMouse = NULL; ///@todo : TheTerrainVisual used to be the first thing destroyed. //I had to put in here so that drawables free their track marks before //the terrain visual deletes the track laying system. MW // destroy the terrain visual representation delete TheTerrainVisual; TheTerrainVisual = NULL; // destroy the display delete TheDisplay; TheDisplay = NULL; delete TheHeaderTemplateManager; TheHeaderTemplateManager = NULL; delete TheLanguageFilter; TheLanguageFilter = NULL; delete TheVideoPlayer; TheVideoPlayer = NULL; // destroy all translators for( Int i = 0; i < m_numTranslators; i++ ) TheMessageStream->removeTranslator( m_translators[ i ] ); m_numTranslators = 0; m_commandTranslator = NULL; delete TheAnim2DCollection; TheAnim2DCollection = NULL; delete TheMappedImageCollection; TheMappedImageCollection = NULL; delete TheKeyboard; TheKeyboard = NULL; delete TheDisplayStringManager; TheDisplayStringManager = NULL; delete TheEva; TheEva = NULL; } // end ~GameClient //------------------------------------------------------------------------------------------------- /** Initialize resources for the game client */ //------------------------------------------------------------------------------------------------- void GameClient::init( void ) { setFrameRate(MSEC_PER_LOGICFRAME_REAL); // from GameCommon.h... tell W3D what our expected framerate is INI ini; // Load the DrawGroupInfo here, before the Display Manager is loaded. ini.load("Data\\INI\\DrawGroupInfo.ini", INI_LOAD_OVERWRITE, NULL); // Override the ini values with localized versions: if (TheGlobalLanguageData && TheGlobalLanguageData->m_drawGroupInfoFont.name.isNotEmpty()) { TheDrawGroupInfo->m_fontName = TheGlobalLanguageData->m_drawGroupInfoFont.name; TheDrawGroupInfo->m_fontSize = TheGlobalLanguageData->m_drawGroupInfoFont.size; TheDrawGroupInfo->m_fontIsBold = TheGlobalLanguageData->m_drawGroupInfoFont.bold; } // create the display string factory TheDisplayStringManager = createDisplayStringManager(); if( TheDisplayStringManager ) { TheDisplayStringManager->init(); TheDisplayStringManager->setName("TheDisplayStringManager"); } // create the keyboard TheKeyboard = createKeyboard(); TheKeyboard->init(); TheKeyboard->setName("TheKeyboard"); // allocate and load image collection for the GUI and just load the 256x256 ones for now TheMappedImageCollection = MSGNEW("GameClientSubsystem") ImageCollection; TheMappedImageCollection->load( 512 ); // now that we have all the images loaded ... load any animation definitions from those images TheAnim2DCollection = MSGNEW("GameClientSubsystem") Anim2DCollection; TheAnim2DCollection->init(); TheAnim2DCollection->setName("TheAnim2DCollection"); // register message translators if( TheMessageStream ) { // // NOTE: Make sure m_translators[] is large enough to accomodate all the translators you // are loading here. See MAX_CLIENT_TRANSLATORS // // since we only allocate one of each, don't bother pooling 'em m_translators[ m_numTranslators++ ] = TheMessageStream->attachTranslator( MSGNEW("GameClientSubsystem") WindowTranslator, 10 ); m_translators[ m_numTranslators++ ] = TheMessageStream->attachTranslator( MSGNEW("GameClientSubsystem") MetaEventTranslator, 20 ); m_translators[ m_numTranslators++ ] = TheMessageStream->attachTranslator( MSGNEW("GameClientSubsystem") HotKeyTranslator, 25 ); m_translators[ m_numTranslators++ ] = TheMessageStream->attachTranslator( MSGNEW("GameClientSubsystem") PlaceEventTranslator, 30 ); m_translators[ m_numTranslators++ ] = TheMessageStream->attachTranslator( MSGNEW("GameClientSubsystem") GUICommandTranslator, 40 ); m_translators[ m_numTranslators++ ] = TheMessageStream->attachTranslator( MSGNEW("GameClientSubsystem") SelectionTranslator, 50 ); m_translators[ m_numTranslators++ ] = TheMessageStream->attachTranslator( MSGNEW("GameClientSubsystem") LookAtTranslator, 60 ); m_translators[ m_numTranslators ] = TheMessageStream->attachTranslator( MSGNEW("GameClientSubsystem") CommandTranslator, 70 ); // we keep a pointer to the command translator because it's useful m_commandTranslator = (CommandTranslator *)TheMessageStream->findTranslator( m_translators[ m_numTranslators++ ] ); m_translators[ m_numTranslators++ ] = TheMessageStream->attachTranslator( MSGNEW("GameClientSubsystem") HintSpyTranslator, 100 ); // // the client message translator should probably remain as the last reaction of the // client before the messages are given to the network for processing. This // lets all systems in the client give events that can be processed by the // client message translator // m_translators[ m_numTranslators++ ] = TheMessageStream->attachTranslator( MSGNEW("GameClientSubsystem") GameClientMessageDispatcher, 999999999 ); } // create the font library TheFontLibrary = createFontLibrary(); if( TheFontLibrary ) TheFontLibrary->init(); // create the mouse TheMouse = createMouse(); TheMouse->parseIni(); TheMouse->initCursorResources(); TheMouse->setName("TheMouse"); // instantiate the display TheDisplay = createGameDisplay(); if( TheDisplay ) { TheDisplay->init(); TheDisplay->setName("TheDisplay"); } TheHeaderTemplateManager = MSGNEW("GameClientSubsystem") HeaderTemplateManager; if(TheHeaderTemplateManager){ TheHeaderTemplateManager->init(); } // create the window manager TheWindowManager = createWindowManager(); if( TheWindowManager ) { TheWindowManager->init(); TheWindowManager->setName("TheWindowManager"); // TheWindowManager->initTestGUI(); } // end if // create the IME manager TheIMEManager = CreateIMEManagerInterface(); if ( TheIMEManager ) { TheIMEManager->init(); TheIMEManager->setName("TheIMEManager"); } // create the shell TheShell = MSGNEW("GameClientSubsystem") Shell; if( TheShell ) { TheShell->init(); TheShell->setName("TheShell"); } // instantiate the in-game user interface TheInGameUI = createInGameUI(); if( TheInGameUI ) { TheInGameUI->init(); TheInGameUI->setName("TheInGameUI"); } TheHotKeyManager = MSGNEW("GameClientSubsystem") HotKeyManager; if( TheHotKeyManager ) { TheHotKeyManager->init(); TheHotKeyManager->setName("TheHotKeyManager"); } // instantiate the terrain visual display TheTerrainVisual = createTerrainVisual(); if( TheTerrainVisual ) { TheTerrainVisual->init(); TheTerrainVisual->setName("TheTerrainVisual"); } // allocate the ray effects manager TheRayEffects = MSGNEW("GameClientSubsystem") RayEffectSystem; if( TheRayEffects ) { TheRayEffects->init(); TheRayEffects->setName("TheRayEffects"); } TheMouse->init(); //finish initializing the mouse. // set the limits of the mouse now that we've created the display and such if( TheMouse ) { TheMouse->setPosition( 0, 0 ); TheMouse->setMouseLimits(); TheMouse->setName("TheMouse"); } // end if // create the video player TheVideoPlayer = createVideoPlayer(); if ( TheVideoPlayer ) { TheVideoPlayer->init(); TheVideoPlayer->setName("TheVideoPlayer"); } // create the language filter. TheLanguageFilter = createLanguageFilter(); if (TheLanguageFilter) { TheLanguageFilter->init(); TheLanguageFilter->setName("TheLanguageFilter"); } TheCampaignManager = MSGNEW("GameClientSubsystem") CampaignManager; TheCampaignManager->init(); TheEva = MSGNEW("GameClientSubsystem") Eva; TheEva->init(); TheEva->setName("TheEva"); TheDisplayStringManager->postProcessLoad(); #ifdef PERF_TIMERS TheGraphDraw = new GraphDraw; #endif } // end init //------------------------------------------------------------------------------------------------- /** Reset the game client for a new game */ void GameClient::reset( void ) { Drawable *draw, *nextDraw; m_drawableHash.clear(); m_drawableHash.resize(DRAWABLE_HASH_SIZE); // need to reset the in game UI to clear drawables before they are destroyed TheInGameUI->reset(); // destroy all Drawables for( draw = m_drawableList; draw; draw = nextDraw ) { nextDraw = draw->getNextDrawable(); destroyDrawable( draw ); } m_drawableList = NULL; TheDisplay->reset(); TheTerrainVisual->reset(); TheRayEffects->reset(); TheVideoPlayer->reset(); TheEva->reset(); // clear any drawable TOC we might have m_drawableTOC.clear(); } // end reset /** ----------------------------------------------------------------------------------------------- * Return a new unique object id. */ DrawableID GameClient::allocDrawableID( void ) { /// @todo Find unused value in current set DrawableID ret = m_nextDrawableID; m_nextDrawableID = (DrawableID)((UnsignedInt)m_nextDrawableID + 1); return ret; } /** ----------------------------------------------------------------------------------------------- * Given a drawable, register it with the GameClient and give it a unique ID. */ void GameClient::registerDrawable( Drawable *draw ) { // assign this drawable a unique ID, this will add it to the fast lookup table too draw->setID( allocDrawableID() ); // add the drawable to the master list draw->prependToList( &m_drawableList ); } // end registerDrawable /** ----------------------------------------------------------------------------------------------- * Redraw all views, update the GUI, play sound effects, etc. */ DECLARE_PERF_TIMER(GameClient_update) DECLARE_PERF_TIMER(GameClient_draw) void GameClient::update( void ) { USE_PERF_TIMER(GameClient_update) // create the FRAME_TICK message GameMessage *frameMsg = TheMessageStream->appendMessage( GameMessage::MSG_FRAME_TICK ); frameMsg->appendTimestampArgument( getFrame() ); static Bool playSizzle = FALSE; // We need to show the movie first. if(TheGlobalData->m_playIntro && !TheDisplay->isMoviePlaying()) { if(TheGameLODManager && TheGameLODManager->didMemPass()) TheDisplay->playLogoMovie("EALogoMovie", 5000, 3000); else TheDisplay->playLogoMovie("EALogoMovie640", 5000, 3000); TheWritableGlobalData->m_playIntro = FALSE; TheWritableGlobalData->m_afterIntro = TRUE; playSizzle = TRUE; } //Initial Game Codition. We must show the movie first and then we can display the shell if(TheGlobalData->m_afterIntro && !TheDisplay->isMoviePlaying()) { if( playSizzle) { TheWritableGlobalData->m_allowExitOutOfMovies = TRUE; if(TheGameLODManager && TheGameLODManager->didMemPass()) TheDisplay->playMovie("Sizzle"); else TheDisplay->playMovie("Sizzle640"); playSizzle = FALSE; } else { TheWritableGlobalData->m_breakTheMovie = TRUE; TheWritableGlobalData->m_allowExitOutOfMovies = TRUE; if(TheGameLODManager && !TheGameLODManager->didMemPass()) { TheWritableGlobalData->m_breakTheMovie = FALSE; WindowLayout *legal = TheWindowManager->winCreateLayout("Menus/LegalPage.wnd"); if(legal) { legal->hide(FALSE); legal->bringForward(); Int beginTime = timeGetTime(); while(beginTime + 4000 > timeGetTime() ) { TheWindowManager->update(); // redraw all views, update the GUI TheDisplay->draw(); Sleep(100); } setFPMode(); legal->destroyWindows(); legal->deleteInstance(); } TheWritableGlobalData->m_breakTheMovie = TRUE; } TheShell->showShellMap(TRUE); TheShell->showShell(); TheWritableGlobalData->m_afterIntro = FALSE; } } // update animation 2d collection TheAnim2DCollection->UPDATE(); // update the keyboard if( TheKeyboard ) { TheKeyboard->UPDATE(); TheKeyboard->createStreamMessages(); } // end if // Update the Eva stuff TheEva->UPDATE(); // update the mouse if( TheMouse ) { TheMouse->UPDATE(); TheMouse->createStreamMessages(); } // end if if(TheGlobalData->m_playIntro || TheGlobalData->m_afterIntro) { // redraw all views, update the GUI { TheDisplay->DRAW(); } { TheDisplay->UPDATE(); } return; } // update the window system itself { TheWindowManager->UPDATE(); } // update the video player { TheVideoPlayer->UPDATE(); } Bool freezeTime = TheTacticalView->isTimeFrozen() && !TheTacticalView->isCameraMovementFinished(); freezeTime = freezeTime || TheScriptEngine->isTimeFrozenDebug(); freezeTime = freezeTime || TheScriptEngine->isTimeFrozenScript(); freezeTime = freezeTime || TheGameLogic->isGamePaused(); Int localPlayerIndex = ThePlayerList ? ThePlayerList->getLocalPlayer()->getPlayerIndex() : 0; // hack to let client spin fast in network games but still do effects at the same pace. -MDC static UnsignedInt lastFrame = ~0; freezeTime = freezeTime || (lastFrame == m_frame); lastFrame = m_frame; if (!freezeTime) { #if defined(_DEBUG) || defined(_INTERNAL) if (TheGlobalData->m_shroudOn) #else if (true) #endif { //localPlayerIndex=TheGhostObjectManager->getLocalPlayerIndex(); //always use the first local player set since normally can't change. Doesn't work with debug "CTRL_SHIFT_SPACE" #ifdef DEBUG_FOG_MEMORY //Find indices of all active players Int numPlayers=ThePlayerList->getPlayerCount(); Int numNonLocalPlayers=0; Int nonLocalPlayerIndices[MAX_PLAYER_COUNT]; for (Int i=0; igetNthPlayer(i); //if (player->getPlayerType == PLAYER_HUMAN) if (player->getPlayerIndex() != localPlayerIndex) nonLocalPlayerIndices[numNonLocalPlayers++]=player->getPlayerIndex(); } //update ghostObjects which don't have drawables or objects. TheGhostObjectManager->updateOrphanedObjects(nonLocalPlayerIndices,numNonLocalPlayers); #else TheGhostObjectManager->updateOrphanedObjects(NULL,0); #endif } // call the update for all client drawables Drawable* draw = firstDrawable(); while (draw) { // update() could free the Drawable, so go ahead and grab 'next' Drawable* next = draw->getNextDrawable(); #if defined(_DEBUG) || defined(_INTERNAL) if (TheGlobalData->m_shroudOn) #else if (true) #endif { //immobile objects need to take snapshots whenever they become fogged //so need to refresh their status. We can't rely on external calls //to getShroudStatus() because they are only made for visible on-screen //objects. Object *object=draw->getObject(); if (object) { #ifdef DEBUG_FOG_MEMORY Int *playerIndex=nonLocalPlayerIndices; for (i=0; igetShroudedStatus(*playerIndex); #endif ObjectShroudStatus ss=object->getShroudedStatus(localPlayerIndex); if (ss >= OBJECTSHROUD_FOGGED && draw->getShroudClearFrame()!=0) { UnsignedInt limit = 2*LOGICFRAMES_PER_SECOND; if (object->isEffectivelyDead()) { // extend the time, so we can see the dead plane blow up & crash. limit += 3*LOGICFRAMES_PER_SECOND; } if (TheGameLogic->getFrame() < limit + draw->getShroudClearFrame()) { // It's been less than 2 seconds since we could see them clear, so keep showing them. ss = OBJECTSHROUD_CLEAR; } } draw->setFullyObscuredByShroud(ss >= OBJECTSHROUD_FOGGED); } } draw->updateDrawable(); draw = next; } } #if defined(_INTERNAL) || defined(_DEBUG) // need to draw the first frame, then don't draw again until TheGlobalData->m_noDraw if (TheGlobalData->m_noDraw > TheGameLogic->getFrame() && TheGameLogic->getFrame() > 0) { return; } #endif // update all particle systems if( !freezeTime ) { // update particle systems TheParticleSystemManager->setLocalPlayerIndex(localPlayerIndex); TheParticleSystemManager->update(); } // end if // update the terrain visuals { TheTerrainVisual->UPDATE(); } // update display { TheDisplay->UPDATE(); } { USE_PERF_TIMER(GameClient_draw) // redraw all views, update the GUI //if(TheGameLogic->getFrame() >= 2) TheDisplay->DRAW(); } { // let display string factory handle its update TheDisplayStringManager->update(); } { // update the shell TheShell->UPDATE(); } { // update the in game UI TheInGameUI->UPDATE(); } } // end update /** ----------------------------------------------------------------------------------------------- * Call the given callback function for each object contained within the given region. */ void GameClient::iterateDrawablesInRegion( Region3D *region, GameClientFuncPtr userFunc, void *userData ) { Drawable *draw, *nextDrawable; for( draw = m_drawableList; draw; draw=nextDrawable ) { nextDrawable = draw->getNextDrawable(); Coord3D pos = *draw->getPosition(); if( region == NULL || (pos.x >= region->lo.x && pos.x <= region->hi.x && pos.y >= region->lo.y && pos.y <= region->hi.y && pos.z >= region->lo.z && pos.z <= region->hi.z) ) { (*userFunc)( draw, userData ); } } } /** ----------------------------------------------------------------------------------------------- * Given an object id, return the associated object. * For efficiency, a small Least Recently Used cache is incorporated. * This method is the primary interface for accessing objects, and should be used * instead of pointers to "attach" objects to each other. */ Drawable* GameClient::findDrawableByID( const DrawableID id ) { DrawablePtrHashIt it = m_drawableHash.find(id); if (it == m_drawableHash.end()) { // no such drawable return NULL; } return (*it).second; } /** ----------------------------------------------------------------------------------------------- * Destroy the drawable immediately. */ void GameClient::destroyDrawable( Drawable *draw ) { // remove any notion of the Drawable in the in-game user interface TheInGameUI->disregardDrawable( draw ); // detach this Drawable from any particle system that may be using it draw->detachFromParticleSystem(); // remove from the master list draw->removeFromList(&m_drawableList); // // because drawables and objects are tightly coupled, not only MUST we maintain // our links in all instances, but it is NECESSARY for the client to actually // modify data in the logic, that is the pointer in an object to *this* drawable // Object *obj = draw->getObject(); if( obj ) { DEBUG_ASSERTCRASH( obj->getDrawable() == draw, ("Object/Drawable pointer mismatch!\n") ); obj->friend_bindToDrawable( NULL ); } // end if // remove the drawable from our hash of drawables removeDrawableFromLookupTable( draw ); // free storage draw->deleteInstance(); } // ------------------------------------------------------------------------------------------------ /** Add drawable to lookup table for fast id searching */ // ------------------------------------------------------------------------------------------------ void GameClient::addDrawableToLookupTable(Drawable *draw ) { // sanity if( draw == NULL ) return; // add to lookup m_drawableHash[ draw->getID() ] = draw; } // end addDrawableToLookupTable // ------------------------------------------------------------------------------------------------ /** Remove drawable from lookup table of fast id searching */ // ------------------------------------------------------------------------------------------------ void GameClient::removeDrawableFromLookupTable( Drawable *draw ) { // sanity if( draw == NULL ) return; // remove from table m_drawableHash.erase( draw->getID() ); } // end removeDrawableFromLookupTable //------------------------------------------------------------------------------------------------- /** Load a map into the game interface */ Bool GameClient::loadMap( AsciiString mapName ) { // sanity if( mapName.isEmpty() ) return false; assert( 0 ); // who calls this? return TRUE; } // end loadMap //------------------------------------------------------------------------------------------------- /** Unload a map from the game interface */ void GameClient::unloadMap( AsciiString mapName ) { assert( 0 ); // who calls this? } // end unloadMap //------------------------------------------------------------------------------------------------- void GameClient::setTimeOfDay( TimeOfDay tod ) { Drawable *draw = firstDrawable(); while( draw ) { draw->setTimeOfDay( tod ); draw = draw->getNextDrawable(); } } //------------------------------------------------------------------------------------------------- void GameClient::assignSelectedDrawablesToGroup( Int group ) { /* Drawable *draw = firstDrawable(); while( draw ) { if( draw->isSelected() && draw->getObject()->isLocallyControlled()) { draw->setDrawableGroup( group ); } else if( draw->getDrawableGroup() == group ) { draw->setDrawableGroup( 0 ); } draw = draw->getNextDrawable(); } */ } //------------------------------------------------------------------------------------------------- void GameClient::selectDrawablesInGroup( Int group ) { /* Drawable *draw = firstDrawable(); // create a message that will assign a group ID to all the selected drawables, this // way when we do things with this current selected group of units, we only have // to refer to the group ID and not each individual object ID GameMessage *teamMsg = TheMessageStream->appendMessage( GameMessage::MSG_CREATE_SELECTED_GROUP ); //We are creating a new group from scratch. teamMsg->appendBooleanArgument( true ); while( draw ) { int counter = 0; const Object *object = draw->getObject(); if( object && draw->getDrawableGroup() == group && object->isLocallyControlled() && !object->isContained() ) { //Only select the object if it is locally controlled and not contained by anything. TheInGameUI->selectDrawable(draw); teamMsg->appendObjectIDArgument( draw->getObject()->getID() ); } else { TheInGameUI->deselectDrawable(draw); } draw = draw->getNextDrawable(); counter++; } */ } // ------------------------------------------------------------------------------------------------ void GameClient::addTextBearingDrawable( Drawable *tbd ) { if ( tbd != NULL ) m_textBearingDrawableList.push_back( tbd ); } // ------------------------------------------------------------------------------------------------ void GameClient::flushTextBearingDrawables( void ) { ///////////////////////////// // WALK THIS LIST AND CALL EACH DRAWABLES TEXTY STUFF ///////////////////////////// for( TextBearingDrawableListIterator it = m_textBearingDrawableList.begin(); it != m_textBearingDrawableList.end(); ++it ) (*it)->drawUIText(); m_textBearingDrawableList.clear(); } // ------------------------------------------------------------------------------------------------ GameMessage::Type GameClient::evaluateContextCommand( Drawable *draw, const Coord3D *pos, CommandTranslator::CommandEvaluateType cmdType ) { if( m_commandTranslator ) return m_commandTranslator->evaluateContextCommand( draw, pos, cmdType ); else return GameMessage::MSG_INVALID; } // end evaluateContextCommand //------------------------------------------------------------------------------------------------- /** Get the ray effect data for a drawable */ void GameClient::getRayEffectData( Drawable *draw, RayEffectData *effectData ) { TheRayEffects->getRayEffectData( draw, effectData ); } // end getRayEffectData //------------------------------------------------------------------------------------------------- /** remove the drawble from the ray effects sytem if present */ void GameClient::removeFromRayEffects( Drawable *draw ) { TheRayEffects->deleteRayEffect( draw ); } // end removeFromRayEffects /** frees all shadow resources used by this module - used by Options screen.*/ void GameClient::releaseShadows(void) { Drawable *draw; for( draw = firstDrawable(); draw; draw = draw->getNextDrawable() ) draw->releaseShadows(); } /** create shadow resources if not already present. Used by Options screen.*/ void GameClient::allocateShadows(void) { Drawable *draw; for( draw = firstDrawable(); draw; draw = draw->getNextDrawable() ) draw->allocateShadows(); } //------------------------------------------------------------------------------------------------- /** Preload assets for the currently loaded map. Those assets include all the damage states * for every building loaded, as well as any faction units/structures we can build and * all their damage states */ //------------------------------------------------------------------------------------------------- void GameClient::preloadAssets( TimeOfDay timeOfDay ) { MEMORYSTATUS before, after; GlobalMemoryStatus(&before); // first, for every drawable in the map load the assets for all states we care about Drawable *draw; for( draw = firstDrawable(); draw; draw = draw->getNextDrawable() ) draw->preloadAssets( timeOfDay ); // // now create a temporary drawble for each of the faction things we can create, preload // their assets, and dump the drawable // AsciiString side; const ThingTemplate *tTemplate; for( tTemplate = TheThingFactory->firstTemplate(); tTemplate; tTemplate = tTemplate->friend_getNextTemplate() ) { // if this isn't one of the objects that can be preloaded ignore it if( tTemplate->isKindOf( KINDOF_PRELOAD ) == FALSE && !TheGlobalData->m_preloadEverything ) continue; // create the drawable and do the preloading draw = TheThingFactory->newDrawable( tTemplate ); if( draw ) { // preload the assets draw->preloadAssets( timeOfDay ); // destroy the drawable TheGameClient->destroyDrawable( draw ); } // end if } // end for GlobalMemoryStatus(&after); DEBUG_LOG(("Preloading memory dwAvailPageFile %d --> %d : %d\n", before.dwAvailPageFile, after.dwAvailPageFile, before.dwAvailPageFile - after.dwAvailPageFile)); DEBUG_LOG(("Preloading memory dwAvailPhys %d --> %d : %d\n", before.dwAvailPhys, after.dwAvailPhys, before.dwAvailPhys - after.dwAvailPhys)); DEBUG_LOG(("Preloading memory dwAvailVirtual %d --> %d : %d\n", before.dwAvailVirtual, after.dwAvailVirtual, before.dwAvailVirtual - after.dwAvailVirtual)); /* DEBUG_LOG(("Preloading memory dwLength %d --> %d : %d\n", before.dwLength, after.dwLength, before.dwLength - after.dwLength)); DEBUG_LOG(("Preloading memory dwMemoryLoad %d --> %d : %d\n", before.dwMemoryLoad, after.dwMemoryLoad, before.dwMemoryLoad - after.dwMemoryLoad)); DEBUG_LOG(("Preloading memory dwTotalPageFile %d --> %d : %d\n", before.dwTotalPageFile, after.dwTotalPageFile, before.dwTotalPageFile - after.dwTotalPageFile)); DEBUG_LOG(("Preloading memory dwTotalPhys %d --> %d : %d\n", before.dwTotalPhys , after.dwTotalPhys, before.dwTotalPhys - after.dwTotalPhys)); DEBUG_LOG(("Preloading memory dwTotalVirtual %d --> %d : %d\n", before.dwTotalVirtual , after.dwTotalVirtual, before.dwTotalVirtual - after.dwTotalVirtual)); */ GlobalMemoryStatus(&before); extern std::vector debrisModelNamesGlobalHack; for (Int i=0; ipreloadModelAssets(debrisModelNamesGlobalHack[i]); } GlobalMemoryStatus(&after); debrisModelNamesGlobalHack.clear(); DEBUG_LOG(("Preloading memory dwAvailPageFile %d --> %d : %d\n", before.dwAvailPageFile, after.dwAvailPageFile, before.dwAvailPageFile - after.dwAvailPageFile)); DEBUG_LOG(("Preloading memory dwAvailPhys %d --> %d : %d\n", before.dwAvailPhys, after.dwAvailPhys, before.dwAvailPhys - after.dwAvailPhys)); DEBUG_LOG(("Preloading memory dwAvailVirtual %d --> %d : %d\n", before.dwAvailVirtual, after.dwAvailVirtual, before.dwAvailVirtual - after.dwAvailVirtual)); TheControlBar->preloadAssets( timeOfDay ); GlobalMemoryStatus(&before); TheParticleSystemManager->preloadAssets( timeOfDay ); GlobalMemoryStatus(&after); DEBUG_LOG(("Preloading memory dwAvailPageFile %d --> %d : %d\n", before.dwAvailPageFile, after.dwAvailPageFile, before.dwAvailPageFile - after.dwAvailPageFile)); DEBUG_LOG(("Preloading memory dwAvailPhys %d --> %d : %d\n", before.dwAvailPhys, after.dwAvailPhys, before.dwAvailPhys - after.dwAvailPhys)); DEBUG_LOG(("Preloading memory dwAvailVirtual %d --> %d : %d\n", before.dwAvailVirtual, after.dwAvailVirtual, before.dwAvailVirtual - after.dwAvailVirtual)); char *textureNames[] = { "ptspruce01.tga", "exrktflame.tga", "cvlimo3_d2.tga", "exfthrowerstream.tga", "uvrockbug_d1.tga", "arcbackgroundc.tga", "grade3.tga", "framebasec.tga", "gradec.tga", "frametopc.tga", "arcbackgrounda.tga", "arcglow2.tga", "framebasea.tga", "gradea.tga", "frametopa.tga", "sauserinterface256_002.tga", "sauserinterface256_001.tga", "unitbackgrounda.tga", "sauserinterface256_004.tga", "sagentank.tga", "sauserinterface256_005.tga", "sagenair.tga", "sauserinterface256_003.tga", "sagenspec.tga", "snuserinterface256_003.tga", "snuserinterface256_002.tga", "unitbackgroundc.tga", "snuserinterface256_004.tga", "sngenredarm.tga", "snuserinterface256_001.tga", "sngenspewea.tga", "sngensecpol.tga", "ciburn.tga", "ptmaple02.tga", "scuserinterface256_005.tga", "scuserinterface256_002.tga", "sauserinterface256_006.tga", "pmcrates.tga", "" }; GlobalMemoryStatus(&before); for (i=0; *textureNames[i]; ++i) TheDisplay->preloadTextureAssets(textureNames[i]); GlobalMemoryStatus(&after); DEBUG_LOG(("Preloading memory dwAvailPageFile %d --> %d : %d\n", before.dwAvailPageFile, after.dwAvailPageFile, before.dwAvailPageFile - after.dwAvailPageFile)); DEBUG_LOG(("Preloading memory dwAvailPhys %d --> %d : %d\n", before.dwAvailPhys, after.dwAvailPhys, before.dwAvailPhys - after.dwAvailPhys)); DEBUG_LOG(("Preloading memory dwAvailVirtual %d --> %d : %d\n", before.dwAvailVirtual, after.dwAvailVirtual, before.dwAvailVirtual - after.dwAvailVirtual)); // preloadTextureNamesGlobalHack2 = preloadTextureNamesGlobalHack; // preloadTextureNamesGlobalHack.clear(); } // end preloadAssets // ------------------------------------------------------------------------------------------------ /** Given a string name, find the drawable TOC entry (if any) associated with it */ // ------------------------------------------------------------------------------------------------ GameClient::DrawableTOCEntry *GameClient::findTOCEntryByName( AsciiString name ) { for( DrawableTOCListIterator it = m_drawableTOC.begin(); it != m_drawableTOC.end(); ++it ) if( (*it).name == name ) return &(*it); return NULL; } // end findTOCEntryByname // ------------------------------------------------------------------------------------------------ /** Given a drawable TOC identifier, find the drawable TOC if any */ // ------------------------------------------------------------------------------------------------ GameClient::DrawableTOCEntry *GameClient::findTOCEntryById( UnsignedShort id ) { for( DrawableTOCListIterator it = m_drawableTOC.begin(); it != m_drawableTOC.end(); ++it ) if( (*it).id == id ) return &(*it); return NULL; } // end findTOCEntryById // ------------------------------------------------------------------------------------------------ /** Add an drawable TOC entry */ // ------------------------------------------------------------------------------------------------ void GameClient::addTOCEntry( AsciiString name, UnsignedShort id ) { DrawableTOCEntry tocEntry; tocEntry.name = name; tocEntry.id = id; m_drawableTOC.push_back( tocEntry ); } // end addTOCEntry // ------------------------------------------------------------------------------------------------ static Bool shouldSaveDrawable(const Drawable* draw) { if (draw->testDrawableStatus(DRAWABLE_STATUS_NO_SAVE)) { if (draw->getObject() == NULL) { return false; } else { DEBUG_CRASH(("You should not ever set DRAWABLE_STATUS_NO_SAVE for a Drawable with an object. (%s)\n",draw->getTemplate()->getName().str())); } } return true; } // ------------------------------------------------------------------------------------------------ /** Xfer drawable table of contents */ // ------------------------------------------------------------------------------------------------ void GameClient::xferDrawableTOC( Xfer *xfer ) { // version XferVersion currentVersion = 1; XferVersion version = currentVersion; xfer->xferVersion( &version, currentVersion ); // clear our current table of contents m_drawableTOC.clear(); // xfer the table UnsignedInt tocCount = 0; if( xfer->getXferMode() == XFER_SAVE ) { AsciiString templateName; // generate a new TOC based on the drawables that are in the map for( Drawable *draw = getDrawableList(); draw; draw = draw->getNextDrawable() ) { if (!shouldSaveDrawable(draw)) continue; // get the name we're working with templateName = draw->getTemplate()->getName(); // if is this drawable name already in the TOC, skip it if( findTOCEntryByName( templateName ) != NULL ) continue; // add this entry to the TOC addTOCEntry( draw->getTemplate()->getName(), ++tocCount ); } // end for obj // xfer entries in the TOC xfer->xferUnsignedInt( &tocCount ); // xfer each TOC entry DrawableTOCListIterator it; DrawableTOCEntry *tocEntry; for( it = m_drawableTOC.begin(); it != m_drawableTOC.end(); ++it ) { // get this toc entry tocEntry = &(*it); // xfer the name xfer->xferAsciiString( &tocEntry->name ); // xfer the paired id xfer->xferUnsignedShort( &tocEntry->id ); } // end for } // end if else { AsciiString templateName; UnsignedShort id; // how many entries are we going to read xfer->xferUnsignedInt( &tocCount ); // read all the entries for( UnsignedInt i = 0; i < tocCount; ++i ) { // read the name xfer->xferAsciiString( &templateName ); // read the id xfer->xferUnsignedShort( &id ); // add this to the TOC addTOCEntry( templateName, id ); } // end for i } // end else } // end xferDrawableTOC // ------------------------------------------------------------------------------------------------ /** Xfer method for Game Client * Version History: * 1: Initial * 2: Adding mission briefing history * 3: Added block markers around drawable data, no version checking is done and therefore * this version breaks compatibility with previous versions. (CBD) */ // ------------------------------------------------------------------------------------------------ void GameClient::xfer( Xfer *xfer ) { // version XferVersion currentVersion = 3; XferVersion version = currentVersion; xfer->xferVersion( &version, currentVersion ); // client frame number xfer->xferUnsignedInt( &m_frame ); // // note that we do not do the id counter here, we did it in the game state block because // it's important to do that part very early in the load process // // !!!DON'T DO THIS!!! ----> xfer->xferDrawableID( &m_nextDrawableID ); <---- !!!DON'T DO THIS!!! // // xfer a table of contents that contain thing template and indentifier pairs. this // table of contents is good for this save file only as unique numbers are only // generated and stored for the actual things that are on this map // xferDrawableTOC( xfer ); // drawable count Drawable *draw; UnsignedShort drawableCount = 0; for( draw = getDrawableList(); draw; draw = draw->getNextDrawable() ) { if (xfer->getXferMode() == XFER_SAVE && !shouldSaveDrawable(draw)) continue; drawableCount++; } xfer->xferUnsignedShort( &drawableCount ); // drawable data DrawableTOCEntry *tocEntry; ObjectID objectID; if( xfer->getXferMode() == XFER_SAVE ) { // iterate all drawables for( draw = getDrawableList(); draw; draw = draw->getNextDrawable() ) { if (!shouldSaveDrawable(draw)) continue; // get TOC entry for this drawable tocEntry = findTOCEntryByName( draw->getTemplate()->getName() ); if( tocEntry == NULL ) { DEBUG_CRASH(( "GameClient::xfer - Drawable TOC entry not found for '%s'\n", draw->getTemplate()->getName().str() )); throw SC_INVALID_DATA; } // end if // xfer toc id entry xfer->xferUnsignedShort( &tocEntry->id ); // begin data block xfer->beginBlock(); // write the object ID this drawable is attached to objectID = draw->getObject() ? draw->getObject()->getID() : INVALID_ID; xfer->xferObjectID( &objectID ); // write snapshot data xfer->xferSnapshot( draw ); // end data block xfer->endBlock(); } // end for, draw } // end if, save else { UnsignedShort tocID; const ThingTemplate *thingTemplate; Int dataSize; // read all entries for( UnsignedShort i = 0; i < drawableCount; ++i ) { // read toc id entry xfer->xferUnsignedShort( &tocID ); // find TOC entry with this identifier tocEntry = findTOCEntryById( tocID ); if( tocEntry == NULL ) { DEBUG_CRASH(( "GameClient::xfer - No TOC entry match for id '%d'\n", tocID )); throw SC_INVALID_DATA; } // end if // read data block size dataSize = xfer->beginBlock(); // find matching thing template thingTemplate = TheThingFactory->findTemplate( tocEntry->name ); if( thingTemplate == NULL ) { DEBUG_CRASH(( "GameClient::xfer - Unrecognized thing template '%s', skipping. ENGINEERS - Are you *sure* it's OK to be ignoring this object from the save file??? Think hard about it!\n", tocEntry->name.str() )); xfer->skip( dataSize ); continue; } // end if // read the object ID this drawable is attached to (if any) xfer->xferObjectID( &objectID ); // // if we have an attached object ID, we won't create a new drawable, we'll use the // one that has been created and attached to the object already // if( objectID != INVALID_ID ) { Object *object = TheGameLogic->findObjectByID( objectID ); // sanity if( object == NULL ) { DEBUG_CRASH(( "GameClient::xfer - Cannot find object '%d' that is supposed to be attached to this drawable '%s'\n", objectID, thingTemplate->getName().str() )); throw SC_INVALID_DATA; } // end if // get the drawable from the object draw = object->getDrawable(); if( draw == NULL ) { DEBUG_CRASH(( "GameClient::xfer - There is no drawable attached to the object '%s' (%d) and there should be\n", object->getTemplate()->getName().str(), object->getID() )); throw SC_INVALID_DATA; } // end if // srj sez: some objects (eg, diguised bombtrucks) may have an "abnormal" drawable. so check. // // note carefully: we do NOT want to use isEquivalentTo() here, because different object reskins // SHOULD count as different templates for our purposes here (which are purely visual). however, we // do need to compare getFinalOverride, because draw->getTemplate() is always gonna return the final // override, while TheThingFactory->findTemplate does not. // const ThingTemplate* drawTemplate = draw->getTemplate(); if (drawTemplate->getFinalOverride() != thingTemplate->getFinalOverride()) { TheGameClient->destroyDrawable( draw ); draw = TheThingFactory->newDrawable( thingTemplate ); TheGameLogic->bindObjectAndDrawable(object, draw); } } // end if else { // // there was no object attached to this drawable when we saved, we need to create a // whole brand new drawable now // draw = TheThingFactory->newDrawable( thingTemplate ); // sanity if( draw == NULL ) { DEBUG_CRASH(( "GameClient::xfer - Unable to create drawable for '%s'\n", thingTemplate->getName().str() )); throw SC_INVALID_DATA; } // end if } // end else // xfer the drawable data xfer->xferSnapshot( draw ); // end block (not necessary since this is a no-op but symettrically nice) xfer->endBlock(); } // end for, i } // end else, load // xfer the in-game mission briefing history list if (version >= 2) { if( xfer->getXferMode() == XFER_SAVE ) { BriefingList *bList = GetBriefingTextList(); Int numEntries = bList->size(); xfer->xferInt(&numEntries); DEBUG_LOG(("Saving %d briefing lines\n", numEntries)); for (BriefingList::const_iterator bIt = bList->begin(); bIt != bList->end(); ++bIt) { AsciiString tempStr = *bIt; DEBUG_LOG(("'%s'\n", tempStr.str())); xfer->xferAsciiString(&tempStr); } } else // XFER_LOAD { Int numEntries = 0; xfer->xferInt(&numEntries); DEBUG_LOG(("Loading %d briefing lines\n", numEntries)); UpdateDiplomacyBriefingText(AsciiString::TheEmptyString, TRUE); // clear out briefing list first while (numEntries-- > 0) { AsciiString tempStr; xfer->xferAsciiString(&tempStr); DEBUG_LOG(("'%s'\n", tempStr.str())); UpdateDiplomacyBriefingText(tempStr, FALSE); } } } } // end xfer // ------------------------------------------------------------------------------------------------ /** Load post process */ // ------------------------------------------------------------------------------------------------ void GameClient::loadPostProcess( void ) { // // due to the fact that during the load process we have called newDrawable for drawables // without objects, and then overwrote their ids with data from the save file, our allocater // id may be far higher than it needs to be. We'll pull it back down as low as we can // Drawable *draw; for( draw = getDrawableList(); draw; draw = draw->getNextDrawable() ) if( draw->getID() >= m_nextDrawableID ) m_nextDrawableID = (DrawableID)((UnsignedInt)draw->getID() + 1); } // end loadPostProcess // ------------------------------------------------------------------------------------------------ /** CRC */ // ------------------------------------------------------------------------------------------------ void GameClient::crc( Xfer *xfer ) { } // end crc