MinefieldBehavior.cpp 24 KB

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