/*
** 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: 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_ALL;
m_damageOCLTypes = DAMAGE_TYPE_FLAGS_ALL;
m_damageParticleTypes = DAMAGE_TYPE_FLAGS_ALL;
} // 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() );
} // 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