| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926 |
- /*
- ** 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) 2001-2003 Electronic Arts Inc. //
- // //
- ////////////////////////////////////////////////////////////////////////////////
- // FILE: MissileAIUpdate.cpp
- // Author: Michael S. Booth, December 2001
- // Desc: Implementation of missile behavior
- #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
- #include "Common/Thing.h"
- #include "Common/ThingTemplate.h"
- #include "Common/RandomValue.h"
- #include "Common/BitFlagsIO.h"
- #include "GameLogic/AIPathfind.h"
- #include "GameLogic/ExperienceTracker.h"
- #include "GameLogic/GameLogic.h"
- #include "GameLogic/Locomotor.h"
- #include "GameLogic/Module/MissileAIUpdate.h"
- #include "GameLogic/Module/AIUpdate.h"
- #include "GameLogic/Module/ContainModule.h"
- #include "GameLogic/Module/PhysicsUpdate.h"
- #include "GameLogic/Object.h"
- #include "GameLogic/PartitionManager.h"
- #include "GameLogic/TerrainLogic.h"
- #include "GameLogic/Weapon.h"
- #include "GameClient/Drawable.h"
- #include "GameClient/FXList.h"
- #include "GameClient/ParticleSys.h"
- const Real BIGNUM = 99999.0f;
- #ifdef _INTERNAL
- // for occasional debugging...
- //#pragma optimize("", off)
- //#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
- #endif
- //-----------------------------------------------------------------------------
- //-----------------------------------------------------------------------------
- //-----------------------------------------------------------------------------
- //-----------------------------------------------------------------------------
- MissileAIUpdateModuleData::MissileAIUpdateModuleData()
- {
- m_tryToFollowTarget = true;
- m_fuelLifetime = 0;
- m_ignitionDelay = 0;
- m_initialVel = 0;
- m_initialDist = 0.0f;
- m_diveDistance = 0.0f;
- m_ignitionFX = NULL;
- m_useWeaponSpeed = false;
- m_detonateOnNoFuel = FALSE;
- m_garrisonHitKillCount = 0;
- m_garrisonHitKillFX = NULL;
- m_lockDistance = 75.0f;
- m_distanceScatterWhenJammed = 75.0f;
- m_detonateCallsKill = FALSE;
- m_killSelfDelay = 3; // just long enough for the contrail to catch up to me
- }
- //-----------------------------------------------------------------------------
- void MissileAIUpdateModuleData::buildFieldParse(MultiIniFieldParse& p)
- {
- AIUpdateModuleData::buildFieldParse(p);
- static const FieldParse dataFieldParse[] =
- {
- { "TryToFollowTarget", INI::parseBool, NULL, offsetof( MissileAIUpdateModuleData, m_tryToFollowTarget ) },
- { "FuelLifetime", INI::parseDurationUnsignedInt, NULL, offsetof( MissileAIUpdateModuleData, m_fuelLifetime ) },
- { "IgnitionDelay", INI::parseDurationUnsignedInt, NULL, offsetof( MissileAIUpdateModuleData, m_ignitionDelay ) },
- { "InitialVelocity", INI::parseVelocityReal, NULL, offsetof( MissileAIUpdateModuleData, m_initialVel) },
- { "DistanceToTravelBeforeTurning", INI::parseReal, NULL, offsetof( MissileAIUpdateModuleData, m_initialDist ) },
- { "DistanceToTargetBeforeDiving", INI::parseReal, NULL, offsetof( MissileAIUpdateModuleData, m_diveDistance ) },
- { "DistanceToTargetForLock",INI::parseReal, NULL, offsetof( MissileAIUpdateModuleData, m_lockDistance ) },
- { "IgnitionFX", INI::parseFXList, NULL, offsetof( MissileAIUpdateModuleData, m_ignitionFX ) },
- { "UseWeaponSpeed", INI::parseBool, NULL, offsetof( MissileAIUpdateModuleData, m_useWeaponSpeed ) },
- { "DetonateOnNoFuel", INI::parseBool, NULL, offsetof( MissileAIUpdateModuleData, m_detonateOnNoFuel ) },
- { "DistanceScatterWhenJammed",INI::parseReal, NULL, offsetof( MissileAIUpdateModuleData, m_distanceScatterWhenJammed ) },
- { "GarrisonHitKillRequiredKindOf", KindOfMaskType::parseFromINI, NULL, offsetof( MissileAIUpdateModuleData, m_garrisonHitKillKindof ) },
- { "GarrisonHitKillForbiddenKindOf", KindOfMaskType::parseFromINI, NULL, offsetof( MissileAIUpdateModuleData, m_garrisonHitKillKindofNot ) },
- { "GarrisonHitKillCount", INI::parseUnsignedInt, NULL, offsetof( MissileAIUpdateModuleData, m_garrisonHitKillCount ) },
- { "GarrisonHitKillFX", INI::parseFXList, NULL, offsetof( MissileAIUpdateModuleData, m_garrisonHitKillFX ) },
- { "DetonateCallsKill", INI::parseBool, NULL, offsetof( MissileAIUpdateModuleData, m_detonateCallsKill ) },
- { "KillSelfDelay", INI::parseDurationUnsignedInt, NULL, offsetof( MissileAIUpdateModuleData, m_killSelfDelay ) },
- { 0, 0, 0, 0 }
- };
- p.add(dataFieldParse);
- }
- //-------------------------------------------------------------------------------------------------
- //-------------------------------------------------------------------------------------------------
- //-------------------------------------------------------------------------------------------------
- //-------------------------------------------------------------------------------------------------
- MissileAIUpdate::MissileAIUpdate( Thing *thing, const ModuleData* moduleData ) : AIUpdateInterface( thing, moduleData )
- {
- const MissileAIUpdateModuleData* d = getMissileAIUpdateModuleData();
- m_state = PRELAUNCH;
- m_stateTimestamp = TheGameLogic->getFrame();
- m_nextTargetTrackTime = 0x7fffffff; // so that we never recalc target pos, by default
- m_launcherID = INVALID_ID;
- m_victimID = INVALID_ID;
- m_isArmed = false;
- m_fuelExpirationDate = 0;
- m_noTurnDistLeft = d->m_initialDist;
- m_prevPos = *getObject()->getPosition();
- m_maxAccel = BIGNUM;
- m_detonationWeaponTmpl = NULL;
- m_exhaustSysTmpl = NULL;
- m_isTrackingTarget = FALSE;
- m_exhaustID = INVALID_PARTICLE_SYSTEM_ID;
- m_extraBonusFlags = 0;
- m_originalTargetPos.zero();
- m_framesTillDecoyed = 0;
- m_noDamage = FALSE;
- m_isJammed = FALSE;
- }
- //-------------------------------------------------------------------------------------------------
- MissileAIUpdate::~MissileAIUpdate()
- {
- tossExhaust();
- }
- //-------------------------------------------------------------------------------------------------
- void MissileAIUpdate::onDelete( void )
- {
- //
- // there is no need to destroy the attached particle systems here because the particle
- // systems themselves detect that their "parent" object is gone and destroy themselves.
- // but to be clean, for the exhaust particle system, we will invalidate the ID so that
- // we can't do anything else with it since we're being deleted
- //
- m_exhaustID = INVALID_PARTICLE_SYSTEM_ID;
- }
- //-------------------------------------------------------------------------------------------------
- void MissileAIUpdate::tossExhaust()
- {
- if (m_exhaustID != INVALID_PARTICLE_SYSTEM_ID)
- {
- TheParticleSystemManager->destroyParticleSystemByID(m_exhaustID);
- m_exhaustID = INVALID_PARTICLE_SYSTEM_ID;
- }
- }
- //-------------------------------------------------------------------------------------------------
- void MissileAIUpdate::switchToState(MissileStateType s)
- {
- if (m_state != s)
- {
- m_state = s;
- m_stateTimestamp = TheGameLogic->getFrame();
- }
- }
- //-------------------------------------------------------------------------------------------------
- // Prepares the missile for launch via proper weapon-system channels.
- //-------------------------------------------------------------------------------------------------
- void MissileAIUpdate::projectileLaunchAtObjectOrPosition(
- const Object *victim,
- const Coord3D* victimPos,
- const Object *launcher,
- WeaponSlotType wslot,
- Int specificBarrelToUse,
- const WeaponTemplate* detWeap,
- const ParticleSystemTemplate* exhaustSysOverride
- )
- {
- DEBUG_ASSERTCRASH(specificBarrelToUse>=0, ("specificBarrelToUse must now be explicit"));
- m_launcherID = launcher ? launcher->getID() : INVALID_ID;
- m_detonationWeaponTmpl = detWeap;
- m_extraBonusFlags = launcher ? launcher->getWeaponBonusCondition() : 0;
- Weapon::positionProjectileForLaunch(getObject(), launcher, wslot, specificBarrelToUse);
- projectileFireAtObjectOrPosition( victim, victimPos, detWeap, exhaustSysOverride );
- }
- #define APPROACH_HEIGHT 10.0f
- //-------------------------------------------------------------------------------------------------
- // The actual firing of the missile once setup.
- //-------------------------------------------------------------------------------------------------
- void MissileAIUpdate::projectileFireAtObjectOrPosition( const Object *victim, const Coord3D *victimPos, const WeaponTemplate *detWeap, const ParticleSystemTemplate* exhaustSysOverride )
- {
- const MissileAIUpdateModuleData* d = getMissileAIUpdateModuleData();
- Object *obj = getObject();
- m_exhaustSysTmpl = exhaustSysOverride;
- m_detonationWeaponTmpl = detWeap;
- Real initialVelToUse = d->m_initialVel;
- if (d->m_useWeaponSpeed)
- {
- Real weaponSpeed = detWeap->getWeaponSpeed();
- initialVelToUse = weaponSpeed;
- Locomotor* curLoco = getCurLocomotor();
- if (curLoco)
- {
- m_maxAccel = weaponSpeed;
- curLoco->setMaxSpeed(weaponSpeed);
- curLoco->setMaxAcceleration(m_maxAccel);
- }
- }
- Real deltaZ = victimPos->z - obj->getPosition()->z;
- Real dx = victimPos->x - obj->getPosition()->x;
- Real dy = victimPos->y - obj->getPosition()->y;
- Real xyDist = sqrt(sqr(dx)+sqr(dy));
- if (xyDist<1) xyDist = 1;
- Real zFactor = 0;
- if (deltaZ>0) {
- zFactor = deltaZ/xyDist;
- }
- Vector3 dir = getObject()->getTransformMatrix()->Get_X_Vector();
- dir.Normalize();
- dir.Z += 2*zFactor;
- dir.Normalize();
- PhysicsBehavior* physics = getObject()->getPhysics();
- if (physics && initialVelToUse > 0)
- {
- Real forceMag = physics->getMass() * initialVelToUse;
- Coord3D force;
- force.x = forceMag * dir.X;
- force.y = forceMag * dir.Y;
- force.z = forceMag * dir.Z;
- physics->applyMotiveForce( &force );
- }
- Vector3 objPos(obj->getPosition()->x, obj->getPosition()->y, obj->getPosition()->z);
- Matrix3D newXform;
- newXform.buildTransformMatrix( objPos, dir );
- obj->setTransformMatrix( &newXform );
- switchToState(LAUNCH);
- m_isTrackingTarget = false;
- // Missiles do their thing by colliding with something and exploding. So they Move to the target
- // instead of Attacking the target.
- if (victim && d->m_tryToFollowTarget)
- {
- getStateMachine()->setGoalPosition(victim->getPosition());
- // ick. const-cast is evil. fix. (srj)
- aiMoveToObject(const_cast<Object*>(victim), CMD_FROM_AI );
- m_originalTargetPos = *victim->getPosition();
- m_isTrackingTarget = TRUE;// Remember that I was originally shot at a moving object, so if the
- // target dies I can do something cool.
- m_victimID = victim->getID();
- }
- else
- {
- // Otherwise, we are just a Coord shot.
- Coord3D initialPos = *victimPos;
- m_originalTargetPos = *victimPos;
- if (d->m_lockDistance>0.0f) {
- initialPos.z += APPROACH_HEIGHT;
- }
- aiMoveToPosition(&initialPos, CMD_FROM_AI );
- m_victimID = INVALID_ID;
- }
- setCurrentVictim( victim );/// extending access to the victim via the parent class
- m_prevPos = *getObject()->getPosition();
- }
- //-------------------------------------------------------------------------------------------------
- //-------------------------------------------------------------------------------------------------
- Bool MissileAIUpdate::projectileHandleCollision( Object *other )
- {
- Object* obj = getObject();
- const MissileAIUpdateModuleData* d = getMissileAIUpdateModuleData();
- // check if our warhead is "armed" - if not, we are inert
- if (projectileIsArmed() == false)
- return true;
- if (other==NULL) {
- // we hit the ground. Check to see if we hit something unexpected.
- Coord3D goal = *getGoalPosition();
- Coord3D pos = *obj->getPosition();
- Coord3D delta;
- delta.x = pos.x-goal.x;
- delta.y = pos.y-goal.y;
- delta.z = pos.z-goal.z;
- if (delta.z > PATHFIND_CELL_SIZE_F) {
- // we're above our target goal.
- if (delta.length() > 3*PATHFIND_CELL_SIZE_F) {
- // we're somewhere else.
- return true;
- }
- }
- }
- if (other != NULL)
- {
- Object *projectileLauncher = TheGameLogic->findObjectByID( projectileGetLauncherID() );
-
- // if it's not the specific thing we were targeting, see if we should incidentally collide...
- if (!m_detonationWeaponTmpl->shouldProjectileCollideWith(projectileLauncher, obj, other, m_victimID))
- {
- //DEBUG_LOG(("ignoring projectile collision with %s at frame %d\n",other->getTemplate()->getName().str(),TheGameLogic->getFrame()));
- return true;
- }
- if (d->m_garrisonHitKillCount > 0)
- {
- ContainModuleInterface* contain = other->getContain();
- if( contain && contain->getContainCount() > 0 && contain->isGarrisonable() && !contain->isImmuneToClearBuildingAttacks() )
- {
- Int numKilled = 0;
- // garrisonable buildings subvert the normal process here.
- const ContainedItemsList* items = contain->getContainedItemsList();
- if (items)
- {
- for (ContainedItemsList::const_iterator it = items->begin(); *it != NULL && numKilled < d->m_garrisonHitKillCount; )
- {
- Object* thingToKill = *it++;
- if (!thingToKill->isEffectivelyDead() && thingToKill->isKindOfMulti(d->m_garrisonHitKillKindof, d->m_garrisonHitKillKindofNot))
- {
- //DEBUG_LOG(("Killed a garrisoned unit (%08lx %s) via Flash-Bang!\n",thingToKill,thingToKill->getTemplate()->getName().str()));
- if (projectileLauncher)
- projectileLauncher->scoreTheKill( thingToKill );
- thingToKill->kill();
- ++numKilled;
- }
- } // next contained item
- } // if items
-
- if (numKilled > 0)
- {
- // note, fx is played at center of building, not at grenade's location
- FXList::doFXObj(d->m_garrisonHitKillFX, other, NULL);
- // don't do the normal explosion; just destroy ourselves & return
- TheGameLogic->destroyObject(obj);
-
- return true;
- }
- } // if a garrisonable thing
- }
- }
- // collided with something... blow'd up!
- detonate();
- // mark ourself as "no collisions" (since we might still exist in slow death mode)
- obj->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_NO_COLLISIONS ) );
- return true;
- }
- //-------------------------------------------------------------------------------------------------
- void MissileAIUpdate::detonate()
- {
- Object* obj = getObject();
- if (m_detonationWeaponTmpl)
- {
-
- TheWeaponStore->handleProjectileDetonation(m_detonationWeaponTmpl, obj, obj->getPosition(), m_extraBonusFlags, !m_noDamage );
-
- if( m_detonationWeaponTmpl->getDieOnDetonate() )
- {
- DamageInfo damageInfo;
- damageInfo.in.m_damageType = DAMAGE_UNRESISTABLE;
- damageInfo.in.m_deathType = DEATH_DETONATED;
- damageInfo.in.m_sourceID = INVALID_ID;
- damageInfo.in.m_amount = obj->getBodyModule()->getMaxHealth();
- obj->attemptDamage( &damageInfo );
- }
- }
- else if( !m_noDamage )
- {
- DamageInfo damageInfo;
- damageInfo.in.m_damageType = DAMAGE_UNRESISTABLE;
- damageInfo.in.m_deathType = DEATH_DETONATED;
- damageInfo.in.m_sourceID = INVALID_ID;
- damageInfo.in.m_amount = obj->getBodyModule()->getMaxHealth();
- obj->attemptDamage( &damageInfo );
- }
- if (obj->getDrawable())
- obj->getDrawable()->setDrawableHidden(true);
- // Delay destroying the object two frames to let the contrail catch up. jba.
- switchToState(KILL_SELF);
- obj->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_MISSILE_KILLING_SELF ) );
- }
- //-------------------------------------------------------------------------------------------------
- void MissileAIUpdate::doPrelaunchState()
- {
- Locomotor* curLoco = getCurLocomotor();
- if (curLoco)
- {
- curLoco->setMaxAcceleration(0);
- curLoco->setMaxTurnRate(0);
- }
- }
- //-------------------------------------------------------------------------------------------------
- void MissileAIUpdate::doKillSelfState()
- {
- const MissileAIUpdateModuleData *modData = getMissileAIUpdateModuleData();
- if (m_stateTimestamp > TheGameLogic->getFrame() - modData->m_killSelfDelay )
- {
- // Hold in this state [modData->m_killSelfDelay] frames to let the contrail catch up. jba.
- return;
- }
- Object* obj = getObject();
- if (m_detonationWeaponTmpl)
- {
- if ( modData->m_detonateCallsKill )
- obj->kill(); // kill it (vs destroying it) so that its Die modules are called
- else
- TheGameLogic->destroyObject( obj );
- }
- switchToState(DEAD);
- }
- //-------------------------------------------------------------------------------------------------
- void MissileAIUpdate::doLaunchState()
- {
- Locomotor* curLoco = getCurLocomotor();
- if (curLoco)
- {
- curLoco->setMaxAcceleration(0);
- curLoco->setMaxTurnRate(0);
- }
- UnsignedInt delay = getMissileAIUpdateModuleData()->m_ignitionDelay;
- if (TheGameLogic->getFrame() - m_stateTimestamp >= delay)
- {
- switchToState(IGNITION);
- }
- }
- //-------------------------------------------------------------------------------------------------
- void MissileAIUpdate::doIgnitionState()
- {
- const MissileAIUpdateModuleData* d = getMissileAIUpdateModuleData();
- Locomotor* curLoco = getCurLocomotor();
- if (curLoco)
- {
- curLoco->setMaxAcceleration(m_maxAccel);
- curLoco->setMaxTurnRate(0);
- }
- FXList::doFXObj(d->m_ignitionFX, getObject());
- if (m_exhaustSysTmpl != NULL)
- {
- m_exhaustID = TheParticleSystemManager->createAttachedParticleSystemID(m_exhaustSysTmpl, getObject());
- }
- // arm the missile's "warhead"
- m_isArmed = true;
- UnsignedInt now = TheGameLogic->getFrame();
- m_fuelExpirationDate = d->m_fuelLifetime ? (now + d->m_fuelLifetime) : 0x7fffffff;
- switchToState(ATTACK_NOTURN);
- }
- //-------------------------------------------------------------------------------------------------
- void MissileAIUpdate::doAttackState(Bool turnOK)
- {
- Locomotor* curLoco = getCurLocomotor();
- const MissileAIUpdateModuleData* d = getMissileAIUpdateModuleData();
- if (TheGameLogic->getFrame() >= m_fuelExpirationDate)
- {
- if( d->m_detonateOnNoFuel )
- {
- detonate();
- return;
- }
- if (curLoco)
- {
- curLoco->setMaxAcceleration(0);
- curLoco->setMaxTurnRate(0);
- }
- tossExhaust();
- }
- else
- {
- if (curLoco)
- {
- curLoco->setMaxAcceleration(m_maxAccel);
- curLoco->setMaxTurnRate(turnOK ? BIGNUM : 0);
- }
- }
- if (d->m_lockDistance > 0)
- {
- Real lockDistanceSquared = d->m_lockDistance;
- Real distanceToTargetSquared;
- if (m_isTrackingTarget && (getGoalObject() != NULL)) {
- distanceToTargetSquared = ThePartitionManager->getDistanceSquared( getObject(), getGoalObject(), FROM_CENTER_2D);
- } else {
- distanceToTargetSquared = ThePartitionManager->getDistanceSquared( getObject(), getGoalPosition(), FROM_CENTER_2D );
- }
- if (lockDistanceSquared>0) {
- if (!m_isTrackingTarget) {
- // Immobile or ground target. Halve the lock distance.
- lockDistanceSquared *= 0.5f;
- }
- lockDistanceSquared *= lockDistanceSquared;
- if (distanceToTargetSquared < lockDistanceSquared) {
- if (!m_isTrackingTarget) {
- // Ground pos. Change to original goal.
- aiMoveToPosition(&m_originalTargetPos, CMD_FROM_AI );
- }
- switchToState(KILL);
- return;
- }
- }
- }
- if(curLoco && curLoco->getPreferredHeight() > 0)
- {
- // Am I close enough to the target to ignore my preferred height setting?
- Real distanceToTargetSquared = ThePartitionManager->getDistanceSquared( getObject(), getGoalPosition(), FROM_CENTER_2D );
- Real diveDistanceSquared = d->m_diveDistance;
- if (curLoco && curLoco->getPreferredHeight()) {
- diveDistanceSquared *= diveDistanceSquared;
- if( distanceToTargetSquared < diveDistanceSquared )
- curLoco->setUsePreciseZPos( true );
- }
- }
- if (m_noTurnDistLeft <= 0.0f)
- {
- switchToState(ATTACK);
- }
- // If I was fired at a flyer and have lost target (most likely they died), then I need to do something better
- // than cloverleaf around their last spot.
- if( m_isTrackingTarget && (getGoalObject() == NULL) )
- airborneTargetGone();
- }
- //-------------------------------------------------------------------------------------------------
- void MissileAIUpdate::doKillState(void)
- {
- if (TheGameLogic->getFrame() >= m_fuelExpirationDate)
- {
- if( getMissileAIUpdateModuleData()->m_detonateOnNoFuel )
- {
- detonate();
- return;
- }
- // srj sez: killstate missiles should not be allowed to live forever.
- airborneTargetGone();
- return;
- }
- /* Kill state uses the OBJECT_STATUS_BRAKING, which is a flag to the locomotor to
- "Cheat" and move directly towards the target. This ensures that the missile will hit.
- jba. */
- Locomotor* curLoco = getCurLocomotor();
- Object *obj = getObject();
- // Objects that are braking don't follow the normal physics, so they end up at their destination exactly.
- obj->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_BRAKING ) );
- if (curLoco)
- {
- curLoco->setMaxAcceleration(m_maxAccel);
- curLoco->setMaxTurnRate(BIGNUM);
- }
- if (isIdle()) {
- // we finished the move
- if (getGoalObject()!=NULL) {
- Locomotor* curLoco = getCurLocomotor();
- Real closeEnough = 1.0f;
- if (curLoco)
- {
- closeEnough = curLoco->getMaxSpeedForCondition(BODY_PRISTINE);
- }
- Real distanceToTargetSq = ThePartitionManager->getDistanceSquared( getObject(), getGoalObject(), FROM_BOUNDINGSPHERE_3D);
- //DEBUG_LOG(("Distance to target %f, closeEnough %f\n", sqrt(distanceToTargetSq), closeEnough));
- if (distanceToTargetSq < closeEnough*closeEnough) {
- Coord3D pos = *getGoalObject()->getPosition();
- getObject()->setPosition(&pos);
- detonate();
- } else{
- aiMoveToObject(getGoalObject(), CMD_FROM_AI );
- }
- } else {
- detonate();
- }
- }
- // If I was fired at a flyer and have lost target (most likely they died), then I need to do something better
- // than cloverleaf around their last spot.
- if( m_isTrackingTarget && (getGoalObject() == NULL) )
- airborneTargetGone();
- }
- //-------------------------------------------------------------------------------------------------
- void MissileAIUpdate::doDeadState()
- {
- Locomotor* curLoco = getCurLocomotor();
- if (curLoco)
- {
- curLoco->setMaxAcceleration(0);
- curLoco->setMaxTurnRate(0);
- }
- }
- //-------------------------------------------------------------------------------------------------
- /**
- * Simulate one frame of a missile's behavior
- */
- UpdateSleepTime MissileAIUpdate::update()
- {
- Coord3D newPos = *getObject()->getPosition();
- if (m_noTurnDistLeft > 0.0f && m_state >= IGNITION)
- {
- Real distThisTurn = sqrtf(sqr(newPos.x-m_prevPos.x) + sqr(newPos.y-m_prevPos.y) + sqr(newPos.z-m_prevPos.z));
- m_noTurnDistLeft -= distThisTurn;
- m_prevPos = newPos;
- }
- //If this missile has been marked to divert to countermeasures, check when
- //that will occur, then do it when the timer expires.
- if( m_framesTillDecoyed && m_framesTillDecoyed <= TheGameLogic->getFrame() )
- {
- Object *missile = getObject();
- m_framesTillDecoyed = 0;
- m_noDamage = TRUE;
- Object *victim = TheGameLogic->findObjectByID( m_victimID );
- if( victim )
- {
- ObjectID targetID = missile->calculateCountermeasureToDivertTo( *victim );
- if( targetID != INVALID_ID )
- {
- victim = TheGameLogic->findObjectByID( targetID );
- getStateMachine()->setGoalPosition(victim->getPosition());
- // ick. const-cast is evil. fix. (srj)
- aiMoveToObject(const_cast<Object*>(victim), CMD_FROM_AI );
- m_originalTargetPos = *victim->getPosition();
- m_isTrackingTarget = TRUE;// Remember that I was originally shot at a moving object, so if the
- // target dies I can do something cool.
- m_victimID = victim->getID();
- }
- }
- }
- if (newPos.z < 0)
- {
- // we ended up under the world. go away.
- TheGameLogic->destroyObject(getObject());
- return UPDATE_SLEEP_FOREVER;
- }
- switch( m_state )
- {
- case PRELAUNCH:
- doPrelaunchState();
- break;
- case LAUNCH:
- doLaunchState();
- // special case!
- if (m_state == IGNITION)
- {
- // just fall thru
- }
- else
- {
- break;
- }
- case IGNITION:
- doIgnitionState();
- break;
- case ATTACK_NOTURN:
- doAttackState(false);
- break;
- case ATTACK:
- doAttackState(true);
- break;
- case KILL:
- doKillState();
- break;
- case KILL_SELF:
- doKillSelfState();
- break;
- case DEAD:
- doDeadState();
- break;
- }
- /*UpdateSleepTime ret =*/ AIUpdateInterface::update();
- #if 1
- // srj sez: doh, why was this never in place? I guess 'cuz so few "smart" missiles ever go in
- // a downward-towards-ground attitude. however, comanche rocket pod missiles do, so this fix
- // is needed for them to target bridges correctly.
- // note that we want to use getHighestLayerForDestination() here, so that anything even slightly
- // below the bridge translates into GROUND. (getLayerForDestination just does a "closest" check)
- PathfindLayerEnum oldLayer = getObject()->getLayer();
- PathfindLayerEnum newLayer = TheTerrainLogic->getHighestLayerForDestination(getObject()->getPosition());
- getObject()->setLayer(newLayer);
- if (projectileIsArmed() && oldLayer != LAYER_GROUND && newLayer == LAYER_GROUND)
- {
- // see if we' still in the bridge's xy area
- Coord3D tmp = *getObject()->getPosition();
- tmp.z = 9999.0f;
- PathfindLayerEnum testLayer = TheTerrainLogic->getHighestLayerForDestination(&tmp);
- if (testLayer == oldLayer)
- {
- // ensure we are slightly above the bridge, to account for fudge & sloppy art
- const Real FUDGE = 2.0f;
- tmp.z = TheTerrainLogic->getLayerHeight(tmp.x, tmp.y, testLayer) + FUDGE;
- getObject()->setPosition(&tmp);
- // blow'd up!
- detonate();
- return UPDATE_SLEEP_NONE;
- }
- }
- #endif
- //return (mine < ret) ? mine : ret;
- /// @todo srj -- someday, make sleepy. for now, must not sleep.
- return UPDATE_SLEEP_NONE;
- }
- //-------------------------------------------------------------------------------------------------
- Bool MissileAIUpdate::processCollision(PhysicsBehavior *physics, Object *other)
- /* Returns true if the physics collide should apply the force. Normally not.
- Also determines whether objects are blocked, and if so, if they are stuck. jba.*/
- {
- // Missiles dont' get blocked by other objects.
- return false;
- }
- //-------------------------------------------------------------------------------------------------
- void MissileAIUpdate::airborneTargetGone()
- {
- // I would really love it if this could retarget, but all of the targeting and legality is done
- // by the Weapon that fired me. The safest thing for me to do in this state is to just run out of gas.
- m_fuelExpirationDate = TheGameLogic->getFrame();
- switchToState(KILL_SELF);
- }
- //-------------------------------------------------------------------------------------------------
- // Set number of frames till missile diverts to countermeasures.
- //-------------------------------------------------------------------------------------------------
- void MissileAIUpdate::setFramesTillCountermeasureDiversionOccurs( UnsignedInt frames )
- {
- UnsignedInt now = TheGameLogic->getFrame();
- m_framesTillDecoyed = now + frames;
- }
- //-------------------------------------------------------------------------------------------------
- void MissileAIUpdate::projectileNowJammed()
- {
- if( m_isJammed )
- return; // Already jammed
- const MissileAIUpdateModuleData *data = getMissileAIUpdateModuleData();
- getObject()->setModelConditionState(MODELCONDITION_JAMMED);
- Coord3D targetPosition;
- if( m_isTrackingTarget && getGoalObject() )
- targetPosition = *getGoalObject()->getPosition();
- else
- targetPosition = *getGoalPosition();
- Real scatter = data->m_distanceScatterWhenJammed;
- targetPosition.x += GameLogicRandomValue(-scatter, scatter);
- targetPosition.y += GameLogicRandomValue(-scatter, scatter);
- targetPosition.z = TheTerrainLogic->getLayerHeight( targetPosition.x,
- targetPosition.y,
- TheTerrainLogic->getHighestLayerForDestination(&targetPosition) );
- getStateMachine()->setGoalObject(NULL);
- // Projectiles are expressly forbidden from getting AIIdle. Who am I to argue.
- // I need to do something though, because I can no longer give a new move command
- // while moving because of the big state machine crash fix.
- //
- aiMoveToPosition( &targetPosition, CMD_FROM_AI );
- m_isTrackingTarget = FALSE;
- m_originalTargetPos = targetPosition;
- m_victimID = INVALID_ID;
- }
- // ------------------------------------------------------------------------------------------------
- /** CRC */
- // ------------------------------------------------------------------------------------------------
- void MissileAIUpdate::crc( Xfer *xfer )
- {
- // extend base class
- AIUpdateInterface::crc(xfer);
- } // end crc
- // ------------------------------------------------------------------------------------------------
- /** Xfer method
- * Version Info:
- * 1: Initial version */
- // ------------------------------------------------------------------------------------------------
- void MissileAIUpdate::xfer( Xfer *xfer )
- {
- // version
- const XferVersion currentVersion = 6;
- XferVersion version = currentVersion;
- xfer->xferVersion( &version, currentVersion );
-
- // extend base class
- AIUpdateInterface::xfer(xfer);
- if (version>=2) {
- xfer->xferCoord3D(&m_originalTargetPos);
- }
- xfer->xferUser(&m_state, sizeof(m_state));
- xfer->xferUnsignedInt(&m_stateTimestamp);
- xfer->xferUnsignedInt(&m_nextTargetTrackTime);
- xfer->xferObjectID(&m_launcherID);
- xfer->xferObjectID(&m_victimID);
- xfer->xferBool(&m_isArmed);
- xfer->xferUnsignedInt(&m_fuelExpirationDate);
- xfer->xferReal(&m_noTurnDistLeft);
- xfer->xferReal(&m_maxAccel);
- AsciiString weaponName;
- if (m_detonationWeaponTmpl)
- {
- weaponName = m_detonationWeaponTmpl->getName();
- }
- xfer->xferAsciiString(&weaponName);
- if (weaponName.isNotEmpty() && m_detonationWeaponTmpl == NULL)
- {
- m_detonationWeaponTmpl = TheWeaponStore->findWeaponTemplate(weaponName);
- }
- AsciiString exhaustName;
- if (m_exhaustSysTmpl)
- {
- exhaustName = m_exhaustSysTmpl->getName();
- }
- xfer->xferAsciiString(&exhaustName);
- if (exhaustName.isNotEmpty() && m_exhaustSysTmpl == NULL)
- {
- m_exhaustSysTmpl = TheParticleSystemManager->findTemplate(exhaustName);
- }
- xfer->xferBool(&m_isTrackingTarget);
- if (version >= 3)
- {
- xfer->xferCoord3D(&m_prevPos);
- }
- if (version >= 4)
- {
- xfer->xferUnsignedInt(&m_extraBonusFlags);
- xfer->xferUser( &m_exhaustID, sizeof( m_exhaustID ) );
- }
- if( version >= 5 )
- {
- xfer->xferUnsignedInt( &m_framesTillDecoyed );
- xfer->xferBool( &m_noDamage );
- }
- if( version>= 6 )
- xfer->xferBool( &m_isJammed );
- } // end xfer
- // ------------------------------------------------------------------------------------------------
- /** Load post process */
- // ------------------------------------------------------------------------------------------------
- void MissileAIUpdate::loadPostProcess( void )
- {
- // extend base class
- AIUpdateInterface::loadPostProcess();
- } // end loadPostProcess
|