/*
** 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