MissileAIUpdate.cpp 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818
  1. /*
  2. ** Command & Conquer Generals(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. }
  71. //-----------------------------------------------------------------------------
  72. void MissileAIUpdateModuleData::buildFieldParse(MultiIniFieldParse& p)
  73. {
  74. AIUpdateModuleData::buildFieldParse(p);
  75. static const FieldParse dataFieldParse[] =
  76. {
  77. { "TryToFollowTarget", INI::parseBool, NULL, offsetof( MissileAIUpdateModuleData, m_tryToFollowTarget ) },
  78. { "FuelLifetime", INI::parseDurationUnsignedInt, NULL, offsetof( MissileAIUpdateModuleData, m_fuelLifetime ) },
  79. { "IgnitionDelay", INI::parseDurationUnsignedInt, NULL, offsetof( MissileAIUpdateModuleData, m_ignitionDelay ) },
  80. { "InitialVelocity", INI::parseVelocityReal, NULL, offsetof( MissileAIUpdateModuleData, m_initialVel) },
  81. { "DistanceToTravelBeforeTurning", INI::parseReal, NULL, offsetof( MissileAIUpdateModuleData, m_initialDist ) },
  82. { "DistanceToTargetBeforeDiving", INI::parseReal, NULL, offsetof( MissileAIUpdateModuleData, m_diveDistance ) },
  83. { "DistanceToTargetForLock",INI::parseReal, NULL, offsetof( MissileAIUpdateModuleData, m_lockDistance ) },
  84. { "IgnitionFX", INI::parseFXList, NULL, offsetof( MissileAIUpdateModuleData, m_ignitionFX ) },
  85. { "UseWeaponSpeed", INI::parseBool, NULL, offsetof( MissileAIUpdateModuleData, m_useWeaponSpeed ) },
  86. { "DetonateOnNoFuel", INI::parseBool, NULL, offsetof( MissileAIUpdateModuleData, m_detonateOnNoFuel ) },
  87. { "GarrisonHitKillRequiredKindOf", KindOfMaskType::parseFromINI, NULL, offsetof( MissileAIUpdateModuleData, m_garrisonHitKillKindof ) },
  88. { "GarrisonHitKillForbiddenKindOf", KindOfMaskType::parseFromINI, NULL, offsetof( MissileAIUpdateModuleData, m_garrisonHitKillKindofNot ) },
  89. { "GarrisonHitKillCount", INI::parseUnsignedInt, NULL, offsetof( MissileAIUpdateModuleData, m_garrisonHitKillCount ) },
  90. { "GarrisonHitKillFX", INI::parseFXList, NULL, offsetof( MissileAIUpdateModuleData, m_garrisonHitKillFX ) },
  91. { 0, 0, 0, 0 }
  92. };
  93. p.add(dataFieldParse);
  94. }
  95. //-------------------------------------------------------------------------------------------------
  96. //-------------------------------------------------------------------------------------------------
  97. //-------------------------------------------------------------------------------------------------
  98. //-------------------------------------------------------------------------------------------------
  99. MissileAIUpdate::MissileAIUpdate( Thing *thing, const ModuleData* moduleData ) : AIUpdateInterface( thing, moduleData )
  100. {
  101. const MissileAIUpdateModuleData* d = getMissileAIUpdateModuleData();
  102. m_state = PRELAUNCH;
  103. m_stateTimestamp = TheGameLogic->getFrame();
  104. m_nextTargetTrackTime = 0x7fffffff; // so that we never recalc target pos, by default
  105. m_launcherID = INVALID_ID;
  106. m_victimID = INVALID_ID;
  107. m_isArmed = false;
  108. m_fuelExpirationDate = 0;
  109. m_noTurnDistLeft = d->m_initialDist;
  110. m_prevPos = *getObject()->getPosition();
  111. m_maxAccel = BIGNUM;
  112. m_detonationWeaponTmpl = NULL;
  113. m_exhaustSysTmpl = NULL;
  114. m_isTrackingTarget = FALSE;
  115. m_exhaustID = INVALID_PARTICLE_SYSTEM_ID;
  116. m_extraBonusFlags = 0;
  117. m_originalTargetPos.zero();
  118. }
  119. //-------------------------------------------------------------------------------------------------
  120. MissileAIUpdate::~MissileAIUpdate()
  121. {
  122. tossExhaust();
  123. }
  124. //-------------------------------------------------------------------------------------------------
  125. void MissileAIUpdate::onDelete( void )
  126. {
  127. //
  128. // there is no need to destroy the attached particle systems here because the particle
  129. // systems themselves detect that their "parent" object is gone and destroy themselves.
  130. // but to be clean, for the exhaust particle system, we will invalidate the ID so that
  131. // we can't do anything else with it since we're being deleted
  132. //
  133. m_exhaustID = INVALID_PARTICLE_SYSTEM_ID;
  134. }
  135. //-------------------------------------------------------------------------------------------------
  136. void MissileAIUpdate::tossExhaust()
  137. {
  138. if (m_exhaustID != INVALID_PARTICLE_SYSTEM_ID)
  139. {
  140. TheParticleSystemManager->destroyParticleSystemByID(m_exhaustID);
  141. m_exhaustID = INVALID_PARTICLE_SYSTEM_ID;
  142. }
  143. }
  144. //-------------------------------------------------------------------------------------------------
  145. void MissileAIUpdate::switchToState(MissileStateType s)
  146. {
  147. if (m_state != s)
  148. {
  149. m_state = s;
  150. m_stateTimestamp = TheGameLogic->getFrame();
  151. }
  152. }
  153. //-------------------------------------------------------------------------------------------------
  154. // Prepares the missile for launch via proper weapon-system channels.
  155. //-------------------------------------------------------------------------------------------------
  156. void MissileAIUpdate::projectileLaunchAtObjectOrPosition(
  157. const Object *victim,
  158. const Coord3D* victimPos,
  159. const Object *launcher,
  160. WeaponSlotType wslot,
  161. Int specificBarrelToUse,
  162. const WeaponTemplate* detWeap,
  163. const ParticleSystemTemplate* exhaustSysOverride
  164. )
  165. {
  166. DEBUG_ASSERTCRASH(specificBarrelToUse>=0, ("specificBarrelToUse must now be explicit"));
  167. m_launcherID = launcher ? launcher->getID() : INVALID_ID;
  168. m_detonationWeaponTmpl = detWeap;
  169. m_extraBonusFlags = launcher ? launcher->getWeaponBonusCondition() : 0;
  170. Weapon::positionProjectileForLaunch(getObject(), launcher, wslot, specificBarrelToUse);
  171. projectileFireAtObjectOrPosition( victim, victimPos, detWeap, exhaustSysOverride );
  172. }
  173. #define APPROACH_HEIGHT 10.0f
  174. //-------------------------------------------------------------------------------------------------
  175. // The actual firing of the missile once setup.
  176. //-------------------------------------------------------------------------------------------------
  177. void MissileAIUpdate::projectileFireAtObjectOrPosition( const Object *victim, const Coord3D *victimPos, const WeaponTemplate *detWeap, const ParticleSystemTemplate* exhaustSysOverride )
  178. {
  179. const MissileAIUpdateModuleData* d = getMissileAIUpdateModuleData();
  180. Object *obj = getObject();
  181. m_exhaustSysTmpl = exhaustSysOverride;
  182. m_detonationWeaponTmpl = detWeap;
  183. Real initialVelToUse = d->m_initialVel;
  184. if (d->m_useWeaponSpeed)
  185. {
  186. Real weaponSpeed = detWeap->getWeaponSpeed();
  187. initialVelToUse = weaponSpeed;
  188. Locomotor* curLoco = getCurLocomotor();
  189. if (curLoco)
  190. {
  191. m_maxAccel = weaponSpeed;
  192. curLoco->setMaxSpeed(weaponSpeed);
  193. curLoco->setMaxAcceleration(m_maxAccel);
  194. }
  195. }
  196. Real deltaZ = victimPos->z - obj->getPosition()->z;
  197. Real dx = victimPos->x - obj->getPosition()->x;
  198. Real dy = victimPos->y - obj->getPosition()->y;
  199. Real xyDist = sqrt(sqr(dx)+sqr(dy));
  200. if (xyDist<1) xyDist = 1;
  201. Real zFactor = 0;
  202. if (deltaZ>0) {
  203. zFactor = deltaZ/xyDist;
  204. }
  205. Vector3 dir = getObject()->getTransformMatrix()->Get_X_Vector();
  206. dir.Normalize();
  207. dir.Z += 2*zFactor;
  208. dir.Normalize();
  209. PhysicsBehavior* physics = getObject()->getPhysics();
  210. if (physics && initialVelToUse > 0)
  211. {
  212. Real forceMag = physics->getMass() * initialVelToUse;
  213. Coord3D force;
  214. force.x = forceMag * dir.X;
  215. force.y = forceMag * dir.Y;
  216. force.z = forceMag * dir.Z;
  217. physics->applyMotiveForce( &force );
  218. }
  219. Vector3 objPos(obj->getPosition()->x, obj->getPosition()->y, obj->getPosition()->z);
  220. Matrix3D newXform;
  221. newXform.buildTransformMatrix( objPos, dir );
  222. obj->setTransformMatrix( &newXform );
  223. switchToState(LAUNCH);
  224. m_isTrackingTarget = false;
  225. // Missiles do their thing by colliding with something and exploding. So they Move to the target
  226. // instead of Attacking the target.
  227. if (victim && d->m_tryToFollowTarget)
  228. {
  229. getStateMachine()->setGoalPosition(victim->getPosition());
  230. // ick. const-cast is evil. fix. (srj)
  231. aiMoveToObject(const_cast<Object*>(victim), CMD_FROM_AI );
  232. m_originalTargetPos = *victim->getPosition();
  233. m_isTrackingTarget = TRUE;// Remember that I was originally shot at a moving object, so if the
  234. // target dies I can do something cool.
  235. m_victimID = victim->getID();
  236. }
  237. else
  238. {
  239. // Otherwise, we are just a Coord shot.
  240. Coord3D initialPos = *victimPos;
  241. m_originalTargetPos = *victimPos;
  242. if (d->m_lockDistance>0.0f) {
  243. initialPos.z += APPROACH_HEIGHT;
  244. }
  245. aiMoveToPosition(&initialPos, CMD_FROM_AI );
  246. m_victimID = INVALID_ID;
  247. }
  248. m_prevPos = *getObject()->getPosition();
  249. }
  250. //-------------------------------------------------------------------------------------------------
  251. //-------------------------------------------------------------------------------------------------
  252. Bool MissileAIUpdate::projectileHandleCollision( Object *other )
  253. {
  254. Object* obj = getObject();
  255. const MissileAIUpdateModuleData* d = getMissileAIUpdateModuleData();
  256. // check if our warhead is "armed" - if not, we are inert
  257. if (projectileIsArmed() == false)
  258. return true;
  259. if (other==NULL) {
  260. // we hit the ground. Check to see if we hit something unexpected.
  261. Coord3D goal = *getGoalPosition();
  262. Coord3D pos = *obj->getPosition();
  263. Coord3D delta;
  264. delta.x = pos.x-goal.x;
  265. delta.y = pos.y-goal.y;
  266. delta.z = pos.z-goal.z;
  267. if (delta.z > PATHFIND_CELL_SIZE_F) {
  268. // we're above our target goal.
  269. if (delta.length() > 3*PATHFIND_CELL_SIZE_F) {
  270. // we're somewhere else.
  271. return true;
  272. }
  273. }
  274. }
  275. if (other != NULL)
  276. {
  277. Object *projectileLauncher = TheGameLogic->findObjectByID( projectileGetLauncherID() );
  278. // if it's not the specific thing we were targeting, see if we should incidentally collide...
  279. if (!m_detonationWeaponTmpl->shouldProjectileCollideWith(projectileLauncher, obj, other, m_victimID))
  280. {
  281. //DEBUG_LOG(("ignoring projectile collision with %s at frame %d\n",other->getTemplate()->getName().str(),TheGameLogic->getFrame()));
  282. return true;
  283. }
  284. if (d->m_garrisonHitKillCount > 0)
  285. {
  286. ContainModuleInterface* contain = other->getContain();
  287. if( contain && contain->getContainCount() > 0 && contain->isGarrisonable() && !contain->isImmuneToClearBuildingAttacks() )
  288. {
  289. Int numKilled = 0;
  290. // garrisonable buildings subvert the normal process here.
  291. const ContainedItemsList* items = contain->getContainedItemsList();
  292. if (items)
  293. {
  294. for (ContainedItemsList::const_iterator it = items->begin(); *it != NULL && numKilled < d->m_garrisonHitKillCount; )
  295. {
  296. Object* thingToKill = *it++;
  297. if (!thingToKill->isEffectivelyDead() && thingToKill->isKindOfMulti(d->m_garrisonHitKillKindof, d->m_garrisonHitKillKindofNot))
  298. {
  299. //DEBUG_LOG(("Killed a garrisoned unit (%08lx %s) via Flash-Bang!\n",thingToKill,thingToKill->getTemplate()->getName().str()));
  300. if (projectileLauncher)
  301. projectileLauncher->scoreTheKill( thingToKill );
  302. thingToKill->kill();
  303. ++numKilled;
  304. }
  305. } // next contained item
  306. } // if items
  307. if (numKilled > 0)
  308. {
  309. // note, fx is played at center of building, not at grenade's location
  310. FXList::doFXObj(d->m_garrisonHitKillFX, other, NULL);
  311. // don't do the normal explosion; just destroy ourselves & return
  312. TheGameLogic->destroyObject(obj);
  313. return true;
  314. }
  315. } // if a garrisonable thing
  316. }
  317. }
  318. // collided with something... blow'd up!
  319. detonate();
  320. // mark ourself as "no collisions" (since we might still exist in slow death mode)
  321. obj->setStatus(OBJECT_STATUS_NO_COLLISIONS);
  322. return true;
  323. }
  324. //-------------------------------------------------------------------------------------------------
  325. void MissileAIUpdate::detonate()
  326. {
  327. Object* obj = getObject();
  328. if (m_detonationWeaponTmpl)
  329. {
  330. TheWeaponStore->handleProjectileDetonation(m_detonationWeaponTmpl, obj, obj->getPosition(), m_extraBonusFlags);
  331. }
  332. else
  333. {
  334. // kill it (vs destroying it) so that its Die modules are called
  335. // obj->kill();
  336. // do it manually, so we can specify DEATH_DETONATED
  337. DamageInfo damageInfo;
  338. damageInfo.in.m_damageType = DAMAGE_UNRESISTABLE;
  339. damageInfo.in.m_deathType = DEATH_DETONATED;
  340. damageInfo.in.m_sourceID = INVALID_ID;
  341. damageInfo.in.m_amount = obj->getBodyModule()->getMaxHealth();
  342. obj->attemptDamage( &damageInfo );
  343. }
  344. if (obj->getDrawable())
  345. obj->getDrawable()->setDrawableHidden(true);
  346. // Delay destroying the object two frames to let the contrail catch up. jba.
  347. switchToState(KILL_SELF);
  348. }
  349. //-------------------------------------------------------------------------------------------------
  350. void MissileAIUpdate::doPrelaunchState()
  351. {
  352. Locomotor* curLoco = getCurLocomotor();
  353. if (curLoco)
  354. {
  355. curLoco->setMaxAcceleration(0);
  356. curLoco->setMaxTurnRate(0);
  357. }
  358. }
  359. //-------------------------------------------------------------------------------------------------
  360. void MissileAIUpdate::doKillSelfState()
  361. {
  362. if (m_stateTimestamp > TheGameLogic->getFrame()-3) {
  363. // Hold in this state 2 frames to let the contrail catch up. jba.
  364. return;
  365. }
  366. Object* obj = getObject();
  367. if (m_detonationWeaponTmpl) {
  368. TheGameLogic->destroyObject( obj );
  369. }
  370. switchToState(DEAD);
  371. }
  372. //-------------------------------------------------------------------------------------------------
  373. void MissileAIUpdate::doLaunchState()
  374. {
  375. Locomotor* curLoco = getCurLocomotor();
  376. if (curLoco)
  377. {
  378. curLoco->setMaxAcceleration(0);
  379. curLoco->setMaxTurnRate(0);
  380. }
  381. UnsignedInt delay = getMissileAIUpdateModuleData()->m_ignitionDelay;
  382. if (TheGameLogic->getFrame() - m_stateTimestamp >= delay)
  383. {
  384. switchToState(IGNITION);
  385. }
  386. }
  387. //-------------------------------------------------------------------------------------------------
  388. void MissileAIUpdate::doIgnitionState()
  389. {
  390. const MissileAIUpdateModuleData* d = getMissileAIUpdateModuleData();
  391. Locomotor* curLoco = getCurLocomotor();
  392. if (curLoco)
  393. {
  394. curLoco->setMaxAcceleration(m_maxAccel);
  395. curLoco->setMaxTurnRate(0);
  396. }
  397. FXList::doFXObj(d->m_ignitionFX, getObject());
  398. if (m_exhaustSysTmpl != NULL)
  399. {
  400. m_exhaustID = TheParticleSystemManager->createAttachedParticleSystemID(m_exhaustSysTmpl, getObject());
  401. }
  402. // arm the missile's "warhead"
  403. m_isArmed = true;
  404. UnsignedInt now = TheGameLogic->getFrame();
  405. m_fuelExpirationDate = d->m_fuelLifetime ? (now + d->m_fuelLifetime) : 0x7fffffff;
  406. switchToState(ATTACK_NOTURN);
  407. }
  408. //-------------------------------------------------------------------------------------------------
  409. void MissileAIUpdate::doAttackState(Bool turnOK)
  410. {
  411. Locomotor* curLoco = getCurLocomotor();
  412. const MissileAIUpdateModuleData* d = getMissileAIUpdateModuleData();
  413. if (TheGameLogic->getFrame() >= m_fuelExpirationDate)
  414. {
  415. if( d->m_detonateOnNoFuel )
  416. {
  417. detonate();
  418. return;
  419. }
  420. if (curLoco)
  421. {
  422. curLoco->setMaxAcceleration(0);
  423. curLoco->setMaxTurnRate(0);
  424. }
  425. tossExhaust();
  426. }
  427. else
  428. {
  429. if (curLoco)
  430. {
  431. curLoco->setMaxAcceleration(m_maxAccel);
  432. curLoco->setMaxTurnRate(turnOK ? BIGNUM : 0);
  433. }
  434. }
  435. if (d->m_lockDistance > 0)
  436. {
  437. Real lockDistanceSquared = d->m_lockDistance;
  438. Real distanceToTargetSquared;
  439. if (m_isTrackingTarget && (getGoalObject() != NULL)) {
  440. distanceToTargetSquared = ThePartitionManager->getDistanceSquared( getObject(), getGoalObject(), FROM_CENTER_2D);
  441. } else {
  442. distanceToTargetSquared = ThePartitionManager->getDistanceSquared( getObject(), getGoalPosition(), FROM_CENTER_2D );
  443. }
  444. if (lockDistanceSquared>0) {
  445. if (!m_isTrackingTarget) {
  446. // Immobile or ground target. Halve the lock distance.
  447. lockDistanceSquared *= 0.5f;
  448. }
  449. lockDistanceSquared *= lockDistanceSquared;
  450. if (distanceToTargetSquared < lockDistanceSquared) {
  451. if (!m_isTrackingTarget) {
  452. // Ground pos. Change to original goal.
  453. aiMoveToPosition(&m_originalTargetPos, CMD_FROM_AI );
  454. }
  455. switchToState(KILL);
  456. return;
  457. }
  458. }
  459. }
  460. if(curLoco && curLoco->getPreferredHeight() > 0)
  461. {
  462. // Am I close enough to the target to ignore my preferred height setting?
  463. Real distanceToTargetSquared = ThePartitionManager->getDistanceSquared( getObject(), getGoalPosition(), FROM_CENTER_2D );
  464. Real diveDistanceSquared = d->m_diveDistance;
  465. if (curLoco && curLoco->getPreferredHeight()) {
  466. diveDistanceSquared *= diveDistanceSquared;
  467. if( distanceToTargetSquared < diveDistanceSquared )
  468. curLoco->setUsePreciseZPos( true );
  469. }
  470. }
  471. if (m_noTurnDistLeft <= 0.0f)
  472. {
  473. switchToState(ATTACK);
  474. }
  475. // If I was fired at a flyer and have lost target (most likely they died), then I need to do something better
  476. // than cloverleaf around their last spot.
  477. if( m_isTrackingTarget && (getGoalObject() == NULL) )
  478. airborneTargetGone();
  479. }
  480. //-------------------------------------------------------------------------------------------------
  481. void MissileAIUpdate::doKillState(void)
  482. {
  483. if (TheGameLogic->getFrame() >= m_fuelExpirationDate)
  484. {
  485. if( getMissileAIUpdateModuleData()->m_detonateOnNoFuel )
  486. {
  487. detonate();
  488. return;
  489. }
  490. // srj sez: killstate missiles should not be allowed to live forever.
  491. airborneTargetGone();
  492. return;
  493. }
  494. /* Kill state uses the OBJECT_STATUS_BRAKING, which is a flag to the locomotor to
  495. "Cheat" and move directly towards the target. This ensures that the missile will hit.
  496. jba. */
  497. Locomotor* curLoco = getCurLocomotor();
  498. Object *obj = getObject();
  499. // Objects that are braking don't follow the normal physics, so they end up at their destination exactly.
  500. obj->setStatus(OBJECT_STATUS_BRAKING, TRUE);
  501. if (curLoco)
  502. {
  503. curLoco->setMaxAcceleration(m_maxAccel);
  504. curLoco->setMaxTurnRate(BIGNUM);
  505. }
  506. if (isIdle()) {
  507. // we finished the move
  508. if (getGoalObject()!=NULL) {
  509. Locomotor* curLoco = getCurLocomotor();
  510. Real closeEnough = 1.0f;
  511. if (curLoco)
  512. {
  513. closeEnough = curLoco->getMaxSpeedForCondition(BODY_PRISTINE);
  514. }
  515. Real distanceToTargetSq = ThePartitionManager->getDistanceSquared( getObject(), getGoalObject(), FROM_CENTER_3D);
  516. //DEBUG_LOG(("Distance to target %f, closeEnough %f\n", sqrt(distanceToTargetSq), closeEnough));
  517. if (distanceToTargetSq < closeEnough*closeEnough) {
  518. Coord3D pos = *getGoalObject()->getPosition();
  519. getObject()->setPosition(&pos);
  520. detonate();
  521. } else{
  522. aiMoveToObject(getGoalObject(), CMD_FROM_AI );
  523. }
  524. } else {
  525. detonate();
  526. }
  527. }
  528. // If I was fired at a flyer and have lost target (most likely they died), then I need to do something better
  529. // than cloverleaf around their last spot.
  530. if( m_isTrackingTarget && (getGoalObject() == NULL) )
  531. airborneTargetGone();
  532. }
  533. //-------------------------------------------------------------------------------------------------
  534. void MissileAIUpdate::doDeadState()
  535. {
  536. Locomotor* curLoco = getCurLocomotor();
  537. if (curLoco)
  538. {
  539. curLoco->setMaxAcceleration(0);
  540. curLoco->setMaxTurnRate(0);
  541. }
  542. }
  543. //-------------------------------------------------------------------------------------------------
  544. /**
  545. * Simulate one frame of a missile's behavior
  546. */
  547. UpdateSleepTime MissileAIUpdate::update()
  548. {
  549. Coord3D newPos = *getObject()->getPosition();
  550. if (m_noTurnDistLeft > 0.0f && m_state >= IGNITION)
  551. {
  552. Real distThisTurn = sqrtf(sqr(newPos.x-m_prevPos.x) + sqr(newPos.y-m_prevPos.y) + sqr(newPos.z-m_prevPos.z));
  553. m_noTurnDistLeft -= distThisTurn;
  554. m_prevPos = newPos;
  555. }
  556. if (newPos.z < 0)
  557. {
  558. // we ended up under the world. go away.
  559. TheGameLogic->destroyObject(getObject());
  560. return UPDATE_SLEEP_FOREVER;
  561. }
  562. switch( m_state )
  563. {
  564. case PRELAUNCH:
  565. doPrelaunchState();
  566. break;
  567. case LAUNCH:
  568. doLaunchState();
  569. // special case!
  570. if (m_state == IGNITION)
  571. {
  572. // just fall thru
  573. }
  574. else
  575. {
  576. break;
  577. }
  578. case IGNITION:
  579. doIgnitionState();
  580. break;
  581. case ATTACK_NOTURN:
  582. doAttackState(false);
  583. break;
  584. case ATTACK:
  585. doAttackState(true);
  586. break;
  587. case KILL:
  588. doKillState();
  589. break;
  590. case KILL_SELF:
  591. doKillSelfState();
  592. break;
  593. case DEAD:
  594. doDeadState();
  595. break;
  596. }
  597. /*UpdateSleepTime ret =*/ AIUpdateInterface::update();
  598. #if 1
  599. // srj sez: doh, why was this never in place? I guess 'cuz so few "smart" missiles ever go in
  600. // a downward-towards-ground attitude. however, comanche rocket pod missiles do, so this fix
  601. // is needed for them to target bridges correctly.
  602. // note that we want to use getHighestLayerForDestination() here, so that anything even slightly
  603. // below the bridge translates into GROUND. (getLayerForDestination just does a "closest" check)
  604. PathfindLayerEnum oldLayer = getObject()->getLayer();
  605. PathfindLayerEnum newLayer = TheTerrainLogic->getHighestLayerForDestination(getObject()->getPosition());
  606. getObject()->setLayer(newLayer);
  607. if (projectileIsArmed() && oldLayer != LAYER_GROUND && newLayer == LAYER_GROUND)
  608. {
  609. // see if we' still in the bridge's xy area
  610. Coord3D tmp = *getObject()->getPosition();
  611. tmp.z = 9999.0f;
  612. PathfindLayerEnum testLayer = TheTerrainLogic->getHighestLayerForDestination(&tmp);
  613. if (testLayer == oldLayer)
  614. {
  615. // ensure we are slightly above the bridge, to account for fudge & sloppy art
  616. const Real FUDGE = 2.0f;
  617. tmp.z = TheTerrainLogic->getLayerHeight(tmp.x, tmp.y, testLayer) + FUDGE;
  618. getObject()->setPosition(&tmp);
  619. // blow'd up!
  620. detonate();
  621. return UPDATE_SLEEP_NONE;
  622. }
  623. }
  624. #endif
  625. //return (mine < ret) ? mine : ret;
  626. /// @todo srj -- someday, make sleepy. for now, must not sleep.
  627. return UPDATE_SLEEP_NONE;
  628. }
  629. //-------------------------------------------------------------------------------------------------
  630. Bool MissileAIUpdate::processCollision(PhysicsBehavior *physics, Object *other)
  631. /* Returns true if the physics collide should apply the force. Normally not.
  632. Also determines whether objects are blocked, and if so, if they are stuck. jba.*/
  633. {
  634. // Missiles dont' get blocked by other objects.
  635. return false;
  636. }
  637. //-------------------------------------------------------------------------------------------------
  638. void MissileAIUpdate::airborneTargetGone()
  639. {
  640. // I would really love it if this could retarget, but all of the targeting and legality is done
  641. // by the Weapon that fired me. The safest thing for me to do in this state is to just run out of gas.
  642. m_fuelExpirationDate = TheGameLogic->getFrame();
  643. switchToState(KILL_SELF);
  644. }
  645. // ------------------------------------------------------------------------------------------------
  646. /** CRC */
  647. // ------------------------------------------------------------------------------------------------
  648. void MissileAIUpdate::crc( Xfer *xfer )
  649. {
  650. // extend base class
  651. AIUpdateInterface::crc(xfer);
  652. } // end crc
  653. // ------------------------------------------------------------------------------------------------
  654. /** Xfer method
  655. * Version Info:
  656. * 1: Initial version */
  657. // ------------------------------------------------------------------------------------------------
  658. void MissileAIUpdate::xfer( Xfer *xfer )
  659. {
  660. // version
  661. const XferVersion currentVersion = 4;
  662. XferVersion version = currentVersion;
  663. xfer->xferVersion( &version, currentVersion );
  664. // extend base class
  665. AIUpdateInterface::xfer(xfer);
  666. if (version>=2) {
  667. xfer->xferCoord3D(&m_originalTargetPos);
  668. }
  669. xfer->xferUser(&m_state, sizeof(m_state));
  670. xfer->xferUnsignedInt(&m_stateTimestamp);
  671. xfer->xferUnsignedInt(&m_nextTargetTrackTime);
  672. xfer->xferObjectID(&m_launcherID);
  673. xfer->xferObjectID(&m_victimID);
  674. xfer->xferBool(&m_isArmed);
  675. xfer->xferUnsignedInt(&m_fuelExpirationDate);
  676. xfer->xferReal(&m_noTurnDistLeft);
  677. xfer->xferReal(&m_maxAccel);
  678. AsciiString weaponName;
  679. if (m_detonationWeaponTmpl)
  680. {
  681. weaponName = m_detonationWeaponTmpl->getName();
  682. }
  683. xfer->xferAsciiString(&weaponName);
  684. if (weaponName.isNotEmpty() && m_detonationWeaponTmpl == NULL)
  685. {
  686. m_detonationWeaponTmpl = TheWeaponStore->findWeaponTemplate(weaponName);
  687. }
  688. AsciiString exhaustName;
  689. if (m_exhaustSysTmpl)
  690. {
  691. exhaustName = m_exhaustSysTmpl->getName();
  692. }
  693. xfer->xferAsciiString(&exhaustName);
  694. if (exhaustName.isNotEmpty() && m_exhaustSysTmpl == NULL)
  695. {
  696. m_exhaustSysTmpl = TheParticleSystemManager->findTemplate(exhaustName);
  697. }
  698. xfer->xferBool(&m_isTrackingTarget);
  699. if (version >= 3)
  700. {
  701. xfer->xferCoord3D(&m_prevPos);
  702. }
  703. if (version >= 4)
  704. {
  705. xfer->xferUnsignedInt(&m_extraBonusFlags);
  706. xfer->xferUser( &m_exhaustID, sizeof( m_exhaustID ) );
  707. }
  708. } // end xfer
  709. // ------------------------------------------------------------------------------------------------
  710. /** Load post process */
  711. // ------------------------------------------------------------------------------------------------
  712. void MissileAIUpdate::loadPostProcess( void )
  713. {
  714. // extend base class
  715. AIUpdateInterface::loadPostProcess();
  716. } // end loadPostProcess