/* ** 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: FlammableUpdate.cpp ///////////////////////////////////////////////////////////////////////// // Author: Graham Smallwood, April 2002 // Desc: Update that manages Aflame and Burned statuses and their effects /////////////////////////////////////////////////////////////////////////////////////////////////// // INCLUDES /////////////////////////////////////////////////////////////////////////////////////// #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine #include "Common/AudioEventRTS.h" #include "Common/GameAudio.h" #include "Common/Xfer.h" #include "GameLogic/GameLogic.h" #include "GameLogic/Object.h" #include "GameLogic/Module/BodyModule.h" #include "GameLogic/Module/FlammableUpdate.h" #include "GameLogic/Module/FireSpreadUpdate.h" //------------------------------------------------------------------------------------------------- FlammableUpdateModuleData::FlammableUpdateModuleData() { m_burnedDelay = 0; m_aflameDuration = 0; m_aflameDamageDelay = 0; m_aflameDamageAmount = 0; // Enabled By Sadullah Nader // Initialization needed m_burningSoundName.clear(); // m_flameDamageLimitData = 20.0f; m_flameDamageExpirationDelay = LOGICFRAMES_PER_SECOND * 2; } //------------------------------------------------------------------------------------------------- /*static*/ void FlammableUpdateModuleData::buildFieldParse(MultiIniFieldParse& p) { UpdateModuleData::buildFieldParse(p); static const FieldParse dataFieldParse[] = { { "BurnedDelay", INI::parseDurationUnsignedInt, NULL, offsetof( FlammableUpdateModuleData, m_burnedDelay ) }, { "AflameDuration", INI::parseDurationUnsignedInt, NULL, offsetof( FlammableUpdateModuleData, m_aflameDuration ) }, { "AflameDamageDelay", INI::parseDurationUnsignedInt, NULL, offsetof( FlammableUpdateModuleData, m_aflameDamageDelay ) }, { "AflameDamageAmount", INI::parseInt, NULL, offsetof( FlammableUpdateModuleData, m_aflameDamageAmount ) }, { "BurningSoundName", INI::parseAsciiString, NULL, offsetof( FlammableUpdateModuleData, m_burningSoundName) }, { "FlameDamageLimit", INI::parseReal, NULL, offsetof( FlammableUpdateModuleData, m_flameDamageLimitData ) }, { "FlameDamageExpiration", INI::parseDurationUnsignedInt, NULL, offsetof( FlammableUpdateModuleData, m_flameDamageExpirationDelay ) }, { 0, 0, 0, 0 } }; p.add(dataFieldParse); } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- FlammableUpdate::FlammableUpdate( Thing *thing, const ModuleData* moduleData ) : UpdateModule( thing, moduleData ) { m_status = FS_NORMAL; m_aflameEndFrame = 0; m_burnedEndFrame = 0; m_damageEndFrame = 0; m_audioHandle = NULL; m_flameDamageLimit = getFlammableUpdateModuleData()->m_flameDamageLimitData; m_lastFlameDamageDealt = 0; setWakeFrame(getObject(), UPDATE_SLEEP_FOREVER); } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- FlammableUpdate::~FlammableUpdate( void ) { stopBurningSound(); } //------------------------------------------------------------------------------------------------- /** Damage has been dealt, this is an opportunity to reach to that damage */ //------------------------------------------------------------------------------------------------- void FlammableUpdate::onDamage( DamageInfo *damageInfo ) { if( damageInfo->in.m_damageType == DAMAGE_FLAME || damageInfo->in.m_damageType == DAMAGE_PARTICLE_BEAM ) { UnsignedInt now = TheGameLogic->getFrame(); if( now - getFlammableUpdateModuleData()->m_flameDamageExpirationDelay > m_lastFlameDamageDealt ) { // If it has been a long time since our last flame damage, reset the threshold m_flameDamageLimit = getFlammableUpdateModuleData()->m_flameDamageLimitData; } m_lastFlameDamageDealt = now; Object *me = getObject(); if( !me->getStatusBits().test( OBJECT_STATUS_AFLAME ) && !me->getStatusBits().test( OBJECT_STATUS_BURNED ) ) { // If I'm not on fire, and I haven't burned up, see if I should try to catch fire. m_flameDamageLimit -= damageInfo->out.m_actualDamageDealt; if( m_flameDamageLimit <= 0 ) { tryToIgnite(); } } } } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- UpdateSleepTime FlammableUpdate::update( void ) { Object *me = getObject(); DEBUG_ASSERTCRASH(m_status == FS_AFLAME, ("hmm, should be aflame")); UnsignedInt now = TheGameLogic->getFrame(); const FlammableUpdateModuleData *data = getFlammableUpdateModuleData(); if( m_damageEndFrame != 0 && now >= m_damageEndFrame ) { m_damageEndFrame = now + data->m_aflameDamageDelay; doAflameDamage(); } if( m_burnedEndFrame != 0 && now >= m_burnedEndFrame ) { // So this status is set, but I am still aflame on an independent timer. me->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_BURNED ) ); me->setModelConditionState( MODELCONDITION_SMOLDERING ); } if( m_aflameEndFrame != 0 && now >= m_aflameEndFrame ) { // This is the important one. I am no longer on fire. if( me->getStatusBits().test( OBJECT_STATUS_BURNED ) ) { // If I am burned, then I will never catch fire again. m_status = FS_BURNED; } else { // otherwise I am free to burn again m_status = FS_NORMAL; } stopBurningSound(); me->clearStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_AFLAME ) ); me->getBodyModule()->setAflame( FALSE ); me->clearModelConditionState( MODELCONDITION_AFLAME ); } return calcSleepTime(); } // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ UpdateSleepTime FlammableUpdate::calcSleepTime() { UnsignedInt now = TheGameLogic->getFrame(); if (m_status == FS_AFLAME && m_aflameEndFrame != 0 && m_aflameEndFrame > now) { UnsignedInt soonest = m_aflameEndFrame; if (m_burnedEndFrame != 0 && m_burnedEndFrame < soonest && m_burnedEndFrame > now) soonest = m_burnedEndFrame; if (m_damageEndFrame != 0 && m_damageEndFrame < soonest && m_damageEndFrame > now) soonest = m_damageEndFrame; DEBUG_ASSERTCRASH(soonest - now > 0, ("hmm")); // UPDATE_SLEEP requires a count-of-frames, not an absolute-frame, so subtract 'now' return UPDATE_SLEEP(soonest - now); } else { return UPDATE_SLEEP_FOREVER; } } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- void FlammableUpdate::tryToIgnite() { if( m_status == FS_NORMAL ) { Object *me = getObject(); me->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_AFLAME ) ); me->getBodyModule()->setAflame( TRUE ); me->setModelConditionState( MODELCONDITION_AFLAME ); startBurningSound(); // bleah. this sucks. (srj) static const NameKeyType key_FireSpreadUpdate = NAMEKEY("FireSpreadUpdate"); FireSpreadUpdate* fu = (FireSpreadUpdate*)getObject()->findUpdateModule(key_FireSpreadUpdate); if (fu != NULL) { fu->startFireSpreading(); } m_status = FS_AFLAME; const FlammableUpdateModuleData *data = getFlammableUpdateModuleData(); UnsignedInt now = TheGameLogic->getFrame(); m_aflameEndFrame = now + data->m_aflameDuration; m_burnedEndFrame = data->m_burnedDelay ? now + data->m_burnedDelay : 0; m_damageEndFrame = data->m_aflameDamageDelay ? now + data->m_aflameDamageDelay : 0; setWakeFrame(getObject(), calcSleepTime()); } } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- void FlammableUpdate::doAflameDamage() { const FlammableUpdateModuleData *data = getFlammableUpdateModuleData(); DamageInfo info; info.in.m_amount = data->m_aflameDamageAmount; info.in.m_sourceID = getObject()->getID(); info.in.m_damageType = DAMAGE_FLAME; info.in.m_deathType = DEATH_BURNED; getObject()->attemptDamage( &info ); } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- void FlammableUpdate::startBurningSound() { const FlammableUpdateModuleData *data = getFlammableUpdateModuleData(); AudioEventRTS audio(data->m_burningSoundName, getObject()->getID()); m_audioHandle = TheAudio->addAudioEvent( &audio ); } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- void FlammableUpdate::stopBurningSound() { if (m_audioHandle) { TheAudio->removeAudioEvent( m_audioHandle ); m_audioHandle = NULL; } } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- Bool FlammableUpdate::wouldIgnite() { if( m_status == FS_NORMAL ) return TRUE; return FALSE; } // ------------------------------------------------------------------------------------------------ /** CRC */ // ------------------------------------------------------------------------------------------------ void FlammableUpdate::crc( Xfer *xfer ) { // extend base class UpdateModule::crc( xfer ); } // end crc // ------------------------------------------------------------------------------------------------ /** Xfer method * Version Info: * 1: Initial version */ // ------------------------------------------------------------------------------------------------ void FlammableUpdate::xfer( Xfer *xfer ) { // version XferVersion currentVersion = 1; XferVersion version = currentVersion; xfer->xferVersion( &version, currentVersion ); // extend base class UpdateModule::xfer( xfer ); // flammability status xfer->xferUser( &m_status, sizeof( FlammabilityStatusType ) ); // aflame end frame xfer->xferUnsignedInt( &m_aflameEndFrame ); // burned end frame xfer->xferUnsignedInt( &m_burnedEndFrame ); // damage end frame xfer->xferUnsignedInt( &m_damageEndFrame ); // flame damage limit xfer->xferReal( &m_flameDamageLimit ); // last flame damage dealt xfer->xferUnsignedInt( &m_lastFlameDamageDealt ); } // end xfer // ------------------------------------------------------------------------------------------------ /** Load post process */ // ------------------------------------------------------------------------------------------------ void FlammableUpdate::loadPostProcess( void ) { // extend base class UpdateModule::loadPostProcess(); } // end loadPostProcess