DumbProjectileBehavior.cpp 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772
  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: DumbProjectileBehavior.cpp
  24. // Author: Steven Johnson, July 2002
  25. // Desc:
  26. #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
  27. #include "Common/BezierSegment.h"
  28. #include "Common/GameCommon.h"
  29. #include "Common/GameState.h"
  30. #include "Common/ThingTemplate.h"
  31. #include "Common/RandomValue.h"
  32. #include "Common/Xfer.h"
  33. #include "GameClient/Drawable.h"
  34. #include "GameClient/FXList.h"
  35. #include "GameLogic/GameLogic.h"
  36. #include "GameLogic/Object.h"
  37. #include "GameLogic/PartitionManager.h"
  38. #include "GameLogic/Module/ContainModule.h"
  39. #include "GameLogic/Module/DumbProjectileBehavior.h"
  40. #include "GameLogic/Module/MissileAIUpdate.h"
  41. #include "GameLogic/Module/PhysicsUpdate.h"
  42. #include "GameLogic/Weapon.h"
  43. #ifdef _INTERNAL
  44. // for occasional debugging...
  45. //#pragma optimize("", off)
  46. //#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
  47. #endif
  48. //-----------------------------------------------------------------------------
  49. //-----------------------------------------------------------------------------
  50. //-----------------------------------------------------------------------------
  51. //-----------------------------------------------------------------------------
  52. const Int DEFAULT_MAX_LIFESPAN = 10 * LOGICFRAMES_PER_SECOND;
  53. //-----------------------------------------------------------------------------
  54. DumbProjectileBehaviorModuleData::DumbProjectileBehaviorModuleData() :
  55. m_maxLifespan(DEFAULT_MAX_LIFESPAN),
  56. m_detonateCallsKill(FALSE),
  57. m_orientToFlightPath(TRUE),
  58. m_tumbleRandomly(FALSE),
  59. m_firstHeight(0.0f),
  60. m_secondHeight(0.0f),
  61. m_firstPercentIndent(0.0f),
  62. m_secondPercentIndent(0.0f),
  63. m_garrisonHitKillCount(0),
  64. m_garrisonHitKillFX(NULL),
  65. m_flightPathAdjustDistPerFrame(0.0f)
  66. {
  67. }
  68. //-----------------------------------------------------------------------------
  69. void DumbProjectileBehaviorModuleData::buildFieldParse(MultiIniFieldParse& p)
  70. {
  71. UpdateModuleData::buildFieldParse(p);
  72. static const FieldParse dataFieldParse[] =
  73. {
  74. { "MaxLifespan", INI::parseDurationUnsignedInt, NULL, offsetof( DumbProjectileBehaviorModuleData, m_maxLifespan ) },
  75. { "TumbleRandomly", INI::parseBool, NULL, offsetof( DumbProjectileBehaviorModuleData, m_tumbleRandomly ) },
  76. { "DetonateCallsKill", INI::parseBool, NULL, offsetof( DumbProjectileBehaviorModuleData, m_detonateCallsKill ) },
  77. { "OrientToFlightPath", INI::parseBool, NULL, offsetof( DumbProjectileBehaviorModuleData, m_orientToFlightPath ) },
  78. { "FirstHeight", INI::parseReal, NULL, offsetof( DumbProjectileBehaviorModuleData, m_firstHeight ) },
  79. { "SecondHeight", INI::parseReal, NULL, offsetof( DumbProjectileBehaviorModuleData, m_secondHeight ) },
  80. { "FirstPercentIndent", INI::parsePercentToReal, NULL, offsetof( DumbProjectileBehaviorModuleData, m_firstPercentIndent ) },
  81. { "SecondPercentIndent", INI::parsePercentToReal, NULL, offsetof( DumbProjectileBehaviorModuleData, m_secondPercentIndent ) },
  82. { "GarrisonHitKillRequiredKindOf", KindOfMaskType::parseFromINI, NULL, offsetof( DumbProjectileBehaviorModuleData, m_garrisonHitKillKindof ) },
  83. { "GarrisonHitKillForbiddenKindOf", KindOfMaskType::parseFromINI, NULL, offsetof( DumbProjectileBehaviorModuleData, m_garrisonHitKillKindofNot ) },
  84. { "GarrisonHitKillCount", INI::parseUnsignedInt, NULL, offsetof( DumbProjectileBehaviorModuleData, m_garrisonHitKillCount ) },
  85. { "GarrisonHitKillFX", INI::parseFXList, NULL, offsetof( DumbProjectileBehaviorModuleData, m_garrisonHitKillFX ) },
  86. { "FlightPathAdjustDistPerSecond", INI::parseVelocityReal, NULL, offsetof( DumbProjectileBehaviorModuleData, m_flightPathAdjustDistPerFrame ) },
  87. { 0, 0, 0, 0 }
  88. };
  89. p.add(dataFieldParse);
  90. }
  91. //-------------------------------------------------------------------------------------------------
  92. //-------------------------------------------------------------------------------------------------
  93. //-------------------------------------------------------------------------------------------------
  94. //-------------------------------------------------------------------------------------------------
  95. DumbProjectileBehavior::DumbProjectileBehavior( Thing *thing, const ModuleData* moduleData ) : UpdateModule( thing, moduleData )
  96. {
  97. m_launcherID = INVALID_ID;
  98. m_victimID = INVALID_ID;
  99. m_detonationWeaponTmpl = NULL;
  100. m_lifespanFrame = 0;
  101. m_flightPath.clear();
  102. m_flightPathSegments = 0;
  103. m_flightPathSpeed = 0;
  104. m_flightPathStart.zero();
  105. m_flightPathEnd.zero();
  106. m_currentFlightPathStep = 0;
  107. m_extraBonusFlags = 0;
  108. }
  109. //-------------------------------------------------------------------------------------------------
  110. DumbProjectileBehavior::~DumbProjectileBehavior()
  111. {
  112. }
  113. //-------------------------------------------------------------------------------------------------
  114. inline Bool within(Real min, Real val, Real max)
  115. {
  116. return min <= val && val <= max;
  117. }
  118. //-------------------------------------------------------------------------------------------------
  119. inline Bool notWithin(Real min, Real val, Real max)
  120. {
  121. return val < min || val > max;
  122. }
  123. #ifdef NOT_IN_USE
  124. #define NO_ONLY_RETURN_CLIPPED_PITCHES
  125. //-------------------------------------------------------------------------------------------------
  126. static Bool calcTrajectory(
  127. const Coord3D& start, // in: where the projectile starts
  128. const Coord3D& end, // in: where the projectile wants to end up
  129. Real velocity, // in: the initial speed of the projectile
  130. Real minPitch, // in: min pitch (-PI/2)
  131. Real maxPitch, // in: max pitch (-PI/2)
  132. Bool preferShortPitch, // in: prefer the shorter or longer path?
  133. Real& angle, // out: the angle to aim
  134. Real& pitch // out: the pitch to aim for
  135. )
  136. {
  137. Bool exactTarget = false;
  138. angle = 0.0f;
  139. pitch = 0.0f;
  140. if (velocity <= 0.0f)
  141. {
  142. DEBUG_CRASH(("cant get there from here (1)"));
  143. return false;
  144. }
  145. Real dx = end.x - start.x;
  146. Real dy = end.y - start.y;
  147. Real dz = end.z - start.z;
  148. // calculating the angle is trivial.
  149. angle = atan2(dy, dx);
  150. // calculating the pitch requires a bit more effort.
  151. Real horizDistSqr = sqr(dx) + sqr(dy);
  152. Real horizDist = sqrt(horizDistSqr);
  153. // calc the two possible pitches that will cover the given horizontal range.
  154. // (this is actually only true if dz==0, but is a good first guess)
  155. Real gravity = fabs(TheGlobalData->m_gravity);
  156. Real gravityTwoDZ = gravity * 2.0f * dz;
  157. // let's start by aiming directly for it. we know this isn't right (unless gravity
  158. // is zero, which it's not) but is a good starting point...
  159. Real theta = atan2(dz, horizDist);
  160. // if the angle isn't pretty shallow, we can get a better initial guess by using
  161. // the code below...
  162. const Real SHALLOW_ANGLE = 0.5f * PI / 180.0f;
  163. if (fabs(theta) > SHALLOW_ANGLE)
  164. {
  165. Real t = horizDist / velocity;
  166. Real vz = (dz/t + 0.5f*gravity*t);
  167. Real sineOfAngle = clamp(-1.0f, vz / velocity, 1.0f);
  168. theta = ASin(sineOfAngle)*0.5f;
  169. }
  170. /*
  171. this is, in theory, the "right" formula for dz==0, but I
  172. get better results with the stuff above:
  173. Real sineOfAngle = (gravity * horizDist) / sqr(velocity);
  174. if (sineOfAngle > 1.0f)
  175. {
  176. return false;
  177. }
  178. Real theta = ASin(sineOfAngle)*0.5f;
  179. */
  180. Real pitches[2];
  181. Real cosPitches[2];
  182. Real sinPitches[2];
  183. Real theta_min = max(minPitch, -PI/2);
  184. Real theta_max = min(maxPitch, PI/2);
  185. const Real MIN_ANGLE_DIFF = (PI/(180.0f*16.0f)); // 1/16th of a degree. yes, we need that accuracy.
  186. //Int numLoops = 0;
  187. while (theta_max > theta_min + MIN_ANGLE_DIFF)
  188. {
  189. //++numLoops;
  190. pitches[0] = theta; // shallower angle
  191. pitches[1] = (theta >= 0.0) ? (PI/2 - theta) : (-PI/2 - theta); // steeper angle
  192. DEBUG_ASSERTCRASH(pitches[0]<=PI/2&&pitches[0]>=-PI/2,("bad pitches[0] %f\n",rad2deg(pitches[0])));
  193. DEBUG_ASSERTCRASH(pitches[1]<=PI/2&&pitches[1]>=-PI/2,("bad pitches[1] %f\n",rad2deg(pitches[1])));
  194. // calc the horiz-speed & time for each.
  195. // note that time can only be negative for 90<angle<270, and since we
  196. // ruled those out above, we're gold.
  197. sinPitches[0] = Sin(pitches[0]);
  198. sinPitches[1] = Sin(pitches[1]);
  199. cosPitches[0] = Cos(pitches[0]);
  200. cosPitches[1] = Cos(pitches[1]);
  201. Real t0 = (horizDist / (velocity * cosPitches[0]));
  202. Real t1 = (horizDist / (velocity * cosPitches[1]));
  203. t0 = MAX(0,t0);
  204. t1 = MAX(0,t1);
  205. DEBUG_ASSERTCRASH(t0>=0&&t1>=0,("neg time"));
  206. Int preferred = ((t0 < t1) == (preferShortPitch)) ? 0 : 1;
  207. // ok, NOW... since dz is virtually NEVER zero, do a little approximation
  208. // to get a better guess. (solving the equations directly are hard and
  209. // it's simpler to approximate)
  210. Bool tooClose = false;
  211. Real vx, vz, root;
  212. vz = velocity*sinPitches[preferred];
  213. root = sqr(vz) - gravityTwoDZ;
  214. if (root < 0.0f)
  215. {
  216. // oops, no solution for our preferred pitch. try the other one.
  217. if (preferred == 0)
  218. tooClose = true; // if this fails for the shallow case, it's 'cuz the result is too close
  219. preferred = 1 - preferred;
  220. vz = velocity*sinPitches[preferred];
  221. root = sqr(vz) - gravityTwoDZ;
  222. if (root < 0.0f)
  223. {
  224. DEBUG_CRASH(("cant get there from here (2)"));
  225. return false;
  226. }
  227. }
  228. #ifdef ONLY_RETURN_CLIPPED_PITCHES
  229. // if we get this far, we have some plausible solution...
  230. // it actually may be off, but let's go ahead and set the result
  231. // (along with the "exact" flag) so that the caller will want to
  232. // try to use the result. (we'll probably iterate and overwrite
  233. // this result with a more precise one.)
  234. if (within(minPitch, pitches[preferred], maxPitch))
  235. {
  236. pitch = pitches[preferred];
  237. exactTarget = true;
  238. }
  239. #else
  240. // if we get this far, we have some plausible solution...
  241. // it actually may be off, but let's go ahead and set the result
  242. // (along with the "exact" flag) so that the caller will want to
  243. // try to use the result. (we'll probably iterate and overwrite
  244. // this result with a more precise one.) NOTE however that we
  245. // don't validate it's within the proper range... caller must do that!
  246. pitch = pitches[preferred];
  247. exactTarget = true;
  248. #endif
  249. vx = velocity*cosPitches[preferred];
  250. Real actualRange = (vx*(vz + sqrt(root)))/gravity;
  251. const Real CLOSE_ENOUGH_RANGE = 5.0f;
  252. if (tooClose || (actualRange < horizDist - CLOSE_ENOUGH_RANGE))
  253. {
  254. theta_min = theta;
  255. theta = (theta + theta_max) * 0.5f;
  256. }
  257. else if (actualRange > horizDist + CLOSE_ENOUGH_RANGE)
  258. {
  259. theta_max = theta;
  260. theta = (theta + theta_min) * 0.5f;
  261. }
  262. else
  263. {
  264. break;
  265. }
  266. }
  267. //DEBUG_LOG(("took %d loops to find a match\n",numLoops));
  268. if (exactTarget)
  269. return true;
  270. return false;
  271. }
  272. #endif NOT_IN_USE
  273. //-------------------------------------------------------------------------------------------------
  274. // Prepares the missile for launch via proper weapon-system channels.
  275. //-------------------------------------------------------------------------------------------------
  276. void DumbProjectileBehavior::projectileLaunchAtObjectOrPosition(
  277. const Object* victim,
  278. const Coord3D* victimPos,
  279. const Object* launcher,
  280. WeaponSlotType wslot,
  281. Int specificBarrelToUse,
  282. const WeaponTemplate* detWeap,
  283. const ParticleSystemTemplate* exhaustSysOverride
  284. )
  285. {
  286. const DumbProjectileBehaviorModuleData* d = getDumbProjectileBehaviorModuleData();
  287. DEBUG_ASSERTCRASH(specificBarrelToUse>=0, ("specificBarrelToUse must now be explicit"));
  288. m_launcherID = launcher ? launcher->getID() : INVALID_ID;
  289. m_extraBonusFlags = launcher ? launcher->getWeaponBonusCondition() : 0;
  290. m_victimID = victim ? victim->getID() : INVALID_ID;
  291. m_detonationWeaponTmpl = detWeap;
  292. m_lifespanFrame = TheGameLogic->getFrame() + d->m_maxLifespan;
  293. Object* projectile = getObject();
  294. Weapon::positionProjectileForLaunch(projectile, launcher, wslot, specificBarrelToUse);
  295. projectileFireAtObjectOrPosition( victim, victimPos, detWeap, exhaustSysOverride );
  296. }
  297. //-------------------------------------------------------------------------------------------------
  298. // The actual firing of the missile once setup. Uses a Bezier curve with points parameterized in ini
  299. //-------------------------------------------------------------------------------------------------
  300. void DumbProjectileBehavior::projectileFireAtObjectOrPosition( const Object *victim, const Coord3D *victimPos, const WeaponTemplate *detWeap, const ParticleSystemTemplate* exhaustSysOverride )
  301. {
  302. const DumbProjectileBehaviorModuleData* d = getDumbProjectileBehaviorModuleData();
  303. Object* projectile = getObject();
  304. Real weaponSpeed = detWeap ? detWeap->getWeaponSpeed() : 0.0f;
  305. Real minWeaponSpeed = detWeap ? detWeap->getMinWeaponSpeed() : 0.0f;
  306. // if an object, aim at the center, not the ground part
  307. Coord3D victimPosToUse;
  308. if (victim)
  309. victim->getGeometryInfo().getCenterPosition(*victim->getPosition(), victimPosToUse);
  310. else
  311. victimPosToUse = *victimPos;
  312. if( detWeap && detWeap->isScaleWeaponSpeed() )
  313. {
  314. // Some weapons want to scale their start speed to the range
  315. Real minRange = detWeap->getMinimumAttackRange();
  316. Real maxRange = detWeap->getUnmodifiedAttackRange();
  317. Real range = sqrt(ThePartitionManager->getDistanceSquared( projectile, &victimPosToUse, FROM_CENTER_2D ) );
  318. Real rangeRatio = (range - minRange) / (maxRange - minRange);
  319. m_flightPathSpeed = (rangeRatio * (weaponSpeed - minWeaponSpeed)) + minWeaponSpeed;
  320. }
  321. else
  322. {
  323. m_flightPathSpeed = weaponSpeed;
  324. }
  325. PhysicsBehavior* physics = projectile->getPhysics();
  326. if ( d->m_tumbleRandomly && physics)
  327. {
  328. physics->setPitchRate( GameLogicRandomValueReal( -1.0f/PI, 1.0f/PI ) );
  329. physics->setYawRate( GameLogicRandomValueReal( -1.0f/PI, 1.0f/PI ) );
  330. physics->setRollRate( GameLogicRandomValueReal( -1.0f/PI, 1.0f/PI ) );
  331. }
  332. m_flightPathStart = *getObject()->getPosition();
  333. m_flightPathEnd = victimPosToUse;
  334. if (!calcFlightPath(true))
  335. {
  336. //Can only fail if wildly incorrect points
  337. TheGameLogic->destroyObject( projectile );
  338. return;
  339. }
  340. m_currentFlightPathStep = 0;// We are at the first point, because the launching put us there
  341. }
  342. //-------------------------------------------------------------------------------------------------
  343. //-------------------------------------------------------------------------------------------------
  344. Bool DumbProjectileBehavior::calcFlightPath(Bool recalcNumSegments)
  345. {
  346. const DumbProjectileBehaviorModuleData* d = getDumbProjectileBehaviorModuleData();
  347. Coord3D controlPoints[4];
  348. //First point is us, last point is them
  349. controlPoints[0] = m_flightPathStart;
  350. controlPoints[3] = m_flightPathEnd;
  351. Real highestInterveningTerrain;
  352. Bool onMap = ThePartitionManager->estimateTerrainExtremesAlongLine( controlPoints[0], controlPoints[3], NULL, &highestInterveningTerrain, NULL, NULL );
  353. if( !onMap )
  354. {
  355. return false;
  356. }
  357. // X and Y for inner points are along the line between us, so normalize and scale a vector between us, but
  358. // only use the x and y of the result
  359. Vector3 targetVector;// 0 origin vector between me and him
  360. targetVector.X = controlPoints[3].x - controlPoints[0].x;
  361. targetVector.Y = controlPoints[3].y - controlPoints[0].y;
  362. targetVector.Z = controlPoints[3].z - controlPoints[0].z;
  363. Real targetDistance = targetVector.Length();
  364. targetVector.Normalize();
  365. Vector3 firstPointAlongLine = targetVector * (targetDistance * d->m_firstPercentIndent );
  366. Vector3 secondPointAlongLine = targetVector * (targetDistance * d->m_secondPercentIndent );
  367. controlPoints[1].x = firstPointAlongLine.X + controlPoints[0].x;// add world start to offset along the origin based vector
  368. controlPoints[1].y = firstPointAlongLine.Y + controlPoints[0].y;
  369. controlPoints[2].x = secondPointAlongLine.X + controlPoints[0].x;
  370. controlPoints[2].y = secondPointAlongLine.Y + controlPoints[0].y;
  371. // Z's are determined using the highest intervening height so they won't hit hills, low end bounded by current Zs
  372. highestInterveningTerrain = max( highestInterveningTerrain, controlPoints[0].z );
  373. highestInterveningTerrain = max( highestInterveningTerrain, controlPoints[3].z );
  374. controlPoints[1].z = highestInterveningTerrain + d->m_firstHeight;
  375. controlPoints[2].z = highestInterveningTerrain + d->m_secondHeight;
  376. // With four control points, we have a curve. We will decide how many frames we want to take to get to the target,
  377. // and fill our vector with those curve points.
  378. BezierSegment flightCurve( controlPoints );
  379. if (recalcNumSegments)
  380. {
  381. Real flightDistance = flightCurve.getApproximateLength();
  382. m_flightPathSegments = ceil( flightDistance / m_flightPathSpeed );
  383. }
  384. flightCurve.getSegmentPoints( m_flightPathSegments, &m_flightPath );
  385. DEBUG_ASSERTCRASH(m_flightPathSegments == m_flightPath.size(), ("m_flightPathSegments mismatch"));
  386. #if defined(_DEBUG) || defined(_INTERNAL)
  387. if( TheGlobalData->m_debugProjectilePath )
  388. displayFlightPath();
  389. #endif
  390. return true;
  391. }
  392. //-------------------------------------------------------------------------------------------------
  393. //-------------------------------------------------------------------------------------------------
  394. Bool DumbProjectileBehavior::projectileHandleCollision( Object *other )
  395. {
  396. const DumbProjectileBehaviorModuleData* d = getDumbProjectileBehaviorModuleData();
  397. if (other != NULL)
  398. {
  399. Object *projectileLauncher = TheGameLogic->findObjectByID( projectileGetLauncherID() );
  400. // if it's not the specific thing we were targeting, see if we should incidentally collide...
  401. if (!m_detonationWeaponTmpl->shouldProjectileCollideWith(projectileLauncher, getObject(), other, m_victimID))
  402. {
  403. //DEBUG_LOG(("ignoring projectile collision with %s at frame %d\n",other->getTemplate()->getName().str(),TheGameLogic->getFrame()));
  404. return true;
  405. }
  406. if (d->m_garrisonHitKillCount > 0)
  407. {
  408. ContainModuleInterface* contain = other->getContain();
  409. if( contain && contain->getContainCount() > 0 && contain->isGarrisonable() && !contain->isImmuneToClearBuildingAttacks() )
  410. {
  411. Int numKilled = 0;
  412. // garrisonable buildings subvert the normal process here.
  413. const ContainedItemsList* items = contain->getContainedItemsList();
  414. if (items)
  415. {
  416. for (ContainedItemsList::const_iterator it = items->begin(); *it != NULL && numKilled < d->m_garrisonHitKillCount; )
  417. {
  418. Object* thingToKill = *it++;
  419. if (!thingToKill->isEffectivelyDead() && thingToKill->isKindOfMulti(d->m_garrisonHitKillKindof, d->m_garrisonHitKillKindofNot))
  420. {
  421. //DEBUG_LOG(("Killed a garrisoned unit (%08lx %s) via Flash-Bang!\n",thingToKill,thingToKill->getTemplate()->getName().str()));
  422. if (projectileLauncher)
  423. projectileLauncher->scoreTheKill( thingToKill );
  424. thingToKill->kill();
  425. ++numKilled;
  426. }
  427. } // next contained item
  428. } // if items
  429. if (numKilled > 0)
  430. {
  431. // note, fx is played at center of building, not at grenade's location
  432. FXList::doFXObj(d->m_garrisonHitKillFX, other, NULL);
  433. // don't do the normal explosion; just destroy ourselves & return
  434. TheGameLogic->destroyObject(getObject());
  435. return true;
  436. }
  437. } // if a garrisonable thing
  438. }
  439. }
  440. // collided with something... blow'd up!
  441. detonate();
  442. // mark ourself as "no collisions" (since we might still exist in slow death mode)
  443. getObject()->setStatus(OBJECT_STATUS_NO_COLLISIONS);
  444. return true;
  445. }
  446. //-------------------------------------------------------------------------------------------------
  447. void DumbProjectileBehavior::detonate()
  448. {
  449. Object* obj = getObject();
  450. if (m_detonationWeaponTmpl)
  451. {
  452. TheWeaponStore->handleProjectileDetonation(m_detonationWeaponTmpl, obj, obj->getPosition(), m_extraBonusFlags);
  453. if ( getDumbProjectileBehaviorModuleData()->m_detonateCallsKill )
  454. {
  455. // don't call kill(); do it manually, so we can specify DEATH_DETONATED
  456. DamageInfo damageInfo;
  457. damageInfo.in.m_damageType = DAMAGE_UNRESISTABLE;
  458. damageInfo.in.m_deathType = DEATH_DETONATED;
  459. damageInfo.in.m_sourceID = INVALID_ID;
  460. damageInfo.in.m_amount = obj->getBodyModule()->getMaxHealth();
  461. obj->attemptDamage( &damageInfo );
  462. }
  463. else
  464. {
  465. TheGameLogic->destroyObject( obj );
  466. }
  467. }
  468. else
  469. {
  470. // don't call kill(); do it manually, so we can specify DEATH_DETONATED
  471. DamageInfo damageInfo;
  472. damageInfo.in.m_damageType = DAMAGE_UNRESISTABLE;
  473. damageInfo.in.m_deathType = DEATH_DETONATED;
  474. damageInfo.in.m_sourceID = INVALID_ID;
  475. damageInfo.in.m_amount = obj->getBodyModule()->getMaxHealth();
  476. obj->attemptDamage( &damageInfo );
  477. }
  478. if (obj->getDrawable())
  479. obj->getDrawable()->setDrawableHidden(true);
  480. }
  481. //-------------------------------------------------------------------------------------------------
  482. /**
  483. * Simulate one frame of a missile's behavior
  484. */
  485. UpdateSleepTime DumbProjectileBehavior::update()
  486. {
  487. const DumbProjectileBehaviorModuleData* d = getDumbProjectileBehaviorModuleData();
  488. if (m_lifespanFrame != 0 && TheGameLogic->getFrame() >= m_lifespanFrame)
  489. {
  490. // lifetime demands detonation
  491. detonate();
  492. return UPDATE_SLEEP_NONE;
  493. }
  494. if( m_currentFlightPathStep >= m_flightPath.size() )
  495. {
  496. // No more steps to use. Would go out of bounds on vector, so have to do something.
  497. // We could allow physics to take over and make us fall, but the point of this whole task
  498. // is to guarentee where the shell explodes. This way, it _will_ explode at the target point.
  499. detonate();
  500. return UPDATE_SLEEP_NONE;
  501. }
  502. if (m_victimID != INVALID_ID && d->m_flightPathAdjustDistPerFrame > 0.0f)
  503. {
  504. Object* victim = TheGameLogic->findObjectByID(m_victimID);
  505. if (victim)
  506. {
  507. Coord3D newVictimPos;
  508. victim->getGeometryInfo().getCenterPosition(*victim->getPosition(), newVictimPos);
  509. Coord3D delta;
  510. delta.x = newVictimPos.x - m_flightPathEnd.x;
  511. delta.y = newVictimPos.y - m_flightPathEnd.y;
  512. delta.z = newVictimPos.z - m_flightPathEnd.z;
  513. Real distVictimMovedSqr = sqr(delta.x) + sqr(delta.y) + sqr(delta.z);
  514. if (distVictimMovedSqr > 0.1f)
  515. {
  516. Real distVictimMoved = sqrtf(distVictimMovedSqr);
  517. if (distVictimMoved > d->m_flightPathAdjustDistPerFrame)
  518. distVictimMoved = d->m_flightPathAdjustDistPerFrame;
  519. delta.normalize();
  520. m_flightPathEnd.x += distVictimMoved * delta.x;
  521. m_flightPathEnd.y += distVictimMoved * delta.y;
  522. m_flightPathEnd.z += distVictimMoved * delta.z;
  523. if (!calcFlightPath(false))
  524. {
  525. DEBUG_CRASH(("Hmm, recalc of flight path returned false... should this happen?"));
  526. detonate();
  527. return UPDATE_SLEEP_NONE;
  528. }
  529. }
  530. }
  531. }
  532. //Otherwise, continue to force the flight path
  533. Coord3D flightStep = m_flightPath[m_currentFlightPathStep];
  534. if (d->m_orientToFlightPath && (!d->m_tumbleRandomly) && m_currentFlightPathStep > 0)
  535. {
  536. // this seems reasonable; however, if this object has a PhysicsBehavior on it, this calc will be wrong,
  537. // since Physics is applying gravity, which we duly ignore, but the prevPos won't be what we expect.
  538. // get it from the flight path instead. (srj)
  539. //Coord3D prevPos = *getObject()->getPosition();
  540. Coord3D prevPos = m_flightPath[m_currentFlightPathStep - 1];
  541. Vector3 curDir(flightStep.x - prevPos.x, flightStep.y - prevPos.y, flightStep.z - prevPos.z);
  542. curDir.Normalize(); // buildTransformMatrix wants it this way
  543. Matrix3D orientMtx;
  544. orientMtx.buildTransformMatrix(Vector3(flightStep.x, flightStep.y, flightStep.z), curDir);
  545. getObject()->setTransformMatrix(&orientMtx);
  546. }
  547. else
  548. {
  549. getObject()->setPosition(&flightStep);
  550. }
  551. // note that we want to use getHighestLayerForDestination() here, so that anything even slightly
  552. // below the bridge translates into GROUND. (getLayerForDestination just does a "closest" check)
  553. PathfindLayerEnum oldLayer = getObject()->getLayer();
  554. PathfindLayerEnum newLayer = TheTerrainLogic->getHighestLayerForDestination(getObject()->getPosition());
  555. getObject()->setLayer(newLayer);
  556. if (oldLayer != LAYER_GROUND && newLayer == LAYER_GROUND)
  557. {
  558. // see if we' still in the bridge's xy area
  559. Coord3D tmp = *getObject()->getPosition();
  560. tmp.z = 9999.0f;
  561. PathfindLayerEnum testLayer = TheTerrainLogic->getHighestLayerForDestination(&tmp);
  562. if (testLayer == oldLayer)
  563. {
  564. // ensure we are slightly above the bridge, to account for fudge & sloppy art
  565. const Real FUDGE = 2.0f;
  566. tmp.z = TheTerrainLogic->getLayerHeight(tmp.x, tmp.y, testLayer) + FUDGE;
  567. getObject()->setPosition(&tmp);
  568. // blow'd up!
  569. detonate();
  570. return UPDATE_SLEEP_NONE;
  571. }
  572. }
  573. ++m_currentFlightPathStep;
  574. return UPDATE_SLEEP_NONE;//This no longer flys with physics, so it needs to not sleep
  575. }
  576. // ------------------------------------------------------------------------------------------------
  577. /** displayFlightPath for debugging */
  578. // ------------------------------------------------------------------------------------------------
  579. #if defined(_DEBUG) || defined(_INTERNAL)
  580. void DumbProjectileBehavior::displayFlightPath()
  581. {
  582. extern void addIcon(const Coord3D *pos, Real width, Int numFramesDuration, RGBColor color);
  583. for( Int pointIndex = 0; pointIndex < m_flightPath.size(); ++pointIndex )
  584. {
  585. addIcon(&m_flightPath[pointIndex], TheGlobalData->m_debugProjectileTileWidth,
  586. TheGlobalData->m_debugProjectileTileDuration,
  587. TheGlobalData->m_debugProjectileTileColor);
  588. }
  589. }
  590. #endif
  591. // ------------------------------------------------------------------------------------------------
  592. /** CRC */
  593. // ------------------------------------------------------------------------------------------------
  594. void DumbProjectileBehavior::crc( Xfer *xfer )
  595. {
  596. // extend base class
  597. UpdateModule::crc( xfer );
  598. } // end crc
  599. // ------------------------------------------------------------------------------------------------
  600. /** Xfer method
  601. * Version Info:
  602. * 1: Initial version */
  603. // ------------------------------------------------------------------------------------------------
  604. void DumbProjectileBehavior::xfer( Xfer *xfer )
  605. {
  606. // version
  607. XferVersion currentVersion = 1;
  608. XferVersion version = currentVersion;
  609. xfer->xferVersion( &version, currentVersion );
  610. // extend base class
  611. UpdateModule::xfer( xfer );
  612. // launcher
  613. xfer->xferObjectID( &m_launcherID );
  614. // victim ID
  615. xfer->xferObjectID( &m_victimID );
  616. xfer->xferInt( &m_flightPathSegments );
  617. xfer->xferReal( &m_flightPathSpeed );
  618. xfer->xferCoord3D( &m_flightPathStart );
  619. xfer->xferCoord3D( &m_flightPathEnd );
  620. // weapon template
  621. AsciiString weaponTemplateName = AsciiString::TheEmptyString;
  622. if( m_detonationWeaponTmpl )
  623. weaponTemplateName = m_detonationWeaponTmpl->getName();
  624. xfer->xferAsciiString( &weaponTemplateName );
  625. if( xfer->getXferMode() == XFER_LOAD )
  626. {
  627. if( weaponTemplateName == AsciiString::TheEmptyString )
  628. m_detonationWeaponTmpl = NULL;
  629. else
  630. {
  631. // find template
  632. m_detonationWeaponTmpl = TheWeaponStore->findWeaponTemplate( weaponTemplateName );
  633. // sanity
  634. if( m_detonationWeaponTmpl == NULL )
  635. {
  636. DEBUG_CRASH(( "DumbProjectileBehavior::xfer - Unknown weapon template '%s'\n",
  637. weaponTemplateName.str() ));
  638. throw SC_INVALID_DATA;
  639. } // end if
  640. } // end else
  641. } // end if
  642. // lifespan frame
  643. xfer->xferUnsignedInt( &m_lifespanFrame );
  644. } // end xfer
  645. // ------------------------------------------------------------------------------------------------
  646. /** Load post process */
  647. // ------------------------------------------------------------------------------------------------
  648. void DumbProjectileBehavior::loadPostProcess( void )
  649. {
  650. // extend base class
  651. UpdateModule::loadPostProcess();
  652. } // end loadPostProcess