heatseeker.cpp 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826
  1. ////////////////////////////////////////////////////////////////////////////////
  2. //
  3. // Copyright 2016 RWS Inc, All Rights Reserved
  4. //
  5. // This program is free software; you can redistribute it and/or modify
  6. // it under the terms of version 2 of the GNU General Public License as published by
  7. // the Free Software Foundation
  8. //
  9. // This program is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU General Public License along
  15. // with this program; if not, write to the Free Software Foundation, Inc.,
  16. // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  17. //
  18. // heatseeker.cpp
  19. // Project: Postal
  20. //
  21. // This module implements the CHeatseeker weapon class which is a heat seeking
  22. // guided missile which will hit people or fire.
  23. //
  24. // History:
  25. // 05/13/97 BRH Started this weapon object from the CHeatseeker code
  26. //
  27. // 05/14/97 BRH Added the IsPathClear() check to help avoid walls.
  28. //
  29. // 05/15/97 BRH Changed the graphic for the missile. Added different
  30. // masks for seek and collide. Changed the offscreen
  31. // behavior so the missile will turn around when it has gone
  32. // off of the edge of the world.
  33. //
  34. // 05/15/97 BRH Changed seeking to QuickCheckClosest so that it picks
  35. // the closest target rather than the first one in the
  36. // list of collisions.
  37. //
  38. // 05/15/97 JMI In ProcessMessages(), on a delete message, the CHeatseeker
  39. // was deleting itself. Then, after ProcessMessages()
  40. // returned, it was checking m_eState, and, if set to
  41. // State_Delete, was returning to avoid possible problems
  42. // referencing a deleted this. The problem is that you cannot
  43. // reference m_eState after the object is deleted b/c that
  44. // would require utilizing the this. Also, on Alpha (and
  45. // Intel (SHMALLOC) too) the memory is altered to a certain
  46. // value to try to catch such problems. This caused m_eState
  47. // to NOT be State_Delete, which resulted in writes to
  48. // freed memory as well.
  49. // To fix this problem, I changed ProcessMessages() to merely
  50. // set the state to State_Delete and utilized the switch in
  51. // Update() to delete this and return.
  52. //
  53. // 05/26/97 BRH Changed check for obstacles to only check for height. It
  54. // had been checking for NOT_WALKABLE which caused it to
  55. // blow up in the wrong places.
  56. //
  57. // 05/29/97 JMI Removed ASSERT on m_pRealm->m_pAttribMap which no longer
  58. // exists.
  59. //
  60. // 06/10/97 BRH Increased the rocket arming time from 200ms to 500ms to
  61. // avoid killing yourself when you are moving & shooting.
  62. //
  63. // 06/11/97 BRH Added shooter ID passing to the explosion that is created.
  64. //
  65. // 06/12/97 BRH Added shooter ID to the call to Setup for the explosion.
  66. //
  67. // 06/18/97 BRH Changed over to using GetRandom()
  68. //
  69. // 06/25/97 BRH Added use of base class 2D shadow on the ground, but loaded
  70. // a smaller shadow resource.
  71. //
  72. // 06/29/97 BRH Added an IsPathClear check during flight to make sure it
  73. // doesn't go through thin fences.
  74. //
  75. // 06/30/97 BRH Added CacheSample for the sound effects to the Preload.
  76. //
  77. // 06/30/97 JMI Now uses CRealm's new GetRealmWidth() and *Height()
  78. // for dimensions of realm's X/Z plane.
  79. //
  80. // 06/30/97 JMI Have to check in now, now.
  81. //
  82. // 06/30/97 JMI Now uses IsPathClear()'s bCheckExtents = false to avoid
  83. // having the edge of the realm count as a path obstacle.
  84. //
  85. // 07/01/97 BRH Added smoke trails to the heatseeker.
  86. //
  87. // 07/01/97 JMI In Update(), when the weapon explodes, it didn't set dNewX
  88. // and dNewZ but still created smoke at this unitialized
  89. // position. Fixed.
  90. //
  91. // 07/04/97 BRH Cut down the trail time to live to make the trails shorter.
  92. //
  93. // 07/08/97 BRH Adjusted the position of the smoke trail.
  94. //
  95. // 07/09/97 JMI Now uses m_pRealm->Make2dResPath() to get the fullpath
  96. // for 2D image components.
  97. //
  98. // 07/09/97 JMI Changed Preload() to take a pointer to the calling realm
  99. // as a parameter.
  100. //
  101. // 07/16/97 BRH Retuned, or untuned the hotspot for the smoke trails.
  102. // Now that the hotspot for the smoke is correct, the
  103. // smoke does not need to be adjusted here.
  104. //
  105. // 07/18/97 JMI Got rid of bogus immitation PlaySample functions.
  106. // Now there is one PlaySample() function. Also, you now
  107. // MUST specify a category and you don't have to specify a
  108. // SoundInstance ptr to specify a volume.
  109. //
  110. // 08/08/97 BRH Changed the arming so that the missle won't arm until
  111. // it stops colliding with the shooter's smash. Also, added
  112. // a special case so that missiles shot by Sentry guns won't
  113. // blow up other Sentry guns.
  114. //
  115. // 08/12/97 BRH Changed collision bits to exclude any object that is
  116. // ducking (which should only be the main dude when he
  117. // is ducking down).
  118. //
  119. // 08/14/97 BRH Added SetCollideBits function so that the the collision
  120. // bits can be set differently for the Dude and Doofus.
  121. // The bits for the collision will bet set to the
  122. // default values for the missile, but then can be changed
  123. // by calling the SetCollideBits function.
  124. //
  125. // 08/15/97 BRH Made the smash radius larger.
  126. //
  127. // 08/17/97 BRH Added looping thrust sound like the rocket.
  128. //
  129. // 08/17/97 JMI Changed m_pthingParent to m_idParent.
  130. //
  131. // 08/23/97 JMI Changed ms_u32SeekExcludeBits to include CSmash::AlmostDead
  132. // so the heatseeker wouldn't seek writhers.
  133. //
  134. // 08/24/97 BRH Added seek bits that can be changed by the shooter so that
  135. // hostiles can set heatseekers that don't seek other enemies
  136. // Also set heatseeker back to previous position before
  137. // exploding so that it doesn't always hit the target from
  138. // behind and blow him forward.
  139. //
  140. // 08/26/97 BRH Fixed problem with brackets that always set the missile
  141. // back a position.
  142. //
  143. // 08/27/97 JMI No longer sets the smash radius to m_sCurRadius during
  144. // Render().
  145. //
  146. ////////////////////////////////////////////////////////////////////////////////
  147. #define HEATSEEKER_CPP
  148. #include "RSPiX.h"
  149. #include <math.h>
  150. #include "heatseeker.h"
  151. #include "dude.h"
  152. #include "explode.h"
  153. #include "fire.h"
  154. #include "SampleMaster.h"
  155. ////////////////////////////////////////////////////////////////////////////////
  156. // Macros/types/etc.
  157. ////////////////////////////////////////////////////////////////////////////////
  158. #define SMALL_SHADOW_FILE "smallshadow.img"
  159. ////////////////////////////////////////////////////////////////////////////////
  160. // Variables/data
  161. ////////////////////////////////////////////////////////////////////////////////
  162. // These are default values -- actually values are set using the editor!
  163. double CHeatseeker::ms_dAccUser = 250.0; // Acceleration due to user
  164. double CHeatseeker::ms_dMaxVelFore = 250.0; // Maximum forward velocity
  165. double CHeatseeker::ms_dMaxVelBack = -250.0; // Maximum backward velocity
  166. double CHeatseeker::ms_dCloseDistance = 30.0; // Close enough to hit CDude
  167. double CHeatseeker::ms_dLineCheckRate = 15.0; // Pixel distance for line checking
  168. long CHeatseeker::ms_lArmingTime = 500; // Time before weapon arms.
  169. long CHeatseeker::ms_lSeekRadius = 150; // Radius of heatseeking circle
  170. short CHeatseeker::ms_sOffScreenDist = 200; // Go off screen this far before blowing up
  171. short CHeatseeker::ms_sAngularVelocity = 120; // Degrees per second
  172. // Set the collision bits
  173. U32 CHeatseeker::ms_u32SeekIncludeBits = CSmash::Character | CSmash::Fire;
  174. U32 CHeatseeker::ms_u32SeekDontcareBits = CSmash::Good | CSmash::Bad;
  175. U32 CHeatseeker::ms_u32SeekExcludeBits = CSmash::Ducking | CSmash::AlmostDead;
  176. U32 CHeatseeker::ms_u32CollideIncludeBits = CSmash::Character | CSmash::Misc | CSmash::Barrel | CSmash::Fire;
  177. U32 CHeatseeker::ms_u32CollideDontcareBits = CSmash::Good | CSmash::Bad;
  178. U32 CHeatseeker::ms_u32CollideExcludeBits = CSmash::Ducking; // Miss if they are ducking
  179. long CHeatseeker::ms_lSmokeTrailInterval = 10; // MS between smoke releases
  180. long CHeatseeker::ms_lSmokeTimeToLive = 1000; // MS for smoke to stay around.
  181. // Let this auto-init to 0
  182. short CHeatseeker::ms_sFileCount;
  183. /// Rocket Animation Files
  184. static char* ms_apszResNames[] =
  185. {
  186. "3d/Gmissile.sop",
  187. "3d/Gmissile.mesh",
  188. "3d/Gmissile.tex",
  189. "3d/Gmissile.hot",
  190. "3d/Gmissile.bounds",
  191. "3d/Gmissile.floor",
  192. NULL,
  193. NULL
  194. };
  195. ////////////////////////////////////////////////////////////////////////////////
  196. // Load object (should call base class version!)
  197. ////////////////////////////////////////////////////////////////////////////////
  198. short CHeatseeker::Load( // Returns 0 if successfull, non-zero otherwise
  199. RFile* pFile, // In: File to load from
  200. bool bEditMode, // In: True for edit mode, false otherwise
  201. short sFileCount, // In: File count (unique per file, never 0)
  202. ULONG ulFileVersion) // In: Version of file format to load.
  203. {
  204. short sResult = CWeapon::Load(pFile, bEditMode, sFileCount, ulFileVersion);
  205. if (sResult == SUCCESS)
  206. {
  207. // Load common data just once per file (not with each object)
  208. if (ms_sFileCount != sFileCount)
  209. {
  210. ms_sFileCount = sFileCount;
  211. // Load static data
  212. switch (ulFileVersion)
  213. {
  214. default:
  215. case 1:
  216. pFile->Read(&ms_dAccUser);
  217. pFile->Read(&ms_dMaxVelFore);
  218. pFile->Read(&ms_dMaxVelBack);
  219. pFile->Read(&ms_dCloseDistance);
  220. break;
  221. }
  222. }
  223. // Load object data
  224. switch (ulFileVersion)
  225. {
  226. default:
  227. case 1:
  228. break;
  229. }
  230. // Make sure there were no file errors
  231. if (!pFile->Error())
  232. {
  233. // Get resources
  234. sResult = GetResources();
  235. }
  236. else
  237. {
  238. sResult = -1;
  239. TRACE("CHeatseeker::Load(): Error reading from file!\n");
  240. }
  241. }
  242. return sResult;
  243. }
  244. ////////////////////////////////////////////////////////////////////////////////
  245. // Save object (should call base class version!)
  246. ////////////////////////////////////////////////////////////////////////////////
  247. short CHeatseeker::Save( // Returns 0 if successfull, non-zero otherwise
  248. RFile* pFile, // In: File to save to
  249. short sFileCount) // In: File count (unique per file, never 0)
  250. {
  251. // In most cases, the base class Save() should be called. In this case it
  252. // isn't because the base class doesn't have a Save()!
  253. // Save common data just once per file (not with each object)
  254. if (ms_sFileCount != sFileCount)
  255. {
  256. ms_sFileCount = sFileCount;
  257. // Save static data
  258. pFile->Write(&ms_dAccUser);
  259. pFile->Write(&ms_dMaxVelFore);
  260. pFile->Write(&ms_dMaxVelBack);
  261. pFile->Write(&ms_dCloseDistance);
  262. }
  263. // Save object data
  264. return 0;
  265. }
  266. ////////////////////////////////////////////////////////////////////////////////
  267. // Update object
  268. ////////////////////////////////////////////////////////////////////////////////
  269. void CHeatseeker::Update(void)
  270. {
  271. USHORT usAttrib;
  272. short sHeight;
  273. double dNewX;
  274. double dNewZ;
  275. double dPrevX;
  276. double dPrevZ;
  277. if (!m_sSuspend)
  278. {
  279. // Get new time
  280. long lThisTime = m_pRealm->m_time.GetGameTime();
  281. // Calculate elapsed time in seconds
  282. double dSeconds = (double)(lThisTime - m_lPrevTime) / 1000.0;
  283. ProcessMessages();
  284. // Check the current state
  285. switch (m_eState)
  286. {
  287. case CWeapon::State_Hide:
  288. case CWeapon::State_Idle:
  289. break;
  290. case CWeapon::State_Fire:
  291. PlaySample(
  292. g_smidRocketFire, // In: Sample to play
  293. SampleMaster::Weapon, // In: User volume adjustment category
  294. DistanceToVolume(m_dX, m_dY, m_dZ, LaunchSndHalfLife), // In: distance to dude
  295. &m_siThrust, // Out: Handle for adjusting sound volume
  296. NULL, // Out: Sample duration
  297. 2841, // In: Where to loop back to in ms
  298. 3090, // In: Where to loop fro in ms
  299. false ); // In: Initial Sound Volume (0 - 255)
  300. m_lTimer = lThisTime + ms_lArmingTime;
  301. m_eState = State_Chase;
  302. break;
  303. //-----------------------------------------------------------------------
  304. // Chase
  305. //-----------------------------------------------------------------------
  306. case CWeapon::State_Chase:
  307. {
  308. // Accelerate toward the target and check for proximity
  309. // and obstacles
  310. // Accelerate doofus up to max velocity
  311. m_dHorizVel += ms_dAccUser * dSeconds;
  312. // Limit to maximum velocity
  313. if (m_dHorizVel > ms_dMaxVelFore)
  314. m_dHorizVel = ms_dMaxVelFore;
  315. else if (m_dHorizVel < ms_dMaxVelBack)
  316. m_dHorizVel = ms_dMaxVelBack;
  317. // Adjust position based on velocity
  318. dNewX = m_dX + rspCos(m_dRot) * (m_dHorizVel * dSeconds);
  319. dNewZ = m_dZ - rspSin(m_dRot) * (m_dHorizVel * dSeconds);
  320. // Check for obstacles
  321. sHeight = m_pRealm->GetHeight((short) dNewX, (short) dNewZ);
  322. usAttrib = m_pRealm->GetFloorAttribute((short) dNewX, (short) dNewZ);
  323. short sRealmH = m_pRealm->GetRealmHeight();
  324. short sRealmW = m_pRealm->GetRealmWidth();
  325. // Once a bit off screen, it should start turning back towards
  326. // the center of the hood.
  327. if (m_dZ > ms_sOffScreenDist + sRealmH ||
  328. m_dZ < -ms_sOffScreenDist ||
  329. m_dX > ms_sOffScreenDist + sRealmW ||
  330. m_dX < -ms_sOffScreenDist)
  331. {
  332. short sTargetAngle = FindAngleTo(sRealmW / 2,
  333. sRealmH / 2);
  334. short sAngleCCL = rspMod360(sTargetAngle - m_dRot);
  335. short sAngleCL = rspMod360((360 - sTargetAngle) + m_dRot);
  336. short sAngleDistance = MIN(sAngleCCL, sAngleCL);
  337. double dAngleChange = MIN((double) sAngleDistance, ms_sAngularVelocity * dSeconds);
  338. if (sAngleCCL < sAngleCL)
  339. m_dRot = rspMod360(m_dRot + dAngleChange);
  340. else
  341. m_dRot = rspMod360(m_dRot - dAngleChange);
  342. }
  343. if (sHeight > m_dY ||
  344. !m_pRealm->IsPathClear( // Returns true, if the entire path is clear.
  345. // Returns false, if only a portion of the path is clear.
  346. // (see *psX, *psY, *psZ).
  347. (short) m_dX, // In: Starting X.
  348. (short) m_dY, // In: Starting Y.
  349. (short) m_dZ, // In: Starting Z.
  350. 3.0, // In: Rate at which to scan ('crawl') path in pixels per
  351. // iteration.
  352. // NOTE: Values less than 1.0 are inefficient.
  353. // NOTE: We scan terrain using GetHeight()
  354. // at only one pixel.
  355. // NOTE: We could change this to a speed in pixels per second
  356. // where we'd assume a certain frame rate.
  357. (short) dNewX, // In: Destination X.
  358. (short) dNewZ, // In: Destination Z.
  359. 0, // In: Max traverser can step up.
  360. NULL, // Out: If not NULL, last clear point on path.
  361. NULL, // Out: If not NULL, last clear point on path.
  362. NULL, // Out: If not NULL, last clear point on path.
  363. false) ) // In: If true, will consider the edge of the realm a path
  364. // inhibitor. If false, reaching the edge of the realm
  365. // indicates a clear path.
  366. {
  367. // Blow Up
  368. m_eState = CWeapon::State_Explode;
  369. // Note that these need to be set even if we explode; otherwise, they
  370. // never get initialized and totally hosened values are sent to
  371. // pSmoke-Setup() which makes Alpha unhappy.
  372. dPrevX = m_dX;
  373. dPrevZ = m_dZ;
  374. }
  375. else
  376. {
  377. dPrevX = m_dX;
  378. dPrevZ = m_dZ;
  379. m_dX = dNewX;
  380. m_dZ = dNewZ;
  381. }
  382. // Check for collisions with other characters if
  383. // the weapon is armed, else see if it is time to arm
  384. // the weapon yet.
  385. if (m_bArmed)
  386. {
  387. CSmash* pSmashed = NULL;
  388. // Change this to quick check closest
  389. if (m_pRealm->m_smashatorium.QuickCheckClosest(&m_smashSeeker,
  390. m_u32SeekBitsInclude,
  391. m_u32SeekBitsDontCare,
  392. m_u32SeekBitsExclude, &pSmashed))
  393. // Find the angle to the closest thing
  394. {
  395. if (m_pRealm->IsPathClear((short) m_dX, (short) m_dY, (short) m_dZ, ms_dLineCheckRate,
  396. (short) pSmashed->m_sphere.sphere.X, (short) pSmashed->m_sphere.sphere.Z) )
  397. {
  398. short sTargetAngle = FindAngleTo(pSmashed->m_sphere.sphere.X, pSmashed->m_sphere.sphere.Z);
  399. short sAngleCCL = rspMod360(sTargetAngle - m_dRot);
  400. short sAngleCL = rspMod360((360 - sTargetAngle) + m_dRot);
  401. short sAngleDistance = MIN(sAngleCCL, sAngleCL);
  402. double dAngleChange = MIN((double) sAngleDistance, ms_sAngularVelocity * dSeconds);
  403. if (sAngleCCL < sAngleCL)
  404. m_dRot = rspMod360(m_dRot + dAngleChange);
  405. else
  406. m_dRot = rspMod360(m_dRot - dAngleChange);
  407. }
  408. }
  409. m_pRealm->m_smashatorium.QuickCheckReset(
  410. &m_smash,
  411. m_u32CollideBitsInclude,
  412. m_u32CollideBitsDontCare,
  413. m_u32CollideBitsExclude & ~CSmash::Ducking);
  414. while (m_pRealm->m_smashatorium.QuickCheckNext(&pSmashed))
  415. {
  416. ASSERT(pSmashed->m_pThing);
  417. const bool bIsPlayer = (pSmashed->m_pThing->GetClassID() == CDudeID);
  418. // we need to check ducking collisions unconditionally so we can unlock an achievement, but then we carry on if it should have missed.
  419. if ((m_u32CollideBitsExclude & CSmash::Ducking) && (pSmashed->m_bits & CSmash::Ducking))
  420. {
  421. if (bIsPlayer)
  422. UnlockAchievement(ACHIEVEMENT_DUCK_UNDER_ROCKET);
  423. continue; // keep going.
  424. }
  425. if (bIsPlayer)
  426. UnlockAchievement(ACHIEVEMENT_ROCKET_TO_THE_FACE);
  427. CThing* pShooter;
  428. m_pRealm->m_idbank.GetThingByID(&pShooter, m_u16ShooterID);
  429. if (pShooter)
  430. {
  431. // If a Sentry gun shot this weapon, and it hit another Sentry gun, then
  432. // ignore the collision.
  433. if (!(pSmashed->m_pThing->GetClassID() == CSentryID && pShooter->GetClassID() == CSentryID))
  434. {
  435. m_eState = CWeapon::State_Explode;
  436. // Go back to previous spot where explosion should be created
  437. m_dX = dPrevX;
  438. m_dZ = dPrevZ;
  439. }
  440. }
  441. // Can't determine the shooter,but we did collide, so blow up.
  442. else
  443. {
  444. m_eState = CWeapon::State_Explode;
  445. // Go back to last position before exploding so it doesn't hit behind
  446. // the target.
  447. m_dX = dPrevX;
  448. m_dZ = dPrevZ;
  449. }
  450. }
  451. }
  452. else
  453. {
  454. // Check for collision with self and if no collision, then arm
  455. CThing* pShooter = NULL;
  456. m_pRealm->m_idbank.GetThingByID(&pShooter, m_u16ShooterID);
  457. // If the shooter is valid, then arm when it clears the shooter
  458. if (pShooter)
  459. {
  460. CSmash* pSmashed = pShooter->GetSmash();
  461. if (pSmashed)
  462. {
  463. pSmashed = (CSmash*) &(((CThing3d*) pShooter)->m_smash);
  464. if (!(m_pRealm->m_smashatorium.QuickCheck(&m_smash, pSmashed)))
  465. m_bArmed = true;
  466. }
  467. else
  468. {
  469. if (lThisTime > m_lTimer)
  470. m_bArmed = true;
  471. }
  472. }
  473. // else do it the old fashioned way, so at least it will arm
  474. else
  475. {
  476. if (lThisTime > m_lTimer)
  477. m_bArmed = true;
  478. }
  479. }
  480. // See if its time to create a new puff of smoke
  481. if (lThisTime > m_lSmokeTimer)
  482. {
  483. m_lSmokeTimer = lThisTime + ms_lSmokeTrailInterval;
  484. CFire* pSmoke = NULL;
  485. if (CThing::Construct(CThing::CFireID, m_pRealm, (CThing**) &pSmoke) == 0)
  486. {
  487. // This needs to be fixed by calculating the position of the back end of
  488. // the rocket in 3D based on the rotation.
  489. pSmoke->Setup(dPrevX, m_dY, dPrevZ, ms_lSmokeTimeToLive, true, CFire::SmallSmoke);
  490. pSmoke->m_u16ShooterID = m_u16ShooterID;
  491. }
  492. }
  493. // Update sound position
  494. SetInstanceVolume(m_siThrust, DistanceToVolume(m_dX, m_dY, m_dZ, LaunchSndHalfLife) );
  495. break;
  496. }
  497. //-----------------------------------------------------------------------
  498. // Explode
  499. //-----------------------------------------------------------------------
  500. case CWeapon::State_Explode:
  501. // Start an explosion object and then kill rocket
  502. // object
  503. CExplode* pExplosion;
  504. if (CThing::Construct(CThing::CExplodeID, m_pRealm, (CThing**) &pExplosion) == 0)
  505. {
  506. pExplosion->Setup(m_dX, MAX(m_dY-30, 0.0), m_dZ, m_u16ShooterID);
  507. PlaySample(
  508. g_smidRocketExplode,
  509. SampleMaster::Destruction,
  510. DistanceToVolume(m_dX, m_dY, m_dZ, ExplosionSndHalfLife) ); // In: Initial Sound Volume (0 - 255)
  511. }
  512. short a;
  513. CFire* pSmoke;
  514. for (a = 0; a < 8; a++)
  515. {
  516. if (CThing::Construct(CThing::CFireID, m_pRealm, (CThing**) &pSmoke) == 0)
  517. {
  518. pSmoke->Setup(m_dX - 4 + GetRandom() % 9, m_dY-20, m_dZ - 4 + GetRandom() % 9, 4000, true, CFire::Smoke);
  519. pSmoke->m_u16ShooterID = m_u16ShooterID;
  520. }
  521. }
  522. delete this;
  523. return;
  524. break;
  525. case CWeapon::State_Deleted:
  526. delete this;
  527. return;
  528. break;
  529. }
  530. // Update sphere.
  531. m_smash.m_sphere.sphere.X = m_dX;
  532. m_smash.m_sphere.sphere.Y = m_dY;
  533. m_smash.m_sphere.sphere.Z = m_dZ;
  534. m_smash.m_sphere.sphere.lRadius = 2 * m_sprite.m_sRadius;
  535. m_smashSeeker.m_sphere.sphere.X = m_dX + (rspCos(m_dRot) * ms_lSeekRadius);
  536. m_smashSeeker.m_sphere.sphere.Y = m_dY;
  537. m_smashSeeker.m_sphere.sphere.Z = m_dZ - (rspSin(m_dRot) * ms_lSeekRadius);
  538. m_smashSeeker.m_sphere.sphere.lRadius = ms_lSeekRadius;
  539. // Update the smash.
  540. m_pRealm->m_smashatorium.Update(&m_smash);
  541. // Save time for next time
  542. m_lPrevTime = lThisTime;
  543. }
  544. }
  545. ////////////////////////////////////////////////////////////////////////////////
  546. // Render object
  547. ////////////////////////////////////////////////////////////////////////////////
  548. void CHeatseeker::Render(void)
  549. {
  550. long lThisTime = m_pRealm->m_time.GetGameTime();
  551. m_sprite.m_pmesh = (RMesh*) m_anim.m_pmeshes->GetAtTime(lThisTime);
  552. m_sprite.m_psop = (RSop*) m_anim.m_psops->GetAtTime(lThisTime);
  553. m_sprite.m_ptex = (RTexture*) m_anim.m_ptextures->GetAtTime(lThisTime);
  554. m_sprite.m_psphere = (RP3d*) m_anim.m_pbounds->GetAtTime(lThisTime);
  555. // Reset rotation so it is not cumulative
  556. m_trans.Make1();
  557. // Set its pointing direction
  558. m_trans.Ry(rspMod360(m_dRot));
  559. // Eventually this should be channel driven also
  560. // m_sprite.m_sRadius = m_sCurRadius;
  561. if (m_eState == State_Hide)
  562. m_sprite.m_sInFlags = CSprite::InHidden;
  563. else
  564. m_sprite.m_sInFlags = 0;
  565. // If we're not a child of someone else...
  566. if (m_idParent == CIdBank::IdNil)
  567. {
  568. // Map from 3d to 2d coords
  569. Map3Dto2D((short) m_dX, (short) m_dY, (short) m_dZ, &m_sprite.m_sX2, &m_sprite.m_sY2);
  570. // Priority is based on Z.
  571. m_sprite.m_sPriority = m_dZ;
  572. // Layer should be based on info we get from the attribute map
  573. m_sprite.m_sLayer = CRealm::GetLayerViaAttrib(m_pRealm->GetLayer((short) m_dX, (short) m_dZ));
  574. m_sprite.m_ptrans = &m_trans;
  575. // Update sprite in scene
  576. m_pRealm->m_scene.UpdateSprite(&m_sprite);
  577. // Draw the 2D shadow sprite
  578. CWeapon::Render();
  579. #if 0
  580. // FEEDBACK.
  581. // Create a line sprite.
  582. CSpriteLine2d* psl2d = new CSpriteLine2d;
  583. if (psl2d != NULL)
  584. {
  585. Map3Dto2D(
  586. m_dX,
  587. m_dY,
  588. m_dZ,
  589. &(psl2d->m_sX2),
  590. &(psl2d->m_sY2) );
  591. Map3Dto2D(
  592. m_smashSeeker.m_sphere.sphere.X,
  593. m_smashSeeker.m_sphere.sphere.Y,
  594. m_smashSeeker.m_sphere.sphere.Z,
  595. &(psl2d->m_sX2End),
  596. &(psl2d->m_sY2End) );
  597. psl2d->m_sPriority = m_smashSeeker.m_sphere.sphere.Z;
  598. psl2d->m_sLayer = m_pRealm->GetLayerViaAttrib(m_pRealm->GetLayer(m_smashSeeker.m_sphere.sphere.X, m_smashSeeker.m_sphere.sphere.Z));
  599. psl2d->m_u8Color = 249;
  600. // Destroy when done.
  601. psl2d->m_sInFlags = CSprite::InDeleteOnRender;
  602. // Put 'er there.
  603. m_pRealm->m_scene.UpdateSprite(psl2d);
  604. }
  605. #endif
  606. }
  607. else
  608. {
  609. // m_idParent is setting our transform relative to its position
  610. // and we are drawn by the scene with the parent.
  611. }
  612. }
  613. ////////////////////////////////////////////////////////////////////////////////
  614. // Setup
  615. ////////////////////////////////////////////////////////////////////////////////
  616. short CHeatseeker::Setup( // Returns 0 if successfull, non-zero otherwise
  617. short sX, // In: New x coord
  618. short sY, // In: New y coord
  619. short sZ) // In: New z coord
  620. {
  621. short sResult = 0;
  622. // Use specified position
  623. m_dX = (double)sX;
  624. m_dY = (double)sY;
  625. m_dZ = (double)sZ;
  626. m_dHorizVel = 0.0;
  627. // Load resources
  628. sResult = GetResources();
  629. // Enable the 2D shadow sprite
  630. PrepareShadow();
  631. m_bArmed = false;
  632. m_smash.m_sphere.sphere.X = m_dX;
  633. m_smash.m_sphere.sphere.Y = m_dY;
  634. m_smash.m_sphere.sphere.Z = m_dZ;
  635. m_smash.m_bits = CSmash::Projectile;
  636. m_smash.m_pThing = this;
  637. m_smashSeeker.m_sphere.sphere.X = m_dX + (rspCos(m_dRot) * ms_lSeekRadius);
  638. m_smashSeeker.m_sphere.sphere.Y = m_dY;
  639. m_smashSeeker.m_sphere.sphere.Z = m_dZ - (rspSin(m_dRot) * ms_lSeekRadius);
  640. m_smashSeeker.m_bits = 0;
  641. m_smashSeeker.m_pThing = this;
  642. m_u32CollideBitsInclude = ms_u32CollideIncludeBits;
  643. m_u32CollideBitsDontCare = ms_u32CollideDontcareBits;
  644. m_u32CollideBitsExclude = ms_u32CollideExcludeBits;
  645. m_u32SeekBitsInclude = ms_u32SeekIncludeBits;
  646. m_u32SeekBitsDontCare = ms_u32SeekDontcareBits;
  647. m_u32SeekBitsExclude = ms_u32SeekExcludeBits;
  648. m_sCurRadius = 10 * m_pRealm->m_scene.m_dScale3d;
  649. return sResult;
  650. }
  651. ////////////////////////////////////////////////////////////////////////////////
  652. // Get all required resources
  653. ////////////////////////////////////////////////////////////////////////////////
  654. short CHeatseeker::GetResources(void) // Returns 0 if successfull, non-zero otherwise
  655. {
  656. short sResult = 0;
  657. sResult = m_anim.Get(ms_apszResNames);
  658. if (sResult == 0)
  659. {
  660. sResult = rspGetResource(&g_resmgrGame, m_pRealm->Make2dResPath(SMALL_SHADOW_FILE), &(m_spriteShadow.m_pImage), RFile::LittleEndian);
  661. if (sResult == 0)
  662. {
  663. // add more gets
  664. }
  665. else
  666. {
  667. TRACE("CGrenade::GetResources - Failed to open 2D shadow image\n");
  668. }
  669. }
  670. else
  671. {
  672. TRACE("CHeatseeker::GetResources - Failed to open 3D animation for rocket\n");
  673. }
  674. return sResult;
  675. }
  676. ////////////////////////////////////////////////////////////////////////////////
  677. // Free all resources
  678. ////////////////////////////////////////////////////////////////////////////////
  679. short CHeatseeker::FreeResources(void) // Returns 0 if successfull, non-zero otherwise
  680. {
  681. m_anim.Release();
  682. return 0;
  683. }
  684. ////////////////////////////////////////////////////////////////////////////////
  685. // Preload - basically trick the resource manager into caching resources
  686. // for this object so there won't be a delay the first time it is
  687. // created.
  688. ////////////////////////////////////////////////////////////////////////////////
  689. short CHeatseeker::Preload(
  690. CRealm* prealm) // In: Calling realm.
  691. {
  692. CAnim3D anim;
  693. RImage* pimage;
  694. short sResult = anim.Get(ms_apszResNames);
  695. anim.Release();
  696. rspGetResource(&g_resmgrGame, prealm->Make2dResPath(SMALL_SHADOW_FILE), &pimage, RFile::LittleEndian);
  697. rspReleaseResource(&g_resmgrGame, &pimage);
  698. CacheSample(g_smidRocketFire);
  699. CacheSample(g_smidRocketExplode);
  700. return sResult;
  701. }
  702. ////////////////////////////////////////////////////////////////////////////////
  703. // ProcessMessages
  704. ////////////////////////////////////////////////////////////////////////////////
  705. void CHeatseeker::ProcessMessages(void)
  706. {
  707. GameMessage msg;
  708. if (m_MessageQueue.DeQ(&msg) == true)
  709. {
  710. switch(msg.msg_Generic.eType)
  711. {
  712. case typeObjectDelete:
  713. m_MessageQueue.Empty();
  714. m_eState = State_Deleted;
  715. // This object is deleted later.
  716. break;
  717. }
  718. }
  719. // Dump the rest of the messages
  720. m_MessageQueue.Empty();
  721. return;
  722. }
  723. ////////////////////////////////////////////////////////////////////////////////
  724. // EOF
  725. ////////////////////////////////////////////////////////////////////////////////