DumbProjectileBehavior.cpp 29 KB

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