missileigc.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505
  1. /*
  2. ** Copyright (C) 1996, 1997 Microsoft Corporation. All Rights Reserved.
  3. **
  4. ** File: missileIGC.cpp
  5. **
  6. ** Author:
  7. **
  8. ** Description:
  9. ** Implementation of the CmissileIGC class. This file was initially created by
  10. ** the ATL wizard for the core object.
  11. **
  12. ** History:
  13. */
  14. // missileIGC.cpp : Implementation of CmissileIGC
  15. #include "pch.h"
  16. #include "missileIGC.h"
  17. /////////////////////////////////////////////////////////////////////////////
  18. // CmissileIGC
  19. CmissileIGC::CmissileIGC(void)
  20. :
  21. m_launcher(NULL),
  22. m_target(NULL),
  23. m_missileType(NULL)
  24. {
  25. }
  26. CmissileIGC::~CmissileIGC(void)
  27. {
  28. }
  29. HRESULT CmissileIGC::Initialize(ImissionIGC* pMission, Time now, const void* data, int dataSize)
  30. {
  31. TmodelIGC<ImissileIGC>::Initialize(pMission, now, data, dataSize);
  32. ZRetailAssert (data && (dataSize == sizeof(DataMissileIGC)));
  33. {
  34. DataMissileIGC* dataMissile = (DataMissileIGC*)data;
  35. m_missileType = dataMissile->pmissiletype;
  36. assert (m_missileType);
  37. assert (m_missileType->GetObjectType() == OT_missileType);
  38. m_missileType->AddRef();
  39. DataMissileTypeIGC* dataMissileType = (DataMissileTypeIGC*)(m_missileType->GetData());
  40. assert (dataMissileType);
  41. //Load the model for the missile
  42. HRESULT hr;
  43. if (iswalpha(dataMissileType->modelName[0])) {
  44. hr = Load(0, dataMissileType->modelName, dataMissileType->textureName, dataMissileType->iconName,
  45. c_mtDamagable | c_mtHitable | c_mtCastRay | c_mtSeenBySide);
  46. GetThingSite()->SetAfterburnerSmokeSize ( 0.25f + (GetRadius () * 0.1f));
  47. GetThingSite()->SetAfterburnerFireDuration (0.33f);
  48. GetThingSite()->SetAfterburnerSmokeDuration (2.0f);
  49. }
  50. else
  51. {
  52. // , should pass in blend color from database
  53. hr = LoadDecal(dataMissileType->textureName, dataMissileType->iconName,
  54. Color((float)dataMissileType->color.r,
  55. (float)dataMissileType->color.g,
  56. (float)dataMissileType->color.b,
  57. (float)dataMissileType->color.a),
  58. dataMissileType->bDirectional,
  59. dataMissileType->width,
  60. c_mtDamagable | c_mtHitable | c_mtCastRay | c_mtSeenBySide,
  61. c_htsSphere);
  62. }
  63. assert (SUCCEEDED(hr));
  64. SetRadius(dataMissileType->radius);
  65. SetSignature(dataMissileType->signature);
  66. m_fraction = 1.0f;
  67. SetPosition(dataMissile->position);
  68. SetVelocity(dataMissile->velocity);
  69. {
  70. Orientation o(dataMissile->forward);
  71. SetOrientation(o);
  72. }
  73. {
  74. Rotation r(dataMissile->forward, dataMissileType->rotation);
  75. SetRotation(r);
  76. }
  77. m_launcher = dataMissile->pLauncher;
  78. assert (m_launcher);
  79. m_launcher->AddRef();
  80. {
  81. HitTest* ht = GetHitTest();
  82. //lifespan == 0 => immortal missile that can hit until it gets terminated on the next update; this is bad
  83. assert (dataMissileType->lifespan > 0.0f);
  84. m_timeExpire = now + dataMissileType->lifespan;
  85. assert (m_timeExpire != now);
  86. //Can't hit your launcher
  87. ht->SetNoHit(m_launcher->GetHitTest());
  88. SetSide(m_launcher->GetSide());
  89. }
  90. m_launcher->SetLastMissileFired(this);
  91. m_timeActivate = now + dataMissileType->readyTime;
  92. SetCluster(dataMissile->pCluster);
  93. if (dataMissile->pTarget && (dataMissile->pTarget->GetCluster() == dataMissile->pCluster))
  94. {
  95. m_target = dataMissile->pTarget;
  96. m_target->AddRef();
  97. //Estimate the time of impact with the target
  98. m_tImpact = dataMissileType->readyTime +
  99. (float)sqrt((2.0f / dataMissileType->acceleration) *
  100. (m_target->GetPosition() -
  101. dataMissile->position +
  102. dataMissileType->readyTime * (m_target->GetVelocity() - dataMissile->velocity)).Length());
  103. }
  104. m_lock = dataMissile->lock;
  105. m_missileID = dataMissile->missileID;
  106. m_bDisarmed = dataMissile->bDud;
  107. SetMass(0.0f);
  108. }
  109. return S_OK;
  110. }
  111. void CmissileIGC::Terminate(void)
  112. {
  113. AddRef();
  114. TmodelIGC<ImissileIGC>::Terminate();
  115. if (m_launcher)
  116. {
  117. if (m_launcher->GetLastMissileFired() == this)
  118. m_launcher->SetLastMissileFired(NULL);
  119. m_launcher->Release();
  120. m_launcher = NULL;
  121. }
  122. if (m_target)
  123. {
  124. m_target->Release();
  125. m_target = NULL;
  126. }
  127. if (m_missileType)
  128. {
  129. m_missileType->Release();
  130. m_missileType = NULL;
  131. }
  132. Release();
  133. }
  134. double NewtonA(double a, double b, double c, double d, double tOld)
  135. {
  136. //Solve:
  137. // f(t) = a + bt + c t^2 - d t^4 = 0
  138. // f'(t) = b + 2c t - 4d t^3
  139. double t0;
  140. double t1 = tOld;
  141. //NYI not that we should need it ...
  142. int nCycles = 0;
  143. do
  144. {
  145. t0 = t1;
  146. double p2 = t0 * t0; //t0^2
  147. double p3 = p2 * t0; //t0^3
  148. double p4 = p3 * t0; //t0^4
  149. double f = a + b * t0 + c * p2 - d * p4;
  150. if (fabs(f) < 0.1)
  151. break; //close enough
  152. double dfdt = b + 2.0 * c * t0 - 4.0 * d * p3;
  153. double delta = f / dfdt;
  154. t1 = t0 - delta;
  155. }
  156. while (++nCycles < 20);
  157. if (nCycles >= 20)
  158. {
  159. debugf("**** Timeout NewtonA(%f %f %f %f %f) %f %f\n", a, b, c, d, tOld, t0, t1);
  160. }
  161. return t0;
  162. }
  163. double NewtonB(double a, double b, double c, double d, double tOld, double ti)
  164. {
  165. //Solve:
  166. // f(t) = a + bt + c t^2 - d t^4 = 0
  167. // f'(t) = b + 2c t - 4d t^3
  168. //
  169. // but, as a special case, quit if dfdt ever >= 0.0
  170. double t0;
  171. double t1 = tOld;
  172. //NYI not that we should need it ...
  173. int nCycles = 0;
  174. do
  175. {
  176. t0 = t1;
  177. double p2 = t0 * t0; //t0^2
  178. double p3 = p2 * t0; //t0^3
  179. double p4 = p3 * t0; //t0^4
  180. double f = a + b * t0 + c * p2 - d * p4;
  181. if (fabs(f) < 0.1)
  182. break; //close enough
  183. double dfdt = b + 2.0 * c * t0 - 4.0 * d * p3;
  184. if (dfdt >= 0.0)
  185. {
  186. //debugf("**** Aborted NewtonB(%f %f %f %f %f) %f %f %f\n", a, b, c, d, dfdt, tOld, t0, t1);
  187. return -1.0;
  188. }
  189. double delta = f / dfdt;
  190. t1 = t0 - delta;
  191. //This is not strictly required (if t is past the inflection point and the slope is negative,
  192. //then there isn't any root before the inflection point to find) ... but it is better to start
  193. //with a more accurate initial guess for t when searching for a root past the inflection point.
  194. if (t1 > ti)
  195. {
  196. //debugf("**** Aborted NewtonB(%f %f %f %f %f) %f %f %f\n", a, b, c, d, dfdt, tOld, t0, t1);
  197. return -1.0;
  198. }
  199. }
  200. while (++nCycles < 20);
  201. if (nCycles >= 20)
  202. {
  203. debugf("**** Timeout NewtonB(%f %f %f %f %f) %f %f\n", a, b, c, d, tOld, t0, t1);
  204. }
  205. return t0;
  206. }
  207. static float solve(const Vector& dP,
  208. const Vector& dV,
  209. float acceleration,
  210. float tOld)
  211. {
  212. assert (acceleration > 0.0f);
  213. //General problem when will the missile hit the target
  214. // Solve |dP + t * dV| = 1/2 acceleration t^2
  215. // F1(t) = dP * dP + 2 * t * dP * dV + t^2 * dV * dV = a + b t + c t^2
  216. // F2(t) = 0.25 acceleration^2 * t^4 = d t^4
  217. //Solve for t such that F1(t) == F2(t)
  218. double a = dP * dP;
  219. double c = dV * dV;
  220. double d = acceleration * acceleration / 4.0;
  221. if (c == 0.0)
  222. {
  223. //Let's eliminate one special case upfront: c = 0 implies a non-moving target (or no lock)
  224. //Either way ... it makes solving the problem easy
  225. // c = 0 => b = 0
  226. // a = d t^4
  227. // t = (a/d)^(1/4)
  228. return float(pow(a/d, 0.25));
  229. }
  230. double b = 2.0 * (dP * dV);
  231. //F(t) = F1(t) - F2(t) = a + b t + c t^2 - d t^4
  232. //F'(t) = b + 2c t - 4d t^3
  233. //F''(t) = 2c - 12 d t^2
  234. //First ... find the inflection points ... F''(t) = 0
  235. // since c, d > 0, there is exactly one > 0.0f
  236. double ti = sqrt(c / (6.0 * d));
  237. //What is the slope at the inflection point?
  238. double dftp = b + (4.0/3.0) * c * ti; //Simplifying a little F'(ti) == b + 2c ti - 4d ti^3
  239. const double epsilon = -0.001;
  240. if (dftp <= epsilon)
  241. {
  242. //OK ... easy case: F'(t) at its local maximum for t > 0 is < 0
  243. //Therefore F'(t) < 0 for all t > 0 and Newton's works very nicely ... use it
  244. return float(NewtonA(a, b, c, d, tOld));
  245. }
  246. if (b < 0.0)
  247. {
  248. //OK ... hard case #1 ... F'(0) = b < 0 & F'(ti) > epsilon
  249. //so there is a local minima in [0, ti] ... which means that there might be a solution
  250. //in [0, tmn] where F'(tmn) = 0
  251. //Unfortunately, tmn is not trivial to find, so we fake it:
  252. //Pick t in [0, ti] such that F'(t) < 0
  253. double t = tOld; //Try our initial guess at a solution
  254. if ((t < ti) && (b + 2.0 * c * t - 4.0 * d * t * t * t > epsilon))
  255. t = 0.0f; //Didn't work: fall back to 0 (which is known to be good)
  256. //Use Newton's with a twist ... if F'(t) >= 0.0 then we can give up (because, barring round-off,
  257. //we'll never overshoot a F(t) = 0 since F''(t) > 0)
  258. t = NewtonB(a, b, c, d, t, ti);
  259. if (t >= 0.0f)
  260. return float(t);
  261. }
  262. //OK ... hard case #2 ... F'(ti) > epsilon
  263. // so there is a local maxima in [ti, infinity] ... which means there might be a solution
  264. // in [ti, tmx] and there will be one in [tmx, infinity], where F'(tmx) = 0
  265. //
  266. // Except that, since we got here, we are reasonably sure that the there is no solution in [ti, tmx]
  267. // (since, if so, there would also be another solution in [0, tmn] & we didn't find it above)
  268. //
  269. // So ... try to find a solution starting at t, where F'(t) < epsilon
  270. //Start past the inflection point
  271. double t = tOld;
  272. if (t < ti)
  273. t = ti + 1.0;
  274. //Search for the a time with a sufficiently negative slope
  275. while (b + 2.0 * c * t - 4.0 * d * t * t * t > epsilon)
  276. t += 1.0; //Keep incrementing till we get it right
  277. //OK ... we found a good starting value.
  278. // Because F''(t) < 0 for all t > ti, we don't have to worry about local maxima
  279. return float(NewtonA(a, b, c, d, t));
  280. }
  281. void CmissileIGC::Update(Time now)
  282. {
  283. if (GetMyLastUpdate() >= m_timeExpire)
  284. {
  285. Explode(GetPosition());
  286. Terminate();
  287. }
  288. else if (now >= GetMyLastUpdate())
  289. {
  290. if (now > m_timeActivate)
  291. {
  292. //Actively track the target
  293. Orientation o = GetOrientation();
  294. float acceleration = m_missileType->GetAcceleration();
  295. float dt = now - ((m_timeActivate > GetMyLastUpdate()) ? m_timeActivate : GetMyLastUpdate());
  296. if (m_target && (m_target->GetCluster() != GetCluster()))
  297. {
  298. m_target->Release();
  299. m_target = NULL; //lost our target
  300. }
  301. const Vector& myPosition = GetPosition();
  302. const Vector& myVelocity = GetVelocity();
  303. if (m_target)
  304. {
  305. //Where will our target be when we get there ... try several passes
  306. //adjusting our speed (to account for velocity).
  307. Vector dP = m_target->GetPosition() - myPosition;
  308. Vector dV = m_target->GetVelocity() * m_lock - myVelocity;
  309. m_tImpact = solve(dP, dV, acceleration, m_tImpact);
  310. o.TurnTo(dP + m_tImpact * dV, m_missileType->GetTurnRate() * dt * GetSide()->GetGlobalAttributeSet().GetAttribute(c_gaTurnRateMissile));
  311. SetOrientation(o);
  312. }
  313. const Vector& backward = o.GetBackward();
  314. //Kick in the primary thrusters
  315. Vector deltaV = backward * (acceleration * dt);
  316. SetVelocity(myVelocity - deltaV);
  317. if (m_timeActivate > GetMyLastUpdate())
  318. {
  319. //The missile activated partway through the update cycle. Fudge things (by a teleport)
  320. //so that the missile ends up at the position it would have ended up if the activate had
  321. //happened exactly at an update boundary
  322. SetPosition(myPosition - deltaV * (m_timeActivate - GetMyLastUpdate()));
  323. }
  324. m_tImpact -= dt;
  325. {
  326. Rotation r = GetRotation();
  327. r.axis(o.GetBackward());
  328. SetRotation(r);
  329. }
  330. assert (GetThingSite());
  331. GetThingSite()->SetAfterburnerThrust(backward, 1.0f);
  332. }
  333. TmodelIGC<ImissileIGC>::Update(now);
  334. }
  335. }
  336. void CmissileIGC::HandleCollision(Time timeCollision,
  337. float tCollision,
  338. const CollisionEntry& entry,
  339. ImodelIGC* pModel)
  340. {
  341. if (!m_bDisarmed)
  342. {
  343. ObjectType ot = pModel->GetObjectType();
  344. if (ot == OT_mine)
  345. return; //Ignore collisions with minefields
  346. IIgcSite* pigc = GetMyMission()->GetIgcSite();
  347. Vector position1;
  348. DamageTypeID dmgid = m_missileType->GetDamageType();
  349. {
  350. //A missile hit something
  351. position1 = GetPosition() + GetVelocity() * tCollision;
  352. Vector position2 = pModel->GetPosition() + pModel->GetVelocity() * tCollision;
  353. if (ot != OT_missile)
  354. {
  355. ExpendableAbilityBitMask eabm = m_missileType->GetCapabilities();
  356. if (eabm & c_eabmWarpBomb)
  357. {
  358. //Hack so that the client will not show an explosion
  359. position1 = Vector::GetZero();
  360. if (ot == OT_warp)
  361. pigc->WarpBombEvent((IwarpIGC*)pModel, this);
  362. }
  363. else if (pModel->GetAttributes() & c_mtDamagable)
  364. {
  365. //The target can take damage
  366. IdamageIGC* pDamage = (IdamageIGC*)pModel;
  367. pDamage->ReceiveDamage(dmgid,
  368. m_missileType->GetPower() * GetSide()->GetGlobalAttributeSet().GetAttribute(c_gaDamageMissiles),
  369. timeCollision,
  370. position2,
  371. position1,
  372. m_launcher);
  373. if (eabm & c_eabmCapture)
  374. {
  375. //Hack so that the client will not show an explosion
  376. position1 = Vector::GetZero();
  377. if ((ot == OT_station) && m_launcher)
  378. {
  379. IstationIGC* pstation = (IstationIGC*)pModel;
  380. if ((!GetMyMission()->GetMissionParams()->bInvulnerableStations) &&
  381. (!pstation->GetStationType()->HasCapability(c_sabmPedestal)) &&
  382. (pstation->GetShieldFraction() < GetMyMission()->GetFloatConstant(c_fcidDownedShield)))
  383. {
  384. pigc->CaptureStationEvent(m_launcher, pstation);
  385. }
  386. }
  387. }
  388. }
  389. }
  390. else
  391. {
  392. //Missiles hitting missiles are a special case: both die without calling either's receive damage method.
  393. //Create explosions for both missiles
  394. pigc->KillMissileEvent(((ImissileIGC*)pModel), position2);
  395. }
  396. }
  397. pigc->KillMissileEvent(this, position1);
  398. }
  399. }
  400. void CmissileIGC::Explode(const Vector& position)
  401. {
  402. //Boom
  403. float p = m_missileType->GetBlastPower();
  404. if (p != 0.0f)
  405. {
  406. GetCluster()->CreateExplosion(m_missileType->GetDamageType(),
  407. p * GetSide()->GetGlobalAttributeSet().GetAttribute(c_gaDamageMissiles),
  408. m_missileType->GetBlastRadius(),
  409. c_etMissile,
  410. GetMyLastUpdate(),
  411. position,
  412. m_launcher);
  413. }
  414. else
  415. GetCluster()->GetClusterSite()->AddExplosion(position, GetRadius(), c_etMissile);
  416. }