MinefieldBehavior.cpp 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699
  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: MinefieldBehavior.cpp //////////////////////////////////////////////////////////////////
  24. // Author: Steven Johnson, June 2002
  25. // Desc: Minefield behavior
  26. ///////////////////////////////////////////////////////////////////////////////////////////////////
  27. // INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
  28. #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
  29. #define DEFINE_RELATIONSHIP_NAMES
  30. #include "Common/GameState.h"
  31. #include "Common/RandomValue.h"
  32. #include "Common/Xfer.h"
  33. #include "GameClient/Drawable.h"
  34. #include "GameClient/InGameUI.h"
  35. #include "GameLogic/GameLogic.h"
  36. #include "GameLogic/Object.h"
  37. #include "GameLogic/Module/AIUpdate.h"
  38. #include "GameLogic/Module/BodyModule.h"
  39. #include "GameLogic/Module/PhysicsUpdate.h"
  40. #include "GameLogic/Module/MinefieldBehavior.h"
  41. #include "GameLogic/Module/AutoHealBehavior.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. // detonation never puts our health below this, since we probably auto-regen
  49. const Real MIN_HEALTH = 0.1f;
  50. //-------------------------------------------------------------------------------------------------
  51. // ------------------------------------------------------------------------------------------------
  52. MinefieldBehaviorModuleData::MinefieldBehaviorModuleData()
  53. {
  54. m_detonationWeapon = NULL;
  55. m_detonatedBy = (1 << ENEMIES) | (1 << NEUTRAL);
  56. m_stopsRegenAfterCreatorDies = true;
  57. m_regenerates = false;
  58. m_workersDetonate = false;
  59. m_creatorDeathCheckRate = LOGICFRAMES_PER_SECOND;
  60. m_scootFromStartingPointTime = 0;
  61. m_repeatDetonateMoveThresh = 1.0f;
  62. m_numVirtualMines = 1;
  63. m_healthPercentToDrainPerSecond = 0.0f;
  64. }
  65. //-------------------------------------------------------------------------------------------------
  66. // ------------------------------------------------------------------------------------------------
  67. /*static*/ void MinefieldBehaviorModuleData::buildFieldParse( MultiIniFieldParse &p )
  68. {
  69. UpdateModuleData::buildFieldParse( p );
  70. static const FieldParse dataFieldParse[] =
  71. {
  72. { "DetonationWeapon", INI::parseWeaponTemplate, NULL, offsetof( MinefieldBehaviorModuleData, m_detonationWeapon ) },
  73. { "DetonatedBy", INI::parseBitString32, TheRelationshipNames, offsetof( MinefieldBehaviorModuleData, m_detonatedBy ) },
  74. { "StopsRegenAfterCreatorDies", INI::parseBool, NULL, offsetof( MinefieldBehaviorModuleData, m_stopsRegenAfterCreatorDies ) },
  75. { "Regenerates", INI::parseBool, NULL, offsetof( MinefieldBehaviorModuleData, m_regenerates ) },
  76. { "WorkersDetonate", INI::parseBool, NULL, offsetof( MinefieldBehaviorModuleData, m_workersDetonate ) },
  77. { "CreatorDeathCheckRate", INI::parseDurationUnsignedInt, NULL, offsetof( MinefieldBehaviorModuleData, m_creatorDeathCheckRate ) },
  78. { "ScootFromStartingPointTime", INI::parseDurationUnsignedInt, NULL, offsetof( MinefieldBehaviorModuleData, m_scootFromStartingPointTime ) },
  79. { "NumVirtualMines", INI::parseUnsignedInt, NULL, offsetof( MinefieldBehaviorModuleData, m_numVirtualMines ) },
  80. { "RepeatDetonateMoveThresh", INI::parseReal, NULL, offsetof( MinefieldBehaviorModuleData, m_repeatDetonateMoveThresh ) },
  81. { "DegenPercentPerSecondAfterCreatorDies", INI::parsePercentToReal, NULL, offsetof( MinefieldBehaviorModuleData, m_healthPercentToDrainPerSecond ) },
  82. { 0, 0, 0, 0 }
  83. };
  84. p.add( dataFieldParse );
  85. }
  86. ///////////////////////////////////////////////////////////////////////////////////////////////////
  87. ///////////////////////////////////////////////////////////////////////////////////////////////////
  88. ///////////////////////////////////////////////////////////////////////////////////////////////////
  89. //-------------------------------------------------------------------------------------------------
  90. //-------------------------------------------------------------------------------------------------
  91. MinefieldBehavior::MinefieldBehavior( Thing *thing, const ModuleData* moduleData )
  92. : UpdateModule( thing, moduleData )
  93. {
  94. const MinefieldBehaviorModuleData* d = getMinefieldBehaviorModuleData();
  95. m_nextDeathCheckFrame = 0;
  96. m_scootFramesLeft = 0;
  97. m_scootVel.zero();
  98. m_scootAccel.zero();
  99. m_detonators.clear();
  100. m_ignoreDamage = false;
  101. m_regenerates = d->m_regenerates;
  102. m_draining = false;
  103. m_virtualMinesRemaining = d->m_numVirtualMines;
  104. for (Int i = 0; i < MAX_IMMUNITY; ++i)
  105. {
  106. m_immunes[i].id = INVALID_ID;
  107. m_immunes[i].collideTime = 0;
  108. }
  109. // start off awake, and we will calcSleepTime from here on
  110. setWakeFrame( getObject(), UPDATE_SLEEP_NONE );
  111. // mines aren't auto-acquirable
  112. getObject()->setStatus(OBJECT_STATUS_NO_ATTACK_FROM_AI);
  113. }
  114. //-------------------------------------------------------------------------------------------------
  115. //-------------------------------------------------------------------------------------------------
  116. MinefieldBehavior::~MinefieldBehavior()
  117. {
  118. }
  119. // ------------------------------------------------------------------------------------------------
  120. // ------------------------------------------------------------------------------------------------
  121. UpdateSleepTime MinefieldBehavior::calcSleepTime()
  122. {
  123. const MinefieldBehaviorModuleData* d = getMinefieldBehaviorModuleData();
  124. // if we're draining we have to update every frame
  125. if (m_draining)
  126. return UPDATE_SLEEP_NONE;
  127. // if we're scooting we need to update every frame
  128. if( m_scootFramesLeft > 0 )
  129. return UPDATE_SLEEP_NONE;
  130. // if there is anybody in our immulity monitoring we need to update every frame
  131. for( Int i = 0; i < MAX_IMMUNITY; ++i )
  132. if( m_immunes[ i ].id != INVALID_ID )
  133. return UPDATE_SLEEP_NONE;
  134. UnsignedInt sleepTime = FOREVER;
  135. UnsignedInt now = TheGameLogic->getFrame();
  136. //
  137. // sleep until the next death check frame we already have figured outif we care
  138. // about it (that is, when our creator dies)
  139. //
  140. if (m_regenerates && d->m_stopsRegenAfterCreatorDies)
  141. sleepTime = min( sleepTime, m_nextDeathCheckFrame - now );
  142. // if we don't want to sleep forever, prevent 0 frame sleeps
  143. if( sleepTime == 0 )
  144. sleepTime = 1;
  145. // sleep forever
  146. return UPDATE_SLEEP( sleepTime );
  147. }
  148. //-------------------------------------------------------------------------------------------------
  149. //-------------------------------------------------------------------------------------------------
  150. UpdateSleepTime MinefieldBehavior::update()
  151. {
  152. Object* obj = getObject();
  153. const MinefieldBehaviorModuleData* d = getMinefieldBehaviorModuleData();
  154. UnsignedInt now = TheGameLogic->getFrame();
  155. if (m_scootFramesLeft > 0)
  156. {
  157. Coord3D pt = *obj->getPosition();
  158. m_scootVel.x += m_scootAccel.x;
  159. m_scootVel.y += m_scootAccel.y;
  160. m_scootVel.z += m_scootAccel.z;
  161. pt.x += m_scootVel.x;
  162. pt.y += m_scootVel.y;
  163. pt.z += m_scootVel.z;
  164. // srj sez: scooting mines always go on the highest layer.
  165. Coord3D tmp = pt;
  166. tmp.z = 99999.0f;
  167. PathfindLayerEnum newLayer = TheTerrainLogic->getHighestLayerForDestination(&tmp);
  168. obj->setLayer(newLayer);
  169. Real ground = TheTerrainLogic->getLayerHeight( pt.x, pt.y, newLayer );
  170. if (newLayer != LAYER_GROUND)
  171. {
  172. // ensure we are slightly above the bridge, to account for fudge & sloppy art
  173. const Real FUDGE = 1.0f;
  174. ground += FUDGE;
  175. }
  176. if (pt.z < ground || m_scootFramesLeft <= 1)
  177. pt.z = ground;
  178. obj->setPosition(&pt);
  179. --m_scootFramesLeft;
  180. }
  181. // check for expired immunities.
  182. for (Int i = 0; i < MAX_IMMUNITY; ++i)
  183. {
  184. if (m_immunes[i].id == INVALID_ID)
  185. continue;
  186. if (TheGameLogic->findObjectByID(m_immunes[i].id) == NULL ||
  187. now > m_immunes[i].collideTime + 2)
  188. {
  189. //DEBUG_LOG(("expiring an immunity %d\n",m_immunes[i].id));
  190. m_immunes[i].id = INVALID_ID; // he's dead, jim.
  191. m_immunes[i].collideTime = 0;
  192. }
  193. }
  194. if (now >= m_nextDeathCheckFrame)
  195. {
  196. // check to see if there is an enemy building on me... since enemy buildings can be build on top of me
  197. // check to see if the building that made me is gone, and whether therefore, I should go
  198. if (m_regenerates && d->m_stopsRegenAfterCreatorDies)
  199. {
  200. m_nextDeathCheckFrame = now + d->m_creatorDeathCheckRate;
  201. ObjectID producerID = getObject()->getProducerID();
  202. if (producerID != INVALID_ID)
  203. {
  204. Object* producer = TheGameLogic->findObjectByID(producerID);
  205. if (producer == NULL || producer->isEffectivelyDead())
  206. {
  207. m_regenerates = false;
  208. m_draining = true;
  209. static const NameKeyType key_AutoHealBehavior = NAMEKEY("AutoHealBehavior");
  210. AutoHealBehavior* ahb = (AutoHealBehavior*)obj->findUpdateModule( key_AutoHealBehavior );
  211. if (ahb)
  212. ahb->stopHealing();
  213. }
  214. }
  215. }
  216. }
  217. if (m_draining)
  218. {
  219. DamageInfo damageInfo;
  220. damageInfo.in.m_amount = (obj->getBodyModule()->getMaxHealth() * d->m_healthPercentToDrainPerSecond) / LOGICFRAMES_PER_SECOND;
  221. damageInfo.in.m_sourceID = obj->getID();
  222. damageInfo.in.m_damageType = DAMAGE_UNRESISTABLE;
  223. damageInfo.in.m_deathType = DEATH_NORMAL;
  224. obj->attemptDamage( &damageInfo );
  225. }
  226. return calcSleepTime();
  227. }
  228. // ------------------------------------------------------------------------------------------------
  229. //-------------------------------------------------------------------------------------------------
  230. void MinefieldBehavior::detonateOnce(const Coord3D& position)
  231. {
  232. const MinefieldBehaviorModuleData* d = getMinefieldBehaviorModuleData();
  233. if (d->m_detonationWeapon)
  234. {
  235. Object* obj = getObject();
  236. TheWeaponStore->createAndFireTempWeapon(d->m_detonationWeapon, obj, &position);
  237. }
  238. if (m_virtualMinesRemaining > 0)
  239. --m_virtualMinesRemaining;
  240. if (!m_regenerates && m_virtualMinesRemaining == 0)
  241. {
  242. TheGameLogic->destroyObject(getObject());
  243. }
  244. else
  245. {
  246. Real percent = (Real)m_virtualMinesRemaining / (Real)d->m_numVirtualMines;
  247. BodyModuleInterface* body = getObject()->getBodyModule();
  248. Real health = body->getHealth();
  249. Real desired = percent * body->getMaxHealth();
  250. if (desired < MIN_HEALTH)
  251. desired = MIN_HEALTH;
  252. Real amount = health - desired;
  253. if (amount > 0.0f)
  254. {
  255. m_ignoreDamage = true;
  256. //body->internalChangeHealth(desired - health);
  257. //can't use this, AutoHeal won't work unless we go thru normal damage stuff
  258. DamageInfo extraDamageInfo;
  259. extraDamageInfo.in.m_damageType = DAMAGE_UNRESISTABLE;
  260. extraDamageInfo.in.m_deathType = DEATH_NONE;
  261. extraDamageInfo.in.m_sourceID = getObject()->getID();
  262. extraDamageInfo.in.m_amount = amount;
  263. getObject()->attemptDamage(&extraDamageInfo);
  264. m_ignoreDamage = false;
  265. }
  266. }
  267. if (m_virtualMinesRemaining == 0)
  268. {
  269. getObject()->setModelConditionState(MODELCONDITION_RUBBLE);
  270. getObject()->setStatus(OBJECT_STATUS_MASKED);
  271. }
  272. else
  273. {
  274. getObject()->clearModelConditionState(MODELCONDITION_RUBBLE);
  275. getObject()->clearStatus(OBJECT_STATUS_MASKED);
  276. }
  277. }
  278. //-----------------------------------------------------------------------------
  279. static Real calcDistSquared(const Coord3D& a, const Coord3D& b)
  280. {
  281. return sqr(a.x - b.x) + sqr(a.y - b.y) + sqr(a.z - b.z);
  282. }
  283. // ------------------------------------------------------------------------------------------------
  284. //-------------------------------------------------------------------------------------------------
  285. void MinefieldBehavior::onCollide( Object *other, const Coord3D *loc, const Coord3D *normal )
  286. {
  287. if (other == NULL || other->isEffectivelyDead())
  288. return;
  289. if (m_virtualMinesRemaining == 0)
  290. return;
  291. Object* obj = getObject();
  292. const MinefieldBehaviorModuleData* d = getMinefieldBehaviorModuleData();
  293. UnsignedInt now = TheGameLogic->getFrame();
  294. // is this guy in our immune list?
  295. // NOTE NOTE NOTE, must always do this check FIRST so that 'collideTime' is updated...
  296. for (Int i = 0; i < MAX_IMMUNITY; ++i)
  297. {
  298. if (m_immunes[i].id == other->getID())
  299. {
  300. //DEBUG_LOG(("ignoring due to immunity %d\n",m_immunes[i].id));
  301. m_immunes[i].collideTime = now;
  302. return;
  303. }
  304. }
  305. if (!d->m_workersDetonate)
  306. {
  307. // infantry+dozer=worker.
  308. if (other->isKindOf(KINDOF_INFANTRY) && other->isKindOf(KINDOF_DOZER))
  309. return;
  310. }
  311. Int requiredMask = 0;
  312. Relationship r = obj->getRelationship(other);
  313. if (r == ALLIES) requiredMask = (1 << ALLIES);
  314. else if (r == ENEMIES) requiredMask = (1 << ENEMIES);
  315. else if (r == NEUTRAL) requiredMask = (1 << NEUTRAL);
  316. if ((d->m_detonatedBy & requiredMask) == 0)
  317. return;
  318. // are we active?
  319. if (m_scootFramesLeft > 0)
  320. return;
  321. // things that are in the process of clearing mines are immune to mine detonation,
  322. // even if we aren't the specific mine they are trying to clear. (however, they must
  323. // have a real mine they area trying to clear... it's possible they could be trying to
  324. // clear a position where there is no mine, in which case we grant them no immunity, muwahahaha)
  325. AIUpdateInterface* otherAI = other->getAI();
  326. if (otherAI && otherAI->isClearingMines() && otherAI->getGoalObject() != NULL)
  327. {
  328. // mine-clearers are granted immunity to us for as long as they continuously
  329. // collide, even if no longer clearing mines. (this prevents the problem
  330. // of a guy who touches two close-together mines while clearing, then puts up his
  331. // detector and is blown to smithereens by the other one.)
  332. for (Int i = 0; i < MAX_IMMUNITY; ++i)
  333. {
  334. if (m_immunes[i].id == INVALID_ID || m_immunes[i].id == other->getID())
  335. {
  336. //DEBUG_LOG(("add/update immunity %d\n",m_immunes[i].id));
  337. m_immunes[i].id = other->getID();
  338. m_immunes[i].collideTime = now;
  339. // wake up
  340. setWakeFrame( obj, calcSleepTime() );
  341. break;
  342. }
  343. }
  344. return;
  345. }
  346. // if we detonated another one nearby, we have to move a little bit to detonate another one.
  347. Bool found = false;
  348. for (std::vector<DetonatorInfo>::iterator it = m_detonators.begin(); it != m_detonators.end(); ++it)
  349. {
  350. if (other->getID() == it->id)
  351. {
  352. found = TRUE;
  353. Real distSqr = calcDistSquared(*other->getPosition(), it->where);
  354. if (distSqr <= sqr(d->m_repeatDetonateMoveThresh))
  355. {
  356. // too close. punt for now.
  357. return;
  358. }
  359. else
  360. {
  361. // far enough. update the loc, then break out and blow up.
  362. it->where = *other->getPosition();
  363. break;
  364. }
  365. }
  366. }
  367. if (!found)
  368. {
  369. // add him to the list.
  370. DetonatorInfo detInfo;
  371. detInfo.id = other->getID();
  372. detInfo.where = *other->getPosition();
  373. m_detonators.push_back(detInfo);
  374. }
  375. Coord3D detPt = *other->getPosition();
  376. obj->getGeometryInfo().clipPointToFootprint(*obj->getPosition(), detPt);
  377. detonateOnce(detPt);
  378. }
  379. // ------------------------------------------------------------------------------------------------
  380. //-------------------------------------------------------------------------------------------------
  381. void MinefieldBehavior::onDamage( DamageInfo *damageInfo )
  382. {
  383. if (m_ignoreDamage)
  384. return;
  385. const MinefieldBehaviorModuleData* d = getMinefieldBehaviorModuleData();
  386. // detonate as many times as neccessary for our virtual mine count to match our health
  387. BodyModuleInterface* body = getObject()->getBodyModule();
  388. for (;;)
  389. {
  390. Real virtualMinesExpectedF = ((Real)d->m_numVirtualMines * body->getHealth() / body->getMaxHealth());
  391. Int virtualMinesExpected =
  392. damageInfo->in.m_damageType == DAMAGE_HEALING ?
  393. REAL_TO_INT_FLOOR(virtualMinesExpectedF) :
  394. REAL_TO_INT_CEIL(virtualMinesExpectedF);
  395. if (virtualMinesExpected > d->m_numVirtualMines)
  396. virtualMinesExpected = d->m_numVirtualMines;
  397. if (m_virtualMinesRemaining < virtualMinesExpected)
  398. {
  399. m_virtualMinesRemaining = virtualMinesExpected;
  400. }
  401. else if (m_virtualMinesRemaining > virtualMinesExpected)
  402. {
  403. if (m_draining &&
  404. damageInfo->in.m_sourceID == getObject()->getID() &&
  405. damageInfo->in.m_damageType == DAMAGE_UNRESISTABLE)
  406. {
  407. // don't detonate.... just ditch a mine
  408. --m_virtualMinesRemaining;
  409. }
  410. else
  411. {
  412. detonateOnce(*getObject()->getPosition());
  413. }
  414. }
  415. else
  416. {
  417. break;
  418. }
  419. }
  420. if (m_virtualMinesRemaining == 0)
  421. {
  422. // oops, if someone did weapon damage they may have nuked our health to zero,
  423. // which would be bad if we regen. prevent this. (srj)
  424. if (m_regenerates && body->getHealth() < MIN_HEALTH)
  425. {
  426. body->internalChangeHealth(MIN_HEALTH - body->getHealth());
  427. }
  428. getObject()->setModelConditionState(MODELCONDITION_RUBBLE);
  429. getObject()->setStatus(OBJECT_STATUS_MASKED);
  430. }
  431. else
  432. {
  433. getObject()->clearModelConditionState(MODELCONDITION_RUBBLE);
  434. getObject()->clearStatus(OBJECT_STATUS_MASKED);
  435. }
  436. }
  437. // ------------------------------------------------------------------------------------------------
  438. //-------------------------------------------------------------------------------------------------
  439. void MinefieldBehavior::onHealing( DamageInfo *damageInfo )
  440. {
  441. onDamage(damageInfo);
  442. }
  443. // ------------------------------------------------------------------------------------------------
  444. //-------------------------------------------------------------------------------------------------
  445. void MinefieldBehavior::onDie( const DamageInfo *damageInfo )
  446. {
  447. TheGameLogic->destroyObject(getObject());
  448. }
  449. // ------------------------------------------------------------------------------------------------
  450. //-------------------------------------------------------------------------------------------------
  451. void MinefieldBehavior::disarm()
  452. {
  453. if (!m_regenerates)
  454. {
  455. TheGameLogic->destroyObject(getObject());
  456. return;
  457. }
  458. // detonation never puts our health below this, since we probably auto-regen
  459. const Real MIN_HEALTH = 0.1f;
  460. BodyModuleInterface* body = getObject()->getBodyModule();
  461. Real desired = MIN_HEALTH;
  462. Real amount = body->getHealth() - desired;
  463. m_ignoreDamage = true;
  464. //body->internalChangeHealth(desired - health);
  465. //can't use this, AutoHeal won't work unless we go thru normal damage stuff
  466. DamageInfo extraDamageInfo;
  467. extraDamageInfo.in.m_damageType = DAMAGE_UNRESISTABLE;
  468. extraDamageInfo.in.m_deathType = DEATH_NONE;
  469. extraDamageInfo.in.m_sourceID = getObject()->getID();
  470. extraDamageInfo.in.m_amount = amount;
  471. getObject()->attemptDamage(&extraDamageInfo);
  472. m_ignoreDamage = false;
  473. m_virtualMinesRemaining = 0;
  474. getObject()->setModelConditionState(MODELCONDITION_RUBBLE);
  475. getObject()->setStatus(OBJECT_STATUS_MASKED);
  476. }
  477. // ------------------------------------------------------------------------------------------------
  478. //-------------------------------------------------------------------------------------------------
  479. void MinefieldBehavior::setScootParms(const Coord3D& start, const Coord3D& end)
  480. {
  481. Object* obj = getObject();
  482. const MinefieldBehaviorModuleData* d = getMinefieldBehaviorModuleData();
  483. UnsignedInt scootFromStartingPointTime = d->m_scootFromStartingPointTime;
  484. Coord3D endOnGround = end;
  485. endOnGround.z = TheTerrainLogic->getGroundHeight( endOnGround.x, endOnGround.y );
  486. if (start.z > endOnGround.z)
  487. {
  488. // figure out how long it will take to fall, and replace scoot time with that
  489. UnsignedInt fallingTime = REAL_TO_INT_CEIL(sqrtf(2.0f * (start.z - endOnGround.z) / fabs(TheGlobalData->m_gravity)));
  490. // we can scoot after we land, but don't want to stop scooting before we land
  491. if (scootFromStartingPointTime < fallingTime)
  492. scootFromStartingPointTime = fallingTime;
  493. }
  494. if (scootFromStartingPointTime == 0)
  495. {
  496. obj->setPosition(&endOnGround);
  497. m_scootFramesLeft = 0;
  498. }
  499. else
  500. {
  501. // x = x0 + vt + 0.5at^2
  502. // thus 2(dx - vt)/t^2 = a
  503. Real dx = endOnGround.x - start.x;
  504. Real dy = endOnGround.y - start.y;
  505. Real dz = endOnGround.z - start.z;
  506. Real dist = sqrt(sqr(dx) + sqr(dy));
  507. if (dist <= 0.1f && fabs(dz) <= 0.1f)
  508. {
  509. obj->setPosition(&endOnGround);
  510. m_scootFramesLeft = 0;
  511. }
  512. else
  513. {
  514. Real t = (Real)scootFromStartingPointTime;
  515. Real scootFromStartingPointSpeed = dist / t;
  516. Real accelMag = fabs(2.0f * (dist - scootFromStartingPointSpeed*t)/sqr(t));
  517. Real dxNorm = (dist <= 0.1f) ? 0.0f : (dx / dist);
  518. Real dyNorm = (dist <= 0.1f) ? 0.0f : (dy / dist);
  519. m_scootVel.x = dxNorm * scootFromStartingPointSpeed;
  520. m_scootVel.y = dyNorm * scootFromStartingPointSpeed;
  521. m_scootAccel.x = -dxNorm * accelMag;
  522. m_scootAccel.y = -dyNorm * accelMag;
  523. m_scootAccel.z = TheGlobalData->m_gravity;
  524. obj->setPosition(&start);
  525. m_scootFramesLeft = scootFromStartingPointTime;
  526. // we need to wake ourselves up because we could be lying here sleeping forever
  527. setWakeFrame( obj, calcSleepTime() );
  528. }
  529. }
  530. }
  531. // ------------------------------------------------------------------------------------------------
  532. /** CRC */
  533. // ------------------------------------------------------------------------------------------------
  534. void MinefieldBehavior::crc( Xfer *xfer )
  535. {
  536. // extend base class
  537. UpdateModule::crc( xfer );
  538. } // end crc
  539. // ------------------------------------------------------------------------------------------------
  540. /** Xfer method
  541. * Version Info:
  542. * 1: Initial version */
  543. // ------------------------------------------------------------------------------------------------
  544. void MinefieldBehavior::xfer( Xfer *xfer )
  545. {
  546. // version
  547. XferVersion currentVersion = 1;
  548. XferVersion version = currentVersion;
  549. xfer->xferVersion( &version, currentVersion );
  550. // extend base class
  551. UpdateModule::xfer( xfer );
  552. // mines remaining
  553. /// @todo srj -- ensure health, appearance, etc are correct for save/reload! post-MP!
  554. xfer->xferUnsignedInt( &m_virtualMinesRemaining );
  555. // next death check frame
  556. xfer->xferUnsignedInt( &m_nextDeathCheckFrame );
  557. // scoot frames left
  558. xfer->xferUnsignedInt( &m_scootFramesLeft );
  559. // scoot velocity
  560. xfer->xferCoord3D( &m_scootVel );
  561. // scoot acceleration
  562. xfer->xferCoord3D( &m_scootAccel );
  563. xfer->xferBool( &m_ignoreDamage );
  564. xfer->xferBool( &m_regenerates );
  565. xfer->xferBool( &m_draining );
  566. // immunities
  567. UnsignedByte maxImmunity = MAX_IMMUNITY;
  568. xfer->xferUnsignedByte( &maxImmunity );
  569. if( maxImmunity != MAX_IMMUNITY )
  570. {
  571. DEBUG_CRASH(( "MinefieldBehavior::xfer - MAX_IMMUNITY has changed size, you must version this code and then you can remove this error message\n" ));
  572. throw SC_INVALID_DATA;
  573. } // end if
  574. for( UnsignedByte i = 0; i < maxImmunity; ++i )
  575. {
  576. // object id
  577. xfer->xferObjectID( &m_immunes[ i ].id );
  578. // collide time
  579. xfer->xferUnsignedInt( &m_immunes[ i ].collideTime );
  580. } // end for, i
  581. if( xfer->getXferMode() == XFER_LOAD )
  582. m_detonators.clear();
  583. } // end xfer
  584. // ------------------------------------------------------------------------------------------------
  585. /** Load post process */
  586. // ------------------------------------------------------------------------------------------------
  587. void MinefieldBehavior::loadPostProcess( void )
  588. {
  589. // extend base class
  590. UpdateModule::loadPostProcess();
  591. } // end loadPostProcess