MissileAIUpdate.cpp 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926
  1. /*
  2. ** Command & Conquer Generals Zero Hour(tm)
  3. ** Copyright 2025 Electronic Arts Inc.
  4. **
  5. ** This program is free software: you can redistribute it and/or modify
  6. ** it under the terms of the GNU General Public License as published by
  7. ** the Free Software Foundation, either version 3 of the License, or
  8. ** (at your option) any later version.
  9. **
  10. ** This program is distributed in the hope that it will be useful,
  11. ** but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. ** GNU General Public License for more details.
  14. **
  15. ** You should have received a copy of the GNU General Public License
  16. ** along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. */
  18. ////////////////////////////////////////////////////////////////////////////////
  19. // //
  20. // (c) 2001-2003 Electronic Arts Inc. //
  21. // //
  22. ////////////////////////////////////////////////////////////////////////////////
  23. // FILE: MissileAIUpdate.cpp
  24. // Author: Michael S. Booth, December 2001
  25. // Desc: Implementation of missile behavior
  26. #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
  27. #include "Common/Thing.h"
  28. #include "Common/ThingTemplate.h"
  29. #include "Common/RandomValue.h"
  30. #include "Common/BitFlagsIO.h"
  31. #include "GameLogic/AIPathfind.h"
  32. #include "GameLogic/ExperienceTracker.h"
  33. #include "GameLogic/GameLogic.h"
  34. #include "GameLogic/Locomotor.h"
  35. #include "GameLogic/Module/MissileAIUpdate.h"
  36. #include "GameLogic/Module/AIUpdate.h"
  37. #include "GameLogic/Module/ContainModule.h"
  38. #include "GameLogic/Module/PhysicsUpdate.h"
  39. #include "GameLogic/Object.h"
  40. #include "GameLogic/PartitionManager.h"
  41. #include "GameLogic/TerrainLogic.h"
  42. #include "GameLogic/Weapon.h"
  43. #include "GameClient/Drawable.h"
  44. #include "GameClient/FXList.h"
  45. #include "GameClient/ParticleSys.h"
  46. const Real BIGNUM = 99999.0f;
  47. #ifdef _INTERNAL
  48. // for occasional debugging...
  49. //#pragma optimize("", off)
  50. //#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
  51. #endif
  52. //-----------------------------------------------------------------------------
  53. //-----------------------------------------------------------------------------
  54. //-----------------------------------------------------------------------------
  55. //-----------------------------------------------------------------------------
  56. MissileAIUpdateModuleData::MissileAIUpdateModuleData()
  57. {
  58. m_tryToFollowTarget = true;
  59. m_fuelLifetime = 0;
  60. m_ignitionDelay = 0;
  61. m_initialVel = 0;
  62. m_initialDist = 0.0f;
  63. m_diveDistance = 0.0f;
  64. m_ignitionFX = NULL;
  65. m_useWeaponSpeed = false;
  66. m_detonateOnNoFuel = FALSE;
  67. m_garrisonHitKillCount = 0;
  68. m_garrisonHitKillFX = NULL;
  69. m_lockDistance = 75.0f;
  70. m_distanceScatterWhenJammed = 75.0f;
  71. m_detonateCallsKill = FALSE;
  72. m_killSelfDelay = 3; // just long enough for the contrail to catch up to me
  73. }
  74. //-----------------------------------------------------------------------------
  75. void MissileAIUpdateModuleData::buildFieldParse(MultiIniFieldParse& p)
  76. {
  77. AIUpdateModuleData::buildFieldParse(p);
  78. static const FieldParse dataFieldParse[] =
  79. {
  80. { "TryToFollowTarget", INI::parseBool, NULL, offsetof( MissileAIUpdateModuleData, m_tryToFollowTarget ) },
  81. { "FuelLifetime", INI::parseDurationUnsignedInt, NULL, offsetof( MissileAIUpdateModuleData, m_fuelLifetime ) },
  82. { "IgnitionDelay", INI::parseDurationUnsignedInt, NULL, offsetof( MissileAIUpdateModuleData, m_ignitionDelay ) },
  83. { "InitialVelocity", INI::parseVelocityReal, NULL, offsetof( MissileAIUpdateModuleData, m_initialVel) },
  84. { "DistanceToTravelBeforeTurning", INI::parseReal, NULL, offsetof( MissileAIUpdateModuleData, m_initialDist ) },
  85. { "DistanceToTargetBeforeDiving", INI::parseReal, NULL, offsetof( MissileAIUpdateModuleData, m_diveDistance ) },
  86. { "DistanceToTargetForLock",INI::parseReal, NULL, offsetof( MissileAIUpdateModuleData, m_lockDistance ) },
  87. { "IgnitionFX", INI::parseFXList, NULL, offsetof( MissileAIUpdateModuleData, m_ignitionFX ) },
  88. { "UseWeaponSpeed", INI::parseBool, NULL, offsetof( MissileAIUpdateModuleData, m_useWeaponSpeed ) },
  89. { "DetonateOnNoFuel", INI::parseBool, NULL, offsetof( MissileAIUpdateModuleData, m_detonateOnNoFuel ) },
  90. { "DistanceScatterWhenJammed",INI::parseReal, NULL, offsetof( MissileAIUpdateModuleData, m_distanceScatterWhenJammed ) },
  91. { "GarrisonHitKillRequiredKindOf", KindOfMaskType::parseFromINI, NULL, offsetof( MissileAIUpdateModuleData, m_garrisonHitKillKindof ) },
  92. { "GarrisonHitKillForbiddenKindOf", KindOfMaskType::parseFromINI, NULL, offsetof( MissileAIUpdateModuleData, m_garrisonHitKillKindofNot ) },
  93. { "GarrisonHitKillCount", INI::parseUnsignedInt, NULL, offsetof( MissileAIUpdateModuleData, m_garrisonHitKillCount ) },
  94. { "GarrisonHitKillFX", INI::parseFXList, NULL, offsetof( MissileAIUpdateModuleData, m_garrisonHitKillFX ) },
  95. { "DetonateCallsKill", INI::parseBool, NULL, offsetof( MissileAIUpdateModuleData, m_detonateCallsKill ) },
  96. { "KillSelfDelay", INI::parseDurationUnsignedInt, NULL, offsetof( MissileAIUpdateModuleData, m_killSelfDelay ) },
  97. { 0, 0, 0, 0 }
  98. };
  99. p.add(dataFieldParse);
  100. }
  101. //-------------------------------------------------------------------------------------------------
  102. //-------------------------------------------------------------------------------------------------
  103. //-------------------------------------------------------------------------------------------------
  104. //-------------------------------------------------------------------------------------------------
  105. MissileAIUpdate::MissileAIUpdate( Thing *thing, const ModuleData* moduleData ) : AIUpdateInterface( thing, moduleData )
  106. {
  107. const MissileAIUpdateModuleData* d = getMissileAIUpdateModuleData();
  108. m_state = PRELAUNCH;
  109. m_stateTimestamp = TheGameLogic->getFrame();
  110. m_nextTargetTrackTime = 0x7fffffff; // so that we never recalc target pos, by default
  111. m_launcherID = INVALID_ID;
  112. m_victimID = INVALID_ID;
  113. m_isArmed = false;
  114. m_fuelExpirationDate = 0;
  115. m_noTurnDistLeft = d->m_initialDist;
  116. m_prevPos = *getObject()->getPosition();
  117. m_maxAccel = BIGNUM;
  118. m_detonationWeaponTmpl = NULL;
  119. m_exhaustSysTmpl = NULL;
  120. m_isTrackingTarget = FALSE;
  121. m_exhaustID = INVALID_PARTICLE_SYSTEM_ID;
  122. m_extraBonusFlags = 0;
  123. m_originalTargetPos.zero();
  124. m_framesTillDecoyed = 0;
  125. m_noDamage = FALSE;
  126. m_isJammed = FALSE;
  127. }
  128. //-------------------------------------------------------------------------------------------------
  129. MissileAIUpdate::~MissileAIUpdate()
  130. {
  131. tossExhaust();
  132. }
  133. //-------------------------------------------------------------------------------------------------
  134. void MissileAIUpdate::onDelete( void )
  135. {
  136. //
  137. // there is no need to destroy the attached particle systems here because the particle
  138. // systems themselves detect that their "parent" object is gone and destroy themselves.
  139. // but to be clean, for the exhaust particle system, we will invalidate the ID so that
  140. // we can't do anything else with it since we're being deleted
  141. //
  142. m_exhaustID = INVALID_PARTICLE_SYSTEM_ID;
  143. }
  144. //-------------------------------------------------------------------------------------------------
  145. void MissileAIUpdate::tossExhaust()
  146. {
  147. if (m_exhaustID != INVALID_PARTICLE_SYSTEM_ID)
  148. {
  149. TheParticleSystemManager->destroyParticleSystemByID(m_exhaustID);
  150. m_exhaustID = INVALID_PARTICLE_SYSTEM_ID;
  151. }
  152. }
  153. //-------------------------------------------------------------------------------------------------
  154. void MissileAIUpdate::switchToState(MissileStateType s)
  155. {
  156. if (m_state != s)
  157. {
  158. m_state = s;
  159. m_stateTimestamp = TheGameLogic->getFrame();
  160. }
  161. }
  162. //-------------------------------------------------------------------------------------------------
  163. // Prepares the missile for launch via proper weapon-system channels.
  164. //-------------------------------------------------------------------------------------------------
  165. void MissileAIUpdate::projectileLaunchAtObjectOrPosition(
  166. const Object *victim,
  167. const Coord3D* victimPos,
  168. const Object *launcher,
  169. WeaponSlotType wslot,
  170. Int specificBarrelToUse,
  171. const WeaponTemplate* detWeap,
  172. const ParticleSystemTemplate* exhaustSysOverride
  173. )
  174. {
  175. DEBUG_ASSERTCRASH(specificBarrelToUse>=0, ("specificBarrelToUse must now be explicit"));
  176. m_launcherID = launcher ? launcher->getID() : INVALID_ID;
  177. m_detonationWeaponTmpl = detWeap;
  178. m_extraBonusFlags = launcher ? launcher->getWeaponBonusCondition() : 0;
  179. Weapon::positionProjectileForLaunch(getObject(), launcher, wslot, specificBarrelToUse);
  180. projectileFireAtObjectOrPosition( victim, victimPos, detWeap, exhaustSysOverride );
  181. }
  182. #define APPROACH_HEIGHT 10.0f
  183. //-------------------------------------------------------------------------------------------------
  184. // The actual firing of the missile once setup.
  185. //-------------------------------------------------------------------------------------------------
  186. void MissileAIUpdate::projectileFireAtObjectOrPosition( const Object *victim, const Coord3D *victimPos, const WeaponTemplate *detWeap, const ParticleSystemTemplate* exhaustSysOverride )
  187. {
  188. const MissileAIUpdateModuleData* d = getMissileAIUpdateModuleData();
  189. Object *obj = getObject();
  190. m_exhaustSysTmpl = exhaustSysOverride;
  191. m_detonationWeaponTmpl = detWeap;
  192. Real initialVelToUse = d->m_initialVel;
  193. if (d->m_useWeaponSpeed)
  194. {
  195. Real weaponSpeed = detWeap->getWeaponSpeed();
  196. initialVelToUse = weaponSpeed;
  197. Locomotor* curLoco = getCurLocomotor();
  198. if (curLoco)
  199. {
  200. m_maxAccel = weaponSpeed;
  201. curLoco->setMaxSpeed(weaponSpeed);
  202. curLoco->setMaxAcceleration(m_maxAccel);
  203. }
  204. }
  205. Real deltaZ = victimPos->z - obj->getPosition()->z;
  206. Real dx = victimPos->x - obj->getPosition()->x;
  207. Real dy = victimPos->y - obj->getPosition()->y;
  208. Real xyDist = sqrt(sqr(dx)+sqr(dy));
  209. if (xyDist<1) xyDist = 1;
  210. Real zFactor = 0;
  211. if (deltaZ>0) {
  212. zFactor = deltaZ/xyDist;
  213. }
  214. Vector3 dir = getObject()->getTransformMatrix()->Get_X_Vector();
  215. dir.Normalize();
  216. dir.Z += 2*zFactor;
  217. dir.Normalize();
  218. PhysicsBehavior* physics = getObject()->getPhysics();
  219. if (physics && initialVelToUse > 0)
  220. {
  221. Real forceMag = physics->getMass() * initialVelToUse;
  222. Coord3D force;
  223. force.x = forceMag * dir.X;
  224. force.y = forceMag * dir.Y;
  225. force.z = forceMag * dir.Z;
  226. physics->applyMotiveForce( &force );
  227. }
  228. Vector3 objPos(obj->getPosition()->x, obj->getPosition()->y, obj->getPosition()->z);
  229. Matrix3D newXform;
  230. newXform.buildTransformMatrix( objPos, dir );
  231. obj->setTransformMatrix( &newXform );
  232. switchToState(LAUNCH);
  233. m_isTrackingTarget = false;
  234. // Missiles do their thing by colliding with something and exploding. So they Move to the target
  235. // instead of Attacking the target.
  236. if (victim && d->m_tryToFollowTarget)
  237. {
  238. getStateMachine()->setGoalPosition(victim->getPosition());
  239. // ick. const-cast is evil. fix. (srj)
  240. aiMoveToObject(const_cast<Object*>(victim), CMD_FROM_AI );
  241. m_originalTargetPos = *victim->getPosition();
  242. m_isTrackingTarget = TRUE;// Remember that I was originally shot at a moving object, so if the
  243. // target dies I can do something cool.
  244. m_victimID = victim->getID();
  245. }
  246. else
  247. {
  248. // Otherwise, we are just a Coord shot.
  249. Coord3D initialPos = *victimPos;
  250. m_originalTargetPos = *victimPos;
  251. if (d->m_lockDistance>0.0f) {
  252. initialPos.z += APPROACH_HEIGHT;
  253. }
  254. aiMoveToPosition(&initialPos, CMD_FROM_AI );
  255. m_victimID = INVALID_ID;
  256. }
  257. setCurrentVictim( victim );/// extending access to the victim via the parent class
  258. m_prevPos = *getObject()->getPosition();
  259. }
  260. //-------------------------------------------------------------------------------------------------
  261. //-------------------------------------------------------------------------------------------------
  262. Bool MissileAIUpdate::projectileHandleCollision( Object *other )
  263. {
  264. Object* obj = getObject();
  265. const MissileAIUpdateModuleData* d = getMissileAIUpdateModuleData();
  266. // check if our warhead is "armed" - if not, we are inert
  267. if (projectileIsArmed() == false)
  268. return true;
  269. if (other==NULL) {
  270. // we hit the ground. Check to see if we hit something unexpected.
  271. Coord3D goal = *getGoalPosition();
  272. Coord3D pos = *obj->getPosition();
  273. Coord3D delta;
  274. delta.x = pos.x-goal.x;
  275. delta.y = pos.y-goal.y;
  276. delta.z = pos.z-goal.z;
  277. if (delta.z > PATHFIND_CELL_SIZE_F) {
  278. // we're above our target goal.
  279. if (delta.length() > 3*PATHFIND_CELL_SIZE_F) {
  280. // we're somewhere else.
  281. return true;
  282. }
  283. }
  284. }
  285. if (other != NULL)
  286. {
  287. Object *projectileLauncher = TheGameLogic->findObjectByID( projectileGetLauncherID() );
  288. // if it's not the specific thing we were targeting, see if we should incidentally collide...
  289. if (!m_detonationWeaponTmpl->shouldProjectileCollideWith(projectileLauncher, obj, other, m_victimID))
  290. {
  291. //DEBUG_LOG(("ignoring projectile collision with %s at frame %d\n",other->getTemplate()->getName().str(),TheGameLogic->getFrame()));
  292. return true;
  293. }
  294. if (d->m_garrisonHitKillCount > 0)
  295. {
  296. ContainModuleInterface* contain = other->getContain();
  297. if( contain && contain->getContainCount() > 0 && contain->isGarrisonable() && !contain->isImmuneToClearBuildingAttacks() )
  298. {
  299. Int numKilled = 0;
  300. // garrisonable buildings subvert the normal process here.
  301. const ContainedItemsList* items = contain->getContainedItemsList();
  302. if (items)
  303. {
  304. for (ContainedItemsList::const_iterator it = items->begin(); *it != NULL && numKilled < d->m_garrisonHitKillCount; )
  305. {
  306. Object* thingToKill = *it++;
  307. if (!thingToKill->isEffectivelyDead() && thingToKill->isKindOfMulti(d->m_garrisonHitKillKindof, d->m_garrisonHitKillKindofNot))
  308. {
  309. //DEBUG_LOG(("Killed a garrisoned unit (%08lx %s) via Flash-Bang!\n",thingToKill,thingToKill->getTemplate()->getName().str()));
  310. if (projectileLauncher)
  311. projectileLauncher->scoreTheKill( thingToKill );
  312. thingToKill->kill();
  313. ++numKilled;
  314. }
  315. } // next contained item
  316. } // if items
  317. if (numKilled > 0)
  318. {
  319. // note, fx is played at center of building, not at grenade's location
  320. FXList::doFXObj(d->m_garrisonHitKillFX, other, NULL);
  321. // don't do the normal explosion; just destroy ourselves & return
  322. TheGameLogic->destroyObject(obj);
  323. return true;
  324. }
  325. } // if a garrisonable thing
  326. }
  327. }
  328. // collided with something... blow'd up!
  329. detonate();
  330. // mark ourself as "no collisions" (since we might still exist in slow death mode)
  331. obj->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_NO_COLLISIONS ) );
  332. return true;
  333. }
  334. //-------------------------------------------------------------------------------------------------
  335. void MissileAIUpdate::detonate()
  336. {
  337. Object* obj = getObject();
  338. if (m_detonationWeaponTmpl)
  339. {
  340. TheWeaponStore->handleProjectileDetonation(m_detonationWeaponTmpl, obj, obj->getPosition(), m_extraBonusFlags, !m_noDamage );
  341. if( m_detonationWeaponTmpl->getDieOnDetonate() )
  342. {
  343. DamageInfo damageInfo;
  344. damageInfo.in.m_damageType = DAMAGE_UNRESISTABLE;
  345. damageInfo.in.m_deathType = DEATH_DETONATED;
  346. damageInfo.in.m_sourceID = INVALID_ID;
  347. damageInfo.in.m_amount = obj->getBodyModule()->getMaxHealth();
  348. obj->attemptDamage( &damageInfo );
  349. }
  350. }
  351. else if( !m_noDamage )
  352. {
  353. DamageInfo damageInfo;
  354. damageInfo.in.m_damageType = DAMAGE_UNRESISTABLE;
  355. damageInfo.in.m_deathType = DEATH_DETONATED;
  356. damageInfo.in.m_sourceID = INVALID_ID;
  357. damageInfo.in.m_amount = obj->getBodyModule()->getMaxHealth();
  358. obj->attemptDamage( &damageInfo );
  359. }
  360. if (obj->getDrawable())
  361. obj->getDrawable()->setDrawableHidden(true);
  362. // Delay destroying the object two frames to let the contrail catch up. jba.
  363. switchToState(KILL_SELF);
  364. obj->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_MISSILE_KILLING_SELF ) );
  365. }
  366. //-------------------------------------------------------------------------------------------------
  367. void MissileAIUpdate::doPrelaunchState()
  368. {
  369. Locomotor* curLoco = getCurLocomotor();
  370. if (curLoco)
  371. {
  372. curLoco->setMaxAcceleration(0);
  373. curLoco->setMaxTurnRate(0);
  374. }
  375. }
  376. //-------------------------------------------------------------------------------------------------
  377. void MissileAIUpdate::doKillSelfState()
  378. {
  379. const MissileAIUpdateModuleData *modData = getMissileAIUpdateModuleData();
  380. if (m_stateTimestamp > TheGameLogic->getFrame() - modData->m_killSelfDelay )
  381. {
  382. // Hold in this state [modData->m_killSelfDelay] frames to let the contrail catch up. jba.
  383. return;
  384. }
  385. Object* obj = getObject();
  386. if (m_detonationWeaponTmpl)
  387. {
  388. if ( modData->m_detonateCallsKill )
  389. obj->kill(); // kill it (vs destroying it) so that its Die modules are called
  390. else
  391. TheGameLogic->destroyObject( obj );
  392. }
  393. switchToState(DEAD);
  394. }
  395. //-------------------------------------------------------------------------------------------------
  396. void MissileAIUpdate::doLaunchState()
  397. {
  398. Locomotor* curLoco = getCurLocomotor();
  399. if (curLoco)
  400. {
  401. curLoco->setMaxAcceleration(0);
  402. curLoco->setMaxTurnRate(0);
  403. }
  404. UnsignedInt delay = getMissileAIUpdateModuleData()->m_ignitionDelay;
  405. if (TheGameLogic->getFrame() - m_stateTimestamp >= delay)
  406. {
  407. switchToState(IGNITION);
  408. }
  409. }
  410. //-------------------------------------------------------------------------------------------------
  411. void MissileAIUpdate::doIgnitionState()
  412. {
  413. const MissileAIUpdateModuleData* d = getMissileAIUpdateModuleData();
  414. Locomotor* curLoco = getCurLocomotor();
  415. if (curLoco)
  416. {
  417. curLoco->setMaxAcceleration(m_maxAccel);
  418. curLoco->setMaxTurnRate(0);
  419. }
  420. FXList::doFXObj(d->m_ignitionFX, getObject());
  421. if (m_exhaustSysTmpl != NULL)
  422. {
  423. m_exhaustID = TheParticleSystemManager->createAttachedParticleSystemID(m_exhaustSysTmpl, getObject());
  424. }
  425. // arm the missile's "warhead"
  426. m_isArmed = true;
  427. UnsignedInt now = TheGameLogic->getFrame();
  428. m_fuelExpirationDate = d->m_fuelLifetime ? (now + d->m_fuelLifetime) : 0x7fffffff;
  429. switchToState(ATTACK_NOTURN);
  430. }
  431. //-------------------------------------------------------------------------------------------------
  432. void MissileAIUpdate::doAttackState(Bool turnOK)
  433. {
  434. Locomotor* curLoco = getCurLocomotor();
  435. const MissileAIUpdateModuleData* d = getMissileAIUpdateModuleData();
  436. if (TheGameLogic->getFrame() >= m_fuelExpirationDate)
  437. {
  438. if( d->m_detonateOnNoFuel )
  439. {
  440. detonate();
  441. return;
  442. }
  443. if (curLoco)
  444. {
  445. curLoco->setMaxAcceleration(0);
  446. curLoco->setMaxTurnRate(0);
  447. }
  448. tossExhaust();
  449. }
  450. else
  451. {
  452. if (curLoco)
  453. {
  454. curLoco->setMaxAcceleration(m_maxAccel);
  455. curLoco->setMaxTurnRate(turnOK ? BIGNUM : 0);
  456. }
  457. }
  458. if (d->m_lockDistance > 0)
  459. {
  460. Real lockDistanceSquared = d->m_lockDistance;
  461. Real distanceToTargetSquared;
  462. if (m_isTrackingTarget && (getGoalObject() != NULL)) {
  463. distanceToTargetSquared = ThePartitionManager->getDistanceSquared( getObject(), getGoalObject(), FROM_CENTER_2D);
  464. } else {
  465. distanceToTargetSquared = ThePartitionManager->getDistanceSquared( getObject(), getGoalPosition(), FROM_CENTER_2D );
  466. }
  467. if (lockDistanceSquared>0) {
  468. if (!m_isTrackingTarget) {
  469. // Immobile or ground target. Halve the lock distance.
  470. lockDistanceSquared *= 0.5f;
  471. }
  472. lockDistanceSquared *= lockDistanceSquared;
  473. if (distanceToTargetSquared < lockDistanceSquared) {
  474. if (!m_isTrackingTarget) {
  475. // Ground pos. Change to original goal.
  476. aiMoveToPosition(&m_originalTargetPos, CMD_FROM_AI );
  477. }
  478. switchToState(KILL);
  479. return;
  480. }
  481. }
  482. }
  483. if(curLoco && curLoco->getPreferredHeight() > 0)
  484. {
  485. // Am I close enough to the target to ignore my preferred height setting?
  486. Real distanceToTargetSquared = ThePartitionManager->getDistanceSquared( getObject(), getGoalPosition(), FROM_CENTER_2D );
  487. Real diveDistanceSquared = d->m_diveDistance;
  488. if (curLoco && curLoco->getPreferredHeight()) {
  489. diveDistanceSquared *= diveDistanceSquared;
  490. if( distanceToTargetSquared < diveDistanceSquared )
  491. curLoco->setUsePreciseZPos( true );
  492. }
  493. }
  494. if (m_noTurnDistLeft <= 0.0f)
  495. {
  496. switchToState(ATTACK);
  497. }
  498. // If I was fired at a flyer and have lost target (most likely they died), then I need to do something better
  499. // than cloverleaf around their last spot.
  500. if( m_isTrackingTarget && (getGoalObject() == NULL) )
  501. airborneTargetGone();
  502. }
  503. //-------------------------------------------------------------------------------------------------
  504. void MissileAIUpdate::doKillState(void)
  505. {
  506. if (TheGameLogic->getFrame() >= m_fuelExpirationDate)
  507. {
  508. if( getMissileAIUpdateModuleData()->m_detonateOnNoFuel )
  509. {
  510. detonate();
  511. return;
  512. }
  513. // srj sez: killstate missiles should not be allowed to live forever.
  514. airborneTargetGone();
  515. return;
  516. }
  517. /* Kill state uses the OBJECT_STATUS_BRAKING, which is a flag to the locomotor to
  518. "Cheat" and move directly towards the target. This ensures that the missile will hit.
  519. jba. */
  520. Locomotor* curLoco = getCurLocomotor();
  521. Object *obj = getObject();
  522. // Objects that are braking don't follow the normal physics, so they end up at their destination exactly.
  523. obj->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_BRAKING ) );
  524. if (curLoco)
  525. {
  526. curLoco->setMaxAcceleration(m_maxAccel);
  527. curLoco->setMaxTurnRate(BIGNUM);
  528. }
  529. if (isIdle()) {
  530. // we finished the move
  531. if (getGoalObject()!=NULL) {
  532. Locomotor* curLoco = getCurLocomotor();
  533. Real closeEnough = 1.0f;
  534. if (curLoco)
  535. {
  536. closeEnough = curLoco->getMaxSpeedForCondition(BODY_PRISTINE);
  537. }
  538. Real distanceToTargetSq = ThePartitionManager->getDistanceSquared( getObject(), getGoalObject(), FROM_BOUNDINGSPHERE_3D);
  539. //DEBUG_LOG(("Distance to target %f, closeEnough %f\n", sqrt(distanceToTargetSq), closeEnough));
  540. if (distanceToTargetSq < closeEnough*closeEnough) {
  541. Coord3D pos = *getGoalObject()->getPosition();
  542. getObject()->setPosition(&pos);
  543. detonate();
  544. } else{
  545. aiMoveToObject(getGoalObject(), CMD_FROM_AI );
  546. }
  547. } else {
  548. detonate();
  549. }
  550. }
  551. // If I was fired at a flyer and have lost target (most likely they died), then I need to do something better
  552. // than cloverleaf around their last spot.
  553. if( m_isTrackingTarget && (getGoalObject() == NULL) )
  554. airborneTargetGone();
  555. }
  556. //-------------------------------------------------------------------------------------------------
  557. void MissileAIUpdate::doDeadState()
  558. {
  559. Locomotor* curLoco = getCurLocomotor();
  560. if (curLoco)
  561. {
  562. curLoco->setMaxAcceleration(0);
  563. curLoco->setMaxTurnRate(0);
  564. }
  565. }
  566. //-------------------------------------------------------------------------------------------------
  567. /**
  568. * Simulate one frame of a missile's behavior
  569. */
  570. UpdateSleepTime MissileAIUpdate::update()
  571. {
  572. Coord3D newPos = *getObject()->getPosition();
  573. if (m_noTurnDistLeft > 0.0f && m_state >= IGNITION)
  574. {
  575. Real distThisTurn = sqrtf(sqr(newPos.x-m_prevPos.x) + sqr(newPos.y-m_prevPos.y) + sqr(newPos.z-m_prevPos.z));
  576. m_noTurnDistLeft -= distThisTurn;
  577. m_prevPos = newPos;
  578. }
  579. //If this missile has been marked to divert to countermeasures, check when
  580. //that will occur, then do it when the timer expires.
  581. if( m_framesTillDecoyed && m_framesTillDecoyed <= TheGameLogic->getFrame() )
  582. {
  583. Object *missile = getObject();
  584. m_framesTillDecoyed = 0;
  585. m_noDamage = TRUE;
  586. Object *victim = TheGameLogic->findObjectByID( m_victimID );
  587. if( victim )
  588. {
  589. ObjectID targetID = missile->calculateCountermeasureToDivertTo( *victim );
  590. if( targetID != INVALID_ID )
  591. {
  592. victim = TheGameLogic->findObjectByID( targetID );
  593. getStateMachine()->setGoalPosition(victim->getPosition());
  594. // ick. const-cast is evil. fix. (srj)
  595. aiMoveToObject(const_cast<Object*>(victim), CMD_FROM_AI );
  596. m_originalTargetPos = *victim->getPosition();
  597. m_isTrackingTarget = TRUE;// Remember that I was originally shot at a moving object, so if the
  598. // target dies I can do something cool.
  599. m_victimID = victim->getID();
  600. }
  601. }
  602. }
  603. if (newPos.z < 0)
  604. {
  605. // we ended up under the world. go away.
  606. TheGameLogic->destroyObject(getObject());
  607. return UPDATE_SLEEP_FOREVER;
  608. }
  609. switch( m_state )
  610. {
  611. case PRELAUNCH:
  612. doPrelaunchState();
  613. break;
  614. case LAUNCH:
  615. doLaunchState();
  616. // special case!
  617. if (m_state == IGNITION)
  618. {
  619. // just fall thru
  620. }
  621. else
  622. {
  623. break;
  624. }
  625. case IGNITION:
  626. doIgnitionState();
  627. break;
  628. case ATTACK_NOTURN:
  629. doAttackState(false);
  630. break;
  631. case ATTACK:
  632. doAttackState(true);
  633. break;
  634. case KILL:
  635. doKillState();
  636. break;
  637. case KILL_SELF:
  638. doKillSelfState();
  639. break;
  640. case DEAD:
  641. doDeadState();
  642. break;
  643. }
  644. /*UpdateSleepTime ret =*/ AIUpdateInterface::update();
  645. #if 1
  646. // srj sez: doh, why was this never in place? I guess 'cuz so few "smart" missiles ever go in
  647. // a downward-towards-ground attitude. however, comanche rocket pod missiles do, so this fix
  648. // is needed for them to target bridges correctly.
  649. // note that we want to use getHighestLayerForDestination() here, so that anything even slightly
  650. // below the bridge translates into GROUND. (getLayerForDestination just does a "closest" check)
  651. PathfindLayerEnum oldLayer = getObject()->getLayer();
  652. PathfindLayerEnum newLayer = TheTerrainLogic->getHighestLayerForDestination(getObject()->getPosition());
  653. getObject()->setLayer(newLayer);
  654. if (projectileIsArmed() && oldLayer != LAYER_GROUND && newLayer == LAYER_GROUND)
  655. {
  656. // see if we' still in the bridge's xy area
  657. Coord3D tmp = *getObject()->getPosition();
  658. tmp.z = 9999.0f;
  659. PathfindLayerEnum testLayer = TheTerrainLogic->getHighestLayerForDestination(&tmp);
  660. if (testLayer == oldLayer)
  661. {
  662. // ensure we are slightly above the bridge, to account for fudge & sloppy art
  663. const Real FUDGE = 2.0f;
  664. tmp.z = TheTerrainLogic->getLayerHeight(tmp.x, tmp.y, testLayer) + FUDGE;
  665. getObject()->setPosition(&tmp);
  666. // blow'd up!
  667. detonate();
  668. return UPDATE_SLEEP_NONE;
  669. }
  670. }
  671. #endif
  672. //return (mine < ret) ? mine : ret;
  673. /// @todo srj -- someday, make sleepy. for now, must not sleep.
  674. return UPDATE_SLEEP_NONE;
  675. }
  676. //-------------------------------------------------------------------------------------------------
  677. Bool MissileAIUpdate::processCollision(PhysicsBehavior *physics, Object *other)
  678. /* Returns true if the physics collide should apply the force. Normally not.
  679. Also determines whether objects are blocked, and if so, if they are stuck. jba.*/
  680. {
  681. // Missiles dont' get blocked by other objects.
  682. return false;
  683. }
  684. //-------------------------------------------------------------------------------------------------
  685. void MissileAIUpdate::airborneTargetGone()
  686. {
  687. // I would really love it if this could retarget, but all of the targeting and legality is done
  688. // by the Weapon that fired me. The safest thing for me to do in this state is to just run out of gas.
  689. m_fuelExpirationDate = TheGameLogic->getFrame();
  690. switchToState(KILL_SELF);
  691. }
  692. //-------------------------------------------------------------------------------------------------
  693. // Set number of frames till missile diverts to countermeasures.
  694. //-------------------------------------------------------------------------------------------------
  695. void MissileAIUpdate::setFramesTillCountermeasureDiversionOccurs( UnsignedInt frames )
  696. {
  697. UnsignedInt now = TheGameLogic->getFrame();
  698. m_framesTillDecoyed = now + frames;
  699. }
  700. //-------------------------------------------------------------------------------------------------
  701. void MissileAIUpdate::projectileNowJammed()
  702. {
  703. if( m_isJammed )
  704. return; // Already jammed
  705. const MissileAIUpdateModuleData *data = getMissileAIUpdateModuleData();
  706. getObject()->setModelConditionState(MODELCONDITION_JAMMED);
  707. Coord3D targetPosition;
  708. if( m_isTrackingTarget && getGoalObject() )
  709. targetPosition = *getGoalObject()->getPosition();
  710. else
  711. targetPosition = *getGoalPosition();
  712. Real scatter = data->m_distanceScatterWhenJammed;
  713. targetPosition.x += GameLogicRandomValue(-scatter, scatter);
  714. targetPosition.y += GameLogicRandomValue(-scatter, scatter);
  715. targetPosition.z = TheTerrainLogic->getLayerHeight( targetPosition.x,
  716. targetPosition.y,
  717. TheTerrainLogic->getHighestLayerForDestination(&targetPosition) );
  718. getStateMachine()->setGoalObject(NULL);
  719. // Projectiles are expressly forbidden from getting AIIdle. Who am I to argue.
  720. // I need to do something though, because I can no longer give a new move command
  721. // while moving because of the big state machine crash fix.
  722. //
  723. aiMoveToPosition( &targetPosition, CMD_FROM_AI );
  724. m_isTrackingTarget = FALSE;
  725. m_originalTargetPos = targetPosition;
  726. m_victimID = INVALID_ID;
  727. }
  728. // ------------------------------------------------------------------------------------------------
  729. /** CRC */
  730. // ------------------------------------------------------------------------------------------------
  731. void MissileAIUpdate::crc( Xfer *xfer )
  732. {
  733. // extend base class
  734. AIUpdateInterface::crc(xfer);
  735. } // end crc
  736. // ------------------------------------------------------------------------------------------------
  737. /** Xfer method
  738. * Version Info:
  739. * 1: Initial version */
  740. // ------------------------------------------------------------------------------------------------
  741. void MissileAIUpdate::xfer( Xfer *xfer )
  742. {
  743. // version
  744. const XferVersion currentVersion = 6;
  745. XferVersion version = currentVersion;
  746. xfer->xferVersion( &version, currentVersion );
  747. // extend base class
  748. AIUpdateInterface::xfer(xfer);
  749. if (version>=2) {
  750. xfer->xferCoord3D(&m_originalTargetPos);
  751. }
  752. xfer->xferUser(&m_state, sizeof(m_state));
  753. xfer->xferUnsignedInt(&m_stateTimestamp);
  754. xfer->xferUnsignedInt(&m_nextTargetTrackTime);
  755. xfer->xferObjectID(&m_launcherID);
  756. xfer->xferObjectID(&m_victimID);
  757. xfer->xferBool(&m_isArmed);
  758. xfer->xferUnsignedInt(&m_fuelExpirationDate);
  759. xfer->xferReal(&m_noTurnDistLeft);
  760. xfer->xferReal(&m_maxAccel);
  761. AsciiString weaponName;
  762. if (m_detonationWeaponTmpl)
  763. {
  764. weaponName = m_detonationWeaponTmpl->getName();
  765. }
  766. xfer->xferAsciiString(&weaponName);
  767. if (weaponName.isNotEmpty() && m_detonationWeaponTmpl == NULL)
  768. {
  769. m_detonationWeaponTmpl = TheWeaponStore->findWeaponTemplate(weaponName);
  770. }
  771. AsciiString exhaustName;
  772. if (m_exhaustSysTmpl)
  773. {
  774. exhaustName = m_exhaustSysTmpl->getName();
  775. }
  776. xfer->xferAsciiString(&exhaustName);
  777. if (exhaustName.isNotEmpty() && m_exhaustSysTmpl == NULL)
  778. {
  779. m_exhaustSysTmpl = TheParticleSystemManager->findTemplate(exhaustName);
  780. }
  781. xfer->xferBool(&m_isTrackingTarget);
  782. if (version >= 3)
  783. {
  784. xfer->xferCoord3D(&m_prevPos);
  785. }
  786. if (version >= 4)
  787. {
  788. xfer->xferUnsignedInt(&m_extraBonusFlags);
  789. xfer->xferUser( &m_exhaustID, sizeof( m_exhaustID ) );
  790. }
  791. if( version >= 5 )
  792. {
  793. xfer->xferUnsignedInt( &m_framesTillDecoyed );
  794. xfer->xferBool( &m_noDamage );
  795. }
  796. if( version>= 6 )
  797. xfer->xferBool( &m_isJammed );
  798. } // end xfer
  799. // ------------------------------------------------------------------------------------------------
  800. /** Load post process */
  801. // ------------------------------------------------------------------------------------------------
  802. void MissileAIUpdate::loadPostProcess( void )
  803. {
  804. // extend base class
  805. AIUpdateInterface::loadPostProcess();
  806. } // end loadPostProcess