/* ** 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: Anim2D.cpp /////////////////////////////////////////////////////////////////////////////// // Author: Colin Day, July 2002 // Desc: A collection of 2D images to make animation /////////////////////////////////////////////////////////////////////////////////////////////////// // INCLUDES /////////////////////////////////////////////////////////////////////////////////////// #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine #define DEFINE_ANIM_2D_MODE_NAMES #include "Common/RandomValue.h" #include "Common/Xfer.h" #include "GameClient/Anim2D.h" #include "GameClient/Display.h" #include "GameClient/Image.h" #include "GameLogic/GameLogic.h" #ifdef _INTERNAL // for occasional debugging... //#pragma optimize("", off) //#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") #endif // GLOBAL ///////////////////////////////////////////////////////////////////////////////////////// Anim2DCollection *TheAnim2DCollection = NULL; /////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////// // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ Anim2DTemplate::Anim2DTemplate( AsciiString name ) { m_name = name; m_images = NULL; m_numFrames = NUM_FRAMES_INVALID; m_framesBetweenUpdates = 0; m_animMode = ANIM_2D_LOOP; m_randomizeStartFrame = FALSE; m_nextTemplate = NULL; } // end Anim2DTemplate // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ Anim2DTemplate::~Anim2DTemplate( void ) { // delete the images if( m_images ) delete [] m_images; } // end ~Anim2DTemplate // ------------------------------------------------------------------------------------------------ /** Field parse table for 2D animation templates */ // ---------i--------------------------------------------------------------------------------------- const FieldParse Anim2DTemplate::s_anim2DFieldParseTable[] = { { "NumberImages", Anim2DTemplate::parseNumImages, NULL, 0 }, { "Image", Anim2DTemplate::parseImage, NULL, 0 }, { "ImageSequence", Anim2DTemplate::parseImageSequence, NULL, 0 }, { "AnimationMode", INI::parseIndexList, Anim2DModeNames, offsetof( Anim2DTemplate, m_animMode ) }, { "AnimationDelay", INI::parseDurationUnsignedShort, NULL, offsetof( Anim2DTemplate, m_framesBetweenUpdates ) }, { "RandomizeStartFrame", INI::parseBool, NULL, offsetof( Anim2DTemplate, m_randomizeStartFrame ) }, { NULL, NULL, NULL, 0 } }; // ------------------------------------------------------------------------------------------------ /** Parse the number of images we will have in this animation and allocate the array for them */ // ------------------------------------------------------------------------------------------------ void Anim2DTemplate::parseNumImages( INI *ini, void *instance, void *store, const void *userData ) { // parse the integer data from the file UnsignedInt numFrames; ini->parseUnsignedInt( ini, instance, &numFrames, userData ); // get the template we are to store into Anim2DTemplate *animTemplate = (Anim2DTemplate *)instance; // animations must have a minimum # of frames Int minimumFrames = 1; if( numFrames < minimumFrames ) { DEBUG_CRASH(( "Anim2DTemplate::parseNumImages - Invalid animation '%s', animations must have '%d' or more frames defined\n", animTemplate->getName().str(), minimumFrames )); throw INI_INVALID_DATA; } // end if // allocate the image array animTemplate->allocateImages( (UnsignedShort)numFrames ); } // end parseNumImages // ------------------------------------------------------------------------------------------------ /** Allocate the image array for an animation template and store the number of frames we have */ // ------------------------------------------------------------------------------------------------ void Anim2DTemplate::allocateImages( UnsignedShort numFrames ) { // store the number of frames m_numFrames = numFrames; // allocate an array to hold the image pointers m_images = NEW const Image *[ m_numFrames ]; // pool[]ify // set all the images to NULL; for( Int i = 0; i < m_numFrames; ++i ) m_images[ i ] = NULL; } // end allocateImages // ------------------------------------------------------------------------------------------------ /** Parsing a single image definition for an animation */ // ------------------------------------------------------------------------------------------------ void Anim2DTemplate::parseImage( INI *ini, void *instance, void *store, const void *userData ) { // parse the image name from the file and store as an image pointer const Image *image; ini->parseMappedImage( ini, instance, &image, userData ); // sanity if( image == NULL ) { //We don't care if we're in the builder //DEBUG_CRASH(( "Anim2DTemplate::parseImage - Image not found\n" )); //throw INI_INVALID_DATA; } // end if // // assign the image to the animation template list of images ... note since we've pre-allocated // the array of images and the index an image is loaded into depends on its order specified // in INI, we need to get the number of images currently loaded into this animation template // so that we can put it at the next free image spot ... and then tell the animation // template we've loaded one more // Anim2DTemplate *animTemplate = (Anim2DTemplate *)instance; animTemplate->storeImage( image ); } // end parseImage // ------------------------------------------------------------------------------------------------ /** This will parse the image sequence of an animation. You can use this as a shortcut to * specifying a series of images for an animation instead of having to specify them all * individually. Image names will be assumed to start with an appended "000" to the end * end of the first image name and incremented up to the number of images for the * animation. NOTE: That the number images *must* have already been specified before * we can parse this entry so we know how many images to allocate and look for */ // ------------------------------------------------------------------------------------------------ /*static*/ void Anim2DTemplate::parseImageSequence( INI *ini, void *instance, void *store, const void *userData ) { // get the animation template Anim2DTemplate *animTemplate = (Anim2DTemplate *)instance; // // before we can read, allocate, and find all the images for the sequence ... we must // know how many total images are in this animation. Verify that now // if( animTemplate->getNumFrames() == NUM_FRAMES_INVALID ) { DEBUG_CRASH(( "Anim2DTemplate::parseImageSequence - You must specify the number of animation frames for animation '%s' *BEFORE* specifying the image sequence name\n", animTemplate->getName().str() )); throw INI_INVALID_DATA; } // end if // // the image storage has already been allocated, all we have to do now is count from // 0 to numImages - 1 and add those images to the template // AsciiString imageBaseName = ini->getNextAsciiString(); AsciiString imageName; const Image *image; for( Int i = 0; i < animTemplate->getNumFrames(); ++i ) { // construct this name imageName.format( "%s%03d", imageBaseName.str(), i ); // search for this image image = TheMappedImageCollection->findImageByName( imageName ); // sanity if( image == NULL ) { DEBUG_CRASH(( "Anim2DTemplate::parseImageSequence - Image '%s' not found for animation '%s'. Check the number of images specified in INI and also make sure all the actual images exist.\n", imageName.str(), animTemplate->getName().str() )); throw INI_INVALID_DATA; } // end if // store the image in the next free frame animTemplate->storeImage( image ); } // end if } // end parseImageSequence // ------------------------------------------------------------------------------------------------ /** Store the image at the next open image slot for the animation */ // ------------------------------------------------------------------------------------------------ void Anim2DTemplate::storeImage( const Image *image ) { // sanity if( image == NULL ) return; // search through the image list and store at the next free spot for( Int i = 0; i < m_numFrames; ++i ) { if( m_images[ i ] == NULL ) { m_images[ i ] = image; return; } // end if } // end for i // if we got here we tried to store an image in an array that was too small DEBUG_CRASH(( "Anim2DTemplate::storeImage - Unable to store image '%s' into animation '%s' because the animation is setup to only support '%d' image frames\n", image->getName().str(), getName().str(), m_numFrames )); throw INI_INVALID_DATA; } // end storeImage // ------------------------------------------------------------------------------------------------ /** Return the Image* for the frame number requested */ // ------------------------------------------------------------------------------------------------ const Image* Anim2DTemplate::getFrame( UnsignedShort frameNumber ) const { // sanity DEBUG_ASSERTCRASH( m_images != NULL, ("Anim2DTemplate::getFrame - Image data is NULL for animation '%s'\n", getName().str()) ); // sanity if( frameNumber < 0 || frameNumber >= m_numFrames ) { DEBUG_CRASH(( "Anim2DTemplate::getFrame - Illegal frame number '%d' for animation '%s'\n", frameNumber, getName().str() )); return NULL; } // end if else { // return the image frame return m_images[ frameNumber ]; } // end else } // end getFrame /////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////// // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ Anim2D::Anim2D( Anim2DTemplate *animTemplate, Anim2DCollection *collectionSystem ) { // sanity DEBUG_ASSERTCRASH( animTemplate != NULL, ("Anim2D::Anim2D - NULL template\n") ); //Added By Sadullah Nader //Initialization m_currentFrame = 0; // // set the template m_template = animTemplate; // initialize members m_status = ANIM_2D_STATUS_NONE; m_alpha = 1.0f; // set the initial frame for the animation based on the type of animation mode or randomize if( m_template->isRandomizedStartFrame() ) randomizeCurrentFrame(); else reset(); m_minFrame = 0; m_maxFrame = m_template->getNumFrames() - 1; m_framesBetweenUpdates = m_template->getNumFramesBetweenUpdates(); //added by Sadullah Nader // initializing pointers to NULL, and clearing Frame counters before // we register ourselves to the System m_collectionSystemNext = NULL; m_collectionSystemPrev = NULL; m_lastUpdateFrame = 0; // if a system is present, register ourselves with that system m_collectionSystem = collectionSystem; if( m_collectionSystem ) m_collectionSystem->registerAnimation( this ); } // end Anim2D // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ Anim2D::~Anim2D( void ) { // if we were registered with a system, un-register ourselves if( m_collectionSystem ) m_collectionSystem->unRegisterAnimation( this ); } // end ~Anim2D // ------------------------------------------------------------------------------------------------ /** Set the current animation frame */ // ------------------------------------------------------------------------------------------------ void Anim2D::setCurrentFrame( UnsignedShort frame ) { // sanity DEBUG_ASSERTCRASH( m_template != NULL, ("Anim2D::reset - No template for animation\n") ); // sanity DEBUG_ASSERTCRASH( TheGameLogic != NULL, ("Anim2D::setCurrentFrame - TheGameLogic must exist to use animation instances (%s)\n", m_template->getName().str()) ); // sanity DEBUG_ASSERTCRASH( frame >= 0 && frame < m_template->getNumFrames(), ("Anim2D::setCurrentFrame - Illegal frame number '%d' in animation\n", frame, m_template->getName().str()) ); // set the frame m_currentFrame = frame; // record the frame of this update to our current frame m_lastUpdateFrame = TheGameLogic->getFrame(); } // end setCurrentFrame // ------------------------------------------------------------------------------------------------ /** Randomize the current frame */ // ------------------------------------------------------------------------------------------------ void Anim2D::randomizeCurrentFrame( void ) { // sanity DEBUG_ASSERTCRASH( m_template != NULL, ("Anim2D::reset - No template for animation\n") ); // set the current frame to a random frame setCurrentFrame( GameClientRandomValue( 0, m_template->getNumFrames() - 1 ) ); } // end randomizeCurrentFrame // ------------------------------------------------------------------------------------------------ /** Reset this animation instance to the "start" of the animation */ // ------------------------------------------------------------------------------------------------ void Anim2D::reset( void ) { // sanity DEBUG_ASSERTCRASH( m_template != NULL, ("Anim2D::reset - No template for animation\n") ); switch( m_template->getAnimMode() ) { // -------------------------------------------------------------------------------------------- case ANIM_2D_ONCE: case ANIM_2D_LOOP: case ANIM_2D_PING_PONG: setCurrentFrame( m_minFrame ); break; // -------------------------------------------------------------------------------------------- case ANIM_2D_ONCE_BACKWARDS: case ANIM_2D_LOOP_BACKWARDS: case ANIM_2D_PING_PONG_BACKWARDS: setCurrentFrame( m_maxFrame ); break; // -------------------------------------------------------------------------------------------- default: DEBUG_CRASH(( "Anim2D::reset - Unknown animation mode '%d' for '%s'\n", m_template->getAnimMode(), m_template->getName().str() )); break; } // end switch, animation mode } // end reset // ------------------------------------------------------------------------------------------------ /** This is called after we are drawn ... if sufficient time has passed since our last * frame update we will update our current frame */ // ------------------------------------------------------------------------------------------------ void Anim2D::tryNextFrame( void ) { // sanity DEBUG_ASSERTCRASH( TheGameLogic != NULL, ("Anim2D::tryNextFrame - TheGameLogic must exist to use animation instances (%s)\n", m_template->getName().str()) ); // how many frames have passed since our last update if( TheGameLogic->getFrame() - m_lastUpdateFrame >= m_framesBetweenUpdates ) { switch( m_template->getAnimMode() ) { // ------------------------------------------------------------------------------------------ case ANIM_2D_ONCE: { if( m_currentFrame < m_maxFrame ) setCurrentFrame( m_currentFrame + 1 ); else setStatus( ANIM_2D_STATUS_COMPLETE ); break; } // end once // ------------------------------------------------------------------------------------------- case ANIM_2D_ONCE_BACKWARDS: { if( m_currentFrame > m_minFrame ) setCurrentFrame( m_currentFrame - 1 ); else setStatus( ANIM_2D_STATUS_COMPLETE ); break; } // end once backwards // ------------------------------------------------------------------------------------------- case ANIM_2D_LOOP: { if( m_currentFrame == m_maxFrame ) setCurrentFrame( m_minFrame ); else setCurrentFrame( m_currentFrame + 1 ); break; } // end loop // ------------------------------------------------------------------------------------------- case ANIM_2D_LOOP_BACKWARDS: { if( m_currentFrame > m_minFrame ) setCurrentFrame( m_currentFrame - 1 ); else setCurrentFrame( m_maxFrame ); break; } // end loop backwards // ------------------------------------------------------------------------------------------- case ANIM_2D_PING_PONG: case ANIM_2D_PING_PONG_BACKWARDS: { if( BitTest( m_status, ANIM_2D_STATUS_REVERSED ) ) { // // decrement frame, unless we're at frame 0 in which case we // increment and reverse directions // if( m_currentFrame == m_minFrame ) { setCurrentFrame( m_currentFrame + 1 ); clearStatus( ANIM_2D_STATUS_REVERSED ); } // end if else { setCurrentFrame( m_currentFrame - 1 ); } // end else } // end if else { // // increment frame, unless we're at the end in which case we decrement // and reverse directions // if( m_currentFrame == m_maxFrame ) { setCurrentFrame( m_currentFrame - 1 ); setStatus( ANIM_2D_STATUS_REVERSED ); } // end if else { setCurrentFrame( m_currentFrame + 1 ); } // end else } // end else break; } // end ping pong / ping pong backwards // ------------------------------------------------------------------------------------------- default: { DEBUG_CRASH(( "Anim2D::tryNextFrame - Unknown animation mode '%d' for '%s'\n", m_template->getAnimMode(), m_template->getName().str() )); break; } // end default } // end switch } // end if } // end tryNextFrame // ------------------------------------------------------------------------------------------------ /** Set status bit */ // ------------------------------------------------------------------------------------------------ void Anim2D::setStatus( UnsignedByte statusBits ) { // set the bits BitSet( m_status, statusBits ); } // end setStatus // ------------------------------------------------------------------------------------------------ /** Clear status bit */ // ------------------------------------------------------------------------------------------------ void Anim2D::clearStatus( UnsignedByte statusBits ) { // clear bits BitClear( m_status, statusBits ); } // end clearStatus // ------------------------------------------------------------------------------------------------ /** Return the "natural" width of the image for our current frame */ // ------------------------------------------------------------------------------------------------ UnsignedInt Anim2D::getCurrentFrameWidth( void ) const { const Image *currentFrameImage = m_template->getFrame( m_currentFrame ); if( currentFrameImage ) return currentFrameImage->getImageWidth(); return 0; } // end getCurrentFrameWidth // ------------------------------------------------------------------------------------------------ /** Return the "natural" height of the image for our current frame */ // ------------------------------------------------------------------------------------------------ UnsignedInt Anim2D::getCurrentFrameHeight( void ) const { const Image *currentFrameImage = m_template->getFrame( m_currentFrame ); if( currentFrameImage ) return currentFrameImage->getImageHeight(); return 0; } // end getCurrentFrameHeight // ------------------------------------------------------------------------------------------------ /** Draw an Anim2D using the natural width and height of the image data */ // ------------------------------------------------------------------------------------------------ void Anim2D::draw( Int x, Int y ) { // get the current image const Image *image = m_template->getFrame( m_currentFrame ); // sanity DEBUG_ASSERTCRASH( image != NULL, ("Anim2D::draw - Image not found for frame '%d' on animation '%s'\n", m_currentFrame, m_template->getName().str()) ); // get the natural width and height of this image const ICoord2D *imageSize = image->getImageSize(); // draw the image Color color = GameMakeColor( 255, 255, 255, 255 * m_alpha ); TheDisplay->drawImage( image, x, y, x + imageSize->x, y + imageSize->y, color ); // // see if it's time for us to go to the next frame in the sequence, we do not update // frame numbers for animation instances that are registered with a system as the // system will update them during its update phase // if( m_collectionSystem == NULL && BitTest( m_status, ANIM_2D_STATUS_FROZEN ) == FALSE ) tryNextFrame(); } // end draw // ------------------------------------------------------------------------------------------------ /** Drawing an Anim2D using a forced width and height */ // ------------------------------------------------------------------------------------------------ void Anim2D::draw( Int x, Int y, Int width, Int height ) { // get the current image const Image *image = m_template->getFrame( m_currentFrame ); // sanity DEBUG_ASSERTCRASH( image != NULL, ("Anim2D::draw - Image not found for frame '%d' on animation '%s'\n", m_currentFrame, m_template->getName().str()) ); // draw image to the display Color color = GameMakeColor( 255, 255, 255, 255 * m_alpha ); TheDisplay->drawImage( image, x, y, x + width, y + height, color ); // // see if it's time for us to go to the next frame in the sequence, we do not update // frame numbers for animation instances that are registered with a system as the // system will update them during its update phase // if( m_collectionSystem == NULL && BitTest( m_status, ANIM_2D_STATUS_FROZEN ) == FALSE ) tryNextFrame(); } // end draw // ------------------------------------------------------------------------------------------------ /** Xfer Method * Version Info: * 1: Initial version */ // ------------------------------------------------------------------------------------------------ void Anim2D::xfer( Xfer *xfer ) { // version XferVersion currentVersion = 1; XferVersion version = currentVersion; xfer->xferVersion( &version, currentVersion ); // current frame xfer->xferUnsignedShort( &m_currentFrame ); // last update frame xfer->xferUnsignedInt( &m_lastUpdateFrame ); // status xfer->xferUnsignedByte( &m_status ); // min frame xfer->xferUnsignedShort( &m_minFrame ); // max frame xfer->xferUnsignedShort( &m_maxFrame ); // frames between updates xfer->xferUnsignedInt( &m_framesBetweenUpdates ); // alpha xfer->xferReal( &m_alpha ); } // end xfer /////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////// // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ Anim2DCollection::Anim2DCollection( void ) { m_templateList = NULL; m_instanceList = NULL; } // end Anim2DCollection // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ Anim2DCollection::~Anim2DCollection( void ) { // there should not be any animation instances registered with us since we're being destroyed DEBUG_ASSERTCRASH( m_instanceList == NULL, ("Anim2DCollection - instance list is not NULL\n") ); // delete all the templates Anim2DTemplate *nextTemplate; while( m_templateList ) { // get next template nextTemplate = m_templateList->friend_getNextTemplate(); // delete this template m_templateList->deleteInstance(); // set the head of our list to the next template m_templateList = nextTemplate; } // end while } // end ~Anim2DCollection // ------------------------------------------------------------------------------------------------ /** Initialize 2D animation collection */ // ------------------------------------------------------------------------------------------------ void Anim2DCollection::init( void ) { INI ini; ini.load( "Data\\INI\\Animation2D.ini", INI_LOAD_OVERWRITE, NULL ); } // end init // ------------------------------------------------------------------------------------------------ /** System update phase */ // ------------------------------------------------------------------------------------------------ void Anim2DCollection::update( void ) { Anim2D *anim; // go through all our animations for( anim = m_instanceList; anim; anim = anim->m_collectionSystemNext ) { // try to update the frame if( BitTest( anim->getStatus(), ANIM_2D_STATUS_FROZEN ) == FALSE ) anim->tryNextFrame(); } // end for, anim } // end update // ------------------------------------------------------------------------------------------------ /** Search the template list for a template with a matching name */ // ------------------------------------------------------------------------------------------------ Anim2DTemplate *Anim2DCollection::findTemplate( const AsciiString& name ) { // search the list for( Anim2DTemplate *animTemplate = m_templateList; animTemplate; animTemplate = animTemplate->friend_getNextTemplate() ) { if( animTemplate->getName() == name ) return animTemplate; } // end for return NULL; // template not found } // end findTemplate //------------------------------------------------------------------------------------------------- Anim2DTemplate* Anim2DCollection::getNextTemplate( Anim2DTemplate *animTemplate ) const { if( animTemplate ) { return animTemplate->friend_getNextTemplate(); } return NULL; } // ------------------------------------------------------------------------------------------------ /** Allocate a new template, assign name, and link to our internal list */ // ------------------------------------------------------------------------------------------------ Anim2DTemplate *Anim2DCollection::newTemplate( const AsciiString& name ) { // allocate a new template Anim2DTemplate *animTemplate = newInstance(Anim2DTemplate)( name ); // link to our template list animTemplate->friend_setNextTemplate( m_templateList ); m_templateList = animTemplate; // return the new template return animTemplate; } // end newTemplate // ------------------------------------------------------------------------------------------------ /** Register animation instance with us. When an animation instance is registered it can * be updated even when it's not drawn */ // ------------------------------------------------------------------------------------------------ void Anim2DCollection::registerAnimation( Anim2D *anim ) { // sanity if( anim == NULL ) return; // sanity DEBUG_ASSERTCRASH( anim->m_collectionSystemNext == NULL && anim->m_collectionSystemPrev == NULL, ("Registering animation instance, instance '%s' is already in a system\n", anim->getAnimTemplate()->getName().str()) ); // tie to our list anim->m_collectionSystemPrev = NULL; anim->m_collectionSystemNext = m_instanceList; if( m_instanceList ) m_instanceList->m_collectionSystemPrev = anim; m_instanceList = anim; } // end registerAnimation // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ void Anim2DCollection::unRegisterAnimation( Anim2D *anim ) { // sanity if( anim == NULL ) return; // if animation is not registered with us do nothing if( anim->m_collectionSystem != this ) return; // unlink from our instnace list if( anim->m_collectionSystemNext ) anim->m_collectionSystemNext->m_collectionSystemPrev = anim->m_collectionSystemPrev; if( anim->m_collectionSystemPrev ) anim->m_collectionSystemPrev->m_collectionSystemNext = anim->m_collectionSystemNext; else m_instanceList = anim->m_collectionSystemNext; } // end unRegisterAnimation