| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418 |
- /*
- ** 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 <http://www.gnu.org/licenses/>.
- */
- ////////////////////////////////////////////////////////////////////////////////
- // //
- // (c) 2003 Electronic Arts Inc. //
- // //
- ////////////////////////////////////////////////////////////////////////////////
- // FILE: CountermeasuresBehavior.cpp //////////////////////////////////////////////////////////////
- // Author: Kris Morness, April 2003
- // Desc: Handles countermeasure firing when under missile threat, and responsible
- // for diverting missiles to the flares.
- ///////////////////////////////////////////////////////////////////////////////////////////////////
- // INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
- #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
- #include "Common/Thing.h"
- #include "Common/ThingTemplate.h"
- #include "Common/INI.h"
- #include "Common/Player.h"
- #include "Common/ThingFactory.h"
- #include "Common/Xfer.h"
- #include "GameClient/ParticleSys.h"
- #include "GameClient/Anim2D.h"
- #include "GameClient/InGameUI.h"
- #include "GameLogic/Module/CountermeasuresBehavior.h"
- #include "GameLogic/Module/BodyModule.h"
- #include "GameLogic/Module/PhysicsUpdate.h"
- #include "GameLogic/GameLogic.h"
- #include "GameLogic/Object.h"
- #include "GameLogic/PartitionManager.h"
- #ifdef _INTERNAL
- // for occasional debugging...
- //#pragma optimize("", off)
- //#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
- #endif
- //-------------------------------------------------------------------------------------------------
- //-------------------------------------------------------------------------------------------------
- struct CountermeasuresPlayerScanHelper
- {
- KindOfMaskType m_kindOfToTest;
- Object *m_theHealer;
- ObjectPointerList *m_objectList;
- };
- static void checkForCountermeasures( Object *testObj, void *userData )
- {
- CountermeasuresPlayerScanHelper *helper = (CountermeasuresPlayerScanHelper*)userData;
- ObjectPointerList *listToAddTo = helper->m_objectList;
- if( testObj->isEffectivelyDead() )
- return;
- if( testObj->getControllingPlayer() != helper->m_theHealer->getControllingPlayer() )
- return;
- if( testObj->isOffMap() )
- return;
- if( !testObj->isAnyKindOf(helper->m_kindOfToTest) )
- return;
- if( testObj->getBodyModule()->getHealth() >= testObj->getBodyModule()->getMaxHealth() )
- return;
- listToAddTo->push_back(testObj);
- }
- //-------------------------------------------------------------------------------------------------
- //-------------------------------------------------------------------------------------------------
- CountermeasuresBehavior::CountermeasuresBehavior( Thing *thing, const ModuleData* moduleData ) : UpdateModule( thing, moduleData )
- {
- const CountermeasuresBehaviorModuleData *data = getCountermeasuresBehaviorModuleData();
- m_availableCountermeasures = data->m_numberOfVolleys * data->m_volleySize;
- m_reactionFrame = 0;
- m_activeCountermeasures = 0;
- m_divertedMissiles = 0;
- m_incomingMissiles = 0;
- m_nextVolleyFrame = 0;
-
- setWakeFrame( getObject(), UPDATE_SLEEP_NONE );
- }
- //-------------------------------------------------------------------------------------------------
- //-------------------------------------------------------------------------------------------------
- CountermeasuresBehavior::~CountermeasuresBehavior( void )
- {
- }
- // ------------------------------------------------------------------------------------------------
- void CountermeasuresBehavior::reportMissileForCountermeasures( Object *missile )
- {
- if( !missile )
- {
- return;
- }
- //Record the number of missiles that have been fired at us
- m_incomingMissiles++;
- if( m_availableCountermeasures + m_activeCountermeasures > 0 )
- {
- //We have countermeasures we can use. Determine now whether or not the incoming missile will
- //be diverted.
- const CountermeasuresBehaviorModuleData *data = getCountermeasuresBehaviorModuleData();
- if( GameLogicRandomValueReal( 0.0f, 1.0f ) < data->m_evasionRate )
- {
- //This missile will be diverted!
- ProjectileUpdateInterface* pui = NULL;
- for( BehaviorModule** u = missile->getBehaviorModules(); *u; ++u )
- {
- if( (pui = (*u)->getProjectileUpdateInterface()) != NULL )
- {
- //Make sure the missile diverts after a delay. The delay needs to be larger than
- //the countermeasure reaction time or else the missile won't have a countermeasure to divert to!
- DEBUG_ASSERTCRASH( data->m_countermeasureReactionFrames < data->m_missileDecoyFrames,
- ("MissileDecoyDelay needs to be less than CountermeasureReactionTime in order to function properly.") );
- pui->setFramesTillCountermeasureDiversionOccurs( data->m_missileDecoyFrames );
- m_divertedMissiles++;
- if( m_activeCountermeasures == 0 && m_reactionFrame == 0 )
- {
- //We need to launch our first volley of countermeasures, but we can't do it now. If we
- //do, it'll look too artificial. Instead, we need to set up a timer to fake a reaction
- //delay.
- m_reactionFrame = TheGameLogic->getFrame() + data->m_countermeasureReactionFrames;
- }
- break;
- }
- }
- }
- }
- }
- //-------------------------------------------------------------------------------------------------
- ObjectID CountermeasuresBehavior::calculateCountermeasureToDivertTo( const Object& victim )
- {
- const CountermeasuresBehaviorModuleData *data = getCountermeasuresBehaviorModuleData();
- //Flares are pushed to the front of the list, but we only want to acquire the "newest" of the flares, therefore
- //stop iterating after we've reached size of a single volley.
- Int iteratorMax = MAX( data->m_volleySize, 1 );
- Real closestDist = 1e15f;
- Object *closestFlare = NULL;
- //Start at the end of the list and go towards the beginning.
- CountermeasuresVec::iterator it = m_counterMeasures.end();
- //end is actually the end so advance the iterator.
- if( it )
- {
- --it;
- while( iteratorMax-- )
- {
- Object *obj = TheGameLogic->findObjectByID( *it );
- if( obj )
- {
- Real dist = ThePartitionManager->getDistanceSquared( obj, getObject(), FROM_CENTER_2D );
- if( dist < closestDist )
- {
- closestDist = dist;
- closestFlare = obj;
- }
- }
- else
- {
- --it;
- }
- }
- }
- if( closestFlare )
- {
- return closestFlare->getID();
- }
- return INVALID_ID;
- }
- //-------------------------------------------------------------------------------------------------
- Bool CountermeasuresBehavior::isActive() const
- {
- return isUpgradeActive();
- }
- //-------------------------------------------------------------------------------------------------
- /** The update callback. */
- //-------------------------------------------------------------------------------------------------
- UpdateSleepTime CountermeasuresBehavior::update( void )
- {
- UnsignedInt now = TheGameLogic->getFrame();
- const CountermeasuresBehaviorModuleData *data = getCountermeasuresBehaviorModuleData();
- Object *obj = getObject();
- if( obj->isEffectivelyDead() )
- {
- return UPDATE_SLEEP_FOREVER;
- }
- if( !isUpgradeActive() )
- {
- return UPDATE_SLEEP_FOREVER;
- }
- //Validate all existing flares, and clean them up as needed.
- for (CountermeasuresVec::iterator it = m_counterMeasures.begin(); it != m_counterMeasures.end(); /*nothing*/ )
- {
- Object *obj = TheGameLogic->findObjectByID( *it );
- if( !obj )
- {
- it = m_counterMeasures.erase( it );
- m_activeCountermeasures--;
- }
- else
- {
- ++it;
- }
- }
-
- if( obj->isAirborneTarget() )
- {
- //Handle flare volley launching (initial reaction, and continuation firing).
- if( m_availableCountermeasures )
- {
- //Deal with the initial volley, but wait until we are permitted to react.
- if( m_reactionFrame )
- {
- if( m_reactionFrame == now )
- {
- //We have been shot at and now that the reaction timer has expired, fire a full volley of
- //countermeasures.
- launchVolley();
- m_nextVolleyFrame = now + data->m_framesBetweenVolleys;
- m_reactionFrame = 0;
- }
- }
- //Handle subsequent volley launching.
- if( m_nextVolleyFrame == now )
- {
- launchVolley();
- m_nextVolleyFrame = now + data->m_framesBetweenVolleys;
- }
- }
- }
- //Handle auto-reloading (data->m_reloadFrames of zero means it's not possible to auto-reload).
- //Aircraft that don't auto-reload require landing at an airfield for resupply.
- if( !m_availableCountermeasures && data->m_reloadFrames )
- {
- if( m_reloadFrame != 0 )
- {
- if( m_reloadFrame <= now )
- {
- //We've successfully reloaded automatically.
- reloadCountermeasures();
- }
- }
- else
- {
- //We just started reloading, so set the frame it'll be ready.
- m_reloadFrame = now + data->m_reloadFrames;
- }
- }
- return UPDATE_SLEEP( UPDATE_SLEEP_NONE );
- }
- //-------------------------------------------------------------------------------------------------
- void CountermeasuresBehavior::reloadCountermeasures()
- {
- const CountermeasuresBehaviorModuleData *data = getCountermeasuresBehaviorModuleData();
- m_availableCountermeasures = data->m_numberOfVolleys * data->m_volleySize;
- m_reloadFrame = 0;
- }
-
- //-------------------------------------------------------------------------------------------------
- void CountermeasuresBehavior::launchVolley()
- {
- const CountermeasuresBehaviorModuleData *data = getCountermeasuresBehaviorModuleData();
- Object *obj = getObject();
- Real volleySize = (Real)data->m_volleySize;
- for( int i = 0; i < data->m_volleySize; i++ )
- {
- //Each flare in a volley will calculate a different vector to fly out. We have a +/- angle to
- //spread out equally. With only one flare, it'll come straight out the back. Two flares will
- //launch at the extreme positive and negative angle. Three flares will launch at extreme angles
- //plus straight back. Four or more will divy it up equally.
- Real currentVolley = (Real)i;
- Real ratio = 0.0f;
- if( volleySize != 1.0f )
- {
- //ratio between -1.0 and +1.0f
- ratio = currentVolley / (volleySize - 1.0f) * 2.0f - 1.0f;
- }
- //Now calculate the angle. Simply multiply it by the ratio!
- Real angle = ratio * data->m_volleyArcAngle;
- Coord3D vel;
- PhysicsBehavior *physics = obj->getPhysics();
- //Calculate the angle to fire the flare by taking the facing angle and rotating it
- //and then scaling it by it's velocity (if it's moving).
- obj->getUnitDirectionVector3D( vel );
- Vector2 flareVector;
- flareVector.X = vel.x;
- flareVector.Y = vel.y;
- flareVector.Normalize();
- flareVector.Rotate( angle );
- //Give it back to the Coord3D
- vel.x = flareVector.X;
- vel.y = flareVector.Y;
- vel.z = 0.0f;
- Real velocity = physics->getVelocityMagnitude();
- if( velocity < 1.0f )
- {
- velocity = -10.0f;
- }
- vel.scale( velocity * data->m_volleyVelocityFactor );
- const ThingTemplate *thing = TheThingFactory->findTemplate( data->m_flareTemplateName );
- if( thing )
- {
- Object *flare = TheThingFactory->newObject( thing, obj->getControllingPlayer()->getDefaultTeam() );
- flare->setPosition( obj->getPosition() );
- flare->setOrientation( obj->getOrientation() );
- physics->transferVelocityTo( flare->getPhysics() );
- flare->getPhysics()->applyMotiveForce( &vel );
- m_activeCountermeasures++;
- m_availableCountermeasures--;
- m_counterMeasures.push_back( flare->getID() );
- }
- }
- }
- //------------------------------------------------------------------------------------------------
- /** CRC */
- //------------------------------------------------------------------------------------------------
- void CountermeasuresBehavior::crc( Xfer *xfer )
- {
- // extend base class
- UpdateModule::crc( xfer );
- // extend base class
- UpgradeMux::upgradeMuxCRC( xfer );
- } // end crc
- //------------------------------------------------------------------------------------------------
- /** Xfer method
- * Version Info:
- * 1: Initial version */
- //------------------------------------------------------------------------------------------------
- void CountermeasuresBehavior::xfer( Xfer *xfer )
- {
- // version
- XferVersion currentVersion = 2;
- XferVersion version = currentVersion;
- xfer->xferVersion( &version, currentVersion );
- // extend base class
- UpdateModule::xfer( xfer );
- // extend base class
- UpgradeMux::upgradeMuxXfer( xfer );
- if( currentVersion >= 2 )
- {
- xfer->xferSTLObjectIDVector( &m_counterMeasures );
- xfer->xferUnsignedInt( &m_availableCountermeasures );
- xfer->xferUnsignedInt( &m_activeCountermeasures );
- xfer->xferUnsignedInt( &m_divertedMissiles );
- xfer->xferUnsignedInt( &m_incomingMissiles );
- xfer->xferUnsignedInt( &m_reactionFrame );
- xfer->xferUnsignedInt( &m_nextVolleyFrame );
- }
- } // end xfer
- //------------------------------------------------------------------------------------------------
- /** Load post process */
- //------------------------------------------------------------------------------------------------
- void CountermeasuresBehavior::loadPostProcess( void )
- {
- // extend base class
- UpdateModule::loadPostProcess();
- // extend base class
- UpgradeMux::upgradeMuxLoadPostProcess();
- } // end loadPostProcess
|