/* ** 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: TransitionDamageFX.cpp /////////////////////////////////////////////////////////////////// // Author: Colin Day, March 2002 // Desc: Damage module capable of launching various effects on damage transitions /////////////////////////////////////////////////////////////////////////////////////////////////// // INCLUDES /////////////////////////////////////////////////////////////////////////////////////// #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine #include "Common/Xfer.h" #include "GameClient/Drawable.h" #include "GameClient/FXList.h" #include "GameLogic/GameLogic.h" #include "GameLogic/Object.h" #include "GameLogic/ObjectCreationList.h" #include "GameLogic/Module/TransitionDamageFX.h" //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- TransitionDamageFXModuleData::TransitionDamageFXModuleData( void ) { Int i, j; for( i = 0; i < BODYDAMAGETYPE_COUNT; i++ ) { for( j = 0; j < DAMAGE_MODULE_MAX_FX; j++ ) { m_fxList[ i ][ j ].fx = NULL; m_fxList[ i ][ j ].locInfo.loc.x = 0.0f; m_fxList[ i ][ j ].locInfo.loc.y = 0.0f; m_fxList[ i ][ j ].locInfo.loc.z = 0.0f; m_fxList[ i ][ j ].locInfo.locType = FX_DAMAGE_LOC_TYPE_COORD; m_fxList[ i ][ j ].locInfo.randomBone = FALSE; m_OCL[ i ][ j ].ocl = NULL; m_OCL[ i ][ j ].locInfo.loc.x = 0.0f; m_OCL[ i ][ j ].locInfo.loc.y = 0.0f; m_OCL[ i ][ j ].locInfo.loc.z = 0.0f; m_OCL[ i ][ j ].locInfo.locType = FX_DAMAGE_LOC_TYPE_COORD; m_OCL[ i ][ j ].locInfo.randomBone = FALSE; m_particleSystem[ i ][ j ].particleSysTemplate = NULL; m_particleSystem[ i ][ j ].locInfo.loc.x = 0.0f; m_particleSystem[ i ][ j ].locInfo.loc.y = 0.0f; m_particleSystem[ i ][ j ].locInfo.loc.z = 0.0f; m_particleSystem[ i ][ j ].locInfo.locType = FX_DAMAGE_LOC_TYPE_COORD; m_particleSystem[ i ][ j ].locInfo.randomBone = FALSE; } // end for j } // end for i m_damageFXTypes = DAMAGE_TYPE_FLAGS_NONE; m_damageFXTypes.flip(); m_damageOCLTypes = DAMAGE_TYPE_FLAGS_NONE; m_damageOCLTypes.flip(); m_damageParticleTypes = DAMAGE_TYPE_FLAGS_NONE; m_damageParticleTypes.flip(); } // end TransitionDamageFXModuleData //------------------------------------------------------------------------------------------------- /** Parse fx location info ... that is a named bone or a coord3d position */ //------------------------------------------------------------------------------------------------- static void parseFXLocInfo( INI *ini, void *instance, FXLocInfo *locInfo ) { const char *token = ini->getNextToken( ini->getSepsColon() ); if( stricmp( token, "bone" ) == 0 ) { // save bone name and location type locInfo->boneName = AsciiString( ini->getNextToken() ); locInfo->locType = FX_DAMAGE_LOC_TYPE_BONE; // // bones are followed by RandomBone:, if random bone is yes, the bone name is // assumed to be a "base bone name" and we will find all the bones with that prefix // when picking an effect position. If it's no, the bone name is assumed to be explicit // token = ini->getNextToken( ini->getSepsColon() ); if( stricmp( token, "randombone" ) != 0 ) { DEBUG_CRASH(( "parseFXLocInfo: Bone name not followed by RandomBone specifier\nPress IGNORE to see which INI file and line # is incorrect." )); throw INI_INVALID_DATA; } // end if // parse the Bool definition ini->parseBool( ini, instance, &locInfo->randomBone, NULL ); } // end if else if( stricmp( token, "loc" ) == 0 ) { // save location and location type locInfo->loc.x = ini->scanReal( ini->getNextSubToken("X") ); locInfo->loc.y = ini->scanReal( ini->getNextSubToken("Y") ); locInfo->loc.z = ini->scanReal( ini->getNextSubToken("Z") ); locInfo->locType = FX_DAMAGE_LOC_TYPE_COORD; } // end else else { // error throw INI_INVALID_DATA; } // end else } // end parseFXLocInfo //------------------------------------------------------------------------------------------------- /** In the form of: * FXListSlot = <> | > FXList:FXListName */ //------------------------------------------------------------------------------------------------- void TransitionDamageFXModuleData::parseFXList( INI *ini, void *instance, void *store, const void *userData ) { const char *token; FXDamageFXListInfo *info = (FXDamageFXListInfo *)store; // parse the location bone or location parseFXLocInfo( ini, instance, &info->locInfo ); // make sure we have an "FXList:" token token = ini->getNextToken( ini->getSepsColon() ); if( stricmp( token, "fxlist" ) != 0 ) { // error throw INI_INVALID_DATA; } // end if // parse the fx list name ini->parseFXList( ini, instance, &info->fx, NULL ); } // end parseFXList //------------------------------------------------------------------------------------------------- /** In the form of: * OCLSlot = <> | > OCL:OCLName */ //------------------------------------------------------------------------------------------------- void TransitionDamageFXModuleData::parseObjectCreationList( INI *ini, void *instance, void *store, const void *userData ) { const char *token; FXDamageOCLInfo *info = (FXDamageOCLInfo *)store; // parse the location bone or location parseFXLocInfo( ini, instance, &info->locInfo ); // make sure we have an "OCL:" token token = ini->getNextToken( ini->getSepsColon() ); if( stricmp( token, "ocl" ) != 0 ) { // error throw INI_INVALID_DATA; } // end if // parse the ocl name ini->parseObjectCreationList( ini, instance, store, &info->ocl ); } // end parseObjectCreationList //------------------------------------------------------------------------------------------------- /** In the form of: * ParticleSlot = <> | > PSys:PSysName */ //------------------------------------------------------------------------------------------------- void TransitionDamageFXModuleData::parseParticleSystem( INI *ini, void *instance, void *store, const void *userData ) { const char *token; FXDamageParticleSystemInfo *info = (FXDamageParticleSystemInfo *)store; // parse the location bone or location parseFXLocInfo( ini, instance, &info->locInfo ); // make sure we have an "PSys:" token token = ini->getNextToken( ini->getSepsColon() ); if( stricmp( token, "psys" ) != 0 ) { // error throw INI_INVALID_DATA; } // end if // parse the particle system name ini->parseParticleSystemTemplate( ini, instance, store, &info->particleSysTemplate ); } // end parseParticleSystem //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- TransitionDamageFX::TransitionDamageFX( Thing *thing, const ModuleData* moduleData ) : DamageModule( thing, moduleData ) { Int i, j; for( i = 0; i < BODYDAMAGETYPE_COUNT; i++ ) for( j = 0; j < DAMAGE_MODULE_MAX_FX; j++ ) m_particleSystemID[ i ][ j ] = INVALID_PARTICLE_SYSTEM_ID; } // end TransitionDamageFX //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- TransitionDamageFX::~TransitionDamageFX( void ) { } // end ~TransitionDamageFX /* //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- void TransitionDamageFX::onDelete( void ) { // // we would in theory delete any particle systems we have created and attached, but the // particle system will automatically delete itself when the object is destroyed // } // end onDelete */ //------------------------------------------------------------------------------------------------- /** Given an FXLoc info struct, return the effect position that we are supposed to use. * The position is local to to the object */ //------------------------------------------------------------------------------------------------- static Coord3D getLocalEffectPos( const FXLocInfo *locInfo, Drawable *draw ) { DEBUG_ASSERTCRASH( locInfo, ("getLocalEffectPos: locInfo is NULL\n") ); if( locInfo->locType == FX_DAMAGE_LOC_TYPE_BONE && draw ) { if( locInfo->randomBone == FALSE ) { Coord3D pos; // get the bone position Int count = draw->getPristineBonePositions( locInfo->boneName.str(), 0, &pos, NULL, 1 ); // sanity, if bone not found revert back to location defined in struct (which is 0,0,0) if( count == 0 ) return locInfo->loc; // return the position retrieved return pos; } // end if else { const Int MAX_BONES = 32; Coord3D positions[ MAX_BONES ]; // get the bone positions Int boneCount; boneCount = draw->getPristineBonePositions( locInfo->boneName.str(), 1, positions, NULL, MAX_BONES ); // sanity, if bone not found revert back to location defined in struct (which is 0,0,0) if( boneCount == 0 ) return locInfo->loc; // pick one of the bone positions Int pick = GameLogicRandomValue( 0, boneCount - 1 ); return positions[ pick ]; } // end else } // end if else return locInfo->loc; } // end getLocalEffectPos //------------------------------------------------------------------------------------------------- /** Switching damage states */ //------------------------------------------------------------------------------------------------- void TransitionDamageFX::onBodyDamageStateChange( const DamageInfo* damageInfo, BodyDamageType oldState, BodyDamageType newState ) { Object *damageSource = NULL; Int i; Drawable *draw = getObject()->getDrawable(); const TransitionDamageFXModuleData *modData = getTransitionDamageFXModuleData(); // get the source of the damage if present if( damageInfo ) damageSource = TheGameLogic->findObjectByID( damageInfo->in.m_sourceID ); // remove any particle systems that might be emitting from our old state for( i = 0; i < DAMAGE_MODULE_MAX_FX; i++ ) { if( m_particleSystemID[ oldState ][ i ] != INVALID_PARTICLE_SYSTEM_ID ) { TheParticleSystemManager->destroyParticleSystemByID( m_particleSystemID[ oldState ][ i ] ); m_particleSystemID[ oldState ][ i ] = INVALID_PARTICLE_SYSTEM_ID; } // end if } // end for i // // when we are transitioning to a "worse" state we will play a set of effects for that // new state to make the transition // if( IS_CONDITION_WORSE( newState, oldState ) ) { const ParticleSystemTemplate *pSystemT; Coord3D pos; // if we are restricted by the damage type executing effect, bail out of here const DamageInfo *lastDamageInfo = getObject()->getBodyModule()->getLastDamageInfo(); for( i = 0; i < DAMAGE_MODULE_MAX_FX; i++ ) { // play fx list for our new state if( modData->m_fxList[ newState ][ i ].fx ) { if( lastDamageInfo == NULL || getDamageTypeFlag( modData->m_damageFXTypes, lastDamageInfo->in.m_damageType ) ) { pos = getLocalEffectPos( &modData->m_fxList[ newState ][ i ].locInfo, draw ); getObject()->convertBonePosToWorldPos( &pos, NULL, &pos, NULL ); FXList::doFXPos( modData->m_fxList[ newState ][ i ].fx, &pos ); } // end if } // end if // do any object creation list for our new state if( modData->m_OCL[ newState ][ i ].ocl ) { if( lastDamageInfo == NULL || getDamageTypeFlag( modData->m_damageOCLTypes, lastDamageInfo->in.m_damageType ) ) { pos = getLocalEffectPos( &modData->m_OCL[ newState ][ i ].locInfo, draw ); getObject()->convertBonePosToWorldPos( &pos, NULL, &pos, NULL ); ObjectCreationList::create( modData->m_OCL[ newState ][ i ].ocl, getObject(), &pos, damageSource->getPosition(), INVALID_ANGLE ); } // end if } // end if // get the template of the system to create pSystemT = modData->m_particleSystem[ newState ][ i ].particleSysTemplate; if( pSystemT ) { if( lastDamageInfo == NULL || getDamageTypeFlag( modData->m_damageParticleTypes, lastDamageInfo->in.m_damageType ) ) { // create a new particle system based on the template provided ParticleSystem* pSystem = TheParticleSystemManager->createParticleSystem( pSystemT ); if( pSystem ) { // get the what is the position we're going to playe the effect at pos = getLocalEffectPos( &modData->m_particleSystem[ newState ][ i ].locInfo, draw ); // // set position on system given any bone position provided, the bone position is // local to the object and that's what we want for the particle system ... the // transormation into world space using the object position is taken care of in // the particle system attachToObject method // pSystem->setPosition( &pos ); // attach to object pSystem->attachToObject( getObject() ); // save the id of this particle system so we can remove it later if it still exists m_particleSystemID[ newState ][ i ] = pSystem->getSystemID(); } // end if } // end if } // end if } // end for i } // end if } // end onBodyDamageStateChange // ------------------------------------------------------------------------------------------------ /** CRC */ // ------------------------------------------------------------------------------------------------ void TransitionDamageFX::crc( Xfer *xfer ) { // extend base class DamageModule::crc( xfer ); } // end crc // ------------------------------------------------------------------------------------------------ /** Xfer method * Version Info: * 1: Initial version */ // ------------------------------------------------------------------------------------------------ void TransitionDamageFX::xfer( Xfer *xfer ) { // version XferVersion currentVersion = 1; XferVersion version = currentVersion; xfer->xferVersion( &version, currentVersion ); // extend base class DamageModule::xfer( xfer ); // particle systems ids xfer->xferUser( m_particleSystemID, sizeof( ParticleSystemID ) * BODYDAMAGETYPE_COUNT * DAMAGE_MODULE_MAX_FX ); } // end xfer // ------------------------------------------------------------------------------------------------ /** Load post process */ // ------------------------------------------------------------------------------------------------ void TransitionDamageFX::loadPostProcess( void ) { // extend base class DamageModule::loadPostProcess(); } // end loadPostProcess